@jaybeeuu/agent-cortex 1.39.0 → 1.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.agents/skills/style-versioning/SKILL.md +1 -1
  2. package/.github/workflows/ci.yml +47 -14
  3. package/AGENTS.md +35 -8
  4. package/CHANGELOG.md +60 -0
  5. package/README.md +174 -97
  6. package/agents/README.md +14 -11
  7. package/bin/agent-cortex.mjs +55 -10
  8. package/bin/installers/claude.mjs +674 -174
  9. package/bin/installers/copilot.mjs +13 -18
  10. package/bin/installers/pi.mjs +293 -51
  11. package/claude/settings.json +4 -0
  12. package/{claude → claude-extras}/scripts/statusline-command.sh +32 -4
  13. package/docs/claude-hooks.md +15 -3
  14. package/docs/ideas/ban-content-assertion-tests.md +73 -0
  15. package/docs/ideas/bead-progress-notes-for-session-resumption.md +75 -0
  16. package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +70 -0
  17. package/docs/ideas/ctx-alias-version-and-update-commands.md +65 -0
  18. package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +67 -0
  19. package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +36 -0
  20. package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +99 -0
  21. package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +33 -0
  22. package/docs/ideas/extension-manifests.md +55 -14
  23. package/docs/ideas/improve-pi-system-prompt.md +5 -2
  24. package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +95 -0
  25. package/docs/ideas/record-idea-implementation-questions.md +68 -0
  26. package/docs/ideas/self-flushing-session.md +72 -0
  27. package/docs/ideas/state-in-beads-not-md.md +69 -0
  28. package/docs/ideas/task-session-context.local.md +95 -0
  29. package/docs/ideas/test-data-creation-helpers.md +92 -0
  30. package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +68 -0
  31. package/docs/ideas/worktree-housekeeping-for-closed-beads.md +39 -0
  32. package/docs/technical-direction/harness-config.md +161 -0
  33. package/docs/technical-direction/migration-execution-labels.md +60 -23
  34. package/extensions/agent-guardrails/README.md +45 -0
  35. package/extensions/agent-guardrails/guardrails.test.ts +42 -0
  36. package/extensions/agent-guardrails/guardrails.ts +66 -0
  37. package/extensions/agent-guardrails/index.ts +22 -0
  38. package/extensions/agent-guardrails/node_modules/.bin/pi +21 -0
  39. package/extensions/agent-guardrails/node_modules/.bin/tsc +21 -0
  40. package/extensions/agent-guardrails/node_modules/.bin/tsserver +21 -0
  41. package/extensions/agent-guardrails/node_modules/.bin/tsx +21 -0
  42. package/extensions/agent-guardrails/package.json +19 -0
  43. package/extensions/agent-guardrails/tsconfig.json +12 -0
  44. package/extensions/agent-modes/README.md +12 -5
  45. package/extensions/agent-modes/discover.test.ts +30 -7
  46. package/extensions/agent-modes/discover.ts +1 -1
  47. package/extensions/agent-modes/index.test.ts +208 -0
  48. package/extensions/agent-modes/index.ts +23 -6
  49. package/extensions/agent-modes/package.json +1 -1
  50. package/extensions/skill-stats/index.test.ts +3 -2
  51. package/lib/cli.mjs +30 -12
  52. package/lib/pi-packages.mjs +169 -0
  53. package/package.json +8 -4
  54. package/pi/settings.json +23 -12
  55. package/plugin.json +1 -1
  56. package/scripts/build-copilot-agents.mjs +1 -1
  57. package/scripts/check-no-sync.mjs +49 -0
  58. package/scripts/lib/compose-agent.mjs +32 -14
  59. package/scripts/lib/fs.mjs +31 -0
  60. package/skills/engineering/grill-with-docs/SKILL.md +1 -1
  61. package/skills/planning/classify-bead/REFERENCE.md +85 -0
  62. package/skills/planning/classify-bead/SKILL.md +32 -36
  63. package/skills/planning/classify-bead/scripts/classify-bead.mjs +292 -0
  64. package/skills/planning/create-task/SKILL.md +19 -10
  65. package/skills/planning/write-a-prd/SKILL.md +2 -2
  66. package/skills/style/style-code/REFERENCE.md +32 -2
  67. package/skills/style/style-code/SKILL.md +5 -21
  68. package/skills/style/style-tests/SKILL.md +69 -17
  69. package/skills/workflow/hitl-collab/SKILL.md +1 -1
  70. package/skills/workflow/run-pipeline-stage/SKILL.md +13 -1
  71. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +12 -0
  72. package/test/classify-bead.test.mjs +277 -0
  73. package/test/claude-installer.test.mjs +199 -139
  74. package/test/cli.test.mjs +95 -41
  75. package/test/copilot-installer.test.mjs +92 -82
  76. package/test/fs.test.mjs +76 -0
  77. package/test/helpers/fake-claude.mjs +152 -0
  78. package/test/installer.test.mjs +542 -57
  79. package/test/pi-installer.test.mjs +457 -102
  80. package/test/pi-packages.test.mjs +295 -0
  81. package/token-map.README.md +23 -12
  82. package/token-map.json +8 -7
  83. package/.claude-plugin/marketplace.json +0 -16
  84. package/claude/.claude-plugin/plugin.json +0 -11
  85. package/claude/agents/plan.md +0 -58
  86. package/claude/agents/ralph-plan.md +0 -121
  87. package/claude/agents/ralph.md +0 -193
  88. package/claude/agents/strategy.md +0 -111
  89. package/claude/hooks/scripts/notify.mjs +0 -54
  90. package/claude/hooks.json +0 -38
  91. /package/{claude → claude-extras}/.mcp.json +0 -0
