Skip to content

feat: sync OpenCode references#50

Merged
fbosch merged 21 commits into
masterfrom
feature/opencode-auto-references
Jul 15, 2026
Merged

feat: sync OpenCode references#50
fbosch merged 21 commits into
masterfrom
feature/opencode-auto-references

Conversation

@fbosch

@fbosch fbosch commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Sync configured documentation sources into detected OpenCode configs as managed local references, without exposing cached files to Git.

Changes

  • Detect OpenCode config layers during init and interactive sync, and persist opt-in or opt-out decisions.
  • Reconcile absolute cache-backed references in JSONC while preserving user-owned entries and rejecting alias collisions.
  • Track locally established ownership for safe updates, source removal, config migration, frozen validation, and symlinked config files.
  • Keep targetDir optional and independent from OpenCode reference paths.

Testing

  • pnpm typecheck
  • biome check .
  • pnpm schema:build
  • node --test --test-concurrency=1 (206 passed, 3 skipped)

Summary by CodeRabbit

  • New Features
    • Added OpenCode integration for syncing documentation as local references, including auto-detection and remembered consent during init/sync.
    • --frozen now blocks both lock drift and OpenCode reference drift.
  • Documentation
    • Updated README to clarify “local only” caching/exposure, optional targetDir behavior, and the updated frozen workflow.
  • Bug Fixes
    • Improved config/lock/OpenCode validation and made OpenCode sync more resilient with rollback on failures.
  • Tests
    • Expanded OpenCode detection, reference sync, consent, validation, and missing-config scenarios.

Copilot AI review requested due to automatic review settings July 12, 2026 16:32
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds OpenCode configuration detection, consent, JSONC reference synchronization, ownership persistence, lock metadata, frozen drift validation, atomic writes, CLI/init integration, tests, and documentation.

Changes

OpenCode synchronization

