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.
- package/.agents/README.md +5 -5
- package/.agents/docs/SDLC.md +75 -107
- package/.agents/docs/configuration.md +1 -1
- package/.agents/docs/quality-gates.md +8 -7
- package/.agents/docs/workflows.md +4 -10
- package/.agents/instructions.md +2 -2
- package/.agents/schemas/agentrc.schema.json +1 -1
- package/.agents/schemas/lifecycle/README.md +2 -1
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +14 -1
- package/.agents/scripts/README.md +0 -46
- package/.agents/scripts/analyze-execution.js +76 -1
- package/.agents/scripts/check-doc-links.js +3 -0
- package/.agents/scripts/check-workflow-cli-lint.js +208 -0
- package/.agents/scripts/epic-audit-recheck.js +1 -1
- package/.agents/scripts/epic-plan-clarity.js +16 -6
- package/.agents/scripts/epic-plan-decompose.js +37 -59
- package/.agents/scripts/epic-plan-healthcheck.js +39 -6
- package/.agents/scripts/epic-plan-spec.js +41 -168
- package/.agents/scripts/epic-reconcile.js +7 -1
- package/.agents/scripts/lib/Logger.js +4 -4
- package/.agents/scripts/lib/audit-suite/index.js +4 -5
- package/.agents/scripts/lib/audit-suite/runner.js +6 -4
- package/.agents/scripts/lib/audit-suite/selector.js +2 -3
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/command-header.js +20 -0
- package/.agents/scripts/lib/config/github.js +1 -3
- package/.agents/scripts/lib/config-settings-schema.js +4 -3
- package/.agents/scripts/lib/degraded-mode.js +1 -1
- package/.agents/scripts/lib/label-constants.js +18 -3
- package/.agents/scripts/lib/label-taxonomy.js +14 -3
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +7 -6
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +18 -8
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -4
- package/.agents/scripts/lib/orchestration/docs-digest.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +4 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +22 -12
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +6 -1
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +18 -2
- package/.agents/scripts/lib/orchestration/file-assumptions.js +68 -7
- package/.agents/scripts/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-context.js +696 -0
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +99 -0
- package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +1015 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +206 -0
- package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
- package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -1
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
- package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
- package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
- package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
- package/.agents/scripts/plan-context.js +204 -0
- package/.agents/scripts/plan-critics.js +199 -0
- package/.agents/scripts/plan-persist.js +383 -0
- package/.agents/scripts/pr-watch-with-update.js +8 -7
- package/.agents/scripts/run-lint.js +10 -11
- package/.agents/scripts/story-plan.js +19 -7
- package/.agents/scripts/sync-claude-commands.js +10 -5
- package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
- package/.agents/workflows/audit-documentation.md +5 -7
- package/.agents/workflows/audit-lighthouse.md +1 -0
- package/.agents/workflows/audit-security.md +1 -0
- package/.agents/workflows/audit-to-stories.md +16 -5
- package/.agents/workflows/git-deliver.md +76 -243
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/code-review.md +7 -6
- package/.agents/workflows/helpers/deliver-epic.md +5 -4
- package/.agents/workflows/helpers/plan-epic-reference.md +120 -96
- package/.agents/workflows/helpers/plan-epic.md +307 -994
- package/.agents/workflows/helpers/plan-story.md +14 -14
- package/.agents/workflows/helpers/scope-triage-gate.md +15 -4
- package/.agents/workflows/helpers/single-story-deliver.md +5 -4
- package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
- package/.agents/workflows/mandrel-update.md +144 -466
- package/.agents/workflows/plan.md +87 -145
- package/docs/CHANGELOG.md +20 -0
- package/lib/cli/registry.js +21 -3
- package/package.json +1 -1
- package/.agents/schemas/loop-unit.schema.json +0 -70
- package/.agents/scripts/assert-branch.js +0 -81
- package/.agents/scripts/check-loop-units.js +0 -204
- package/.agents/scripts/detect-merges.js +0 -111
- package/.agents/scripts/git-pr-quality-gate.js +0 -205
- package/.agents/scripts/git-rebase-and-resolve.js +0 -234
- package/.agents/scripts/hierarchy-gate.js +0 -192
- package/.agents/scripts/hydrate-context.js +0 -179
- package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
- package/.agents/scripts/loc-delta.js +0 -205
- package/.agents/scripts/render-manifest.js +0 -143
- package/.agents/scripts/run-audit-suite.js +0 -97
- package/.agents/scripts/select-audits.js +0 -155
- package/.agents/scripts/update-mutation-baseline.js +0 -189
- package/.agents/workflows/explain.md +0 -118
- package/.agents/workflows/git-merge-pr.md +0 -377
- package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
- package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
- package/.agents/workflows/loops/README.md +0 -65
- package/.agents/workflows/loops/fix-failing-tests.md +0 -74
- package/.agents/workflows/loops/nightly-audit.md +0 -81
- 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
|
+
}
|