package/README.md CHANGED
@@ -9,8 +9,6 @@ pi, and [Claude Code](https://code.claude.com/docs) (as a plugin).
9
9
  ```
10
10
  agent-cortex/
11
11
  ├── plugin.json # Copilot plugin manifest
12
- ├── .claude-plugin/
13
- │ └── marketplace.json # Claude marketplace (exposes ./claude as the "jaybeeuu" market)
14
12
  ├── agents/ # Canonical agents — composable <name>/ dirs (see agents/README.md)
15
13
  │ ├── *.agent.md # ralph, ralph-plan, plan, strategy — GENERATED from <name>/ by scripts/build-copilot-agents.mjs
16
14
  │ ├── ralph/ # composable form (shared agent.md + per-harness pi/, copilot/, claude/)
@@ -27,28 +25,26 @@ agent-cortex/
27
25
  │ ├── style/ # style-code, style-tests, style-comms, style-documentation
28
26
  │ └── workflow/ # ralph, run-pipeline-stage, create-task, …
29
27
  ├── extensions/ # pi extensions (pi only)
28
+ │ ├── agent-guardrails/ # behavioural circuit-breakers in the system prompt
30
29
  │ ├── agent-modes/ # switchable agent modes (reads composable agents/)
31
30
  │ ├── skill-stats/
32
31
  │ └── notify/
33
- ├── pi/ # Global pi configuration (see below)
34
- │ └── settings.json
32
+ ├── pi/ # Committed pi config templates (see below)
33
+ │ ├── settings.json
34
+ │ └── keybindings.json
35
35
  ├── token-map.json # canonical tool/path/agent names per harness (install-time token substitution)
36
36
  ├── token-map.README.md # design decisions behind token-map.json
37
37
  ├── bin/
38
38
  │ ├── agent-cortex.mjs # CLI entrypoint
39
39
  │ └── installers/
40
40
  │ ├── copilot.mjs # shared generator: agent-cortex install copilot + scripts/build-copilot-agents.mjs
41
- │ └── claude.mjs # install-time generator + Claude Code registrar (pnpm build:claude = generate-only --output form)
41
+ │ └── claude.mjs # materialises ~/.agent-cortex/claude + ~/.claude/settings.json, registers with Claude Code (--output <dir> = generate-only)
42
42
  ├── scripts/
43
43
  │ └── build-copilot-agents.mjs # thin wrapper over bin/installers/copilot.mjs (regenerates agents/*.agent.md)
44
- └── claude/ # Self-contained Claude Code plugin (GENERATED — do not hand-edit)
45
- ├── .claude-plugin/
46
- │ └── plugin.json
47
- ├── .mcp.json # MCP servers (context7, github) — hand-authored
48
- ├── hooks.json # SessionStart + Notification hooks — generated from hooks/claude/
49
- ├── hooks/ # hook scripts (e.g. scripts/notify.mjs) — generated from hooks/claude/
50
- ├── agents/ # composed from agents/<name>/claude/ + copied from agents-native/
51
- └── skills/ # symlinks to the grouped skills/ dirs (29 skills)
44
+ ├── claude/ # Committed Claude user-settings template (settings.json) — merged into ~/.claude/settings.json
45
+ └── claude-extras/ # Hand-authored Claude plugin extras
46
+ ├── .mcp.json # MCP servers (context7, github) — copied into installs
47
+ └── scripts/ # statusline-command.sh — copied into installs (executable)
52
48
  ```
53
49
 
54
50
  The same `agents/` and `skills/` power three harnesses (Copilot, pi, Claude Code).
@@ -61,36 +57,45 @@ install-time and build-time output can never diverge) and committed so Copilot C
61
57
  The `{{TOOL:...}}` / `{{PATH:...}}` tokens written in agent and skill files are resolved
62
58
  per harness at install time from `token-map.json`, the single source of truth for
63
59
  canonical tool/path/agent names (see `token-map.README.md` and the `contract` section).
64
- The `claude/` subtree is **generated** by the install-time generator
65
- (`bin/installers/claude.mjs`). A plain `agent-cortex install claude` regenerates the
66
- subtree AND registers it with Claude Code by driving the `claude plugin` CLI (marketplace
67
- add → install → marketplace update → plugin update). `pnpm build:claude` is the pure,
68
- generate-only alias — it passes `--output claude`, so CI and release drift-checks never
69
- spawn the claude CLI or touch `~/.claude`. CI regenerates it and checks
70
- it is never stale:
71
-
72
- - **Skills** stay single-source — `claude/skills/<name>` are symlinks into the grouped
73
- `skills/<group>/<name>` dirs (Claude discovers skills only one level deep, so the
74
- grouping is flattened via links, not copies).
75
- - **Agents** can't be shared files (the frontmatter formats differ), so `claude/agents/*.md`
76
- are composed from the canonical `agents/<name>/` directories' `claude/` harness dirs
60
+ The Claude plugin is **materialised** at install time by `bin/installers/claude.mjs`. A
61
+ plain `agent-cortex install claude` copies the plugin into the home install root
62
+ (`~/.agent-cortex/claude`), writes a marketplace manifest at
63
+ `~/.agent-cortex/.claude-plugin/marketplace.json` exposing it, and registers it with
64
+ Claude Code by driving the `claude plugin` CLI — state-checked and idempotent (a fresh
65
+ install adds the marketplace + installs the plugin; a re-run updates what state says is
66
+ out of date; a repeat install at the same version is a no-op; a missing or pre-v2 CLI
67
+ warns and prints the manual registration commands instead of failing). The generated
68
+ `claude/` plugin subtree is never committed (only the `claude/settings.json` template and
69
+ the hand-authored `claude-extras/` are), so CI validates the materialiser itself — a
70
+ temp-dir install plus structural checks:
71
+
72
+ - **Skills** stay single-source — the installer copies each `skills/<group>/<name>/` dir
73
+ flat into `skills/<name>/` (Claude discovers skills only one level deep) with
74
+ `{{TOOL:...}}` / `{{PATH:...}}` tokens substituted against token-map.json's claude
75
+ column — no symlinks, so literal tokens never reach Claude.
76
+ - **Agents** can't be shared files (the frontmatter formats differ), so the materialised
77
+ plugin's `agents/*.md` are composed from the canonical `agents/<name>/` directories' `claude/`
78
+ harness dirs
77
79
  (frontmatter.json + section files), exactly like the Copilot flats are composed from their
