mandrel 2.7.0 → 2.8.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/README.md +104 -330
  2. package/.agents/agents/auditor.md +135 -0
  3. package/.agents/agents/plan-critic.md +80 -0
  4. package/.agents/audit-checklists/dependencies.md +7 -0
  5. package/.agents/audit-checklists/documentation.md +1 -0
  6. package/.agents/docs/SDLC.md +69 -162
  7. package/.agents/docs/configuration.md +133 -499
  8. package/.agents/docs/quality-gates.md +59 -180
  9. package/.agents/instructions.md +170 -295
  10. package/.agents/rules/changelog-style.md +8 -66
  11. package/.agents/rules/ci-remediation.md +65 -124
  12. package/.agents/rules/gherkin-standards.md +10 -31
  13. package/.agents/rules/git-conventions-reference.md +28 -61
  14. package/.agents/rules/git-conventions.md +1 -1
  15. package/.agents/rules/orchestration-error-handling.md +5 -15
  16. package/.agents/rules/security-baseline.md +7 -13
  17. package/.agents/rules/shell-conventions.md +4 -13
  18. package/.agents/rules/test-seams.md +2 -2
  19. package/.agents/rules/testing-standards.md +7 -17
  20. package/.agents/skills/core/api-and-interface-design/SKILL.md +23 -297
  21. package/.agents/skills/core/api-and-interface-design/reference.md +76 -0
  22. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +20 -327
  23. package/.agents/skills/core/browser-testing-with-devtools/reference.md +74 -0
  24. package/.agents/skills/core/code-review-and-quality/reference.md +16 -398
  25. package/.agents/skills/core/debugging-and-error-recovery/reference.md +14 -281
  26. package/.agents/skills/core/documentation-and-adrs/reference.md +42 -338
  27. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +28 -326
  28. package/.agents/skills/core/idea-refinement/SKILL.md +67 -193
  29. package/.agents/skills/core/security-and-hardening/SKILL.md +15 -31
  30. package/.agents/skills/core/security-and-hardening/reference.md +15 -273
  31. package/.agents/skills/skills.index.json +5 -5
  32. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +16 -222
  33. package/.agents/skills/stack/qa/gherkin-authoring/reference.md +157 -0
  34. package/.agents/skills/stack/qa/playwright/SKILL.md +0 -29
  35. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +19 -23
  36. package/.agents/skills/stack/qa/qa-harness/SKILL.md +35 -53
  37. package/.agents/skills/stack/qa/vitest/SKILL.md +0 -29
  38. package/.agents/workflows/audit-accessibility.md +34 -108
  39. package/.agents/workflows/audit-architecture.md +35 -123
  40. package/.agents/workflows/audit-clean-code.md +19 -78
  41. package/.agents/workflows/audit-data-model.md +32 -100
  42. package/.agents/workflows/audit-dependencies.md +47 -111
  43. package/.agents/workflows/audit-devops.md +16 -83
  44. package/.agents/workflows/audit-documentation.md +46 -93
  45. package/.agents/workflows/audit-navigability.md +26 -80
  46. package/.agents/workflows/audit-performance.md +40 -106
  47. package/.agents/workflows/audit-privacy.md +17 -80
  48. package/.agents/workflows/audit-quality.md +35 -99
  49. package/.agents/workflows/audit-security.md +20 -78
  50. package/.agents/workflows/audit-seo.md +20 -98
  51. package/.agents/workflows/audit-sre.md +20 -88
  52. package/.agents/workflows/audit-to-stories.md +1 -8
  53. package/.agents/workflows/audit-ux-ui.md +17 -80
  54. package/.agents/workflows/deliver.md +54 -9
  55. package/.agents/workflows/git-cleanup.md +50 -275
  56. package/.agents/workflows/helpers/audit-lens-core.md +230 -0
  57. package/.agents/workflows/helpers/code-review.md +11 -23
  58. package/.agents/workflows/helpers/deliver-story-reference.md +114 -17
  59. package/.agents/workflows/helpers/deliver-story.md +26 -186
  60. package/.agents/workflows/helpers/qa-core.md +174 -0
  61. package/.agents/workflows/helpers/qa-run-scenario-reference.md +35 -0
  62. package/.agents/workflows/helpers/qa-run-scenario.md +11 -25
  63. package/.agents/workflows/helpers/worktree-lifecycle.md +6 -67
  64. package/.agents/workflows/mandrel-update.md +7 -13
  65. package/.agents/workflows/plan.md +17 -15
  66. package/.agents/workflows/qa-assist.md +140 -269
  67. package/.agents/workflows/qa-explore.md +125 -316
  68. package/.agents/workflows/qa-run.md +180 -380
  69. package/docs/CHANGELOG.md +10 -0
  70. package/package.json +1 -1
  71. package/.agents/workflows/helpers/audit-dual-path.md +0 -59
  72. package/.agents/workflows/helpers/audit-self-check.md +0 -70
  73. package/.agents/workflows/helpers/audit-severity-scale.md +0 -19
@@ -286,429 +286,128 @@ top-level keys are validation errors.
286
286
 
287
287
  <!-- END GENERATED:agentrc -->
288
288
 
