mandrel 1.91.0 → 1.93.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 (112) 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/lib/orchestration/file-assumptions.js +68 -7
  41. package/.agents/scripts/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
  42. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
  43. package/.agents/scripts/lib/orchestration/plan-context.js +696 -0
  44. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
  45. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +99 -0
  46. package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
  48. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
  49. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +1015 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +206 -0
  51. package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
  52. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
  53. package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
  54. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  55. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -1
  56. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
  57. package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
  58. package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
  59. package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
  60. package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
  61. package/.agents/scripts/plan-context.js +204 -0
  62. package/.agents/scripts/plan-critics.js +199 -0
  63. package/.agents/scripts/plan-persist.js +383 -0
  64. package/.agents/scripts/pr-watch-with-update.js +8 -7
  65. package/.agents/scripts/run-lint.js +10 -11
  66. package/.agents/scripts/story-plan.js +19 -7
  67. package/.agents/scripts/sync-claude-commands.js +10 -5
  68. package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
  69. package/.agents/workflows/audit-documentation.md +5 -7
  70. package/.agents/workflows/audit-lighthouse.md +1 -0
  71. package/.agents/workflows/audit-security.md +1 -0
  72. package/.agents/workflows/audit-to-stories.md +16 -5
  73. package/.agents/workflows/git-deliver.md +76 -243
  74. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  75. package/.agents/workflows/helpers/code-review.md +7 -6
  76. package/.agents/workflows/helpers/deliver-epic.md +5 -4
  77. package/.agents/workflows/helpers/plan-epic-reference.md +120 -96
  78. package/.agents/workflows/helpers/plan-epic.md +307 -994
  79. package/.agents/workflows/helpers/plan-story.md +14 -14
  80. package/.agents/workflows/helpers/scope-triage-gate.md +15 -4
  81. package/.agents/workflows/helpers/single-story-deliver.md +5 -4
  82. package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
  83. package/.agents/workflows/mandrel-update.md +144 -466
  84. package/.agents/workflows/plan.md +87 -145
  85. package/docs/CHANGELOG.md +20 -0
  86. package/lib/cli/registry.js +21 -3
  87. package/package.json +1 -1
  88. package/.agents/schemas/loop-unit.schema.json +0 -70
  89. package/.agents/scripts/assert-branch.js +0 -81
  90. package/.agents/scripts/check-loop-units.js +0 -204
  91. package/.agents/scripts/detect-merges.js +0 -111
  92. package/.agents/scripts/git-pr-quality-gate.js +0 -205
  93. package/.agents/scripts/git-rebase-and-resolve.js +0 -234
  94. package/.agents/scripts/hierarchy-gate.js +0 -192
  95. package/.agents/scripts/hydrate-context.js +0 -179
  96. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
  97. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
  98. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
  99. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
  100. package/.agents/scripts/loc-delta.js +0 -205
  101. package/.agents/scripts/render-manifest.js +0 -143
  102. package/.agents/scripts/run-audit-suite.js +0 -97
  103. package/.agents/scripts/select-audits.js +0 -155
  104. package/.agents/scripts/update-mutation-baseline.js +0 -189
  105. package/.agents/workflows/explain.md +0 -118
  106. package/.agents/workflows/git-merge-pr.md +0 -377
  107. package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
  108. package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
  109. package/.agents/workflows/loops/README.md +0 -65
  110. package/.agents/workflows/loops/fix-failing-tests.md +0 -74
  111. package/.agents/workflows/loops/nightly-audit.md +0 -81
  112. package/.agents/workflows/loops/watch-ci.md +0 -68
@@ -0,0 +1,177 @@
1
+ /**
2
+ * plan-critic-conditions.js — risk/size-conditional dispatch decisions for
3
+ * the /plan author-step critics (Epic #4474 PR6, design §4).
4
+ *
5
+ * The collapsed plan flow keeps the consolidation (8.3) and pre-mortem
6
+ * (8.5) critics as fresh-context sub-agent dispatches, but makes each
7
+ * dispatch **conditional** instead of unconditional — the dominant plan
8
+ * cost is turns × standing context, and an unconditional critic pays a
9
+ * full sub-agent spawn even when it provably has nothing to find. This
10
+ * module computes those decisions deterministically so the workflow never
11
+ * judges its own dispatch conditions:
12
+ *
13
+ * - **Consolidation (8.3)**: dispatch only when the existing
14
+ * `evaluateConsolidationPrecondition` gate says `dispatch: true` AND
15
+ * (the draft has more than `CONSOLIDATION_STORY_THRESHOLD` stories OR
16
+ * the precondition confirmed a divergence from the Delivery Slicing
17
+ * table). A fail-open precondition (missing/unparseable table) on a
18
+ * small draft is NOT a confirmed divergence — it skips, because a
19
+ * ≤-threshold draft is small enough for gate #2's single-view review
20
+ * to catch a distorted shape without a dedicated sub-agent.
21
+ * - **Pre-mortem (8.5)**: dispatch when the risk verdict's overall level
22
+ * is `high`, OR the ticket count is at least half of `maxTickets`, OR
23
+ * any configured `planning.riskHeuristics` phrase matches the plan
24
+ * text (case-insensitive substring).
25
+ *
26
+ * Under-firing risk (design PR6 note): the persist validators are
27
+ * unchanged hard gates and G2's cohort re-measures plan quality; every
28
+ * skip decision this module produces is logged to the plan-metrics ledger
29
+ * (`appendCriticSkip`) by the callers so under-firing is auditable.
30
+ *
31
+ * Pure, synchronous, no I/O — the `plan-critics.js` CLI owns reading the
32
+ * authored artifacts and the resolved config.
33
+ */
34
+
35
+ import { evaluateConsolidationPrecondition } from './consolidation-precondition.js';
36
+ import { deriveRiskEnvelope } from './planning-risk.js';
37
+
38
+ /**
39
+ * Draft-story count above which the consolidation critic fires even
40
+ * without a confirmed slicing divergence (design §6 PR6: "> 5 stories").
41
+ */
42
+ export const CONSOLIDATION_STORY_THRESHOLD = 5;
43
+
44
+ /**
45
+ * @typedef {Object} CriticDispatchDecision
46
+ * @property {'consolidation'|'pre-mortem'} critic
47
+ * @property {boolean} dispatch
48
+ * @property {string[]} reasons Why the critic fires — or why it is safe to
49
+ * skip. Never empty: a skip's reasons are the audit trail the
50
+ * plan-metrics ledger records.
51
+ */
52
+
53
+ /**
54
+ * Decide the 8.3 consolidation dispatch: precondition AND size/divergence.
55
+ *
56
+ * @param {object} input
57
+ * @param {object[]} input.draftStories - The draft `tickets.json` array
58
+ * (raw Story objects with top-level `slug` / `depends_on` / `body`).
59
+ * @param {string} input.specText - The text carrying the `## Delivery
60
+ * Slicing` table. At author time this is the authored `techspec.md`
61
+ * content (the Epic body carries the same folded section post-persist).
62
+ * @returns {CriticDispatchDecision}
63
+ */
64
+ export function evaluateConsolidationDispatch({ draftStories, specText }) {
65
+ const precondition = evaluateConsolidationPrecondition({
66
+ draftStories,
67
+ epicBody: specText,
68
+ });
69
+
70
+ if (!precondition.dispatch) {
71
+ return {
72
+ critic: 'consolidation',
73
+ dispatch: false,
74
+ reasons: precondition.reasons,
75
+ };
76
+ }
77
+
78
+ const storyCount = draftStories.length;
79
+ const oversized = storyCount > CONSOLIDATION_STORY_THRESHOLD;
80
+ const diverges = precondition.cause === 'divergence';
81
+
82
+ if (!oversized && !diverges) {
83
+ return {
84
+ critic: 'consolidation',
85
+ dispatch: false,
86
+ reasons: [
87
+ `Draft has ${storyCount} story(ies) (≤ ${CONSOLIDATION_STORY_THRESHOLD}) and no confirmed Delivery Slicing divergence — gate #2's single-view review covers a draft this small.`,
88
+ ...precondition.reasons,
89
+ ],
90
+ };
91
+ }
92
+
93
+ const reasons = [];
94
+ if (diverges) reasons.push(...precondition.reasons);
95
+ if (oversized) {
96
+ reasons.push(
97
+ `Draft has ${storyCount} stories (> ${CONSOLIDATION_STORY_THRESHOLD}) — large enough that a distorted shape can hide from the gate #2 single view.`,
98
+ );
99
+ }
100
+ if (!diverges && precondition.cause === 'fail-open') {
101
+ reasons.push(...precondition.reasons);
102
+ }
103
+ return { critic: 'consolidation', dispatch: true, reasons };
104
+ }
105
+
106
+ /**
107
+ * Decide the 8.5 pre-mortem dispatch: high risk, or size ≥ ½ budget, or a
108
+ * risk-heuristic phrase match.
109
+ *
110
+ * @param {object} input
111
+ * @param {import('./planning-risk.js').RiskVerdict} input.riskVerdict -
112
+ * The authored `risk-verdict.json` payload; the overall level is derived
113
+ * deterministically from its axes (`deriveRiskEnvelope`), never trusted
114
+ * as a free-standing field.
115
+ * @param {number} input.ticketCount - Draft ticket count (0 in the
116
+ * single-delivery shape — no tickets exist).
117
+ * @param {number} input.maxTickets - The reviewability budget
118
+ * (`getLimits(config).maxTickets`).
119
+ * @param {string[]} [input.riskHeuristics] - `planning.riskHeuristics`
120
+ * phrases from the resolved config.
121
+ * @param {string} [input.planText] - Concatenated plan text the heuristics
122
+ * match against (tech spec + serialized tickets + risk summary).
123
+ * @returns {CriticDispatchDecision}
124
+ */
125
+ export function evaluatePremortemDispatch({
126
+ riskVerdict,
127
+ ticketCount,
128
+ maxTickets,
129
+ riskHeuristics = [],
130
+ planText = '',
131
+ }) {
132
+ if (!Number.isInteger(maxTickets) || maxTickets <= 0) {
133
+ throw new TypeError(
134
+ 'evaluatePremortemDispatch: maxTickets must be a positive integer',
135
+ );
136
+ }
137
+ const reasons = [];
138
+
139
+ const { overallLevel } = deriveRiskEnvelope(riskVerdict);
140
+ if (overallLevel === 'high') {
141
+ reasons.push(
142
+ 'Risk verdict overall level is high — predicted-rework findings are worth a fresh-context pass.',
143
+ );
144
+ }
145
+
146
+ const count = Number.isInteger(ticketCount) ? ticketCount : 0;
147
+ if (count * 2 >= maxTickets) {
148
+ reasons.push(
149
+ `Ticket count ${count} is at least half the reviewability budget (maxTickets ${maxTickets}).`,
150
+ );
151
+ }
152
+
153
+ const haystack = String(planText).toLowerCase();
154
+ const matched = riskHeuristics.filter(
155
+ (phrase) =>
156
+ typeof phrase === 'string' &&
157
+ phrase.trim().length > 0 &&
158
+ haystack.includes(phrase.trim().toLowerCase()),
159
+ );
160
+ if (matched.length > 0) {
161
+ reasons.push(
162
+ `planning.riskHeuristics match(es) in the plan text: ${matched.map((p) => `"${p.trim()}"`).join(', ')}.`,
163
+ );
164
+ }
165
+
166
+ if (reasons.length > 0) {
167
+ return { critic: 'pre-mortem', dispatch: true, reasons };
168
+ }
169
+
170
+ return {
171
+ critic: 'pre-mortem',
172
+ dispatch: false,
173
+ reasons: [
174
+ `Overall risk is ${overallLevel} (not high), ticket count ${count} is under half the budget (maxTickets ${maxTickets}), and no planning.riskHeuristics phrase matches the plan text.`,
175
+ ],
176
+ };
177
+ }
@@ -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
+ }