@jaybeeuu/agent-cortex 2.9.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 +131 -0
- package/README.md +8 -4
- package/agents/README.md +5 -0
- package/agents/guppi/agent.md +70 -1
- package/bin/installers/claude.mjs +7 -3
- package/package.json +1 -1
- package/scripts/lib/compose-agent.mjs +45 -7
- package/skills/review/review-defects/SKILL.md +2 -1
- package/skills/review/review-report/EXAMPLES.md +7 -3
- 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 +48 -32
- package/skills/workflow/hitl-queue/scripts/boundary.mjs +493 -0
- package/skills/workflow/hitl-queue/scripts/queue.mjs +464 -78
- package/skills/workflow/hitl-queue/scripts/recommend.mjs +492 -0
- package/skills/workflow/hitl-queue/scripts/scopes.mjs +479 -0
- package/skills/workflow/prioritise-work/SKILL.md +9 -9
- package/skills/workflow/prioritise-work/scripts/strategy.mjs +191 -1
- package/skills/workflow/prioritise-work/templates/strategy.md +27 -1
- package/skills/workflow/ralph/REFERENCE.md +6 -1
- package/skills/workflow/ralph/scripts/prune-worktrees.mjs +185 -10
- package/token-map.README.md +14 -2
- package/token-map.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9d86a2d: Rank bd due dates in the `hitl-queue`. A bead's `--due` date now multiplies its total by the
|
|
8
|
+
factor of the narrowest tier its remaining time fits inside — `1` more than a week out, `1.5`
|
|
9
|
+
within a week, `2` within two days, `4` within a day, `8` once overdue. The numbers live in the
|
|
10
|
+
strategy's new optional `## Deadline tiers` table, so tuning them is a strategy edit and a
|
|
11
|
+
`bump`, never a code change; a strategy without that section ranks on the documented default,
|
|
12
|
+
and `validate` fails a table it cannot read. Each row shows the deadline, the time left and the
|
|
13
|
+
factor, an overdue bead is flagged `OVERDUE` rather than lifted in silence, and a bead with no
|
|
14
|
+
due date keeps the neutral factor. A deferred bead stays out of the queue until its
|
|
15
|
+
`defer_until` passes, applying bd's own wake rule to bd's own field.
|
|
16
|
+
- 32395f6: Re-rank at every item boundary, with the comparison and the reason on the trail. `hitl-queue` gains
|
|
17
|
+
`boundary.mjs`: one command compares the theme's next item (the highest-ranked bead under the epic)
|
|
18
|
+
against the queue's top item, prints **both stored totals**, and says whether the scores point to
|
|
19
|
+
`stay` or `switch`. It computes no score — the totals, components and multipliers are the queue's —
|
|
20
|
+
and it recommends rather than switches: a run with no `--decide` writes nothing.
|
|
21
|
+
|
|
22
|
+
`--decide stay|switch|move` records the human's answer as one timestamped `bd note` on the theme
|
|
23
|
+
bead, carrying both scores and the reason, so a later session reads why the theme was kept or left.
|
|
24
|
+
A decision that contradicts the scores needs `--reason`, so an unexplained override cannot reach
|
|
25
|
+
the trail. A missing theme item, a named bead outside the queue and an empty queue are stated with
|
|
26
|
+
one next step instead of guessed.
|
|
27
|
+
|
|
28
|
+
The guppi agent gains its work unit: the theme is the current epic, a boundary is the completion of
|
|
29
|
+
one item, and every boundary ends in the documented trade-off — continue in the theme, switch
|
|
30
|
+
because higher-priority work waits elsewhere, or move because a break improves the next item. It
|
|
31
|
+
never leaves a theme without both scores on screen, and a wish for a different order stays a
|
|
32
|
+
`prioritise-work` change.
|
|
33
|
+
|
|
34
|
+
- 763869c: Recommend one item from the queue and the strategy. `hitl-queue` gains `recommend.mjs`: one command
|
|
35
|
+
prints the queue's top row — its total, the four RICE components, the unblocking and
|
|
36
|
+
work-in-progress multipliers, what the answer unblocks — together with the line of the committed
|
|
37
|
+
strategy that justifies it, quoted from the file and refused when the file does not contain it.
|
|
38
|
+
`--bead <id>` recommends the bead a human named and notes the queue's own top choice in one line.
|
|
39
|
+
|
|
40
|
+
The guppi agent now opens a session with that one block and waits, answers "what should we look
|
|
41
|
+
at?" through the same steps, and states a condition with one next step instead of choosing when the
|
|
42
|
+
strategy is missing, nothing is scored, the queue is empty, or a named bead is not in the queue. It
|
|
43
|
+
still proposes only: no execution, no claim, no merge, no push.
|
|
44
|
+
|
|
45
|
+
- 64b5deb: Resolve `{{PATH:skills/...}}` rows to the tree each install stages, so an agent row
|
|
46
|
+
always names a file the install contains. Claude Code stages skills flat
|
|
47
|
+
(`skills/<name>/SKILL.md`), so a row authored against the repository's grouped tree lost
|
|
48
|
+
its group segment and rendered as a bare relative path that resolved to nothing. The
|
|
49
|
+
staged shape is now declared per harness in `token-map.json`'s `layouts` table, the
|
|
50
|
+
claude installer resolves agent rows against the plugin root as it already did for
|
|
51
|
+
skills, and `test/rendered-paths-resolve.test.mjs` stages both installs and fails when
|
|
52
|
+
any rendered row names a file that is not there.
|
|
53
|
+
- bed237e: Make the HITL queue scope-aware: it reads several `bd` workspaces and keeps one writable.
|
|
54
|
+
|
|
55
|
+
A scope list — `.agent-cortex/scopes.json` in the repository, then
|
|
56
|
+
`~/.agent-cortex/scopes.json` (`AGENT_CORTEX_HOME` overrides the root), then one implicit scope
|
|
57
|
+
for the current workspace — names each workspace, its kind (`code`, `personal`, `ops`) and whether
|
|
58
|
+
it is read-only. `readOnly` defaults to `true`, and a scope is writable only when it is not marked
|
|
59
|
+
read-only _and_ it is the current workspace, so a copied list cannot widen the writable set on
|
|
60
|
+
another machine.
|
|
61
|
+
|
|
62
|
+
Every configured scope contributes rows, each tagged with its scope. A scope whose directory is
|
|
63
|
+
missing or carries no `.beads` of its own is reported `unavailable` with the reason and skipped —
|
|
64
|
+
bd resolves a workspace by walking up, so the marker check is what stops one workspace's beads
|
|
65
|
+
being attributed to another. A failed `bd` call marks its own scope unavailable; the run still
|
|
66
|
+
exits `0`. A read-only scope passes through one guard that refuses any call other than the two
|
|
67
|
+
reads the queue issues, so the boundary survives a later edit.
|
|
68
|
+
|
|
69
|
+
One daily scope note sits above the ranked list, quoting the `- daily: <scope>, …` line from the
|
|
70
|
+
personal scope's own strategy and citing its version, section and scope. `--shared`
|
|
71
|
+
(`AGENT_CORTEX_QUEUE_SHARED=1`) marks output that leaves the machine: personal scopes are not read,
|
|
72
|
+
so personal rows and the daily note are withheld and counted.
|
|
73
|
+
|
|
74
|
+
`prioritise-work` documents the daily line, and guppi's agent text explains scopes, the note and the
|
|
75
|
+
privacy boundary.
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- 7418a33: Prove that a refused boundary decision records nothing. The two refusal cases in
|
|
80
|
+
`test/boundary.test.mjs` now assert an empty trail as well as the message: `--decide` with an
|
|
81
|
+
outcome outside the three, and a decision against the scores with no `--reason`. The fake `bd` in
|
|
82
|
+
`test/helpers/fake-bd-queue.mjs` already records every `note` call, so the shared harness now
|
|
83
|
+
returns the trail a refused build left behind. A comment that claimed the parser refused a
|
|
84
|
+
bad `--decide` before the builder saw it was wrong for this case: the command line does refuse it,
|
|
85
|
+
but the test calls the builder directly, and the builder checks the runtime value itself, because
|
|
86
|
+
the union is a compile-time constraint only. No behaviour changes.
|
|
87
|
+
- d8de6ba: Harden the guppi interrupt render test so a label that no longer sits with its own body fails.
|
|
88
|
+
|
|
89
|
+
The order assertion read the four step markers by offset, and the marker was the label, so the test
|
|
90
|
+
proved the four labels were ordered and nothing more. A render could keep every label in place and
|
|
91
|
+
exchange the two bodies — the record's text under `Rewind.`, the rewind's under `Record.` — inverting
|
|
92
|
+
the record-before-rewind rule the protocol exists to protect, and the test stayed green.
|
|
93
|
+
|
|
94
|
+
The test now reads the steps as (label, body) pairs and asserts the pair order, so each label must
|
|
95
|
+
carry its own body. No shipped text changed.
|
|
96
|
+
|
|
97
|
+
- fd9c9a3: Remove a landed closed bead's worktree when the merge deleted its remote branch.
|
|
98
|
+
`prune-worktrees.mjs` refused every such worktree with "has no remote-tracking
|
|
99
|
+
branch", because a merge that deletes the branch also removes the local branch's
|
|
100
|
+
upstream — so the one case the script exists to handle failed housekeeping. A
|
|
101
|
+
missing upstream is now distinguished by content: with the tree clean and the
|
|
102
|
+
base branch holding the branch's own version of every path the branch changed
|
|
103
|
+
since their merge base, the worktree and its local branch are removed; otherwise
|
|
104
|
+
the worktree is kept and the refusal names the risk. The test is one-sided, so a
|
|
105
|
+
base that moved on after the branch was cut still proves the branch landed. It
|
|
106
|
+
proves content and never history: a removed branch is discarded with every
|
|
107
|
+
commit the base does not contain, such as a reverted change or an empty commit.
|
|
108
|
+
The base is the remote's default branch, read from `origin/HEAD` and never
|
|
109
|
+
hardcoded, with `--base <ref>` as the documented override; an unresolvable base
|
|
110
|
+
keeps the worktree and refuses. A base that resolves to the worktree's own
|
|
111
|
+
commit also keeps the worktree and refuses, because comparing a branch with
|
|
112
|
+
itself proves nothing.
|
|
113
|
+
|
|
114
|
+
The content proof also survives a path git would otherwise rewrite. The branch's
|
|
115
|
+
changed paths are read with `git diff --name-only -z` and split on NUL, then fed
|
|
116
|
+
back under `git --literal-pathspecs`, so a path holding a non-ASCII byte, a double
|
|
117
|
+
quote, a backslash, a leading colon or a newline round-trips exactly. Without both,
|
|
118
|
+
such a path matched no pathspec, the second diff came back empty, and a branch the
|
|
119
|
+
base never received was removed with an empty reason list.
|
|
120
|
+
|
|
121
|
+
- 74ac309: Correct two shipped-text contradictions in the review docs: a count word that the data beside it
|
|
122
|
+
disproved, and a promise the sibling file did not keep.
|
|
123
|
+
|
|
124
|
+
`review-report`'s trend example is headed "re-run after two more rejections" but prints eight class
|
|
125
|
+
deltas, and one label per rejection makes that eight rejections. The heading now says eight, and the
|
|
126
|
+
sample is the script's own output over a tracker that took eight more rejections: eight classes
|
|
127
|
+
moved, the other four stayed, and the first-pass rate held because the rejections re-ran four
|
|
128
|
+
changes that already held a review cohort.
|
|
129
|
+
|
|
130
|
+
`review-defects` promised `EXAMPLES.md` carried "one real case per class". The file holds twelve
|
|
131
|
+
cases and eleven real observations, with `scope-creep` described as a situation because no recorded
|
|
132
|
+
case exists. The promise now says what the file delivers rather than inventing a case to satisfy it.
|
|
133
|
+
|
|
3
134
|
## 2.9.0
|
|
4
135
|
|
|
5
136
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -454,16 +454,20 @@ guppi helps with the work an agent cannot — design, ideation, prioritisation,
|
|
|
454
454
|
manual confirmations. It is resident and propose-only: it recommends the next item and names the
|
|
455
455
|
reason, then loads `plan`, `grill-me`, `hitl-collab`, `flesh-out-idea` or `prioritise-work` for the
|
|
456
456
|
chosen item. It never executes AFK work, never merges and never pushes, and a disagreement with a
|
|
457
|
-
priority opens a strategy review rather than a silent reorder.
|
|
458
|
-
|
|
459
|
-
|
|
457
|
+
priority opens a strategy review rather than a silent reorder. When that item is done it re-ranks
|
|
458
|
+
at the boundary: the theme's next item against the queue's top item, both scores on screen, then one
|
|
459
|
+
question — stay in the theme, switch to higher-priority work, or move for a single item. Between
|
|
460
|
+
tasks it clears the context with `/clear`, and only after writing what was decided, what is open and
|
|
461
|
+
what comes next to the bead trail and the memory. The tracker, not the window, holds the state that
|
|
462
|
+
matters.
|
|
460
463
|
|
|
461
464
|
```sh
|
|
462
465
|
claude --agent agent-cortex:guppi
|
|
463
466
|
```
|
|
464
467
|
|
|
465
468
|
Its authority is prioritisation, not action. `prioritise-work` owns the committed strategy file;
|
|
466
|
-
`score-bead` records the score and `hitl-queue` prints the order
|
|
469
|
+
`score-bead` records the score, and `hitl-queue` prints the order and recommends one item from it
|
|
470
|
+
with the line of the strategy that justifies the choice. guppi reports them and never
|
|
467
471
|
recomputes them. Personal work is a town-level bd scope — no rig, no polecats, no refinery, no merge
|
|
468
472
|
queue.
|
|
469
473
|
|
package/agents/README.md
CHANGED
|
@@ -152,6 +152,11 @@ When composing the final agent file for a specific harness:
|
|
|
152
152
|
files / installing for claude and pi; pi's `agent-modes` extension does the same at runtime. See the `contract` section of `token-map.json` and
|
|
153
153
|
`token-map.README.md` for the rules.
|
|
154
154
|
|
|
155
|
+
A `{{PATH:skills/…}}` row is authored against this repository's tree, which nests every skill
|
|
156
|
+
one group deep, and resolves to the tree the harness stages. Claude Code stages skills flat, so
|
|
157
|
+
the row loses its group segment there; the pi install keeps it. `token-map.json`'s `layouts`
|
|
158
|
+
table records the shape per harness, so a row never names a file the install does not contain.
|
|
159
|
+
|
|
155
160
|
The pi harness's `agent-modes` extension is a runtime consumer of this format: it
|
|
156
161
|
composes agent prompts on the fly from `agent.md` + `pi/frontmatter.json`, substituting
|
|
157
162
|
tokens against `token-map.json`'s pi column with no install step. See
|
package/agents/guppi/agent.md
CHANGED
|
@@ -23,7 +23,7 @@ This file never restates a skill's rules.
|
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| `prioritise-work` | Choosing what to work on next, or the committed strategy is missing, stale or disputed. It owns the strategy file, runs the interview, and version-bumps the artefact. | {{PATH:skills/workflow/prioritise-work/SKILL.md}} |
|
|
25
25
|
| `plan` | The human has chosen a feature, and it needs a PRD, a task breakdown and classified beads. | {{PATH:skills/workflow/plan/SKILL.md}} |
|
|
26
|
-
| `hitl-queue` | The human needs to know which open decision to answer first, or
|
|
26
|
+
| `hitl-queue` | The human needs to know which open decision to answer first, or the session must open with one recommendation drawn from that queue. | {{PATH:skills/workflow/hitl-queue/SKILL.md}} |
|
|
27
27
|
| `hitl-collab` | Work needs a human: a review, a manual step, a confirmation, or a handoff document. | {{PATH:skills/workflow/hitl-collab/SKILL.md}} |
|
|
28
28
|
| `grill-me` | A plan or a design needs stress-testing before anyone acts on it. | {{PATH:skills/productivity/grill-me/SKILL.md}} |
|
|
29
29
|
| `flesh-out-idea` | An idea bead needs deepening, or promotion to an epic with task children. | {{PATH:skills/planning/flesh-out-idea/SKILL.md}} |
|
|
@@ -31,6 +31,56 @@ This file never restates a skill's rules.
|
|
|
31
31
|
**The strategy skill is `prioritise-work`.** It owns the committed strategy file, and it is the only
|
|
32
32
|
skill in this list that writes that file.
|
|
33
33
|
|
|
34
|
+
## Session start
|
|
35
|
+
|
|
36
|
+
A session opens with **one** recommendation, never a list, and then waits. `hitl-queue` ranks, the
|
|
37
|
+
strategy justifies, and guppi says the one item the two agree on.
|
|
38
|
+
|
|
39
|
+
1. **Read the rubric.** Run `validate` in `prioritise-work`. It prints the file when it passes, and
|
|
40
|
+
that printed text is the only place a justification may be quoted from.
|
|
41
|
+
2. **Read the queue's top row.** Run `recommend --json` in `hitl-queue`. The row carries the bead,
|
|
42
|
+
its total, the four components, both multipliers and the beads it unblocks. Report them as given;
|
|
43
|
+
guppi computes no score.
|
|
44
|
+
3. **Say the block.** Choose the one line of the rubric that justifies that bead, and run
|
|
45
|
+
`recommend --why "<that line>"`. The script refuses a line the file does not contain, so an
|
|
46
|
+
invented reason fails before it is spoken. Say the block it prints, then **wait** for the human.
|
|
47
|
+
|
|
48
|
+
**"What should we look at?"** is that same request in other words, and so is any opening that asks
|
|
49
|
+
what to do next. Nothing about the session — the theme, the backlog, the last decision — replaces
|
|
50
|
+
the three steps.
|
|
51
|
+
|
|
52
|
+
**When the human names a bead instead**, pass it as `--bead <id>`. The recommendation is their bead,
|
|
53
|
+
and the block adds one line naming the queue's own top choice. Where the two disagree, that line is
|
|
54
|
+
the whole of the disagreement: the human's choice stands, and a wish to see another bead first is a
|
|
55
|
+
strategy change through `prioritise-work`, never a silent override.
|
|
56
|
+
|
|
57
|
+
### When nothing can be recommended
|
|
58
|
+
|
|
59
|
+
Four conditions each stop at a stated result and one offered next step. Never fill the gap with a
|
|
60
|
+
suggestion of your own, and never score or reorder anything to clear it.
|
|
61
|
+
|
|
62
|
+
| The block says | What guppi offers |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `No recommendation · no usable strategy` | The `prioritise-work` interview, which writes or bumps the file |
|
|
65
|
+
| `No recommendation · nothing is scored` | `score-bead` on one bead, then the queue again |
|
|
66
|
+
| `No recommendation · the queue is empty` | Filing the decision as an `execution:hitl` bead, or lifting a deferral |
|
|
67
|
+
| `No recommendation · <id> is not in the queue` | `bd show <id>` to see why, then a bead from the queue |
|
|
68
|
+
|
|
69
|
+
## The work unit
|
|
70
|
+
|
|
71
|
+
A session works one **theme** — the current epic — and re-ranks at every **boundary**: the
|
|
72
|
+
completion of one item (a decision recorded, a plan created, a handoff sent, a review given).
|
|
73
|
+
|
|
74
|
+
At each boundary, run `boundary` in `hitl-queue` and say the block it prints: the theme's next item
|
|
75
|
+
against the queue's top item, with both scores and the reason that names them. Then ask for one of
|
|
76
|
+
three outcomes — **stay**, **switch** because higher-priority work waits elsewhere, or **move**
|
|
77
|
+
because a break improves the next item — and pass the answer as `--decide`, which writes the
|
|
78
|
+
comparison and the reason to the theme bead's trail with a timestamp.
|
|
79
|
+
|
|
80
|
+
Never leave a theme without both scores on screen. Never record a decision the human did not give.
|
|
81
|
+
A wish for a different order is a `prioritise-work` change, never a boundary one.
|
|
82
|
+
{{PATH:skills/workflow/hitl-queue/BOUNDARY.md}} carries the block, the states and the options.
|
|
83
|
+
|
|
34
84
|
## Boundaries
|
|
35
85
|
|
|
36
86
|
- **Recommends, never acts.** State the next item and the reason for it. The human chooses. The
|
|
@@ -55,6 +105,25 @@ skill in this list that writes that file.
|
|
|
55
105
|
A scope is one bd workspace. guppi reads several scopes and writes to one: the **current
|
|
56
106
|
workspace**. Every other scope is read-only, whatever the request.
|
|
57
107
|
|
|
108
|
+
The scope list is configuration, not this file: `.agent-cortex/scopes.json` in the repository, or
|
|
109
|
+
`~/.agent-cortex/scopes.json` for the scopes that belong to the person rather than the checkout.
|
|
110
|
+
The `hitl-queue` skill reads it and prints one ranked list across every scope it can read, tagging
|
|
111
|
+
each row with the scope it came from. A scope that is absent or carries no tracker of its own is
|
|
112
|
+
reported `unavailable` and skipped: a machine without the personal repo still has a queue. Read the
|
|
113
|
+
list and the privacy rules from {{PATH:skills/workflow/hitl-queue/SCOPES.md}} rather than
|
|
114
|
+
restating them here.
|
|
115
|
+
|
|
116
|
+
**The daily note is the queue's first line about the day.** It names the scopes the day belongs to
|
|
117
|
+
and quotes the personal strategy line it came from. Report it as printed, above the ranked list,
|
|
118
|
+
and never paraphrase the quote: the citation is the point. Which scopes the day belongs to is the
|
|
119
|
+
human's call, written in their own strategy — when the note says it is missing, offer the
|
|
120
|
+
`prioritise-work` interview rather than choosing a scope.
|
|
121
|
+
|
|
122
|
+
**Personal work stays in the personal scope.** Never copy a personal bead's text into a code
|
|
123
|
+
workspace, a handoff document or a pull request, and never write to a scope other than the current
|
|
124
|
+
workspace. An output that leaves the machine — a pasted report, a message to another agent, a PR
|
|
125
|
+
body — comes from a `--shared` run, which withholds personal rows and the daily note.
|
|
126
|
+
|
|
58
127
|
**Personal work is a town-level scope, never a rig.** A rig is a code-project container — refinery,
|
|
59
128
|
witness, polecats, seeded patrols — and it suits code projects. Personal work belongs to the mayor
|
|
60
129
|
role at town level: no rig, no polecats, no refinery, no merge queue. Never describe personal work
|
|
@@ -156,10 +156,14 @@ function byName(a, b) {
|
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
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}.
|
|
159
162
|
* @param {string} root
|
|
163
|
+
* @param {{ pluginRoot: string | undefined, warn: Warn }} options
|
|
160
164
|
* @returns {Promise<{ files: { file: string, content: string }[] }>}
|
|
161
165
|
*/
|
|
162
|
-
async function buildAgents(root) {
|
|
166
|
+
async function buildAgents(root, { pluginRoot, warn }) {
|
|
163
167
|
const files = [];
|
|
164
168
|
const entries = (await readdir(join(root, "agents"), { withFileTypes: true })).sort((a, b) =>
|
|
165
169
|
byName(a.name, b.name),
|
|
@@ -170,7 +174,7 @@ async function buildAgents(root) {
|
|
|
170
174
|
const dir = join(root, "agents", entry.name);
|
|
171
175
|
if (!(await isFile(join(dir, "agent.md"))) || !(await isFile(join(dir, "claude", "frontmatter.json")))) continue;
|
|
172
176
|
|
|
173
|
-
const fm = await composeAgent(root, entry.name, CLAUDE);
|
|
177
|
+
const fm = await composeAgent(root, entry.name, CLAUDE, { pluginRoot, resolveRelativePaths: true, warn });
|
|
174
178
|
const slug = fm.name.startsWith(NAME_PREFIX) ? fm.name.slice(NAME_PREFIX.length) : fm.name;
|
|
175
179
|
const header =
|
|
176
180
|
`---\n` +
|
|
@@ -720,7 +724,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
720
724
|
? await installInstructions({ root, target: instructionsTarget, dryRun, warn: warnSink })
|
|
721
725
|
: null;
|
|
722
726
|
|
|
723
|
-
const agents = await buildAgents(root);
|
|
727
|
+
const agents = await buildAgents(root, { pluginRoot: rootPlugin, warn: warnSink });
|
|
724
728
|
const skillOut = join(out, "skills");
|
|
725
729
|
const skills = await buildSkills(root, skillOut, { dryRun, pluginRoot: rootPlugin, tokenMap, warn: warnSink });
|
|
726
730
|
const docs = await installDocs({ root, output: out, dryRun, tokenMap });
|
package/package.json
CHANGED
|
@@ -8,22 +8,25 @@
|
|
|
8
8
|
// 1. {{SECTION:name}} is replaced with <dir>/<harness>/<name>.md content.
|
|
9
9
|
// 2. {{TOOL:key}} is replaced with the harness column of token-map.json.
|
|
10
10
|
// 3. {{PATH:arg}} with a named key resolves through token-map.json's paths
|
|
11
|
-
// table; anything else is a relative path and stays verbatim
|
|
11
|
+
// table; anything else is a relative path and stays verbatim, except that
|
|
12
|
+
// installers resolve it against the plugin root and map a `skills/…` row
|
|
13
|
+
// onto the tree that harness stages skills in (token-map.json `layouts`).
|
|
12
14
|
// Substitution order is SECTION → TOOL → PATH (matches extensions/agent-modes).
|
|
13
15
|
// Missing sections, unknown tool keys, or schema-invalid frontmatter are hard
|
|
14
16
|
// errors: a committed generated file must never ship a literal token or a
|
|
15
17
|
// silently dropped section.
|
|
16
18
|
//
|
|
17
|
-
// Options (
|
|
18
|
-
// `
|
|
19
|
+
// Options (both installers pass `pluginRoot` and `resolveRelativePaths`; pi also
|
|
20
|
+
// passes `dropNullTools`):
|
|
19
21
|
// dropNullTools replace null-mapped tool tokens with an empty string and
|
|
20
22
|
// warn instead of throwing (pi's prose rule per the
|
|
21
23
|
// token-map contract)
|
|
22
24
|
// pluginRoot override the plugin_root used for resolved paths
|
|
23
25
|
// (installers compute it from the actual install layout)
|
|
24
26
|
// resolveRelativePaths non-keyed {{PATH:...}} args resolve to pluginRoot/arg
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
+
// (a `skills/…` row mapped onto the harness's staged
|
|
28
|
+
// skills shape) instead of staying verbatim (install
|
|
29
|
+
// time; without it they stay bare for runtime resolution)
|
|
27
30
|
// warn callback for drop/omit warnings (default: no-op; the
|
|
28
31
|
// pi installer collects them into its result)
|
|
29
32
|
//
|
|
@@ -43,6 +46,7 @@ import { join } from "node:path";
|
|
|
43
46
|
* @property {number} [version]
|
|
44
47
|
* @property {Record<string, Record<string, string | null>>} tools
|
|
45
48
|
* @property {Record<string, Record<string, string | PathSpec>>} paths
|
|
49
|
+
* @property {Record<string, Record<string, string>>} [layouts]
|
|
46
50
|
*/
|
|
47
51
|
|
|
48
52
|
/**
|
|
@@ -100,6 +104,8 @@ function noop() {}
|
|
|
100
104
|
* with a warning when `dropNullTools`, otherwise throw.
|
|
101
105
|
* - PATH: named key → resolve through the paths table (plugin_root overridable);
|
|
102
106
|
* non-keyed → join(pluginRoot, arg) when `resolveRelativePaths`, else verbatim.
|
|
107
|
+
* A non-keyed `skills/…` arg is mapped onto the harness's staged shape first
|
|
108
|
+
* (see stagedRelativePath).
|
|
103
109
|
* - SECTION tokens are *not* handled here — the agent composer resolves them from
|
|
104
110
|
* the harness's section files before calling this.
|
|
105
111
|
*
|
|
@@ -231,6 +237,36 @@ async function parseFrontmatter(path, name, harness) {
|
|
|
231
237
|
|
|
232
238
|
// ─── Path resolution ─────────────────────────────────────────────────────────
|
|
233
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Map a repository-relative path onto the tree the harness stages it in.
|
|
242
|
+
*
|
|
243
|
+
* The repository nests every skill one group deep (skills/<group>/<name>/…), so
|
|
244
|
+
* an authored `skills/…` row is written in that grouped form. An installer that
|
|
245
|
+
* stages skills flat — Claude Code's plugin scan discovers skills/<name>/SKILL.md
|
|
246
|
+
* directly under skills/ — drops the group segment, and a row that kept it would
|
|
247
|
+
* name a directory the install does not have. The shape is a harness fact, read
|
|
248
|
+
* from token-map.json's layouts table, so a harness that stages skills
|
|
249
|
+
* differently needs no change here.
|
|
250
|
+
*
|
|
251
|
+
* @param {string} arg Non-keyed {{PATH:...}} argument
|
|
252
|
+
* @param {string} harness
|
|
253
|
+
* @param {TokenMap} tokenMap
|
|
254
|
+
* @returns {string}
|
|
255
|
+
*/
|
|
256
|
+
function stagedRelativePath(arg, harness, tokenMap) {
|
|
257
|
+
const segments = arg.split("/");
|
|
258
|
+
if (segments[0] !== "skills") return arg;
|
|
259
|
+
// Every install ships a skill as a directory below skills/, so a shorter arg
|
|
260
|
+
// names nothing it can contain: fail now rather than render a dead row.
|
|
261
|
+
if (segments.length < 3) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`{{PATH:${arg}}} is not a staged path — a skill row is authored as skills/<group>/<name>/<file>`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
if (tokenMap.layouts?.skills?.[harness] !== "flat") return arg;
|
|
267
|
+
return [segments[0], segments[2], ...segments.slice(3)].join("/");
|
|
268
|
+
}
|
|
269
|
+
|
|
234
270
|
/**
|
|
235
271
|
* Resolve a {{PATH:...}} argument for a harness's generated output.
|
|
236
272
|
* Named keys resolve through token-map.json's paths table (recursively for
|
|
@@ -238,7 +274,7 @@ async function parseFrontmatter(path, name, harness) {
|
|
|
238
274
|
* the installer beads own precise runtime path resolution (see token-map.README.md).
|
|
239
275
|
* With the installer options (`pluginRoot`, `resolveRelativePaths`) the
|
|
240
276
|
* plugin_root base and bare relative paths resolve against the actual install
|
|
241
|
-
* layout instead.
|
|
277
|
+
* layout instead, and a `skills/…` row is mapped onto that layout.
|
|
242
278
|
* @param {string} arg
|
|
243
279
|
* @param {string} harness
|
|
244
280
|
* @param {TokenMap} tokenMap
|
|
@@ -248,7 +284,9 @@ async function parseFrontmatter(path, name, harness) {
|
|
|
248
284
|
function resolvePath(arg, harness, tokenMap, opts = {}) {
|
|
249
285
|
const entry = tokenMap.paths?.[arg];
|
|
250
286
|
if (!entry) {
|
|
251
|
-
if (opts.resolveRelativePaths && opts.pluginRoot)
|
|
287
|
+
if (opts.resolveRelativePaths && opts.pluginRoot) {
|
|
288
|
+
return join(opts.pluginRoot, stagedRelativePath(arg, harness, tokenMap));
|
|
289
|
+
}
|
|
252
290
|
return arg;
|
|
253
291
|
}
|
|
254
292
|
|
|
@@ -111,7 +111,8 @@ class that keeps appearing is a gap in the stage before this one.
|
|
|
111
111
|
|
|
112
112
|
## Worked examples
|
|
113
113
|
|
|
114
|
-
`EXAMPLES.md` carries
|
|
114
|
+
`EXAMPLES.md` carries a case per class, with the rejection text a reviewer should write. Eleven are
|
|
115
|
+
real observations; `scope-creep` is a described situation, because no recorded case exists.
|
|
115
116
|
|
|
116
117
|
## See Also
|
|
117
118
|
|
|
@@ -99,16 +99,20 @@ the whole table rather than the top three.
|
|
|
99
99
|
|
|
100
100
|
## With a previous report
|
|
101
101
|
|
|
102
|
-
The same project, recorded as a baseline one month earlier and re-run after
|
|
102
|
+
The same project, recorded as a baseline one month earlier and re-run after eight more rejections:
|
|
103
103
|
|
|
104
104
|
```
|
|
105
105
|
Change since the previous report
|
|
106
106
|
Against /tmp/rr-example/trend/.agent-cortex-docs/defect-report.json, reported 2026-04-01T09:00:00Z
|
|
107
107
|
criteria-unmet 1 (+1), docs-drift 1 (+1), missing-proof 1 (+1), and 5 more
|
|
108
|
-
First-pass review rate
|
|
108
|
+
First-pass review rate 33% (from 33%, no change)
|
|
109
|
+
Fix rounds review 12 (+8)
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
One label per rejection, so the eight rejections added eight labels: eight classes moved and the
|
|
113
|
+
other four stayed where they were. The rejections re-ran four changes that already held a review
|
|
114
|
+
cohort, so the rate did not move while the fix-round count did. The comparison reads the report the
|
|
115
|
+
script wrote at `--baseline`, so it compares like with like.
|
|
112
116
|
A class that did not change is not listed; when no class changed, the section says so. A baseline
|
|
113
117
|
file with an unrecognised schema is refused rather than read as zeros, because a baseline that
|
|
114
118
|
cannot be read must not become an invented trend.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# The boundary re-rank
|
|
2
|
+
|
|
3
|
+
A session that answers one item at a time drifts: the next item is whichever one was nearest, not
|
|
4
|
+
whichever one matters most. This file defines the unit a session works in, and the one comparison
|
|
5
|
+
it makes when that unit finishes.
|
|
6
|
+
|
|
7
|
+
## The unit of work
|
|
8
|
+
|
|
9
|
+
| Term | Meaning |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Theme** | The current epic. One theme at a time, and its children are the items it needs. |
|
|
12
|
+
| **Item** | One thing the theme needs from a human: a decision answered, a plan shaped, a handoff written, a review given. |
|
|
13
|
+
| **Boundary** | The completion of one item. The point where the session stops and checks whether the theme is still the right place to be. |
|
|
14
|
+
|
|
15
|
+
The theme and its trail are the session's memory, not the window: a clear drops the window, and
|
|
16
|
+
`bd show <epic-id>` still holds what was decided and why. [RECOMMEND.md](RECOMMEND.md) carries the
|
|
17
|
+
one-item session start; this file carries what happens after that item is done.
|
|
18
|
+
|
|
19
|
+
## The decision at every boundary
|
|
20
|
+
|
|
21
|
+
One trade-off, three outcomes, and no fourth:
|
|
22
|
+
|
|
23
|
+
| Outcome | When it is right | Read from the scores? |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `stay` | Continue in the theme. The theme's next item still leads the queue. | Yes |
|
|
26
|
+
| `switch` | Leave the theme, because higher-priority work waits elsewhere. | Yes |
|
|
27
|
+
| `move` | Leave the theme for one item, because a break improves the next item. | No — a judgement, so it carries its own reason |
|
|
28
|
+
|
|
29
|
+
The first two are readable from the two totals. A `move` is not: it is about what the next item
|
|
30
|
+
needs, so it is always the human's reason and never the queue's.
|
|
31
|
+
|
|
32
|
+
## The block
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Boundary · theme tst-t · strategy v2
|
|
36
|
+
recommendation: switch — the queue's top item scores 8.0 against the theme's next 4.2, so higher-priority work waits elsewhere
|
|
37
|
+
theme next tst-a · total 4.2 · Decide the theme question
|
|
38
|
+
queue top tst-b · total 8.0 · Decide the outside question
|
|
39
|
+
next: stay, switch or move is the human's call — nothing is recorded until they answer
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Both scores are always printed**, and the reason names both. That is the whole rule: an agent
|
|
43
|
+
that leaves a theme without showing what it left for has asserted a priority it never displayed.
|
|
44
|
+
A switch with no comparison on screen is a defect, not a summary.
|
|
45
|
+
|
|
46
|
+
The recommendation is derived from the two stored totals and nothing else. The totals, the
|
|
47
|
+
components and both multipliers are the queue's; this comparison computes no score, so the number
|
|
48
|
+
a reader checks is the number the queue ranked by. [SKILL.md](SKILL.md) owns that arithmetic.
|
|
49
|
+
|
|
50
|
+
## The record
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node <skill-scripts>/boundary.mjs boundary --theme <epic-id>
|
|
54
|
+
node <skill-scripts>/boundary.mjs boundary --theme <epic-id> --decide switch
|
|
55
|
+
node <skill-scripts>/boundary.mjs boundary --theme <epic-id> --decide move --reason "<what the break buys>"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A run with no `--decide` prints the comparison and writes nothing. A run that names a decision
|
|
59
|
+
writes one trail entry with `bd note` on the **theme bead** — the epic that owns the items — and
|
|
60
|
+
prints it back:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
trail: tst-t · [2026-11-15T09:30:00.000Z] boundary: switch · theme next tst-a total 4.2 vs queue top tst-b total 8.0 · reason: the queue's top item scores 8.0 against the theme's next 4.2, so higher-priority work waits elsewhere
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Three parts, and all three are required: the **timestamp**, **both scores**, and the **reason**.
|
|
67
|
+
A later session reads `bd show tst-t` and sees why the theme was kept or left, without reading a
|
|
68
|
+
transcript. An entry with a reason but no numbers cannot be checked, and an entry with numbers but
|
|
69
|
+
no reason says nothing about intent.
|
|
70
|
+
|
|
71
|
+
## Recommends, never switches
|
|
72
|
+
|
|
73
|
+
- **The human decides.** `--decide` is the recording step, and it is passed after an answer, never
|
|
74
|
+
instead of asking for one. Nothing switches on its own.
|
|
75
|
+
- **A decision against the recommendation needs `--reason`.** A `move` always does, and so does a
|
|
76
|
+
`stay` or `switch` that contradicts the two scores. The refusal is the point: an unexplained
|
|
77
|
+
override is the case the trail exists to catch.
|
|
78
|
+
- **The boundary changes what to do next, never what outranks what.** The order is the queue's and
|
|
79
|
+
the rubric is the strategy's. A wish for a different order is a `prioritise-work` change, not a
|
|
80
|
+
boundary decision.
|
|
81
|
+
|
|
82
|
+
## Which item is compared
|
|
83
|
+
|
|
84
|
+
`--theme <epic-id>` names the theme. The theme's next item is the **highest-ranked item under that
|
|
85
|
+
theme** in the queue, so the comparison cannot be steered by naming a weak one. `--next <id>` names
|
|
86
|
+
another item when the session knows better, and the queue's top item is rank 1 either way.
|
|
87
|
+
|
|
88
|
+
## The states that are stated instead of guessed
|
|
89
|
+
|
|
90
|
+
| First line | What it means | The step it offers |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `No boundary · the queue is empty` | No open `execution:hitl` bead exists | File the theme's next item, or lift a deferral |
|
|
93
|
+
| `No boundary · theme <id> has no ranked item` | Nothing scored sits under that theme | Name the item with `--next`, or score it with `score-bead` |
|
|
94
|
+
| `No boundary · <id> is not in the queue` | The named item is not an open scored `execution:hitl` bead | Name one from the queue, or score that bead |
|
|
95
|
+
|
|
96
|
+
None of them guesses an item and none is a failure of the run: exit `0` means an answer was
|
|
97
|
+
printed, whichever answer it was.
|
|
98
|
+
|
|
99
|
+
| Option | Effect |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `--theme <epic-id>` | The theme; required, because a boundary belongs to one |
|
|
102
|
+
| `--next <id>` | The theme's next item, when the session names it instead |
|
|
103
|
+
| `--decide <outcome>` | `stay`, `switch` or `move` — record the human's decision |
|
|
104
|
+
| `--reason "<text>"` | Why that decision; required when it contradicts the recommendation |
|
|
105
|
+
| `--file <path>` | Strategy file (default `.agent-cortex/working-docs/strategy.md`, relative to the repository root) |
|
|
106
|
+
| `--include-ideas` | Rank `kind:idea` beads too, as the queue does |
|
|
107
|
+
| `--cwd <dir>` | Directory the strategy path and `bd` resolve from |
|
|
108
|
+
| `--json` | Print the answer as JSON, including the decision and the trail entry |
|
|
109
|
+
|
|
110
|
+
Exit `1` means a `bd` call failed and exit `2` means a malformed call — a missing `--theme`, an
|
|
111
|
+
outcome outside the three, or a decision against the scores with no reason. `BD_PATH` pins the `bd`
|
|
112
|
+
binary.
|
|
113
|
+
|
|
114
|
+
## Red Flags
|
|
115
|
+
|
|
116
|
+
- Red flag: leaving a theme without the two scores on screen, so the reader has to trust the agent.
|
|
117
|
+
- Red flag: recording a decision with no reason, or a reason with no numbers in the trail entry.
|
|
118
|
+
- Red flag: switching because the agent judged the priority itself, instead of reading the queue.
|
|
119
|
+
- Red flag: recomputing a total here, so the comparison and the queue can disagree.
|
|
120
|
+
- Red flag: recording a decision the human has not given, so the session switches on its own.
|
|
121
|
+
- Red flag: writing the trail entry to another scope, so a theme's history lands somewhere its
|
|
122
|
+
owner will not read.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Deadlines
|
|
2
|
+
|
|
3
|
+
A `--due` date is the one deadline field, and `bd` already stores it. The queue multiplies the
|
|
4
|
+
total by the factor of the narrowest tier the remaining time fits inside. The table is the
|
|
5
|
+
strategy's to tune, the boundary is arithmetic, and the row prints what it applied.
|
|
6
|
+
|
|
7
|
+
## The shipped table
|
|
8
|
+
|
|
9
|
+
| Time left | Factor |
|
|
10
|
+
|---|---|
|
|
11
|
+
| more than a week | 1 |
|
|
12
|
+
| within a week | 1.5 |
|
|
13
|
+
| within two days | 2 |
|
|
14
|
+
| within a day | 4 |
|
|
15
|
+
| overdue | 8 |
|
|
16
|
+
|
|
17
|
+
Those numbers are the **shipped default**: a strategy with no `## Deadline tiers` section ranks on
|
|
18
|
+
this table, and so does a run whose header says `strategy unknown`.
|
|
19
|
+
|
|
20
|
+
## A strategy's table
|
|
21
|
+
|
|
22
|
+
The strategy's optional `## Deadline tiers` section carries the table a repository tunes, so
|
|
23
|
+
moving a tier is a strategy edit and a `bump`, never a code change. A row reads
|
|
24
|
+
`| <when> | <factor> |`, narrowest first, and `<when>` is `within <n><m|h|d>`,
|
|
25
|
+
`more than <n><m|h|d>` or `overdue`.
|
|
26
|
+
|
|
27
|
+
A table that is present must carry exactly one `overdue` row, exactly one `more than` row and at
|
|
28
|
+
least one `within` row, and `validate` exits 1 naming every row it cannot read — a malformed table
|
|
29
|
+
never falls back to the default in silence.
|
|
30
|
+
|
|
31
|
+
## Where a boundary falls
|
|
32
|
+
|
|
33
|
+
A boundary falls on the side that names it: with exactly one day left a bead earns 4, and a
|
|
34
|
+
millisecond more earns 2. A bead whose deadline is exactly now has not slipped yet, so it is not
|
|
35
|
+
overdue. A bead with no due date sits outside the tiers and keeps the factor 1, so tuning a row
|
|
36
|
+
never lifts undated work by accident.
|
|
37
|
+
|
|
38
|
+
## The deadline clause in a row
|
|
39
|
+
|
|
40
|
+
A ranked row shows the deadline, the time left and the factor, and an overdue bead is flagged
|
|
41
|
+
rather than lifted in silence:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
2 bd-2 8.0 rice 4.2 = R4 · I3 · C0.7 · E2 ×1.00 dependents(0) ×1.0 deadline ×8 OVERDUE by 1d (due 2026-11-14T08:59Z)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
An unscored row has no total to multiply, so it shows the due moment and `OVERDUE` with no factor
|
|
48
|
+
at all:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
- bd-6 — unscored — no rice metadata OVERDUE by 2d 11h (due 2026-11-13T00:00Z)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Deferral
|
|
55
|
+
|
|
56
|
+
`--defer` is bd's own field and bd's own rule, so the queue reads it rather than inventing a second
|
|
57
|
+
one. `bd ready` wakes a bead whose defer date has passed, while `bd list` keeps reporting the stale
|
|
58
|
+
`deferred` status it was parked in, so the queue applies that same wake rule to `defer_until`. A
|
|
59
|
+
bead deferred into the future is absent and counted; a bead whose defer date has passed is listed.
|