78
80
  `copilot/` dirs. Claude only loads agents from a plugin's default `agents/` dir, so the plugin
79
- root is `claude/` — isolating it from the Copilot `.agent.md` files.
81
+ ships them at its own root (default `~/.agent-cortex/claude/agents/`) — isolating them from the
82
+ Copilot `.agent.md` files.
80
83
  - **Claude-native agents** that have no Copilot equivalent live in `agents-native/*.md` and are
81
- copied verbatim into `claude/agents/`. `ralph` is one: it is reimplemented for Claude around
84
+ copied verbatim into the materialised plugin's `agents/`. `ralph` is one: it is reimplemented
85
+ for Claude around
82
86
  background workers + an independent review gate (the Copilot Ralph's `task`/`read_agent`
83
87
  poll loop has no Claude equivalent), so it can't be mechanically converted.
84
- - **Manifests** are generated too: `claude/.claude-plugin/plugin.json` (its `version` tracks
85
- `package.json`, so it never goes stale) and `claude/hooks.json` (copied from the canonical
88
+ - **Manifests** are generated too: the plugin's `.claude-plugin/plugin.json` (its `version`
89
+ tracks `package.json`, so it never goes stale) and `hooks.json` (copied from the canonical
86
90
  `hooks/claude/hooks.json` source), plus any support files under `hooks/claude/` bundled into
87
- `claude/hooks/` so hook commands can reach them via `$CLAUDE_PLUGIN_ROOT`. `claude/.mcp.json`
88
- and `claude/scripts/` stay hand-authored. See `docs/claude-hooks.md` for the extension→hook
89
- mapping and the rejections (auto-name, skill-stats, subagent, agent-modes).
91
+ the plugin's `hooks/` so hook commands can reach them via `$CLAUDE_PLUGIN_ROOT`. Hand-authored
92
+ extras — `.mcp.json` and `scripts/` — are copied into every install from the `claude-extras/`
93
+ dir, which is their canonical store. See `docs/claude-hooks.md` for the
94
+ extension→hook mapping and the rejections (auto-name, agent-guardrails, skill-stats, subagent, agent-modes).
90
95
 
91
96
  Edit the sources (`agents/<name>/` composable dirs, `agents-native/*.md`, `skills/**`,
92
- `hooks/claude/`, `package.json`), never anything under `claude/` or the generated
93
- `agents/*.agent.md` files.
97
+ `hooks/claude/`, `claude-extras/`, `package.json`), never the generated
98
+ `agents/*.agent.md` files or anything under the materialised `~/.agent-cortex/claude`.
94
99
 
95
100
  ## CI
96
101
 
@@ -100,6 +105,15 @@ does its own checkout and `pnpm install` rather than sharing build artifacts fro
100
105
  the `setup` job — pnpm workspace symlinks don't survive artifact upload/download,
101
106
  so artifact sharing would break the workspace resolution that the build depends on.
102
107
 
108
+ The repo commits no generated `claude/` plugin output, so the `claude-plugin-check` job
109
+ validates the Claude plugin **materialiser** instead of diffing a committed mirror:
110
+ it runs `node bin/agent-cortex.mjs install claude --output <tmp dir>` and checks the
111
+ result structurally — `plugin.json` version tracks `package.json`, every generated and
112
+ hand-authored piece is present, no literal `{{TOOL:...}}`/`{{PATH:...}}` tokens survive,
113
+ and no symlinks leak into the copied tree. The Copilot drift check
114
+ (`pnpm build:copilot` + `git diff --exit-code -- 'agents/*.agent.md'`) still guards the
115
+ committed flat agent files.
116
+
103
117
  A separate `changeset-check` job runs only on pull requests and fails any PR that
104
118
  touches a versioned path (`extensions/`, `skills/`, `agents/`, `package.json`, or
105
119
  `plugin.json`) without a changeset in `.changeset/`. Add one with `pnpm changeset` —
@@ -109,7 +123,9 @@ On pushes to `main`, a `release` job (gated on `lint`, `test`, and
109
123
  `claude-plugin-check`) runs changesets to open a `chore: version packages` PR when
110
124
  changesets are pending, then publishes to npm once it lands. The version step runs
111
125
  `pnpm version-packages` — bumping `package.json`, syncing `plugin.json`, and
112
- regenerating the committed generated output so the drift gates stay green; the
126
+ regenerating the committed Copilot agent files (the Claude plugin is materialised at
127
+ install time with the package version, so it has no committed output to regenerate)
128
+ so the drift gates stay green; the
113
129
  publish step runs `pnpm publish-package` (pack + provenance publish). Publish
114
130
  authenticates via npm Trusted Publishing (OIDC) — no npm token or GitHub
