mandrel 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.
Files changed (73) hide show
  1. package/.agents/agents/.markdownlint.json +4 -0
  2. package/.agents/agents/acceptance-critic.md +30 -5
  3. package/.agents/agents/auditor.md +36 -19
  4. package/.agents/agents/plan-critic.md +31 -5
  5. package/.agents/agents/story-worker.md +91 -100
  6. package/.agents/docs/configuration.md +39 -1
  7. package/.agents/docs/execution-reference.md +13 -0
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +131 -265
  10. package/.agents/rules/git-conventions.md +47 -83
  11. package/.agents/rules/orchestration-error-handling.md +28 -0
  12. package/.agents/schemas/agentrc.schema.json +44 -1
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +1 -1
  15. package/.agents/scripts/apply-quality-bootstrap.js +1 -1
  16. package/.agents/scripts/audit-to-stories.js +51 -0
  17. package/.agents/scripts/check-test-temp-hygiene.js +438 -0
  18. package/.agents/scripts/deliver-recover.js +23 -6
  19. package/.agents/scripts/lib/audit-suite/index.js +5 -0
  20. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
  21. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  22. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +120 -55
  23. package/.agents/scripts/lib/config/temp-paths.js +121 -1
  24. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  25. package/.agents/scripts/lib/config-settings-schema.js +32 -0
  26. package/.agents/scripts/lib/findings/semantic-issue-search.js +43 -5
  27. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  28. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  29. package/.agents/scripts/lib/observability/terse-result.js +114 -0
  30. package/.agents/scripts/lib/orchestration/complexity-gate.js +318 -0
  31. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  32. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  33. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  34. package/.agents/scripts/lib/orchestration/plan-context.js +60 -0
  35. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  36. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  37. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  38. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +102 -2
  39. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +215 -14
  40. package/.agents/scripts/lib/orchestration/resolve-stories.js +7 -0
  41. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  42. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +221 -8
  43. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  44. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  45. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +55 -14
  46. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +9 -3
  47. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  48. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  49. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -1
  50. package/.agents/scripts/lib/orchestration/task-body-validator.js +13 -40
  51. package/.agents/scripts/lib/story-body/body-format-lints.js +215 -0
  52. package/.agents/scripts/lib/story-body/story-body.js +18 -2
  53. package/.agents/scripts/lib/templates/decomposer-prompts.js +29 -6
  54. package/.agents/scripts/lib/test-env.js +65 -0
  55. package/.agents/scripts/plan-context.js +66 -9
  56. package/.agents/scripts/plan-critics.js +115 -3
  57. package/.agents/scripts/plan-persist.js +11 -1
  58. package/.agents/scripts/plan-run-epilogue.js +1 -1
  59. package/.agents/scripts/providers/github/issues.js +54 -7
  60. package/.agents/scripts/providers/github/search-budget.js +124 -0
  61. package/.agents/scripts/providers/github/search-query.js +71 -0
  62. package/.agents/scripts/single-story-confirm-merge.js +79 -10
  63. package/.agents/scripts/single-story-init.js +19 -3
  64. package/.agents/scripts/stories-wave-tick.js +1 -1
  65. package/.agents/scripts/sync-branch-from-base.js +9 -3
  66. package/.agents/workflows/deliver.md +86 -230
  67. package/.agents/workflows/helpers/deliver-reference.md +167 -0
  68. package/.agents/workflows/helpers/deliver-story-reference.md +203 -0
  69. package/.agents/workflows/helpers/deliver-story.md +114 -422
  70. package/.agents/workflows/helpers/plan-reference.md +211 -0
  71. package/.agents/workflows/plan.md +107 -279
  72. package/docs/CHANGELOG.md +47 -0
  73. package/package.json +1 -1
@@ -112,6 +112,209 @@ not a substitute for prefixing paths correctly.
112
112
 
113
113
  ---
114
114
 