289
- ## `project` (required)
290
-
291
- Project-local execution behaviour. Only `paths` is required; everything else
292
- falls back to documented defaults.
293
-
294
- ### `project.paths` (required)
295
-
296
- Filesystem roots the framework reads from. All three keys are required —
297
- the resolver no longer applies code-level fallbacks; a missing value is a
298
- validation error with a clear `instancePath`.
299
-
300
- | Field | Required | Purpose |
301
- | ----------- | -------- | ------------------------------------------------ |
302
- | `agentRoot` | Yes | Path to the materialized framework payload (e.g. `.agents`). |
303
- | `docsRoot` | Yes | Path to project documentation (e.g. `docs`). |
304
- | `tempRoot` | Yes | Path for ephemeral artefacts (e.g. `temp`). |
305
-
306
- `auditOutputDir` is derived (not configurable) it resolves to
307
- `${tempRoot}/audits` and is the canonical destination for every
308
- `audit-*` workflow's result reports **and** the audit-suite's prompt
309
- artifacts. Override `tempRoot` to relocate audit output.
310
-
311
- ### `project.baseBranch`
312
-
313
- | Field | Required | Default | Purpose |
314
- | ------------ | -------- | ------- | ------------------------------------------------------------------------- |
315
- | `baseBranch` | No | (none) | Default branch name (e.g. `main`). Read by close, push, and rebase paths. |
316
-
317
- ### `project.commands`
318
-
319
- Executable strings the framework spawns for validation, testing, and baseline
320
- ratchets. Strings must be non-empty and pass the shell-injection guard
321
- (`safeString` — disallows `;`, `&`, `|`, backtick, `$`, `<`, `>`).
322
-
323
- `typecheck` is nullable to indicate "not applicable for this repo"; `null`
324
- is the canonical disabled value.
325
-
326
- | Field | Required | Default | Type | Purpose |
327
- | -------------- | -------- | ----------------------------- | --------------- | ------------------------------------------------------ |
328
- | `lintBaseline` | No | (none) | `string` | Structured-output linter for the lint ratchet. |
329
- | `test` | No | (none) | `string` | Project test runner. |
330
- | `typecheck` | No | `null` | `string \| null` | Strict type-checking. `null` = disabled. |
331
- | `formatCheck` | No | `npx biome format .` | `string` | Read-only format check used by close-validation. |
332
- | `formatWrite` | No | `npx biome format --write .` | `string` | Auto-format invocation used by `runFormatAutofix`. |
333
-
334
- Read with `getCommands(config)` — see
335
- [`config-resolver.js`](../scripts/lib/config-resolver.js).
336
-
337
- ### `project.docsContextFiles`
338
-
339
- | Field | Required | Default | Purpose |
340
- | ------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------- |
341
- | `docsContextFiles` | No | `[]` | Files the context-hydration engine includes when assembling agent prompts. Resolved against `paths.docsRoot`. |
342
-
343
- > **Entries are plain filenames, not globs.** Each entry is resolved as a
344
- > single file under `paths.docsRoot`; the loader does **not** expand glob
345
- > patterns. This matters for the decisions log: when a project adopts the
346
- > index + `decisions/` ADR-directory layout (see
347
- > [`documentation-and-adrs`](../skills/core/documentation-and-adrs/SKILL.md)),
348
- > the **index** `decisions.md` stays the mandatory-read and the per-ADR bodies
349
- > under `decisions/` are link-followed on demand — **index-only by default**.
350
- > Auto-loading every ADR body into each task's context would reintroduce the
351
- > bloat the directory split exists to remove. A project that genuinely wants
352
- > the full ADR set in mandatory context must opt in by listing the individual
353
- > ADR files explicitly (one filename per entry); there is no built-in
354
- > `decisions/*.md` glob.
355
-
356
- ---
357
-
358
- ## `github`
359
-
360
- Ticketing provider configuration. Required when any GitHub-aware workflow
361
- runs (which is the common case).
362
-
363
- ### `github` top-level
364
-
365
- | Field | Required | Purpose |
366
- | ---------------- | -------- | ------------------------------------------------------------------ |
367
- | `owner` | Yes | GitHub repository owner (user or org). |
368
- | `repo` | Yes | GitHub repository name. |
369
- | `projectNumber` | No | GitHub Projects V2 number for custom field writes. |
370
- | `projectOwner` | No | Project board owner (defaults to `owner`). |
371
- | `operatorHandle` | No | `@`-prefixed handle used in operator @mentions. |
372
-
373
- ### `github.branchProtection`
374
-
375
- Drives the `node .agents/scripts/bootstrap.js` flow that creates or merges
376
- branch protection on the base branch.
377
-
378
- | Field | Required | Default | Purpose |
379
- | ---------------- | -------- | ------- | ----------------------------------------------------------------------- |
380
- | `enforce` | No | `true` | When `true`, `node .agents/scripts/bootstrap.js` calls `applyBranchProtection(...)`. |
381
- | `requiredChecks` | No | `[]` | Array of `{ name, cmd[] }` entries used both as required-status-check expectations on the PR and as local close-validation gate invocations. |
382
-
383
- Each `requiredChecks` entry takes the shape:
384
-
385
- ```jsonc
386
- { "name": "lint", "cmd": ["npm", "run", "lint"] }
387
- ```
388
-
389
- ### `github.mergeMethods`
390
-
391
- Repository-level merge-method allowlist applied by bootstrap.
392
-
393
- | Field | Required | Default | Purpose |
394
- | ------------------------ | -------- | ------- | ------------------------------------------------ |
395
- | `allow_squash_merge` | No | `true` | Permit squash merges through the UI / `gh`. |
396
- | `allow_rebase_merge` | No | `false` | Permit rebase merges. |
397
- | `allow_merge_commit` | No | `false` | Permit merge commits. |
398
- | `allow_auto_merge` | No | `true` | Enable auto-merge for the repository. |
399
- | `delete_branch_on_merge` | No | `true` | Delete the source branch after merge. |
400
-
401
- ### `github.notifications`
402
-
403
- | Field | Required | Default | Purpose |
404
- | ----------------- | -------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
405
- | `mentionOperator` | No | `false` | When `true`, friction comments @-mention `operatorHandle` for `medium`-severity dispatches (high always @mentions). |
406
- | `commentEvents` | No | `["state-transition", "story-merged", "operator-message"]` | Allowlist of event names that reach the GitHub ticket comment channel. |
407
- | `webhookEvents` | No | `["state-transition", "story-merged", "story-closing", "operator-message", "merge.unlanded", "merge.flip-failed", "loop.tick"]` | Allowlist of event names that reach `NOTIFICATION_WEBHOOK_URL`. The vocabulary mirrors the events the v2 runtime actually emits through `notify()`. |
408
-
409
- Both fields' enums are pinned in the schema and rejected if extended. To
410
- suppress a channel entirely, set its array to `[]`.
411
-
412
- > **Severity assignment.** Story state transitions fire `medium`. Blockers
413
- > and HITL gates fire `high` (webhook prefix `[Action Required]` when an
414
- > allowlisted blocker event reaches the webhook).
415
-
416
- ---
417
-
418
- ## `planning`
419
-
420
- `/plan` tuning. All fields optional. **Defaults are advisory**, not Story-width
421
- ceilings: the retired `taskSizing.softFiles` / `hardFiles` file-count gates,
422
- `planning.modelCapacity` operator knobs, and `maxTokenBudget` envelope are
423
- gone. Session-mass ceilings live as absolute authored-token constants on
424
- `DEFAULT_MODEL_CAPACITY` in `ticket-validator-sizing.js` (soft 30k / hard 75k).
425
- Soft findings are advisory; the hard ceiling rejects only Spec novels, and
426
- `wide` lifts that rejection. Cohesion / split policy / conflict advisories
427
- remain the primary sizing signal.
428
-
429
- | Field | Required | Default | Purpose |
289
+ ## Field semantics
290
+
291
+ The generated table above together with the exhaustive
292
+ [`agentrc-reference.json`](agentrc-reference.json) is the SSOT for every
293
+ key's name, type, and default. This section adds only the semantics those
294
+ sources cannot capture: accessor gotchas, derived values, extender forms, and
295
+ the operational behaviour behind a handful of keys. It does **not** restate
296
+ names or defaults.
297
+
298
+ ### `project`
299
+
300
+ - **`paths` (required).** All three roots are required — the resolver applies
301
+ no code-level fallback, so a missing value is a validation error with a clear
302
+ `instancePath`. `auditOutputDir` is **derived, not configurable**: it
303
+ resolves to `${tempRoot}/audits` (the destination for every `audit-*` report
304
+ and the audit-suite prompt artifacts). Override `tempRoot` to relocate it.
305
+ - **`commands`.** Each string must be non-empty and pass the shell-injection
306
+ guard (`safeString` disallows `;`, `&`, `|`, backtick, `$`, `<`, `>`).
307
+ `typecheck` is nullable; `null` is the canonical "not applicable for this
308
+ repo" value. Read the block via `getCommands(config)`
309
+ ([`config-resolver.js`](../scripts/lib/config-resolver.js)).
310
+ - **`docsContextFiles`.** Entries are **plain filenames, not globs** — each
311
+ resolves to a single file under `paths.docsRoot`. For the decisions log this
312
+ means the **index** `decisions.md` stays the mandatory-read while per-ADR
313
+ bodies under a `decisions/` directory are link-followed on demand (index-only
314
+ by default; there is no `decisions/*.md` glob). See
315
+ [`documentation-and-adrs`](../skills/core/documentation-and-adrs/SKILL.md).
316
+
317
+ ### `github`
318
+
319
+ - **`operatorHandle`.** Schema-required but per-contributor: the committed
320
+ `.agentrc.json` carries only the `@[USERNAME]` placeholder, which
321
+ [`normalizeOperatorHandle`](../scripts/lib/orchestration/ticket-lease.js)
322
+ resolves to `null`, making the lease guards fail closed until you set your own
323
+ handle locally (see [Per-machine local overrides](#per-machine-local-overrides)).
324
+ `github.owner` / `repo` are shared and stay committed with real values.
325
+ - **`branchProtection.requiredChecks`.** Each `{ name, cmd[] }` entry is used
326
+ **both** as a required-status-check expectation on the PR and as a local
327
+ close-validation gate invocation.
328
+ - **`notifications`.** Both event allowlists filter their channel
329
+ independently; their enums are pinned in the schema. Set an array to `[]` to
330
+ suppress a channel. The severity vocabulary and shipped default allowlists
331
+ are documented under [`SDLC.md` § Notification system](SDLC.md#notification-system).
332
+
333
+ ### `planning`
334
+
335
+ Defaults are **advisory, not Story-width ceilings**. Session-mass ceilings live
336
+ as absolute authored-token constants on `DEFAULT_MODEL_CAPACITY` in
337
+ `ticket-validator-sizing.js` (soft 30k / hard 75k); there is no `maxTokenBudget`
338
+ envelope, and cohesion / split policy / conflict advisories are the primary
339
+ sizing signal. The `codebaseSnapshot` `skinny` tier caps at 250 paths using
340
+ **per-top-level-directory proportional budgeting** (round-robin across matched
341
+ trees) so a large dot-prefixed tree like `.agents/scripts/**` cannot monopolise
342
+ the budget and truncate away the consumer's own source; the shipped `include`
343
+ scans `.agents/scripts/**`, `src/**`, `lib/**`, `app/**`, `packages/**` and
344
+ `exclude` drops `node_modules`, build dirs, and test files. Override `include`
345
+ only when the project's source layout differs.
346
+
347
+ ### `delivery`
348
+
349
+ - **`docsFreshness.paths`.** Files refreshed during the post-PR-merge release
350
+ tagging step.
351
+ - **`quality` accessor gotcha.** Internal callers MUST read the quality block
352
+ via `getQuality(config)` where `config` is the full `resolveConfig()` envelope
353
+ (or any object exposing `delivery.quality.*`). Passing a sub-pick like
354
+ `getQuality({ agentSettings })` silently resolves to framework defaults —
355
+ `agentSettings` is not part of the post-Epic-#2880 resolver output. The same
356
+ applies to `getBaselines(config)`.
357
+ - **`quality.gates.<tier>` common shape.** Every gate shares
358
+ `enabled` / `baselinePath` / `tolerance` (`{ kind, value }`) /
359
+ `floors` (`{ <workspace>: { <metric>: <number> } }`) /
360
+ `components` (`{ <component>: [<glob>] }`). The `crap` / `maintainability` /
361
+ `duplication` `targetDirs` default `["src"]`. The `bundle-size` gate has
362
+ **no** `refreshTag` — there is no scorer to regenerate its baseline, so its
363
+ one-shot refresh is the `BUNDLE_SIZE_REFRESH=1` env var, not a config knob
364
+ (see [`quality-gates.md` § Bundle-size ratchet](quality-gates.md#bundle-size-ratchet--one-shot-refreshacknowledge-story-151)).
365
+ Extend the list-valued gate keys with the deep-merge extender form (see
366
+ [How to extend](#how-to-extend)).
367
+
368
+ #### `delivery.worktreeIsolation` node_modules strategies
369
+
370
+ When `enabled: true`, each Story runs in its own worktree under
371
+ `.worktrees/story-<id>/`, and `nodeModulesStrategy` picks how `node_modules` is
372
+ populated. This is the single home for the strategy trade-offs (README's
373
+ worktree section points here):
374
+
375
+ | Strategy | When to use | Cold-start cost | Notes |
430
376
  | --- | --- | --- | --- |
431
- | `riskHeuristics` | No | (framework list) | Free-form rubric for `risk::high` decisions (informational does not gate runtime). Accepts a plain array or `{ append/prepend }`. |
432
- | `failOnSharedEditors` | No | `false` | When `true`, upgrade shared-editor findings to hard errors. Soft advisory otherwise. |
433
- | `requireExplicitCrossStoryDeps` | No | `false` | When `true`, upgrade implicit cross-Story dep findings to hard errors. Soft advisory otherwise. |
434
- | `failOnRegistryConflicts` | No | `false` | When `true`, upgrade concurrent registry-edit findings to hard errors. Soft advisory otherwise. |
435
- | `failOnLargeFanOut` | No | `false` | When `true`, upgrade delete blast-radius (`fan-out-warning`) findings to hard errors. Soft advisory otherwise. |
436
- | `largeFanOutThreshold` | No | `10` | Call-site count above which a Story that **deletes** a module warns. Not a Story-size limit — it only scores base-branch references to a path marked `assumption: "deletes"`. |
437
- | `crossCuttingRegistries` | No | listener/handler index patterns | Registry path patterns flagged when two Stories edit them concurrently. |
438
- | `navigation` | No | (empty → no-op) | Opt-in reachability gate for route-adding Stories. |
439
-
440
- ### Conflict advisories (`failOn*` / `largeFanOutThreshold`)
441
-
442
- These knobs do **not** force thin Stories. By default every conflict finding
443
- is `soft` (visible to the planner, does not trip re-decompose). Set a
444
- `failOn*` flag only when you want that finding class to become a hard
445
- `errors[]` refusal.
446
-
447
- `largeFanOutThreshold` specifically:
448
-
449
- 1. Looks only at `body.changes[]` entries with `assumption: "deletes"`.
450
- 2. Counts distinct base-branch files that reference the deleted module basename.
451
- 3. Emits `fan-out-warning` when `callSiteCount > largeFanOutThreshold` (default 10).
452
- 4. Stays soft unless `failOnLargeFanOut: true`. Persist can still require an
453
- explicit `--allow-large-fan-out` operator flag for very large delete blasts;
454
- that is separate from Story sizing.
455
-
456
- ### `planning.codebaseSnapshot`
457
-
458
- Controls the codebase-snapshot fetcher that grounds Tech Spec / Story authoring.
459
-
460
- | Field | Required | Default | Purpose |
461
- | -------------------- | -------- | ----------- | ------------------------------------------------------------------------ |
462
- | `tier` | No | `'skinny'` | One of `'skinny'` or `'medium'`. `medium` opts into a richer snapshot. |
463
- | `include` | No | (see below) | Glob patterns whose matches are included in the snapshot. |
464
- | `exclude` | No | (see below) | Glob patterns whose matches are excluded from the snapshot. |
465
- | `recentCommitWindow` | No | `30` | Number of recent commits to summarise in the snapshot header. |
466
-
467
- The shipped `include` default scans `.agents/scripts/**`, `src/**`,
468
- `lib/**`, `app/**`, and `packages/**`; the shipped `exclude` default drops
469
- `node_modules`, `dist`, `build`, `.next`, `.turbo`, `coverage`, and
470
- `*.test.*` / `*.spec.*`. Override only when the project's source layout
471
- differs.
472
-
473
- The `skinny` tier caps its file list at 250 paths. When the include set
474
- matches more than that, the cap is applied with **per-top-level-directory
475
- proportional budgeting** (round-robin across the matched top-level trees)
476
- rather than a flat lexicographic slice — so a large, dot-prefixed tree like
477
- `.agents/scripts/**` can no longer monopolise the budget and truncate away
478
- the consumer's own `src/` / `lib/` source. When `.agents/scripts/**` is the
479
- only matching tree (the Mandrel-repo dogfood case), the round-robin
480
- degenerates to taking the first 250 sorted paths, so that snapshot stays
481
- useful. When truncation occurs, `/plan` emits an operator-visible warning
482
- naming the dropped file count and suggesting `tier: "medium"` and/or a
483
- narrowed `include`. Opt into the richer `medium` tier or narrow `include`
484
- if the partial skinny view is insufficient.
485
-
486
- ### `planning.navigation`
487
-
488
- Opt-in. When `routeGlobs` is absent or empty the reachability gate is a
489
- silent no-op. When configured, a Story that adds a matching route path is
490
- expected to also touch a `navRegistry` token.
491
-
492
- | Field | Required | Default | Purpose |
493
- | ------------- | -------- | ------- | ------- |
494
- | `routeGlobs` | No | `[]` | Globs marking user-facing route paths. |
495
- | `navRegistry` | No | `[]` | Tokens identifying the nav-registry SSOT. |
496
-
497
- ---
498
-
499
- ## `delivery`
500
-
501
- `/deliver` and `/deliver` tuning. All sub-blocks are optional and
502
- fall back to documented defaults (or are no-ops when omitted).
503
-
504
- ### `delivery.execution`
505
-
506
- | Field | Required | Default | Purpose |
507
- | ----------- | -------- | ---------- | ------------------------------------------------------------------ |
508
- | `timeoutMs` | No | `600000` | Per-spawn timeout (ms) for child processes the framework launches. |
509
-
510
- > Session-mass ceilings live as absolute authored-token constants on
511
- > `DEFAULT_MODEL_CAPACITY` in `ticket-validator-sizing.js` (soft 30k / hard
512
- > 75k). There is no `maxTokenBudget` envelope — cohesion is the primary
513
- > sizing signal; the numeric backstop only catches Spec novels.
514
-
515
- ### `delivery.docsFreshness`
516
-
517
- | Field | Required | Default | Purpose |
518
- | ------- | -------- | ------- | ------------------------------------------------------------- |
519
- | `paths` | No | `[]` | Files refreshed during the post-PR-merge release tagging step. |
520
-
521
- ### `delivery.deliverRunner`
522
-
523
- | Field | Required | Default | Purpose |
524
- | --------------------------- | -------- | ------- | ------------------------------------------------ |
525
- | `concurrencyCap` | No | `3` | Max ready Stories dispatched by `/deliver` at once. |
526
-
527
- ### `delivery.worktreeIsolation`
528
-
529
- Story-level worktree isolation. When `enabled: true`, `/deliver` runs
530
- each Story inside `.worktrees/story-<id>/` instead of moving the main
531
- checkout's HEAD.
532
-
533
- | Field | Required | Default | Purpose |
534
- | ----------------------- | --------------- | ---------------- | ----------------------------------------------------------- |
535
- | `enabled` | No | `true` | Master switch. |
536
- | `root` | Conditional | `.worktrees` | Required when `enabled: true`. Worktree parent directory. |
537
- | `nodeModulesStrategy` | No | `clone` (darwin/linux); `per-worktree` (Windows) | One of `per-worktree`, `clone`, `symlink`, `pnpm-store`. `clone` copy-on-write (reflink/clonefile) clones the donor's `node_modules` and skips the per-tree install on a byte-exact lockfile match, falling back to `per-worktree` on any failure. |
538
- | `primeFromPath` | No | `null` | Optional source path used to prime `node_modules`. |
539
- | `allowSymlinkOnWindows` | No | `false` | Permit symlink strategy on Windows (requires admin/dev mode). |
540
- | `reapOnSuccess` | No | `true` | Reap the worktree after a successful Story close. |
541
- | `bootstrapFiles` | No | `[".env", ".mcp.json", ".agentrc.local.json", ".agents/instructions.local.md"]` | Untracked files copied into each new worktree (local overrides included). |
542
-
543
- ### `delivery.signals`
544
-
545
- Friction-detector thresholds consumed by progress-signal listeners.
546
-
547
- | Field | Required | Default | Purpose |
548
- | ---------------------- | -------- | ------- | ------------------------------------------------------------------------ |
549
- | `rework.editsPerFile` | No | (none) | Edits to the same file before a rework signal fires. |
550
- | `retry.repeatCount` | No | (none) | Identical retried commands before a retry signal fires. |
551
-
552
- ### `delivery.quality`
553
-
554
- Quality-gate configuration. Lives under `delivery.quality` and uses the
555
- uniform `gates.<tier>` shape (seven gates: `lint`, `coverage`, `crap`,
556
- `maintainability`, `mutation`, `lighthouse`, `bundle-size`) introduced by
557
- Epic #1720 Story #1737.
558
-
559
- #### `delivery.quality.gateScoping`
560
-
561
- | Field | Required | Default | Purpose |
562
- | --------- | -------- | -------- | ------------------------------------------------------------------------------ |
563
- | `scope` | No | `'full'` | One of `'diff'` (only files changed vs `diffRef`) or `'full'` (all targetDirs). |
564
- | `diffRef` | No | (none) | Ref used to compute the diff when `scope: 'diff'` (e.g. `main`). |
565
-
566
- #### `delivery.quality.gates.<tier>` — common shape
567
-
568
- Every gate shares the same envelope:
569
-
570
- | Field | Required | Purpose |
571
- | -------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
572
- | `enabled` | No | Master switch. `false` makes all three gate sites self-skip for this tier. |
573
- | `baselinePath` | No | Path to the per-tier ratchet baseline file (e.g. `baselines/crap.json`). |
574
- | `tolerance` | No | `{ kind: 'absolute' \| 'percent', value: <number> }` — slack permitted when comparing scores against baseline. |
575
- | `floors` | No | `{ <workspace>: { <metric>: <number> } }` — absolute floors below which the gate fails regardless of baseline. |
576
- | `components` | No | `{ <component>: [<glob>, ...] }` — optional component-level grouping for breakdown reporting. |
577
-
578
- Tier-specific knobs:
579
-
580
- ##### `gates.lint`
581
-
582
- No tier-specific knobs beyond the common shape.
583
-
584
- ##### `gates.coverage`
585
-
586
- | Field | Required | Default | Purpose |
587
- | -------------- | -------- | -------------------------------- | -------------------------------------------------------- |
588
- | `coveragePath` | No | `coverage/coverage-final.json` | Per-method coverage artifact consumed by the gate. |
589
- | `timeoutMs` | No | `600000` | Wall clock (ms) for `npm run test:coverage` spawned by `coverage-capture.js`. SIGKILL → exit 124 (GNU `timeout` convention) so close-validation can branch on hang-vs-failure. |
590
-
591
- > **Canonical accessor.** Internal callers MUST read this block via
592
- > `getQuality(config)` where `config` is the full envelope returned by
593
- > `resolveConfig()` (or any object that exposes `delivery.quality.*`).
594
- > Passing a sub-pick such as `getQuality({ agentSettings })` silently
595
- > resolves to framework defaults — `agentSettings` is not part of the
596
- > post-Epic-#2880 resolver output and `getQuality` reads only
597
- > `config?.delivery?.quality`. The same applies to `getBaselines(config)`.
598
-
599
- ##### `gates.crap`
600
-
601
- | Field | Required | Default | Purpose |
602
- | ------------------- | ----------- | --------------------------- | ---------------------------------------------------------------- |
603
- | `targetDirs` | No | `["src"]` | Source dirs to score. Accepts list or `{ append/prepend }` form. |
604
- | `newMethodCeiling` | No | `30` | Max CRAP score allowed for methods absent from the baseline. |
605
- | `requireCoverage` | No | `true` | When `true`, methods without coverage are skipped (not failed). |
606
- | `friction.markerKey`| No | `crap-baseline-regression` | Friction-log marker for regressions. |
607
- | `refreshTag` | No | `baseline-refresh:` | Subject prefix the refresh-guardrail expects on baseline-only commits. |
608
- | `refreshTimeoutMs` | No | `60000` | Bounded timeout (ms) for `npm run crap:update` in the baseline-attribution refresh path. SIGKILL → exit 124 → Story `agent::blocked`. |
609
-
610
- ##### `gates.maintainability`
611
-
612
- | Field | Required | Default | Purpose |
613
- | ------------------ | -------- | ----------- | ---------------------------------------------------------------- |
614
- | `targetDirs` | No | `["src"]` | Source dirs to score. Accepts list or `{ append/prepend }` form. |
615
- | `refreshTimeoutMs` | No | `60000` | Bounded timeout (ms) for `npm run maintainability:update` in the baseline-attribution refresh path. |
616
-
617
- ##### `gates.mutation`
618
-
619
- | Field | Required | Default | Purpose |
620
- | -------------------- | -------- | ------- | -------------------------------------------------------- |
621
- | `strykerConfigPath` | No | `null` | Path to the Stryker config the mutation gate consumes. |
622
-
623
- ##### `gates.lighthouse`
624
-
625
- | Field | Required | Default | Purpose |
626
- | --------- | -------- | ------- | ---------------------------------------------------------------------------------------- |
627
- | `baseUrl` | No | `null` | Base URL Lighthouse audits. |
628
- | `routes` | No | `[]` | Array of `{ path, formFactor? }` entries, where `formFactor` is `'mobile'` or `'desktop'`. |
629
-
630
- ##### `gates.bundle-size`
631
-
632
- | Field | Required | Default | Purpose |
633
- | --------- | -------- | ------- | ---------------------------------------------------------------------------------------- |
634
- | `bundles` | No | `[]` | Array of `{ name, path, limit }` entries (e.g. `{ "name": "app", "path": "dist/app.js", "limit": "100kB" }`). |
635
-
636
- Unlike `crap` / `maintainability`, this gate has no `refreshTag` config
637
- field — there is no scorer to regenerate the baseline from source, so the
638
- one-shot refresh/acknowledge mechanism is an env var, not a config knob.
639
- See [`.agents/docs/quality-gates.md` § Bundle-size ratchet](quality-gates.md#bundle-size-ratchet--one-shot-refreshacknowledge-story-151)
640
- for `BUNDLE_SIZE_REFRESH=1` usage.
641
-
642
- #### `delivery.quality.formatAutofix`
643
-
644
- | Field | Required | Default | Purpose |
645
- | ----------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
646
- | `timeoutMs` | No | (none) | Bounded timeout (ms) for the close-time `npx biome format --write` spawn. SIGKILL maps to exit 124 → Story `agent::blocked`. |
647
-
648
- #### `delivery.quality.codingGuardrails`
649
-
650
- | Field | Required | Default | Purpose |
651
- | ---------------------- | -------- | ------- | ------------------------------------------------------------------ |
652
- | `cyclomaticFlag` | No | (none) | Cyclomatic-complexity value at which the engineer should refactor. |
653
- | `cyclomaticMustFix` | No | (none) | Cyclomatic-complexity value that hard-fails the gate. |
654
- | `requireSiblingTest` | No | (none) | When `true`, a new function requires a sibling test file. |
655
-
656
- #### `delivery.quality.autoRefresh`
657
-
658
- Controls Story-close auto-baseline-refresh for gated metrics.
659
-
660
- | Field | Required | Default | Purpose |
661
- | ------------- | -------- | ------- | -------------------------------------------------------- |
662
- | `enabled` | No | (none) | Master switch. |
663
- | `crapJumpCap` | No | (none) | Max CRAP jump the auto-refresher will absorb before failing. |
664
- | `scope` | No | (none) | One of `'diff'` or `'full'`. |
665
-
666
- #### `delivery.quality.baselineEpsilon`
667
-
668
- Per-kind epsilon (introduced by Story #1964). Sub-epsilon row deltas resolve
669
- to prior bytes so cross-environment variance does not rewrite the on-disk
670
- baseline.
671
-
672
- | Field | Required | Default | Purpose |
673
- | ----------------- | -------- | ------- | -------------------------------------- |
674
- | `maintainability` | No | (none) | Epsilon for maintainability rows. |
675
- | `crap` | No | (none) | Epsilon for CRAP rows. |
676
- | `coverage` | No | (none) | Epsilon for coverage rows. |
677
- | `mutation` | No | (none) | Epsilon for mutation rows. |
678
- | `lint` | No | (none) | Epsilon for lint rows. |
679
- | `lighthouse` | No | (none) | Epsilon for Lighthouse rows. |
680
- | `bundle-size` | No | (none) | Epsilon for bundle-size rows. |
681
-
682
- ### `delivery.codeReview`
683
-
684
- Configuration block for the code-review pipeline that runs during Story
685
- delivery (`helpers/deliver-story` / `runCodeReview()`). Select providers
686
- via the `providers[]` chain (generated table above); when unset or empty,
687
- the factory defaults to `[{ name: "native" }]`. See
688
- [`.agents/README.md` § Code review providers](../README.md#code-review-providers-pluggable-chain)
689
- for chain-entry fields (`name`, `scopes`, `optional`, `manualPrompt`, `when`).
690
-
691
- | Field | Required | Default | Purpose |
692
- | ------------------ | -------- | ------- | -------------------------------------------------------------------- |
693
- | `providers` | No | `[{ name: "native" }]` | Review-provider chain. Inline adapters merge `Finding[]`; manual-prompt entries append suggestions. |
694
- | `providerConfig` | No | `{}` | Optional escape hatch for adapter-specific configuration. Reserved; no documented keys yet. |
695
- | `maxFixAttempts` | No | `3` | Max auto-fix retry attempts per finding. `0` disables auto-fix. |
696
- | `maxFixScopeFiles` | No | `5` | Max file count a single auto-fix may modify before escalating to `agent::blocked`. |
697
- | `autoFixSeverity` | No | `"medium"` | Severity threshold for on-branch remediation (`medium` or `high`). |
698
-
699
- #### Dual-scope fix budget
700
-
701
- `maxFixAttempts` and `maxFixScopeFiles` are enforced during the risk-routed
702
- Story review ceremony using the same configured values for every Story in
703
- a `/deliver` run. Setting `maxFixAttempts: 0` disables auto-fix; there is
704
- no per-Story override.
705
-
706
- ### `delivery.feedbackLoop`
707
-
708
- | Field | Required | Default | Purpose |
709
- | ----------------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
710
- | `auditResultsAutoFile` | No | `true` | When `true`, the deliver finalize path auto-files non-blocking findings from the unified `verification-results` comment as follow-up issues routed by source classification. (The former `codeReviewAutoFile` key was retired with its graduator when Story #4411 unified the pass; a config carrying it fails validation.) |
711
- | `retroProposals` | No | `true` | When `true`, auto-file the retro's actionable routed proposals as GitHub follow-up issues (Story #4418). |
377
+ | `clone` | **Default on darwin/linux** copy-on-write clone of donor `node_modules`. | Near-zero on reflink FS. | Falls back to `per-worktree` on unsupported/cross-volume FS. |
378
+ | `per-worktree` | Default on Windows; safe fallback everywhere. | Full `npm ci` per Story. | Independent `node_modules` per worktree. |
379
+ | `symlink` | npm/yarn repos wanting the fast path. **Opt-in.** | Near-zero. | Junctions a donor `node_modules`; refuses on Windows unless `allowSymlinkOnWindows`. |
380
+ | `pnpm-store` | pnpm repos. **Opt-in.** | Fast (store-backed). | `pnpm install --frozen-lockfile` against the shared store. |
381
+
382
+ For `symlink`, also set `primeFromPath` (relative path to the donor worktree
383
+ whose `node_modules/` is reused `"."` = the root checkout, which must already
384
+ have `node_modules/`; `single-story-init.js` pre-checks this) and, on Windows,
385
+ `allowSymlinkOnWindows: true` (junctions, no admin rights). `single-story-init.js`
386
+ then skips `npm ci` and junctions/symlinks the donor's `node_modules`.
387
+
388
+ #### `delivery.codeReview` provider chain
389
+
390
+ `providers[]` is iterated in declaration order (unset/empty
391
+ `[{ name: "native" }]`). Beyond the field list in the generated table: `scopes`
392
+ defaults to both `story` and `epic`; `when` is a label predicate (`when.label`
393
+ / `when.labelAny`) that silently skips the entry when false; `manualPrompt`
394
+ entries contribute a one-line suggestion and do **not** affect severity counts
395
+ or the `halted` gate. **Cross-runtime contract:** manual-prompt providers emit
396
+ Markdown only and MUST NEVER throw under any host; inline providers that shell
397
+ out to a host-specific binary (e.g. `security-review`
398
+ `claude --print /security-review`) SHOULD be declared `optional: true` so
399
+ non-Claude consumers can pin the same `.agents/` version unmodified. The fix
400
+ budget (`maxFixAttempts` / `maxFixScopeFiles`) uses the same values for every
401
+ Story in a run — there is no per-Story override.
402
+
403
+ #### `delivery.feedbackLoop` — verification-results auto-graduation
404
+
405
+ `auditResultsAutoFile` auto-graduates surviving non-blocking findings from the
406
+ unified `verification-results` comment into follow-up issues; the graduator
407
+ embeds a content-derived idempotency marker so re-runs skip findings that
408
+ already have an issue (re-enabling after a manual-triage window is safe). The
409
+ former `codeReviewAutoFile` key was retired when Story #4411 unified the pass
410
+ a config carrying it fails validation; delete it.
712
411
 
713
412
  ---
714
413
 
@@ -765,18 +464,12 @@ the lint ratchet, and the CRAP/MI gates.
765
464
  | `baselines/maintainability.json` | `update-maintainability-baseline.js` | `npm run maintainability:update` |
766
465
  | `baselines/bundle-size.json` | consumer's own build/measure step | Commit the build's measured sizes; for an intentional growth, run the check with `BUNDLE_SIZE_REFRESH=1` (see [Bundle-size ratchet](quality-gates.md#bundle-size-ratchet--one-shot-refreshacknowledge-story-151)) |
767
466
 
768
- These files are the contract. They are read by every gate (Story close, push
769
- hook, CI) and are regenerated only via tagged `baseline-refresh:` commits
770
- with a non-empty body. The convention is operator-enforced; see the CRAP
771
- section of [`quality-gates.md`](quality-gates.md) for the policy.
772
-
773
- Paths are configured in `delivery.quality.gates.<tier>.baselinePath`. The
774
- default values match the canonical layout above; override only when a
775
- project genuinely stores baselines elsewhere.
776
-
777
- The `.agents/state/` directory itself is created on demand by the progress
778
- reporter; the framework does not require it to exist ahead of time and does
779
- not commit its contents.
467
+ These files are the contract read by every gate (Story close, push hook, CI)
468
+ and regenerated only via tagged `baseline-refresh:` commits with a non-empty
469
+ body (see [`quality-gates.md`](quality-gates.md) for the policy). Paths are
470
+ configured in `delivery.quality.gates.<tier>.baselinePath`, defaulting to the
471
+ layout above. The `.agents/state/` directory is created on demand by the
472
+ progress reporter and is not committed.
780
473
 
781
474
  ---
782
475
 
@@ -999,72 +692,13 @@ move and the allowlist response in the same diff.
999
692
 
1000
693
  ---
1001
694
 
1002
- ## CLI subcommand quick-reference
1003
-
1004
- `mandrel --help` prints the full subcommand list. Each subcommand that
1005
- mutates state supports `--dry-run` to preview without writing. The table
1006
- below covers every dispatch-visible subcommand:
1007
-
1008
- | Subcommand | What it does | Key flags |
1009
- | ---------- | ------------ | --------- |
1010
- | `init` | Install and configure mandrel in the current project. | `--assume-yes`, `--skip-github`, `--dry-run` |
1011
- | `sync` | Re-materialize `.agents/` from the installed package payload. | `--dry-run`, `--force` |
1012
- | `sync-commands` | Rebuild `.claude/commands/` from `.agents/workflows/`. | — |
1013
- | `doctor` | Run readiness checks and report remedies. | — |
1014
- | `update` | Upgrade mandrel to the newest published version. | `--dry-run`, `--install-cmd` |
1015
- | `migrate` | Apply version-keyed migrations for a version range. | `--from`, `--to`, `--dry-run` |
1016
- | `explain` | Print resolved config values with sources. | `--json` |
1017
- | `uninstall` | Reverse a recorded install using the install ledger. | `--include-github`, `--dry-run` |
1018
-
1019
- ### `mandrel explain`
1020
-
1021
- Prints every resolved config key — its effective value, its source layer
1022
- (`[agentrc]` or `[default]`), and a one-line description. Secret-shaped
1023
- values are shown as `<redacted>`. Useful when debugging unexpected behavior
1024
- caused by config layering.
1025
-
1026
- ```bash
1027
- mandrel explain # human-readable report
1028
- mandrel explain --json # JSON report for scripting
1029
- ```
1030
-
1031
- ### `mandrel sync-commands`
1032
-
1033
- Regenerates the flat `.claude/commands/` tree from `.agents/workflows/`. Runs
1034
- automatically at install time (via `prepare`) and as part of `mandrel
1035
- sync`/`update`; manual invocations are only needed when the commands/ tree is
1036
- manually deleted or edited by hand. Refuses to project when the materialized
1037
- `.agents/` tree doesn't match the running CLI's own version.
1038
-
1039
- ```bash
1040
- mandrel sync-commands
1041
- ```
1042
-
1043
- ### `mandrel sync-agents`
1044
-
1045
- Regenerates the flat `.claude/agents/` tree from `.agents/agents/` — the
1046
- role-scoped boot contexts `delivery.routing.roleScopedAgents` (default
1047
- `true`) dispatches spawns against. Same wiring and version-match refusal as
1048
- `mandrel sync-commands` above.
1049
-
1050
- ```bash
1051
- mandrel sync-agents
1052
- ```
695
+ ## CLI subcommand reference
1053
696
 
1054
- ### `mandrel uninstall`
1055
-
1056
- Reverses a recorded install using the install ledger
1057
- (`.agents/.install-manifest.json`). Restoration is marker-based and
1058
- non-destructive: operator-authored content that pre-existed the install
1059
- is preserved; only install-created files and framework additions are removed.
1060
- GitHub-side state is never acted on automatically; it is surfaced as a
1061
- manual checklist.
1062
-
1063
- ```bash
1064
- mandrel uninstall # reverse all local mutations
1065
- mandrel uninstall --dry-run # preview without writing
1066
- mandrel uninstall --include-github # acknowledge GitHub-side follow-ups
1067
- ```
697
+ The `mandrel` CLI subcommands (`init`, `sync`, `sync-commands`, `sync-agents`,
698
+ `doctor`, `update`, `migrate`, `explain`, `uninstall`) — what each does, its
699
+ key flags, and the per-subcommand notes are documented once in
700
+ [`.agents/README.md` § CLI subcommand reference](../README.md#cli-subcommand-reference).
701
+ Run `mandrel --help` for the live list.
1068
702
 
1069
703
  ---
1070
704