115
131
  secret is needed, only the one-time npm-side setup on npmjs.com (package →
@@ -118,30 +134,51 @@ are sourced entirely from `main`.
118
134
 
119
135
  ## Installation
120
136
 
121
- ### Symlink as global pi config
122
-
123
- This repo's `pi/settings.json` is symlinked to `~/.pi/agent/settings.json`,
124
- making it the canonical store for personal pi agent configuration:
125
-
126
- ```sh
127
- ~/.pi/agent/settings.json -> /path/to/agent-cortex/pi/settings.json
128
- ```
129
-
130
- All `pi install` / `pi remove` commands write to this file, and changes are
131
- committed to git. On a fresh machine:
132
-
133
- ```sh
134
- git clone https://github.com/jaybeeuu/agent-cortex
135
- ln -sf "$PWD/agent-cortex/pi/settings.json" ~/.pi/agent/settings.json
136
- ```
137
+ ### Pi config files (`settings.json` + `keybindings.json`)
138
+
139
+ `agent-cortex install pi` materialises `~/.pi/agent/settings.json` and
140
+ `~/.pi/agent/keybindings.json` as real CLI-managed files — the legacy symlinks
141
+ into this repo's `pi/` directory are gone. `pi/settings.json` and
142
+ `pi/keybindings.json` are the committed **templates**; the live files exist only
143
+ on the machine, so re-run the installer after pulling changes.
144
+
145
+ `settings.json` is merged. The template supplies defaults for keys the live file
146
+ lacks, every value already present in the live file wins, and keys the template
147
+ does not know about are preserved — so personal config (and anything pi itself
148
+ wrote) survives re-install. The one key the CLI owns outright is `packages`: it
149
+ is rebuilt from the template with the repo path entry resolved against the live
150
+ settings file.
151
+
152
+ Two consequences follow from that precedence:
153
+
154
+ - **Template changes to non-`packages` keys never reach an existing install.**
155
+ Because the live value always wins, editing a default in `pi/settings.json`
156
+ only affects a fresh install (or a manually deleted live file) — edit the live
157
+ file to adopt a new default.
158
+ - **`pi install npm:<pkg>` entries do not survive re-install.** `packages` is
159
+ rebuilt from the template, so any `npm:` package pi added to the live file is
160
+ dropped on the next `agent-cortex install pi`. Declare long-lived packages in
161
+ the repo template instead.
162
+
163
+ An unparseable live `settings.json` is warned about and overwritten.
164
+ `keybindings.json` follows a checksum rule: written from the template, refreshed
165
+ when the template changes, and left untouched once you edit it by hand (delete
166
+ it to re-adopt the template).
137
167
 
138
168
  ### Pi package dependencies
139
169
 
140
- These packages are declared in `pi/settings.json` and auto-installed by pi:
170
+ These packages are declared in the package manifest (`package.json` → `pi.packages`) and
171
+ provisioned by `agent-cortex install pi`, so a clean `~/.pi` gets the tools they provide
172
+ without a manual `pi install`. They are also declared in the template `pi/settings.json`,
173
+ so the materialised `settings.json` registers them with pi.
141
174
 
142
175
  | Package | Version | Purpose |
143
176
  |---|---|---|
144
- | [`pi-web-access`](https://www.npmjs.com/package/pi-web-access) | 0.10.7 | Web search, URL fetching, GitHub repo access, PDF/YouTube/video analysis |
177
+ | [`pi-web-access`](https://www.npmjs.com/package/pi-web-access) | 0.10.7 | Web search, URL fetching, GitHub repo access, PDF/YouTube/video analysis (`fetch_content`) |
178
+ | [`pi-questions`](https://www.npmjs.com/package/pi-questions) | latest | Structured interactive questions (`ask_questions`) |
179
+
180
+ Pass `--no-provision` to skip the package install step (e.g. offline machines); a
181
+ missing `pi` CLI or failed install warns and leaves the rest of the install intact.
145
182
 
146
183
  Desktop notifications are handled by the local `extensions/notify/` extension
147
184
  (replaces the former `pi-notify` dependency). It sends an OSC desktop
@@ -159,6 +196,7 @@ composed agents and token-substituted skills into pi's user scope:
159
196
  agent-cortex install pi
160
197
  # → ~/.pi/agent/agents/<name>.agent.md (ralph, plan, ralph-plan, strategy)
161
198
  # → ~/.pi/agent/skills/ (token-substituted skill tree)
199
+ # → ~/.pi/agent/settings.json + keybindings.json (merged from the repo templates)
162
200
  ```
163
201
 
164
202
  Flags:
@@ -166,8 +204,9 @@ Flags:
166
204
  | Flag | Meaning |
167
205
  | --- | --- |
168
206
  | `--dry-run` | Show what would be installed without writing anything |
169
- | `--output <dir>` | Install into `<dir>/agents` and `<dir>/skills` (default `~/.pi/agent`) |
207
+ | `--output <dir>` | Install into `<dir>/agents`, `<dir>/skills` and the config files (default `~/.pi/agent`) |
170
208
  | `--plugin-root <dir>` | Override the plugin root used for `{{PATH:...}}` tokens (default: token-map.json's pi value — use it for checkout or symlinked installs) |
209
+ | `--no-provision` | Skip provisioning the third-party pi packages declared in `package.json` `pi.packages` (default: install them via the `pi` CLI) |
171
210
 
172
211
  Re-run whenever you pull changes (`git pull` + reinstall, or after `pnpm build:copilot`).
173
212
 
@@ -185,32 +224,43 @@ copilot plugin install ./agent-cortex
185
224
 
186
225
  ### Claude Code plugin (separate)
187
226
 
188
- The Claude plugin is the self-contained `claude/` subtree (manifest at
189
- `claude/.claude-plugin/plugin.json`), containing **4 agents** (`strategy`, `plan`,
190
- `ralph-plan`, `ralph`), **29 skills**, `SessionStart` + `Notification` hooks, and 2 MCP servers.
191
-
192
- `claude/` is committed (the marketplace install flow reads it straight from the working
193
- tree, so a fresh clone installs as-is), and rebuilt via the install-time generator if you
194
- change the sources:
227
+ A plain `agent-cortex install claude` **materialises** the plugin into the home install
228
+ root `~/.agent-cortex/claude` — **4 agents** (`strategy`, `plan`, `ralph-plan`, `ralph`),
229
+ **29 skills** (copied flat per skill, `{{TOOL:...}}` / `{{PATH:...}}` token-substituted — no
230
+ symlinks), `SessionStart` + `Notification` hooks, and 2 MCP servers — writes a marketplace
231
+ manifest at `~/.agent-cortex/.claude-plugin/marketplace.json` exposing `./claude`, and
232
+ registers it with Claude Code, and merges the committed `claude/settings.json` template into
233
+ `~/.claude/settings.json`: the template supplies defaults, personal values win, and the
234
+ installer owns exactly `enabledPlugins` + `extraKnownMarketplaces` — every other key
235
+ (`permissions`, `hooks`, `env`, `statusLine`, unknown keys) is preserved. The only committed
236
+ Claude config is that template plus the hand-authored extras in `claude-extras/`
237
+ (`.mcp.json`, `scripts/`); the plugin subtree is generated at install time — there is nothing
238
+ to drift.
239
+
240
+ The generate-only `--output <dir>` form is for previewing and CI validation; the
241
+ documented path is the plain install to `~/.agent-cortex/claude`:
195
242
 
196
243
  ```sh
197
244
  pnpm build:copilot # or: node scripts/build-copilot-agents.mjs (regenerates agents/*.agent.md)
198
- pnpm build:claude # or: node bin/agent-cortex.mjs install claude --output claude (generate-only)
199
- # or the install-time entry (same generator, less typing):
200
245
  node bin/agent-cortex.mjs install copilot # regenerates agents/*.agent.md in place
201
246
  node bin/agent-cortex.mjs install copilot --dry-run # plan only, no writes
202
247
  node bin/agent-cortex.mjs install copilot --output /tmp/x # preview the flat files elsewhere
203
- node bin/agent-cortex.mjs install claude # regenerates ./claude AND registers it with Claude Code
248
+ node bin/agent-cortex.mjs install claude # materialises ~/.agent-cortex/claude + marketplace manifest AND registers it with Claude Code (user scope, idempotent)
204
249
  node bin/agent-cortex.mjs install claude --dry-run # plan generation + registration, no writes/spawns
205
- node bin/agent-cortex.mjs install claude --output /tmp/x # generate only — no registration
250
+ node bin/agent-cortex.mjs install claude --require-register # fail (non-zero exit) when the claude CLI can't drive registration
251
+ node bin/agent-cortex.mjs install claude --output /tmp/x # generate only (preview/CI) — no marketplace manifest, no registration
206
252
  ```
207
253
 
208
254
  #### Try it for one session
209
255
 
256
+ Generate a throwaway plugin and point Claude Code at it — no install or registration
257
+ needed:
258
+
210
259
  ```sh
211
- claude --plugin-dir /path/to/agent-cortex/claude
260
+ node bin/agent-cortex.mjs install claude --output /tmp/agent-cortex-claude
261
+ claude --plugin-dir /tmp/agent-cortex-claude
212
262
  # verify what loaded:
213
- claude --plugin-dir /path/to/agent-cortex/claude plugin details agent-cortex
263
+ claude --plugin-dir /tmp/agent-cortex-claude plugin details agent-cortex
214
264
  ```
215
265
 
216
266
  `SKILL.md` edits are picked up live in that session; agent, hook, and MCP changes need
@@ -218,24 +268,42 @@ claude --plugin-dir /path/to/agent-cortex/claude plugin details agent-cortex
218
268
 
219
269
  #### Install persistently (recommended)
220
270
 
221
- The repo ships a marketplace manifest (`.claude-plugin/marketplace.json`) that exposes the
222
- `claude/` subtree. A plain `agent-cortex install claude` does both halves in one step: it
223
- regenerates `claude/` and registers the plugin with Claude Code by driving the `claude
224
- plugin` CLI (marketplace add → install → marketplace update → plugin update), pointing it
225
- at this checkout's manifest. This requires the `claude` plugin CLI (Claude Code v2+):
271
+ The plain install does both halves in one step: it materialises the plugin into
272
+ `~/.agent-cortex/claude`, writes `~/.agent-cortex/.claude-plugin/marketplace.json` (the
273
+ home install root doubles as the marketplace root — the manifest exposes `./claude`), and
274
+ registers it with Claude Code by driving the `claude plugin` CLI against that root at
275
+ **user scope** (the `claude plugin install` default, so the plugin is available in every
276
+ session — the recommended persistent flow). Registration is idempotent by state, not by
277
+ exit code: `claude plugin marketplace list --json` picks add-vs-update and
278
+ `claude plugin list --json` picks install-vs-update against the materialised version. A
279
+ fresh install adds the marketplace and installs the plugin; a re-run is the update path
280
+ (`marketplace update`, plus `plugin update` only when a newer version is materialised); a
281
+ repeat install at the same version is a true no-op. It requires the `claude` plugin CLI
282
+ (Claude Code v2+): with a missing or pre-v2 CLI the installer warns and prints the manual
283
+ commands below, exiting 0 — `--require-register` makes registration mandatory and fails
284
+ non-zero when it can't run:
226
285
 
227
286
  ```sh
228
- git clone https://github.com/jaybeeuu/agent-cortex # first time (or git pull to update)
229
- agent-cortex install claude # regenerate + register (user scope)
287
+ agent-cortex install claude # materialise + register (user scope, idempotent)
288
+ agent-cortex install claude --require-register # register or fail the install
230
289
  ```
231
290
 
232
291
  `--dry-run` prints the full plan without spawning the claude CLI or writing anything;
233
- `--output <dir>` regenerates only, with no registration. The equivalent manual registration
234
- (e.g. on a Claude Code build without the plugin CLI) adds the checkout by **absolute path**
235
- (a bare `.` is rejected):
292
+ `--output <dir>` generates only, with no marketplace manifest and no registration.
293
+
294
+ The install also merges the committed `claude/settings.json` template into
295
+ `~/.claude/settings.json` (the settings file Claude Code reads at user scope). The template
296
+ supplies defaults and personal values win; the installer owns exactly `enabledPlugins` +
297
+ `extraKnownMarketplaces`, so `permissions`, `hooks`, `env`, `statusLine` and unknown keys are
298
+ never touched. A legacy symlinked `settings.json` is replaced with a real file rather than
299
+ written through, and `--output` leaves the user's Claude config alone.
300
+
301
+ The equivalent manual registration adds a marketplace root by **absolute path** (a bare `.` is
302
+ rejected) — the home install root (`~/.agent-cortex`) is the marketplace root written by
303
+ the plain install; the repo checkout ships no manifest:
236
304
 
237
305
  ```sh
238
- claude plugin marketplace add /path/to/agent-cortex
306
+ claude plugin marketplace add ~/.agent-cortex # after a plain install
239
307
  claude plugin install agent-cortex@jaybeeuu # every session (user scope)
240
308
  # or, for this project only:
241
309
  claude plugin install agent-cortex@jaybeeuu --scope local
@@ -246,17 +314,23 @@ flag, and Ralph is just `claude --agent agent-cortex:ralph`.
246
314
 
247
315
  #### Update
248
316
 
249
- The marketplace source is your local checkout, so after pulling changes re-run the installer —
250
- it rebuilds the `claude/` subtree and refreshes the registered plugin (marketplace update →
251
- plugin update) in one step:
317
+ The plain install re-materialises from the current sources, so after pulling changes
318
+ re-run it — it refreshes the materialised plugin and re-registers whatever the installed
319
+ state says is out of date (`marketplace update`, plus `plugin update` only when a newer
320
+ version is materialised; a repeat install at the same version is a no-op) in one step:
252
321
 
253
322
  ```sh
254
323
  git pull
255
- agent-cortex install claude # rebuild + refresh (restart Claude Code to apply)
324
+ agent-cortex install claude # re-materialise + refresh (restart Claude Code to apply)
256
325
  ```
257
326
 
258
- `pnpm build:claude` only regenerates the subtree (no registration), so on its own it won't
259
- refresh an installed plugin.
327
+ Registration matches the marketplace by **name**, not path: a marketplace previously added
328
+ from a different root is refreshed in place rather than re-pointed at the fresh home
329
+ install. Re-point it with
330
+ `claude plugin marketplace remove jaybeeuu` and re-run the install.
331
+
332
+ The plain install IS the update — there is no separate build step. (`--output <dir>` only
333
+ generates a preview and never refreshes an installed plugin.)
260
334
 
261
335
  #### Uninstall
262
336
 
@@ -281,7 +355,7 @@ The lean **Ralph** runs as the interactive main agent (not delegated — it must
281
355
  receive its workers' completions):
282
356
 
283
357
  ```sh
284
- claude --agent agent-cortex:ralph # (add --plugin-dir .../claude if not installed)
358
+ claude --agent agent-cortex:ralph # (omitted once installed via agent-cortex install claude; else --plugin-dir ~/.agent-cortex/claude)
285
359
  ```
286
360
 
287
361
  It finds ready beads, spawns parallel background workers (implement → independent review →
@@ -302,7 +376,7 @@ extension→hook audit.
302
376
  #### Not ported / follow-ups
303
377
 
304
378
  - Only `session-start` and `notify` had Claude equivalents (both ported to hooks); the other
305
- pi `extensions/` (auto-name, skill-stats, subagent, agent-modes) have none — the audit and
379
+ pi `extensions/` (auto-name, agent-guardrails, skill-stats, subagent, agent-modes) have none — the audit and
306
380
  rejection rationale live in `docs/claude-hooks.md`.
307
381
  - Ralph follow-ups: multi-feature epic branches, and a GitHub-trigger routine to auto-resume
308
382
  after a PR merge (instead of manual re-invocation).
@@ -313,14 +387,17 @@ extension→hook audit.
313
387
 
314
388
  Edit the **sources** — the composable `agents/<name>/` directories (shared `agent.md` +
315
389
  per-harness frontmatter/sections, auto-composed by `scripts/build-copilot-agents.mjs` and
316
- the install-time generator `bin/installers/claude.mjs`), `agents-native/*.md` (Claude-only
390
+ `bin/installers/claude.mjs`), `agents-native/*.md` (Claude-only
317
391
  agents like `ralph` — the canonical bodies the installer copies verbatim), `skills/**`,
318
- `hooks/claude/hooks.json` (hook config), and `package.json` (plugin
319
- version) — then run `pnpm build:claude` (the generate-only alias of `agent-cortex install
320
- claude --output claude`; run the plain install instead if you also want the plugin
321
- re-registered with Claude Code). Never hand-edit
322
- anything under `claude/` except the hand-authored `claude/.mcp.json` and `claude/scripts/`
323
- files, or the generated `agents/*.agent.md` files, `claude/agents/`, `claude/skills/`,
324
- `claude/.claude-plugin/plugin.json`, and `claude/hooks.json`. CI runs
325
- `git diff --exit-code claude/agents/ claude/skills/ claude/.claude-plugin/plugin.json claude/hooks.json`
326
- and `git diff --exit-code -- 'agents/*.agent.md'` so the generated outputs can never drift.
392
+ `hooks/claude/hooks.json` (hook config), `claude/settings.json` (the user-settings template
393
+ merged into `~/.claude/settings.json`), `claude-extras/` (hand-authored `.mcp.json` and
394
+ `scripts/`), and `package.json` (plugin
395
+ version — picked up at install time) — then run
396
+ `agent-cortex install claude` (re-materialises the home plugin, merges settings, and
397
+ re-registers it; `--output <dir>` generates a preview only). The only committed file under
398
+ `claude/` is the settings template; `~/.agent-cortex/claude` is generated in full
399
+ (`.claude-plugin/plugin.json`, `agents/`, `skills/`, `hooks.json`, `.mcp.json`, `scripts/`)
400
+ so never edit anything under it. The generated `agents/*.agent.md` files are still
401
+ drift-checked by CI (`git diff --exit-code -- 'agents/*.agent.md'`), and CI validates the
402
+ Claude materialiser (structural checks on a temp-dir install) so sources and installer can
403
+ never silently diverge.
package/agents/README.md CHANGED
@@ -143,8 +143,8 @@ When composing the final agent file for a specific harness:
143
143
  2. Replace `{{SECTION:name}}` with content from `<harness>/<name>.md`
144
144
  3. `{{TOOL:name}}` and `{{PATH:name}}` are resolved against `token-map.json` — the
145
145
  canonical tool/path/agent names per harness. `scripts/lib/compose-agent.mjs` (used by
146
- `build:copilot` / `build:claude` and by `bin/installers/pi.mjs`) resolves them when
147
- generating the flat files / installing for pi; pi's `agent-modes` extension does the
146
+ `build:copilot` and by `bin/installers/claude.mjs` / `bin/installers/pi.mjs`) resolves them when
147
+ generating the flat files / installing for claude and pi; pi's `agent-modes` extension does the
148
148
  same at runtime. See the `contract` section of `token-map.json` and
149
149
  `token-map.README.md` for the rules.
150
150
 
@@ -209,21 +209,24 @@ flat `agents/*.agent.md` files are built from them:
209
209
  - `scripts/build-copilot-agents.mjs` (`pnpm build:copilot`) composes `agents/*.agent.md` from
210
210
  each agent's `copilot/` harness dir — committed for Copilot CLI, which loads `*.agent.md`
211
211
  from `plugin.json`'s `agents: "agents/"` path.
212
- - `bin/installers/claude.mjs` (plain `agent-cortex install claude` also registers the plugin
213
- with Claude Code; `pnpm build:claude` is its generate-only alias, running `install claude
214
- --output claude`) composes `claude/agents/*.md` from each agent's `claude/` harness dir (except
215
- `ralph`, which ships natively from `agents-native/ralph.md`, copied verbatim).
212
+ - `bin/installers/claude.mjs` (plain `agent-cortex install claude` materialises the plugin
213
+ into `~/.agent-cortex/claude` and registers it with Claude Code; `--output <dir>` is its
214
+ generate-only form, used by tests/CI) composes the plugin's
215
+ `agents/*.md` from each agent's `claude/` harness dir (except `ralph`, which ships natively
216
+ from `agents-native/ralph.md`, copied verbatim).
216
217
  - The pi harness delivers two ways: the `agent-modes` extension composes `agent.md` +
217
218
  `pi/frontmatter.json` at runtime against `token-map.json`, and `agent-cortex install pi`
218
219
  materialises composed agents + token-substituted skills into `~/.pi/agent` at install
219
220
  time (see README "Pi harness agents & skills").
220
221
 
221
- Never edit the generated flat/claude files by hand — edit the composable directory. CI
222
- regenerates both outputs and fails on drift. Generation is install-time: `claude` ships
222
+ Never edit the generated flat materialised files by hand — edit the composable directory.
223
+ Generation is install-time: `claude` ships
223
224
  via `bin/installers/claude.mjs`, the generator behind plain `agent-cortex install claude`
224
- (which also registers the plugin with Claude Code) and its generate-only `pnpm
225
- build:claude` alias (`install claude --output claude`) — the committed `claude/` subtree
226
- is regenerated by it and drift-checked, so it can never be stale; `copilot` ships via
225
+ (which materialises into `~/.agent-cortex/claude`, merges the committed
226
+ `claude/settings.json` into `~/.claude/settings.json`, and registers the plugin with Claude
227
+ Code); the only committed `claude/` file is that settings template (hand-authored extras live
228
+ in `claude-extras/`), and CI validates the materialiser with structural checks on a temp-dir
229
+ install; `copilot` ships via
227
230
  `bin/installers/copilot.mjs`, the shared code path behind both `agent-cortex install
228
231
  copilot` and `scripts/build-copilot-agents.mjs`; `pi` ships via
229
232
  `bin/installers/pi.mjs`, which materialises composed agents + token-substituted skills
@@ -30,8 +30,12 @@ if (parsed.command === "install") {
30
30
 
31
31
  if (parsed.harness === "pi") {
32
32
  try {
33
- const result = installPi({
33
+ const result = await installPi({
34
34
  dryRun: parsed.dryRun ?? false,
35
+ // Third-party pi packages are only provisioned for a real install:
36
+ // --output is the generate-only form, --no-provision opts an offline
37
+ // machine out explicitly.
38
+ provisionPackages: parsed.output === undefined && !parsed.noProvision,
35
39
  warn: () => {}, // warnings surface once in the printed summary
36
40
  ...(parsed.output ? { output: parsed.output } : {}),
37
41
  ...(parsed.pluginRoot ? { pluginRoot: parsed.pluginRoot } : {}),
@@ -47,21 +51,36 @@ if (parsed.command === "install") {
47
51
  process.stdout.write(`Installing agent-cortex for "${parsed.harness}" harness…\n`);
48
52
 
49
53
  // claude is the first wired harness: a plain `agent-cortex install claude`
50
- // regenerates the plugin subtree from the canonical sources (agents/,
51
- // agents-native/, skills/, hooks/claude/) via the shared installClaude
52
- // generator AND registers it with Claude Code (marketplace add → install →
53
- // update) — `pnpm build:claude` is the generate-only alias of this
54
- // invocation (it passes `--output claude`), so there is no separate
55
- // build-time code path and CI drift-checks never touch ~/.claude.
54
+ // materialises the plugin into the home install root (~/.agent-cortex/claude)
55
+ // with copied, token-substituted skills, writes the marketplace manifest at
56
+ // ~/.agent-cortex/.claude-plugin/marketplace.json, merges the committed
57
+ // claude/settings.json template into ~/.claude/settings.json (owning only
58
+ // enabledPlugins + extraKnownMarketplaces), and registers it with Claude Code
59
+ // (marketplace add → install → update); `--output <dir>` is the generate-only
60
+ // form (tests/CI validation), and the generated claude/ plugin subtree is
61
+ // never committed — only the settings template is.
56
62
  if (parsed.harness === "claude") {
57
63
  // Avoid loading the installer on the help/summary paths and for other harnesses.
58
64
  const { installClaude, registerClaude } = await import("./installers/claude.mjs");
59
65
  try {
60
- installClaude({ output: parsed.output, dryRun: parsed.dryRun });
66
+ const result = await installClaude({
67
+ dryRun: parsed.dryRun ?? false,
68
+ warn: () => {}, // warnings surface once via result.warnings
69
+ ...(parsed.output ? { output: parsed.output } : {}),
70
+ });
71
+ for (const warning of result.warnings) {
72
+ process.stdout.write(` ⚠ ${warning}\n`);
73
+ }
61
74
  // Runtime registration happens only for the plain install — `--output`
62
75
  // keeps generating only (documented in --help).
63
76
  if (parsed.output === undefined) {
64
- registerClaude({ dryRun: parsed.dryRun });
77
+ await registerClaude({
78
+ root: result.marketplaceRoot,
79
+ manifest: result.marketplaceManifest,
80
+ pluginVersion: result.pluginVersion,
81
+ dryRun: parsed.dryRun ?? false,
82
+ requireRegister: parsed.requireRegister ?? false,
83
+ });
65
84
  }
66
85
  process.exit(0);
67
86
  } catch (err) {
@@ -76,7 +95,7 @@ if (parsed.command === "install") {
76
95
  if (parsed.harness === "copilot") {
77
96
  const { installCopilot } = await import("./installers/copilot.mjs");
78
97
  try {
79
- installCopilot({ output: parsed.output, dryRun: parsed.dryRun });
98
+ await installCopilot({ output: parsed.output, dryRun: parsed.dryRun });
80
99
  process.exit(0);
81
100
  } catch (err) {
82
101
  process.stderr.write(`Install failed: ${err.message}\n`);
@@ -93,10 +112,36 @@ function printPiInstall(result) {
93
112
  process.stdout.write(` ✓ ${agent.name}.agent.md → ${agent.filePath}\n`);
94
113
  }
95
114
  process.stdout.write(` ✓ Substituted ${result.skills.md} markdown file(s) across ${result.skills.skills} skill(s) → ${result.skills.dir}\n`);
115
+ printManagedConfig("settings.json", result.settings);
116
+ printManagedConfig("keybindings.json", result.keybindings);
117
+ // A dry run installs nothing, so report the plan — otherwise the documented
118
+ // "show what would be installed" contract prints no packages at all.
119
+ const packages = result.packages ?? { planned: [], installed: [] };
120
+ for (const source of result.dryRun ? packages.planned : packages.installed) {
121
+ process.stdout.write(` ✓ ${result.dryRun ? "Would install" : "Installed"} pi package ${source}\n`);
122
+ }
96
123
  for (const warning of result.warnings) {
97
124
  process.stdout.write(` ⚠ ${warning}\n`);
98
125
  }
99
126
  if (result.dryRun) {
100
127
  process.stdout.write("(dry-run — nothing written)\n");
101
128
  }
129
+ }
130
+
131
+ /** Report one managed pi config file (settings.json / keybindings.json). */
132
+ function printManagedConfig(label, info) {
133
+ if (!info) return;
134
+ if (info.action === "would-remove-symlink") {
135
+ process.stdout.write(` → would remove symlink ${info.path} and write ${label}\n`);
136
+ } else if (info.action === "would-write") {
137
+ process.stdout.write(` → would write ${label} → ${info.path}\n`);
138
+ } else if (info.action === "unchanged") {
139
+ process.stdout.write(` · ${label} → ${info.path} (unchanged)\n`);
140
+ } else if (info.action === "skipped") {
141
+ process.stdout.write(` ⚠ ${label} modified after install — left untouched → ${info.path}\n`);
142
+ } else if (info.action === "removed-symlink") {
143
+ process.stdout.write(` ✓ ${label} → ${info.path} (removed legacy symlink)\n`);
144
+ } else {
145
+ process.stdout.write(` ✓ ${label} → ${info.path}\n`);
146
+ }
102
147
  }