115
+ ## Engine invariants and the lite route
116
+
117
+ The v2 engine's trait table:
118
+
119
+ | Trait | v2 `/deliver-story` |
120
+ | --- | --- |
121
+ | Ticket type | `type::story` only |
122
+ | Branch | `story-<id>` seeded from `project.baseBranch` (`main`) |
123
+ | Merge target | `main` via PR (squash + required checks) |
124
+ | Epic integration branch | **None** — no `epic/<id>`, no `--no-ff` wave merge |
125
+ | Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session |
126
+ | Ceremony | Per-Story, routed off the derived change level via `ceremony-routing.js` |
127
+
128
+ **Ceremony-lite Stories still land through this engine unchanged (Story #4683).** A Story that `/plan` routed onto the ceremony-lite path (its
129
+ `complexityRoute.route === "lite"`) collapses only the *advisory* plan/deliver
130
+ ceremony — the fresh-critic / Tech-Spec authoring a one-artifact scope does
131
+ not earn. It does **not** get a cheaper landing: the close-validation gates
132
+ (lint / test / format / coverage / CRAP / maintainability), the PR to `main`,
133
+ and the `rules/security-baseline.md` MUSTs all run exactly as for a
134
+ full-ceremony Story. The lite route's `preserves` field is the machine-readable
135
+ record of those non-negotiables; there is no lite-specific gate bypass.
136
+
137
+ **The lite route persists to delivery as the `route::lite` label (Story #4707).** Persist stamps every Story of a lite-routed plan with that marker
138
+ (and ledgers the route — including any audited planner-downgrade reason — on
139
+ its `story-plan-state` checkpoint); a full-routed Story carries no marker.
140
+ `/deliver` reads the label via `resolveStoryDispatchMode`
141
+ (`lib/orchestration/complexity-gate.js`) and executes a lite Story
142
+ **inline in the deliver session** — no `story-worker` sub-agent boot, and
143
+ the Step 1a acceptance self-eval runs its critics **inline** (no
144
+ fresh-context acceptance-critic sub-agent dispatch; sub-agent boots are the
145
+ dominant deliver-phase token cost at trivial scope). Inline execution
146
+ changes the isolation only: the engine, every script gate, and the
147
+ terminal envelope are byte-identical either way.
148
+
149
+ ---
150
+
151
+ ## Step 1 — Implementation detail
152
+
153
+ **Docs context — digest-first.** Read a full doc only when the Story's own
154
+ context points you at one — do not ingest the whole
155
+ `project.docsContextFiles` set up front. If the caller provides a
156
+ `docsDigestPath`, prefer that compact outline and pull individual files on
157
+ demand. See [`.agents/instructions.md` § 3](../../instructions.md).
158
+
159
+ **Write-time audit checklists.** When the caller provides a `checklistPath`
160
+ (footprint-matched **local**-lens authoring checklists), read it before you
161
+ write and self-check as you author. When absent, lens-aware coverage still
162
+ runs maker-blind at Story-scope review inside the close subprocess. The
163
+ dispatch step produces `checklistPath` from the Story's predicted footprint
164
+ before it spawns the worker (Story #4627) — see [`/deliver`](../deliver.md).
165
+
166
+ **Pre-eval full-suite discipline (spine step 5).** Repo-invariant guards —
167
+ drift-guard and schema tests living outside the Story's scoped greps — are
168
+ the failure class that actually bounces deliveries: close-validation
169
+ discovers them only after the whole close pipeline has run, at several times
170
+ the cost of one pre-eval full-suite run.
171
+
172
+ **Conflict with `main` mid-implementation** → resolve as you would any branch
173
+ rebase. There is no `epic/<id>` intermediate, so the rebase base is `main`
174
+ directly.
175
+
176
+ ### Step 1a — self-eval mechanics
177
+
178
+ **Critic evidence-share (Story #4250).** When the critic runs a `verify[]`
179
+ command that is byte-identical to a close gate (`lint` / `typecheck`), it
180
+ records the pass into the Story evidence keyspace via `--standalone` so
181
+ close short-circuits the gate at unchanged HEAD. Run it in the **Story
182
+ worktree** (`workCwd` from Step 0):
183
+
184
+ ```bash
185
+ node <main-repo>/.agents/scripts/evidence-gate.js \
186
+ --standalone --scope-id <storyId> --gate lint \
187
+ --worktree <workCwd> -- npm run lint
188
+ ```
189
+
190
+ **On `decision: "block"`** — post a `friction` comment naming the unmet
191
+ criteria, then transition the Story to `agent::blocked`:
192
+
193
+ ```bash
194
+ node .agents/scripts/diagnose-friction.js --story <storyId> \
195
+ --cmd node .agents/scripts/acceptance-eval.js --story <storyId> --verdict <verdict-path>
196
+ node .agents/scripts/update-ticket-state.js --ticket <storyId> --state agent::blocked
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Step 2 — Ceremony detail
202
+
203
+ **Compute the change set once** (Story #4593) with the shared enumerator —
204
+ the same module close uses — and reuse that one list downstream:
205
+
206
+ ```bash
207
+ node --input-type=module -e '
208
+ import { computeChangeSet } from "<main-repo>/.agents/scripts/lib/orchestration/change-set.js";
209
+ const { files } = computeChangeSet({ baseRef: "main", headRef: "story-<storyId>" });
210
+ console.log(JSON.stringify(files));
211
+ '
212
+ ```
213
+
214
+ Derive the level with
215
+ [`deriveChangeLevel`](../../scripts/lib/orchestration/review-depth.js) over
216
+ the one computed change-set list: a diff touching a sensitive path registered
217
+ in `.agents/schemas/audit-rules.json` derives `high`, one touching none
218
+ derives `low`, and an unenumerable diff (`files === null`) derives `null`.
219
+ Hand the **same** list to every acceptance critic you spawn (Step 1a) — a
220
+ critic that re-ran its own `git diff` could score against a different set
221
+ than the one that routed it.
222
+
223
+ Resolve fresh-vs-inline acceptance critics per AC-cluster with
224
+ [`resolveCeremonyForRisk`](../../scripts/lib/orchestration/ceremony-routing.js)
225
+ (`minimal` → always inline; `strict` → always fresh; `standard` →
226
+ `high`/`null` → `fresh`, `low` → `inline` unless the `freshCriticSampleRate`
227
+ floor forces `fresh`). Review depth reads the same derived level via
228
+ `review-depth.js` inside close, so the two decisions cannot disagree.
229
+
230
+ **Lite-route override (Story #4707).** When the Story carries the
231
+ `route::lite` marker (`resolveStoryDispatchMode` → `inline`), run every
232
+ acceptance critic **inline** — do not spawn fresh-context critic sub-agents
233
+ regardless of what the profile would otherwise resolve. The self-eval rigor
234
+ (scoring each `acceptance[]` item against the one computed change set, with
235
+ `verify[]` output as evidence) is unchanged; only the sub-agent boot is
236
+ removed. Hard gates are untouched.
237
+
238
+ ---
239
+
240
+ ## Step 3 — Merge wait, async mode, and flags
241
+
242
+ **What close does internally.** The script runs the close-validation gates
243
+ against `baseBranch`, syncs the Story branch from `origin/<baseBranch>`
244
+ (Story #2580 — the parallel-race defence), pushes `story-<id>`, opens (or
245
+ reuses) a PR against `baseBranch` with a `Closes #<storyId>` footer, enables
246
+ GitHub native auto-merge (`--auto --squash --delete-branch`) **when
247
+ `delivery.ci.autoMerge` is `"trust-ci"` (the default)**, flips the Story to
248
+ `agent::closing`, reaps the worktree, releases the lease, then **waits for
249
+ the merge** and — on a confirmed merge — flips `agent::done` and runs the
250
+ post-land tail.
251
+
252
+ **The merge wait is bounded and resumable.** Two budgets, deliberately
253
+ separate (`delivery.mergeWatch.*`):
254
+
255
+ - **`maxWaitSeconds`** (default 300) bounds **one invocation**, sized to fit
256
+ inside a single host tool invocation (~10 min ceiling) alongside the gates
257
+ that precede it. Expiry → `pending`. Pass `--max-wait-seconds <n>` to raise
258
+ it when your host has no such ceiling and you want to land in one block.
259
+ - **`maxBudgetSeconds`** (default 3600) bounds the **cumulative** wait across
260
+ resumes, anchored at the PR's `createdAt` so resuming does not restart the
261
+ clock. Exhausting *this* is the genuine give-up → `blocked`.
262
+
263
+ The wait probes the checks every poll: a red required check fails fast as
264
+ `checks-failed` instead of burning the budget, and a PR that falls behind its
265
+ base is brought up to date within `updateAttempts` tries.
266
+
267
+ **Async merge-confirm mode (`delivery.mergeWatch.mode: "async"`, Story #4698).** Under the default `"sync"` the merge wait runs in the foreground as
268
+ described above. When a consumer's CI routinely takes longer than the host
269
+ tool ceiling (~10 min) can hold a single close invocation, the foreground
270
+ wait almost always expires `pending` after burning ~5 minutes of the slot —
271
+ so `"async"` makes that async confirm a designed mode instead of an expiry
272
+ accident. In async mode the close arms auto-merge, runs one short **~60s
273
+ probe window** (long enough to catch an instant merge and, via the
274
+ head-anchored required-check predicate, an instantly-red required check),
275
+ then returns the standard `pending` terminal with a `nextCommand`. When you
276
+ receive that `pending` envelope, launch its `nextCommand`
277
+ (`single-story-confirm-merge.js … --wait`) as a **background** invocation —
278
+ host **background Bash** (`run_in_background`), whose completion re-invokes
279
+ the agent — and continue; do **not** sit in a foreground poll the tool
280
+ ceiling will kill. `single-story-confirm-merge.js` is already idempotent and
281
+ owns the whole tail, so no new state holder is needed, and
282
+ `deliver-recover.js` remains the recovery path for an orphaned confirm. The
283
+ cumulative `maxBudgetSeconds` give-up is unchanged; `"sync"` behaviour is
284
+ byte-compatible.
285
+
286
+ **`delivery.ci.autoMerge` policy.** Under the default `"trust-ci"`, GitHub
287
+ native auto-merge is armed and the PR squash-merges once its **required**
288
+ checks pass. Under `"strict"`, the close **does not arm auto-merge** — the
289
+ PR opens and waits for an **operator merge**, exactly as `--no-auto-merge`
290
+ does per-run.
291
+
292
+ **Close flags:**
293
+
294
+ - `--skip-validation` — bypass the gates. Use only when re-running close
295
+ after a fixed gate failure that's already known to pass.
296
+ - `--skip-sync` — bypass the base-sync (Story #2580). Use only after a
297
+ hand-resolved sync, or in tests.
298
+ - `--no-auto-merge` — disable auto-merge. Use when the PR materially changes
299
+ behaviour and warrants a pre-merge eyeball; the operator then merges via
300
+ the GitHub UI.
301
+ - `--wait-merge` — **close-and-land** (Story #4428). Forces close to poll
302
+ the armed PR to merge confirmation and flip `agent::done` itself. When
303
+ neither land flag is passed, close defaults from
304
+ `delivery.routing.closeAndLand` (**true**): attended and headless delivers
305
+ share the land-in-one-close happy path.
306
+ - `--no-wait-merge` — explicit opt-out that always wins. Use when the
307
+ operator wants the PR left at `agent::closing` for a human land (or a
308
+ wrapper that will invoke `single-story-confirm-merge.js` itself). Reports
309
+ `pending` — the work is not done, nothing is broken, and one named command
310
+ finishes it.
311
+ - `--max-wait-seconds <n>` — raise the merge wait's per-invocation bound for
312
+ this run (Story #4543). Use from a headless caller with no host
313
+ tool-invocation ceiling to keep single-block semantics without editing the
314
+ consumer's config.
315
+
316
+ ---
317
+
115
318
  ## Step 3 — Close pipeline detail
116
319
 
117
320
  The `single-story-close.js` script, in order: