diff --git a/.beastmode/artifacts/design/2026-04-12-lockfile-path-fix-dcd0.md b/.beastmode/artifacts/design/2026-04-12-lockfile-path-fix-dcd0.md new file mode 100644 index 00000000..5a732e38 --- /dev/null +++ b/.beastmode/artifacts/design/2026-04-12-lockfile-path-fix-dcd0.md @@ -0,0 +1,45 @@ +--- +phase: design +epic-id: bm-dcd0 +epic-slug: lockfile-path-fix-dcd0 +epic-name: Lockfile Path Fix +--- + +## Problem Statement + +The watch loop lockfile path is hardcoded to `cli/.beastmode-watch.lock`. When the CLI runs from a different working directory (e.g., inside a worktree), the resolved path points to a non-existent directory, causing an ENOENT error that prevents the dashboard from starting. + +## Solution + +Move the lockfile from `cli/.beastmode-watch.lock` to `.beastmode/.beastmode-watch.lock`. The `.beastmode/` directory is project-rooted and always exists, making the lockfile resilient to working directory changes. + +## User Stories + +1. As a developer running `beastmode dashboard`, I want the lockfile to resolve correctly regardless of working directory, so that the dashboard starts without ENOENT errors. +2. As a developer with a stale lockfile from a previous session, I want the stale-PID detection to work at the new path, so that I don't get locked out. +3. As a developer, I want the lockfile gitignored at its new path, so that it never gets committed. + +## Implementation Decisions + +- Change `lockfilePath()` in `cli/src/lockfile.ts` to resolve to `.beastmode/` instead of `cli/`. +- Update the test in `cli/src/__tests__/watch.test.ts` that hardcodes the `cli/` path for the stale lockfile scenario. +- Update `.gitignore` entry from `.beastmode-watch.lock` to `.beastmode/.beastmode-watch.lock`. +- Update context doc references in `context/design/orchestration.md` and `context/design/cli.md` to reflect the new path. + +## Testing Decisions + +- Existing lockfile tests (acquire, release, stale detection) cover the behavior — only the path constant and the stale-lockfile test setup need updating. +- No new tests required; the fix is a path change, not a behavior change. + +## Out of Scope + +- Lockfile format changes. +- Additional lockfile features (timeout, retry, etc.). + +## Further Notes + +None. + +## Deferred Ideas + +None. diff --git a/.beastmode/artifacts/implement/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix-dcd0.1.md b/.beastmode/artifacts/implement/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix-dcd0.1.md new file mode 100644 index 00000000..69f28aa3 --- /dev/null +++ b/.beastmode/artifacts/implement/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix-dcd0.1.md @@ -0,0 +1,35 @@ +--- +phase: implement +epic-id: bm-dcd0 +epic-slug: lockfile-path-fix-dcd0 +feature-id: lockfile-path-fix-dcd0.1 +feature-name: Lockfile Path Fix +feature-slug: lockfile-path-fix-dcd0.1 +status: completed +--- + +# Implementation Report: Lockfile Path Fix + +**Date:** 2026-04-12 +**Feature Plan:** .beastmode/artifacts/plan/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix.1.md +**Tasks completed:** 3/3 +**Review cycles:** 6 (spec: 3, quality: 3) +**Concerns:** 0 +**BDD verification:** skipped + +## Completed Tasks +- Task 1: Update lockfile path and test (haiku) — clean +- Task 2: Update .gitignore entry (haiku) — clean +- Task 3: Update context documentation (haiku) — clean + +## Concerns +None. + +## Blocked Tasks +None. + +## BDD Verification +- Result: skipped +- Reason: No Integration Test Scenarios in feature plan — skip gate classified this feature as non-behavioral. + +All tasks completed cleanly — no concerns or blockers. diff --git a/.beastmode/artifacts/implement/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix-dcd0.1.tasks.md b/.beastmode/artifacts/implement/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix-dcd0.1.tasks.md new file mode 100644 index 00000000..057bd5f9 --- /dev/null +++ b/.beastmode/artifacts/implement/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix-dcd0.1.tasks.md @@ -0,0 +1,220 @@ +# Lockfile Path Fix -- Write Tasks + +## Goal + +Move the watch loop lockfile from `cli/.beastmode-watch.lock` to `.beastmode/.beastmode-watch.lock` so that the lockfile resolves correctly regardless of working directory. The `.beastmode/` directory is project-rooted and always exists, eliminating ENOENT errors when the dashboard runs from worktree directories. + +## Architecture + +Single-constant path change in `cli/src/lockfile.ts`. All lockfile consumers (acquire, release, read, stale detection) call the same `lockfilePath()` function, so changing the constant propagates everywhere. No behavioral changes -- only the resolved filesystem path differs. + +## Tech Stack + +- Runtime: Bun +- Language: TypeScript +- Test runner: vitest (run via `bun --bun vitest run`) +- Test location: `cli/src/__tests__/watch.test.ts` + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `cli/src/lockfile.ts` | Modify | Change path resolution from `cli/` to `.beastmode/`; update module doc comment | +| `cli/src/__tests__/watch.test.ts` | Modify | Update hardcoded stale-lockfile path in test from `cli/` to `.beastmode/` | +| `.gitignore` | Modify | Replace bare `.beastmode-watch.lock` with specific `.beastmode/.beastmode-watch.lock` | +| `.beastmode/context/design/orchestration.md` | Modify | Update path reference in Recovery section | +| `.beastmode/context/design/cli.md` | Modify | Update path reference in Recovery Model section | + +## Wave Isolation + +| Wave | Tasks | Files | Parallel-safe | Reason | +|------|-------|-------|---------------|--------| +| 1 | T1, T2, T3 | T1: `cli/src/lockfile.ts`, `cli/src/__tests__/watch.test.ts` / T2: `.gitignore` / T3: `.beastmode/context/design/orchestration.md`, `.beastmode/context/design/cli.md` | no | T2 and T3 are independent but T1 must precede test verification; sequential is safer for 3 trivial tasks | + +## Tasks + +### Task 1: Update lockfile path and test + +**Wave:** 1 +**Depends on:** - + +**Files:** +- Modify: `cli/src/lockfile.ts:4,17` +- Modify: `cli/src/__tests__/watch.test.ts:57` +- Test: `cli/src/__tests__/watch.test.ts` + +- [x] **Step 1: Update the module doc comment in lockfile.ts** + +In `cli/src/lockfile.ts`, replace the doc comment on line 4 that says `cli/.beastmode-watch.lock` with `.beastmode/.beastmode-watch.lock`: + +```typescript +/** + * Lockfile manager — prevents duplicate watch instances. + * + * Creates .beastmode/.beastmode-watch.lock on start, removes on clean shutdown. + * Detects stale lockfiles by checking if the PID is still running. + */ +``` + +The old comment reads: + +```typescript +/** + * Lockfile manager — prevents duplicate watch instances. + * + * Creates cli/.beastmode-watch.lock on start, removes on clean shutdown. + * Detects stale lockfiles by checking if the PID is still running. + */ +``` + +- [x] **Step 2: Change the path constant in lockfilePath()** + +In `cli/src/lockfile.ts`, line 17, change `"cli"` to `".beastmode"` in the `lockfilePath` function: + +```typescript +function lockfilePath(projectRoot: string): string { + return resolve(projectRoot, ".beastmode", LOCKFILE_NAME); +} +``` + +The old code reads: + +```typescript +function lockfilePath(projectRoot: string): string { + return resolve(projectRoot, "cli", LOCKFILE_NAME); +} +``` + +- [x] **Step 3: Update the stale-lockfile test path** + +In `cli/src/__tests__/watch.test.ts`, line 57, change the hardcoded lockfile path in the "detects stale lockfile (dead PID)" test from `"cli"` to `".beastmode"`: + +```typescript + const lockPath = resolve(TEST_ROOT, ".beastmode", ".beastmode-watch.lock"); +``` + +The old code reads: + +```typescript + const lockPath = resolve(TEST_ROOT, "cli", ".beastmode-watch.lock"); +``` + +- [x] **Step 4: Run all lockfile tests to verify they pass** + +Run: `cd cli && bun --bun vitest run src/__tests__/watch.test.ts -t "lockfile" --reporter=verbose` + +Expected: All 4 lockfile tests PASS: +- acquires lock when no lockfile exists +- prevents duplicate lock acquisition +- releases lock cleanly +- detects stale lockfile (dead PID) + +Note: The test setup already creates both `cli/` and `.beastmode/` directories under `TEST_ROOT` (see `setupTestRoot()` at line 13-17 of watch.test.ts), so no test fixture changes are needed beyond the path string. + +- [x] **Step 5: Run the full watch test suite to verify nothing else broke** + +Run: `cd cli && bun --bun vitest run src/__tests__/watch.test.ts --reporter=verbose` + +Expected: All tests in the file PASS (lockfile, DispatchTracker, and WatchLoop tests). + +- [x] **Step 6: Commit** + +```bash +git add cli/src/lockfile.ts cli/src/__tests__/watch.test.ts +git commit -m "fix(lockfile): resolve lockfile path to .beastmode/ instead of cli/" +``` + +### Task 2: Update .gitignore entry + +**Wave:** 1 +**Depends on:** Task 1 + +**Files:** +- Modify: `.gitignore:10` + +- [x] **Step 1: Replace the gitignore entry** + +In `.gitignore`, line 10, replace the bare pattern `.beastmode-watch.lock` with the specific path `.beastmode/.beastmode-watch.lock`: + +``` +.beastmode/.beastmode-watch.lock +``` + +The old entry reads: + +``` +.beastmode-watch.lock +``` + +The full `.gitignore` after this change should read: + +``` +# Beastmode session state and worktrees +.beastmode/state/ +.beastmode/sessions/ +.beastmode/worktrees/ +.beastmode/pipeline/ +.beastmode/config.yaml +.claude/worktrees/ +.claude/settings.local.json +.beastmode/artifacts/**/*.output.json +.beastmode/.beastmode-watch.lock +.DS_Store +``` + +- [x] **Step 2: Verify the gitignore entry works** + +Run: `cd /Users/D038720/Code/github.com/bugroger/beastmode/.claude/worktrees/lockfile-path-fix-dcd0 && git check-ignore .beastmode/.beastmode-watch.lock` + +Expected: Output `.beastmode/.beastmode-watch.lock` (the path is ignored). + +- [x] **Step 3: Commit** + +```bash +git add .gitignore +git commit -m "fix(gitignore): update lockfile entry to match new .beastmode/ path" +``` + +### Task 3: Update context documentation + +**Wave:** 1 +**Depends on:** Task 1 + +**Files:** +- Modify: `.beastmode/context/design/orchestration.md:31` +- Modify: `.beastmode/context/design/cli.md:34` + +- [x] **Step 1: Update orchestration.md** + +In `.beastmode/context/design/orchestration.md`, line 31, replace the old path with the new one: + +```markdown +- Lockfile (`.beastmode/.beastmode-watch.lock`) prevents duplicate watch instances — single orchestrator guarantee +``` + +The old line reads: + +```markdown +- Lockfile (`cli/.beastmode-watch.lock`) prevents duplicate watch instances — single orchestrator guarantee +``` + +- [x] **Step 2: Update cli.md** + +In `.beastmode/context/design/cli.md`, line 34, replace the old path with the new one: + +```markdown +- Lockfile (`.beastmode/.beastmode-watch.lock`) prevents duplicate watch instances +``` + +The old line reads: + +```markdown +- Lockfile (`cli/.beastmode-watch.lock`) prevents duplicate watch instances +``` + +- [x] **Step 3: Commit** + +```bash +git add .beastmode/context/design/orchestration.md .beastmode/context/design/cli.md +git commit -m "docs: update lockfile path references in context docs" +``` diff --git a/.beastmode/artifacts/plan/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix.1.md b/.beastmode/artifacts/plan/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix.1.md new file mode 100644 index 00000000..09cf7239 --- /dev/null +++ b/.beastmode/artifacts/plan/2026-04-12-lockfile-path-fix-dcd0--lockfile-path-fix.1.md @@ -0,0 +1,41 @@ +--- +phase: plan +epic-id: bm-dcd0 +epic-slug: lockfile-path-fix-dcd0 +feature-name: Lockfile Path Fix +wave: 1 +--- + +# Lockfile Path Fix + +**Design:** `.beastmode/artifacts/design/2026-04-12-lockfile-path-fix-dcd0.md` + +## User Stories + +1. As a developer running `beastmode dashboard`, I want the lockfile to resolve correctly regardless of working directory, so that the dashboard starts without ENOENT errors. +2. As a developer with a stale lockfile from a previous session, I want the stale-PID detection to work at the new path, so that I don't get locked out. +3. As a developer, I want the lockfile gitignored at its new path, so that it never gets committed. + +## What to Build + +Change the lockfile resolution from the `cli/` subdirectory to the `.beastmode/` directory, which is project-rooted and always exists regardless of working directory. + +The lockfile module's path function currently resolves to `/cli/`. Change it to resolve to `/.beastmode/`. This is a single-constant change — all consumers (acquire, release, read, stale detection) go through the same path function, so behavior is preserved. + +Update the test that manually constructs the lockfile path for the stale-PID scenario to use the new `.beastmode/` directory. + +Update the `.gitignore` entry to match the new specific path `.beastmode/.beastmode-watch.lock` instead of the bare pattern `.beastmode-watch.lock`. + +Update context documentation references in orchestration and CLI design docs that cite the old `cli/.beastmode-watch.lock` path. + +## Integration Test Scenarios + + + +## Acceptance Criteria + +- [ ] `lockfilePath()` resolves to `/.beastmode/.beastmode-watch.lock` +- [ ] Existing lockfile tests pass (acquire, release, stale detection) with no behavior changes +- [ ] `.gitignore` contains the new path entry +- [ ] Context docs reference the new path +- [ ] Dashboard starts without ENOENT when run from a worktree directory diff --git a/.beastmode/artifacts/release/2026-04-12-lockfile-path-fix-dcd0.md b/.beastmode/artifacts/release/2026-04-12-lockfile-path-fix-dcd0.md new file mode 100644 index 00000000..bfa93f32 --- /dev/null +++ b/.beastmode/artifacts/release/2026-04-12-lockfile-path-fix-dcd0.md @@ -0,0 +1,34 @@ +--- +phase: release +epic-id: lockfile-path-fix-dcd0 +epic-slug: lockfile-path-fix-dcd0 +bump: patch +--- + +# Release: lockfile-path-fix-dcd0 + +**Version:** v0.127.2 +**Date:** 2026-04-12 + +## Highlights + +Fixes ENOENT error when starting the dashboard from a worktree by moving the watch loop lockfile from `cli/` to the project-rooted `.beastmode/` directory. + +## Fixes + +- Resolve lockfile path to `.beastmode/.beastmode-watch.lock` instead of `cli/.beastmode-watch.lock` +- Update `.gitignore` entry to match new lockfile path + +## Docs + +- Update lockfile path references in `context/design/orchestration.md` and `context/design/cli.md` + +## Full Changelog + +- `8ba83c61` fix(lockfile): resolve lockfile path to .beastmode/ instead of cli/ +- `e4876797` fix(gitignore): update lockfile entry to match new .beastmode/ path +- `6a4bcb9a` docs: update lockfile path references in context docs +- `cacdd19c` implement(lockfile-path-fix-dcd0): checkpoint +- `5f49351b` validate(lockfile-path-fix-dcd0): checkpoint +- `64dcf079` plan(lockfile-path-fix-dcd0): checkpoint +- `c076f7ed` design(lockfile-path-fix-dcd0): checkpoint diff --git a/.beastmode/artifacts/validate/2026-04-12-lockfile-path-fix-dcd0.md b/.beastmode/artifacts/validate/2026-04-12-lockfile-path-fix-dcd0.md new file mode 100644 index 00000000..3cf4d043 --- /dev/null +++ b/.beastmode/artifacts/validate/2026-04-12-lockfile-path-fix-dcd0.md @@ -0,0 +1,56 @@ +--- +phase: validate +epic-id: lockfile-path-fix-dcd0 +epic-slug: lockfile-path-fix-dcd0 +status: passed +--- + +# Validation Report: Lockfile Path Fix + +**Date:** 2026-04-12 +**Epic:** lockfile-path-fix-dcd0 + +## Status: PASS + +### Tests + +**Lockfile-specific tests:** 4/4 PASS +- acquires lock when no lockfile exists +- prevents duplicate lock acquisition +- releases lock cleanly +- detects stale lockfile (dead PID) + +**Full watch test suite:** 26/26 PASS + +**Full project test suite:** 1812 pass, 2 fail + +| Failing Test | File Touched by Epic | Verdict | +|---|---|---| +| readme-update.integration.test.ts — "contains npx beastmode uninstall command" | No | Pre-existing | +| tree-view.test.ts — "renders leaf entries under feature" | No | Pre-existing | + +Both failures are pre-existing on main, unrelated to lockfile path changes. + +**Baseline comparison:** 1812 passing vs 1773 baseline (post dashboard-spinner-bug-fixes) — net +39, no regressions introduced. + +### Types + +37 type errors — all pre-existing in untouched files. Matches baseline (37 errors across 13 files from dashboard-spinner-bug-fixes baseline). + +No new type errors introduced. + +### Lint + +Skipped — no lint command configured. + +### Custom Gates + +None configured. + +### Acceptance Criteria Verification + +From design doc: +1. **Lockfile resolves to `.beastmode/.beastmode-watch.lock`** — PASS (verified via `lockfilePath()` constant change + all 4 lockfile tests passing) +2. **No behavioral changes** — PASS (26/26 watch tests pass, same behavior, different path) +3. **`.gitignore` covers new path** — PASS (entry updated to `.beastmode/.beastmode-watch.lock`) +4. **Context docs updated** — PASS (orchestration.md and cli.md updated) diff --git a/.beastmode/context/design/cli.md b/.beastmode/context/design/cli.md index 6e077176..eeba5a73 100644 --- a/.beastmode/context/design/cli.md +++ b/.beastmode/context/design/cli.md @@ -31,7 +31,7 @@ ## Recovery Model - State files are the recovery point, not sessions — stateless session model - On startup, scan for existing worktrees with uncommitted changes and re-dispatch from last committed state -- Lockfile (`cli/.beastmode-watch.lock`) prevents duplicate watch instances +- Lockfile (`.beastmode/.beastmode-watch.lock`) prevents duplicate watch instances ## Manifest Lifecycle - CLI creates manifest at first phase dispatch (design) via store.create(slug) before dispatching — manifest exists throughout the entire skill session diff --git a/.beastmode/context/design/orchestration.md b/.beastmode/context/design/orchestration.md index d5f69ba5..e8f41ec9 100644 --- a/.beastmode/context/design/orchestration.md +++ b/.beastmode/context/design/orchestration.md @@ -28,7 +28,7 @@ ## Recovery - State files are the recovery point, not sessions — on startup, scan for existing worktrees with uncommitted changes - ALWAYS re-dispatch from last committed state on recovery — no session persistence required -- Lockfile (`cli/.beastmode-watch.lock`) prevents duplicate watch instances — single orchestrator guarantee +- Lockfile (`.beastmode/.beastmode-watch.lock`) prevents duplicate watch instances — single orchestrator guarantee ## Lifecycle - Start via `beastmode dashboard`, stop via Ctrl+C — foreground process with explicit control diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e9fdb0ce..34c51ea1 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ { "name": "beastmode", "description": "Agentic workflow skills for Claude Code. Activate beastmode.", - "version": "0.127.1", + "version": "0.127.2", "source": "./plugin" } ] diff --git a/.gitignore b/.gitignore index 22ee0073..a486737f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ .claude/worktrees/ .claude/settings.local.json .beastmode/artifacts/**/*.output.json -.beastmode-watch.lock +.beastmode/.beastmode-watch.lock .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d202368..223ca0c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to beastmode. --- +## v0.127.2 — Lockfile Path Fix (2026-04-12) + +Fix ENOENT error when starting the dashboard from a worktree by moving the watch loop lockfile from `cli/` to the project-rooted `.beastmode/` directory. + +### Fixes + +- Resolve lockfile path to `.beastmode/.beastmode-watch.lock` instead of `cli/.beastmode-watch.lock` +- Update `.gitignore` entry to match new lockfile path + +### Docs + +- Update lockfile path references in orchestration and CLI context docs + +--- + ## v0.127.1 — One-Sentence Project Bootstrap (2026-04-12) Replace the multi-step Install section in the README with a single "Get the Party Started" prose paragraph that users paste into Claude Code to bootstrap their entire project in one shot. diff --git a/cli/src/__tests__/watch.test.ts b/cli/src/__tests__/watch.test.ts index 17f5df8f..42379287 100644 --- a/cli/src/__tests__/watch.test.ts +++ b/cli/src/__tests__/watch.test.ts @@ -54,7 +54,7 @@ describe("lockfile", () => { it("detects stale lockfile (dead PID)", () => { // Write a lockfile with a definitely-dead PID - const lockPath = resolve(TEST_ROOT, "cli", ".beastmode-watch.lock"); + const lockPath = resolve(TEST_ROOT, ".beastmode", ".beastmode-watch.lock"); writeFileSync( lockPath, JSON.stringify({ pid: 999999999, startedAt: new Date().toISOString() }), diff --git a/cli/src/lockfile.ts b/cli/src/lockfile.ts index b03b19cc..93bfa14b 100644 --- a/cli/src/lockfile.ts +++ b/cli/src/lockfile.ts @@ -1,7 +1,7 @@ /** * Lockfile manager — prevents duplicate watch instances. * - * Creates cli/.beastmode-watch.lock on start, removes on clean shutdown. + * Creates .beastmode/.beastmode-watch.lock on start, removes on clean shutdown. * Detects stale lockfiles by checking if the PID is still running. */ @@ -14,7 +14,7 @@ import type { LockfileInfo } from "./dispatch/index.js"; const LOCKFILE_NAME = ".beastmode-watch.lock"; function lockfilePath(projectRoot: string): string { - return resolve(projectRoot, "cli", LOCKFILE_NAME); + return resolve(projectRoot, ".beastmode", LOCKFILE_NAME); } /** Check if a process with the given PID is still running. */ diff --git a/package.json b/package.json index d79d4d0a..b653a678 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "files": [ "src/npx-cli/", "plugin/", - "cli/" + "cli/", + ".claude-plugin/" ], "scripts": { "test:npx": "node --test src/npx-cli/__tests__/*.test.mjs", diff --git a/plugin/plugin.json b/plugin/plugin.json index 953c890b..b6d2740e 100644 --- a/plugin/plugin.json +++ b/plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "beastmode", "description": "Agentic workflow skills for Claude Code. Activate beastmode.", - "version": "0.127.1", + "version": "0.127.2", "author": { "name": "bugroger" }, diff --git a/src/npx-cli/config-merger.mjs b/src/npx-cli/config-merger.mjs index 2948c668..1a0389cb 100644 --- a/src/npx-cli/config-merger.mjs +++ b/src/npx-cli/config-merger.mjs @@ -37,8 +37,8 @@ async function mergeKnownMarketplaces(homeDir) { data['beastmode-marketplace'] = { source: { - source: 'npm', - name: 'beastmode', + source: 'directory', + path: join(homeDir, '.claude', 'plugins', 'marketplaces', 'bugroger'), }, installLocation: join(homeDir, '.claude', 'plugins', 'marketplaces', 'bugroger'), lastUpdated: new Date().toISOString(), diff --git a/src/npx-cli/index.mjs b/src/npx-cli/index.mjs index 7f142d9d..f2f5b842 100755 --- a/src/npx-cli/index.mjs +++ b/src/npx-cli/index.mjs @@ -18,6 +18,9 @@ switch (command) { homeDir: homedir(), packageDir, }); + if (!result.success) { + console.error(`Install failed at step "${result.step}": ${result.error}`); + } process.exit(result.success ? 0 : 1); break; } diff --git a/src/npx-cli/plugin-copier.mjs b/src/npx-cli/plugin-copier.mjs index 01c76646..93223abc 100644 --- a/src/npx-cli/plugin-copier.mjs +++ b/src/npx-cli/plugin-copier.mjs @@ -1,5 +1,5 @@ // src/npx-cli/plugin-copier.mjs -import { cp, rm, mkdir } from 'node:fs/promises'; +import { cp, rm, mkdir, symlink } from 'node:fs/promises'; import { join } from 'node:path'; /** @@ -19,12 +19,12 @@ export async function copyPlugin({ homeDir, packageDir, version }) { // Clean-replace marketplace directory await rm(marketplaceDir, { recursive: true, force: true }); - await mkdir(marketplaceDir, { recursive: true }); + await mkdir(join(marketplaceDir, '.claude-plugin'), { recursive: true }); - // Copy marketplace.json to marketplace dir + // Copy marketplace.json into .claude-plugin/ subdir (where Claude Code expects it) await cp( join(pluginMetaDir, 'marketplace.json'), - join(marketplaceDir, 'marketplace.json') + join(marketplaceDir, '.claude-plugin', 'marketplace.json') ); // Clean-replace cache directory @@ -34,6 +34,9 @@ export async function copyPlugin({ homeDir, packageDir, version }) { // Copy plugin tree to cache dir (includes plugin.json, skills/, agents/, hooks/) await cp(pluginSourceDir, cacheDir, { recursive: true }); + // Symlink plugin content into marketplace dir so "source": "./plugin" resolves + await symlink(cacheDir, join(marketplaceDir, 'plugin')); + console.log(`Plugin files copied to ${marketplaceDir}`); console.log(`Plugin cache written to ${cacheDir}`); }