mandrel 1.92.0 → 1.94.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 (144) hide show
  1. package/.agents/agents/acceptance-critic.md +129 -0
  2. package/.agents/agents/retro.md +42 -0
  3. package/.agents/agents/story-worker.md +162 -0
  4. package/.agents/docs/configuration.md +7 -1
  5. package/.agents/docs/execution-reference.md +27 -2
  6. package/.agents/instructions.md +43 -33
  7. package/.agents/personas/engineer.md +26 -112
  8. package/.agents/personas/security-engineer.md +1 -2
  9. package/.agents/rules/git-conventions-reference.md +225 -0
  10. package/.agents/rules/git-conventions.md +25 -200
  11. package/.agents/rules/security-baseline.md +5 -0
  12. package/.agents/rules/testing-standards.md +106 -13
  13. package/.agents/schemas/agentrc.schema.json +31 -1
  14. package/.agents/schemas/lifecycle/slice.end.schema.json +21 -0
  15. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +20 -0
  16. package/.agents/schemas/lifecycle/slice.start.schema.json +17 -0
  17. package/.agents/scripts/acceptance-eval.js +62 -18
  18. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  19. package/.agents/scripts/bookkeeping-reconcile.js +117 -0
  20. package/.agents/scripts/check-context-budget.js +62 -5
  21. package/.agents/scripts/diagnose-friction.js +0 -6
  22. package/.agents/scripts/epic-deliver-prepare.js +272 -10
  23. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
  24. package/.agents/scripts/lib/close-validation/gates.js +159 -21
  25. package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
  26. package/.agents/scripts/lib/config/delivery-routing.js +87 -0
  27. package/.agents/scripts/lib/config/explain.js +2 -0
  28. package/.agents/scripts/lib/config-resolver.js +1 -1
  29. package/.agents/scripts/lib/config-settings-schema-delivery.js +37 -3
  30. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -0
  31. package/.agents/scripts/lib/doc-tiers.js +37 -2
  32. package/.agents/scripts/lib/observability/active-story-env.js +111 -2
  33. package/.agents/scripts/lib/observability/hook-heartbeat.js +219 -0
  34. package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
  35. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
  36. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
  37. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +270 -0
  38. package/.agents/scripts/lib/orchestration/ceremony-routing.js +141 -0
  39. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -124
  40. package/.agents/scripts/lib/orchestration/deliver-route.js +173 -0
  41. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +1 -1
  42. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +233 -0
  43. package/.agents/scripts/lib/orchestration/file-assumptions.js +68 -7
  44. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +270 -0
  45. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +83 -2
  46. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +6 -0
  47. package/.agents/scripts/lib/orchestration/plan-context.js +189 -3
  48. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
  49. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +99 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +38 -1
  51. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +16 -1
  52. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
  53. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +1 -0
  54. package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -2
  55. package/.agents/scripts/lib/provider-factory.js +1 -1
  56. package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
  57. package/.agents/scripts/plan-context.js +28 -10
  58. package/.agents/scripts/post-structured-comment.js +38 -0
  59. package/.agents/scripts/slice-phase.js +361 -0
  60. package/.agents/scripts/sync-claude-agents.js +165 -0
  61. package/.agents/scripts/update-ticket-state.js +31 -0
  62. package/.agents/scripts/wave-tick.js +138 -9
  63. package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
  64. package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
  65. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
  66. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +5 -5
  67. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +8 -8
  68. package/.agents/skills/core/epic-plan-premortem/SKILL.md +4 -4
  69. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +26 -56
  70. package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
  71. package/.agents/skills/core/idea-refinement/SKILL.md +2 -8
  72. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
  73. package/.agents/skills/skills.index.json +11 -381
  74. package/.agents/workflows/deliver.md +47 -4
  75. package/.agents/workflows/helpers/acceptance-self-eval.md +38 -13
  76. package/.agents/workflows/helpers/deliver-epic-reference.md +18 -5
  77. package/.agents/workflows/helpers/deliver-epic-single.md +331 -0
  78. package/.agents/workflows/helpers/deliver-epic.md +51 -8
  79. package/.agents/workflows/helpers/deliver-stories.md +15 -5
  80. package/.agents/workflows/helpers/epic-deliver-story.md +12 -3
  81. package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
  82. package/.agents/workflows/helpers/plan-epic-reference.md +19 -8
  83. package/.agents/workflows/helpers/plan-epic.md +95 -27
  84. package/.agents/workflows/helpers/scope-triage-gate.md +9 -0
  85. package/.agents/workflows/mandrel-update.md +1 -1
  86. package/.agents/workflows/plan.md +16 -4
  87. package/docs/CHANGELOG.md +23 -0
  88. package/lib/cli/registry.js +95 -0
  89. package/package.json +4 -2
  90. package/.agents/personas/engineer-mobile.md +0 -120
  91. package/.agents/personas/engineer-web.md +0 -111
  92. package/.agents/personas/product.md +0 -94
  93. package/.agents/personas/refactorer.md +0 -113
  94. package/.agents/personas/sre.md +0 -86
  95. package/.agents/personas/ux-designer.md +0 -95
  96. package/.agents/scripts/epic-plan-decompose.js +0 -54
  97. package/.agents/scripts/epic-plan-spec.js +0 -64
  98. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
  99. package/.agents/scripts/plan-critics.js +0 -227
  100. package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
  101. package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
  102. package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
  103. package/.agents/skills/core/code-simplification/SKILL.md +0 -389
  104. package/.agents/skills/core/context-engineering/SKILL.md +0 -309
  105. package/.agents/skills/core/context-engineering/examples.md +0 -58
  106. package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
  107. package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
  108. package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
  109. package/.agents/skills/core/idea-refinement/examples.md +0 -437
  110. package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
  111. package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
  112. package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
  113. package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
  114. package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
  115. package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
  116. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
  117. package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
  118. package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
  119. package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
  120. package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
  121. package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
  122. package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
  123. package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
  124. package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
  125. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
  126. package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
  127. package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
  128. package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
  129. package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
  130. package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
  131. package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
  132. package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
  133. package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
  134. package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
  135. package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
  136. package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
  137. package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
  138. package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
  139. package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
  140. package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
  141. package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
  142. package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
  143. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
  144. package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
@@ -0,0 +1,99 @@
1
+ /**
2
+ * plan-critics-evaluate.js — shared critic-dispatch evaluation for the
3
+ * collapsed /plan flow (#4496 fix 6; extracted from the `plan-critics.js`
4
+ * CLI so the persist surface folds the same evaluation in as a pre-write
5
+ * phase).
6
+ *
7
+ * Two consumers:
8
+ * - `plan-persist.js` (via `runPlanPersist`) — evaluates the dispatch
9
+ * conditions as a deterministic pre-write phase, prints the verdicts,
10
+ * and records every skip on the plan-metrics ledger, so the headless
11
+ * path never pays a standalone CLI turn for the same decision.
12
+ * - `plan-critics.js` — the standalone CLI survives one release as a
13
+ * thin shim over this module for the attended pre-gate evaluation
14
+ * (the verdict folds into gate #2's view before the persist runs).
15
+ *
16
+ * Pure evaluation: no file I/O, no GitHub calls, no ledger writes — the
17
+ * callers own artifact loading and skip recording.
18
+ *
19
+ * @module lib/orchestration/plan-critics-evaluate
20
+ */
21
+
22
+ import { getLimits } from '../config-resolver.js';
23
+ import {
24
+ evaluateConsolidationDispatch,
25
+ evaluatePremortemDispatch,
26
+ } from './plan-critic-conditions.js';
27
+
28
+ /**
29
+ * Resolve the planning risk heuristics list from the canonical config
30
+ * block (same resolution `plan-context.js` and the decompose context use).
31
+ *
32
+ * @param {object} config
33
+ * @returns {string[]}
34
+ */
35
+ function resolveRiskHeuristics(config = {}) {
36
+ if (Array.isArray(config.planning?.riskHeuristics)) {
37
+ return config.planning.riskHeuristics;
38
+ }
39
+ return config.agentSettings?.planning?.riskHeuristics || [];
40
+ }
41
+
42
+ /**
43
+ * Evaluate the consolidation + pre-mortem critic dispatch conditions over
44
+ * the authored planning artifacts (design §4 / #4474 PR6 conditions,
45
+ * unchanged):
46
+ *
47
+ * - Consolidation: skipped outright when `tickets` is null/absent (the
48
+ * single-delivery shape authors no draft tickets); otherwise the
49
+ * deterministic precondition + size/divergence conditions.
50
+ * - Pre-mortem: risk verdict overall level high, OR ticket count at least
51
+ * half `maxTickets`, OR any `planning.riskHeuristics` phrase matching
52
+ * the plan text.
53
+ *
54
+ * @param {{
55
+ * techSpecContent: string,
56
+ * riskVerdict: { summary?: string },
57
+ * tickets?: Array<object>|null,
58
+ * config?: object,
59
+ * }} args
60
+ * @returns {{
61
+ * consolidation: { critic: string, dispatch: boolean, reasons: string[] },
62
+ * premortem: { critic: string, dispatch: boolean, reasons: string[] },
63
+ * }}
64
+ */
65
+ export function evaluatePlanCritics({
66
+ techSpecContent,
67
+ riskVerdict,
68
+ tickets = null,
69
+ config = {},
70
+ }) {
71
+ const ticketList = Array.isArray(tickets) ? tickets : null;
72
+ const consolidation =
73
+ ticketList === null
74
+ ? {
75
+ critic: 'consolidation',
76
+ dispatch: false,
77
+ reasons: [
78
+ 'single-delivery shape — no draft tickets exist to consolidate.',
79
+ ],
80
+ }
81
+ : evaluateConsolidationDispatch({
82
+ draftStories: ticketList,
83
+ specText: techSpecContent,
84
+ });
85
+
86
+ const premortem = evaluatePremortemDispatch({
87
+ riskVerdict,
88
+ ticketCount: ticketList?.length ?? 0,
89
+ maxTickets: getLimits(config).maxTickets,
90
+ riskHeuristics: resolveRiskHeuristics(config),
91
+ planText: [
92
+ techSpecContent ?? '',
93
+ ticketList ? JSON.stringify(ticketList) : '',
94
+ riskVerdict?.summary ?? '',
95
+ ].join('\n'),
96
+ });
97
+
98
+ return { consolidation, premortem };
99
+ }
@@ -130,6 +130,7 @@ import {
130
130
  read as readPlanState,
131
131
  write as writePlanState,
132
132
  } from '../epic-plan-state-store.js';
133
+ import { evaluatePlanCritics } from '../plan-critics-evaluate.js';
133
134
  import {
134
135
  appendCriticSkip,
135
136
  readPlanMetrics,
@@ -426,9 +427,10 @@ export async function runPlanPersist({
426
427
  let validated = null;
427
428
  let amendPartition = null;
428
429
  let reachability = null;
430
+ let gateSet = null;
429
431
  if (mode !== 'single') {
430
432
  amendPartition = mode === 'amend' ? partitionAmendTickets(tickets) : null;
431
- const gateSet = mode === 'amend' ? buildMergedTicketSet(tickets) : tickets;
433
+ gateSet = mode === 'amend' ? buildMergedTicketSet(tickets) : tickets;
432
434
  const maxTickets = getLimits(config).maxTickets;
433
435
  if (gateSet.length > maxTickets && !allowOverBudget) {
434
436
  throw new Error(
@@ -533,6 +535,40 @@ export async function runPlanPersist({
533
535
  );
534
536
  }
535
537
 
538
+ // ---- Step 4.7: folded critic dispatch evaluation (#4496 fix 6 — the
539
+ // former standalone `plan-critics.js` turn). Deterministic and git-local,
540
+ // still zero provider calls: the verdicts are printed (and returned on
541
+ // the result) as part of the pre-write phase, and every skip decision is
542
+ // appended to the plan-metrics ledger exactly as the standalone CLI did,
543
+ // so under-firing stays auditable without a separate invocation on the
544
+ // headless path. Advisory by construction — the deterministic validators
545
+ // above remain the unchanged hard gates. ----
546
+ const critics = evaluatePlanCritics({
547
+ techSpecContent,
548
+ riskVerdict,
549
+ tickets: gateSet,
550
+ config,
551
+ });
552
+ for (const decision of [critics.consolidation, critics.premortem]) {
553
+ Logger.info(
554
+ `[plan-persist] critic ${decision.critic}: ` +
555
+ `${decision.dispatch ? 'dispatch' : 'skip'} — ` +
556
+ decision.reasons.join('; '),
557
+ );
558
+ if (!decision.dispatch) {
559
+ // Best-effort by contract — a failed append never fails the persist.
560
+ await appendCriticSkip(
561
+ {
562
+ critic: decision.critic,
563
+ reasons: decision.reasons,
564
+ cli: 'plan-persist',
565
+ epicId: requestedEpicId,
566
+ },
567
+ config,
568
+ );
569
+ }
570
+ }
571
+
536
572
  // ---- Step 5: ideation fold / Epic resolution (first provider call). ----
537
573
  const { epicId, epic, created } = await resolveTargetEpic({
538
574
  epicId: requestedEpicId,
@@ -902,6 +938,7 @@ export async function runPlanPersist({
902
938
  freshness,
903
939
  healthcheck,
904
940
  reachability,
941
+ critics,
905
942
  reconcile,
906
943
  specPath: specFilePath,
907
944
  waveTable,
@@ -89,10 +89,16 @@ function renderWaveTableLines(waveTable) {
89
89
  * record `{ deliveryShape, sliceCount, routingReasons }` (Epic #4474 PR4)
90
90
  * for the spec-only mode.
91
91
  *
92
+ * Every auto-waiver the persist derived is printed WITH its reason
93
+ * (#4496 fix 2) — e.g. the no-BDD-runner acceptance-disposition waiver
94
+ * (`planningRisk.acceptanceWaivedReason`) — so the summary is
95
+ * self-explanatory and a headless reader never has to re-derive a persist
96
+ * outcome from framework source.
97
+ *
92
98
  * @param {{
93
99
  * epicId: number,
94
100
  * ticketCount: number,
95
- * planningRisk: { overallLevel?: string, gateDecision?: string },
101
+ * planningRisk: { overallLevel?: string, gateDecision?: string, acceptanceDisposition?: string, acceptanceWaivedReason?: string },
96
102
  * reviewRouting: { decision?: string },
97
103
  * freshness?: { stale?: number, ambiguous?: number },
98
104
  * healthcheck?: { ok?: boolean, waived?: boolean, skipped?: boolean },
@@ -137,6 +143,14 @@ export function buildPlanSummaryCommentBody({
137
143
  ? `- Single-delivery plan (\`delivery::single\`): no Story tree — the Delivery Slicing table is the audit trail.`
138
144
  : `- ${ticketCount} Story ticket(s) persisted across ${waveTable.length} wave(s).`;
139
145
 
146
+ // Auto-waivers always ship with their reason (#4496 fix 2): the summary
147
+ // is authoritative, so the line must be self-explanatory on its own.
148
+ const waiverLines = planningRisk?.acceptanceWaivedReason
149
+ ? [
150
+ `- ⚠️ Acceptance disposition auto-waived to \`not-applicable\` — ${planningRisk.acceptanceWaivedReason}`,
151
+ ]
152
+ : [];
153
+
140
154
  const amendLines = amend
141
155
  ? [
142
156
  `- Amend delta: ${amend.created.length} added, ${amend.recreated.length} modified (closed + recreated), ${amend.closed.length} closed, ${amend.keptCount} kept untouched.`,
@@ -176,6 +190,7 @@ export function buildPlanSummaryCommentBody({
176
190
  headLine,
177
191
  ...amendLines,
178
192
  `- Risk: ${planningRisk?.overallLevel ?? 'unknown'} · ${planningRisk?.gateDecision ?? 'unknown'} (review routing: ${reviewRouting?.decision ?? 'unknown'}).`,
193
+ ...waiverLines,
179
194
  freshnessLine,
180
195
  healthcheckLine,
181
196
  // G2 measurement receipt (Epic #4474 PR1/PR7): the plan-CLI invocation
@@ -129,6 +129,7 @@ export async function runCloseValidationPhase({
129
129
  config,
130
130
  epicBranch: baseBranch,
131
131
  cwd: worktreePath || cwd,
132
+ log: (m) => Logger.info(m),
132
133
  }),
133
134
  log: (m) => Logger.info(m),
134
135
  storyId,
@@ -140,6 +140,7 @@ export async function runPreMergeGates({
140
140
  config,
141
141
  epicBranch,
142
142
  cwd: worktreePath || cwd,
143
+ log: (m) => logger.info?.(m),
143
144
  });
144
145
  const gateCount = Array.isArray(gates) ? gates.length : 0;
145
146
  // Story #2250 — emit `close-validate.start` only when both an epicId
@@ -272,7 +272,7 @@ export function validateAcFreshness({
272
272
  * Epic #2501 introduced this guard after the legacy `baseline-refresh`
273
273
  * leading-token prescription created a wave of commit-msg hook failures
274
274
  * across story-deliver sub-agents. See
275
- * `.agents/skills/core/baseline-refresh/SKILL.md` for the canonical refresh
275
+ * `.agents/skills/core/gates-and-baselines/SKILL.md` for the canonical refresh
276
276
  * shape (Conventional-Commits subject + `baseline-refresh: true` body
277
277
  * trailer).
278
278
  */
@@ -589,8 +589,25 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
589
589
  gitRunner: sharedGitRunner,
590
590
  cwd: opts.cwd,
591
591
  });
592
+ // Auto-normalizations (#4496 fix 5) get their own prefix so the logged
593
+ // warning is self-explanatory; everything else on the warnings channel
594
+ // is a legacy-shape deprecation nudge.
595
+ const normalizationWarnings = new Set(
596
+ (assumptionReport.normalizations ?? []).map((n) => n.path),
597
+ );
592
598
  for (const warning of assumptionReport.warnings) {
593
- Logger.warn(`[ticket-validator] assumption-deprecation: ${warning}`);
599
+ const isNormalization = warning.includes('auto-normalized to "creates"');
600
+ Logger.warn(
601
+ `[ticket-validator] ${isNormalization ? 'assumption-normalized' : 'assumption-deprecation'}: ${warning}`,
602
+ );
603
+ }
604
+ if (normalizationWarnings.size > 0) {
605
+ Logger.warn(
606
+ `[ticket-validator] ${normalizationWarnings.size} refactors-existing ` +
607
+ 'declaration(s) on base-untracked path(s) auto-normalized to ' +
608
+ '"creates" — the gate proceeds; update the plan declarations at ' +
609
+ 'the next amend.',
610
+ );
594
611
  }
595
612
  assumptionErrors = assumptionReport.errors;
596
613
  }
@@ -4,7 +4,7 @@
4
4
  * Accepts the canonical resolved config object (the wrapper returned by
5
5
  * `resolveConfig()` with `config.github` populated). The legacy
6
6
  * `orchestration`-shaped argument is no longer supported as part of the
7
- * Epic #2880 hard cutover; see `.agents/rules/git-conventions.md#contract-cutovers-—-no-shim-layer`.
7
+ * Epic #2880 hard cutover; see `.agents/rules/git-conventions-reference.md#contract-cutovers-—-no-shim-layer`.
8
8
  *
9
9
  * @see docs/v5-implementation-plan.md Sprint 1B
10
10
  */
@@ -88,7 +88,7 @@ Your job is to take an Epic (including its inline User Stories) and a Technical
88
88
 
89
89
  ### LABEL CONVENTIONS:
90
90
  - Every ticket must have the \`type::story\` label. No other type label is allowed — the retired Feature and Task tiers have no labels under this hierarchy.
91
- - Every ticket must have a \`persona::[engineer|architect|qa-engineer|engineer-web|etc]\` label indicating WHO should execute it.
91
+ - Every ticket must have a \`persona::[engineer|architect|qa-engineer|security-engineer|etc]\` label indicating WHO should execute it.
92
92
 
93
93
  ### OUTPUT FORMAT:
94
94
  You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown blocks.
@@ -11,7 +11,7 @@
11
11
  * stdout-pure JSON envelope. The PR7 cutover retired the delegate CLIs —
12
12
  * this is the only emit-context surface.
13
13
  *
14
- * Two entry forms (exactly one is required):
14
+ * Three entry forms (exactly one is required):
15
15
  *
16
16
  * --epic <id> Existing-Epic mode. Envelope carries `epic`,
17
17
  * `clarity` (Epic Clarity Gate rubric), `replan`
@@ -23,6 +23,15 @@
23
23
  * dup search). No clarity score: the ideation path
24
24
  * is definitionally clear.
25
25
  *
26
+ * --seed "<text>" Headless ideation entry (#4496 fix 1) — neither
27
+ * the Epic nor the one-pager exists yet. The dup
28
+ * search runs off the raw seed text, and the
29
+ * envelope additively carries `seed`, `scopeTriage`
30
+ * (the scope-triage rubric applied CLI-side — fix 6)
31
+ * and `onePagerSpec`, so the one-pager sections are
32
+ * authored in the same batched write as the spec
33
+ * artifacts.
34
+ *
26
35
  * Flags:
27
36
  * --pretty Pretty-print the JSON envelope.
28
37
  * --full-context Bypass the planning-context budget (unbounded body).
@@ -58,10 +67,11 @@ import { createProvider } from './lib/provider-factory.js';
58
67
  * captured output is exactly one `JSON.parse`-able payload.
59
68
  *
60
69
  * @param {{
61
- * mode: 'epic'|'one-pager',
70
+ * mode: 'epic'|'one-pager'|'seed',
62
71
  * epicId?: number,
63
72
  * onePagerPath?: string,
64
73
  * onePagerContent?: string,
74
+ * seedText?: string,
65
75
  * provider: object,
66
76
  * config: object,
67
77
  * settings: object,
@@ -77,6 +87,7 @@ export async function emitPlanContext({
77
87
  epicId,
78
88
  onePagerPath,
79
89
  onePagerContent,
90
+ seedText,
80
91
  provider,
81
92
  config,
82
93
  settings,
@@ -90,6 +101,7 @@ export async function emitPlanContext({
90
101
  epicId,
91
102
  onePagerPath,
92
103
  onePagerContent,
104
+ seedText,
93
105
  provider,
94
106
  config,
95
107
  settings,
@@ -108,6 +120,7 @@ async function main() {
108
120
  options: {
109
121
  epic: { type: 'string' },
110
122
  'one-pager': { type: 'string' },
123
+ seed: { type: 'string' },
111
124
  pretty: { type: 'boolean', default: false },
112
125
  'full-context': { type: 'boolean', default: false },
113
126
  },
@@ -117,12 +130,16 @@ async function main() {
117
130
  const hasEpic = typeof values.epic === 'string' && values.epic.length > 0;
118
131
  const hasOnePager =
119
132
  typeof values['one-pager'] === 'string' && values['one-pager'].length > 0;
120
- if (hasEpic === hasOnePager) {
133
+ const hasSeed = typeof values.seed === 'string' && values.seed.length > 0;
134
+ const entryForms = [hasEpic, hasOnePager, hasSeed].filter(Boolean).length;
135
+ if (entryForms !== 1) {
121
136
  throw new Error(
122
- 'Pass exactly one of --epic <id> or --one-pager <path>. ' +
123
- '(--epic: existing-Epic mode; --one-pager: ideation mode.)',
137
+ 'Pass exactly one of --epic <id>, --one-pager <path> or --seed "<text>". ' +
138
+ '(--epic: existing-Epic mode; --one-pager: ideation mode; ' +
139
+ '--seed: headless ideation entry.)',
124
140
  );
125
141
  }
142
+ const mode = hasEpic ? 'epic' : hasOnePager ? 'one-pager' : 'seed';
126
143
 
127
144
  let epicId;
128
145
  if (hasEpic) {
@@ -159,21 +176,22 @@ async function main() {
159
176
  const provider = createProvider(config);
160
177
 
161
178
  // Plan-metrics ledger (#4474 PR1): stamp entry/exit + mode so the folded
162
- // emit surface is measured against the 12-phase baseline. One-pager mode
163
- // has no Epic yet, so the record routes to the standalone stream
164
- // (epicId null) exactly like `story-plan.js`.
179
+ // emit surface is measured against the 12-phase baseline. One-pager and
180
+ // seed modes have no Epic yet, so the record routes to the standalone
181
+ // stream (epicId null) exactly like `story-plan.js`.
165
182
  await recordPlanInvocation(
166
183
  {
167
184
  cli: 'plan-context',
168
- mode: hasEpic ? 'epic' : 'one-pager',
185
+ mode,
169
186
  epicId: hasEpic ? epicId : null,
170
187
  config,
171
188
  },
172
189
  () =>
173
190
  emitPlanContext({
174
- mode: hasEpic ? 'epic' : 'one-pager',
191
+ mode,
175
192
  epicId,
176
193
  onePagerPath: hasOnePager ? values['one-pager'] : undefined,
194
+ seedText: hasSeed ? values.seed : undefined,
177
195
  provider,
178
196
  config,
179
197
  settings,
@@ -22,6 +22,10 @@ import fs from 'node:fs/promises';
22
22
  import { parseArgs } from 'node:util';
23
23
  import { runAsCli } from './lib/cli-utils.js';
24
24
  import { resolveConfig } from './lib/config-resolver.js';
25
+ import {
26
+ outboxPathFor,
27
+ postCommentOrBuffer,
28
+ } from './lib/orchestration/bookkeeping-outbox.js';
25
29
  import {
26
30
  assertValidStructuredCommentType,
27
31
  upsertStructuredComment,
@@ -37,6 +41,11 @@ Flags:
37
41
  retro, epic-run-state, wave-0-start) (required).
38
42
  --body-file Path to a file containing the markdown body (required).
39
43
  --provider Provider name (default: inferred from .agentrc.json github block).
44
+ --buffer Headless mode: buffer this upsert to the per-Epic bookkeeping
45
+ outbox instead of posting live. Requires --epic. Drained to
46
+ GitHub once at finalize by bookkeeping-reconcile.js (Epic
47
+ #4476). Omit for attended runs (posts live, unchanged).
48
+ --epic <id> Epic id owning the outbox (required with --buffer).
40
49
  --help Show this message.
41
50
  `;
42
51
 
@@ -64,6 +73,8 @@ export function parseArgv(argv) {
64
73
  marker: { type: 'string' },
65
74
  'body-file': { type: 'string' },
66
75
  provider: { type: 'string' },
76
+ buffer: { type: 'boolean' },
77
+ epic: { type: 'string' },
67
78
  help: { type: 'boolean' },
68
79
  },
69
80
  strict: false,
@@ -115,6 +126,33 @@ export async function main(argv = process.argv.slice(2)) {
115
126
  : config;
116
127
  const provider = createProvider(effectiveConfig);
117
128
 
129
+ // Headless buffering (Epic #4476 M5): route the upsert to the per-Epic
130
+ // outbox instead of a live GitHub round-trip. Requires --epic to locate the
131
+ // outbox; finalize's bookkeeping-reconcile.js drains it once. Attended runs
132
+ // (no --buffer) are unchanged.
133
+ if (values.buffer) {
134
+ const epicId = Number.parseInt(values.epic ?? '', 10);
135
+ if (!Number.isInteger(epicId) || epicId <= 0) {
136
+ process.stderr.write(
137
+ '[post-structured-comment] --buffer requires --epic <id>.\n',
138
+ );
139
+ process.exit(2);
140
+ }
141
+ assertValidStructuredCommentType(values.marker);
142
+ const { buffered } = await postCommentOrBuffer({
143
+ provider,
144
+ ticketId,
145
+ marker: values.marker,
146
+ body,
147
+ headless: true,
148
+ outboxPath: outboxPathFor(epicId, config),
149
+ });
150
+ process.stdout.write(
151
+ `${JSON.stringify({ success: true, ticketId, type: values.marker, buffered })}\n`,
152
+ );
153
+ return;
154
+ }
155
+
118
156
  const envelope = await runPostStructuredComment({
119
157
  ticketId,
120
158
  type: values.marker,