@jaybeeuu/agent-cortex 1.38.1 → 1.40.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.
- package/.agents/skills/style-versioning/SKILL.md +1 -1
- package/.github/workflows/ci.yml +47 -14
- package/AGENTS.md +19 -3
- package/CHANGELOG.md +42 -0
- package/README.md +118 -63
- package/agents/README.md +13 -10
- package/agents/ralph-plan/agent.md +1 -1
- package/agents/ralph-plan.agent.md +1 -1
- package/bin/agent-cortex.mjs +29 -8
- package/bin/installers/claude.mjs +569 -105
- package/bin/installers/copilot.mjs +22 -12
- package/bin/installers/pi.mjs +37 -34
- package/docs/claude-hooks.md +3 -3
- package/docs/ideas/extension-manifests.md +75 -0
- package/docs/ideas/record-ideas-as-beads.md +115 -0
- package/docs/ideas/subagent-visibility.md +72 -0
- package/docs/ideas/task-session-context.md +89 -0
- package/docs/research/skill-design-research.md +4 -4
- package/docs/research/skill-improvements-analysis.md +1 -1
- package/docs/skills/skill-anatomy.md +38 -2
- package/docs/technical-direction/migration-execution-labels.md +132 -0
- package/extensions/agent-modes/README.md +3 -2
- package/extensions/agent-modes/discover.test.ts +30 -7
- package/extensions/agent-modes/discover.ts +1 -1
- package/extensions/skill-stats/index.test.ts +3 -2
- package/lib/cli.mjs +24 -6
- package/package.json +4 -4
- package/plugin.json +1 -1
- package/pnpm-workspace.yaml +1 -1
- package/scripts/build-copilot-agents.mjs +1 -1
- package/scripts/check-no-sync.mjs +49 -0
- package/scripts/lib/compose-agent.mjs +32 -14
- package/skills/engineering/design-an-interface/SKILL.md +78 -58
- package/skills/engineering/grill-with-docs/REFERENCE.md +78 -0
- package/skills/engineering/grill-with-docs/SKILL.md +66 -63
- package/skills/engineering/improve-codebase-architecture/SKILL.md +66 -48
- package/skills/engineering/technical-direction/FORMAT.md +38 -0
- package/skills/engineering/technical-direction/SKILL.md +70 -77
- package/skills/planning/classify-bead/SKILL.md +59 -35
- package/skills/planning/create-task/REFERENCE.md +40 -0
- package/skills/planning/create-task/SKILL.md +70 -66
- package/skills/planning/create-task/scripts/create-chores.ts +6 -1
- package/skills/planning/create-task/scripts/node_modules/.bin/tsc +21 -0
- package/skills/planning/create-task/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/planning/create-task/scripts/node_modules/.bin/tsx +21 -0
- package/skills/planning/prd-to-tasks/REFERENCE.md +66 -0
- package/skills/planning/prd-to-tasks/SKILL.md +86 -170
- package/skills/planning/record-idea/FORMAT.md +44 -0
- package/skills/planning/record-idea/SKILL.md +70 -53
- package/skills/planning/request-refactor-plan/FORMAT.md +45 -0
- package/skills/planning/request-refactor-plan/SKILL.md +57 -46
- package/skills/planning/write-a-prd/FORMAT.md +54 -0
- package/skills/planning/write-a-prd/SKILL.md +49 -48
- package/skills/planning/write-a-ticket/SKILL.md +68 -53
- package/skills/productivity/handoff/SKILL.md +12 -0
- package/skills/productivity/init-beads/SKILL.md +48 -40
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsc +21 -0
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsx +21 -0
- package/skills/productivity/write-a-skill/SKILL.md +2 -1
- package/skills/review/maintain-agent-docs/SKILL.md +71 -51
- package/skills/workflow/hitl-collab/SKILL.md +86 -17
- package/skills/workflow/ralph/REFERENCE.md +22 -17
- package/skills/workflow/ralph/SKILL.md +88 -74
- package/skills/workflow/run-pipeline-stage/REFERENCE.md +22 -0
- package/skills/workflow/run-pipeline-stage/SKILL.md +91 -93
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +6 -107
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsc +21 -0
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsx +21 -0
- package/test/claude-installer.test.mjs +308 -0
- package/test/cli.test.mjs +40 -30
- package/test/copilot-installer.test.mjs +92 -82
- package/test/helpers/fake-claude.mjs +152 -0
- package/test/installer.test.mjs +309 -57
- package/test/pi-installer.test.mjs +115 -99
- package/token-map.README.md +21 -10
- package/token-map.json +7 -6
- package/.claude-plugin/marketplace.json +0 -16
- package/claude/.claude-plugin/plugin.json +0 -11
- package/claude/agents/plan.md +0 -58
- package/claude/agents/ralph-plan.md +0 -121
- package/claude/agents/ralph.md +0 -193
- package/claude/agents/strategy.md +0 -111
- package/claude/hooks/scripts/notify.mjs +0 -54
- package/claude/hooks.json +0 -38
- /package/{claude → claude-extras}/.mcp.json +0 -0
- /package/{claude → claude-extras}/scripts/statusline-command.sh +0 -0
|
@@ -58,9 +58,9 @@ Both repos follow the same two-part format: capability sentence + `"Use when..."
|
|
|
58
58
|
> "Disciplined diagnosis loop for hard bugs and performance regressions. Reproduce → minimise → hypothesise → instrument → fix → regression-test. Use when user says 'diagnose this' / 'debug this', reports a bug, says something is broken/throwing/failing, or describes a performance regression."
|
|
59
59
|
|
|
60
60
|
**agent-cortex example** (vaguer triggers):
|
|
61
|
-
> "Create a bead, classify it, and if AFK expand it into pipeline stage
|
|
61
|
+
> "Create a task bead, classify it as AFK or HITL, and if AFK expand it into pipeline stage chores plus a HITL PR gate. Use when starting tracked work with implementation and review gates — 'create a task for this', 'track this end-to-end', or 'get this into the ralph pipeline'."
|
|
62
62
|
|
|
63
|
-
The agent-cortex description
|
|
63
|
+
The agent-cortex description leads with internal mechanics ("task bead", "AFK", "HITL") rather than what the user is trying to accomplish — the quoted triggers were added in a later refactor, but the jargon remains.
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
@@ -180,7 +180,7 @@ Move to Phase 2 only when both boxes are checked.
|
|
|
180
180
|
|
|
181
181
|
**Evidence**: Anthropic Building Effective Agents: *"Think of this as writing a great docstring for a junior developer on your team."* For agents, descriptions are the only routing signal. FLAN (Wei et al., ICLR 2022) showed instruction-tuned models generalise from natural language task descriptions — use the vocabulary a user would *actually speak*, not internal system jargon. Anthropic tool use note: `"Use when"` language *actively counteracts* the model's default tendency to solve problems without invoking tools.
|
|
182
182
|
|
|
183
|
-
**Current state**: some agent-cortex descriptions describe internal mechanics rather than user intent. E.g., `create-task`: *"Use when
|
|
183
|
+
**Current state**: some agent-cortex descriptions describe internal mechanics rather than user intent. E.g., `create-task`: *"Use when starting tracked work with implementation and review gates — 'create a task for this', 'track this end-to-end', or 'get this into the ralph pipeline'."* — rewritten to add quoted user triggers (per this rec), though it still leans on internal jargon like "ralph pipeline" a user wouldn't say.
|
|
184
184
|
|
|
185
185
|
**Recommendation**: Rewrite descriptions to lead with user-vocabulary triggers. Include 2–4 quoted phrases or scenario patterns a user might say:
|
|
186
186
|
|
|
@@ -219,7 +219,7 @@ argument-hint: "Describe the bug or paste the error message"
|
|
|
219
219
|
|
|
220
220
|
### Rec 10 — Fix prd-to-epics Missing SKILL.md
|
|
221
221
|
|
|
222
|
-
**Current state**: `skills/prd-to-epics/` directory
|
|
222
|
+
**Current state**: Resolved in #16 — the broken `skills/prd-to-epics/` directory was removed; epic/task decomposition is consolidated into the `prd-to-tasks` skill (see CHANGELOG 1.0.0).
|
|
223
223
|
|
|
224
224
|
**Recommendation**: Either write the missing `SKILL.md` or remove the directory. This is a straightforward bug.
|
|
225
225
|
|
|
@@ -110,7 +110,7 @@ REFERENCE.md" — keeping SKILL.md as the primary working document rather than g
|
|
|
110
110
|
| Verification checklists | Planned |
|
|
111
111
|
| Concrete examples | Planned |
|
|
112
112
|
| Reconsider 100-line limit | Done (raised to 150) |
|
|
113
|
-
| ASCII workflow diagrams |
|
|
113
|
+
| ASCII workflow diagrams | Done (ASCII-only style guide in skill-anatomy.md) |
|
|
114
114
|
| Cross-references between skills | Planned |
|
|
115
115
|
| Consistent skeleton template | Partially adopted |
|
|
116
116
|
|
|
@@ -57,6 +57,23 @@ For each optional section, define **what it is**, include an **example snippet**
|
|
|
57
57
|
| `Phase-gate checklists` | Mid-workflow gates that must pass before proceeding | `- [ ] Phase 1 complete: baseline tests captured.` | Multi-phase workflows (3+ distinct phases) | Linear single-phase tasks |
|
|
58
58
|
| `Cross-skill references` | Explicit delegation to another skill for a real sub-task | `- Run \\`review-security\\` before committing.` | Another skill owns a sub-domain better than restating it | Self-contained skills |
|
|
59
59
|
| `Examples` | Full worked sample of expected outcome/output | `- Input: ... Output: ...` | Any non-trivial output contract | Trivially self-evident skills |
|
|
60
|
+
| `Workflow diagrams` | Inline ASCII diagram showing step flow and decision points | Fenced block of `[Box]`, `|`, `-`, `>`, `v` arrows, e.g. `[Start] --> [Validate] --> [Run]` | Multi-step or branching workflows where a glanceable flow improves comprehension | Simple linear workflows already clear from numbered steps |
|
|
61
|
+
|
|
62
|
+
## Workflow diagram style guide
|
|
63
|
+
|
|
64
|
+
When a skill includes a workflow diagram, follow these rules so it renders
|
|
65
|
+
reliably in any CLI:
|
|
66
|
+
|
|
67
|
+
- **ASCII only** — never Mermaid, images, or unicode box-drawing characters.
|
|
68
|
+
Keep to `|`, `-`, `>`, `+`, and `v`.
|
|
69
|
+
- **Fenced block** — wrap the diagram in a fenced code block so monospace is
|
|
70
|
+
preserved.
|
|
71
|
+
- **At most 80 characters per line** — narrow enough for terminal-width readers.
|
|
72
|
+
- **Short box labels** — imperative verbs or nouns (e.g. `[Validate scope]`),
|
|
73
|
+
never full sentences.
|
|
74
|
+
- **Read left-to-right, top-down** — prefer one branch level over dense joins.
|
|
75
|
+
- **Mirror the numbered workflow** — the diagram is a glanceable summary of the
|
|
76
|
+
`## Workflow` steps, never a source of truth that contradicts them.
|
|
60
77
|
|
|
61
78
|
## Section ordering
|
|
62
79
|
|
|
@@ -92,6 +109,7 @@ A skill passes anatomy review when:
|
|
|
92
109
|
- [ ] Tool references use `{{TOOL:name}}` tokens, not hardcoded tool names.
|
|
93
110
|
- [ ] Required sections exist and follow canonical ordering.
|
|
94
111
|
- [ ] Optional sections (if present) include what/when/example guidance.
|
|
112
|
+
- [ ] Workflow diagrams (if present) are ASCII-only and follow the workflow diagram style guide.
|
|
95
113
|
- [ ] Verification checklist contains concrete, machine-checkable items.
|
|
96
114
|
- [ ] Supporting docs are split by purpose when `SKILL.md` would exceed ~150 lines.
|
|
97
115
|
|
|
@@ -119,8 +137,26 @@ disable-model-invocation: false
|
|
|
119
137
|
## Workflow
|
|
120
138
|
|
|
121
139
|
1. Gather required context and validate scope boundaries.
|
|
122
|
-
2.
|
|
123
|
-
3.
|
|
140
|
+
2. Reject early with the reason when validation fails; otherwise continue.
|
|
141
|
+
3. Execute deterministic steps in order, recording key decisions.
|
|
142
|
+
4. Produce the required output artifact or terminal response.
|
|
143
|
+
|
|
144
|
+
Workflow at a glance (include only when the flow branches):
|
|
145
|
+
|
|
146
|
+
[Trigger]
|
|
147
|
+
|
|
|
148
|
+
v
|
|
149
|
+
[Gather context]
|
|
150
|
+
|
|
|
151
|
+
v
|
|
152
|
+
[Validate scope] --(invalid)--> [Reject early]
|
|
153
|
+
|
|
|
154
|
+
(valid)
|
|
155
|
+
v
|
|
156
|
+
[Run deterministic steps]
|
|
157
|
+
|
|
|
158
|
+
v
|
|
159
|
+
[Produce output artifact]
|
|
124
160
|
|
|
125
161
|
## Red Flags
|
|
126
162
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Technical Direction: Execution-Label Migration Tooling (two-axis model)
|
|
2
|
+
|
|
3
|
+
## Problem and target outcome
|
|
4
|
+
|
|
5
|
+
- Migrate all beads workspaces from `implementation-type:afk|hitl` to `execution:afk|hitl`
|
|
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.
|
|
9
|
+
|
|
10
|
+
## Current-state constraints
|
|
11
|
+
|
|
12
|
+
- bd CLI is a global binary (`~/.local/bin/bd`); label ops: `bd label list-all [--json]`,
|
|
13
|
+
`bd list --label-pattern/regex [--json]`, `bd label add/remove` (batch multiple ids).
|
|
14
|
+
- `bd list` supports `--label-pattern`/`--json`; `bd count` does NOT support `--label-pattern`.
|
|
15
|
+
- `bd ready --label-pattern 'stage:*'` works; `bd ready -l execution:afk` verified.
|
|
16
|
+
- 3 script packages exist with per-skill `package.json` + own `pnpm-workspace.yaml` +
|
|
17
|
+
own `pnpm-lock.yaml` (create-task, validate-agent-dir, run-pipeline-stage). They are NOT
|
|
18
|
+
members of the root workspace (root glob `skills/*/scripts` does not match the nested
|
|
19
|
+
`skills/<domain>/<name>/scripts`; root lockfile has zero refs to them).
|
|
20
|
+
- All 3 scripts are stdlib-only (`node:child_process`, `node:fs`, `node:path`, `node:url`).
|
|
21
|
+
- Extensions (`extensions/skill-stats`) have real peer deps on `@earendil-works/pi-coding-agent`
|
|
22
|
+
— they remain workspace members with their own package.json.
|
|
23
|
+
- `node_modules` gitignored; per-skill modules are installed manually today — a fragility in
|
|
24
|
+
fresh clones and PI-installed contexts.
|
|
25
|
+
|
|
26
|
+
## Decisions (locked in planning session)
|
|
27
|
+
|
|
28
|
+
### D1: Script runtime — all skill scripts become zero-dep `.mjs` with `// @ts-check`
|
|
29
|
+
|
|
30
|
+
- Every script in `skills/*/*/scripts/` runs with bare `node` (built-ins + shelling out to
|
|
31
|
+
`bd`). No tsx, no node_modules at runtime, no skill-scripts-dir resolution problem in any
|
|
32
|
+
installed context.
|
|
33
|
+
- Build-in-publish (Option B) rejected: contradicts the repo's no-compilation PI package
|
|
34
|
+
model; 3 tiny stdlib-only scripts do not justify dist artifacts.
|
|
35
|
+
- Root keeps `tsx`/`typescript`/`@types/node` for extensions and for `tsc --noEmit`
|
|
36
|
+
typechecking of `.mjs` via `checkJs`.
|
|
37
|
+
|
|
38
|
+
### D2: Single package.json (root workspace consolidation)
|
|
39
|
+
|
|
40
|
+
- Fold the 3 per-skill script packages into the root workspace: delete their `package.json`,
|
|
41
|
+
`pnpm-workspace.yaml`, `pnpm-lock.yaml`; convert scripts to `.mjs` (D1); root
|
|
42
|
+
`package.json` owns typecheck/test wiring.
|
|
43
|
+
- Extensions stay as workspace members (`extensions/*` glob) — their package.jsons are
|
|
44
|
+
semantically meaningful (peer deps on the pi runtime).
|
|
45
|
+
- Fresh-clone/installed-context verification: `pnpm install` at root then
|
|
46
|
+
`pnpm -r typecheck` + `pnpm -r test` (or root `node --test` wiring) cover all scripts.
|
|
47
|
+
|
|
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.
|
|
63
|
+
|
|
64
|
+
### D4: Migration skill
|
|
65
|
+
|
|
66
|
+
- 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).
|
|
70
|
+
- Repo-agnostic: no agent-cortex-specific paths in the runnable steps.
|
|
71
|
+
|
|
72
|
+
### D6: npm packaging hygiene (no build step — F9)
|
|
73
|
+
|
|
74
|
+
- The npm tarball currently ships the whole repo, including `.beads/` (the live task DB),
|
|
75
|
+
`.github/`, `.changeset/`, `.junie/`, tests, and token-map.json. No `.npmignore`, no
|
|
76
|
+
`files` field.
|
|
77
|
+
- Rejected: a compile build. Scripts are zero-dep `.mjs` (D1) — nothing to transpile;
|
|
78
|
+
extensions are TS by pi contract (`index.ts` entrypoint, loader transpiles; the pi runtime
|
|
79
|
+
import is type-only). Root build script is deliberately a no-op.
|
|
80
|
+
- Chosen: (1) `files` whitelist in package.json — ship only runtime assets (skills/, agents/,
|
|
81
|
+
extensions/, bin/, lib/, AGENTS.md, plugin.json, README, LICENSE); (2) `prepublishOnly`
|
|
82
|
+
gate running `pnpm typecheck && pnpm test`; (3) wire `test/cli.test.mjs` into root `test`.
|
|
83
|
+
- Revisit trigger: if an extension ever gains real runtime deps, bundle that extension with
|
|
84
|
+
esbuild at prepack — not a repo-wide build.
|
|
85
|
+
|
|
86
|
+
### D5: Knocked-on invocation changes (F4/F5)
|
|
87
|
+
|
|
88
|
+
- ralph init step 9: `pnpm --prefix skills/workflow/run-pipeline-stage/scripts exec tsx
|
|
89
|
+
generate-progress.ts` → `node <abs>/generate-progress.mjs`.
|
|
90
|
+
- create-task chore creation: `<skill-scripts>/node_modules/.bin/tsx create-chores.ts` →
|
|
91
|
+
`node <abs>/create-chores.mjs`.
|
|
92
|
+
|
|
93
|
+
## Options considered
|
|
94
|
+
|
|
95
|
+
### Option A: all scripts zero-dep `.mjs` (chosen)
|
|
96
|
+
- Pros: node-only everywhere; no install step; kills resolution problem; `node --test` native.
|
|
97
|
+
- Cons: JSDoc typing instead of TS syntax; small convention shift (`.ts` → `.mjs`).
|
|
98
|
+
|
|
99
|
+
### Option B: keep `.ts`, build in publish pipeline
|
|
100
|
+
- Pros: full TS syntax.
|
|
101
|
+
- Cons: compile step + dist artifacts contradict no-build PI package; installed contexts must
|
|
102
|
+
ship/install built output; over-engineered for 3 stdlib-only scripts.
|
|
103
|
+
|
|
104
|
+
### Option C: per-skill tsx packages (status quo, rejected)
|
|
105
|
+
- Pros: existing pattern.
|
|
106
|
+
- Cons: 3 orphaned mini-workspaces, manual install fragility, resolution doc dance
|
|
107
|
+
(`<skill-scripts>/node_modules/.bin/tsx`), contradicts single-package.json preference.
|
|
108
|
+
|
|
109
|
+
## Tradeoffs accepted
|
|
110
|
+
|
|
111
|
+
- Scripts use JSDoc annotations where types matter; `tsc --noEmit` with `checkJs` keeps a
|
|
112
|
+
typecheck gate at root.
|
|
113
|
+
- Closed beads get their history labels renamed — accepted for a clean label space.
|
|
114
|
+
|
|
115
|
+
## Validation plan
|
|
116
|
+
|
|
117
|
+
- F1-T3: scratch beads workspace (temp dir): create test beads with both labels + a dep pair,
|
|
118
|
+
dry-run (assert zero writes), apply, verify rename, re-run idempotence, `bd ready -l
|
|
119
|
+
execution:afk` smoke, then run the migration skill end-to-end once.
|
|
120
|
+
- F8-T3: fresh clone (or temp copy) + `pnpm install` at root; `pnpm -r typecheck` and
|
|
121
|
+
`pnpm -r test` green; scripts execute via bare `node`.
|
|
122
|
+
|
|
123
|
+
## Revisit triggers
|
|
124
|
+
|
|
125
|
+
- A future script needs third-party deps at runtime → revisit D1 (either root dep or
|
|
126
|
+
per-package re-introduction).
|
|
127
|
+
- New extension with conflicting devDeps → revisit D2 membership rules.
|
|
128
|
+
|
|
129
|
+
## References
|
|
130
|
+
|
|
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.
|
|
@@ -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
|
|
38
|
-
|
|
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)
|
|
@@ -137,8 +137,8 @@ describe("discoverAgents (composable source format)", () => {
|
|
|
137
137
|
|
|
138
138
|
// {{TOOL:bash}} → bash.
|
|
139
139
|
assert.match(plan.prompt, /Run with bash\./);
|
|
140
|
-
// {{TOOL:ask_user}} →
|
|
141
|
-
|
|
140
|
+
// {{TOOL:ask_user}} → ask_questions (pi's native structured-questions tool).
|
|
141
|
+
assert.match(plan.prompt, /Ask the user with ask_questions\./);
|
|
142
142
|
assert.doesNotMatch(plan.prompt, /\{\{TOOL:ask_user\}\}/);
|
|
143
143
|
// Relative {{PATH:...}} resolves against the actual package root (dir of agents/).
|
|
144
144
|
assert.match(plan.prompt, new RegExp(`Read the plan skill at ${escapeRegExp(fx.dir)}/skills/workflow/plan/SKILL\\.md\\.`));
|
|
@@ -147,7 +147,7 @@ describe("discoverAgents (composable source format)", () => {
|
|
|
147
147
|
assert.doesNotMatch(plan.prompt, /\{\{/);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
it("
|
|
150
|
+
it("maps ask_user → ask_questions and skill → read into the PI tool set", () => {
|
|
151
151
|
const fx = track(makeFixture());
|
|
152
152
|
writeComposableAgent(fx, "plan", {
|
|
153
153
|
frontmatter: {
|
|
@@ -159,8 +159,30 @@ describe("discoverAgents (composable source format)", () => {
|
|
|
159
159
|
|
|
160
160
|
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
161
161
|
assert.equal(agents.length, 1);
|
|
162
|
-
// ask_user →
|
|
163
|
-
assert.deepEqual(agents[0].tools, ["bash", "fetch_content"]);
|
|
162
|
+
// ask_user → ask_questions, skill → read (pi reads skill files), web_fetch → fetch_content.
|
|
163
|
+
assert.deepEqual(agents[0].tools, ["ask_questions", "bash", "fetch_content", "read"]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("warns and omits a tool whose pi mapping is null", () => {
|
|
167
|
+
const fx = track(makeFixture());
|
|
168
|
+
const NULL_MAP = {
|
|
169
|
+
tools: { ask_user: { copilot: "ask_user", claude: "AskUserQuestion", pi: null } },
|
|
170
|
+
paths: {},
|
|
171
|
+
} as TokenMap;
|
|
172
|
+
writeComposableAgent(fx, "plan", {
|
|
173
|
+
frontmatter: {
|
|
174
|
+
name: "agent-cortex:plan",
|
|
175
|
+
description: "Planner",
|
|
176
|
+
tools: ["ask_user", "bash"],
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const agents = discoverAgents(fx.agentsDir, NULL_MAP);
|
|
181
|
+
assert.equal(agents.length, 1);
|
|
182
|
+
// With a null pi column the entry is omitted (and warned about), per the contract.
|
|
183
|
+
// (bash survives so the result is observable — an all-dropped list would be
|
|
184
|
+
// normalised to DEFAULT_TOOLS, which is "no restriction", not the omit result.)
|
|
185
|
+
assert.deepEqual(agents[0].tools, ["bash"]);
|
|
164
186
|
});
|
|
165
187
|
|
|
166
188
|
it("uses the built-in fallback map when token-map.json is unavailable", () => {
|
|
@@ -230,8 +252,9 @@ describe("loadTokenMap", () => {
|
|
|
230
252
|
it("reads the real token-map.json at the package root", () => {
|
|
231
253
|
assert.ok(REAL_TOKEN_MAP);
|
|
232
254
|
assert.equal(typeof REAL_TOKEN_MAP?.tools?.bash?.pi, "string");
|
|
233
|
-
// ask_user
|
|
234
|
-
assert.equal(REAL_TOKEN_MAP?.tools?.ask_user?.pi,
|
|
255
|
+
// ask_user → ask_questions (pi's native structured-questions tool); skill → read.
|
|
256
|
+
assert.equal(REAL_TOKEN_MAP?.tools?.ask_user?.pi, "ask_questions");
|
|
257
|
+
assert.equal(REAL_TOKEN_MAP?.tools?.skill?.pi, "read");
|
|
235
258
|
assert.ok(REAL_TOKEN_MAP?.paths?.agents_dir);
|
|
236
259
|
});
|
|
237
260
|
});
|
|
@@ -120,7 +120,7 @@ function prettifyId(id: string): string {
|
|
|
120
120
|
* Translate a frontmatter tool list into PI tool names.
|
|
121
121
|
*
|
|
122
122
|
* With token-map.json: canonical keys are translated via the pi column; a null
|
|
123
|
-
* mapping means PI has no equivalent
|
|
123
|
+
* mapping means PI has no equivalent for that tool and the entry is omitted;
|
|
124
124
|
* a name that is not a canonical key passes through untouched (it may be a
|
|
125
125
|
* native PI tool such as `fetch_content`). Without the map, the built-in
|
|
126
126
|
* COPILOT_TO_PI fallback applies and unknown names pass through.
|
|
@@ -331,17 +331,18 @@ describe("loadStore / saveStore", () => {
|
|
|
331
331
|
const file = join(tmpDir, "test-store.json");
|
|
332
332
|
const original = freshStore();
|
|
333
333
|
original.totalTurns = 42;
|
|
334
|
+
const recentTs = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString();
|
|
334
335
|
original.skills["test-skill"] = {
|
|
335
336
|
loadedCount: 5,
|
|
336
337
|
invokedCount: 2,
|
|
337
338
|
readCount: 3,
|
|
338
|
-
lastUsed:
|
|
339
|
+
lastUsed: recentTs,
|
|
339
340
|
byProject: {
|
|
340
341
|
"/project/a": {
|
|
341
342
|
loadedCount: 3,
|
|
342
343
|
invokedCount: 1,
|
|
343
344
|
readCount: 2,
|
|
344
|
-
lastUsed:
|
|
345
|
+
lastUsed: recentTs,
|
|
345
346
|
},
|
|
346
347
|
},
|
|
347
348
|
};
|
package/lib/cli.mjs
CHANGED
|
@@ -5,9 +5,10 @@ const SUPPORTED_HARNESSES = ["copilot", "claude", "pi"];
|
|
|
5
5
|
// malformed options surface as `optionError` on the parsed result; the CLI
|
|
6
6
|
// prints them and exits 1.
|
|
7
7
|
const INSTALL_OPTIONS = {
|
|
8
|
-
"--dry-run": { key: "dryRun" },
|
|
8
|
+
"--dry-run": { key: "dryRun", boolean: true },
|
|
9
9
|
"--output": { key: "output" },
|
|
10
10
|
"--plugin-root": { key: "pluginRoot" },
|
|
11
|
+
"--require-register": { key: "requireRegister", boolean: true },
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export function parseArgs(argv) {
|
|
@@ -34,8 +35,8 @@ export function parseArgs(argv) {
|
|
|
34
35
|
if (!spec) {
|
|
35
36
|
return { command: "install", harness, optionError: `Unknown option "${arg}". Run "agent-cortex --help" for usage.` };
|
|
36
37
|
}
|
|
37
|
-
if (spec.
|
|
38
|
-
options.
|
|
38
|
+
if (spec.boolean) {
|
|
39
|
+
options[spec.key] = true;
|
|
39
40
|
continue;
|
|
40
41
|
}
|
|
41
42
|
if (inlineValue !== null) {
|
|
@@ -66,9 +67,26 @@ Commands:
|
|
|
66
67
|
install <harness> Install agent-cortex for a harness
|
|
67
68
|
Supported harnesses: ${SUPPORTED_HARNESSES.join(", ")}
|
|
68
69
|
Options (claude):
|
|
69
|
-
--output
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
(no --output) Materialise the plugin into
|
|
71
|
+
~/.agent-cortex/claude, write the
|
|
72
|
+
marketplace manifest at
|
|
73
|
+
~/.agent-cortex/.claude-plugin/
|
|
74
|
+
marketplace.json, and register it with
|
|
75
|
+
Claude Code via the "claude plugin" CLI
|
|
76
|
+
(marketplace add → install → update,
|
|
77
|
+
idempotent by state: a re-run is the
|
|
78
|
+
update path, repeat install a no-op;
|
|
79
|
+
requires the plugin CLI, v2+)
|
|
80
|
+
--output <dir> Write the plugin subtree to <dir> only —
|
|
81
|
+
no marketplace manifest or registration
|
|
82
|
+
(default: ~/.agent-cortex/claude)
|
|
83
|
+
--dry-run Print what would be generated/registered
|
|
84
|
+
without writing
|
|
85
|
+
--require-register
|
|
86
|
+
Fail the install when Claude Code
|
|
87
|
+
registration cannot run (missing CLI,
|
|
88
|
+
pre-v2 build); default warns and prints
|
|
89
|
+
the manual registration commands
|
|
72
90
|
Options (copilot):
|
|
73
91
|
--output <dir> Write the flat *.agent.md files to <dir>
|
|
74
92
|
(default: <package root>/agents — the dir
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaybeeuu/agent-cortex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.40.0",
|
|
4
4
|
"description": "Personal PI package with custom agents, skills, and extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "echo 'Build complete — no compilation needed for PI package'",
|
|
31
|
-
"lint": "pnpm typecheck",
|
|
31
|
+
"lint": "pnpm typecheck && pnpm check:sync",
|
|
32
|
+
"check:sync": "node scripts/check-no-sync.mjs",
|
|
32
33
|
"test": "node --test test/*.test.mjs && pnpm -r test",
|
|
33
34
|
"typecheck": "pnpm -r typecheck",
|
|
34
|
-
"build:claude": "node bin/agent-cortex.mjs install claude",
|
|
35
35
|
"build:copilot": "node scripts/build-copilot-agents.mjs",
|
|
36
36
|
"changeset": "changeset",
|
|
37
|
-
"version-packages": "changeset version && bash scripts/sync-plugin-version.sh && pnpm build:
|
|
37
|
+
"version-packages": "changeset version && bash scripts/sync-plugin-version.sh && pnpm build:copilot",
|
|
38
38
|
"publish-package": "TARBALL=$(pnpm pack | tail -n 1) && npm publish \"$TARBALL\" --provenance --access public",
|
|
39
39
|
"release": "changeset publish",
|
|
40
40
|
"postinstall": "bash scripts/install-instructions.sh"
|
package/plugin.json
CHANGED
package/pnpm-workspace.yaml
CHANGED
|
@@ -27,4 +27,4 @@ import { installCopilot } from "../bin/installers/copilot.mjs";
|
|
|
27
27
|
|
|
28
28
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
29
29
|
|
|
30
|
-
installCopilot({ root: ROOT, output: join(ROOT, "agents") });
|
|
30
|
+
await installCopilot({ root: ROOT, output: join(ROOT, "agents") });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Repo convention: never block the main thread — no `*Sync` functions
|
|
2
|
+
// (fs/child_process/...) in tooling code. Runs via `pnpm lint` (CI) and
|
|
3
|
+
// locally. Scans the toolchain and test dirs: bin/, scripts/, lib/, hooks/,
|
|
4
|
+
// test/. Skill-owned scripts (skills/*/scripts) and extensions are separate
|
|
5
|
+
// follow-up surfaces tracked outside this check.
|
|
6
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
7
|
+
import { join, relative } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
11
|
+
const SCAN_DIRS = ["bin", "scripts", "lib", "hooks", "test"];
|
|
12
|
+
const EXTENSIONS = new Set([".mjs", ".js", ".ts"]);
|
|
13
|
+
const SELF = "scripts/check-no-sync.mjs";
|
|
14
|
+
|
|
15
|
+
const SYNC_CALL = /\b\w*Sync\s*\(/;
|
|
16
|
+
|
|
17
|
+
/** Recursively collect files under dir, skipping this script and node_modules. */
|
|
18
|
+
async function collect(dir) {
|
|
19
|
+
const out = [];
|
|
20
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
21
|
+
for (const e of entries) {
|
|
22
|
+
if (e.name === "node_modules") continue;
|
|
23
|
+
const full = join(dir, e.name);
|
|
24
|
+
if (e.isDirectory()) out.push(...(await collect(full)));
|
|
25
|
+
else if (EXTENSIONS.has(e.name.slice(e.name.lastIndexOf(".")))) out.push(full);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const offenders = [];
|
|
31
|
+
for (const dir of SCAN_DIRS) {
|
|
32
|
+
const abs = join(ROOT, dir);
|
|
33
|
+
for (const file of await collect(abs)) {
|
|
34
|
+
const rel = relative(ROOT, file);
|
|
35
|
+
if (rel === SELF) continue;
|
|
36
|
+
const src = await readFile(file, "utf-8");
|
|
37
|
+
const hits = src.split("\n").map((line, i) => ({ line: i + 1, text: line })).filter((l) => SYNC_CALL.test(l.text));
|
|
38
|
+
if (hits.length) offenders.push({ rel, hits });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (offenders.length) {
|
|
43
|
+
console.error("✗ Never block the main thread — *Sync function calls found:");
|
|
44
|
+
for (const { rel, hits } of offenders) {
|
|
45
|
+
for (const { line, text } of hits) console.error(` ${rel}:${line}: ${text.trim()}`);
|
|
46
|
+
}
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
console.log("✓ no *Sync calls in tooling code (bin/, scripts/, lib/, hooks/, test/)");
|
|
@@ -29,9 +29,19 @@
|
|
|
29
29
|
//
|
|
30
30
|
// Zero dependencies so it runs on the CI Node (20) and local Node alike.
|
|
31
31
|
|
|
32
|
-
import {
|
|
32
|
+
import { readFile, stat } from "node:fs/promises";
|
|
33
33
|
import { join } from "node:path";
|
|
34
34
|
|
|
35
|
+
/** True when a path exists (stat succeeds). */
|
|
36
|
+
async function pathExists(p) {
|
|
37
|
+
try {
|
|
38
|
+
await stat(p);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// ─── Token substitution ──────────────────────────────────────────────────────
|
|
36
46
|
|
|
37
47
|
const TOOL_RE = /\{\{TOOL:([^}]+)\}\}/g;
|
|
@@ -92,22 +102,30 @@ export function substituteTokens(content, harness, tokenMap, options = {}) {
|
|
|
92
102
|
* @param {string} harness Harness id: "copilot" | "claude" | "pi"
|
|
93
103
|
* @returns {{ name: string, description: string, tools: string[], model?: string, argumentHint?: string, body: string }}
|
|
94
104
|
*/
|
|
95
|
-
export function composeAgent(root, name, harness, options = {}) {
|
|
105
|
+
export async function composeAgent(root, name, harness, options = {}) {
|
|
96
106
|
const { dropNullTools = false, pluginRoot, resolveRelativePaths = false, warn = noop } = options;
|
|
97
107
|
const agentDir = join(root, "agents", name);
|
|
98
|
-
const frontmatter = parseFrontmatter(join(agentDir, harness, "frontmatter.json"), name, harness);
|
|
99
|
-
const tokenMap = loadTokenMap(root);
|
|
108
|
+
const frontmatter = await parseFrontmatter(join(agentDir, harness, "frontmatter.json"), name, harness);
|
|
109
|
+
const tokenMap = await loadTokenMap(root);
|
|
100
110
|
|
|
101
|
-
let body =
|
|
111
|
+
let body = await readFile(join(agentDir, "agent.md"), "utf-8");
|
|
102
112
|
|
|
103
|
-
// 1. {{SECTION:name}} → <harness>/<name>.md (may itself contain TOOL/PATH
|
|
104
|
-
|
|
113
|
+
// 1. {{SECTION:name}} → <harness>/<name>.md (may itself contain TOOL/PATH
|
|
114
|
+
// tokens). Single pass over the original body — inserted section content
|
|
115
|
+
// is never rescanned (same semantics as the sync replace()).
|
|
116
|
+
const sectionRe = /\{\{SECTION:([^}]+)\}\}/g;
|
|
117
|
+
let out = "";
|
|
118
|
+
let last = 0;
|
|
119
|
+
for (let m = sectionRe.exec(body); m; m = sectionRe.exec(body)) {
|
|
120
|
+
const section = m[1];
|
|
105
121
|
const sectionPath = join(agentDir, harness, `${section}.md`);
|
|
106
|
-
if (!
|
|
122
|
+
if (!(await pathExists(sectionPath))) {
|
|
107
123
|
throw new Error(`agent "${name}": missing section "${section}" for harness "${harness}" (${sectionPath})`);
|
|
108
124
|
}
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
out += body.slice(last, m.index) + (await readFile(sectionPath, "utf-8")).trim();
|
|
126
|
+
last = m.index + m[0].length;
|
|
127
|
+
}
|
|
128
|
+
body = out + body.slice(last);
|
|
111
129
|
|
|
112
130
|
// 2. + 3. TOOL/PATH substitution (contextualised for error messages)
|
|
113
131
|
body = substituteTokens(body, harness, tokenMap, {
|
|
@@ -127,8 +145,8 @@ const REQUIRED_FIELDS = ["name", "description", "tools"];
|
|
|
127
145
|
const KNOWN_OPTIONAL_FIELDS = ["model", "argumentHint"];
|
|
128
146
|
|
|
129
147
|
/** Parse and schema-check a harness frontmatter.json (strict, per agents/README.md). */
|
|
130
|
-
function parseFrontmatter(path, name, harness) {
|
|
131
|
-
const raw =
|
|
148
|
+
async function parseFrontmatter(path, name, harness) {
|
|
149
|
+
const raw = await readFile(path, "utf-8");
|
|
132
150
|
let fm;
|
|
133
151
|
try {
|
|
134
152
|
fm = JSON.parse(raw);
|
|
@@ -225,11 +243,11 @@ export function translateToolList(tools, tokenMap, harness, { warn = noop } = {}
|
|
|
225
243
|
// ─── Token map ───────────────────────────────────────────────────────────────
|
|
226
244
|
|
|
227
245
|
/** Load token-map.json from the package root; missing/incomplete maps hard-fail. */
|
|
228
|
-
export function loadTokenMap(root) {
|
|
246
|
+
export async function loadTokenMap(root) {
|
|
229
247
|
const path = join(root, "token-map.json");
|
|
230
248
|
let raw;
|
|
231
249
|
try {
|
|
232
|
-
raw =
|
|
250
|
+
raw = await readFile(path, "utf-8");
|
|
233
251
|
} catch {
|
|
234
252
|
throw new Error(`missing ${path} — the composer requires token-map.json`);
|
|
235
253
|
}
|