@jaybeeuu/agent-cortex 1.42.0 → 2.0.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 +78 -8
- package/.github/workflows/ci.yml +51 -38
- package/AGENTS.md +32 -17
- package/CHANGELOG.md +182 -0
- package/README.md +167 -126
- package/agents/README.md +26 -27
- package/agents/ralph/agent.md +7 -10
- package/agents/ralph/claude/polling.md +1 -1
- package/bin/agent-cortex.mjs +46 -44
- package/bin/installers/claude.mjs +2 -2
- package/bin/installers/pi.mjs +885 -237
- package/docs/claude-hooks.md +5 -4
- package/docs/commit-signing.md +231 -0
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +10 -1
- package/docs/ideas/extension-manifests.md +5 -0
- package/docs/research/disable-model-invocation.md +82 -0
- package/docs/research/skill-design-research.md +2 -2
- package/docs/skills/skill-anatomy.md +5 -2
- package/docs/technical-direction/harness-config.md +30 -14
- package/docs/technical-direction/migration-execution-labels.md +17 -9
- package/docs/user-preferences.md +2 -2
- package/extensions/agent-modes/README.md +3 -3
- package/extensions/agent-modes/discover.test.ts +19 -0
- package/extensions/agent-modes/discover.ts +31 -9
- package/extensions/session-start/README.md +38 -0
- package/extensions/session-start/index.test.ts +122 -0
- package/extensions/session-start/index.ts +27 -0
- package/extensions/session-start/package.json +19 -0
- package/extensions/session-start/tsconfig.json +12 -0
- package/extensions/skill-stats/pnpm-lock.yaml +329 -0
- package/extensions/subagent/README.md +3 -3
- package/extensions/subagent/agents.test.ts +176 -0
- package/extensions/subagent/agents.ts +101 -11
- package/extensions/subagent/index.ts +2 -1
- package/extensions/subagent/package.json +1 -1
- package/lib/cli.mjs +40 -16
- package/package.json +8 -9
- package/pi/settings.json +1 -7
- package/pnpm-workspace.yaml +6 -0
- package/scripts/check-changeset.mjs +42 -0
- package/scripts/lib/compose-agent.mjs +10 -10
- package/scripts/lib/release-gates.mjs +61 -0
- package/scripts/lib/verify-worker-tree.mjs +107 -0
- package/scripts/resolve-publish-command.mjs +17 -0
- package/skills/planning/create-task/scripts/pnpm-lock.yaml +342 -0
- package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +329 -0
- package/skills/workflow/git-workflow/SKILL.md +35 -1
- package/skills/workflow/ralph/REFERENCE.md +20 -4
- package/skills/workflow/ralph/SKILL.md +9 -6
- package/skills/workflow/run-pipeline-stage/SKILL.md +11 -4
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +2 -0
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +3 -0
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +2 -0
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +11 -0
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +342 -0
- package/test/cli.test.mjs +37 -39
- package/test/helpers/fake-pi.mjs +132 -0
- package/test/pi-installer.test.mjs +345 -286
- package/test/pi-register.test.mjs +342 -0
- package/test/pi-sweep.test.mjs +619 -0
- package/test/release-gates.test.mjs +190 -0
- package/test/verify-worker-tree.test.mjs +120 -0
- package/token-map.README.md +16 -18
- package/token-map.json +29 -33
- package/agents/plan/copilot/frontmatter.json +0 -6
- package/agents/plan.agent.md +0 -59
- package/agents/ralph/copilot/frontmatter.json +0 -6
- package/agents/ralph/copilot/polling.md +0 -62
- package/agents/ralph-plan/copilot/frontmatter.json +0 -6
- package/agents/ralph-plan.agent.md +0 -126
- package/agents/ralph.agent.md +0 -331
- package/agents/strategy/copilot/frontmatter.json +0 -6
- package/agents/strategy.agent.md +0 -112
- package/bin/installers/copilot.mjs +0 -124
- package/extensions/session-start.ts +0 -44
- package/hooks.json +0 -10
- package/lib/pi-packages.mjs +0 -169
- package/plugin.json +0 -35
- package/scripts/build-copilot-agents.mjs +0 -30
- package/scripts/install-instructions.sh +0 -82
- package/scripts/sync-plugin-version.sh +0 -17
- package/test/copilot-installer.test.mjs +0 -308
- package/test/pi-packages.test.mjs +0 -295
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Behaviour tests for the release gates that keep `main` from re-publishing a
|
|
2
|
+
// version: the inverted PR changeset gate and the no-publish release gate.
|
|
3
|
+
// The decision logic is pure; the two CLI adapters are exercised through their
|
|
4
|
+
// process boundary because that is the contract CI depends on.
|
|
5
|
+
|
|
6
|
+
import { describe, it } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import {
|
|
11
|
+
PUBLISH_COMMAND,
|
|
12
|
+
NO_PUBLISH_COMMAND,
|
|
13
|
+
evaluateChangesetGate,
|
|
14
|
+
parseNameStatus,
|
|
15
|
+
resolvePublishCommand,
|
|
16
|
+
} from "../scripts/lib/release-gates.mjs";
|
|
17
|
+
|
|
18
|
+
const CHECK_CHANGESET = fileURLToPath(new URL("../scripts/check-changeset.mjs", import.meta.url));
|
|
19
|
+
const RESOLVE_PUBLISH = fileURLToPath(
|
|
20
|
+
new URL("../scripts/resolve-publish-command.mjs", import.meta.url),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function runScript(script, { args = [], env = {}, stdin = "" } = {}) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const child = spawn(process.execPath, [script, ...args], {
|
|
26
|
+
env: { ...process.env, ...env },
|
|
27
|
+
});
|
|
28
|
+
let stdout = "";
|
|
29
|
+
let stderr = "";
|
|
30
|
+
child.stdout.on("data", (chunk) => {
|
|
31
|
+
stdout += chunk;
|
|
32
|
+
});
|
|
33
|
+
child.stderr.on("data", (chunk) => {
|
|
34
|
+
stderr += chunk;
|
|
35
|
+
});
|
|
36
|
+
child.on("error", reject);
|
|
37
|
+
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
38
|
+
child.stdin.end(stdin);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("parseNameStatus", () => {
|
|
43
|
+
it("parses added and modified paths from git diff --name-status output", () => {
|
|
44
|
+
const changes = parseNameStatus("M\tpackage.json\nA\t.changeset/fix-release.md\n");
|
|
45
|
+
|
|
46
|
+
assert.deepEqual(changes, [
|
|
47
|
+
{ status: "M", path: "package.json" },
|
|
48
|
+
{ status: "A", path: ".changeset/fix-release.md" },
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses the new path for a rename", () => {
|
|
53
|
+
const changes = parseNameStatus("R087\t.changeset/old.md\t.changeset/new.md\n");
|
|
54
|
+
|
|
55
|
+
assert.deepEqual(changes, [{ status: "R", path: ".changeset/new.md" }]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns no changes for empty output", () => {
|
|
59
|
+
assert.deepEqual(parseNameStatus(""), []);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("evaluateChangesetGate", () => {
|
|
64
|
+
it("passes a PR that adds a changeset", () => {
|
|
65
|
+
const result = evaluateChangesetGate({
|
|
66
|
+
changes: [{ status: "A", path: ".changeset/fix-release.md" }],
|
|
67
|
+
labels: [],
|
|
68
|
+
headRef: "fix/release-gate",
|
|
69
|
+
actor: "jaybeeuu",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
assert.deepEqual(result, { ok: true, reason: "changeset-added" });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("fails a CI-only change that adds no changeset", () => {
|
|
76
|
+
const result = evaluateChangesetGate({
|
|
77
|
+
changes: [{ status: "M", path: ".github/workflows/ci.yml" }],
|
|
78
|
+
labels: [],
|
|
79
|
+
headRef: "chore/bump-pnpm",
|
|
80
|
+
actor: "jaybeeuu",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
assert.equal(result.ok, false);
|
|
84
|
+
assert.equal(result.reason, "missing-changeset");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("fails a PR that only modifies an existing changeset", () => {
|
|
88
|
+
const result = evaluateChangesetGate({
|
|
89
|
+
changes: [{ status: "M", path: ".changeset/pending.md" }],
|
|
90
|
+
labels: [],
|
|
91
|
+
headRef: "chore/edit-changeset",
|
|
92
|
+
actor: "jaybeeuu",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
assert.equal(result.ok, false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("fails a PR that only adds the changeset README", () => {
|
|
99
|
+
const result = evaluateChangesetGate({
|
|
100
|
+
changes: [{ status: "A", path: ".changeset/README.md" }],
|
|
101
|
+
labels: [],
|
|
102
|
+
headRef: "docs/changesets",
|
|
103
|
+
actor: "jaybeeuu",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
assert.equal(result.ok, false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("passes an unlabelled-change PR carrying the no-publish label", () => {
|
|
110
|
+
const result = evaluateChangesetGate({
|
|
111
|
+
changes: [{ status: "M", path: ".github/workflows/ci.yml" }],
|
|
112
|
+
labels: ["no-publish"],
|
|
113
|
+
headRef: "chore/bump-pnpm",
|
|
114
|
+
actor: "jaybeeuu",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert.deepEqual(result, { ok: true, reason: "no-publish-label" });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("passes the Version Packages PR by head branch", () => {
|
|
121
|
+
const result = evaluateChangesetGate({
|
|
122
|
+
changes: [{ status: "M", path: "package.json" }],
|
|
123
|
+
labels: [],
|
|
124
|
+
headRef: "changeset-release/main",
|
|
125
|
+
actor: "jaybeeuu",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
assert.deepEqual(result, { ok: true, reason: "version-packages-pr" });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("passes a PR opened by the GitHub Actions bot", () => {
|
|
132
|
+
const result = evaluateChangesetGate({
|
|
133
|
+
changes: [{ status: "M", path: "package.json" }],
|
|
134
|
+
labels: [],
|
|
135
|
+
headRef: "automation/some-branch",
|
|
136
|
+
actor: "github-actions[bot]",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
assert.deepEqual(result, { ok: true, reason: "version-packages-pr" });
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("resolvePublishCommand", () => {
|
|
144
|
+
it("publishes by default when the merged PR has no labels", () => {
|
|
145
|
+
assert.equal(resolvePublishCommand([]), PUBLISH_COMMAND);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("publishes when the merged PR carries unrelated labels", () => {
|
|
149
|
+
assert.equal(resolvePublishCommand(["bug", "priority:p2"]), PUBLISH_COMMAND);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("refuses to publish when the merged PR carries the no-publish label", () => {
|
|
153
|
+
assert.equal(resolvePublishCommand(["bug", "no-publish"]), NO_PUBLISH_COMMAND);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("matches the label exactly, not as a substring", () => {
|
|
157
|
+
assert.equal(resolvePublishCommand(["no-publish-ci"]), PUBLISH_COMMAND);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("check-changeset CLI", () => {
|
|
162
|
+
it("exits 0 for a no-publish PR with no diff", async () => {
|
|
163
|
+
const { code } = await runScript(CHECK_CHANGESET, {
|
|
164
|
+
env: { PR_LABELS: '["no-publish"]', PR_HEAD_REF: "chore/bump-pnpm", PR_ACTOR: "jaybeeuu" },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
assert.equal(code, 0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("exits 1 and explains the no-publish escape hatch for a changeset-less PR", async () => {
|
|
171
|
+
const { code, stderr } = await runScript(CHECK_CHANGESET, {
|
|
172
|
+
stdin: "M\tpackage.json\n",
|
|
173
|
+
env: { PR_LABELS: "[]", PR_HEAD_REF: "chore/bump-pnpm", PR_ACTOR: "jaybeeuu" },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert.equal(code, 1);
|
|
177
|
+
assert.match(stderr, /no-publish/);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("resolve-publish-command CLI", () => {
|
|
182
|
+
it("prints the noop command for a no-publish label set", async () => {
|
|
183
|
+
const { code, stdout } = await runScript(RESOLVE_PUBLISH, {
|
|
184
|
+
args: ['["no-publish"]'],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
assert.equal(code, 0);
|
|
188
|
+
assert.equal(stdout.trim(), NO_PUBLISH_COMMAND);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Integration tests for scripts/lib/verify-worker-tree.mjs. The module exists to
|
|
2
|
+
// tell a worker that delivered from one that only said it did, so each case runs
|
|
3
|
+
// a real git repository and asserts the verdict — no mocking of git, because git
|
|
4
|
+
// is the thing whose state the verdict is about.
|
|
5
|
+
|
|
6
|
+
import { describe, it } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { promisify } from "node:util";
|
|
13
|
+
|
|
14
|
+
import { verifyWorkerTree } from "../scripts/lib/verify-worker-tree.mjs";
|
|
15
|
+
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
|
|
18
|
+
/** Run a git command in `cwd`. */
|
|
19
|
+
async function git(cwd, args) {
|
|
20
|
+
await execFileAsync("git", args, { cwd });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A repository with one base commit; returns its directory and that commit. */
|
|
24
|
+
async function createRepo() {
|
|
25
|
+
const dir = await mkdtemp(join(tmpdir(), "verify-worker-tree-"));
|
|
26
|
+
await git(dir, ["init", "--quiet"]);
|
|
27
|
+
await git(dir, ["config", "user.email", "t@example.com"]);
|
|
28
|
+
await git(dir, ["config", "user.name", "T"]);
|
|
29
|
+
// A developer's global config may enable commit signing; the fixture must not
|
|
30
|
+
// depend on a secret key being present.
|
|
31
|
+
await git(dir, ["config", "commit.gpgsign", "false"]);
|
|
32
|
+
await writeFile(join(dir, "README.md"), "base\n");
|
|
33
|
+
await git(dir, ["add", "-A"]);
|
|
34
|
+
await git(dir, ["commit", "-m", "base"]);
|
|
35
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: dir });
|
|
36
|
+
return { dir, base: stdout.trim() };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Commit a file in the repo, as a delivering worker would. */
|
|
40
|
+
async function commitWork(dir) {
|
|
41
|
+
await writeFile(join(dir, "worker-output.txt"), "delivered\n");
|
|
42
|
+
await git(dir, ["add", "-A"]);
|
|
43
|
+
await git(dir, ["commit", "-m", "worker: deliver"]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("verifyWorkerTree", () => {
|
|
47
|
+
it("passes a worker that committed and left a clean tree", async () => {
|
|
48
|
+
const { dir, base } = await createRepo();
|
|
49
|
+
try {
|
|
50
|
+
await commitWork(dir);
|
|
51
|
+
|
|
52
|
+
const verdict = await verifyWorkerTree({ worktree: dir, base });
|
|
53
|
+
|
|
54
|
+
assert.deepEqual(verdict, { ok: true, reasons: [], commitsAhead: 1 });
|
|
55
|
+
} finally {
|
|
56
|
+
await rm(dir, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("fails a worker that reported success but left its work uncommitted", async () => {
|
|
61
|
+
const { dir, base } = await createRepo();
|
|
62
|
+
try {
|
|
63
|
+
await writeFile(join(dir, "left-behind.txt"), "uncommitted\n");
|
|
64
|
+
|
|
65
|
+
const verdict = await verifyWorkerTree({ worktree: dir, base });
|
|
66
|
+
|
|
67
|
+
assert.equal(verdict.ok, false);
|
|
68
|
+
assert.equal(verdict.reasons[0], "worktree is dirty: 1 uncommitted path(s)");
|
|
69
|
+
assert.equal(verdict.commitsAhead, 0);
|
|
70
|
+
} finally {
|
|
71
|
+
await rm(dir, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("fails a worker that left a clean tree with nothing committed", async () => {
|
|
76
|
+
const { dir, base } = await createRepo();
|
|
77
|
+
try {
|
|
78
|
+
const verdict = await verifyWorkerTree({ worktree: dir, base });
|
|
79
|
+
|
|
80
|
+
assert.equal(verdict.ok, false);
|
|
81
|
+
assert.equal(verdict.reasons.length, 1);
|
|
82
|
+
assert.match(verdict.reasons[0], /^no commit ahead of base [0-9a-f]{7}$/);
|
|
83
|
+
} finally {
|
|
84
|
+
await rm(dir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("reports a dirty tree and a missing commit together", async () => {
|
|
89
|
+
const { dir, base } = await createRepo();
|
|
90
|
+
try {
|
|
91
|
+
await writeFile(join(dir, "partial.txt"), "half done\n");
|
|
92
|
+
|
|
93
|
+
const verdict = await verifyWorkerTree({ worktree: dir, base });
|
|
94
|
+
|
|
95
|
+
assert.equal(verdict.ok, false);
|
|
96
|
+
assert.equal(verdict.reasons.length, 2);
|
|
97
|
+
assert.equal(verdict.reasons[0], "worktree is dirty: 1 uncommitted path(s)");
|
|
98
|
+
assert.match(verdict.reasons[1], /^no commit ahead of base [0-9a-f]{7}$/);
|
|
99
|
+
} finally {
|
|
100
|
+
await rm(dir, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("counts every commit the worker made, not just the last", async () => {
|
|
105
|
+
const { dir, base } = await createRepo();
|
|
106
|
+
try {
|
|
107
|
+
await commitWork(dir);
|
|
108
|
+
await writeFile(join(dir, "second.txt"), "more\n");
|
|
109
|
+
await git(dir, ["add", "-A"]);
|
|
110
|
+
await git(dir, ["commit", "-m", "worker: second"]);
|
|
111
|
+
|
|
112
|
+
const verdict = await verifyWorkerTree({ worktree: dir, base });
|
|
113
|
+
|
|
114
|
+
assert.equal(verdict.ok, true);
|
|
115
|
+
assert.equal(verdict.commitsAhead, 2);
|
|
116
|
+
} finally {
|
|
117
|
+
await rm(dir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
package/token-map.README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# token-map.json — multi-harness token substitution
|
|
2
2
|
|
|
3
3
|
`token-map.json` is the central, single-source mapping of **tool names**, **paths**, and
|
|
4
|
-
**agent naming conventions** across the
|
|
4
|
+
**agent naming conventions** across the two harnesses this repo ships to. It is the
|
|
5
5
|
foundation for the install-time generation system: each harness installer (`agent-cortex
|
|
6
6
|
install <harness>`) reads the map and substitutes `{{TOOL:...}}` / `{{PATH:...}}` tokens in
|
|
7
7
|
the files it generates.
|
|
@@ -14,15 +14,14 @@ decisions behind the map.
|
|
|
14
14
|
|
|
15
15
|
| id | Platform | Agent format | Plugin root |
|
|
16
16
|
|---|---|---|---|
|
|
17
|
-
| `
|
|
18
|
-
| `pi` | pi coding agent | `agents/<name>/` composable dirs (composed at runtime by agent-modes, or installed install-time by `agent-cortex install pi`) | `~/.pi/agent/npm/node_modules/@jaybeeuu/agent-cortex` |
|
|
17
|
+
| `pi` | pi coding agent | `agents/<name>/` composable dirs (composed at runtime by agent-modes, or rendered install-time by `agent-cortex install pi`) | `~/.agent-cortex/pi` |
|
|
19
18
|
| `claude` | Claude Code | `~/.agent-cortex/claude/agents/*.md` (materialised home install) | `${CLAUDE_PLUGIN_ROOT}` (env var) |
|
|
20
19
|
|
|
21
20
|
The harness ids match the values accepted by `agent-cortex install <harness>` in
|
|
22
|
-
`lib/cli.mjs` (`SUPPORTED_HARNESSES`). The pi `plugin_root` row is the
|
|
23
|
-
|
|
24
|
-
installs pass `--plugin-root <dir>` to override it (see README "Pi
|
|
25
|
-
|
|
21
|
+
`lib/cli.mjs` (`SUPPORTED_HARNESSES`). The pi `plugin_root` row is the staged package
|
|
22
|
+
directory `agent-cortex install pi` materialises, which pi then loads by path; checkout
|
|
23
|
+
and symlinked installs pass `--plugin-root <dir>` to override it (see README "Pi package
|
|
24
|
+
staging (`agent-cortex install pi`)").
|
|
26
25
|
|
|
27
26
|
## Design decisions
|
|
28
27
|
|
|
@@ -44,12 +43,13 @@ rather than inventing a dotted form:
|
|
|
44
43
|
### 2. What is mapped
|
|
45
44
|
|
|
46
45
|
- **`tools`** — the twelve canonical tool names as authored in `agents/<name>/agent.md`
|
|
47
|
-
(
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
(the canonical authoring names). Each row gives the equivalent tool name per harness.
|
|
47
|
+
The claude column is consumed by `bin/installers/claude.mjs` (the install-time
|
|
48
|
+
generator — PR #91, #92); the pi column is grounded
|
|
50
49
|
in pi's documented built-in tool surface (`read`, `bash`, `edit`, `write`, `grep`, `find`,
|
|
51
50
|
`ls`) plus `ask_questions` — added by the pi-questions package (mapped from
|
|
52
|
-
`ask_user`) — and `task` / `read_agent` / `wait_for_agents` subagent tools
|
|
51
|
+
`ask_user`) — and `task` / `read_agent` / `wait_for_agents` subagent tools, the last three
|
|
52
|
+
supplied by this repo's own `extensions/subagent` rather than by pi; `skill` maps
|
|
53
53
|
to `read` (skills load by reading the listed SKILL.md path).
|
|
54
54
|
- **`paths`** — `plugin_root`, `agents_dir`, and `skills_dir`. These are the install-time
|
|
55
55
|
variables that differ per harness. `plugin_root` is the anchor; the other two resolve
|
|
@@ -100,7 +100,7 @@ so the warn-and-omit path applies only to future null entries. See
|
|
|
100
100
|
|
|
101
101
|
The full contract is embedded in `token-map.json` under `contract`. In summary, installers:
|
|
102
102
|
|
|
103
|
-
1. Bind a harness id (`
|
|
103
|
+
1. Bind a harness id (`claude` | `pi`).
|
|
104
104
|
2. Substitute in order: `SECTION` (left for the composer), then `TOOL`, then `PATH`.
|
|
105
105
|
3. For `TOOL`: look up the canonical key, replace with the harness name; null → drop/omit;
|
|
106
106
|
unknown → throw.
|
|
@@ -110,7 +110,7 @@ The full contract is embedded in `token-map.json` under `contract`. In summary,
|
|
|
110
110
|
|
|
111
111
|
## Authoring guidance
|
|
112
112
|
|
|
113
|
-
- Write tokens with the **canonical
|
|
113
|
+
- Write tokens with the **canonical authoring names**: `Use {{TOOL:task}} to spawn
|
|
114
114
|
subagents`, `Read {{PATH:skills/workflow/plan/SKILL.md}}`.
|
|
115
115
|
- Never embed a harness-specific name in the canonical sources (`agents/<name>/agent.md`,
|
|
116
116
|
`skills/**/SKILL.md`) — that defeats the map. The materialised Claude plugin
|
|
@@ -124,11 +124,9 @@ The full contract is embedded in `token-map.json` under `contract`. In summary,
|
|
|
124
124
|
|
|
125
125
|
- `agents/README.md` — composable agent directory format (ADR #63), defines the token
|
|
126
126
|
grammar the map implements.
|
|
127
|
-
- `scripts/
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
files; `bin/installers/copilot.mjs` is the shared generator both `pnpm build:copilot`
|
|
131
|
-
and `agent-cortex install copilot` run.
|
|
127
|
+
- `scripts/lib/compose-agent.mjs` — the shared token composer: resolves `{{TOOL:...}}` /
|
|
128
|
+
`{{PATH:...}}` against the map and `{{SECTION:...}}` from the per-harness section files
|
|
129
|
+
for `bin/installers/claude.mjs` and `bin/installers/pi.mjs`.
|
|
132
130
|
- `bin/installers/claude.mjs` — the single Claude generator: plain
|
|
133
131
|
`agent-cortex install claude` materialises the plugin into `~/.agent-cortex/claude`
|
|
134
132
|
with token-substituted skills, merges the committed `claude/settings.json` into
|
package/token-map.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-cortex token map",
|
|
3
|
-
"description": "Central mapping of tool names, paths, and agent naming conventions across the
|
|
3
|
+
"description": "Central mapping of tool names, paths, and agent naming conventions across the claude and pi harnesses. Single source of truth for install-time token substitution. See token-map.README.md for the design rationale.",
|
|
4
4
|
"version": 1,
|
|
5
|
-
"harnesses": ["
|
|
5
|
+
"harnesses": ["claude", "pi"],
|
|
6
6
|
"token_format": {
|
|
7
7
|
"convention": "{{TYPE:NAME}}",
|
|
8
8
|
"decision": "Adopt the {{TOOL:name}} / {{PATH:name}} / {{SECTION:name}} grammar already standardised by ADR #63 (agents/README.md) rather than a dotted {{tool.name}} form. The uppercase type prefix disambiguates token kinds at a glance, keeps tool/path/section names in one namespace collision-free, and matches the token grammar skills are already written against.",
|
|
@@ -13,40 +13,36 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"tools": {
|
|
16
|
-
"bash": { "
|
|
17
|
-
"view": { "
|
|
18
|
-
"edit": { "
|
|
19
|
-
"create": { "
|
|
20
|
-
"grep": { "
|
|
21
|
-
"rg": { "
|
|
22
|
-
"glob": { "
|
|
23
|
-
"ask_user": { "
|
|
24
|
-
"web_fetch": { "
|
|
25
|
-
"skill": { "
|
|
26
|
-
"task": { "
|
|
27
|
-
"read_agent": { "
|
|
28
|
-
"wait_for_agents": { "
|
|
16
|
+
"bash": { "claude": "Bash", "pi": "bash" },
|
|
17
|
+
"view": { "claude": "Read", "pi": "read" },
|
|
18
|
+
"edit": { "claude": "Edit", "pi": "edit" },
|
|
19
|
+
"create": { "claude": "Write", "pi": "write" },
|
|
20
|
+
"grep": { "claude": "Grep", "pi": "grep" },
|
|
21
|
+
"rg": { "claude": "Grep", "pi": "grep" },
|
|
22
|
+
"glob": { "claude": "Glob", "pi": "find" },
|
|
23
|
+
"ask_user": { "claude": "AskUserQuestion", "pi": "ask_questions" },
|
|
24
|
+
"web_fetch": { "claude": "WebFetch", "pi": "fetch_content" },
|
|
25
|
+
"skill": { "claude": "Skill", "pi": "read" },
|
|
26
|
+
"task": { "claude": "Task", "pi": "task" },
|
|
27
|
+
"read_agent": { "claude": null, "pi": "read_agent" },
|
|
28
|
+
"wait_for_agents": { "claude": null, "pi": "wait_for_agents" }
|
|
29
29
|
},
|
|
30
30
|
"paths": {
|
|
31
31
|
"plugin_root": {
|
|
32
|
-
"copilot": "~/.copilot/installed-plugins/_direct/agent-cortex",
|
|
33
32
|
"claude": "${CLAUDE_PLUGIN_ROOT}",
|
|
34
|
-
"pi": "~/.
|
|
33
|
+
"pi": "~/.agent-cortex/pi"
|
|
35
34
|
},
|
|
36
35
|
"agents_dir": {
|
|
37
|
-
"copilot": { "base": "plugin_root", "relative": "agents" },
|
|
38
36
|
"claude": { "base": "plugin_root", "relative": "agents" },
|
|
39
37
|
"pi": { "base": "plugin_root", "relative": "agents" }
|
|
40
38
|
},
|
|
41
39
|
"skills_dir": {
|
|
42
|
-
"copilot": { "base": "plugin_root", "relative": "skills" },
|
|
43
40
|
"claude": { "base": "plugin_root", "relative": "skills" },
|
|
44
41
|
"pi": { "base": "plugin_root", "relative": "skills" }
|
|
45
42
|
}
|
|
46
43
|
},
|
|
47
44
|
"agents": {
|
|
48
45
|
"prefix": {
|
|
49
|
-
"copilot": "agent-cortex:",
|
|
50
46
|
"claude": "",
|
|
51
47
|
"pi": "agent-cortex:"
|
|
52
48
|
},
|
|
@@ -54,40 +50,40 @@
|
|
|
54
50
|
"ralph": ["claude"]
|
|
55
51
|
},
|
|
56
52
|
"sources": {
|
|
57
|
-
"copilot": "agents/<name>/ composable dirs (canonical) — composed to agents/*.agent.md by scripts/build-copilot-agents.mjs",
|
|
58
53
|
"pi": "agents/<name>/ composable dirs (canonical) — composed at runtime by the agent-modes extension",
|
|
59
54
|
"claude": "agents/<name>/ composable dirs (canonical) — composed by bin/installers/claude.mjs (install-time generator, the only generator — the generated claude/ plugin subtree is not committed), plus agents-native/*.md for deferred agents"
|
|
60
55
|
}
|
|
61
56
|
},
|
|
62
57
|
"notes": {
|
|
63
58
|
"tools": {
|
|
64
|
-
"rg": "
|
|
59
|
+
"rg": "Canonical alias for grep. Claude and pi collapse it onto their single search tool (Grep / grep).",
|
|
65
60
|
"glob": "Claude has a dedicated Glob tool. pi's built-in pattern-search tool is find (hypa_find when the pi-hypa extension is installed).",
|
|
66
|
-
"ask_user": "
|
|
67
|
-
"web_fetch": "pi's fetch_content comes from the pi-web-access package (
|
|
68
|
-
"skill": "
|
|
69
|
-
"read_agent": "Claude's Task returns results inline — there is no separate read step, so read_agent drops on claude (null).
|
|
70
|
-
"
|
|
61
|
+
"ask_user": "Claude (AskUserQuestion) prompts the user mid-run. pi's equivalent is ask_questions, provided by the pi-questions package (an ordinary runtime dependency of this package that `agent-cortex install pi` bundles into the staged package's node_modules) — the genuine interactive-questioning tool, restoring grilling in plan/ralph-plan/strategy modes. Like fetch_content (pi-web-access), it is only present when its package is installed, so the mapping depends on that package (see the extension-manifests idea). pi still intentionally ships no permission popups; ask_questions is the interactive mid-run question tool, not a popup.",
|
|
62
|
+
"web_fetch": "pi's fetch_content comes from the pi-web-access package (an ordinary runtime dependency of this package that `agent-cortex install pi` bundles into the staged package's node_modules).",
|
|
63
|
+
"skill": "Claude (Skill) exposes skills as a tool. pi has no dedicated skill tool — skills are surfaced in the always-present 'Available skills' context block and loaded by reading the listed SKILL.md path ({{PATH:<skill>/SKILL.md}}). The pi mapping is read, the tool used to load a skill's file; a dedicated skill tool is a follow-on design candidate if prompting proves insufficient.",
|
|
64
|
+
"read_agent": "Claude's Task returns results inline — there is no separate read step, so read_agent drops on claude (null). pi exposes read_agent as a fallback to wait_for_agents.",
|
|
65
|
+
"task": "Claude provides a subagent tool natively. On pi it is supplied by this repo's own extensions/subagent extension (which also registers read_agent and wait_for_agents), not by pi itself — a pi session started before that extension was installed has no dispatch tool at all. Skills must therefore stop and report a missing subagent tool rather than improvising a backgrounded CLI run, which has no completion signal.",
|
|
66
|
+
"wait_for_agents": "PI-only blocking wait over background subagent runs (the task tool): returns the results of completed agents and STILL RUNNING for the rest. Claude's Task delivers results inline with no wait step — null on claude."
|
|
71
67
|
},
|
|
72
68
|
"paths": {
|
|
73
|
-
"pi": "plugin_root
|
|
69
|
+
"pi": "plugin_root is the staged package `agent-cortex install pi` materialises at ~/.agent-cortex/pi; pi loads that directory by path and the shipped extensions resolve their own resources relative to it (agent-modes computes plugin_root from its actual install location). A checkout or symlinked install differs — installers compute plugin_root from the actual resolution and can override it (--plugin-root).",
|
|
74
70
|
"claude": "plugin_root stays ${CLAUDE_PLUGIN_ROOT} (env var) for the materialised plugin — never a literal ~/.agent-cortex/claude. Claude Code copies marketplace-installed plugins (including local directory sources like ~/.agent-cortex/claude) into its own cache and expands the env var per context, so a literal home path in substituted files would break under cache-copy; the installer passes the env form through verbatim."
|
|
75
71
|
},
|
|
76
72
|
"agents": {
|
|
77
|
-
"prefix": "Claude strips the agent-cortex: name prefix during conversion (NAME_PREFIX in bin/installers/claude.mjs):
|
|
78
|
-
"deferred": "ralph has no generated claude counterpart — it is authored natively as agents-native/ralph.md (its event-driven orchestration cannot be produced by section composition from the polling
|
|
73
|
+
"prefix": "Claude strips the agent-cortex: name prefix during conversion (NAME_PREFIX in bin/installers/claude.mjs): pi agents are agent-cortex:<slug>, claude agents are <slug>.",
|
|
74
|
+
"deferred": "ralph has no generated claude counterpart — it is authored natively as agents-native/ralph.md (its event-driven orchestration cannot be produced by section composition from the polling pi source)."
|
|
79
75
|
}
|
|
80
76
|
},
|
|
81
77
|
"contract": {
|
|
82
78
|
"version": 1,
|
|
83
79
|
"summary": "Installers substitute {{TOOL:...}}, {{PATH:...}}, and {{SECTION:...}} tokens in generated harness files using this map. This contract is the single reference installers must implement; token-map.json is the data those rules operate on.",
|
|
84
|
-
"harness_ids": ["
|
|
80
|
+
"harness_ids": ["claude", "pi"],
|
|
85
81
|
"binding": "The harness id is the same value the installer is invoked with (agent-cortex install <harness>). Each harness reads its own column from the tools, paths, and agents sections.",
|
|
86
82
|
"substitution_order": ["SECTION", "TOOL", "PATH"],
|
|
87
83
|
"rules": [
|
|
88
|
-
"TOOL: replace {{TOOL:<key>}} with tools.<key>.<harness>. Canonical keys are exact, case-sensitive strings (the
|
|
84
|
+
"TOOL: replace {{TOOL:<key>}} with tools.<key>.<harness>. Canonical keys are exact, case-sensitive strings (the names the canonical sources in agents/<name>/agent.md are authored with).",
|
|
89
85
|
"TOOL null: a null value means the harness has no equivalent. In prose, remove the token (and emit a warning). In a tool-list context (frontmatter tools: [...]), omit the entry. Do not substitute an empty string.",
|
|
90
|
-
"TOOL unknown: a {{TOOL:<key>}} with no entry in tools is a hard error — the map must be extended. This mirrors the generators (
|
|
86
|
+
"TOOL unknown: a {{TOOL:<key>}} with no entry in tools is a hard error — the map must be extended. This mirrors the generators (bin/installers/claude.mjs, bin/installers/pi.mjs), which throw on unknown tools.",
|
|
91
87
|
"PATH named key: replace {{PATH:<key>}} with the resolved value of paths.<key>.<harness>.",
|
|
92
88
|
"PATH relative: replace {{PATH:<relative>}} with plugin_root + '/' + <relative> (e.g. {{PATH:skills/workflow/plan/SKILL.md}}).",
|
|
93
89
|
"PATH resolution: a value may be a literal string, an ${ENV_VAR} token, or a {base, relative} spec. Resolve {base, relative} by joining relative onto the resolved base. Leave ${ENV_VAR} values verbatim — the harness expands them at runtime (claude's ${CLAUDE_PLUGIN_ROOT}).",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent-cortex:plan",
|
|
3
|
-
"description": "Guide a feature from first discussion through PRD and task breakdown to classified beads ready for ralph execution. Full end-to-end planning workflow using the plan skill. Use when starting a new feature, scoping work to hand off to ralph, or saying 'plan this'.",
|
|
4
|
-
"tools": ["bash", "view", "edit", "create", "grep", "glob", "ask_user", "task", "read_agent", "skill"],
|
|
5
|
-
"argumentHint": "Plan <feature or change description>"
|
|
6
|
-
}
|
package/agents/plan.agent.md
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# GENERATED from agents/plan/ by scripts/build-copilot-agents.mjs — DO NOT EDIT.
|
|
3
|
-
description: "Guide a feature from first discussion through PRD and task breakdown to classified beads ready for ralph execution. Full end-to-end planning workflow using the plan skill. Use when starting a new feature, scoping work to hand off to ralph, or saying 'plan this'."
|
|
4
|
-
name: "agent-cortex:plan"
|
|
5
|
-
tools: ["bash", "view", "edit", "create", "grep", "glob", "ask_user", "task", "read_agent", "skill"]
|
|
6
|
-
argument-hint: "Plan <feature or change description>"
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# plan — end-to-end feature planning agent
|
|
10
|
-
|
|
11
|
-
You are a planning agent. Your job is to guide a feature from first discussion through PRD and task breakdown to classified beads ready for ralph execution.
|
|
12
|
-
|
|
13
|
-
You follow the **plan** skill workflow. The skill is defined in `skills/workflow/plan/SKILL.md`. Read it now to load the full instructions.
|
|
14
|
-
|
|
15
|
-
## Workflow summary
|
|
16
|
-
|
|
17
|
-
### Phase 1 — Understand and specify
|
|
18
|
-
1. Ask the user to describe the problem, goal, or feature.
|
|
19
|
-
2. Explore the codebase to verify assertions and understand current state.
|
|
20
|
-
3. Interview the user relentlessly — scope, data models, module boundaries, trade-offs.
|
|
21
|
-
4. Sketch major modules.
|
|
22
|
-
5. Draft a PRD using the `write-a-prd` template.
|
|
23
|
-
6. Present the PRD for user approval.
|
|
24
|
-
7. Create an epic bead from the approved PRD.
|
|
25
|
-
|
|
26
|
-
### Phase 2 — Break into tasks
|
|
27
|
-
8. Review PRD stories and identify vertical-slice phases.
|
|
28
|
-
9. Define each phase (title, stories, what to build, acceptance criteria).
|
|
29
|
-
10. Create epics for multi-phase work.
|
|
30
|
-
11. Present the breakdown for user approval.
|
|
31
|
-
|
|
32
|
-
### Phase 3 — Create and classify beads
|
|
33
|
-
13. Create task beads via `create-task` for each task.
|
|
34
|
-
14. Tag parent epics with aggregate classification.
|
|
35
|
-
15. Present the full bead tree.
|
|
36
|
-
|
|
37
|
-
### Phase 4 — Prepare handoff to ralph
|
|
38
|
-
16. Determine the handoff bead (epic or meta-epic).
|
|
39
|
-
17. Tag it `workflow:ralph`.
|
|
40
|
-
18. Push bead state with `bd dolt push`.
|
|
41
|
-
19. Present the handoff summary.
|
|
42
|
-
|
|
43
|
-
## Permitted writes
|
|
44
|
-
|
|
45
|
-
| Location | Purpose |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `.agent-cortex/working-docs/` | Research notes, decisions, exploration findings — gitignored |
|
|
48
|
-
| Bead fields (`description`, `design`) | Plans and specifications |
|
|
49
|
-
| `docs/prd/<topic>.md` | PRD documents |
|
|
50
|
-
|
|
51
|
-
Do **not** modify source code or tracked documentation outside these locations.
|
|
52
|
-
|
|
53
|
-
## Constraints
|
|
54
|
-
|
|
55
|
-
- Never modify source code.
|
|
56
|
-
- Always get user approval before moving between phases.
|
|
57
|
-
- Each phase has a gate — do not skip it.
|
|
58
|
-
- Work in `.agent-cortex/working-docs/` for scratchpad notes.
|
|
59
|
-
- Use `bd` commands for all bead operations.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent-cortex:ralph",
|
|
3
|
-
"description": "Use when running all pending beads end-to-end: finds the next available beads (dependencies met), runs the full implement → review → fix cycle for each feature in its own worktree, opens and reports PRs immediately, then waits for human merges before continuing.",
|
|
4
|
-
"tools": ["bash", "view", "rg", "glob", "task", "read_agent"],
|
|
5
|
-
"argumentHint": "Run all pending beads"
|
|
6
|
-
}
|