@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
@@ -0,0 +1,161 @@
1
+ # Technical Direction: Harness config and third-party extensions
2
+
3
+ ## Problem and target outcome
4
+
5
+ - **What we are solving.** agent-cortex ships agents and skills to three harnesses (pi, Claude
6
+ Code, Copilot CLI) and needs a declared, reproducible set of *third-party* extensions those
7
+ agents depend on (pi packages / Claude plugins). Two bespoke mechanisms currently exist and
8
+ disagree: `main` declares packages in `package.json` → `pi.packages` and provisions them from
9
+ `lib/pi-packages.mjs`; the unmerged pi-harness epic (`agnt-ctx-dfq3`) declares them in
10
+ `pi.extensions.json` / `claude.extensions.json` and provisions them via `agent-cortex ext
11
+ install`. **Neither file is read by any harness** — both are agent-cortex inventions.
12
+ - **Target outcome.** One idiomatic mechanism per harness: extensions the agents *require* are
13
+ resolved by the harness's native dependency field; extensions that are *optional* are declared
14
+ in the harness's committed settings template. No bespoke manifest format survives.
15
+ - **Why now.** The epic is unmerged and diverging from `main` (7 commits behind), and PR #142
16
+ (`ext prune`) was closed as redundant with the harnesses' own package management. Keeping both
17
+ models means keeping the drift the original idea set out to remove.
18
+
19
+ ## Current-state constraints
20
+
21
+ - **Public package.** `publishConfig.access: public`, npm `@jaybeeuu/agent-cortex@1.40.0` — every
22
+ bundled dependency or committed template affects anyone who installs the package, not just the
23
+ author.
24
+ - **pi is idiomatic two ways.** Machine state is `~/.pi/agent/settings.json` → `packages[]`, owned
25
+ by `pi install/remove/list`. A package's own resource declarations live in `package.json` → `pi`
26
+ (`extensions`, `skills`, `prompts`, `themes`); third-party pi packages are depended on via
27
+ `dependencies` + `bundledDependencies` and referenced through `node_modules/…`. **There is no
28
+ `pi.packages` field** — pi ignores it.
29
+ - **Claude is idiomatic two ways.** Machine state is `~/.claude/settings.json` → `enabledPlugins`
30
+ + `extraKnownMarketplaces`. A plugin declares other plugins in `.claude-plugin/plugin.json` →
31
+ `dependencies`, which Claude Code resolves and auto-installs.
32
+ - **Copilot has no dependency mechanism.** `copilot plugin install/list/uninstall` exists, but its
33
+ state is auto-managed `~/.copilot/config.json` (not committable) and `plugin.json` declares no
34
+ dependencies.
35
+ - **The epic already built two-thirds of the target.** Unmerged commit `afab2c6` (F3) materialises
36
+ `pi/settings.json` + `pi/keybindings.json` from committed templates with merge-keep-personal and
37
+ the CLI owning `packages`; `1ff212d` (F4) makes bundled pi extensions load via package
38
+ registration. Only `1e0e970` (F5, the bespoke manifests) is off-direction.
39
+ - **Unknowns resolved during this memo:** pi bundling is feasible (see Validation plan); Claude
40
+ dependency auto-install is real but cross-marketplace needs an allowlist; Claude currently
41
+ declares **zero** third-party plugins.
42
+
43
+ ## Options considered
44
+
45
+ ### Option A: one bespoke config per harness
46
+
47
+ - **Approach.** A `config/<harness>.json` (or the epic's `*.extensions.json`) holds settings and
48
+ the extension list; the CLI provisions from it.
49
+ - **Pros.** Uniform across harnesses; full control; explicit single place to look.
50
+ - **Cons.** A third format that neither harness reads; duplicates the native `packages` /
51
+ `enabledPlugins` fields; re-creates the manifest-vs-machine drift the idea exists to remove.
52
+ - **Risks.** The redundancy that made `ext prune` (F6) not worth building.
53
+
54
+ ### Option B: native dependency fields + committed settings templates *(recommended)*
55
+
56
+ - **Approach.** Required extensions → `dependencies` + `bundledDependencies` (pi) and
57
+ `.claude-plugin/plugin.json` `dependencies` (Claude). Optional extensions → the harness's own
58
+ committed settings template (`pi/settings.json` `packages`; `claude/settings.json`
59
+ `enabledPlugins` + `extraKnownMarketplaces`), materialised with merge-keep-personal. Drop the
60
+ bespoke manifests, the `ext install`/`ext prune` family, and the separate diff command —
61
+ `agent-cortex install <harness>` reconciles.
62
+ - **Pros.** Idiomatic; no bespoke format; each harness resolves its own required deps; the
63
+ optional list lives in the file the harness already reads, so `pi list` / `claude plugin list
64
+ --json` are the exact machine-state comparison; drift self-heals on install.
65
+ - **Cons.** pi bundling shifts update cadence to agent-cortex releases and grows the tarball;
66
+ Claude cross-marketplace deps need an allowlist; the committed template also carries personal
67
+ settings; Copilot gets nothing.
68
+ - **Risks.** Loading TS extensions from bundled `node_modules/`; maintaining the Claude
69
+ cross-marketplace allowlist.
70
+
71
+ ### Option C: fully delegate to the harnesses
72
+
73
+ - **Approach.** pi bundles its deps, Claude auto-installs plugin deps, `install` only registers;
74
+ there is no optional layer at all.
75
+ - **Pros.** Least code; no declaration surface to maintain.
76
+ - **Cons.** No way to declare the optional/opt-in personal toolset; the original problem —
77
+ "the toolset isn't reproducible or documented" — goes unsolved for everything not strictly
78
+ required.
79
+ - **Risks.** Silently drops the reproducibility goal.
80
+
81
+ ## Recommendation
82
+
83
+ **Option B.** It is the only option that satisfies both halves of the problem: *required*
84
+ extensions resolve through the harness's own mechanism (so an agent's tools can't be missing),
85
+ and *optional* ones stay declared and reproducible without inventing a format the harnesses
86
+ ignore. It also deletes the most code — the two competing bespoke models, `lib/pi-packages.mjs`,
87
+ and the `ext` command family — rather than adding a third.
88
+
89
+ Alternatives were not chosen because: **A** keeps a third format and the drift it causes; **C**
90
+ abandons the declared-toolset goal that started the work.
91
+
92
+ **Concrete disposition:** salvage the epic's F3 (`afab2c6`) and F4 (`1ff212d`) — they *are* the
93
+ settings-template and bundled-dependency mechanisms — drop F5/F6 (`1e0e970` and PR #142), close
94
+ the epic, and delete `pi.packages` + `lib/pi-packages.mjs` from `main`.
95
+
96
+ ## Tradeoffs accepted
97
+
98
+ - **pi bundling changes update cadence.** Bundled pi packages are pinned by agent-cortex's
99
+ `package.json` ranges and update when agent-cortex releases, not via `pi update`. Accepted: it
100
+ makes the required toolset a property of agent-cortex rather than of the machine.
101
+ - **Tarball size.** `pi-web-access` pulls 5 runtime deps (readability, linkedom, p-limit, turndown,
102
+ unpdf). Accepted for the required set; the optional set stays provisioned, not bundled.
103
+ - **Claude cross-marketplace allowlist.** If a required Claude plugin lives in another
104
+ marketplace, agent-cortex's `marketplace.json` must list it in
105
+ `allowCrossMarketplaceDependenciesOn`. Accepted; currently moot (zero declared plugins).
106
+ - **Committed personal settings.** `pi/settings.json` already carries models/theme; the template
107
+ model makes the repo own the whole pi settings surface. Accepted; F3's merge preserves personal
108
+ keys and only `packages` is CLI-owned.
109
+ - **Copilot gets no mechanism.** Documented exception — the README says `copilot plugin install`.
110
+ Accepted because Copilot has no dependency field and no committable config.
111
+ - **No dedicated diff command.** A read-only `status` is deferred until drift is shown to hurt.
112
+
113
+ ## Validation plan
114
+
115
+ Run before implementation (three already run during this memo):
116
+
117
+ 1. **Can `pi-questions` / `pi-web-access` be bundled and load from `node_modules/`?** ✅ *Ran.*
118
+ `pi-questions@0.3.4` is a single extension file with no deps or install-time state;
119
+ `pi-web-access@0.10.7` is an extension + skills with 5 runtime deps. Both are conventional pi
120
+ packages — bundling is feasible. Success signal: a scratch package depending on both loads
121
+ `ask_questions` / `fetch_content` in a fresh pi session.
122
+ 2. **Does Claude's `plugin.json` `dependencies` auto-install from agent-cortex's marketplace?**
123
+ ✅ *Ran.* Yes, but cross-marketplace deps require `allowCrossMarketplaceDependenciesOn` in the
124
+ root `marketplace.json`, and version constraints need `{plugin-name}--v{version}` git tags.
125
+ 3. **Does F3 reconcile `packages` when the template drops one?** ✅ *Ran.* Yes — the CLI owns
126
+ `packages` outright, so removing an entry from the template removes it on next install. The
127
+ documented limitation is the reverse (ad-hoc `pi install` entries don't persist), which is
128
+ intended.
129
+
130
+ Still to run before shipping: a scratch end-to-end install on a clean `HOME` proving a required
131
+ pi extension loads from `node_modules/` and an optional one is materialised from the template.
132
+
133
+ ## Revisit triggers
134
+
135
+ - pi or Claude ship a first-class *optional* dependency field, making the settings template
136
+ unnecessary.
137
+ - Claude still declares zero third-party plugins after the migration — then Claude parity is
138
+ speculative and should be dropped rather than maintained.
139
+ - Bundling `pi-web-access`'s native-ish dependency tree (unpdf, linkedom) proves brittle or
140
+ unacceptable on a fresh machine.
141
+ - Copilot gains a committable config or a plugin dependency field — revisit the documented
142
+ exception.
143
+ - Drift between the committed template and the machine becomes a recurring annoyance — add the
144
+ deferred read-only `status`.
145
+
146
+ ## References
147
+
148
+ - pi docs, *Settings* and *Packages* (installed at
149
+ `…/@earendil-works/pi-coding-agent@0.85.1/docs/settings.md`, `…/docs/packages.md`): `packages`
150
+ array is the machine store; the `pi` manifest keys are `extensions`/`skills`/`prompts`/`themes`;
151
+ other pi packages are bundled and referenced through `node_modules/`.
152
+ - Claude Code, *Constrain plugin dependency versions* — https://code.claude.com/docs/en/plugin-dependencies
153
+ (auto-install, cross-marketplace allowlist, tag naming).
154
+ - Claude Code, *Settings* — https://code.claude.com/docs/en/settings (`enabledPlugins`,
155
+ `extraKnownMarketplaces`).
156
+ - GitHub Copilot CLI, *Plugin reference* — https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference
157
+ and *Configuration directory* — https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
158
+ - Code evidence: `package.json` (`pi.packages`), `lib/pi-packages.mjs` (main's provisioner);
159
+ `pi.extensions.json`, `claude.extensions.json`, `lib/extension-manifest.mjs` (epic F5, `1e0e970`);
160
+ `bin/installers/pi.mjs` settings materialisation (epic F3, `afab2c6`);
161
+ `docs/ideas/extension-manifests.md` (the originating idea).
@@ -1,11 +1,15 @@
1
- # Technical Direction: Execution-Label Migration Tooling (two-axis model)
1
+ # Technical Direction: Migration Tooling (two-axis model)
2
2
 
3
3
  ## Problem and target outcome
4
4
 
5
5
  - Migrate all beads workspaces from `implementation-type:afk|hitl` to `execution:afk|hitl`
6
6
  (strict rename, agent-agnostic) as part of the two-axis workflow model epic.
7
- - Provide a repo-agnostic tool + skill any workspace can run, and a single-package.json
8
- script tooling story across the agent-cortex repo.
7
+ - Migrate loose idea markdown files (`docs/ideas/*.md`) to first-class `kind:idea` beads
8
+ in any workspace.
9
+ - Both migrations are **steps in one versioned migration command** — `agent-cortex migrate`
10
+ (aliased `ctx migrate`), which detects a repo's current migration version and applies
11
+ ordered steps up to the latest. Repo-agnostic tool + skill any workspace can run, and a
12
+ single-package.json script tooling story across the agent-cortex repo.
9
13
 
10
14
  ## Current-state constraints
11
15
 
@@ -22,6 +26,8 @@
22
26
  — they remain workspace members with their own package.json.
23
27
  - `node_modules` gitignored; per-skill modules are installed manually today — a fragility in
24
28
  fresh clones and PI-installed contexts.
29
+ - The CLI (`bin/agent-cortex.mjs` + `lib/cli.mjs`) is already zero-dep stdlib-only and is the
30
+ natural home for a migrate command; it currently wires only `install <harness>`.
25
31
 
26
32
  ## Decisions (locked in planning session)
27
33
 
@@ -45,28 +51,40 @@
45
51
  - Fresh-clone/installed-context verification: `pnpm install` at root then
46
52
  `pnpm -r typecheck` + `pnpm -r test` (or root `node --test` wiring) cover all scripts.
47
53
 
48
- ### D3: Migration tool shape
49
-
50
- - Script: `skills/productivity/migration/scripts/rename-execution-labels.mjs` — zero-dep,
51
- `// @ts-check` + JSDoc.
52
- - Behavior:
53
- 1. Enumerate: `bd list --label-pattern 'implementation-type:*' --json`.
54
- 2. Group bead ids by label value (afk | hitl). A bead carrying both gets both execution labels.
55
- 3. Dry-run (default): print per-bead plan + summary counts; zero writes; exit 0.
56
- 4. Apply (`--apply`): batched `bd label add execution:<v> <ids...>` then
57
- `bd label remove implementation-type:<v> <ids...>` per group.
58
- 5. Idempotent: re-run finds zero targets → no-op.
59
- 6. Fail loudly if `bd` unavailable or any subcommand errors.
60
- - Scope: rename ALL beads carrying `implementation-type:*` (including closed) — the F7
61
- acceptance "zero implementation-type:* labels remain" requires it, and bd label history
62
- tolerates it.
54
+ ### D3: Migration command — versioned registry in the CLI
55
+
56
+ - Command: `agent-cortex migrate` (alias `ctx migrate`), wired into `bin/agent-cortex.mjs`
57
+ + `lib/cli.mjs` (parseArgs + buildHelpText), zero-dep stdlib-only like the rest of the CLI.
58
+ - A **versioned migration registry**: each step declares the version it migrates to.
59
+ `migrate` detects the current version of the target repo's state and applies each step in
60
+ order up to the latest. Steps:
61
+ 1. **v1: label rename** (F1) — `implementation-type:afk|hitl` → `execution:afk|hitl`:
62
+ `bd list --label-pattern 'implementation-type:*' --json`; group ids by label value
63
+ (a bead with both gets both); dry-run default (per-bead plan + summary, zero writes,
64
+ exit 0); `--apply` batched `bd label add execution:<v> <ids...>` then
65
+ `bd label remove implementation-type:<v> <ids...>` per group; idempotent re-run
66
+ (zero targets → no-op); fail loudly if `bd` missing or a subcommand errors.
67
+ Scope: ALL beads including closed (F7 acceptance "zero implementation-type:* labels
68
+ remain" requires it; bd label history tolerates it).
69
+ 2. **v2: ideas → beads** (F2) — `docs/ideas/*.md` (default, `--dir` override) →
70
+ `kind:idea` beads: map template sections to bead fields (Problem→--description,
71
+ How→--design, When/Priority/Validity/Constraints/Notes→--notes, referential→--context),
72
+ `bd create -l kind:idea -p <preserved priority, default P4>`; preserve content
73
+ fidelity (keep section markers inside fields); dry-run; safe deletion order
74
+ (docs/ideas/ + new-idea.sh only after F3 lands); `record-ideas-as-beads.md` migrates
75
+ itself last.
76
+ - Shared plumbing: preflight (detect current version), `--dry-run` (default), `--apply`,
77
+ per-step verification, and a final summary. Do NOT run v1 against agent-cortex's own
78
+ .beads — the repo cutover is feature F7 (landed after skill updates so ralph never
79
+ deadlocks).
63
80
 
64
81
  ### D4: Migration skill
65
82
 
66
83
  - Location: `skills/productivity/migration/SKILL.md` (productivity = workflow tooling).
67
- - Docs the full sequence for any workspace: preflight (count via `bd label list-all`),
68
- dry-run, apply, post-checks (grep stale refs; `bd ready -l execution:afk` smoke), plus
69
- pointers to idea-file migration (F2) and skill/doc updates (F3/F4/F5/F6).
84
+ - Docs the full sequence for any workspace: preflight (current-version detection),
85
+ `agent-cortex migrate` dry-run, apply, post-checks (grep stale refs; `bd ready -l
86
+ execution:afk` smoke), plus pointers to idea-file migration (F2) and skill/doc updates
87
+ (F3/F4/F5/F6).
70
88
  - Repo-agnostic: no agent-cortex-specific paths in the runnable steps.
71
89
 
72
90
  ### D6: npm packaging hygiene (no build step — F9)
@@ -106,17 +124,31 @@
106
124
  - Cons: 3 orphaned mini-workspaces, manual install fragility, resolution doc dance
107
125
  (`<skill-scripts>/node_modules/.bin/tsx`), contradicts single-package.json preference.
108
126
 
127
+ ### Option D: standalone one-off migration script (original D3, superseded)
128
+ - Was: `skills/productivity/migration/scripts/rename-execution-labels.mjs` as a standalone
129
+ script with its own skill. Superseded by the unified `migrate` command: the label rename
130
+ is migration step v1 inside the same versioned registry as the idea-file migration (F2),
131
+ sharing preflight/dry-run/verify plumbing. Rationale (user directive, 2026-09-13): the
132
+ target is a migration *command* that converts any given repo from the current version to
133
+ the latest — one surface, not N ad-hoc scripts; also aligns with the planned `ctx`
134
+ alias/version/update surface (docs/ideas/ctx-alias-version-and-update-commands.md).
135
+
109
136
  ## Tradeoffs accepted
110
137
 
111
138
  - Scripts use JSDoc annotations where types matter; `tsc --noEmit` with `checkJs` keeps a
112
139
  typecheck gate at root.
113
140
  - Closed beads get their history labels renamed — accepted for a clean label space.
141
+ - migrate command grows the CLI surface beyond `install`; kept small (one register of
142
+ versioned steps) to avoid a second mechanism drifting from bd's own state.
114
143
 
115
144
  ## Validation plan
116
145
 
117
146
  - F1-T3: scratch beads workspace (temp dir): create test beads with both labels + a dep pair,
118
147
  dry-run (assert zero writes), apply, verify rename, re-run idempotence, `bd ready -l
119
148
  execution:afk` smoke, then run the migration skill end-to-end once.
149
+ - F2-T1: scratch repo with several idea `.md` files: `agent-cortex migrate` v2 dry-run
150
+ (zero writes), apply, verify kind:idea beads with content fidelity + priorities, delete
151
+ file flow, re-run idempotence.
120
152
  - F8-T3: fresh clone (or temp copy) + `pnpm install` at root; `pnpm -r typecheck` and
121
153
  `pnpm -r test` green; scripts execute via bare `node`.
122
154
 
@@ -125,8 +157,13 @@
125
157
  - A future script needs third-party deps at runtime → revisit D1 (either root dep or
126
158
  per-package re-introduction).
127
159
  - New extension with conflicting devDeps → revisit D2 membership rules.
160
+ - A third migration step appears → extend the registry in place; if steps start sharing
161
+ little plumbing, revisit the unified-command decision (Option D).
128
162
 
129
163
  ## References
130
164
 
131
- - Code evidence: `skills/*/scripts/package.json` (3 orphaned), root `package.json` +
132
- `pnpm-workspace.yaml`, bd CLI help (`bd list`, `bd label`), epic `agnt-ctx-s5xd` notes.
165
+ - Code evidence: `bin/agent-cortex.mjs` + `lib/cli.mjs` (CLI surface), `skills/*/scripts/
166
+ package.json` (3 orphaned), root `package.json` + `pnpm-workspace.yaml`, bd CLI help
167
+ (`bd list`, `bd label`, `bd create`), epic `agnt-ctx-s5xd` notes.
168
+ - Idea file: `docs/ideas/ctx-alias-version-and-update-commands.md` (ctx alias + version/update
169
+ surface that `migrate` joins).
@@ -0,0 +1,45 @@
1
+ # Agent Guardrails
2
+
3
+ Appends behavioural circuit-breakers to the PI system prompt once per user prompt.
4
+
5
+ This is the first slice of `docs/ideas/improve-pi-system-prompt.md`: the highest-leverage
6
+ token reduction is behavioural, not structural. The extension injects a stable block via
7
+ the `before_agent_start` hook — which fires once per user prompt, before the agent loop — so
8
+ the rules apply to every turn of the run without rewriting Pi's default prompt.
9
+
10
+ ## Rules
11
+
12
+ - **Circuit-breakers** — two strikes then stop; never repeat a tool call without a stated
13
+ rationale; check in when a cycle produces no progress.
14
+ - **Check-in triggers** — stop and present options when retries fail, the requirement is
15
+ ambiguous, a decision with trade-offs is needed, scope must expand, an action is
16
+ destructive, or you are blocked.
17
+ - **Atomic changes** — one concern per change; smallest diff that works; no tangential
18
+ refactoring or cleanup.
19
+
20
+ A structured workflow (a skill or pipeline stage with its own explicit retry and check-in
21
+ rules) takes precedence where it is explicit.
22
+
23
+ ## Structure
24
+
25
+ ```
26
+ agent-guardrails/
27
+ ├── README.md # This file
28
+ ├── index.ts # Extension entry point — appends on before_agent_start
29
+ ├── guardrails.ts # The guardrails text + appendGuardrails composition
30
+ └── guardrails.test.ts # Composition tests
31
+ ```
32
+
33
+ ## Testing
34
+
35
+ ```bash
36
+ cd extensions/agent-guardrails
37
+ node --test --import tsx/esm guardrails.test.ts
38
+ ```
39
+
40
+ ## Design notes
41
+
42
+ - The block is a stable constant appended after any chained system-prompt changes, so the
43
+ prompt-cache prefix is preserved.
44
+ - `appendGuardrails` is idempotent — a prompt that already carries the marker is returned
45
+ unchanged, so a duplicate extension load cannot double the rules.
@@ -0,0 +1,42 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { GUARDRAILS, GUARDRAILS_MARKER, appendGuardrails } from "./guardrails.ts";
5
+
6
+ // ─── appendGuardrails ─────────────────────────────────────────────────────────
7
+
8
+ describe("appendGuardrails", () => {
9
+ it("appends the guardrails block after the base prompt", () => {
10
+ const result = appendGuardrails("You are a helpful agent.");
11
+
12
+ assert.ok(result.startsWith("You are a helpful agent."));
13
+ assert.ok(result.includes(GUARDRAILS));
14
+ });
15
+
16
+ it("preserves a chained base prompt from earlier handlers", () => {
17
+ const base = "Base prompt.\n\n---\nAgent mode instructions.";
18
+ const result = appendGuardrails(base);
19
+
20
+ assert.ok(result.startsWith(base));
21
+ });
22
+
23
+ it("trims trailing whitespace before appending", () => {
24
+ const result = appendGuardrails("Base prompt.\n\n");
25
+
26
+ assert.equal(result, `Base prompt.\n\n${GUARDRAILS}`);
27
+ });
28
+
29
+ it("is idempotent when applied twice", () => {
30
+ const once = appendGuardrails("Base prompt.");
31
+ const twice = appendGuardrails(once);
32
+
33
+ assert.equal(twice, once);
34
+ assert.equal(twice.split(GUARDRAILS_MARKER).length - 1, 1);
35
+ });
36
+
37
+ it("returns the prompt unchanged when the guardrails are already present", () => {
38
+ const existing = `Base prompt.\n\n${GUARDRAILS}`;
39
+
40
+ assert.equal(appendGuardrails(existing), existing);
41
+ });
42
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Behavioural guardrails for the PI system prompt.
3
+ *
4
+ * The first slice of docs/ideas/improve-pi-system-prompt.md: hard circuit-breakers
5
+ * against wasted retries, explicit check-in triggers, and atomic-change discipline.
6
+ * Kept as a stable constant so it can be appended to every turn's system prompt
7
+ * without invalidating the prompt-cache prefix.
8
+ *
9
+ * The marker doubles as an idempotency guard: if a chained system prompt already
10
+ * carries the block (e.g. the extension loaded twice), it is not appended again.
11
+ */
12
+
13
+ export const GUARDRAILS_MARKER = "## Operating discipline (agent-cortex)";
14
+
15
+ export const GUARDRAILS = `${GUARDRAILS_MARKER}
16
+
17
+ Behavioural circuit-breakers for every task. They override the instinct to keep
18
+ retrying. A structured workflow (a skill or pipeline stage with its own explicit
19
+ retry and check-in rules) takes precedence where it is explicit.
20
+
21
+ ### Circuit-breakers
22
+
23
+ - **Two strikes, then stop.** If the same command, test, or fix fails twice for
24
+ the same reason, stop. Do not attempt a third variation. Report what you tried
25
+ and ask how to proceed.
26
+ - **No repeat without rationale.** Never re-run a tool call with the same input
27
+ unless you can state, in one sentence, what changed and why it will now
28
+ succeed. If you cannot, stop and ask.
29
+ - **No progress, no next cycle.** If a full cycle of work produces no observable
30
+ progress, check in instead of starting another cycle.
31
+
32
+ ### Check in when…
33
+
34
+ Stop and present options instead of continuing when any of these is true:
35
+
36
+ - Two attempts at the same problem have failed.
37
+ - The requirement is ambiguous, or two instructions conflict.
38
+ - The work needs a decision the user has not made, or a choice with material
39
+ trade-offs.
40
+ - You need to change something outside the task's stated scope.
41
+ - The next action is destructive or hard to reverse.
42
+ - You are blocked and no in-scope action will unblock you.
43
+
44
+ When checking in, state: what you tried, what happened, the options, and your
45
+ recommendation.
46
+
47
+ ### Atomic changes
48
+
49
+ - **One concern per change.** Do not refactor, rename, reformat, or tidy code the
50
+ task does not require. Record it and leave it for a separate change.
51
+ - **Smallest diff that works.** Make the minimal change that satisfies the
52
+ requirement.
53
+ - Never mix a fix with unrelated cleanup in the same change.
54
+ `;
55
+
56
+ /**
57
+ * Append the guardrails block to a system prompt.
58
+ *
59
+ * The base prompt is preserved verbatim (minus trailing whitespace) so callers
60
+ * can chain this after other `before_agent_start` handlers. Idempotent: a prompt
61
+ * that already contains the marker is returned unchanged.
62
+ */
63
+ export function appendGuardrails(systemPrompt: string): string {
64
+ if (systemPrompt.includes(GUARDRAILS_MARKER)) return systemPrompt;
65
+ return `${systemPrompt.trimEnd()}\n\n${GUARDRAILS}`;
66
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Agent Guardrails Extension
3
+ *
4
+ * Appends behavioural circuit-breakers to the PI system prompt on every turn:
5
+ * hard retry limits, explicit check-in triggers, and atomic-change discipline
6
+ * (the first slice of docs/ideas/improve-pi-system-prompt.md).
7
+ *
8
+ * The block is stable text appended after any chained system-prompt changes, so
9
+ * it keeps the prompt-cache prefix intact while remaining authoritative for the
10
+ * model. See guardrails.ts for the rules and appendGuardrails for composition.
11
+ */
12
+
13
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
+ import { appendGuardrails } from "./guardrails.ts";
15
+
16
+ export { GUARDRAILS, GUARDRAILS_MARKER, appendGuardrails } from "./guardrails.ts";
17
+
18
+ export default function agentGuardrailsExtension(pi: ExtensionAPI): void {
19
+ pi.on("before_agent_start", (event) => {
20
+ return { systemPrompt: appendGuardrails(event.systemPrompt) };
21
+ });
22
+ }
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/@earendil-works+pi-coding-agent@0.78.1_ws@8.21.0_zod@4.4.3/node_modules/@earendil-works/pi-coding-agent/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/@earendil-works+pi-coding-agent@0.78.1_ws@8.21.0_zod@4.4.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/@earendil-works+pi-coding-agent@0.78.1_ws@8.21.0_zod@4.4.3/node_modules/@earendil-works/pi-coding-agent/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/@earendil-works+pi-coding-agent@0.78.1_ws@8.21.0_zod@4.4.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../@earendil-works/pi-coding-agent/dist/cli.js" "$@"
19
+ else
20
+ exec node "$basedir/../@earendil-works/pi-coding-agent/dist/cli.js" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@"
19
+ else
20
+ exec node "$basedir/../tsx/dist/cli.mjs" "$@"
21
+ fi
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@agent-cortex/agent-guardrails",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node --test --import tsx/esm guardrails.test.ts",
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "devDependencies": {
11
+ "@earendil-works/pi-coding-agent": "^0.78.1",
12
+ "@types/node": "^22.0.0",
13
+ "tsx": "^4.0.0",
14
+ "typescript": "^5.0.0"
15
+ },
16
+ "peerDependencies": {
17
+ "@earendil-works/pi-coding-agent": "^0.78.1"
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "allowImportingTsExtensions": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["*.ts"]
12
+ }
@@ -34,8 +34,9 @@ contract:
34
34
  1. `{{SECTION:name}}` tokens are replaced with the agent's `pi/<name>.md`.
35
35
  2. `{{TOOL:key}}` / `{{PATH:key}}` tokens are substituted against the pi column
36
36
  of `token-map.json`, with the plugin root resolved from the actual extension
37
- location. Tools without a PI equivalent (`ask_user`, `skill`) are omitted
38
- from the tool set.
37
+ location. Tools map through that column (e.g. `ask_user` → `ask_questions`,
38
+ `skill` → `read`); a tool with a null mapping — any future one — is omitted
39
+ from the tool set with a warning.
39
40
 
40
41
  Flat `agents/*.agent.md` files are parsed as a fallback when an agent has no
41
42
  composable directory — they are generated output (scripts/build-copilot-agents.mjs)
@@ -45,10 +46,16 @@ flat counterpart for the same agent id.
45
46
  ## Development
46
47
 
47
48
  Discovery and prompt composition live in `discover.ts` (unit-tested against
48
- fixture directories in `discover.test.ts`); `index.ts` only wires discovery
49
- into the extension API.
49
+ fixture directories in `discover.test.ts`); `index.ts` wires discovery into the
50
+ extension API and registers `--agent` at load time (`index.test.ts` covers that
51
+ timing and the flag contract).
52
+
53
+ `--agent` must be registered from the extension factory, not from a
54
+ `session_start` handler: PI matches parsed CLI flags against the extensions
55
+ loaded at startup, so late registration makes `pi --agent <name>` fail with
56
+ `Unknown option: --agent`.
50
57
 
51
58
  ```bash
52
- pnpm test # run discover.test.ts
59
+ pnpm test # run discover.test.ts and index.test.ts
53
60
  pnpm typecheck # tsc --noEmit
54
61
  ```