mandrel 2.39.0 → 2.41.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/README.md +8 -5
- package/.agents/agents/auditor.md +5 -0
- package/.agents/docs/SDLC.md +21 -12
- package/.agents/instructions.md +22 -22
- package/.agents/rules/api-conventions.md +43 -7
- package/.agents/rules/ci-remediation.md +3 -14
- package/.agents/rules/gherkin-standards.md +21 -6
- package/.agents/rules/git-conventions.md +6 -5
- package/.agents/rules/security-baseline.md +6 -7
- package/.agents/rules/testing-standards.md +75 -198
- package/.agents/scripts/audit-to-stories.js +510 -66
- package/.agents/scripts/install-matrix-assert.js +2 -2
- package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +45 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +290 -0
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +94 -3
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +10 -0
- package/.agents/scripts/lib/label-constants.js +18 -0
- package/.agents/scripts/lib/label-taxonomy.js +18 -5
- package/.agents/scripts/lib/orchestration/dependency-candidates.js +139 -0
- package/.agents/scripts/lib/orchestration/epic-candidates.js +159 -0
- package/.agents/scripts/lib/orchestration/epic-checklist.js +103 -0
- package/.agents/scripts/lib/orchestration/epic-container.js +202 -0
- package/.agents/scripts/lib/orchestration/epic-expansion.js +148 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +97 -36
- package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +80 -0
- package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +192 -0
- package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +324 -0
- package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +164 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +30 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +26 -5
- package/.agents/scripts/lib/orchestration/run-epilogue.js +130 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +11 -1
- package/.agents/scripts/plan-persist.js +98 -1
- package/.agents/scripts/providers/github/sub-issue-add.js +218 -0
- package/.agents/scripts/resolve-stories.js +42 -2
- package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +5 -10
- package/.agents/skills/core/browser-testing-with-devtools/reference.md +7 -46
- package/.agents/skills/core/code-review-and-quality/SKILL.md +0 -5
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +0 -3
- package/.agents/skills/core/gates-and-baselines/SKILL.md +10 -137
- package/.agents/skills/core/gates-and-baselines/reference.md +103 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +2 -18
- package/.agents/skills/core/security-and-hardening/SKILL.md +2 -4
- package/.agents/skills/core/security-and-hardening/reference.md +0 -70
- package/.agents/skills/skills.index.json +10 -70
- package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +4 -10
- package/.agents/skills/stack/qa/gherkin-authoring/reference.md +9 -113
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +29 -154
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +157 -98
- package/.agents/templates/docs/audit-sweep-runbook.md +169 -0
- package/.agents/workflows/audit-to-stories.md +85 -7
- package/.agents/workflows/git-cleanup.md +3 -2
- package/.agents/workflows/git-deliver.md +3 -2
- package/.agents/workflows/helpers/audit-lens-core.md +24 -4
- package/.agents/workflows/helpers/deliver-reference.md +8 -0
- package/.agents/workflows/helpers/plan-reference.md +109 -1
- package/.agents/workflows/mandrel-deliver.md +47 -43
- package/.agents/workflows/mandrel-plan.md +61 -55
- package/.agents/workflows/qa-assist.md +20 -17
- package/.agents/workflows/qa-explore.md +30 -29
- package/.agents/workflows/qa-run.md +2 -1
- package/docs/CHANGELOG.md +30 -0
- package/package.json +1 -1
- package/.agents/rules/changelog-style.md +0 -180
- package/.agents/rules/shell-conventions.md +0 -61
- package/.agents/scripts/lib/qa/coverage-verdict.js +0 -214
- package/.agents/skills/core/api-and-interface-design/SKILL.md +0 -55
- package/.agents/skills/core/api-and-interface-design/reference.md +0 -76
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +0 -45
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +0 -56
- package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +0 -54
- package/.agents/skills/core/idea-refinement/refinement-criteria.md +0 -155
- package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +0 -15
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +0 -105
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +0 -152
- package/.agents/skills/stack/qa/vitest/SKILL.md +0 -22
|
@@ -44,10 +44,12 @@ They remain read-only emitters of audit reports.
|
|
|
44
44
|
## Phase 1 — Discover & parse
|
|
45
45
|
|
|
46
46
|
Run the CLI in `--scan` mode against the resolved glob. It parses every
|
|
47
|
-
|
|
47
|
+
finding block, normalises the fields (`Severity` / `Impact` are
|
|
48
48
|
both recognised; `Dimension` / `Category` likewise), and extracts file
|
|
49
|
-
paths mentioned in the body.
|
|
50
|
-
|
|
49
|
+
paths mentioned in the body. A `###` heading that carries no severity axis and
|
|
50
|
+
holds `####` blocks is read as a **grouping header**: its `####` children are
|
|
51
|
+
the findings, and the header itself never becomes one. It then stamps each
|
|
52
|
+
finding with a stable sha1 fingerprint via the shared
|
|
51
53
|
[`lib/findings/route-finding.js`](../scripts/lib/findings/route-finding.js)
|
|
52
54
|
helper (`fingerprintFinding`) — the single dedup/route implementation
|
|
53
55
|
shared with `qa-explore`. The workflow carries **no** separate inline
|
|
@@ -64,6 +66,19 @@ The emitted plan envelope carries `findings`, `groups`, `edges`,
|
|
|
64
66
|
`classifications`, and `summary`. Subsequent phases consume the file
|
|
65
67
|
rather than re-parsing the reports.
|
|
66
68
|
|
|
69
|
+
**The tally cross-check is automatic.** Every report declares
|
|
70
|
+
`Severity tally: Critical <n> / High <n> / Medium <n> / Low <n>` in its
|
|
71
|
+
Executive Summary; the scan compares that line with what it parsed and carries
|
|
72
|
+
each disagreement on `summary.reportFailures[]` as
|
|
73
|
+
`{ sourceReport, kind, reported, parsed }`. The kinds are `missing-tally` (no
|
|
74
|
+
line), `tally-mismatch` (line and parse disagree), and `unresolved-severity` (a
|
|
75
|
+
finding whose severity did not resolve — dropped from grouping, never filed as
|
|
76
|
+
an `unknown` group). They print to stderr before `--scan` returns its plan, so
|
|
77
|
+
a mis-parsed report is never read as a clean audit: re-run the lens rather than
|
|
78
|
+
file from it. Over older reports predating the mandate,
|
|
79
|
+
`--scan --allow-missing-tally` downgrades **only** `missing-tally` to a
|
|
80
|
+
warning.
|
|
81
|
+
|
|
67
82
|
## Phase 2 — HITL: severity gate
|
|
68
83
|
|
|
69
84
|
Read the plan envelope's `summary.tally`. Present the operator with the
|
|
@@ -117,6 +132,11 @@ Ask:
|
|
|
117
132
|
> default-single policy.
|
|
118
133
|
> - **Individual standalone Stories** — opens one GitHub Issue per
|
|
119
134
|
> group directly (no plan ceremony).
|
|
135
|
+
>
|
|
136
|
+
> Either way, if the sweep proposes **more than 2** Stories they are grouped
|
|
137
|
+
> under a **container Epic** by default — a title, a one-paragraph goal and a
|
|
138
|
+
> child checklist, carrying nothing a child does not already carry. Say so if
|
|
139
|
+
> you would rather file them flat.
|
|
120
140
|
|
|
121
141
|
**STOP** until the operator picks.
|
|
122
142
|
|
|
@@ -133,7 +153,14 @@ node .agents/scripts/audit-to-stories.js --emit-plan-seed \
|
|
|
133
153
|
The seed renders the canonical one-pager sections — Problem Statement,
|
|
134
154
|
Recommended Direction, Key Assumptions (with links to every source
|
|
135
155
|
report), MVP Scope (the M proposed Stories), Key Files (so `/mandrel-plan`'s
|
|
136
|
-
authoring step has concrete anchors), Not Doing.
|
|
156
|
+
authoring step has concrete anchors), Grouping, Not Doing.
|
|
157
|
+
|
|
158
|
+
**Grouping is the container-Epic directive.** Above 2 proposed Stories the
|
|
159
|
+
seed instructs `/mandrel-plan` to group them under one Epic — a sweep is the
|
|
160
|
+
clearest case for a container, since every Story shares a provenance and an
|
|
161
|
+
operator usually delivers them together. It is a directive in the text, not an
|
|
162
|
+
automatic write: Phase 4 above is where an operator declines it. Below the
|
|
163
|
+
threshold the section says so and asks for nothing.
|
|
137
164
|
|
|
138
165
|
Chain into the existing planning entrypoint:
|
|
139
166
|
|
|
@@ -215,6 +242,14 @@ its footprint guard ignores the shared provenance footers, so an unwired cohort
|
|
|
215
242
|
is genuinely unordered and `/mandrel-deliver` will co-dispatch Stories the edges say
|
|
216
243
|
must follow one another.
|
|
217
244
|
|
|
245
|
+
**Preconditions.** The pass writes through the configured provider, so it needs
|
|
246
|
+
`github.owner` **and** `github.repo` in `.agentrc.json` plus working `gh` auth
|
|
247
|
+
(`GH_TOKEN`/`gh auth status`) — the same two things Phase 1's dedup needs. When
|
|
248
|
+
either is missing the command refuses and names which one; fix that and re-run
|
|
249
|
+
the exact command above. Do not transcribe the footers by hand: `/mandrel-deliver`
|
|
250
|
+
reads them, but the native `blocked_by` relations only exist if this pass wrote
|
|
251
|
+
them.
|
|
252
|
+
|
|
218
253
|
## Phase 6 — Idempotency (folded into Phase 1 scan)
|
|
219
254
|
|
|
220
255
|
The `--scan` step routes each group's findings through the shared
|
|
@@ -289,6 +324,8 @@ summarising the run:
|
|
|
289
324
|
|
|
290
325
|
When the single-plan path ran, link the Story (or plan-run) the chained
|
|
291
326
|
`/mandrel-plan` opened. When the Standalone-Stories path ran, list every Issue URL.
|
|
327
|
+
Either way, name the container Epic if one was created — it is the single id
|
|
328
|
+
that delivers the whole sweep (`/mandrel-deliver <epicId>`).
|
|
292
329
|
|
|
293
330
|
## Constraints
|
|
294
331
|
|
|
@@ -330,18 +367,59 @@ writing their `temp/audits/audit-*-results.md` reports, then (2) invokes the
|
|
|
330
367
|
CLI's **`--auto` mode** over those results:
|
|
331
368
|
|
|
332
369
|
```bash
|
|
333
|
-
node .agents/scripts/audit-to-stories.js --auto [--dry-run] \
|
|
370
|
+
node .agents/scripts/audit-to-stories.js --auto [--dry-run] [--ledger-commit] \
|
|
334
371
|
[--glob "temp/audits/audit-*-results.md"] [--severity <floor>]
|
|
335
372
|
```
|
|
336
373
|
|
|
374
|
+
The routine shape is **lenses full-scope → dry-run → live with a ledger PR**:
|
|
375
|
+
|
|
376
|
+
1. Run the `audit-*` lenses with no `--paths` and no change-set filter. A
|
|
377
|
+
sweep scoped to a change set re-reports the same recent files every cycle
|
|
378
|
+
and never reaches the untouched code where findings accumulate.
|
|
379
|
+
2. `--auto --dry-run` for the first cycles — zero writes, summary only. Read
|
|
380
|
+
`totals.create` and raise the severity floor until it is a batch the team
|
|
381
|
+
would actually take on.
|
|
382
|
+
3. `--auto --ledger-commit` once the tallies stop surprising you.
|
|
383
|
+
|
|
337
384
|
`--auto` runs with **no interactive gates**: it resolves the severity floor
|
|
338
385
|
from `delivery.auditToStories.severityFloor` (default `high`, overridable with
|
|
339
386
|
`--severity`), applies the two-stage dedup, reconciles the cross-run ledger,
|
|
340
387
|
and prints a run-summary JSON (create / skip-open / skip-reoccurring /
|
|
341
388
|
suppressed-by-ledger tallies, plus the re-detected open Issue numbers an
|
|
342
389
|
operator may want a "re-detected" comment on). `--dry-run` performs zero GitHub
|
|
343
|
-
writes and skips the ledger write, emitting only the summary.
|
|
344
|
-
|
|
390
|
+
writes and skips the ledger write, emitting only the summary.
|
|
391
|
+
|
|
392
|
+
`--auto` **fails closed on any `summary.reportFailures[]` entry** (Phase 1): an
|
|
393
|
+
unattended sweep has no operator to read a warning, so a missing or mismatched
|
|
394
|
+
`Severity tally:` line — or a finding whose severity did not resolve — exits
|
|
395
|
+
non-zero having opened no Issue and written no ledger. `--allow-missing-tally`
|
|
396
|
+
is a `--scan` affordance that `--auto` ignores. A red sweep means the report is
|
|
397
|
+
untrustworthy: re-run the lens. The host scheduler owns the cadence; this
|
|
398
|
+
workflow owns the routing.
|
|
399
|
+
|
|
400
|
+
### The ledger is consumer state — commit it
|
|
401
|
+
|
|
402
|
+
`baselines/audit-ledger.json` is **committed consumer state, not scratch
|
|
403
|
+
output**. A scheduled sweep normally runs on an ephemeral checkout, so unless
|
|
404
|
+
the reconciled ledger is committed back it dies with the clone: every later
|
|
405
|
+
sweep starts amnesiac, re-proposing findings already filed and re-surfacing
|
|
406
|
+
findings a human already rejected.
|
|
407
|
+
|
|
408
|
+
`--ledger-commit` closes that loop. After the summary prints — and only when
|
|
409
|
+
the ledger changed — it creates `chore/audit-ledger-<YYYY-MM-DD>` from HEAD,
|
|
410
|
+
commits **only** the ledger file, pushes it, and opens a PR against
|
|
411
|
+
`project.baseBranch`. **Auto-merge is never requested**: a human glance at the
|
|
412
|
+
`accepted-risk` / `regressed` flips before it lands is the point. A git or `gh`
|
|
413
|
+
failure is fatal and names its step, but only after the summary is printed, so
|
|
414
|
+
a broken remote never costs the operator the run's findings. `--dry-run` skips
|
|
415
|
+
the tail. Without the flag, a changed ledger on a checkout that cannot persist
|
|
416
|
+
it — no `origin`, or HEAD off the base branch — sets `ledger.unpersisted: true`
|
|
417
|
+
in the summary and warns on stderr naming the file.
|
|
418
|
+
|
|
419
|
+
The full sweep procedure — tally cross-check, the ledger PR, the
|
|
420
|
+
enrich-before-deliver step and the label convention — ships as a
|
|
421
|
+
consumer-copyable template at
|
|
422
|
+
[`templates/docs/audit-sweep-runbook.md`](../templates/docs/audit-sweep-runbook.md).
|
|
345
423
|
|
|
346
424
|
## See also
|
|
347
425
|
|
|
@@ -17,8 +17,9 @@ Reach for it when the automated hygiene left an unusual state behind.
|
|
|
17
17
|
> **When to run**: after a session that landed several PRs, or before starting a
|
|
18
18
|
> new Story, to put the local checkout into a known tidy state.
|
|
19
19
|
>
|
|
20
|
-
> **Persona**: `devops-engineer`
|
|
21
|
-
> `
|
|
20
|
+
> **Persona**: `devops-engineer` — git guidance comes from the always-on
|
|
21
|
+
> [`rules/git-conventions.md`](../rules/git-conventions.md), which needs no
|
|
22
|
+
> skill citation.
|
|
22
23
|
|
|
23
24
|
The enumeration + reap logic lives in
|
|
24
25
|
[`git-cleanup.js`](../scripts/git-cleanup.js) — it computes the candidate list,
|
|
@@ -19,8 +19,9 @@ explicitly; the interactive choice prompt fires **only** when the detected
|
|
|
19
19
|
state is genuinely ambiguous, so the common path stays non-interactive and
|
|
20
20
|
scriptable.
|
|
21
21
|
|
|
22
|
-
> **Persona**: `devops-engineer`
|
|
23
|
-
> `
|
|
22
|
+
> **Persona**: `devops-engineer` — git guidance comes from the always-on
|
|
23
|
+
> [`rules/git-conventions.md`](../rules/git-conventions.md), which needs no
|
|
24
|
+
> skill citation.
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
@@ -51,7 +51,8 @@ path). The report MUST include every section its lens template mandates — writ
|
|
|
51
51
|
`_No findings._` rather than omitting a section — and always an
|
|
52
52
|
`## Executive Summary` and a `## Detailed Findings` section. The Executive
|
|
53
53
|
Summary carries the self-cross-check `kept <k> / dropped <d>` line
|
|
54
|
-
([below](#self-cross-check))
|
|
54
|
+
([below](#self-cross-check)) and, beside it, the machine-readable severity
|
|
55
|
+
tally ([below](#severity-tally)).
|
|
55
56
|
|
|
56
57
|
Every finding under `## Detailed Findings` uses the shared 7-field skeleton
|
|
57
58
|
below. A lens may **add** fields (e.g. a WCAG success criterion, a CWE ID, a
|
|
@@ -64,9 +65,11 @@ drops or renames a shared field.
|
|
|
64
65
|
```markdown
|
|
65
66
|
## Executive Summary
|
|
66
67
|
|
|
67
|
-
[The lens's headline read
|
|
68
|
-
line. A lens may mandate additional report
|
|
69
|
-
findings — its own body names them.]
|
|
68
|
+
[The lens's headline read, the self-cross-check `kept <k> / dropped <d>` line,
|
|
69
|
+
and the severity tally line below. A lens may mandate additional report
|
|
70
|
+
sections between here and the findings — its own body names them.]
|
|
71
|
+
|
|
72
|
+
Severity tally: Critical <n> / High <n> / Medium <n> / Low <n>
|
|
70
73
|
|
|
71
74
|
## Detailed Findings
|
|
72
75
|
|
|
@@ -188,6 +191,23 @@ that rests on one of them:
|
|
|
188
191
|
A lens that keeps every finding still records `dropped 0` — the line's absence
|
|
189
192
|
is itself a defect (it means the pass did not run).
|
|
190
193
|
|
|
194
|
+
## Severity tally (mandatory, machine-readable) {#severity-tally}
|
|
195
|
+
|
|
196
|
+
The Executive Summary MUST also carry exactly one tally line, counting the
|
|
197
|
+
findings you kept:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
Severity tally: Critical <n> / High <n> / Medium <n> / Low <n>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Write every bucket, zeros included, and never count `Info` — the scale already
|
|
204
|
+
excludes it from scheduled work. This line is the report's own checksum:
|
|
205
|
+
`audit-to-stories --scan` cross-checks it against the findings its parser
|
|
206
|
+
extracted and names any disagreement as a **report failure**
|
|
207
|
+
(`summary.reportFailures[]`), and `--auto` refuses to file anything from a
|
|
208
|
+
report whose line is missing or wrong. A parse that silently drops findings is
|
|
209
|
+
otherwise indistinguishable from a clean audit.
|
|
210
|
+
|
|
191
211
|
## Execution strategy {#execution-strategy}
|
|
192
212
|
|
|
193
213
|
A lens is a self-contained, read-only unit of work — exactly the shape a
|
|
@@ -292,6 +292,14 @@ This executes, in order:
|
|
|
292
292
|
(files issues when auto-file is on; posts `follow-ups`).
|
|
293
293
|
- `sibling-coherence` — Spec/Acceptance coherence check across sibling bodies
|
|
294
294
|
(`plan-run-sibling-coherence`).
|
|
295
|
+
- `epic-close` — closes a container Epic once **every** child Story is
|
|
296
|
+
`agent::done`, as `completed`. This is the only completion cascade v2 has:
|
|
297
|
+
it closes the container and nothing else — no child status roll-up, no label
|
|
298
|
+
inheritance, no reopening. Because linkage is parent→child only, the parent
|
|
299
|
+
is found by scanning open `type::epic` issues, and only an Epic containing
|
|
300
|
+
one of *this run's* Stories is considered, so an unrelated container is
|
|
301
|
+
never swept. An Epic with an outstanding child is reported `pending` and
|
|
302
|
+
left open.
|
|
295
303
|
|
|
296
304
|
A single-Story run skips the epilogue — follow-ups are captured on merge
|
|
297
305
|
confirm instead (`captureStoryFollowUps`).
|
|
@@ -234,7 +234,7 @@ Each `stories.json` entry: `slug` (`^[a-z0-9][a-z0-9-]*$`), `type: "story"`,
|
|
|
234
234
|
`title`, `body` (`goal`, optional `spec`, `changes[{path, assumption}]` —
|
|
235
235
|
`creates|refactors-existing|deletes`, `non_goals`, `reason_to_exist`),
|
|
236
236
|
top-level `acceptance[]`, `verify[]` (`… (unit|contract|e2e|validate)`), and
|
|
237
|
-
`depends_on[]` (
|
|
237
|
+
`depends_on[]` (a sibling slug, or `#<id>` for an existing open Story).
|
|
238
238
|
|
|
239
239
|
Nothing in that shape inventories the repo for the author. `changes[]` arrives
|
|
240
240
|
pre-resolved against the working tree, and Phase 8's
|
|
@@ -414,6 +414,114 @@ That is the whole point of running it first: a dry run that comes back clean
|
|
|
414
414
|
has already paid for every deterministic refusal, so the real persist has
|
|
415
415
|
nothing left to discover except network failure.
|
|
416
416
|
|
|
417
|
+
## The container Epic (Gate #3)
|
|
418
|
+
|
|
419
|
+
Gate #3 has two branches, in this order: **adopt** an Epic that already exists,
|
|
420
|
+
else **create** a new one.
|
|
421
|
+
|
|
422
|
+
### Adopting an open Epic (any N)
|
|
423
|
+
|
|
424
|
+
`epicCandidates[]` in the plan-context envelope lists **every open `type::epic`
|
|
425
|
+
issue**, each `{ id, title, url, score, childIds }`, ranked by token overlap
|
|
426
|
+
between the seed and the Epic's title, `## Goal` and its children's titles. The
|
|
427
|
+
list is deliberately **complete rather than thresholded**: a low score is
|
|
428
|
+
evidence for the operator to weigh, and hiding a candidate is exactly how a
|
|
429
|
+
plan opens its second container for one body of work.
|
|
430
|
+
|
|
431
|
+
This branch fires at **any N, N=1 included** — "add this to the Epic we started
|
|
432
|
+
last week" is the single-Story case, and refusing it below three Stories would
|
|
433
|
+
leave the common follow-up plan with nowhere to file itself. The three-Story
|
|
434
|
+
threshold governs **creation** only, where it still holds: at two Stories a
|
|
435
|
+
pair of ids is as easy to carry as one container id.
|
|
436
|
+
|
|
437
|
+
On a yes, pass `--epic <id>`. Persist then:
|
|
438
|
+
|
|
439
|
+
1. resolves the id **before the first create** (dry run included) — it must be
|
|
440
|
+
**open** and carry `type::epic`, or the run hard-errors having written
|
|
441
|
+
nothing;
|
|
442
|
+
2. after the Stories exist, appends one `- [ ] #N` row per new Story to the
|
|
443
|
+
Epic's checklist via `appendEpicChildIds`, which is idempotent and preserves
|
|
444
|
+
existing rows' **checked state**, their order, and the fingerprint marker;
|
|
445
|
+
3. mirrors a native sub-issue edge per child.
|
|
446
|
+
|
|
447
|
+
The refusal posture is the **opposite** of creation's, deliberately. Creation
|
|
448
|
+
degrades (an unensurable label just skips the container) because the operator
|
|
449
|
+
never named one. Adoption cannot: the operator named a specific id, so silently
|
|
450
|
+
not adopting it would leave them believing their Stories were filed somewhere
|
|
451
|
+
they were not. Hence a hard error, raised while nothing has been written and
|
|
452
|
+
the fix is free. Once the Stories are live the posture flips back — a failed
|
|
453
|
+
checklist write or sub-issue edge only warns.
|
|
454
|
+
|
|
455
|
+
**Only open Epics are adoptable.** A closed Epic is a finished body of work;
|
|
456
|
+
joining one would reopen a container the epilogue deliberately closed and
|
|
457
|
+
re-scope a completed plan. Open a new container, or reopen the old one by hand.
|
|
458
|
+
|
|
459
|
+
`--epic` and `--epic-title`/`--epic-goal` are **mutually exclusive** — a run
|
|
460
|
+
either joins a container or opens one — and supplying both is a usage error
|
|
461
|
+
raised before any I/O.
|
|
462
|
+
|
|
463
|
+
### Creating a new container (N>2)
|
|
464
|
+
|
|
465
|
+
Above two Stories, `/mandrel-plan` offers to group them under one `type::epic`
|
|
466
|
+
container. Confirmed, persist opens it **after** the Stories — its body embeds
|
|
467
|
+
their issue numbers and its sub-issue edges need their database ids — and
|
|
468
|
+
links every created Story both ways it can: a `- [ ] #N` body checklist and a
|
|
469
|
+
native GitHub sub-issue edge. Both are written because each survives what the
|
|
470
|
+
other does not; the delivery-side reader unions them.
|
|
471
|
+
|
|
472
|
+
What the Epic must never carry: an `agent::*` label (that absence keeps it out
|
|
473
|
+
of the bare `/mandrel-deliver` ready list and outside the `type::story`-scoped
|
|
474
|
+
body lint), a `## Spec`, an `acceptance[]` / `verify[]`, or any path, finding
|
|
475
|
+
or rationale a child does not already hold. It is a container; unique content
|
|
476
|
+
here is content no delivering agent reads.
|
|
477
|
+
|
|
478
|
+
What the **Stories** never gain is an `Epic: #N` footer. Linkage is
|
|
479
|
+
parent→child only, which is exactly why every existing refusal of that footer
|
|
480
|
+
still stands and each Story stays independently deliverable (ADR
|
|
481
|
+
`20260905-5139`).
|
|
482
|
+
|
|
483
|
+
Degradation is deliberate: an unensurable `type::epic` label skips the Epic
|
|
484
|
+
entirely (an unlabelled container is not a container), while a failed
|
|
485
|
+
sub-issue edge only warns — the checklist still lists every child. Either way
|
|
486
|
+
the Stories are untouched and deliver by id. A resumed persist adopts an
|
|
487
|
+
existing Epic carrying the same fingerprint, which is keyed on the title **and
|
|
488
|
+
the exact child set**, so a run grouping different Stories never adopts the
|
|
489
|
+
wrong container.
|
|
490
|
+
|
|
491
|
+
## Cross-plan `depends_on` (`#<id>`)
|
|
492
|
+
|
|
493
|
+
A `depends_on[]` entry is read **lexically**: `some-slug` is a sibling inside
|
|
494
|
+
this plan, `#<id>` an **external** blocker already live on the tracker. The
|
|
495
|
+
second form is what lets a plan authored today wait on a Story an earlier plan
|
|
496
|
+
opened and never had to name.
|
|
497
|
+
|
|
498
|
+
`dependencyCandidates[]` in the envelope is the advisory prompt for it: open
|
|
499
|
+
`type::story` issues whose declared `changes[]` footprint intersects the seed's
|
|
500
|
+
`complexitySignals.predictedPaths`, each carrying the `overlappingPaths[]` that
|
|
501
|
+
matched. Overlap is computed on **declared footprints** and not on prose, using
|
|
502
|
+
the same `storyFootprint` the wave runner uses to withhold colliding Stories at
|
|
503
|
+
dispatch — so the planner sees the collision the runtime would later enforce,
|
|
504
|
+
one layer earlier and while it is still cheap to order around. A seed naming no
|
|
505
|
+
paths short-circuits to `[]` with no provider call at all.
|
|
506
|
+
|
|
507
|
+
It stays advisory: two Stories can touch a shared barrel file with no real
|
|
508
|
+
ordering between them, and only the operator knows.
|
|
509
|
+
|
|
510
|
+
External refs are **excluded from sibling ordering and cycle detection** —
|
|
511
|
+
a Story already open is not scheduled by this run, so it has no position in the
|
|
512
|
+
topological sort and cannot close a cycle back into a Story that does not exist
|
|
513
|
+
yet. They are validated **before any create** (dry run included): each must
|
|
514
|
+
resolve to an **open `type::story`**, and a closed issue, a container Epic, a
|
|
515
|
+
missing id or a non-Story hard-errors with every bad ref named in one pass. The
|
|
516
|
+
strictness is the point — a blocker that can never be satisfied reads to the
|
|
517
|
+
delivery engine as a permanent wedge rather than as an error worth reporting.
|
|
518
|
+
|
|
519
|
+
Persist renders the entry unchanged as a `blocked by #<id>` footer line and
|
|
520
|
+
mirrors the native `blocked_by` edge, which is the same pair of surfaces a
|
|
521
|
+
sibling edge produces. `/mandrel-deliver` therefore gates on it with no engine
|
|
522
|
+
change: `resolve-stories.js` has always resolved foreign blockers from live
|
|
523
|
+
state.
|
|
524
|
+
|
|
417
525
|
## Ready means fully persisted
|
|
418
526
|
|
|
419
527
|
`agent::ready` is the **terminal** step, not part of the creating POST.
|
|
@@ -7,8 +7,8 @@ description:
|
|
|
7
7
|
|
|
8
8
|
# /mandrel-deliver
|
|
9
9
|
|
|
10
|
-
> **Lean spine.** Happy path + gate list. Sequencing, dispatch mechanics,
|
|
11
|
-
>
|
|
10
|
+
> **Lean spine.** Happy path + gate list. Sequencing, dispatch mechanics, intent
|
|
11
|
+
> phrases, ceremony and the epilogue live in on-demand
|
|
12
12
|
> [`helpers/deliver-reference.md`](helpers/deliver-reference.md) ("reference"
|
|
13
13
|
> below); the unplanned path in
|
|
14
14
|
> [`helpers/deliver-light.md`](helpers/deliver-light.md). Every delivery reads
|
|
@@ -21,9 +21,8 @@ close-and-land tail; Stories are implemented via
|
|
|
21
21
|
[`helpers/deliver-story.md`](helpers/deliver-story.md).
|
|
22
22
|
|
|
23
23
|
The dependency graph is **discovered, not declared** — `resolve-stories.js`
|
|
24
|
-
reads it from live state, so you can deliver Stories **across plan runs and
|
|
25
|
-
|
|
26
|
-
`route::lite` a body-derived hint only.
|
|
24
|
+
reads it from live state, so you can deliver Stories **across plan runs and over
|
|
25
|
+
time**. `plan-run::<id>` is filter metadata, never a resolution input.
|
|
27
26
|
|
|
28
27
|
## Inputs
|
|
29
28
|
|
|
@@ -36,25 +35,28 @@ you read:
|
|
|
36
35
|
| `/mandrel-deliver 4712` | ids | One Story via `helpers/deliver-story.md`, **inline in this session** — no `story-worker` spawn. |
|
|
37
36
|
| `/mandrel-deliver 4712 4713 …` | ids | Resolve the set, sequence by the discovered graph via `stories-wave-tick.js`, dispatch sub-agents. |
|
|
38
37
|
| `/mandrel-deliver 4712 - 4716` | ids | A **range** — every id in the inclusive span. |
|
|
38
|
+
| `/mandrel-deliver 4700` (a `type::epic`) | ids | The Epic's **open** child Stories. Mixes with Story ids. |
|
|
39
39
|
| `/mandrel-deliver add a --json flag to doctor` | prompt | Unplanned work: gate, author a receipt Story, land it — [`helpers/deliver-light.md`](helpers/deliver-light.md). |
|
|
40
40
|
|
|
41
41
|
**The discriminator is lexical and total.** An argument matching `^#?\d+$` is an
|
|
42
42
|
id, and `^#?\d+\s*[-–—]\s*#?\d+$` an inclusive **range** — pass one on as a
|
|
43
|
-
single unspaced token, never hand-expanded (reference
|
|
43
|
+
single unspaced token, never hand-expanded (reference). Either shape
|
|
44
44
|
means ids; anything else means a prompt. A **mixed** invocation (ids *and*
|
|
45
|
-
prose) is a **hard error** — refuse it and ask which was meant. A ticket
|
|
46
|
-
`type::story`, or
|
|
45
|
+
prose) is a **hard error** — refuse it and ask which was meant. A ticket that
|
|
46
|
+
is neither `type::story` nor `type::epic`, or that carries an `Epic: #N`
|
|
47
|
+
footer, is a hard error too — container Epics link parent→child only, so that
|
|
48
|
+
footer stays refused.
|
|
47
49
|
|
|
48
50
|
## Saying what you want
|
|
49
51
|
|
|
50
|
-
No flags to remember: state intent — *"…but I'll merge it myself"*, *"…one at
|
|
51
|
-
|
|
52
|
+
No flags to remember: state intent — *"…but I'll merge it myself"*, *"…one at a
|
|
53
|
+
time"* — and announce what you read. Phrasings and the flags they fill in:
|
|
52
54
|
reference § Intent phrases.
|
|
53
55
|
|
|
54
56
|
`--yes` is **runner-set, never operator-typed**: cron, `/loop` and headless
|
|
55
57
|
dispatch set it to mean *nobody is at the keyboard*, which fails the unplanned
|
|
56
|
-
path's over-scope stop closed to an envelope instead of a question. Never
|
|
57
|
-
|
|
58
|
+
path's over-scope stop closed to an envelope instead of a question. Never add it
|
|
59
|
+
to an attended run.
|
|
58
60
|
|
|
59
61
|
## Procedure
|
|
60
62
|
|
|
@@ -66,9 +68,11 @@ it to an operator or add it to an attended run.
|
|
|
66
68
|
`node .agents/scripts/resolve-stories.js --ids <id,id,...>`. It validates the
|
|
67
69
|
set and shows what will run: read `stories[]`, `dag[]` and `done[]` to
|
|
68
70
|
present the order in step 2, but do **not** thread them into step 3 — the
|
|
69
|
-
tick re-resolves the graph every beat.
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
tick re-resolves the graph every beat. An Epic id expands to its open child
|
|
72
|
+
Stories first — **announce it**. It hard-errors (exit 1) on an id that is
|
|
73
|
+
neither a Story nor an Epic, on an `Epic: #N` footer, on an Epic with no
|
|
74
|
+
open children, or on edges it cannot read — a missing gate would co-dispatch
|
|
75
|
+
against an unlanded blocker.
|
|
72
76
|
|
|
73
77
|
2. **Confirm (N>1).** Present the order; wait unless `--yes`.
|
|
74
78
|
|
|
@@ -87,62 +91,62 @@ it to an operator or add it to an attended run.
|
|
|
87
91
|
Each beat re-probes live state to derive done / in-flight itself; you never
|
|
88
92
|
compute them. `--dispatched` is the one thing you must supply — the
|
|
89
93
|
append-only list of every id you spawned this run. Cross-run de-confliction
|
|
90
|
-
via the assignee lease is automatic
|
|
91
|
-
Dispatch mechanics). Branch on the exit code:
|
|
94
|
+
via the assignee lease is automatic. Branch on the exit code:
|
|
92
95
|
- **0** — dispatch each `ready` id (already capped and overlap-free); an
|
|
93
96
|
empty `ready` with work in flight means "waiting", so keep looping;
|
|
94
97
|
`epilogueDue: true` means every Story is done — step 4.
|
|
95
98
|
- **2 / 3 / 4** — `cycleError` / `wedged` / `blocked`: stop the loop and
|
|
96
|
-
route per reference
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
route per reference. **4** is the protocol's HITL pause
|
|
100
|
+
([`instructions.md` § 1.J](../instructions.md)) — surface it and wait for
|
|
101
|
+
the operator; never poll.
|
|
99
102
|
|
|
100
103
|
4. **Close each hand-off** (§ Closing what the workers hand back), then, with
|
|
101
104
|
every Story landed, run the **per-run epilogue (N>1)**:
|
|
102
|
-
`node .agents/scripts/plan-run-epilogue.js --stories 101,102
|
|
103
|
-
|
|
105
|
+
`node .agents/scripts/plan-run-epilogue.js --stories 101,102`, which also
|
|
106
|
+
closes a container Epic whose children all landed. N=1 skips it
|
|
107
|
+
([reference](helpers/deliver-reference.md)).
|
|
104
108
|
|
|
105
109
|
5. **Correct what the change invalidated.** If a memory you recalled this
|
|
106
|
-
session is now wrong — a trap this landed, a budget it moved — fix that
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
session is now wrong — a trap this landed, a budget it moved — fix that entry
|
|
111
|
+
now, while both the old belief and the new fact are in context, and say so
|
|
112
|
+
when you report. No memory substrate → skip silently. Sweeping the whole pool
|
|
113
|
+
is [`/memory-consolidate`](memory-consolidate.md), not this step.
|
|
110
114
|
|
|
111
115
|
## Closing what the workers hand back {#tail}
|
|
112
116
|
|
|
113
117
|
**The tail is the orchestrator's, not the worker's.** A dispatched
|
|
114
118
|
`story-worker` stops at a pushed branch and returns a hand-off; **you** run
|
|
115
119
|
[`helpers/deliver-story.md`](helpers/deliver-story.md) Step 3
|
|
116
|
-
(`single-story-close.js`) for it, foreground, and relay
|
|
120
|
+
(`single-story-close.js`) for it, foreground, and relay its envelope.
|
|
117
121
|
|
|
118
|
-
**Serialize the tail.** Implementation runs in parallel; closing does not.
|
|
119
|
-
one Story at a time — closes contend on the base branch, the merge queue
|
|
120
|
-
the checkout. A worker handing back mid-close waits its turn.
|
|
122
|
+
**Serialize the tail.** Implementation runs in parallel; closing does not.
|
|
123
|
+
Close one Story at a time — closes contend on the base branch, the merge queue
|
|
124
|
+
and the checkout. A worker handing back mid-close waits its turn.
|
|
121
125
|
|
|
122
|
-
**A worker returning no terminal envelope is
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
**A worker returning no terminal envelope is expected**, not a failure to answer
|
|
127
|
+
with a re-dispatch: only close mints one. Close the pushed branch, or probe
|
|
128
|
+
read-only with `node .agents/scripts/deliver-recover.js --story <storyId>` and
|
|
129
|
+
resume what it names.
|
|
126
130
|
|
|
127
131
|
**Reading the outcome.** Each close ends the Story in one schema-validated
|
|
128
132
|
envelope — `landed` | `pending` | `blocked` | `failed`; statuses, exits and
|
|
129
|
-
fields are digest § 5. `pending` is **not** a failure — `nextCommand
|
|
130
|
-
it; run that, do not re-dispatch.
|
|
133
|
+
fields are digest § 5. `pending` is **not** a failure — run its `nextCommand`.
|
|
131
134
|
|
|
132
135
|
**Branch model (authoritative).** `story-<id>` → PR → `main` (squash +
|
|
133
136
|
required checks), per digest § 2; dependent Stories land sequentially. Ceremony
|
|
134
|
-
depth (profiles + derived level via `ceremony-routing.js`, review
|
|
135
|
-
|
|
137
|
+
depth (profiles + the derived level via `ceremony-routing.js`, which review
|
|
138
|
+
depth reads): reference § Ceremony.
|
|
136
139
|
|
|
137
140
|
## Constraints
|
|
138
141
|
|
|
139
142
|
- **Land or block — never a silent local build** (digest § 2). Attended delivers
|
|
140
143
|
default to close-and-land (`delivery.routing.closeAndLand: true`); rest at
|
|
141
|
-
`agent::closing` only when a human owns
|
|
142
|
-
- **`/mandrel-deliver` never plans.** Planned tickets come from
|
|
143
|
-
an over-scope prompt **escalates and
|
|
144
|
-
session to rescue it
|
|
145
|
-
§ Escalation is
|
|
144
|
+
`agent::closing` only when a human owns the merge.
|
|
145
|
+
- **`/mandrel-deliver` never plans.** Planned tickets come from
|
|
146
|
+
[`/mandrel-plan`](mandrel-plan.md), and an over-scope prompt **escalates and
|
|
147
|
+
ends** — never invoke `/mandrel-plan` in this session to rescue it
|
|
148
|
+
([`helpers/deliver-light.md`](helpers/deliver-light.md) § Escalation is
|
|
149
|
+
terminal).
|
|
146
150
|
|
|
147
151
|
## See also
|
|
148
152
|
|