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
|
@@ -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 —
|
|
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
|
-
*
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
import '
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
110
|
-
* `
|
|
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.
|
|
184
|
-
*
|
|
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`)
|
|
7
|
-
* top-level CLIs (`run-audit-suite.js`, `select-audits.js`).
|
|
6
|
+
* helpers (`matchesFilePattern`, `matchesAnyFilePattern`).
|
|
8
7
|
*
|
|
9
|
-
* Consumers import from this file
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
*
|
|
15
|
-
*
|
|
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 `
|
|
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
|
|
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
|
|
301
|
-
//
|
|
302
|
-
//
|
|
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
|
-
* (
|
|
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
|
|
127
|
-
* healthcheck (`
|
|
128
|
-
* `
|
|
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
|
|
112
|
-
//
|
|
113
|
-
//
|
|
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 —
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
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.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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[] }}
|
|
150
|
-
* when the draft matches the Delivery Slicing table
|
|
151
|
-
* dependency shape; `dispatch: true` (with `reasons`)
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
* (`
|
|
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 (`
|
|
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;
|