Layer / File(s) Summary
Configuration and lock contracts
src/config/*, src/cache/lock.ts, src/atomic-write.ts, package.json, tsconfig.json, docs.config.schema.json, src/api.ts
Adds the OpenCode configuration shape, validates aliases, propagates settings, records lock metadata, adds atomic writes, and wires aliases and exports.
Detection and consent flow
src/opencode/detection.ts, src/opencode/consent.ts, src/commands/init.ts, src/cli/index.ts
Detects configuration candidates, persists consent, and integrates OpenCode choices into initialization and interactive sync.
Reference planning and ownership
src/opencode/references.ts, src/opencode/ownership.ts
Parses JSONC, plans managed references, detects drift and collisions, removes stale aliases, and persists ownership with rollback support.
Sync and supporting filesystem handling
src/commands/sync.ts, src/cache/materialize.ts, src/commands/verify.ts, src/git/fetch-source.ts
Adds OpenCode planning, frozen drift checks, lock and TOC sequencing, reference application, ownership rollback, and centralized filesystem error sets.
Validation and documentation
tests/*, README.md, .github/workflows/ci.yml
Tests detection, initialization, schema validation, synchronization, rollback, installation behavior, and documents the OpenCode workflow and targetDir behavior while relocating the CI build step.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant SyncCommand
  participant ReferencePlanner
  participant OpenCodeConfig
  participant OwnershipStore
  participant LockFile
  CLI->>SyncCommand: resolve OpenCode consent
  SyncCommand->>OwnershipStore: read ownership
  SyncCommand->>ReferencePlanner: plan references
  ReferencePlanner->>OpenCodeConfig: parse and compare JSONC
  ReferencePlanner-->>SyncCommand: return drift and changes
  SyncCommand->>OpenCodeConfig: apply managed references
  SyncCommand->>OwnershipStore: persist ownership
  SyncCommand->>LockFile: write OpenCode lock metadata
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding OpenCode reference synchronization.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/opencode-auto-references

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class OpenCode integration to docs-cache: detect existing OpenCode configs, store a consent decision in docs.config.json, and keep OpenCode references in sync with the canonical cache directory while tracking ownership for safe updates/removals.

Changes:

  • Introduces OpenCode config detection + consent persistence during init and interactive sync.
  • Plans/applies managed OpenCode reference updates (JSONC-preserving) and records managed aliases in docs-lock.json.
  • Adds local ownership tracking and atomic file writes; expands schema/docs and adds comprehensive tests.

Reviewed changes

Copilot reviewed 22 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tsconfig.json Adds #opencode/* TS path alias.
tests/schema.test.js Verifies schema includes opencode and no unintended per-source fields.
tests/opencode-references.test.js End-to-end tests for managed reference creation/update/removal and safety cases.
tests/opencode-detection.test.js Tests OpenCode config detection precedence behavior.
tests/init.test.js Tests init-time OpenCode consent persistence.
tests/config-validation.test.js Tests conditional validation for OpenCode alias constraints.
src/opencode/references.ts Implements planning/applying OpenCode reference reconciliation with drift detection.
src/opencode/ownership.ts Adds local ownership state read/write/restore helpers.
src/opencode/detection.ts Adds OpenCode config candidate discovery + detection.
src/opencode/consent.ts Persists user consent decision back into docs-cache config.
src/config/schema.ts Extends config schema/types with optional opencode.
src/config/io.ts Ensures opencode is preserved when merging config base.
src/config/index.ts Threads opencode through validation/stripping/types.
src/commands/sync.ts Integrates reference planning/apply/rollback + lock and ownership persistence.
src/commands/init.ts Detects OpenCode config and prompts/stores consent during init.
src/cli/index.ts Adds interactive sync-time consent prompt when appropriate.
src/cache/lock.ts Adds opencode section to lock + writes lock atomically.
src/atomic-write.ts New atomic write helper used for lock/OpenCode writes.
src/api.ts Exposes new OpenCode helpers via public API.
README.md Documents OpenCode reference syncing behavior and config field.
pnpm-lock.yaml Locks new dependency additions.
package.json Adds jsonc-parser dependency and #opencode/* export mapping.
docs.config.schema.json Adds opencode to published JSON schema.
build.config.ts Adds build alias mapping for #opencode/*.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/atomic-write.ts
Comment thread src/commands/sync.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d8af00d18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/sync.ts Outdated
Comment thread src/cli/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/opencode/detection.ts (1)

76-85: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider early return in detectOpenCodeConfig to reduce unnecessary filesystem access.

The function iterates all candidates and keeps the last match. Since candidate order is already prioritized (later candidates have higher priority), you could iterate in reverse and return on the first match, avoiding unnecessary access calls for remaining candidates.

♻️ Proposed optimization
 export const detectOpenCodeConfig = async (startDir: string) => {
 	const candidates = await getOpenCodeConfigCandidates(startDir);
-	let detected: string | null = null;
-	for (const candidate of candidates) {
-		if (await exists(candidate)) {
-			detected = candidate;
-		}
+	for (let i = candidates.length - 1; i >= 0; i--) {
+		if (await exists(candidates[i])) {
+			return candidates[i];
+		}
 	}
-	return detected;
+	return null;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/opencode/detection.ts` around lines 76 - 85, Update detectOpenCodeConfig
to iterate candidates in reverse priority order and immediately return the first
candidate for which exists(candidate) succeeds. Remove the detected accumulator
and preserve the null return when no candidates exist.
src/cache/lock.ts (1)

16-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

DocsCacheOpenCodeLock is duplicated across multiple files.

The same interface is defined in src/cache/lock.ts, src/commands/sync.ts, src/opencode/references.ts, and src/opencode/ownership.ts. Consider importing from a single source to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cache/lock.ts` around lines 16 - 25, Use the canonical
DocsCacheOpenCodeLock definition from src/cache/lock.ts and remove duplicate
declarations in the sync and OpenCode modules. Update affected references to
import and reuse the shared interface while preserving its existing configPath
and aliases contract.
README.md (1)

109-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify that docs-lock.json's opencode.aliases is informational, not the enforcement source.

The text says aliases are "recorded... in docs-lock.json so removing a source removes only its own reference," but per the implementation (and the "does not trust lock aliases as OpenCode ownership" test), the actual ownership authority is a separate local state file, not docs-lock.json. Worth a short clarification so readers don't assume editing/tracking the lock file affects collision handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 109 - 123, Clarify the README description of
docs-lock.json so opencode.aliases is identified as informational only, while
OpenCode reference ownership and collision handling rely on the separate local
state file. State that editing or tracking docs-lock.json does not change
ownership enforcement or managed-reference cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/sync.ts`:
- Around line 1069-1090: Update the catch block surrounding
writeOpenCodeOwnership and writeLock so failures from restoreOpenCodeOwnership
and openCodeReferences.rollback are contained without replacing the original
caught error. Preserve both rollback attempts where enabled, then rethrow the
original error after rollback cleanup completes.
- Around line 982-993: Gate the readOpenCodeOwnership and planOpenCodeReferences
calls in the sync command so they run only for modes that consume OpenCode
state, excluding install and non-frozen lockOnly. Preserve the existing planning
behavior for applicable modes, and add a regression test covering install when
the saved OpenCode configPath is missing.

In `@src/opencode/references.ts`:
- Around line 163-176: Update the apply function’s rollback handling so failures
while restoring files cannot replace the original write error. Preserve the
initial error from the forward write loop, attempt all reversions in
written.reverse(), and ensure any rollback errors are suppressed or otherwise
handled without changing the error ultimately rethrown.

In `@tests/init.test.js`:
- Around line 194-209: Update the initConfig test around the OpenCode detection
assertion to isolate environment-dependent home-directory lookup by temporarily
setting HOME and OPENCODE_CONFIG_DIR to the test fixture context, matching the
environment management used in opencode-detection.test.js. Restore both
environment variables after the test, while preserving the existing project
config setup and assertion.

---

Nitpick comments:
In `@README.md`:
- Around line 109-123: Clarify the README description of docs-lock.json so
opencode.aliases is identified as informational only, while OpenCode reference
ownership and collision handling rely on the separate local state file. State
that editing or tracking docs-lock.json does not change ownership enforcement or
managed-reference cleanup.

In `@src/cache/lock.ts`:
- Around line 16-25: Use the canonical DocsCacheOpenCodeLock definition from
src/cache/lock.ts and remove duplicate declarations in the sync and OpenCode
modules. Update affected references to import and reuse the shared interface
while preserving its existing configPath and aliases contract.

In `@src/opencode/detection.ts`:
- Around line 76-85: Update detectOpenCodeConfig to iterate candidates in
reverse priority order and immediately return the first candidate for which
exists(candidate) succeeds. Remove the detected accumulator and preserve the
null return when no candidates exist.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 985b873d-b08c-426f-af16-fd735eb43563

📥 Commits

Reviewing files that changed from the base of the PR and between ab0a6fa and 4d8af00.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • README.md
  • build.config.ts
  • docs.config.schema.json
  • package.json
  • src/api.ts
  • src/atomic-write.ts
  • src/cache/lock.ts
  • src/cli/index.ts
  • src/commands/init.ts
  • src/commands/sync.ts
  • src/config/index.ts
  • src/config/io.ts
  • src/config/schema.ts
  • src/opencode/consent.ts
  • src/opencode/detection.ts
  • src/opencode/ownership.ts
  • src/opencode/references.ts
  • tests/config-validation.test.js
  • tests/init.test.js
  • tests/opencode-detection.test.js
  • tests/opencode-references.test.js
  • tests/schema.test.js
  • tsconfig.json

Comment thread src/commands/sync.ts
Comment thread src/commands/sync.ts Outdated
Comment thread src/opencode/references.ts Outdated
Comment thread tests/init.test.js Outdated
@pkg-pr-new

pkg-pr-new Bot commented Jul 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/docs-cache@50

commit: 6236aaa

@fbosch

fbosch commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 094b2e1c38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/sync.ts Outdated
@fbosch

fbosch commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 489e2496c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/opencode/references.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/install.test.js (1)

204-265: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit assertions to strengthen the test.

The test relies entirely on runSync not throwing — there are zero explicit assertions. If runSync silently swallows errors, the test would pass even with bugs. Adding at least one assertion (e.g., verifying the lock file was written or that installed files exist) would make the test more robust and self-documenting.

♻️ Suggested additions
 	await runSync(
 		{
 			configPath,
 			cacheDirOverride: cacheDir,
 			json: false,
 			lockOnly: true,
 			offline: false,
 			failOnMiss: false,
 		},
 		{ resolveRemoteCommit },
 	);
+
+	const lockPath = path.join(cacheDir, "docs-lock.json");
+	assert.ok(await readFile(lockPath, "utf8").then(JSON.parse).then((l) => l.opencode?.configPath), "lock should record opencode.configPath");

 	await runSync(
 		{
 			configPath,
 			cacheDirOverride: cacheDir,
 			json: false,
 			lockOnly: false,
 			offline: false,
 			failOnMiss: false,
 			install: true,
 		},
 		{
 			fetchSource: async () => ({
 				repoDir,
 				cleanup: async () => undefined,
 			}),
 			materializeSource: async ({ cacheDir: outputRoot, sourceId }) => {
 				const outputDir = path.join(outputRoot, sourceId);
 				await mkdir(outputDir, { recursive: true });
 				await writeFile(path.join(outputDir, "README.md"), "hello", "utf8");
 				await writeFile(
 					path.join(outputDir, ".manifest.jsonl"),
 					`${JSON.stringify({ path: "README.md", size: 5 })}\n`,
 				);
 				return { bytes: 5, fileCount: 1, manifestSha256: "manifest" };
 			},
 		},
 	);
+
+	assert.ok(
+		await readFile(path.join(cacheDir, "local", "README.md"), "utf8").then((c) => c === "hello"),
+		"installed README.md should exist with expected content",
+	);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/install.test.js` around lines 204 - 265, Add explicit postcondition
assertions to the test “install and lock-only ignore a missing OpenCode config”
after the runSync calls, verifying the lock-only operation writes the expected
lock file and the install operation produces the expected installed README.md.
Keep the assertions focused on successful observable outcomes rather than
relying on runSync not throwing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/install.test.js`:
- Around line 204-265: Add explicit postcondition assertions to the test
“install and lock-only ignore a missing OpenCode config” after the runSync
calls, verifying the lock-only operation writes the expected lock file and the
install operation produces the expected installed README.md. Keep the assertions
focused on successful observable outcomes rather than relying on runSync not
throwing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e00fffe6-8512-4bea-bca2-5027bc98b240

📥 Commits

Reviewing files that changed from the base of the PR and between 094b2e1 and e1a9ca8.

📒 Files selected for processing (12)
  • src/cache/lock.ts
  • src/cli/index.ts
  • src/commands/sync.ts
  • src/config/index.ts
  • src/opencode/consent.ts
  • src/opencode/detection.ts
  • src/opencode/ownership.ts
  • src/opencode/references.ts
  • tests/config-validation.test.js
  • tests/init.test.js
  • tests/install.test.js
  • tests/opencode-references.test.js
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/opencode/consent.ts
  • src/config/index.ts
  • tests/config-validation.test.js
  • src/opencode/ownership.ts
  • src/cli/index.ts
  • tests/init.test.js
  • src/opencode/detection.ts
  • src/opencode/references.ts
  • src/cache/lock.ts
  • tests/opencode-references.test.js
  • src/commands/sync.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/opencode-detection.test.js`:
- Line 52: Update the test named “OpenCode detection disables project config
only for true or 1” to include an assertion covering the `"true"` value, while
preserving the existing `"false"`, `"0"`, and `"1"` cases so the test title
accurately reflects its coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bfbee646-25e0-44f8-9be6-80150b95a8fc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a9ca8 and 8a10888.

📒 Files selected for processing (13)
  • src/cache/materialize.ts
  • src/cli/index.ts
  • src/commands/init.ts
  • src/commands/sync.ts
  • src/commands/verify.ts
  • src/config/schema.ts
  • src/git/fetch-source.ts
  • src/opencode/consent.ts
  • src/opencode/detection.ts
  • src/opencode/references.ts
  • tests/init.test.js
  • tests/opencode-detection.test.js
  • tests/opencode-references.test.js
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/opencode/detection.ts
  • src/cli/index.ts
  • src/opencode/consent.ts
  • tests/init.test.js
  • tests/opencode-references.test.js
  • src/opencode/references.ts
  • src/commands/init.ts
  • src/commands/sync.ts

Comment thread tests/opencode-detection.test.js
@fbosch
fbosch merged commit 46fe144 into master Jul 15, 2026
10 checks passed
@fbosch
fbosch deleted the feature/opencode-auto-references branch July 15, 2026 22:39
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.

2 participants