@jaybeeuu/agent-cortex 2.8.0 → 2.10.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/CHANGELOG.md +304 -0
- package/README.md +30 -3
- package/agents/README.md +7 -2
- package/agents/guppi/agent.md +231 -0
- package/agents/guppi/claude/frontmatter.json +6 -0
- package/agents/guppi/pi/frontmatter.json +6 -0
- package/agents/plan/agent.md +6 -0
- package/agents/ralph-plan/agent.md +6 -0
- package/bin/installers/claude.mjs +8 -23
- package/bin/installers/pi.mjs +8 -2
- package/extensions/interrupt-bookmark/README.md +129 -0
- package/extensions/interrupt-bookmark/index.test.ts +717 -0
- package/extensions/interrupt-bookmark/index.ts +354 -0
- package/extensions/interrupt-bookmark/package.json +20 -0
- package/extensions/skill-stats/README.md +84 -3
- package/extensions/skill-stats/index.ts +62 -3
- package/extensions/skill-stats/package.json +1 -1
- package/extensions/skill-stats/rollup.test.ts +643 -0
- package/extensions/skill-stats/rollup.ts +415 -0
- package/extensions/skill-stats/timeline.ts +101 -0
- package/package.json +1 -1
- package/scripts/lib/compose-agent.mjs +45 -7
- package/scripts/lib/manifest.mjs +29 -0
- package/skills/review/review-defects/EXAMPLES.md +80 -4
- package/skills/review/review-defects/SKILL.md +47 -16
- package/skills/review/review-report/EXAMPLES.md +118 -0
- package/skills/review/review-report/SKILL.md +138 -0
- package/skills/review/review-report/scripts/report.mjs +1032 -0
- package/skills/workflow/hitl-queue/BOUNDARY.md +122 -0
- package/skills/workflow/hitl-queue/DEADLINES.md +59 -0
- package/skills/workflow/hitl-queue/RECOMMEND.md +89 -0
- package/skills/workflow/hitl-queue/SCOPES.md +118 -0
- package/skills/workflow/hitl-queue/SKILL.md +150 -0
- package/skills/workflow/hitl-queue/scripts/boundary.mjs +493 -0
- package/skills/workflow/hitl-queue/scripts/queue.mjs +1012 -0
- package/skills/workflow/hitl-queue/scripts/recommend.mjs +492 -0
- package/skills/workflow/hitl-queue/scripts/scopes.mjs +479 -0
- package/skills/workflow/plan/SKILL.md +13 -0
- package/skills/workflow/prioritise-work/CHALLENGE.md +139 -0
- package/skills/workflow/prioritise-work/SKILL.md +150 -0
- package/skills/workflow/prioritise-work/scripts/challenge.mjs +505 -0
- package/skills/workflow/prioritise-work/scripts/strategy.mjs +756 -0
- package/skills/workflow/prioritise-work/templates/strategy.md +87 -0
- package/skills/workflow/ralph/REFERENCE.md +6 -1
- package/skills/workflow/ralph/scripts/prune-worktrees.mjs +185 -10
- package/skills/workflow/run-pipeline-stage/playbooks/review.md +5 -2
- package/skills/workflow/score-bead/FORMAT.md +73 -0
- package/skills/workflow/score-bead/SKILL.md +112 -0
- package/skills/workflow/score-bead/scripts/rice.mjs +664 -0
- package/skills/workflow/using-agent-skills/SKILL.md +6 -1
- package/token-map.README.md +14 -2
- package/token-map.json +17 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-cortex:guppi",
|
|
3
|
+
"description": "Resident prioritising collaborator for human-in-the-loop work. Recommends what to look at next from the committed strategy, then helps plan, grill, flesh out or hand off the chosen item. Use when asking 'what should I work on', choosing the next HITL item, or disagreeing with a priority.",
|
|
4
|
+
"tools": ["Bash", "Read", "Edit", "Write", "Grep", "Glob", "AskUserQuestion", "Task", "Skill"],
|
|
5
|
+
"argumentHint": "Guppi — what should I look at next?"
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-cortex:guppi",
|
|
3
|
+
"description": "Resident prioritising collaborator for human-in-the-loop work. Recommends what to look at next from the committed strategy, then helps plan, grill, flesh out or hand off the chosen item. Use when asking 'what should I work on', choosing the next HITL item, or disagreeing with a priority.",
|
|
4
|
+
"tools": ["bash", "view", "edit", "create", "grep", "glob", "ask_user", "task", "read_agent", "skill", "interrupt_checkpoint", "interrupt_resume"],
|
|
5
|
+
"argumentHint": "Guppi — what should I look at next?"
|
|
6
|
+
}
|
package/agents/plan/agent.md
CHANGED
|
@@ -6,6 +6,12 @@ You follow the **plan** skill workflow. The skill is defined in `{{PATH:skills/w
|
|
|
6
6
|
|
|
7
7
|
## Workflow summary
|
|
8
8
|
|
|
9
|
+
### Phase 0 — Report the HITL queue
|
|
10
|
+
1. Load the `hitl-queue` skill and report its queue exactly as the script prints it — before asking
|
|
11
|
+
the user anything, presenting a plan, or dispatching a task.
|
|
12
|
+
2. Name the top item plainly: its id, its title, and what answering it unblocks. When nothing is
|
|
13
|
+
scored, report the unscored list and ask which bead to score rather than picking one by feel.
|
|
14
|
+
|
|
9
15
|
### Phase 1 — Understand and specify
|
|
10
16
|
1. Ask the user to describe the problem, goal, or feature.
|
|
11
17
|
2. Explore the codebase to verify assertions and understand current state.
|
|
@@ -24,6 +24,12 @@ Do **not** write anywhere else. No source files, no READMEs, no tracked docs, no
|
|
|
24
24
|
### 1. Load context
|
|
25
25
|
Invoke the **beads** skill (set workspace root, run `bd prime`). Hold the output in memory.
|
|
26
26
|
|
|
27
|
+
Then report the HITL queue: load the **hitl-queue** skill, run its `queue` subcommand, and report
|
|
28
|
+
the list exactly as the script prints it, naming the top item — its id, its title, and what
|
|
29
|
+
answering it unblocks. It is the first thing the session says, before any sub-agent is dispatched
|
|
30
|
+
or any plan is proposed. When nothing is scored, report the unscored list and ask which bead to
|
|
31
|
+
score rather than picking one by feel.
|
|
32
|
+
|
|
27
33
|
If the input looks like a PRD (product requirements document), invoke the **prd-to-tasks** skill first — it handles exploration, user quizzing, and creates epics and task beads directly. Then continue from step 7 (Agree with the user).
|
|
28
34
|
|
|
29
35
|
### 2. Understand the request
|
|
@@ -75,6 +75,7 @@ import { homedir } from "node:os";
|
|
|
75
75
|
import { composeAgent, loadTokenMap, substituteTokens } from "../../scripts/lib/compose-agent.mjs";
|
|
76
76
|
import { installDocs } from "../../scripts/lib/docs.mjs";
|
|
77
77
|
import { isDirectory, isFile } from "../../scripts/lib/fs.mjs";
|
|
78
|
+
import { normaliseRepository } from "../../scripts/lib/manifest.mjs";
|
|
78
79
|
import { flattenSkillLinks } from "../../scripts/lib/skill-links.mjs";
|
|
79
80
|
|
|
80
81
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
@@ -155,10 +156,14 @@ function byName(a, b) {
|
|
|
155
156
|
|
|
156
157
|
/**
|
|
157
158
|
* Compose every agent from agents/<name>/ — all harnesses share the composable sources.
|
|
159
|
+
* `{{PATH:...}}` rows resolve against the plugin root, as they do in skills: a bare
|
|
160
|
+
* relative path would name a file relative to the reader's working directory, and the
|
|
161
|
+
* install ships the file under ${CLAUDE_PLUGIN_ROOT}.
|
|
158
162
|
* @param {string} root
|
|
163
|
+
* @param {{ pluginRoot: string | undefined, warn: Warn }} options
|
|
159
164
|
* @returns {Promise<{ files: { file: string, content: string }[] }>}
|
|
160
165
|
*/
|
|
161
|
-
async function buildAgents(root) {
|
|
166
|
+
async function buildAgents(root, { pluginRoot, warn }) {
|
|
162
167
|
const files = [];
|
|
163
168
|
const entries = (await readdir(join(root, "agents"), { withFileTypes: true })).sort((a, b) =>
|
|
164
169
|
byName(a.name, b.name),
|
|
@@ -169,7 +174,7 @@ async function buildAgents(root) {
|
|
|
169
174
|
const dir = join(root, "agents", entry.name);
|
|
170
175
|
if (!(await isFile(join(dir, "agent.md"))) || !(await isFile(join(dir, "claude", "frontmatter.json")))) continue;
|
|
171
176
|
|
|
172
|
-
const fm = await composeAgent(root, entry.name, CLAUDE);
|
|
177
|
+
const fm = await composeAgent(root, entry.name, CLAUDE, { pluginRoot, resolveRelativePaths: true, warn });
|
|
173
178
|
const slug = fm.name.startsWith(NAME_PREFIX) ? fm.name.slice(NAME_PREFIX.length) : fm.name;
|
|
174
179
|
const header =
|
|
175
180
|
`---\n` +
|
|
@@ -268,26 +273,6 @@ async function copyTree(src, dest, transform, dryRun) {
|
|
|
268
273
|
return { md, files };
|
|
269
274
|
}
|
|
270
275
|
|
|
271
|
-
/**
|
|
272
|
-
* Normalise package.json's `repository` field into the string Claude Code's
|
|
273
|
-
* plugin manifest requires. npm permits a string or an object
|
|
274
|
-
* (`{ type, url, directory }`); Claude rejects anything but a string, and one
|
|
275
|
-
* invalid manifest makes the whole plugin fail to load, so the object form must
|
|
276
|
-
* never be copied through. Prefer `url`, fall back to `directory`, and omit the
|
|
277
|
-
* field when neither is usable rather than emitting junk.
|
|
278
|
-
* @param {unknown} repository parsed package.json `repository` value
|
|
279
|
-
* @returns {string | undefined}
|
|
280
|
-
*/
|
|
281
|
-
function normaliseRepository(repository) {
|
|
282
|
-
if (typeof repository === "string") return repository.trim() === "" ? undefined : repository;
|
|
283
|
-
if (repository === null || typeof repository !== "object") return undefined;
|
|
284
|
-
const { url, directory } = /** @type {{ url?: unknown, directory?: unknown }} */ (repository);
|
|
285
|
-
for (const candidate of [url, directory]) {
|
|
286
|
-
if (typeof candidate === "string" && candidate.trim() !== "") return candidate;
|
|
287
|
-
}
|
|
288
|
-
return undefined;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
276
|
/**
|
|
292
277
|
* plugin.json manifest; version tracks the package so it can never go stale.
|
|
293
278
|
* @param {string} root
|
|
@@ -739,7 +724,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
739
724
|
? await installInstructions({ root, target: instructionsTarget, dryRun, warn: warnSink })
|
|
740
725
|
: null;
|
|
741
726
|
|
|
742
|
-
const agents = await buildAgents(root);
|
|
727
|
+
const agents = await buildAgents(root, { pluginRoot: rootPlugin, warn: warnSink });
|
|
743
728
|
const skillOut = join(out, "skills");
|
|
744
729
|
const skills = await buildSkills(root, skillOut, { dryRun, pluginRoot: rootPlugin, tokenMap, warn: warnSink });
|
|
745
730
|
const docs = await installDocs({ root, output: out, dryRun, tokenMap });
|
package/bin/installers/pi.mjs
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
//
|
|
13
13
|
// <output>/package.json pi manifest — keywords ["pi-package"],
|
|
14
14
|
// pi.extensions + pi.skills (conventional
|
|
15
|
-
// dirs plus the bundled third-party packages)
|
|
15
|
+
// dirs plus the bundled third-party packages),
|
|
16
|
+
// repository (this package's origin, so the
|
|
17
|
+
// shipped skills resolve it from the install)
|
|
16
18
|
// <output>/agents/<slug>.agent.md composed flat agents (agents/<name>/
|
|
17
19
|
// agent.md + pi/ harness dirs, {{TOOL:...}} /
|
|
18
20
|
// {{PATH:...}} substituted against
|
|
@@ -122,6 +124,7 @@ import { homedir } from "node:os";
|
|
|
122
124
|
import { composeAgent, loadTokenMap, substituteTokens, translateToolList } from "../../scripts/lib/compose-agent.mjs";
|
|
123
125
|
import { installDocs } from "../../scripts/lib/docs.mjs";
|
|
124
126
|
import { isDirectory, isFile } from "../../scripts/lib/fs.mjs";
|
|
127
|
+
import { normaliseRepository } from "../../scripts/lib/manifest.mjs";
|
|
125
128
|
|
|
126
129
|
const PI = "pi";
|
|
127
130
|
|
|
@@ -137,7 +140,7 @@ const PI = "pi";
|
|
|
137
140
|
/**
|
|
138
141
|
* The generated package's pi manifest (`//` carries the generated-file marker).
|
|
139
142
|
* @typedef {{ "//": string, name: string, version: string, private: boolean,
|
|
140
|
-
* description: string, type: string, keywords: string[],
|
|
143
|
+
* description: string, repository?: string, type: string, keywords: string[],
|
|
141
144
|
* pi: { extensions: string[], skills: string[] } }} PiManifest
|
|
142
145
|
*/
|
|
143
146
|
|
|
@@ -1338,6 +1341,9 @@ async function buildManifest({ root, dependencies }) {
|
|
|
1338
1341
|
version: typeof pkg?.version === "string" ? pkg.version : "0.0.0",
|
|
1339
1342
|
private: true,
|
|
1340
1343
|
description: "agent-cortex pi resources — generated by `agent-cortex install pi`",
|
|
1344
|
+
// Where this package came from, carried so the installed artefact states its own origin:
|
|
1345
|
+
// the shipped skills read it instead of assuming a checkout or a hardcoded slug.
|
|
1346
|
+
repository: normaliseRepository(pkg?.repository),
|
|
1341
1347
|
type: "module",
|
|
1342
1348
|
keywords: ["pi-package"],
|
|
1343
1349
|
pi: {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Interrupt Bookmark Extension
|
|
2
|
+
|
|
3
|
+
Gives an agent a checkpoint and a rewind, so an interruption does not drag its turns into the
|
|
4
|
+
context the original task then has to carry.
|
|
5
|
+
|
|
6
|
+
PI can rewind a session tree and summarise the branch it abandons. Nothing exposed that as a
|
|
7
|
+
deliberate operation, so an interruption — a meeting note, PR feedback to pass on, a Slack todo —
|
|
8
|
+
stayed in the live context and the task carried it. This extension adds two tools around pi's own
|
|
9
|
+
navigation: one records a resume point, one returns to it.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
| Tool | Description |
|
|
14
|
+
| `interrupt_checkpoint` | Records the current leaf id and returns it |
|
|
15
|
+
| `interrupt_resume` | Rewinds to a checkpoint, keeps the abandoned branch under a summary, and ends the turn |
|
|
16
|
+
|
|
17
|
+
`interrupt_resume` takes `{ checkpointId, summary }`. `summary` describes the interruption; it
|
|
18
|
+
reaches pi's summariser as `customInstructions`.
|
|
19
|
+
|
|
20
|
+
After the turn settles, the rewind happens on its own: the abandoned branch is summarised by pi,
|
|
21
|
+
the summary entry is labelled `interrupt`, and the session continues from the checkpoint. The
|
|
22
|
+
summary is reported in the session (a notification and an `interrupt-rewind` custom entry), not in
|
|
23
|
+
the tool result — the tool call is part of the branch that the rewind summarises.
|
|
24
|
+
|
|
25
|
+
Nothing is deleted. The abandoned branch stays in the session file and in `/tree`, under the
|
|
26
|
+
summary. No model call is made by this extension: the summary comes from pi's own tree-navigation
|
|
27
|
+
summariser.
|
|
28
|
+
|
|
29
|
+
## The two steps, and why they are two
|
|
30
|
+
|
|
31
|
+
`waitForIdle()` and `navigateTree()` exist on `ExtensionCommandContext` only. The installed
|
|
32
|
+
`types.d.ts` (pi 1.5.2) declares them there, and `docs/extensions.md` states the reason: they
|
|
33
|
+
deadlock when called while the turn that invoked them is still running. A tool receives
|
|
34
|
+
`ExtensionToolContext`, which has no `navigateTree` at all, and the session itself throws while it
|
|
35
|
+
is streaming — which it always is during a tool call.
|
|
36
|
+
|
|
37
|
+
So `interrupt_resume` cannot navigate. It validates the request, records it, and returns
|
|
38
|
+
`terminate: true`. An `agent_settled` handler then dispatches `/interrupt-rewind <checkpointId>`
|
|
39
|
+
through `sendUserMessage(..., { expandPromptTemplates: true })`, which runs the command rather than
|
|
40
|
+
prompting the model. That is the pattern pi documents for a tool that must reach a command-only API
|
|
41
|
+
(`docs/extensions.md`, "ctx.reload()").
|
|
42
|
+
|
|
43
|
+
The declared navigation signature, quoted from the installed package's own
|
|
44
|
+
`dist/core/extensions/types.d.ts`:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
navigateTree(targetId: string, options?: {
|
|
48
|
+
summarize?: boolean;
|
|
49
|
+
customInstructions?: string;
|
|
50
|
+
replaceInstructions?: boolean;
|
|
51
|
+
label?: string;
|
|
52
|
+
}): Promise<{ cancelled: boolean }>;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Every option name here is real: `summarize`, `customInstructions`, `replaceInstructions` and
|
|
56
|
+
`label`. `label` is applied to the branch-summary entry when `summarize` is set, and to the target
|
|
57
|
+
entry when it is not.
|
|
58
|
+
|
|
59
|
+
## Failure modes
|
|
60
|
+
|
|
61
|
+
| Case | Behaviour |
|
|
62
|
+
| Unknown checkpoint id | Fails before anything is written, naming the id |
|
|
63
|
+
| Already at the checkpoint | Reports that there is no interrupt to summarise, and writes nothing |
|
|
64
|
+
| Session still streaming | Waits for idle first, then navigates |
|
|
65
|
+
| Navigation rejected | Retries once, after another wait for idle; a second failure reports the reason and leaves the session unchanged |
|
|
66
|
+
| A second rewind requested | Refused while the first is scheduled |
|
|
67
|
+
|
|
68
|
+
A failed rewind records `ok: false` and the reason, and tells the caller to continue in the current
|
|
69
|
+
context rather than silently carrying the interruption.
|
|
70
|
+
|
|
71
|
+
## What this extension does not do
|
|
72
|
+
|
|
73
|
+
- It does not decide when to checkpoint or what to record. The interrupt protocol belongs to the
|
|
74
|
+
agent that asks for the checkpoint.
|
|
75
|
+
- It cannot report the summary entry in the tool result, for the reason above.
|
|
76
|
+
- It needs a harness that dispatches extension commands. Where the command never runs, the rewind
|
|
77
|
+
reports that nothing was pending and the session is unchanged.
|
|
78
|
+
|
|
79
|
+
## Claude Code parity
|
|
80
|
+
|
|
81
|
+
The mechanism differs in kind between harnesses, so it is named once through `token-map.json`: the
|
|
82
|
+
`interrupt_rewind` row renders as `interrupt_resume` on pi and as Claude Code's own `/rewind` on
|
|
83
|
+
claude. Claude Code checkpoints and rewinds with a user command rather than a model-callable tool,
|
|
84
|
+
so prose that names the row must describe only what both do — the session returns to an earlier
|
|
85
|
+
point and the abandoned turns leave the live context — never a call shape, and it must say who
|
|
86
|
+
acts: the agent where the harness gives it a tool, the human where the harness gives it a user
|
|
87
|
+
command. The row is consumed by `agents/guppi/agent.md`'s interrupt protocol, which checkpoints,
|
|
88
|
+
records the interrupt, rewinds and resumes; `test/guppi-interrupt-render.test.mjs` stages both
|
|
89
|
+
installs to prove the fork.
|
|
90
|
+
|
|
91
|
+
Both halves are wired per harness. guppi's pi frontmatter declares this extension's
|
|
92
|
+
`interrupt_checkpoint` and `interrupt_resume` in its tool list, so steps 1 and 3 of the protocol are
|
|
93
|
+
callable there. On claude the agent declares neither, because `/rewind` is the human's command, and
|
|
94
|
+
the protocol hands that step to the human.
|
|
95
|
+
|
|
96
|
+
An agent that uses these tools declares them in its own tool list, under the registered names
|
|
97
|
+
`interrupt_checkpoint` and `interrupt_resume`. pi replaces the whole tool selection when a list is
|
|
98
|
+
given. A tool an agent does not name is therefore never declared to its model, and the agent cannot
|
|
99
|
+
call it. A tool list carries tool names only, so the `interrupt_rewind` prose row never appears in
|
|
100
|
+
one.
|
|
101
|
+
|
|
102
|
+
## Proven against pi
|
|
103
|
+
|
|
104
|
+
Behaviour is tested against a complete `ExtensionAPI` double, with a fake session that rejects
|
|
105
|
+
navigation while a response is active, exactly as pi does. Only the **pi** install is staged and
|
|
106
|
+
loaded by `test/pi-extensions-load.test.mjs`: `bin/installers/pi.mjs` copies `extensions/` into the
|
|
107
|
+
staged package, so that test loads this extension from a temp directory outside the checkout. It
|
|
108
|
+
covers no claude install, because there is no claude extension to load — `bin/installers/claude.mjs`
|
|
109
|
+
generates agents, skills, hooks and docs, and copies no `extensions/` tree at all.
|
|
110
|
+
|
|
111
|
+
The claude side was therefore checked by hand, not by a test of this extension: `/rewind` is one of
|
|
112
|
+
the commands the installed Claude Code CLI defines itself (verified in 2.1.294), and the
|
|
113
|
+
`interrupt_rewind` row renders as `interrupt_resume` on pi and `/rewind` on claude when the token is
|
|
114
|
+
substituted directly. The row's consumer is guppi's interrupt protocol, and
|
|
115
|
+
`test/guppi-interrupt-render.test.mjs` checks both staged renders; this hand check is what
|
|
116
|
+
established the claude name before that test existed.
|
|
117
|
+
|
|
118
|
+
The session shape a rewind produces was checked against pi's own `SessionManager` separately:
|
|
119
|
+
`branchWithSummary()` plus the label change leaves a `branch_summary` entry parented at the
|
|
120
|
+
checkpoint, with `fromId` naming the abandoned leaf, every original entry still present, and the
|
|
121
|
+
label recorded. A full session run — a real summariser call and a live `/tree` — needs a configured
|
|
122
|
+
model and was not exercised here.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pnpm test # behaviour: capture, refusal, dispatch, rewind, retry, branch retention
|
|
128
|
+
pnpm typecheck # tsc --noEmit, which also checks the complete ExtensionAPI double
|
|
129
|
+
```
|