Skip to content

fix(opencode): extract to config dir and clean stale files on install#400

Merged
rhuanbarreto merged 10 commits into
mainfrom
fix/opencode-plugin-install-skills
Jun 9, 2026
Merged

fix(opencode): extract to config dir and clean stale files on install#400
rhuanbarreto merged 10 commits into
mainfrom
fix/opencode-plugin-install-skills

Conversation

@rhuanbarreto

@rhuanbarreto rhuanbarreto commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix extraction target — The plugins repo PR #117 changed the opencode tarball from flat archgate-*.md files to nested agents/ + skills/ directories. The CLI was extracting into opencodeAgentsDir() (~/.config/opencode/agents/), which double-nested agents and misplaced skills under agents/skills/. Now extracts into opencodeConfigDir() (~/.config/opencode/) so the tarball's directories land correctly.
  • Clean stale files before extractioncleanArchgateFiles() removes old archgate-* agents and skill directories before extracting, preventing dangling files when components are renamed or removed upstream. Scoped to archgate-* entries only — user files are untouched.
  • DRY Cursor + opencode installs — Extracted installEditorPluginBundle() shared by both editors: ensure agents/ + 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 validate passes (lint, typecheck, format, 1256 tests, 39/39 ADR checks, knip, build)
  • Manual: archgate plugin install --editor opencode extracts agents to ~/.config/opencode/agents/ and skills to ~/.config/opencode/skills/
  • Manual: Re-running install cleans old files before extracting (no dangling artifacts)

Summary by CodeRabbit

  • Documentation

    • Added Git hooks setup guide and updated editor plugin installation docs/checklist.
  • New

    • Added config-based Git hooks (.githooks) for pre-commit (lint/typecheck/format-check) and pre-push validation.
  • Bug Fixes

    • Installer now removes stale plugin artifacts during installation and targets correct extraction directories.
  • Refactor

    • Consolidated plugin installation into a unified bundle-based installer and standardized success messaging.
  • Tests

    • Added tests covering plugin install cleanup and extraction behavior.
  • Chores

    • Updated formatting hook to run formatter after write/edit actions.

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>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

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

View logs

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.1% (6794 / 7540)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 88.1% 2078 / 2359
src/engine/ 93.1% 1379 / 1481
src/formats/ 100.0% 141 / 141
src/helpers/ 89.8% 3196 / 3559

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>
@coderabbitai

coderabbitai Bot commented Jun 9, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1f72e19e-4d88-4897-9153-e85a7966aa95

📥 Commits

Reviewing files that changed from the base of the PR and between e16da99 and 03439d4.

📒 Files selected for processing (1)
  • tests/helpers/plugin-install-cleanup.test.ts

📝 Walkthrough

Walkthrough

Refactors 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.

Changes

Editor Plugin Installation and Git Hooks

Layer / File(s) Summary
Git Hooks Infrastructure
.claude/settings.json, .githooks, CLAUDE.md
Adds .githooks for Git 2.54+ (pre-commit: lint/typecheck/format-check; pre-push: validate), documents activation/opt-out, and changes the formatting PostToolUse hook to use $(jq -r .tool_input.file_path) substitution.
Shared Plugin Bundle Installer
src/helpers/plugin-install.ts
Adds installEditorPluginBundle that ensures agents/ and skills/, removes stale archgate-* artifacts with Bun.Glob, downloads the authenticated plugin asset, writes a temp tarball, extracts it (tar -xzf -C baseDir), and always cleans up the temp tarball. Updates imports to use opencodeConfigDir.
Cursor & Opencode wiring
src/helpers/plugin-install.ts
Refactors installCursorPlugin to delegate to installEditorPluginBundle (baseDir .cursor, apiPath /api/cursor) and updates installOpencodePlugin to use opencodeConfigDir() and /api/opencode, preserving existing opencode post-configuration.
Messaging and Docs
src/commands/plugin/install.ts, CLAUDE.md
Changes opencode branch messaging from “agent(s)” → “plugin” for guards and success logs; documents using installEditorPluginBundle() in the editor-target checklist.
Plugin Cleanup Validation Tests
tests/helpers/plugin-install-cleanup.test.ts
Adds Bun tests covering opencode and cursor installers: stale archgate-* cleanup, non-archgate file preservation, directory creation on clean installs, and that extraction -C targets the correct editor base directory.

Sequence Diagram

sequenceDiagram
  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
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

I hop through temp tar and dir,
I chase old archgate files afar,
Bundles land in proper place,
Hooks catch lint with gentle grace,
A tidy install, carrot-star 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: fixing opencode plugin extraction to use config dir and adding stale file cleanup on install.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/opencode-plugin-install-skills

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

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 9, 2026
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>

@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

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 win

Add bounds check for -C flag index.

The test assumes the -C flag exists in the spawn arguments, but indexOf returns -1 if not found. Accessing callArgs[targetIdx + 1] when targetIdx is -1 would incorrectly read callArgs[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

📥 Commits

Reviewing files that changed from the base of the PR and between 5cb5059 and b883de2.

📒 Files selected for processing (1)
  • tests/helpers/plugin-install-cleanup.test.ts

Comment thread tests/helpers/plugin-install-cleanup.test.ts Outdated
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>

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between b883de2 and e073f03.

📒 Files selected for processing (1)
  • tests/helpers/plugin-install-cleanup.test.ts

Comment thread 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>
@rhuanbarreto

Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve

@rhuanbarreto rhuanbarreto enabled auto-merge (squash) June 9, 2026 20:48
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@rhuanbarreto rhuanbarreto merged commit 51b41e7 into main Jun 9, 2026
27 checks passed
@rhuanbarreto rhuanbarreto deleted the fix/opencode-plugin-install-skills branch June 9, 2026 20:53
@archgatebot archgatebot Bot mentioned this pull request Jun 9, 2026
rhuanbarreto pushed a commit that referenced this pull request Jun 9, 2026
# 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>
@archgatebot archgatebot Bot mentioned this pull request Jun 10, 2026
rhuanbarreto pushed a commit that referenced this pull request Jun 10, 2026
# 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>
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