fix(opencode): extract to config dir and clean stale files on install#400
Conversation
The plugins repo PR #117 changed the opencode tarball from flat agent files to nested agents/ + skills/ directories. The CLI was extracting into opencodeAgentsDir() (~/.config/opencode/agents/), which double- nested agents and misplaced skills. - Fix extraction target: opencodeConfigDir() instead of opencodeAgentsDir() - Add cleanArchgateFiles() to remove stale archgate-* agents/skills before extraction, preventing dangling files from renamed components - Extract installEditorPluginBundle() shared by Cursor and opencode: ensure dirs → clean old files → download and extract tarball - Update install command messaging from "agents" to "plugin" Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Deploying archgate-cli with
|
| Latest commit: |
03439d4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2161c5a5.archgate-cli.pages.dev |
| Branch Preview URL: | https://fix-opencode-plugin-install.archgate-cli.pages.dev |
Code Coverage
Full HTML report available in workflow artifacts. Per-directory breakdown
|
The original tests only checked that tar was called — they never asserted where extraction targets, which is exactly the bug surface that allowed the wrong target directory to go undetected. - Assert opencode extracts to config dir (not agents subdir) - Assert cursor extracts to user dir (not a nested subdir) - Test stale archgate-* files are removed before extraction - Test non-archgate user files survive cleanup - Test clean install with no pre-existing files works Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Git's xargs treats backslashes as escape characters, stripping them from Windows paths (E:\archgate\cli\... → E:archgatecli...). Replace the jq|xargs pipeline with command substitution which preserves backslashes correctly. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Add .githooks config file with pre-commit and pre-push hooks: - pre-commit: lint + typecheck + format:check (~15s fast gate) - pre-push: full `bun run validate` (~60s, mirrors CI) Activate with: git config --local include.path ../.githooks Also fix the Claude Code PostToolUse format hook to tolerate files that oxfmt cannot format (e.g., extensionless config files). Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Replace readdirSync + manual string filtering with Bun.Glob.scanSync for cleaning stale archgate files — more idiomatic and avoids reading the entire directory just to filter. Inline downloadAndExtractTarball into installEditorPluginBundle since it was its only caller after the DRY refactor. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughRefactors editor plugin installation into a shared installEditorPluginBundle, adds Git 2.54+ config-based hooks and docs, updates messaging from “agents” to “plugin”, and adds tests validating stale-archgate cleanup and correct extraction targets. ChangesEditor Plugin Installation and Git Hooks
Sequence DiagramsequenceDiagram
participant installCursorPlugin
participant installOpencodePlugin
participant installEditorPluginBundle
participant downloadPluginAsset
participant BunGlob as Bun.Glob
participant BunSpawn as Bun.spawn
installCursorPlugin->>installEditorPluginBundle: call(baseDir=.cursor, apiPath=/api/cursor)
installOpencodePlugin->>installEditorPluginBundle: call(baseDir=opencode config dir, apiPath=/api/opencode)
installEditorPluginBundle->>BunGlob: glob("archgate-*") -> remove matching files/dirs
installEditorPluginBundle->>downloadPluginAsset: request authenticated bundle
downloadPluginAsset->>installEditorPluginBundle: return tarball bytes
installEditorPluginBundle->>BunSpawn: spawn("tar -xzf", "-C", baseDir)
installEditorPluginBundle->>BunSpawn: spawn cleanup / unlink temp tarball
🎯 3 (Moderate) | ⏱️ ~20 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Bun runs all test files in a single process sharing Bun.env. The cursor cleanup tests set Bun.env.HOME = tempDir, which leaked to the parallel vscode-settings tests causing getVscodeUserSettingsPath() to resolve against the temp dir instead of the real home. The opencode tests (which only override XDG_CONFIG_HOME) already cover the same installEditorPluginBundle cleanup behavior. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/helpers/plugin-install-cleanup.test.ts (1)
177-182:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd bounds check for
-Cflag index.The test assumes the
-Cflag exists in the spawn arguments, butindexOfreturns-1if not found. AccessingcallArgs[targetIdx + 1]whentargetIdxis-1would incorrectly readcallArgs[0](the command name), causing the test to pass incorrectly or fail with a confusing error.🛡️ Proposed fix to add bounds check
const callArgs = spawnSpy.mock.calls[0][0] as string[]; const targetIdx = callArgs.indexOf("-C"); + expect(targetIdx).toBeGreaterThanOrEqual(0); const targetDir = callArgs[targetIdx + 1]; // Must end with /opencode (config dir), not /opencode/agents expect(targetDir).toMatch(/opencode$/u); expect(targetDir).not.toMatch(/agents$/u);🤖 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/helpers/plugin-install-cleanup.test.ts` around lines 177 - 182, The test currently assumes the "-C" flag exists and reads callArgs[targetIdx + 1] without checking bounds; update the test around spawnSpy / callArgs / targetIdx / targetDir to assert that targetIdx is >= 0 and that targetIdx + 1 is < callArgs.length before reading targetDir (e.g., add expect(targetIdx).toBeGreaterThanOrEqual(0) and expect(targetIdx + 1).toBeLessThan(callArgs.length) or otherwise throw/fail early), then proceed to validate targetDir as before.
🤖 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/helpers/plugin-install-cleanup.test.ts`:
- Around line 186-190: Update the comment to remove the "full coverage" claim
and clarify scope: state that the opencode tests exercise only the shared
installEditorPluginBundle() stale-file cleanup for the opencode baseDir, and
note that Cursor-specific integration (cursorUserDir(), mergeCursorHooks(),
hooks.json merging and any cleanup dependent on cursorUserDir() resolution)
remains untested here; reference that Cursor download/extract is covered
elsewhere by installCursorPlugin but not these cleanup/hooks behaviors.
---
Outside diff comments:
In `@tests/helpers/plugin-install-cleanup.test.ts`:
- Around line 177-182: The test currently assumes the "-C" flag exists and reads
callArgs[targetIdx + 1] without checking bounds; update the test around spawnSpy
/ callArgs / targetIdx / targetDir to assert that targetIdx is >= 0 and that
targetIdx + 1 is < callArgs.length before reading targetDir (e.g., add
expect(targetIdx).toBeGreaterThanOrEqual(0) and expect(targetIdx +
1).toBeLessThan(callArgs.length) or otherwise throw/fail early), then proceed to
validate targetDir as before.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: eb6e8579-d7e1-4858-841c-6a40e7f2903b
📒 Files selected for processing (1)
tests/helpers/plugin-install-cleanup.test.ts
Move Bun.env.HOME override into a nested beforeEach inside the cursor describe block instead of inline in each test body. The top-level afterEach already restores the original HOME value. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/helpers/plugin-install-cleanup.test.ts`:
- Around line 189-225: Add tests to tests/helpers/plugin-install-cleanup.test.ts
exercising mergeCursorHooks by simulating HOME=tempDir and invoking
installCursorPlugin("test-token") while controlling mockFetch/spawnSpy; verify
that when ~/.cursor/hooks.json does not exist it is created, when it exists
existing entries are preserved, and that the archgate check hook is merged under
the afterFileEdit key. Locate the mergeCursorHooks behavior via the
installCursorPlugin flow (reference mergeCursorHooks and hooks.json) and add
assertions that read and parse the generated hooks.json to confirm creation,
preservation of prior keys/values, and presence of the new afterFileEdit hook
entry.
- Around line 189-225: Add two tests to the existing "cursor" describe: (1)
"preserves non-archgate files during cleanup" — create temp HOME
.cursor/agents/my-custom-agent.md and .cursor/skills/my-custom-skill/ plus some
archgate files, call mockFetch and await installCursorPlugin("test-token"), then
assert my-custom-agent.md and my-custom-skill still exist while archgate-*
entries were removed (use the same agentsDir and skillsDir variables and
existsSync checks as other tests). (2) "creates agents and skills on clean
install" — ensure HOME has no .cursor (remove if present), call mockFetch and
await installCursorPlugin("test-token"), then assert that .cursor,
.cursor/agents and .cursor/skills now exist; reference installCursorPlugin,
mockFetch and the spawnSpy pattern used by the existing "extracts into cursor
user dir" test to validate behavior.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e0a15590-6569-49d0-ac98-9f82d4b91395
📒 Files selected for processing (1)
tests/helpers/plugin-install-cleanup.test.ts
Bun runs all test files in a single process sharing Bun.env, so setting HOME in one file leaks to parallel files. Mock the paths module to redirect cursorUserDir() via a test-scoped variable instead. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Assert targetIdx >= 0 before accessing callArgs[targetIdx + 1] so the test fails clearly if tar args change, rather than silently reading the wrong index. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
|
@coderabbitai resolve |
✅ Action performedComments resolved and changes approved. |
# archgate ## [0.44.0](v0.43.0...v0.44.0) (2026-06-09) ### Features * **cursor:** automated plugin install via tarball download ([#395](#395)) ([41eb37e](41eb37e)) ### Bug Fixes * **opencode:** extract to config dir and clean stale files on install ([#400](#400)) ([51b41e7](51b41e7)) --- This PR was generated with [simple-release](https://github.com/TrigenSoftware/simple-release). <details> <summary>📄 Cheatsheet</summary> <br> You can configure the bot's behavior through a pull request comment using the `!simple-release/set-options` command. ### Command Format ````md !simple-release/set-options ```json { "bump": {}, "publish": {} } ``` ```` ### Useful Parameters #### Bump | Parameter | Type | Description | |-----------|------|-------------| | `version` | `string` | Force set specific version | | `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type | | `prerelease` | `string` | Pre-release identifier (e.g., "alpha", "beta") | | `firstRelease` | `boolean` | Whether this is the first release | | `skip` | `boolean` | Skip version bump | | `byProject` | `Record<string, object>` | Per-project bump options for monorepos | #### Publish | Parameter | Type | Description | |-----------|------|-------------| | `skip` | `boolean` | Skip publishing | | `access` | `'public' \| 'restricted'` | Package access level | | `tag` | `string` | Tag for npm publication | ### Usage Examples #### Force specific version ````md !simple-release/set-options ```json { "bump": { "version": "2.0.0" } } ``` ```` #### Force major bump ````md !simple-release/set-options ```json { "bump": { "as": "major" } } ``` ```` #### Create alpha pre-release ````md !simple-release/set-options ```json { "bump": { "prerelease": "alpha" } } ``` ```` #### Publish with specific access and tag ````md !simple-release/set-options ```json { "bump": { "prerelease": "beta" }, "publish": { "access": "public", "tag": "beta" } } ``` ```` ### Access Restrictions The command can only be used by users with permissions: - repository owner - organization member - collaborator ### Notes - The last comment with `!simple-release/set-options` command takes priority - JSON must be valid, otherwise the command will be ignored - Parameters apply only to the current release execution - The command can be updated by editing the comment or adding a new one </details> <!-- Please do not edit this comment. simple-release-pull-request: true simple-release-branch-from: release simple-release-branch-to: main --> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
# archgate ## [0.45.0](v0.43.0...v0.45.0) (2026-06-10) ### Features * **cursor:** automated plugin install via tarball download ([#395](#395)) ([41eb37e](41eb37e)) ### Bug Fixes * **opencode:** extract to config dir and clean stale files on install ([#400](#400)) ([51b41e7](51b41e7)) * **tests:** eliminate user-scope pollution and global module mocks in tests ([#401](#401)) ([0cff642](0cff642)) --- This PR was generated with [simple-release](https://github.com/TrigenSoftware/simple-release). <details> <summary>📄 Cheatsheet</summary> <br> You can configure the bot's behavior through a pull request comment using the `!simple-release/set-options` command. ### Command Format ````md !simple-release/set-options ```json { "bump": {}, "publish": {} } ``` ```` ### Useful Parameters #### Bump | Parameter | Type | Description | |-----------|------|-------------| | `version` | `string` | Force set specific version | | `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type | | `prerelease` | `string` | Pre-release identifier (e.g., "alpha", "beta") | | `firstRelease` | `boolean` | Whether this is the first release | | `skip` | `boolean` | Skip version bump | | `byProject` | `Record<string, object>` | Per-project bump options for monorepos | #### Publish | Parameter | Type | Description | |-----------|------|-------------| | `skip` | `boolean` | Skip publishing | | `access` | `'public' \| 'restricted'` | Package access level | | `tag` | `string` | Tag for npm publication | ### Usage Examples #### Force specific version ````md !simple-release/set-options ```json { "bump": { "version": "2.0.0" } } ``` ```` #### Force major bump ````md !simple-release/set-options ```json { "bump": { "as": "major" } } ``` ```` #### Create alpha pre-release ````md !simple-release/set-options ```json { "bump": { "prerelease": "alpha" } } ``` ```` #### Publish with specific access and tag ````md !simple-release/set-options ```json { "bump": { "prerelease": "beta" }, "publish": { "access": "public", "tag": "beta" } } ``` ```` ### Access Restrictions The command can only be used by users with permissions: - repository owner - organization member - collaborator ### Notes - The last comment with `!simple-release/set-options` command takes priority - JSON must be valid, otherwise the command will be ignored - Parameters apply only to the current release execution - The command can be updated by editing the comment or adding a new one </details> <!-- Please do not edit this comment. simple-release-pull-request: true simple-release-branch-from: release simple-release-branch-to: main --> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary
archgate-*.mdfiles to nestedagents/+skills/directories. The CLI was extracting intoopencodeAgentsDir()(~/.config/opencode/agents/), which double-nested agents and misplaced skills underagents/skills/. Now extracts intoopencodeConfigDir()(~/.config/opencode/) so the tarball's directories land correctly.cleanArchgateFiles()removes oldarchgate-*agents and skill directories before extracting, preventing dangling files when components are renamed or removed upstream. Scoped toarchgate-*entries only — user files are untouched.installEditorPluginBundle()shared by both editors: ensureagents/+skills/dirs → clean old archgate files → download and extract tarball. Editor-specific post-steps (Cursor hooks merge, opencode settings) remain in each editor's function.Test plan
bun run validatepasses (lint, typecheck, format, 1256 tests, 39/39 ADR checks, knip, build)archgate plugin install --editor opencodeextracts agents to~/.config/opencode/agents/and skills to~/.config/opencode/skills/Summary by CodeRabbit
Documentation
New
Bug Fixes
Refactor
Tests
Chores