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.
- 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/{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 +510 -0
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -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 +978 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +191 -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/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 +186 -0
- package/.agents/scripts/plan-critics.js +227 -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 +109 -96
- package/.agents/workflows/helpers/plan-epic.md +246 -999
- package/.agents/workflows/helpers/plan-story.md +14 -14
- package/.agents/workflows/helpers/scope-triage-gate.md +6 -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 +73 -143
- package/docs/CHANGELOG.md +13 -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
|
@@ -240,13 +240,17 @@ export async function runDecomposePhase(
|
|
|
240
240
|
* @param {typeof defaultRunPlanHealthcheck} args.runHealthcheckFn
|
|
241
241
|
* @returns {Promise<{ ok: boolean, waived?: boolean, reason?: string|null }>}
|
|
242
242
|
*/
|
|
243
|
-
function enforceFanOutGate(
|
|
243
|
+
export function enforceFanOutGate(
|
|
244
|
+
findings,
|
|
245
|
+
allowLargeFanOut,
|
|
246
|
+
tag = 'epic-plan-decompose',
|
|
247
|
+
) {
|
|
244
248
|
const fanOut = (findings ?? []).filter((f) => f.kind === 'fan-out-warning');
|
|
245
249
|
if (fanOut.length === 0) return;
|
|
246
250
|
if (allowLargeFanOut) {
|
|
247
251
|
for (const f of fanOut) {
|
|
248
252
|
Logger.warn(
|
|
249
|
-
`[
|
|
253
|
+
`[${tag}] Persisting a large-fan-out deletion: ` +
|
|
250
254
|
`Task "${f.taskSlug}" deletes "${f.path}" with ${f.callSiteCount} ` +
|
|
251
255
|
`call site(s) (threshold ${f.threshold}). Operator override --allow-large-fan-out.`,
|
|
252
256
|
);
|
|
@@ -260,7 +264,7 @@ function enforceFanOutGate(findings, allowLargeFanOut) {
|
|
|
260
264
|
)
|
|
261
265
|
.join('\n');
|
|
262
266
|
throw new Error(
|
|
263
|
-
`[
|
|
267
|
+
`[${tag}] ${fanOut.length} Task(s) declare large-fan-out deletions:\n${lines}\n\n` +
|
|
264
268
|
`Split each deletion into a subsystem-by-subsystem migration across multiple Stories, ` +
|
|
265
269
|
`or rerun --allow-large-fan-out after confirming the deletion is intentional.`,
|
|
266
270
|
);
|
|
@@ -284,24 +288,30 @@ function enforceFanOutGate(findings, allowLargeFanOut) {
|
|
|
284
288
|
*
|
|
285
289
|
* @param {object[]} findings
|
|
286
290
|
*/
|
|
287
|
-
function surfaceSoftConflictFindings(
|
|
291
|
+
export function surfaceSoftConflictFindings(
|
|
292
|
+
findings,
|
|
293
|
+
tag = 'epic-plan-decompose',
|
|
294
|
+
) {
|
|
288
295
|
const soft = (findings ?? []).filter(
|
|
289
296
|
(f) => f?.severity === 'soft' && f?.kind !== 'fan-out-warning',
|
|
290
297
|
);
|
|
291
298
|
if (soft.length === 0) return;
|
|
292
299
|
Logger.warn(
|
|
293
|
-
`[
|
|
300
|
+
`[${tag}] ${soft.length} soft cross-Story conflict finding(s) — review before approving the plan:`,
|
|
294
301
|
);
|
|
295
302
|
for (const finding of soft) {
|
|
296
|
-
Logger.warn(
|
|
297
|
-
`[epic-plan-decompose] soft conflict: ${renderHardConflictError(finding)}`,
|
|
298
|
-
);
|
|
303
|
+
Logger.warn(`[${tag}] soft conflict: ${renderHardConflictError(finding)}`);
|
|
299
304
|
}
|
|
300
305
|
}
|
|
301
306
|
|
|
302
|
-
async function runHealthcheckGate({
|
|
307
|
+
export async function runHealthcheckGate({
|
|
308
|
+
epicId,
|
|
309
|
+
epic,
|
|
310
|
+
runHealthcheckFn,
|
|
311
|
+
tag = 'epic-plan-decompose',
|
|
312
|
+
}) {
|
|
303
313
|
Logger.info(
|
|
304
|
-
`[
|
|
314
|
+
`[${tag}] Running post-plan readiness healthcheck for Epic #${epicId}...`,
|
|
305
315
|
);
|
|
306
316
|
let result;
|
|
307
317
|
try {
|
|
@@ -321,13 +331,13 @@ async function runHealthcheckGate({ epicId, epic, runHealthcheckFn }) {
|
|
|
321
331
|
const waived = labels.includes(PLANNING_HEALTHCHECK_WAIVED);
|
|
322
332
|
if (waived) {
|
|
323
333
|
Logger.warn(
|
|
324
|
-
`[
|
|
334
|
+
`[${tag}] Healthcheck failed for Epic #${epicId} but '${PLANNING_HEALTHCHECK_WAIVED}' is applied — proceeding with agent::ready handoff. Reason: ${result?.reason ?? '(no reason reported)'}`,
|
|
325
335
|
);
|
|
326
336
|
return { ok: false, waived: true, reason: result?.reason ?? null };
|
|
327
337
|
}
|
|
328
338
|
|
|
329
339
|
throw new Error(
|
|
330
|
-
`[
|
|
340
|
+
`[${tag}] Refusing agent::ready handoff for Epic #${epicId}: ` +
|
|
331
341
|
`post-plan healthcheck failed (${result?.reason ?? '(no reason reported)'}). ` +
|
|
332
342
|
`Resolve the failing check(s), or apply the '${PLANNING_HEALTHCHECK_WAIVED}' ` +
|
|
333
343
|
`label to the Epic to override and rerun the persist phase.`,
|
|
@@ -113,7 +113,12 @@ export async function buildAuthoringContext(
|
|
|
113
113
|
settings = {},
|
|
114
114
|
opts = {},
|
|
115
115
|
) {
|
|
116
|
-
|
|
116
|
+
// Epic #4474 (M3 PR2): `opts.epic` is an optional prefetched Epic object
|
|
117
|
+
// so a caller that already holds the issue (the folded `plan-context.js`
|
|
118
|
+
// envelope build, which also needs the raw body for clarity scoring and
|
|
119
|
+
// re-plan detection) does not pay a second provider fetch. Absent, the
|
|
120
|
+
// fetch behaviour is unchanged.
|
|
121
|
+
const epic = opts.epic ?? (await provider.getEpic(epicId));
|
|
117
122
|
if (!epic) {
|
|
118
123
|
throw new Error(`Epic #${epicId} not found.`);
|
|
119
124
|
}
|
|
@@ -568,7 +568,20 @@ export function diff({ spec, state, ghState } = {}) {
|
|
|
568
568
|
|
|
569
569
|
for (const entity of flattenSpec(spec)) {
|
|
570
570
|
seenSpecSlugs.add(entity.slug);
|
|
571
|
-
|
|
571
|
+
// Epic #4474 (PR3) — a mapping entry without a numeric `issueNumber`
|
|
572
|
+
// is a partial-failure tombstone: the apply's state writer projects an
|
|
573
|
+
// entry per spec slug, but only completed creations carry a number
|
|
574
|
+
// (`buildState` seeds new slugs with `issueNumber: null`). Treating
|
|
575
|
+
// such an entry as "mapped" made a `--resume` after a rate-limit crash
|
|
576
|
+
// silently skip every failed creation (no Create emitted; the content
|
|
577
|
+
// hash matches, so no Update either — a lossy resume). A numberless
|
|
578
|
+
// entry is therefore unmapped here → Create. No duplicate risk: any
|
|
579
|
+
// live same-title issue was already reseeded onto the slug (with its
|
|
580
|
+
// number) by `reseedMappingFromGh` before the diff runs.
|
|
581
|
+
const mappedEntry = mapping[entity.slug];
|
|
582
|
+
const mapped = Number.isInteger(mappedEntry?.issueNumber)
|
|
583
|
+
? mappedEntry
|
|
584
|
+
: undefined;
|
|
572
585
|
|
|
573
586
|
if (!mapped) {
|
|
574
587
|
plan.creates.push(
|
|
@@ -630,9 +643,12 @@ export function diff({ spec, state, ghState } = {}) {
|
|
|
630
643
|
}
|
|
631
644
|
}
|
|
632
645
|
|
|
633
|
-
// Closes — anything in mapping not seen in spec.
|
|
646
|
+
// Closes — anything in mapping not seen in spec. A numberless
|
|
647
|
+
// partial-failure tombstone (see above) has no live issue to close;
|
|
648
|
+
// dropping it from the spec simply drops the entry.
|
|
634
649
|
for (const [slug, mapped] of Object.entries(mapping)) {
|
|
635
650
|
if (seenSpecSlugs.has(slug)) continue;
|
|
651
|
+
if (!Number.isInteger(mapped?.issueNumber)) continue;
|
|
636
652
|
plan.closes.push(
|
|
637
653
|
closeOp({
|
|
638
654
|
slug,
|
|
@@ -1,35 +1,22 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file */
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* ledger-diff.js — structural-diff and ordering-assertion helpers over a
|
|
3
|
+
* lifecycle ledger (`temp/epic-<id>/lifecycle.ndjson`) for the
|
|
4
|
+
* repeatability invariants.
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* by epic.merge.ready (same seqId
|
|
14
|
-
* chain; armed.seqId > ready.seqId).
|
|
15
|
-
* reconcile-ordering — pr.created must be preceded by
|
|
16
|
-
* acceptance.reconcile.ok.
|
|
17
|
-
* — exits 0 on pass; exits 1 with a structured message on fail.
|
|
6
|
+
* Relocated from the retired `.agents/scripts/lifecycle-diff.js` CLI
|
|
7
|
+
* (#4482): the assertion helpers are the live surface — they pin the
|
|
8
|
+
* cross-listener ordering contracts in the lifecycle invariant tests —
|
|
9
|
+
* while the CLI wrapper had no workflow, script, or CI consumer.
|
|
18
10
|
*
|
|
19
11
|
* Invariants are derived from Tech Spec #2189 § Repeatability Acceptance
|
|
20
12
|
* Criteria.
|
|
21
13
|
*/
|
|
22
14
|
|
|
23
|
-
import { readFileSync } from 'node:fs';
|
|
24
|
-
import { parseArgs } from 'node:util';
|
|
25
|
-
|
|
26
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
27
|
-
|
|
28
15
|
/**
|
|
29
16
|
* Parse an NDJSON lifecycle ledger into an array of records. Blank
|
|
30
17
|
* lines tolerated; malformed JSON throws with line number. Duplicated
|
|
31
|
-
* from `
|
|
32
|
-
*
|
|
18
|
+
* from `trace-logger.js` to keep the diff surface decoupled from the
|
|
19
|
+
* listener surface.
|
|
33
20
|
*/
|
|
34
21
|
export function parseLedgerText(text) {
|
|
35
22
|
const out = [];
|
|
@@ -41,7 +28,7 @@ export function parseLedgerText(text) {
|
|
|
41
28
|
out.push(JSON.parse(line));
|
|
42
29
|
} catch (_err) {
|
|
43
30
|
throw new Error(
|
|
44
|
-
`
|
|
31
|
+
`ledger-diff: malformed JSON in ledger on line ${i + 1}: ${line.slice(0, 80)}`,
|
|
45
32
|
);
|
|
46
33
|
}
|
|
47
34
|
}
|
|
@@ -151,56 +138,3 @@ export function assertReconcileOrdering(records) {
|
|
|
151
138
|
}
|
|
152
139
|
return { ok: true };
|
|
153
140
|
}
|
|
154
|
-
|
|
155
|
-
const ASSERTIONS = new Map([
|
|
156
|
-
['merge-gate-ordering', assertMergeGateOrdering],
|
|
157
|
-
['reconcile-ordering', assertReconcileOrdering],
|
|
158
|
-
]);
|
|
159
|
-
|
|
160
|
-
function loadLedger(p) {
|
|
161
|
-
return parseLedgerText(readFileSync(p, 'utf8'));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function main() {
|
|
165
|
-
const { values, positionals } = parseArgs({
|
|
166
|
-
options: {
|
|
167
|
-
assert: { type: 'string' },
|
|
168
|
-
},
|
|
169
|
-
allowPositionals: true,
|
|
170
|
-
args: process.argv.slice(2),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// --assert <mode> <ledger>
|
|
174
|
-
if (!values.assert) {
|
|
175
|
-
process.stderr.write('Usage: lifecycle-diff --assert <mode> <ledger>\n');
|
|
176
|
-
return 2;
|
|
177
|
-
}
|
|
178
|
-
if (positionals.length !== 1) {
|
|
179
|
-
process.stderr.write(
|
|
180
|
-
'lifecycle-diff --assert <mode> requires exactly one positional ledger path\n',
|
|
181
|
-
);
|
|
182
|
-
return 2;
|
|
183
|
-
}
|
|
184
|
-
const assertion = ASSERTIONS.get(values.assert);
|
|
185
|
-
if (!assertion) {
|
|
186
|
-
process.stderr.write(
|
|
187
|
-
`lifecycle-diff: unknown --assert mode "${values.assert}". Valid: ${[...ASSERTIONS.keys()].join(', ')}\n`,
|
|
188
|
-
);
|
|
189
|
-
return 2;
|
|
190
|
-
}
|
|
191
|
-
const records = loadLedger(positionals[0]);
|
|
192
|
-
const result = assertion(records);
|
|
193
|
-
if (result.ok) {
|
|
194
|
-
process.stdout.write(`[lifecycle-diff] PASS ${values.assert}\n`);
|
|
195
|
-
return 0;
|
|
196
|
-
}
|
|
197
|
-
process.stderr.write(
|
|
198
|
-
`[lifecycle-diff] FAIL ${values.assert}: ${result.reason}\n`,
|
|
199
|
-
);
|
|
200
|
-
return 1;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
await runAsCli(import.meta.url, main, {
|
|
204
|
-
source: 'lifecycle-diff',
|
|
205
|
-
propagateExitCode: true,
|
|
206
|
-
});
|
|
@@ -199,7 +199,7 @@ export class AcceptanceReconciler {
|
|
|
199
199
|
? epicIdFromPayload
|
|
200
200
|
: this.epicId;
|
|
201
201
|
|
|
202
|
-
// 1. Start emit. The downstream
|
|
202
|
+
// 1. Start emit. The downstream ledger-diff `reconcile-ordering`
|
|
203
203
|
// assert checks for `acceptance.reconcile.ok` before `pr.created`,
|
|
204
204
|
// but operators want to see the `.start` marker in the trace too.
|
|
205
205
|
try {
|