Skip to content

feat: Finalize OCX v2 clean break#119

Open
kdcokenny wants to merge 103 commits intomainfrom
v2-clean-break
Open

feat: Finalize OCX v2 clean break#119
kdcokenny wants to merge 103 commits intomainfrom
v2-clean-break

Conversation

@kdcokenny
Copy link
Owner

@kdcokenny kdcokenny commented Jan 31, 2026

Summary

This PR finalizes the OCX v2 architecture with several breaking improvements:

  • V2 registry protocol: Root-relative component targets (no more /src prefix guessing)
  • Receipt-based install tracking: Replaced ocx.lock with .opencode/receipts/ for granular component metadata
  • Profile layering improvements: Local-first writes with proper config isolation
  • Ghost CLI removal: Removed deprecated ghost migration tooling
  • New commands: Added remove and verify commands for component management

Test Results

All tests passing:

  • 734 tests pass
  • ⏭️ 11 tests skipped
  • 0 tests failed
  • Lint check passed (Biome)
  • Type check passed (TypeScript strict mode)

Breaking Changes

  • Registry manifests must now use v2 format with targets array containing root-relative paths
  • ocx.lock is replaced by .opencode/receipts/*.json for installed components
  • Ghost CLI commands removed (ocx ghost migrate)

Documentation

  • Added V2_RECEIPT_IMPLEMENTATION.md with full receipt system design
  • Updated all docs to reflect v2 changes
  • Added receipt JSON schema

Summary by cubic

Finalizes OCX v2 with alias‑first registries, receipt‑based installs (.ocx/receipt.jsonc v1), global‑only layered profiles, registry protocol v2 (root‑relative targets, normalized types), a unified dry‑run/JSON/exit‑code contract, hardened blocklist path validation with local .opencode containment, and a Mintlify docs site at /docs. Adds ocx migrate for v1.4.6 → v2 with --global fan‑out (root + all profiles), per‑target summaries, and preview_with_errors/partial_failure statuses.

  • New Features

    • Registries & installs: require --name; alias/component refs; ephemeral --from; hash‑based canonical IDs; remove version pinning; receipt v1 at .ocx/receipt.jsonc.
    • Profiles & CLI: global‑only profiles with layered merge (objects deep‑merge; arrays replace; plugins/instructions concat+dedupe); “first type wins” instruction discovery with local AGENTS.md priority; standardized --dry-run across commands with shared JSON schema; exit codes INTEGRITY=65, NOT_FOUND=66; minimal short‑flag policy; removed diff; normalized types/paths (skill/plugin/agent/tool/command/bundle/profile, plural dirs).
    • Docs: Mintlify site at /docs with redirects and full CLI/reference; examples and schemas updated to v2; ocx migrate supports direct URL profile installs and alias‑flexible resolution.
  • Bug Fixes

    • Path safety: switch to blocklist validation with strict containment; restore local .opencode installs and keep profile/global installs flattened; clearer path/integrity errors.
    • Resolution & output: profile add uses registries declared in the profile; flexible aliasing and URL installs; strict JSON‑mode output (spinners off, deterministic); correct NOT_FOUND (66); fix remove integrity check; improved error handling and path resolution; CI cache keys use bun.lock.

Written for commit f65a4c5. Summary will update on new commits.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 31, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
ocx f65a4c5 Feb 16 2026, 09:29 PM

@codecov
Copy link

codecov bot commented Jan 31, 2026

Codecov Report

❌ Patch coverage is 12.42938% with 930 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.37%. Comparing base (ed74da6) to head (f65a4c5).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/cli/src/commands/add.ts 2.11% 416 Missing ⚠️
packages/cli/src/commands/profile/add.ts 5.64% 234 Missing ⚠️
packages/cli/src/commands/self/uninstall.ts 24.88% 169 Missing ⚠️
.../cli/src/commands/profile/install-from-registry.ts 9.52% 57 Missing ⚠️
packages/cli/src/commands/self/update.ts 54.71% 24 Missing ⚠️
packages/cli/src/commands/init.ts 5.55% 17 Missing ⚠️
packages/cli/src/commands/opencode.ts 56.66% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #119      +/-   ##
==========================================
+ Coverage   48.34%   49.37%   +1.03%     
==========================================
  Files          47       55       +8     
  Lines        4944     7305    +2361     
==========================================
+ Hits         2390     3607    +1217     
- Misses       2554     3698    +1144     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kdcokenny kdcokenny changed the title feat: finalize OCX v2 clean break feat: Finalize OCX v2 clean break Jan 31, 2026
- Add getLayered() method to ProfileManager for merging global and local profiles
- Update ConfigResolver to use layered profile loading
- Implement local profile AGENTS.md discovery (highest priority)
- Add 19 unit tests for profile layering behavior

Merge behavior (matching OpenCode):
- Objects (registries): deep merge, local adds to global
- Arrays (exclude/include): local replaces global
- Special arrays (plugin/instructions): concatenate + dedupe
- Scalars: local wins

Documentation updates:
- Clarify --global flag usage in all profile examples
- Add 'Local vs Global Profiles' section to PROFILES.md
- Update configuration merging documentation
- Add testing philosophy section to MANUAL_TESTING.md
- Add profile layering test cases
- Remove @Version parsing from update command (never worked)
- Remove dead version field from registry config schema
- Remove version pinning sections from all documentation
- Fix component names to use real registry components:
  - kdco/agents → kdco/researcher
  - kdco/skills → kdco/code-philosophy
  - kdco/plugins → kdco/notify
- Profile install now uses only registries declared in profile's ocx.jsonc
- Add -g/--global to profile remove/move (local is default)
- Remove --force from profile add docs (use remove + add)
- Remove "marks active profile" claim from profile list docs
- Use full profile registries for transitive deps
- Only check last-profile guard for global removals
- Remove unused registries variable in profile add
- Update docs to match CLI output ("Global profiles:")
- Accept both ocx:bundle/ocx:profile and bundle/profile types
- Support direct URL profile installation (--from http://...)
- build: use path.resolve() for absolute path handling
- resolver: registry not found returns NOT_FOUND (66) instead of CONFIG (78)
- update: specific error when component not installed with empty receipt
- docs: correct test 17.7 exit code expectation (1, not 78)
Update all documentation to use correct receipt path:
- Old: .opencode/ocx-receipt.json
- New: .ocx/receipt.jsonc
- Remove --force from init calls (not a valid option)
- Add --global to profile move/remove tests (tests use global profiles)

Fixes ~50 test failures caused by tests using invalid/missing flags.
The init command does not accept a --force flag. These test calls
were causing unnecessary errors in the test output.

Changes:
- Remove --force from all init command calls in tests
- Tests now use correct init syntax without flags
OPENCODE_CONFIG_DIR always points to global config directory.
Profile config is passed via OPENCODE_CONFIG_CONTENT.
- Pass cwd to ProfileManager.create() in ConfigResolver
- Add kdco registry to test-profile-with-deps mock
@kdcokenny kdcokenny marked this pull request as ready for review February 4, 2026 00:00
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 issues found across 136 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/cli/src/commands/verify.ts">

<violation number="1" location="packages/cli/src/commands/verify.ts:56">
P2: Spinner output corrupts JSON when `--json` flag is used. The spinner should be disabled when `options.json` is true to ensure clean, parseable JSON output.</violation>
</file>

<file name="packages/cli/src/commands/add.ts.bak5">

<violation number="1" location="packages/cli/src/commands/add.ts.bak5:911">
P1: Regular npm dependencies are incorrectly placed in `devDependencies`. The function combines both `npmDeps` and `npmDevDeps` into one array, then `mergeDevDependencies` writes all of them to `devDependencies`. Regular dependencies should go to `dependencies` field instead.</violation>
</file>

<file name="packages/cli/src/utils/instruction-paths.ts">

<violation number="1" location="packages/cli/src/utils/instruction-paths.ts:147">
P2: Variable `path` shadows the imported `path` module from `node:path`. Rename the loop variable to avoid confusion and potential bugs when maintaining this code.</violation>
</file>

<file name="docs/OPENCODE_REFERENCE.md">

<violation number="1" location="docs/OPENCODE_REFERENCE.md:1046">
P3: Instruction discovery is described as a project-tree walk, but the implementation only walks up from the working directory to the git root. This doc line should reflect the upward search to avoid misleading users about which instruction files are discovered.</violation>
</file>

<file name="packages/cli/src/commands/profile/add.ts">

<violation number="1" location="packages/cli/src/commands/profile/add.ts:311">
P2: Catch-all error handler masks the real error. `manager.get()` can throw `OcxConfigError` or schema validation errors, not just `ProfileNotFoundError`. Catching and re-throwing as `ProfileNotFoundError` loses critical diagnostic information and misleads users.</violation>
</file>

<file name="docs/REGISTRY_PROTOCOL.md">

<violation number="1" location="docs/REGISTRY_PROTOCOL.md:135">
P2: The install locations for plugin/agent/command/tool are listed as singular directories, but the CLI only accepts plural prefixes (plugins/, agents/, commands/, tools/). Update the table to use the plural paths so registry authors generate valid targets.</violation>
</file>

<file name="packages/cli/src/commands/diff.ts">

<violation number="1" location="packages/cli/src/commands/diff.ts:127">
P2: The `fileStatus` variable is computed from `integrity.details` but its `status` property is never used. This appears to be incomplete implementation - the integrity check result is fetched but ignored. The subsequent code duplicates file existence checks. Either use `fileStatus.status` to optimize the logic (e.g., skip comparison for 'intact' files, handle 'missing' directly) or remove the unused integrity lookup.</violation>
</file>

<file name="packages/cli/src/schemas/registry.ts">

<violation number="1" location="packages/cli/src/schemas/registry.ts:193">
P2: The prefix validation logic incorrectly allows files like `AGENTS.md.backup` or `ocx.jsonc.old` to pass validation because `startsWith` is used for exact file name matches. For non-directory prefixes (no trailing `/`), only exact matches should be allowed.</violation>
</file>

<file name="packages/cli/src/commands/remove.ts">

<violation number="1" location="packages/cli/src/commands/remove.ts:80">
P2: Error message only shows modified files but not missing files. When `integrity.intact` is false due to missing files (but no modifications), the error says "has been modified" with an empty list, which is misleading. Include `integrity.missing` in the error message.</violation>

<violation number="2" location="packages/cli/src/commands/remove.ts:100">
P1: Success message shown before receipt is persisted. If `writeReceipt()` fails after `spin?.succeed()`, the user sees success but files are deleted without updating the receipt. Move the success message after the write operation completes.</violation>
</file>

<file name="packages/cli/src/profile/manager.ts">

<violation number="1" location="packages/cli/src/profile/manager.ts:208">
P2: Inconsistent `cwd` usage: `exists(name, false)` uses `this.cwd` from constructor, but `loadFromLocal(name, cwd)` uses the `cwd` parameter. If these differ, the existence check and load will target different directories. Consider removing the `cwd` parameter and using `this.cwd` consistently, or passing `cwd` to the existence check.</violation>
</file>

<file name="docs/MANUAL_TESTING.md">

<violation number="1" location="docs/MANUAL_TESTING.md:71">
P2: Hard-coded absolute paths make the manual test steps non-portable. Use a placeholder (e.g., `$OCX_REPO` or `~/workspace/ocx`) so other developers can follow the guide without editing every command.</violation>

<violation number="2" location="docs/MANUAL_TESTING.md:698">
P3: The update test headings (7.6/7.7) are misplaced inside the diff section, which breaks numbering and mixes unrelated commands. These update tests should live under Section 7 (ocx update), leaving Section 8 exclusively for diff cases.</violation>
</file>

<file name="packages/cli/src/commands/registry.ts">

<violation number="1" location="packages/cli/src/commands/registry.ts:100">
P2: Incorrect string escaping: `\\n` produces literal `\n` characters instead of a newline. Use `\n` (single backslash) for an actual line break in the error message.</violation>
</file>

<file name="packages/cli/src/commands/profile/install-from-registry.ts">

<violation number="1" location="packages/cli/src/commands/profile/install-from-registry.ts:314">
P2: The `typeof regsRaw === "object"` check doesn't exclude arrays since `typeof [] === "object"` in JavaScript. This could allow malformed config (e.g., `"registries": []`) to pass validation and cause runtime errors downstream.</violation>
</file>

<file name="docs/CREATING_REGISTRIES.md">

<violation number="1" location="docs/CREATING_REGISTRIES.md:134">
P2: Inconsistent directory name: uses `plugins/` here but the Component Types table shows `plugin/` (singular). These should match to avoid confusion.</violation>

<violation number="2" location="docs/CREATING_REGISTRIES.md:360">
P2: Duplicate `## Dependencies` section header. The first one should likely be renamed to something like `## Instruction Files` or the instruction content should be moved under the existing Dependencies section with appropriate subsection headers.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 60 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="docs/integrations/workspace.mdx">

<violation number="1" location="docs/integrations/workspace.mdx:41">
P3: The specialist agent is labeled `review` in the diagram, but the rest of the doc (agent list) calls it `reviewer`. This inconsistency makes it unclear which agent name is correct. Rename the diagram label to `reviewer` to match the rest of the doc.</violation>
</file>

<file name="docs/cli/add.mdx">

<violation number="1" location="docs/cli/add.mdx:79">
P3: Label `.ocx/receipt.jsonc` as the V1 receipt file to match the repository convention.

(Based on your team's feedback about labeling `.ocx/receipt.jsonc` as V1.) [FEEDBACK_USED]</violation>
</file>

<file name="docs/cli/search.mdx">

<violation number="1" location="docs/cli/search.mdx:81">
P2: The error cause still references the deprecated lock file. Update it to reflect the receipt-based install tracking so the docs match the v2 behavior.</violation>
</file>

<file name="docs/maintainers/migration-v1-4-0.mdx">

<violation number="1" location="docs/maintainers/migration-v1-4-0.mdx:28">
P2: The migration guide instructs running `ocx ghost migrate`, but ghost CLI commands were removed in this clean-break release. This command will fail and mislead users; update the guide to remove the automatic migration step or replace it with the supported flow.</violation>
</file>

<file name="docs/registries/create.mdx">

<violation number="1" location="docs/registries/create.mdx:304">
P3: The conflict example uses `agent/researcher.md`, but the documented directory for agents is `agents/`. This mismatch can mislead users about the correct path.</violation>
</file>

<file name="docs/enterprise/overview.mdx">

<violation number="1" location="docs/enterprise/overview.mdx:31">
P3: Label `.ocx/receipt.jsonc` as the V1 receipt to match the required naming convention.

(Based on your team's feedback about labeling .ocx/receipt.jsonc as V1.) [FEEDBACK_USED]</violation>
</file>

<file name="docs/reference/agents.mdx">

<violation number="1" location="docs/reference/agents.mdx:54">
P2: The agent config directory is documented as `.opencode/agent/`, but other docs/tests use `.opencode/agents/`. This inconsistency will send users to the wrong folder. Update to the plural form.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 38 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="docs/PROFILES.md">

<violation number="1" location="docs/PROFILES.md:199">
P3: The alias commands in this table omit `--global`, but the doc now says `--global` is required for all profile commands. Update the alias column so it doesn’t contradict the requirement.</violation>
</file>

<file name="docs/MANUAL_TESTING.md">

<violation number="1" location="docs/MANUAL_TESTING.md:1757">
P2: The new global-only section mixes two behaviors (requiring `--global` vs defaulting to global). This contradicts the subsequent tests that expect commands to fail without `--global`, so the docs should state one clear behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 7 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="docs/CLI.md">

<violation number="1" location="docs/CLI.md:651">
P2: Label the `.ocx/receipt.jsonc` receipt format as V1, not V2, to match repository conventions.

(Based on your team's feedback about labeling .ocx/receipt.jsonc as V1.) [FEEDBACK_USED]</violation>
</file>

<file name="docs/MANUAL_TESTING.md">

<violation number="1" location="docs/MANUAL_TESTING.md:1970">
P3: Update the receipt naming to V1 when describing the .ocx/receipt.jsonc migration; calling it V2 conflicts with the documented convention.

(Based on your team's feedback about labeling .ocx/receipt.jsonc as V1.) [FEEDBACK_USED]</violation>

<violation number="2" location="docs/MANUAL_TESTING.md:2001">
P3: Adjust the expected message to refer to the V1 receipt format to match the .ocx/receipt.jsonc naming convention.

(Based on your team's feedback about labeling .ocx/receipt.jsonc as V1.) [FEEDBACK_USED]</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:98">
P3: The README now calls the receipt target “v2,” but other docs label `.ocx/receipt.jsonc` as the V1 receipt file. Align the README wording with the existing V1 terminology (or omit the version) to avoid confusing users about which receipt format they’ll end up with.

(Based on your team's feedback about labeling .ocx/receipt.jsonc as V1.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

When --global is used, ocx migrate now discovers and processes the
global root plus every directory under profiles/*, sorted alphabetically.
Each target is analyzed/applied independently with per-target summaries.
Apply mode continues after individual target failures and exits non-zero
with a partial_failure status when any target errors.
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/commands/migrate/index.ts">

<violation number="1" location="packages/cli/src/commands/migrate/index.ts:97">
P2: Preview mode for global migration lacks per-target error handling, unlike apply mode. If any target throws unexpectedly (e.g., permission error), the entire preview crashes rather than continuing and reporting the failure per-target. Consider wrapping the `analyzeTarget` call in a try-catch consistent with the apply path.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@kdcokenny
Copy link
Owner Author

r2m

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/tests/registry/fetcher.test.ts">

<violation number="1" location="packages/cli/tests/registry/fetcher.test.ts:225">
P3: Test name contradicts its assertions; it says "returns null" but expects a non-null ancient-format result. Rename the test to reflect the actual behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

expect(classifyRegistryIndexIssue(42)).toBeNull()
})

it("returns null for empty array (classified as ancient-format)", () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Test name contradicts its assertions; it says "returns null" but expects a non-null ancient-format result. Rename the test to reflect the actual behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/tests/registry/fetcher.test.ts, line 225:

<comment>Test name contradicts its assertions; it says "returns null" but expects a non-null ancient-format result. Rename the test to reflect the actual behavior.</comment>

<file context>
@@ -137,3 +138,227 @@ describe("fetcher", () => {
+		expect(classifyRegistryIndexIssue(42)).toBeNull()
+	})
+
+	it("returns null for empty array (classified as ancient-format)", () => {
+		const result = classifyRegistryIndexIssue([])
+		expect(result).not.toBeNull()
</file context>
Suggested change
it("returns null for empty array (classified as ancient-format)", () => {
it("returns 'ancient-format' for empty array", () => {
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments