mandrel 1.91.0 → 1.92.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 (109) hide show
  1. package/.agents/README.md +5 -5
  2. package/.agents/docs/SDLC.md +75 -107
  3. package/.agents/docs/configuration.md +1 -1
  4. package/.agents/docs/quality-gates.md +8 -7
  5. package/.agents/docs/workflows.md +4 -10
  6. package/.agents/instructions.md +2 -2
  7. package/.agents/schemas/agentrc.schema.json +1 -1
  8. package/.agents/schemas/lifecycle/README.md +2 -1
  9. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  10. package/.agents/schemas/risk-verdict.schema.json +14 -1
  11. package/.agents/scripts/README.md +0 -46
  12. package/.agents/scripts/analyze-execution.js +76 -1
  13. package/.agents/scripts/check-doc-links.js +3 -0
  14. package/.agents/scripts/check-workflow-cli-lint.js +208 -0
  15. package/.agents/scripts/epic-audit-recheck.js +1 -1
  16. package/.agents/scripts/epic-plan-clarity.js +16 -6
  17. package/.agents/scripts/epic-plan-decompose.js +37 -59
  18. package/.agents/scripts/epic-plan-healthcheck.js +39 -6
  19. package/.agents/scripts/epic-plan-spec.js +41 -168
  20. package/.agents/scripts/epic-reconcile.js +7 -1
  21. package/.agents/scripts/lib/Logger.js +4 -4
  22. package/.agents/scripts/lib/audit-suite/index.js +4 -5
  23. package/.agents/scripts/lib/audit-suite/runner.js +6 -4
  24. package/.agents/scripts/lib/audit-suite/selector.js +2 -3
  25. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  26. package/.agents/scripts/lib/command-header.js +20 -0
  27. package/.agents/scripts/lib/config/github.js +1 -3
  28. package/.agents/scripts/lib/config-settings-schema.js +4 -3
  29. package/.agents/scripts/lib/degraded-mode.js +1 -1
  30. package/.agents/scripts/lib/label-constants.js +18 -3
  31. package/.agents/scripts/lib/label-taxonomy.js +14 -3
  32. package/.agents/scripts/lib/mutation/baseline-snapshot.js +7 -6
  33. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +18 -8
  34. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -4
  35. package/.agents/scripts/lib/orchestration/docs-digest.js +2 -2
  36. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +4 -0
  37. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +22 -12
  38. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +6 -1
  39. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +18 -2
  40. package/.agents/scripts/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
  41. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
  42. package/.agents/scripts/lib/orchestration/plan-context.js +510 -0
  43. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
  44. package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
  45. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
  46. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +978 -0
  48. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +191 -0
  49. package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
  50. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
  51. package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
  52. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  53. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
  54. package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
  55. package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
  56. package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
  57. package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
  58. package/.agents/scripts/plan-context.js +186 -0
  59. package/.agents/scripts/plan-critics.js +227 -0
  60. package/.agents/scripts/plan-persist.js +383 -0
  61. package/.agents/scripts/pr-watch-with-update.js +8 -7
  62. package/.agents/scripts/run-lint.js +10 -11
  63. package/.agents/scripts/story-plan.js +19 -7
  64. package/.agents/scripts/sync-claude-commands.js +10 -5
  65. package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
  66. package/.agents/workflows/audit-documentation.md +5 -7
  67. package/.agents/workflows/audit-lighthouse.md +1 -0
  68. package/.agents/workflows/audit-security.md +1 -0
  69. package/.agents/workflows/audit-to-stories.md +16 -5
  70. package/.agents/workflows/git-deliver.md +76 -243
  71. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  72. package/.agents/workflows/helpers/code-review.md +7 -6
  73. package/.agents/workflows/helpers/deliver-epic.md +5 -4
  74. package/.agents/workflows/helpers/plan-epic-reference.md +109 -96
  75. package/.agents/workflows/helpers/plan-epic.md +246 -999
  76. package/.agents/workflows/helpers/plan-story.md +14 -14
  77. package/.agents/workflows/helpers/scope-triage-gate.md +6 -4
  78. package/.agents/workflows/helpers/single-story-deliver.md +5 -4
  79. package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
  80. package/.agents/workflows/mandrel-update.md +144 -466
  81. package/.agents/workflows/plan.md +73 -143
  82. package/docs/CHANGELOG.md +13 -0
  83. package/lib/cli/registry.js +21 -3
  84. package/package.json +1 -1
  85. package/.agents/schemas/loop-unit.schema.json +0 -70
  86. package/.agents/scripts/assert-branch.js +0 -81
  87. package/.agents/scripts/check-loop-units.js +0 -204
  88. package/.agents/scripts/detect-merges.js +0 -111
  89. package/.agents/scripts/git-pr-quality-gate.js +0 -205
  90. package/.agents/scripts/git-rebase-and-resolve.js +0 -234
  91. package/.agents/scripts/hierarchy-gate.js +0 -192
  92. package/.agents/scripts/hydrate-context.js +0 -179
  93. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
  94. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
  95. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
  96. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
  97. package/.agents/scripts/loc-delta.js +0 -205
  98. package/.agents/scripts/render-manifest.js +0 -143
  99. package/.agents/scripts/run-audit-suite.js +0 -97
  100. package/.agents/scripts/select-audits.js +0 -155
  101. package/.agents/scripts/update-mutation-baseline.js +0 -189
  102. package/.agents/workflows/explain.md +0 -118
  103. package/.agents/workflows/git-merge-pr.md +0 -377
  104. package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
  105. package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
  106. package/.agents/workflows/loops/README.md +0 -65
  107. package/.agents/workflows/loops/fix-failing-tests.md +0 -74
  108. package/.agents/workflows/loops/nightly-audit.md +0 -81
  109. package/.agents/workflows/loops/watch-ci.md +0 -68
@@ -1,191 +1,64 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /* node:coverage ignore file */
4
+
3
5
  /**
4
- * epic-plan-spec.js — Phase 7 (spec) entry point for the split planning flow.
5
- *
6
- * Two idempotent modes and a single-purpose label lifecycle:
6
+ * epic-plan-spec.js — RETIRED delegate CLI (Epic #4474, PR7).
7
7
  *
8
- * 1. --emit-context Prints the planner authoring context (Epic body,
9
- * scraped project docs, recommended system prompts) as
10
- * JSON. The authoring middle is the
11
- * `epic-plan-spec-author` Skill (see
12
- * `.agents/skills/core/epic-plan-spec-author/SKILL.md`),
13
- * which consumes this envelope and writes the Tech Spec
14
- * markdown file.
8
+ * The 12-phase plan pipeline collapsed to context author → persist:
15
9
  *
16
- * 2. (default) Given author-provided Tech Spec and risk-verdict
17
- * files, validates the risk verdict against
18
- * `risk-verdict.schema.json`, derives the planningRisk
19
- * envelope, folds the authored content into managed
20
- * sections of the Epic body (Story #4324 — no separate
21
- * context tickets), records the verdict as a
22
- * `risk-verdict` structured comment, flips the Epic to
23
- * `agent::review-spec`, and upserts the
24
- * `epic-plan-state` structured comment.
10
+ * - `--emit-context` moved to `plan-context.js` (single authoring
11
+ * envelope: Epic body + docs digest + codebase snapshot + duplicate
12
+ * search + clarity + system prompts).
13
+ * - The persist half (section gate, risk verdict, managed sections,
14
+ * checkpoint) moved to `plan-persist.js` (single GitHub-write surface).
25
15
  *
26
- * --force regenerates the existing Tech Spec.
27
- * --steal forcibly transfers a foreign Epic-lease claim (the plan-lease guard
28
- * fails closed, so any foreign assignee blocks the run unless stolen).
16
+ * This file is a **re-export shim only** — it carries external importers of
17
+ * the historic named-export surface one more release (#4474 design §6 PR7
18
+ * risk note) and is deleted in the next release. Internal consumers import
19
+ * the phase modules directly; do not add new imports of this file.
29
20
  *
30
- * Exit codes:
31
- * 0 — phase complete, Epic is now `agent::review-spec`.
32
- * 1 — fatal error (see stderr).
33
- *
34
- * The phase implementations live under
35
- * `lib/orchestration/epic-plan-spec/phases/`. This file is now a thin CLI
36
- * entry that wires argv → phases.
21
+ * Invoking it as a CLI is refused with a pointer to the successor CLIs.
37
22
  */
38
23
 
39
- // Fail-fast if the framework's runtime deps are not installed must be the
40
- // first import so the check runs before any third-party-importing sibling
41
- // module is evaluated (Story #3432).
42
- import './lib/runtime-deps/ensure-installed.js';
43
- import { readFile } from 'node:fs/promises';
44
- import {
24
+ // cli-opt-out: retired delegate shim (Epic #4474 PR7)deliberately
25
+ // refuses CLI execution with a pointer to plan-context.js/plan-persist.js
26
+ // instead of wiring runAsCli around a dead main().
27
+ import { pathToFileURL } from 'node:url';
28
+
29
+ export {
45
30
  forkAndCommitEpicSnapshot,
46
31
  forkMainToEpic,
47
32
  } from './lib/baseline-snapshot.js';
48
-
49
- // Re-exported so the historic import path
50
- // (`epic-plan-spec.js#forkAndCommitEpicSnapshot`) and existing tests keep
51
- // working after Story #1585 relocated the wrapper into the lower-level
52
- // `lib/baseline-snapshot.js` module. `forkMainToEpic` is also re-exported
53
- // for the same reason.
54
- export { forkAndCommitEpicSnapshot, forkMainToEpic };
55
-
56
- import { runAsCli } from './lib/cli-utils.js';
57
- import {
58
- PROJECT_ROOT,
59
- resolveConfig,
60
- validateOrchestrationConfig,
61
- } from './lib/config-resolver.js';
62
- import { Logger, routeAllOutputToStderr, STDERR_LOGGER } from './lib/Logger.js';
63
- import {
33
+ export {
64
34
  buildAuthoringContext,
65
35
  resolveMemoryDir,
66
36
  } from './lib/orchestration/epic-plan-spec/phases/authoring-context.js';
67
- import { parseEpicPlanSpecArgs } from './lib/orchestration/epic-plan-spec/phases/cli-args.js';
68
- import { drainPendingCleanupAtBoot } from './lib/orchestration/epic-plan-spec/phases/drain.js';
69
- import {
37
+ export { drainPendingCleanupAtBoot } from './lib/orchestration/epic-plan-spec/phases/drain.js';
38
+ export {
70
39
  planEpic,
71
40
  resolveAcceptancePersistence,
72
41
  } from './lib/orchestration/epic-plan-spec/phases/plan-epic.js';
73
- import {
74
- loadRiskVerdict,
75
- validateRiskVerdict,
76
- } from './lib/orchestration/epic-plan-spec/phases/risk-verdict.js';
77
- import { runSpecPhase } from './lib/orchestration/epic-plan-spec/phases/run-spec-phase.js';
78
- import { runSpecFreshnessCheck } from './lib/orchestration/epic-plan-spec/phases/spec-freshness.js';
79
- import { resolveReviewRouting } from './lib/orchestration/plan-review-routing.js';
80
- import { createProvider } from './lib/provider-factory.js';
81
-
82
- // Re-exports for stable public API: tests and external callers import these
83
- // from `epic-plan-spec.js`. The implementations live in `phases/`.
84
42
  export {
85
- buildAuthoringContext,
86
- drainPendingCleanupAtBoot,
87
43
  loadRiskVerdict,
88
- planEpic,
89
- resolveAcceptancePersistence,
90
- resolveMemoryDir,
91
- resolveReviewRouting,
92
- runSpecFreshnessCheck,
93
- runSpecPhase,
94
44
  validateRiskVerdict,
95
- };
96
-
97
- async function main() {
98
- const { values, epicId } = parseEpicPlanSpecArgs();
99
-
100
- let config;
101
- let settings;
102
- try {
103
- config = resolveConfig();
104
- // `settings` retains the legacy bag shape used by buildAuthoringContext
105
- // and friends: `{ baseBranch, paths, planning, ... }`. Build it from the
106
- // canonical blocks rather than the legacy shim.
107
- settings = {
108
- baseBranch: config.project?.baseBranch,
109
- paths: config.project?.paths,
110
- planning: config.planning,
111
- docsContextFiles: config.project?.docsContextFiles,
112
- };
113
- validateOrchestrationConfig(config);
114
- } catch (err) {
115
- throw new Error(`Config schema validation failed:\n${err.message}`);
116
- }
117
- const provider = createProvider(config);
118
-
119
- const emitContext = values['emit-context'];
120
- // Story #2278 — in --emit-context mode stdout is reserved for the JSON
121
- // envelope. Flip every Logger sink that could land on stdout to stderr
122
- // *before* any pipeline code runs (drainPendingCleanupAtBoot,
123
- // buildAuthoringContext → ensureDocsDigest — Story #4433 cut the digest
124
- // build over from the old buildDocsContext/scrapeProjectDocs full-content
125
- // read path), so a captured file is unconditionally parseable by
126
- // `JSON.parse`.
127
- if (emitContext) routeAllOutputToStderr();
128
-
129
- try {
130
- await drainPendingCleanupAtBoot({
131
- repoRoot: PROJECT_ROOT,
132
- config,
133
- provider,
134
- // In --emit-context mode stdout is reserved for the JSON envelope;
135
- // route every drain/sweep log line through stderr so the captured
136
- // file is unconditionally parseable.
137
- logger: emitContext ? STDERR_LOGGER : undefined,
138
- });
139
- } catch (err) {
140
- Logger.warn(
141
- `[epic-plan-spec] pending-cleanup drain skipped: ${err.message}`,
142
- );
143
- }
144
-
145
- if (emitContext) {
146
- const ctx = await buildAuthoringContext(epicId, provider, settings, {
147
- fullContext: values['full-context'],
148
- github: config.github ?? null,
149
- });
150
- const json = values.pretty
151
- ? JSON.stringify(ctx, null, 2)
152
- : JSON.stringify(ctx);
153
- process.stdout.write(`${json}\n`);
154
- return;
155
- }
156
-
157
- if (!values['tech-spec'] || !values['risk-verdict']) {
158
- throw new Error(
159
- 'Missing --tech-spec and/or --risk-verdict file paths. (Use --emit-context first to gather authoring context; the epic-plan-spec-author Skill writes all artifacts including risk-verdict.json.)',
160
- );
161
- }
162
-
163
- // Read + schema-validate the planner-authored risk verdict before any
164
- // GitHub mutation: a malformed verdict fails closed here (Epic #3865).
165
- const riskVerdict = loadRiskVerdict(values['risk-verdict']);
166
-
167
- const readPromises = [readFile(values['tech-spec'], 'utf8')];
168
- if (values['acceptance-table']) {
169
- readPromises.push(readFile(values['acceptance-table'], 'utf8'));
170
- }
171
- const [techSpecContent, acceptanceSpecContent = null] =
172
- await Promise.all(readPromises);
173
-
174
- const result = await runSpecPhase(
175
- epicId,
176
- provider,
177
- { techSpecContent, acceptanceSpecContent },
178
- settings,
179
- {
180
- force: values.force,
181
- forceReview: values['force-review'],
182
- steal: values.steal === true,
183
- config,
184
- riskVerdict,
185
- },
45
+ } from './lib/orchestration/epic-plan-spec/phases/risk-verdict.js';
46
+ export { runSpecPhase } from './lib/orchestration/epic-plan-spec/phases/run-spec-phase.js';
47
+ export { runSpecFreshnessCheck } from './lib/orchestration/epic-plan-spec/phases/spec-freshness.js';
48
+ export { resolveReviewRouting } from './lib/orchestration/plan-review-routing.js';
49
+
50
+ // CLI execution is retired — fail loudly with the successor surface instead
51
+ // of silently doing nothing (a stale automation script should break visibly).
52
+ if (
53
+ process.argv[1] &&
54
+ import.meta.url === pathToFileURL(process.argv[1]).href
55
+ ) {
56
+ process.stderr.write(
57
+ '[epic-plan-spec] retired (Epic #4474): the plan pipeline is ' +
58
+ 'context → author → persist.\n' +
59
+ ' - authoring envelope: node .agents/scripts/plan-context.js --epic <id>\n' +
60
+ ' - persist (all gates): node .agents/scripts/plan-persist.js --epic <id> ...\n' +
61
+ 'This file survives one release as an import shim only.\n',
186
62
  );
187
-
188
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
63
+ process.exit(1);
189
64
  }
190
-
191
- runAsCli(import.meta.url, main, { source: 'epic-plan-spec' });
@@ -291,7 +291,13 @@ export function reseedMappingFromGh(state, spec, ghState) {
291
291
  }
292
292
 
293
293
  for (const entity of flattenSpecForReseed(spec)) {
294
- if (mapping[entity.slug]) continue; // already mapped
294
+ // Epic #4474 (PR3) a mapping entry WITHOUT an issue number is a
295
+ // partial-failure tombstone (the apply's state writer persists every
296
+ // spec slug; only completed creations carry `issueNumber`). Treat it
297
+ // as unmapped here so a title-matched live issue reseeds it instead
298
+ // of leaving a numberless entry the diff engine can neither update
299
+ // nor (post-fix) skip-create.
300
+ if (Number.isInteger(mapping[entity.slug]?.issueNumber)) continue; // already mapped
295
301
  const candidates = openByTitle.get(entity.title) ?? [];
296
302
  // Pick the lowest unclaimed candidate for determinism.
297
303
  const match = candidates
@@ -106,8 +106,8 @@ let progressStdoutSink = (msg) => console.log(msg);
106
106
  * Flip every Logger output that can land on stdout (`info`, `warn`, and the
107
107
  * stdout branch of `createProgress`) to stderr for the lifetime of the
108
108
  * process. Idempotent. Use when stdout is reserved for a structured payload
109
- * — for example the `--emit-context` JSON envelopes emitted by
110
- * `epic-plan-spec.js` and `epic-plan-decompose.js`, where any interleaved
109
+ * — for example the JSON envelope emitted by
110
+ * `plan-context.js`, where any interleaved
111
111
  * `[Orchestrator] ℹ️ …` log line corrupts the captured file
112
112
  * (Story #2278).
113
113
  */
@@ -180,8 +180,8 @@ export const NOOP_LOGGER = Object.freeze({
180
180
 
181
181
  /**
182
182
  * Frozen logger that routes every level to **stderr**. Use this when a
183
- * caller's stdout is a structured payload (e.g. `--emit-context` JSON
184
- * envelopes from `epic-plan-spec.js` / `epic-plan-decompose.js`) and any
183
+ * caller's stdout is a structured payload (e.g. the authoring-context JSON
184
+ * envelope from `plan-context.js`) and any
185
185
  * progress/telemetry log must not interleave with the payload. Mirrors the
186
186
  * `{ info, warn, error, debug }` shape that the orchestration helpers
187
187
  * accept via optional `logger` arguments.
@@ -3,12 +3,11 @@
3
3
  *
4
4
  * Single library entry point for the audit-suite. Exports the two public
5
5
  * functions (`runAuditSuite`, `selectAudits`) plus the pure rule-matching
6
- * helpers (`matchesFilePattern`, `matchesAnyFilePattern`) used by the
7
- * top-level CLIs (`run-audit-suite.js`, `select-audits.js`).
6
+ * helpers (`matchesFilePattern`, `matchesAnyFilePattern`).
8
7
  *
9
- * Consumers import from this file rather than reaching upward into the
10
- * CLI scripts (`../../run-audit-suite.js`, `../../select-audits.js`)
11
- * that direction inversion is the goal of Story #1083 / Epic #1072.
8
+ * Consumers import from this file the former top-level CLI wrappers
9
+ * (`run-audit-suite.js`, `select-audits.js`) were retired in #4482; this
10
+ * barrel is the only supported entry point (Story #1083 / Epic #1072).
12
11
  *
13
12
  * @example
14
13
  * import { runAuditSuite, selectAudits } from './lib/audit-suite/index.js';
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * lib/audit-suite/runner.js — `runAuditSuite` aggregation core.
3
3
  *
4
- * Extracted from `.agents/scripts/run-audit-suite.js` (Story #963, Epic #946).
4
+ * Extracted from the former `run-audit-suite.js` CLI (Story #963, Epic #946;
5
+ * the CLI wrapper itself was retired in #4482 — `runAuditSuite` via the
6
+ * barrel is the only supported entry point).
5
7
  *
6
8
  * The runner composes the focused helpers from this directory:
7
9
  * - frontmatter.js → `summarizeWorkflow`
@@ -10,9 +12,9 @@
10
12
  * - workflow-loader.js → `loadWorkflow`, `defaultWriteArtifact`
11
13
  *
12
14
  * It owns the audit envelope shape (`metadata`, `findings`, `workflows`) and
13
- * the per-audit fan-out + result reduction. The CLI entry-point in
14
- * `.agents/scripts/run-audit-suite.js` reduces to argument parsing and
15
- * stdout/stderr glue around this function.
15
+ * the per-audit fan-out + result reduction. The former CLI entry-point
16
+ * (`run-audit-suite.js`) was retired in #4482; callers invoke
17
+ * `runAuditSuite` via the `lib/audit-suite/index.js` barrel.
16
18
  */
17
19
 
18
20
  import fs from 'node:fs/promises';
@@ -11,9 +11,8 @@
11
11
  * - selectAudits — main entry; reads audit-rules.json, runs `git
12
12
  * diff --name-only`, applies keyword + glob rules.
13
13
  *
14
- * The CLI wrapper at `.agents/scripts/select-audits.js` reduces to argv
15
- * parsing, provider construction, JSON stdout, and degraded-mode exit-code
16
- * mapping. All rule-matching lives here.
14
+ * All rule-matching lives here; the former `select-audits.js` CLI wrapper
15
+ * was retired in #4482 (consumers call `selectAudits` via the barrel).
17
16
  */
18
17
 
19
18
  import { readFileSync } from 'node:fs';
@@ -2,7 +2,7 @@
2
2
  * BDD runner detection + pending-tag verification (Epic #2001 Story #2094
3
3
  * Task #2103; workspace-aware extension from Story #2956).
4
4
  *
5
- * Used by `epic-plan-spec.js#buildAuthoringContext` to decide whether the
5
+ * Used by `buildAuthoringContext` (the `plan-context.js` envelope) to decide whether the
6
6
  * acceptance-table section should plan **features-first** Story ordering (a real
7
7
  * pending-tag is available, so the features-first Story can ship `.feature`
8
8
  * files marked `@pending` / `@skip` ahead of the implementation Stories) or
@@ -31,3 +31,23 @@ export function applyHeader(content, header) {
31
31
  const body = content.slice(block.length).replace(/^\r?\n/, '');
32
32
  return `${block}\n${header}${body}`;
33
33
  }
34
+
35
+ /**
36
+ * True when a workflow opts out of slash-command projection via a
37
+ * `command: false` key in its YAML frontmatter (#4482). Used for dual-use
38
+ * lens files (e.g. `audit-security.md`, `audit-lighthouse.md`) that stay in
39
+ * the payload as `/deliver` audit-suite prompts but must NOT surface as
40
+ * standalone slash commands because the host ships a native equivalent.
41
+ *
42
+ * Both `sync-claude-commands.js` (projection + orphan-reap) and the
43
+ * `commands-in-sync` doctor check (parity expectation) consult this flag so
44
+ * an excluded workflow never reads as "not synced".
45
+ *
46
+ * @param {string} content - Raw workflow `.md` content.
47
+ * @returns {boolean}
48
+ */
49
+ export function isCommandExcluded(content) {
50
+ const frontmatter = String(content).match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
51
+ if (!frontmatter) return false;
52
+ return /^command:\s*false\s*$/m.test(frontmatter[1]);
53
+ }
@@ -8,15 +8,13 @@
8
8
  * drives both:
9
9
  *
10
10
  * - `/agents-bootstrap-github` — registers GitHub required-status checks.
11
- * - `/git-merge-pr` → `git-pr-quality-gate.js` — runs each check locally
12
- * before merge.
13
11
  */
14
12
 
15
13
  /**
16
14
  * Default required-check suite. Mirrors the live CI required-check set
17
15
  * (`lint` + `test` + `baselines`); consumers override via
18
16
  * `github.branchProtection.requiredChecks` in `.agentrc.json`. Kept in sync
19
- * with `DEFAULT_CHECKS` in `git-pr-quality-gate.js` and the CI job names in
17
+ * with the CI job names in
20
18
  * `.github/workflows/ci.yml` — the retired `format:check` folded into `lint`
21
19
  * (Story #1829) and `lifecycle-doc-drift` collapsed into `lint`/`docs:check`
22
20
  * (Epic #1943), so neither belongs in the default set.
@@ -297,9 +297,10 @@ const PLANNING_SCHEMA = {
297
297
  failOnRegistryConflicts: { type: 'boolean' },
298
298
  failOnLargeFanOut: { type: 'boolean' },
299
299
  largeFanOutThreshold: { type: 'integer', minimum: 0 },
300
- // Navigability-reachability config consumed by the epic-plan-healthcheck
301
- // --paranoid reachability check (Epic #4131, F7). Opt-in: absent or empty
302
- // routeGlobs degrades to a silent no-op.
300
+ // Navigability-reachability config consumed by the plan-persist draft
301
+ // reachability gate (Epic #4131 F7; demoted into persist by #4474 PR6 —
302
+ // the manual epic-plan-healthcheck --paranoid re-check reads it too).
303
+ // Opt-in: absent or empty routeGlobs degrades to a silent no-op.
303
304
  navigation: {
304
305
  type: 'object',
305
306
  properties: {
@@ -2,7 +2,7 @@
2
2
  * Shared helper for the explicit-degraded contract used by soft-failing gates.
3
3
  *
4
4
  * Tech Spec #819 §"Degraded-mode contract (Story 3)" — three soft-fail sites
5
- * (select-audits diff-timeout, lint-baseline JSON-parse, baseline-refresh
5
+ * (audit-suite selector diff-timeout, lint-baseline JSON-parse, baseline-refresh
6
6
  * guardrail git-diff) historically returned a silent zero/empty result. The
7
7
  * new contract is:
8
8
  *
@@ -123,9 +123,9 @@ export const META_LABELS = {
123
123
  /**
124
124
  * Planning-axis labels (Epic #2880 F7). Currently scoped to the
125
125
  * `planning::healthcheck-waived` operator-applied waiver, which is the
126
- * documented escape hatch for the `/plan` Phase 10 readiness
127
- * healthcheck (`epic-plan-healthcheck.js`). The persist half of
128
- * `epic-plan-decompose.js` refuses to flip an Epic to `agent::ready`
126
+ * documented escape hatch for the inline post-plan readiness
127
+ * healthcheck (`runPlanHealthcheck`). The persist surface
128
+ * (`plan-persist.js`) refuses to flip an Epic to `agent::ready`
129
129
  * when the healthcheck returned `ok: false` unless this label is
130
130
  * present — see `.agents/docs/SDLC.md` § "`agent::ready` exit conditions"
131
131
  * for the full handoff contract.
@@ -147,6 +147,20 @@ export const PLANNING_LABELS = {
147
147
  */
148
148
  export const PLANNING_HEALTHCHECK_WAIVED = 'planning::healthcheck-waived';
149
149
 
150
+ /**
151
+ * Delivery-routing labels (Epic #4474 PR4 — design §2 mode matrix).
152
+ * `delivery::single` marks an Epic whose plan was persisted in the
153
+ * spec-only single-delivery mode: no Story tree exists and the Delivery
154
+ * Slicing table of the Epic body's Tech Spec is the audit trail.
155
+ * `plan-persist.js` applies it when the risk verdict declares
156
+ * `deliveryShape: "single"`; a fan-out re-persist over the same Epic
157
+ * removes it. The marker is **inert until #4475** lands the deliver-side
158
+ * reader — nothing in the deliver path consumes it yet.
159
+ */
160
+ export const DELIVERY_LABELS = {
161
+ SINGLE: 'delivery::single',
162
+ };
163
+
150
164
  /** Palette for the taxonomy; consumed by label-taxonomy.js. */
151
165
  export const LABEL_COLORS = {
152
166
  TYPE: '#7057FF',
@@ -155,4 +169,5 @@ export const LABEL_COLORS = {
155
169
  PERSONA: '#C5DEF5',
156
170
  ACCEPTANCE: '#FBCA04',
157
171
  PLANNING: '#FEF2C0',
172
+ DELIVERY: '#BFD4F2',
158
173
  };
@@ -12,6 +12,7 @@ import { fileURLToPath } from 'node:url';
12
12
  import {
13
13
  ACCEPTANCE_LABELS,
14
14
  AGENT_LABELS,
15
+ DELIVERY_LABELS,
15
16
  LABEL_COLORS,
16
17
  PERSONA_LABEL_PREFIX,
17
18
  PLANNING_LABELS,
@@ -108,15 +109,25 @@ export const LABEL_TAXONOMY = [
108
109
 
109
110
  // Planning axis — operator-applied waivers for the planning → delivery
110
111
  // handoff gates. Currently the sole entry is the `healthcheck-waived`
111
- // override consumed by the persist half of `epic-plan-decompose.js`
112
- // when `epic-plan-healthcheck.js` returned `ok: false` for a reason
113
- // the operator has triaged and accepted.
112
+ // override consumed by `plan-persist.js` when the inline healthcheck
113
+ // (`runPlanHealthcheck`) returned `ok: false` for a reason the
114
+ // operator has triaged and accepted.
114
115
  {
115
116
  name: PLANNING_LABELS.HEALTHCHECK_WAIVED,
116
117
  color: LABEL_COLORS.PLANNING,
117
118
  description:
118
119
  'Operator override — allows agent::ready handoff despite a failing post-plan healthcheck',
119
120
  },
121
+
122
+ // Delivery routing — applied by `plan-persist.js` when the risk verdict
123
+ // declares `deliveryShape: "single"` (Epic #4474 PR4). Inert until #4475
124
+ // lands the deliver-side reader.
125
+ {
126
+ name: DELIVERY_LABELS.SINGLE,
127
+ color: LABEL_COLORS.DELIVERY,
128
+ description:
129
+ 'Single-delivery plan — no Story tree; the Delivery Slicing table is the audit trail (#4474/#4475)',
130
+ },
120
131
  ];
121
132
 
122
133
  /** @type {Array<{ name: string, type: 'single_select', options?: string[] }>} */
@@ -28,9 +28,10 @@
28
28
  * `lib/baseline-snapshot.js`: the fs surface and the clock are injected so
29
29
  * unit tests never touch real files. The two writer paths
30
30
  * (`readBaseline`, `writeBaseline`) are split because the typical caller
31
- * sequence is read-then-merge-then-write — for instance,
32
- * `update-mutation-baseline.js` reads the prior baseline to preserve the
33
- * configured `tolerancePct` if the Stryker run does not override it.
31
+ * sequence is read-then-merge-then-write — a refresh CLI reads the prior
32
+ * baseline to preserve the configured `tolerancePct` if the Stryker run
33
+ * does not override it (the former `update-mutation-baseline.js` wrapper
34
+ * was retired in #4482; the gate ships dormant).
34
35
  */
35
36
 
36
37
  import fs from 'node:fs';
@@ -89,9 +90,9 @@ export function readBaseline(baselinePath, opts = {}) {
89
90
  * The on-disk shape is canonicalised: `workspaces` keys are sorted
90
91
  * alphabetically (with `"*"` always first) and the file ends with a
91
92
  * trailing newline. This produces a byte-stable serialisation, which
92
- * the `update-mutation-baseline.js` entry-point relies on to detect
93
- * "no change" runs (skip the baseline-refresh commit when re-running
94
- * Stryker produces an identical baseline).
93
+ * lets a refresh caller detect "no change" runs (skip the
94
+ * baseline-refresh commit when re-running Stryker produces an identical
95
+ * baseline).
95
96
  *
96
97
  * @param {string} baselinePath
97
98
  * @param {{ generatedAt?: string, tolerancePct?: number, workspaces: Record<string, number> }} payload
@@ -19,9 +19,12 @@
19
19
  * Delivery Slicing section, an unparseable "Independent?" cell — resolves to
20
20
  * `dispatch: true`. This gate can only ever *save* a dispatch when it is
21
21
  * confident the critic has nothing to do; it never disables the critic's
22
- * ability to catch a real divergence. Phase 8.4 (reachability critic), 8.5
23
- * (pre-mortem critic), and the deterministic ticket validator are
24
- * unconditional this precondition governs only the 8.3 dispatch.
22
+ * ability to catch a real divergence. Since Epic #4474 PR6 this precondition
23
+ * is one input to the risk/size-conditional dispatch layer
24
+ * (`plan-critic-conditions.js`): reachability (8.4) is a deterministic
25
+ * persist-side check (`plan-reachability.js`) and the pre-mortem critic
26
+ * (8.5) is risk/size-gated; the deterministic ticket validator remains
27
+ * unconditional.
25
28
  *
26
29
  * Pure, synchronous, no I/O — callers own reading `tickets.json` and the
27
30
  * Epic body off disk / the GitHub API.
@@ -146,10 +149,14 @@ function isBddScaffoldStory(story) {
146
149
  * top-level `slug` / `depends_on` / `body` (serialized string).
147
150
  * @param {string} input.epicBody - The Epic body carrying the folded Tech
148
151
  * Spec sections (`## Delivery Slicing` onward).
149
- * @returns {{ dispatch: boolean, reasons: string[] }} `dispatch: false` only
150
- * when the draft matches the Delivery Slicing table 1:1 in count and
151
- * dependency shape; `dispatch: true` (with `reasons`) otherwise, including
152
- * every fail-open case.
152
+ * @returns {{ dispatch: boolean, cause: 'match'|'divergence'|'fail-open', reasons: string[] }}
153
+ * `dispatch: false` only when the draft matches the Delivery Slicing table
154
+ * 1:1 in count and dependency shape; `dispatch: true` (with `reasons`)
155
+ * otherwise, including every fail-open case. `cause` distinguishes a
156
+ * **confirmed** divergence (count or dependency-shape mismatch) from the
157
+ * fail-open ambiguity (missing/unparseable table) — the #4474 PR6
158
+ * conditional-dispatch layer treats only the former as a firing condition
159
+ * on small drafts.
153
160
  */
154
161
  export function evaluateConsolidationPrecondition({ draftStories, epicBody }) {
155
162
  if (!Array.isArray(draftStories)) {
@@ -162,6 +169,7 @@ export function evaluateConsolidationPrecondition({ draftStories, epicBody }) {
162
169
  if (!slicing) {
163
170
  return {
164
171
  dispatch: true,
172
+ cause: 'fail-open',
165
173
  reasons: [
166
174
  'Delivery Slicing section is missing or unparseable — fail-open to the critic.',
167
175
  ],
@@ -175,6 +183,7 @@ export function evaluateConsolidationPrecondition({ draftStories, epicBody }) {
175
183
  if (slicedStories.length !== slicing.length) {
176
184
  return {
177
185
  dispatch: true,
186
+ cause: 'divergence',
178
187
  reasons: [
179
188
  `Story count diverges from Delivery Slicing: ${slicing.length} proposed slice(s) vs ${slicedStories.length} non-scaffold draft Story(ies).`,
180
189
  ],
@@ -201,11 +210,12 @@ export function evaluateConsolidationPrecondition({ draftStories, epicBody }) {
201
210
  }
202
211
 
203
212
  if (reasons.length > 0) {
204
- return { dispatch: true, reasons };
213
+ return { dispatch: true, cause: 'divergence', reasons };
205
214
  }
206
215
 
207
216
  return {
208
217
  dispatch: false,
218
+ cause: 'match',
209
219
  reasons: [
210
220
  `Draft matches Delivery Slicing 1:1 in count and dependency shape (${slicing.length} slice(s)) — skipping the 8.3 consolidation dispatch.`,
211
221
  ],
@@ -9,9 +9,8 @@
9
9
  * file I/O decisions, or process.exit(). All I/O choices are delegated
10
10
  * to the caller.
11
11
  *
12
- * Consumers:
13
- * - `.agents/scripts/hydrate-context.js` the only supported CLI wrapper
14
- * (imports hydrateContext; `--emit prompt` writes the raw hydrated prompt)
12
+ * Consumers import `hydrateContext` from this module directly — the former
13
+ * `hydrate-context.js` CLI wrapper was retired (#4482) with no live consumer.
15
14
  *
16
15
  * @see .agents/scripts/lib/ITicketingProvider.js
17
16
  */
@@ -153,7 +152,7 @@ function getVersion() {
153
152
  * @param {string} body
154
153
  * @returns {Record<string, number>}
155
154
  */
156
- export function parseHierarchy(body) {
155
+ function parseHierarchy(body) {
157
156
  const result = {};
158
157
  if (!body) return result;
159
158
 
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * Story #4433 extends this module with {@link ensureDocsDigest}, a shared
17
17
  * generate-and-write export so the planner-context surface
18
- * (`epic-plan-spec.js` / `authoring-context.js`) can produce a session docs
18
+ * (`plan-context.js` / `authoring-context.js`) can produce a session docs
19
19
  * digest without duplicating the mkdir+writeFile plumbing
20
20
  * `epic-deliver-prepare.js` already owns for the delivery-children digest.
21
21
  */
@@ -146,7 +146,7 @@ export async function buildDocsDigest({ docsContextFiles, docsRoot } = {}) {
146
146
  * write) when there is nothing to digest. This is the single shared
147
147
  * generate-and-persist export both digest producers call: the per-Epic
148
148
  * delivery-children digest (`epic-deliver-prepare.js`) and the planner-
149
- * context digest (`epic-plan-spec.js` → `authoring-context.js`, Story
149
+ * context digest (`plan-context.js` → `authoring-context.js`, Story
150
150
  * #4433). Callers own path construction (temp-root layout, epic id, etc.)
151
151
  * so both surfaces can keep — or deliberately share — their own convention;
152
152
  * this function only owns "build digest, ensure parent dir, write file".
@@ -104,6 +104,10 @@ export function validateTickets(tickets, config, opts = {}) {
104
104
  const validated = validateAndNormalizeTickets(tickets, {
105
105
  baseBranchRef,
106
106
  conflictPolicy,
107
+ // Thread the repo cwd into the AC-freshness / file-assumption git
108
+ // probes (#4474 PR7) — without it they silently ran against
109
+ // process.cwd(), which is only the repo root by coincidence.
110
+ cwd: opts.cwd,
107
111
  });
108
112
  validateTaskBodies(validated);
109
113
  return validated;