@jaybeeuu/agent-cortex 1.37.1 → 1.39.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 +9 -8
- package/.github/workflows/ci.yml +190 -8
- package/AGENTS.md +27 -6
- package/CHANGELOG.md +72 -0
- package/README.md +156 -39
- package/agents/README.md +49 -13
- package/agents/plan/agent.md +51 -0
- package/agents/plan/claude/frontmatter.json +6 -0
- package/agents/plan/copilot/frontmatter.json +6 -0
- package/agents/plan/pi/frontmatter.json +6 -0
- package/agents/plan.agent.md +3 -0
- package/agents/ralph/agent.md +262 -0
- package/agents/ralph/claude/frontmatter.json +6 -0
- package/agents/ralph/claude/polling.md +31 -0
- package/agents/ralph/copilot/frontmatter.json +6 -0
- package/agents/ralph/copilot/polling.md +62 -0
- package/agents/ralph/pi/frontmatter.json +6 -0
- package/agents/ralph/pi/polling.md +74 -0
- package/agents/ralph-plan/agent.md +114 -0
- package/agents/ralph-plan/claude/frontmatter.json +6 -0
- package/agents/ralph-plan/copilot/frontmatter.json +6 -0
- package/agents/ralph-plan/pi/frontmatter.json +6 -0
- package/agents/ralph-plan.agent.md +6 -3
- package/agents/ralph.agent.md +83 -57
- package/agents/strategy/agent.md +104 -0
- package/agents/strategy/claude/frontmatter.json +6 -0
- package/agents/strategy/copilot/frontmatter.json +6 -0
- package/agents/strategy/pi/frontmatter.json +6 -0
- package/agents/strategy.agent.md +3 -0
- package/bin/agent-cortex.mjs +76 -1
- package/bin/installers/claude.mjs +376 -0
- package/bin/installers/copilot.mjs +129 -0
- package/bin/installers/pi.mjs +197 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/agents/plan.md +3 -1
- package/claude/agents/ralph-plan.md +6 -4
- package/claude/agents/strategy.md +3 -1
- package/claude/hooks/scripts/notify.mjs +54 -0
- package/claude/hooks.json +15 -0
- package/docs/claude-hooks.md +115 -0
- package/docs/ideas/auto-model-selection.md +75 -0
- package/docs/ideas/extension-manifests.md +75 -0
- package/docs/ideas/improve-pi-system-prompt.md +34 -0
- package/docs/ideas/model-selector.md +40 -0
- package/docs/ideas/pi-task-memory.md +34 -0
- package/docs/ideas/qa-extension.md +43 -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/ideas/unified-ci-pipeline.md +28 -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 +47 -2
- package/docs/technical-direction/migration-execution-labels.md +132 -0
- package/extensions/agent-modes/README.md +54 -0
- package/extensions/agent-modes/discover.test.ts +241 -0
- package/extensions/agent-modes/discover.ts +458 -0
- package/extensions/agent-modes/index.ts +13 -224
- package/extensions/agent-modes/node_modules/.bin/pi +21 -0
- package/extensions/agent-modes/node_modules/.bin/tsc +21 -0
- package/extensions/agent-modes/node_modules/.bin/tsserver +21 -0
- package/extensions/agent-modes/node_modules/.bin/tsx +21 -0
- package/extensions/agent-modes/package.json +20 -0
- package/extensions/agent-modes/tsconfig.json +12 -0
- package/extensions/subagent/README.md +19 -0
- package/extensions/subagent/index.ts +131 -45
- package/extensions/subagent/node_modules/.bin/pi +21 -0
- package/extensions/subagent/node_modules/.bin/pi-ai +21 -0
- package/extensions/subagent/node_modules/.bin/tsc +21 -0
- package/extensions/subagent/node_modules/.bin/tsserver +21 -0
- package/extensions/subagent/node_modules/.bin/tsx +21 -0
- package/extensions/subagent/package.json +23 -0
- package/extensions/subagent/tsconfig.json +12 -0
- package/extensions/subagent/wait.test.ts +202 -0
- package/extensions/subagent/wait.ts +118 -0
- package/hooks/claude/hooks.json +38 -0
- package/hooks/claude/scripts/notify.mjs +54 -0
- package/lib/cli.mjs +68 -3
- package/package.json +6 -4
- package/plugin.json +1 -1
- package/pnpm-workspace.yaml +1 -1
- package/scripts/build-copilot-agents.mjs +30 -0
- package/scripts/lib/compose-agent.mjs +241 -0
- 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 +24 -19
- 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/generate-progress.ts +4 -2
- 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/skills/workflow/using-agent-skills/SKILL.md +122 -0
- package/test/claude-installer.test.mjs +248 -0
- package/test/cli.test.mjs +115 -2
- package/test/copilot-installer.test.mjs +298 -0
- package/test/installer.test.mjs +179 -0
- package/test/notify-hook.test.mjs +63 -0
- package/test/pi-installer.test.mjs +371 -0
- package/token-map.README.md +130 -0
- package/token-map.json +106 -0
- package/.github/workflows/ci.test.ts +0 -217
- package/.github/workflows/release.yml +0 -58
- package/scripts/build-claude-agents.mjs +0 -179
- package/scripts/publish-release.sh +0 -32
|
@@ -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.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Agent Modes Extension
|
|
2
|
+
|
|
3
|
+
Makes the agent-cortex agents (ralph, ralph-plan, plan, strategy) available as
|
|
4
|
+
switchable PI modes. Each mode injects the agent's full system prompt into the
|
|
5
|
+
running context, restricts tools to the agent's declared tool set, and shows a
|
|
6
|
+
status indicator with the active agent name.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
| Command | Description |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `/agent` | Show the agent mode selector |
|
|
13
|
+
| `/agent <name>` | Switch to an agent mode directly (e.g. `/agent ralph`) |
|
|
14
|
+
| `/agent default` | Return to default mode (full tool access) |
|
|
15
|
+
| `Ctrl+Shift+A` | Cycle through agent modes |
|
|
16
|
+
| `--agent <name>` | Start PI in a specific agent mode |
|
|
17
|
+
|
|
18
|
+
## How agents are discovered
|
|
19
|
+
|
|
20
|
+
Agents are read from the **composable source format** in the package's
|
|
21
|
+
`agents/` directory:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
agents/<name>/
|
|
25
|
+
├── agent.md # Shared body (harness-agnostic)
|
|
26
|
+
└── pi/
|
|
27
|
+
├── frontmatter.json # PI metadata: name, description, tools
|
|
28
|
+
└── <section>.md # Optional sections referenced by {{SECTION:name}}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The PI system prompt is composed from `agent.md` per the token-map.json
|
|
32
|
+
contract:
|
|
33
|
+
|
|
34
|
+
1. `{{SECTION:name}}` tokens are replaced with the agent's `pi/<name>.md`.
|
|
35
|
+
2. `{{TOOL:key}}` / `{{PATH:key}}` tokens are substituted against the pi column
|
|
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.
|
|
39
|
+
|
|
40
|
+
Flat `agents/*.agent.md` files are parsed as a fallback when an agent has no
|
|
41
|
+
composable directory — they are generated output (scripts/build-copilot-agents.mjs)
|
|
42
|
+
but may exist for non-migrated agents. A composable directory always wins over its
|
|
43
|
+
flat counterpart for the same agent id.
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
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.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm test # run discover.test.ts
|
|
53
|
+
pnpm typecheck # tsc --noEmit
|
|
54
|
+
```
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { describe, it, after } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type AgentDef,
|
|
10
|
+
type TokenMap,
|
|
11
|
+
discoverAgents,
|
|
12
|
+
loadTokenMap,
|
|
13
|
+
} from "./discover.ts";
|
|
14
|
+
|
|
15
|
+
// Real token-map.json at the package root — the canonical tool/path map the
|
|
16
|
+
// extension ships with. Used to verify translation against the source of truth.
|
|
17
|
+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
18
|
+
const REAL_TOKEN_MAP = loadTokenMap(join(REPO_ROOT, "agents"));
|
|
19
|
+
|
|
20
|
+
// ─── Fixtures ─────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
interface Fixture {
|
|
23
|
+
dir: string;
|
|
24
|
+
agentsDir: string;
|
|
25
|
+
cleanup: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function makeFixture(): Fixture {
|
|
29
|
+
const dir = mkdtempSync(join(tmpdir(), "agent-modes-"));
|
|
30
|
+
return {
|
|
31
|
+
dir,
|
|
32
|
+
agentsDir: join(dir, "agents"),
|
|
33
|
+
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeFixture(fx: Fixture, relPath: string, content: string): void {
|
|
38
|
+
const p = join(fx.dir, relPath);
|
|
39
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
40
|
+
writeFileSync(p, content);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeComposableAgent(
|
|
44
|
+
fx: Fixture,
|
|
45
|
+
name: string,
|
|
46
|
+
opts: {
|
|
47
|
+
body?: string;
|
|
48
|
+
frontmatter?: Record<string, unknown>;
|
|
49
|
+
sections?: Record<string, string>;
|
|
50
|
+
} = {},
|
|
51
|
+
): void {
|
|
52
|
+
const base = `agents/${name}`;
|
|
53
|
+
writeFixture(fx, `${base}/agent.md`, opts.body ?? `# ${name}\nBody of ${name}.`);
|
|
54
|
+
writeFixture(
|
|
55
|
+
fx,
|
|
56
|
+
`${base}/pi/frontmatter.json`,
|
|
57
|
+
JSON.stringify(
|
|
58
|
+
opts.frontmatter ?? {
|
|
59
|
+
name: `agent-cortex:${name}`,
|
|
60
|
+
description: `Description of ${name}`,
|
|
61
|
+
tools: ["bash", "view", "task"],
|
|
62
|
+
},
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
for (const [section, body] of Object.entries(opts.sections ?? {})) {
|
|
66
|
+
writeFixture(fx, `${base}/pi/${section}.md`, body);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeFlatAgent(fx: Fixture, name: string, body: string): void {
|
|
71
|
+
writeFixture(fx, `agents/${name}.agent.md`, body);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fixtures: Fixture[] = [];
|
|
75
|
+
function track(fx: Fixture): Fixture {
|
|
76
|
+
fixtures.push(fx);
|
|
77
|
+
return fx;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
after(() => {
|
|
81
|
+
for (const fx of fixtures) fx.cleanup();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const FLAT_FRONTMATTER = (name: string, tools: string) =>
|
|
85
|
+
`---\ndescription: "Flat description of ${name}"\nname: "agent-cortex:${name}"\ntools: ${tools}\n---\n\nFlat body of ${name}.`;
|
|
86
|
+
|
|
87
|
+
// ─── Tokens ───────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
const TOKEN_PROSE =
|
|
90
|
+
"Run with {{TOOL:bash}}.\n" +
|
|
91
|
+
"Ask the user with {{TOOL:ask_user}}.\n" +
|
|
92
|
+
"Read the plan skill at {{PATH:skills/workflow/plan/SKILL.md}}.\n" +
|
|
93
|
+
"Agents live in {{PATH:agents_dir}}.";
|
|
94
|
+
|
|
95
|
+
// ─── Composable discovery ────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
describe("discoverAgents (composable source format)", () => {
|
|
98
|
+
it("discovers agents from <name>/agent.md + <name>/pi/frontmatter.json", () => {
|
|
99
|
+
const fx = track(makeFixture());
|
|
100
|
+
writeComposableAgent(fx, "ralph", {
|
|
101
|
+
body: "# ralph\nUse {{TOOL:task}} to spawn subagents.\n\n{{SECTION:polling}}",
|
|
102
|
+
frontmatter: {
|
|
103
|
+
name: "agent-cortex:ralph",
|
|
104
|
+
description: "Runs beads end-to-end",
|
|
105
|
+
tools: ["bash", "view", "rg", "glob", "task", "read_agent"],
|
|
106
|
+
},
|
|
107
|
+
sections: {
|
|
108
|
+
polling: "## PI polling\nPoll with task + read_agent.",
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
113
|
+
|
|
114
|
+
assert.equal(agents.length, 1);
|
|
115
|
+
const ralph = agents[0];
|
|
116
|
+
assert.equal(ralph.id, "ralph");
|
|
117
|
+
assert.equal(ralph.name, "agent-cortex:ralph");
|
|
118
|
+
assert.equal(ralph.description, "Runs beads end-to-end");
|
|
119
|
+
// Canonical tool names translated to PI names via token-map (glob→find, rg→grep, view→read).
|
|
120
|
+
assert.deepEqual(ralph.tools, ["bash", "find", "grep", "read", "read_agent", "task"]);
|
|
121
|
+
// SECTION token resolved from pi/<section>.md; TOOL token substituted.
|
|
122
|
+
assert.match(ralph.prompt, /Use task to spawn subagents\./);
|
|
123
|
+
assert.match(ralph.prompt, /Poll with task \+ read_agent\./);
|
|
124
|
+
assert.doesNotMatch(ralph.prompt, /\{\{/);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("substitutes TOOL / PATH tokens against token-map.json using the real plugin root", () => {
|
|
128
|
+
const fx = track(makeFixture());
|
|
129
|
+
writeComposableAgent(fx, "plan", {
|
|
130
|
+
body: TOKEN_PROSE,
|
|
131
|
+
frontmatter: { name: "agent-cortex:plan", description: "Planner", tools: [] },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
135
|
+
assert.equal(agents.length, 1);
|
|
136
|
+
const plan = agents[0];
|
|
137
|
+
|
|
138
|
+
// {{TOOL:bash}} → bash.
|
|
139
|
+
assert.match(plan.prompt, /Run with bash\./);
|
|
140
|
+
// {{TOOL:ask_user}} → null for pi — token dropped (dangling prose is the author's
|
|
141
|
+
// responsibility per token-map.README), not left as literal token syntax.
|
|
142
|
+
assert.doesNotMatch(plan.prompt, /\{\{TOOL:ask_user\}\}/);
|
|
143
|
+
// Relative {{PATH:...}} resolves against the actual package root (dir of agents/).
|
|
144
|
+
assert.match(plan.prompt, new RegExp(`Read the plan skill at ${escapeRegExp(fx.dir)}/skills/workflow/plan/SKILL\\.md\\.`));
|
|
145
|
+
// Named {{PATH:agents_dir}} resolves to <root>/agents.
|
|
146
|
+
assert.match(plan.prompt, new RegExp(`Agents live in ${escapeRegExp(fx.agentsDir)}\\.`));
|
|
147
|
+
assert.doesNotMatch(plan.prompt, /\{\{/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("drops null-mapped tools (ask_user, skill) from the PI tool set", () => {
|
|
151
|
+
const fx = track(makeFixture());
|
|
152
|
+
writeComposableAgent(fx, "plan", {
|
|
153
|
+
frontmatter: {
|
|
154
|
+
name: "agent-cortex:plan",
|
|
155
|
+
description: "Planner",
|
|
156
|
+
tools: ["bash", "ask_user", "skill", "web_fetch"],
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
161
|
+
assert.equal(agents.length, 1);
|
|
162
|
+
// ask_user → null (omitted), skill → null (omitted), web_fetch → fetch_content.
|
|
163
|
+
assert.deepEqual(agents[0].tools, ["bash", "fetch_content"]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("uses the built-in fallback map when token-map.json is unavailable", () => {
|
|
167
|
+
const fx = track(makeFixture());
|
|
168
|
+
writeComposableAgent(fx, "legacy", {
|
|
169
|
+
frontmatter: {
|
|
170
|
+
name: "agent-cortex:legacy",
|
|
171
|
+
description: "No token map",
|
|
172
|
+
tools: ["view", "glob", "ask_user"],
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const agents = discoverAgents(fx.agentsDir);
|
|
177
|
+
assert.equal(agents.length, 1);
|
|
178
|
+
// Copilot→PI fallback: view→read, glob→find+ls; unmapped ask_user passes through.
|
|
179
|
+
assert.deepEqual(agents[0].tools, ["ask_user", "find", "ls", "read"]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("returns an empty list for a missing agents directory", () => {
|
|
183
|
+
const fx = track(makeFixture());
|
|
184
|
+
assert.deepEqual(discoverAgents(join(fx.dir, "does-not-exist"), REAL_TOKEN_MAP), []);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("returns an empty list when no agents are present", () => {
|
|
188
|
+
const fx = track(makeFixture());
|
|
189
|
+
mkdirSync(fx.agentsDir, { recursive: true });
|
|
190
|
+
assert.deepEqual(discoverAgents(fx.agentsDir, REAL_TOKEN_MAP), []);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ─── Flat fallback ────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
describe("discoverAgents (flat fallback)", () => {
|
|
197
|
+
it("prefers a composable directory over its flat file for the same agent", () => {
|
|
198
|
+
const fx = track(makeFixture());
|
|
199
|
+
writeComposableAgent(fx, "ralph", { body: "Composable body." });
|
|
200
|
+
writeFlatAgent(fx, "ralph", FLAT_FRONTMATTER("ralph", '["bash", "view"]'));
|
|
201
|
+
|
|
202
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
203
|
+
|
|
204
|
+
assert.equal(agents.length, 1);
|
|
205
|
+
assert.equal(agents[0].id, "ralph");
|
|
206
|
+
assert.match(agents[0].prompt, /Composable body\./);
|
|
207
|
+
assert.doesNotMatch(agents[0].prompt, /Flat body/);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("falls back to flat *.agent.md for agents without a composable directory", () => {
|
|
211
|
+
const fx = track(makeFixture());
|
|
212
|
+
writeFlatAgent(fx, "strategy", FLAT_FRONTMATTER("strategy", '["bash", "view", "web_fetch"]'));
|
|
213
|
+
|
|
214
|
+
const agents = discoverAgents(fx.agentsDir, REAL_TOKEN_MAP);
|
|
215
|
+
|
|
216
|
+
assert.equal(agents.length, 1);
|
|
217
|
+
const strategy = agents[0];
|
|
218
|
+
assert.equal(strategy.id, "strategy");
|
|
219
|
+
assert.equal(strategy.name, "agent-cortex:strategy");
|
|
220
|
+
assert.equal(strategy.description, "Flat description of strategy");
|
|
221
|
+
// view→read, web_fetch→fetch_content via token-map.
|
|
222
|
+
assert.deepEqual(strategy.tools, ["bash", "fetch_content", "read"]);
|
|
223
|
+
assert.match(strategy.prompt, /Flat body of strategy\./);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ─── loadTokenMap ────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
describe("loadTokenMap", () => {
|
|
230
|
+
it("reads the real token-map.json at the package root", () => {
|
|
231
|
+
assert.ok(REAL_TOKEN_MAP);
|
|
232
|
+
assert.equal(typeof REAL_TOKEN_MAP?.tools?.bash?.pi, "string");
|
|
233
|
+
// ask_user has no PI equivalent per the contract — must stay null.
|
|
234
|
+
assert.equal(REAL_TOKEN_MAP?.tools?.ask_user?.pi, null);
|
|
235
|
+
assert.ok(REAL_TOKEN_MAP?.paths?.agents_dir);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
function escapeRegExp(s: string): string {
|
|
240
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
241
|
+
}
|