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.
Files changed (109) 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/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
  41. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
  42. package/.agents/scripts/lib/orchestration/plan-context.js +510 -0
  43. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
  44. package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
  45. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
  46. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +978 -0
  48. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +191 -0
  49. package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
  50. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
  51. package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
  52. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  53. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
  54. package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
  55. package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
  56. package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
  57. package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
  58. package/.agents/scripts/plan-context.js +186 -0
  59. package/.agents/scripts/plan-critics.js +227 -0
  60. package/.agents/scripts/plan-persist.js +383 -0
  61. package/.agents/scripts/pr-watch-with-update.js +8 -7
  62. package/.agents/scripts/run-lint.js +10 -11
  63. package/.agents/scripts/story-plan.js +19 -7
  64. package/.agents/scripts/sync-claude-commands.js +10 -5
  65. package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
  66. package/.agents/workflows/audit-documentation.md +5 -7
  67. package/.agents/workflows/audit-lighthouse.md +1 -0
  68. package/.agents/workflows/audit-security.md +1 -0
  69. package/.agents/workflows/audit-to-stories.md +16 -5
  70. package/.agents/workflows/git-deliver.md +76 -243
  71. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  72. package/.agents/workflows/helpers/code-review.md +7 -6
  73. package/.agents/workflows/helpers/deliver-epic.md +5 -4
  74. package/.agents/workflows/helpers/plan-epic-reference.md +109 -96
  75. package/.agents/workflows/helpers/plan-epic.md +246 -999
  76. package/.agents/workflows/helpers/plan-story.md +14 -14
  77. package/.agents/workflows/helpers/scope-triage-gate.md +6 -4
  78. package/.agents/workflows/helpers/single-story-deliver.md +5 -4
  79. package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
  80. package/.agents/workflows/mandrel-update.md +144 -466
  81. package/.agents/workflows/plan.md +73 -143
  82. package/docs/CHANGELOG.md +13 -0
  83. package/lib/cli/registry.js +21 -3
  84. package/package.json +1 -1
  85. package/.agents/schemas/loop-unit.schema.json +0 -70
  86. package/.agents/scripts/assert-branch.js +0 -81
  87. package/.agents/scripts/check-loop-units.js +0 -204
  88. package/.agents/scripts/detect-merges.js +0 -111
  89. package/.agents/scripts/git-pr-quality-gate.js +0 -205
  90. package/.agents/scripts/git-rebase-and-resolve.js +0 -234
  91. package/.agents/scripts/hierarchy-gate.js +0 -192
  92. package/.agents/scripts/hydrate-context.js +0 -179
  93. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
  94. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
  95. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
  96. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
  97. package/.agents/scripts/loc-delta.js +0 -205
  98. package/.agents/scripts/render-manifest.js +0 -143
  99. package/.agents/scripts/run-audit-suite.js +0 -97
  100. package/.agents/scripts/select-audits.js +0 -155
  101. package/.agents/scripts/update-mutation-baseline.js +0 -189
  102. package/.agents/workflows/explain.md +0 -118
  103. package/.agents/workflows/git-merge-pr.md +0 -377
  104. package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
  105. package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
  106. package/.agents/workflows/loops/README.md +0 -65
  107. package/.agents/workflows/loops/fix-failing-tests.md +0 -74
  108. package/.agents/workflows/loops/nightly-audit.md +0 -81
  109. package/.agents/workflows/loops/watch-ci.md +0 -68
@@ -1,205 +0,0 @@
1
- #!/usr/bin/env node
2
- /* node:coverage ignore file */
3
-
4
- /**
5
- * loc-delta.js — Epic-level LOC accounting (Epic #1181 / Story #1441 /
6
- * Task #1457).
7
- *
8
- * Verifies the Skills-migration acceptance criterion: across the four
9
- * SSOT directories
10
- * - .agents/scripts/
11
- * - .agents/skills/
12
- * - .agents/workflows/
13
- * - .agents/README.md
14
- * the signed line delta between `main` and `HEAD` must be < 0 (the
15
- * migration is meant to *retire* code, not add it). Smoke-test files
16
- * under `tests/skills/` are intentionally excluded — they exist to pin
17
- * the migration, not to be counted against the LOC budget.
18
- *
19
- * Output: a Markdown-flavoured per-directory breakdown on stdout plus a
20
- * single signed total line. Exit 0 iff total < 0; exit 1 otherwise.
21
- *
22
- * Usage:
23
- * node .agents/scripts/loc-delta.js # main...HEAD
24
- * node .agents/scripts/loc-delta.js --base main # explicit base
25
- * node .agents/scripts/loc-delta.js --json # machine output
26
- */
27
-
28
- import { spawnSync } from 'node:child_process';
29
-
30
- import { runAsCli } from './lib/cli-utils.js';
31
-
32
- const SCOPE_PATHS = [
33
- '.agents/scripts/',
34
- '.agents/skills/',
35
- '.agents/workflows/',
36
- '.agents/README.md',
37
- ];
38
-
39
- const EXCLUDES = ['tests/skills/'];
40
-
41
- function parseArgs(argv) {
42
- const out = { base: 'main', head: 'HEAD', json: false };
43
- for (let i = 0; i < argv.length; i++) {
44
- const arg = argv[i];
45
- if (arg === '--base') {
46
- out.base = argv[++i];
47
- } else if (arg === '--head') {
48
- out.head = argv[++i];
49
- } else if (arg === '--json') {
50
- out.json = true;
51
- } else if (arg === '--help' || arg === '-h') {
52
- out.help = true;
53
- }
54
- }
55
- return out;
56
- }
57
-
58
- /**
59
- * Compute the (added, removed) line counts between `base` and `head`
60
- * restricted to the given path. Uses `git diff --numstat` so the parse
61
- * is unambiguous (tab-separated, additions in column 1, removals in
62
- * column 2, path in column 3). Binary files surface as `-` and are
63
- * skipped.
64
- */
65
- export function computeDeltaForPath({ base, head, path: scopePath }) {
66
- const range = `${base}...${head}`;
67
- const result = spawnSync(
68
- 'git',
69
- ['diff', '--numstat', range, '--', scopePath],
70
- { encoding: 'utf8' },
71
- );
72
- if (result.status !== 0) {
73
- throw new Error(
74
- `git diff failed for ${scopePath}: ${result.stderr || result.stdout}`,
75
- );
76
- }
77
- let added = 0;
78
- let removed = 0;
79
- for (const rawLine of result.stdout.split('\n')) {
80
- const line = rawLine.trim();
81
- if (!line) continue;
82
- const parts = line.split('\t');
83
- if (parts.length < 3) continue;
84
- const [a, r, file] = parts;
85
- if (a === '-' || r === '-') continue;
86
- if (EXCLUDES.some((ex) => file.startsWith(ex))) continue;
87
- added += Number.parseInt(a, 10) || 0;
88
- removed += Number.parseInt(r, 10) || 0;
89
- }
90
- return { added, removed, delta: added - removed };
91
- }
92
-
93
- /**
94
- * Compute the per-directory deltas + signed total. Returns a shape that
95
- * is friendly both for human rendering and for `--json` consumers.
96
- */
97
- export function computeLocDelta({ base, head } = {}) {
98
- const effectiveBase = base ?? 'main';
99
- const effectiveHead = head ?? 'HEAD';
100
- const perPath = SCOPE_PATHS.map((scopePath) => ({
101
- path: scopePath,
102
- ...computeDeltaForPath({
103
- base: effectiveBase,
104
- head: effectiveHead,
105
- path: scopePath,
106
- }),
107
- }));
108
- const total = perPath.reduce(
109
- (acc, row) => ({
110
- added: acc.added + row.added,
111
- removed: acc.removed + row.removed,
112
- delta: acc.delta + row.delta,
113
- }),
114
- { added: 0, removed: 0, delta: 0 },
115
- );
116
- return {
117
- base: effectiveBase,
118
- head: effectiveHead,
119
- excludes: EXCLUDES,
120
- perPath,
121
- total,
122
- pass: total.delta < 0,
123
- };
124
- }
125
-
126
- function renderHuman(report) {
127
- const lines = [];
128
- lines.push('--- loc-delta ---');
129
- lines.push(`base=${report.base} head=${report.head}`);
130
- lines.push(`excluded prefixes: ${report.excludes.join(', ')}`);
131
- lines.push('');
132
- lines.push('| Directory | Added | Removed | Net delta |');
133
- lines.push('| --- | ---: | ---: | ---: |');
134
- for (const row of report.perPath) {
135
- const sign = row.delta > 0 ? '+' : '';
136
- lines.push(
137
- `| ${row.path} | ${row.added} | ${row.removed} | ${sign}${row.delta} |`,
138
- );
139
- }
140
- lines.push('');
141
- const sign = report.total.delta > 0 ? '+' : '';
142
- lines.push(
143
- `Totals: added=${report.total.added} · removed=${report.total.removed} · net=${sign}${report.total.delta}`,
144
- );
145
- lines.push(
146
- report.pass
147
- ? 'Result: PASS (net LOC delta is negative)'
148
- : 'Result: FAIL (net LOC delta is not negative — Epic acceptance not met)',
149
- );
150
- return lines.join('\n');
151
- }
152
-
153
- function help() {
154
- return `Usage: node .agents/scripts/loc-delta.js [--base <ref>] [--head <ref>] [--json]
155
-
156
- Verifies that the signed line delta across the four SSOT directories
157
- (.agents/scripts/, .agents/skills/, .agents/workflows/, .agents/README.md)
158
- is negative between <base> (default: main) and <head> (default: HEAD).
159
- Smoke-test files under tests/skills/ are excluded from the count.
160
-
161
- Exit code: 0 iff net delta < 0; 1 otherwise.
162
- `;
163
- }
164
-
165
- async function main() {
166
- const args = parseArgs(process.argv.slice(2));
167
- if (args.help) {
168
- process.stdout.write(help());
169
- return 0;
170
- }
171
- let report;
172
- try {
173
- report = computeLocDelta({ base: args.base, head: args.head });
174
- } catch (err) {
175
- process.stderr.write(`loc-delta: ${err.message}\n`);
176
- return 1;
177
- }
178
- if (args.json) {
179
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
180
- } else {
181
- process.stdout.write(`${renderHuman(report)}\n`);
182
- }
183
- return report.pass ? 0 : 1;
184
- }
185
-
186
- // Direct-CLI guard: drives `main()` through the framework's `runAsCli`
187
- // helper so the canonical main-guard shape (enforced by
188
- // `tests/enforcement/cli-wrapper.test.js`) stays intact. When imported
189
- // by a test, runAsCli is a no-op and the named exports remain reachable.
190
- runAsCli(
191
- import.meta.url,
192
- async () => {
193
- const code = await main();
194
- if (code !== 0) process.exit(code);
195
- },
196
- {
197
- source: 'loc-delta',
198
- onError(err) {
199
- process.stderr.write(
200
- `loc-delta: unexpected error: ${err?.message ?? err}\n`,
201
- );
202
- process.exit(1);
203
- },
204
- },
205
- );
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * .agents/scripts/render-manifest.js — Derived-view Manifest Renderer
5
- *
6
- * Regenerates `temp/epic-<epicId>/manifest.{md,json}` from the
7
- * `dispatch-manifest` structured comment on the Epic (Epic #1030 Story
8
- * #1040 / Task #1054 — migrated from the legacy flat
9
- * `temp/dispatch-manifest-<epicId>.{md,json}` layout). The comment is
10
- * the single source of truth for which Stories the sprint committed to;
11
- * the per-Epic files are a convenience view that lets wave-gate runs,
12
- * local tooling, and CI consumers work offline.
13
- *
14
- * Running this script never mutates GitHub state — it only performs a read
15
- * of the existing comment and writes the derived artefacts under the
16
- * per-Epic tree.
17
- *
18
- * Usage:
19
- * node .agents/scripts/render-manifest.js --epic <EPIC_ID>
20
- *
21
- * Exit codes:
22
- * 0 — render succeeded.
23
- * 1 — no manifest comment (or no parseable JSON) on the Epic.
24
- * 2 — configuration or provider transport error.
25
- */
26
-
27
- import fs from 'node:fs';
28
- import path from 'node:path';
29
- import { parseArgs } from 'node:util';
30
- import { runAsCli } from './lib/cli-utils.js';
31
- import { epicArtifactPath } from './lib/config/temp-paths.js';
32
- import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
33
- import { Logger } from './lib/Logger.js';
34
- import { parseFencedJson } from './lib/orchestration/structured-comment-parser.js';
35
- import { findStructuredComment } from './lib/orchestration/ticketing.js';
36
- import { atomicWrite } from './lib/presentation/manifest-persistence.js';
37
- import { createProvider } from './lib/provider-factory.js';
38
-
39
- export function extractManifestJson(body) {
40
- return parseFencedJson(body);
41
- }
42
-
43
- /**
44
- * Write the rendered manifest files. Separated from the I/O boundary so
45
- * tests can exercise the pure contract without touching disk.
46
- *
47
- * @param {{ epicId: number, body: string, parsed: object, projectRoot?: string }} opts
48
- * @returns {{ mdPath: string, jsonPath: string }}
49
- */
50
- export function writeRenderedManifest({
51
- epicId,
52
- body,
53
- parsed,
54
- projectRoot = PROJECT_ROOT,
55
- config,
56
- }) {
57
- // Per-Epic layout (Epic #1030 Story #1040): write under
58
- // `<projectRoot>/temp/epic-<eid>/manifest.{md,json}`. Bind the artifact
59
- // path to the explicit projectRoot (Story #3900): pre-absolutise the
60
- // tempRoot against projectRoot so temp-paths honours it verbatim rather
61
- // than anchoring a relative tempRoot to the main checkout (correct for the
62
- // lifecycle ledger, wrong for this per-project on-disk manifest write).
63
- const relTempRoot =
64
- config?.project?.paths?.tempRoot &&
65
- !path.isAbsolute(config.project.paths.tempRoot)
66
- ? config.project.paths.tempRoot
67
- : 'temp';
68
- const boundConfig = path.isAbsolute(config?.project?.paths?.tempRoot ?? '')
69
- ? config
70
- : { project: { paths: { tempRoot: path.join(projectRoot, relTempRoot) } } };
71
- const mdPath = epicArtifactPath(epicId, 'manifest.md', boundConfig);
72
- const jsonPath = epicArtifactPath(epicId, 'manifest.json', boundConfig);
73
- const epicDir = path.dirname(mdPath);
74
- if (!fs.existsSync(epicDir)) {
75
- fs.mkdirSync(epicDir, { recursive: true });
76
- }
77
- // Both writes go through the atomic write-then-rename helper so a
78
- // crash mid-write (rename throws / process killed) leaves either the
79
- // pre-existing artefact intact or no file at all — never a partial
80
- // truncation. The `.tmp` residue is best-effort removed on failure
81
- // (see `atomicWrite` in manifest-persistence.js).
82
- atomicWrite(mdPath, body);
83
- atomicWrite(jsonPath, `${JSON.stringify(parsed, null, 2)}\n`);
84
- return { mdPath, jsonPath };
85
- }
86
-
87
- export async function renderManifestFromComment({
88
- epicId,
89
- injectedProvider,
90
- } = {}) {
91
- if (!epicId || Number.isNaN(epicId) || epicId <= 0) {
92
- throw new Error('Usage: node render-manifest.js --epic <EPIC_ID>');
93
- }
94
-
95
- const config = resolveConfig();
96
- const provider = injectedProvider || createProvider(config);
97
-
98
- const comment = await findStructuredComment(
99
- provider,
100
- epicId,
101
- 'dispatch-manifest',
102
- );
103
- if (!comment) {
104
- Logger.error(
105
- `[render-manifest] No dispatch-manifest comment on Epic #${epicId}. ` +
106
- `Run the dispatcher (\`node .agents/scripts/dispatcher.js ${epicId}\`) first.`,
107
- );
108
- process.exit(1);
109
- }
110
-
111
- const parsed = extractManifestJson(comment.body);
112
- if (!parsed) {
113
- Logger.error(
114
- `[render-manifest] dispatch-manifest comment #${comment.id} on Epic #${epicId} did not contain a parseable JSON block.`,
115
- );
116
- process.exit(1);
117
- }
118
-
119
- const { mdPath, jsonPath } = writeRenderedManifest({
120
- epicId,
121
- body: comment.body,
122
- parsed,
123
- });
124
-
125
- const storyCount = Array.isArray(parsed.stories) ? parsed.stories.length : 0;
126
- Logger.info(
127
- `[render-manifest] ✅ Rendered ${storyCount} story(ies) for Epic #${epicId}:\n` +
128
- ` - ${mdPath}\n` +
129
- ` - ${jsonPath}`,
130
- );
131
- return { mdPath, jsonPath, stories: storyCount };
132
- }
133
-
134
- async function main() {
135
- const { values } = parseArgs({
136
- options: { epic: { type: 'string' } },
137
- strict: false,
138
- });
139
- const epicId = Number.parseInt(values.epic ?? '', 10);
140
- await renderManifestFromComment({ epicId });
141
- }
142
-
143
- runAsCli(import.meta.url, main, { source: 'render-manifest' });
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /* node:coverage ignore file */
4
-
5
- /**
6
- * run-audit-suite.js — CLI + SDK for running a list of audit workflows.
7
- *
8
- * Successor to the retired mandrel MCP tools. See ADR 20260424-702a in
9
- * docs/decisions.md for the migration table.
10
- *
11
- * Story #963 (Epic #946) decomposed the previous ~445 LOC monolith (MI=0.0)
12
- * into focused helpers under `lib/audit-suite/`:
13
- * - frontmatter.js — `extractFrontmatter` + summary derivation
14
- * - substitutions.js — `{{key}}` templating + CLI implicit defaults
15
- * - workflow-loader.js — filesystem IO for workflow markdown + artifacts
16
- * - findings.js — severity histogram aggregation
17
- * - cli.js — `parseAuditList`, `parseArgv`, HELP banner
18
- * - runner.js — `runAuditSuite` aggregation core
19
- *
20
- * This file is the pipeline entry-point: it parses argv, glues the
21
- * helpers together, writes the JSON envelope to stdout, and re-exports the
22
- * symbols that downstream callers (`audit-orchestrator.js` and the test
23
- * suite) already import from this path.
24
- *
25
- * Usage:
26
- * node .agents/scripts/run-audit-suite.js \
27
- * --audits <comma-list> [--ticket <id>] [--base-branch main] \
28
- * [--substitution key=value]...
29
- *
30
- * Output: a single JSON object on stdout matching the MCP envelope:
31
- * { metadata: { ... }, findings: [...], workflows: [...] }
32
- *
33
- * Exit codes:
34
- * 0 — suite completed (findings entries are not failures)
35
- * non-zero — argument or substitution validation failure (error on stderr)
36
- */
37
-
38
- import { HELP, parseArgv, parseAuditList } from './lib/audit-suite/cli.js';
39
- import { runAuditSuite } from './lib/audit-suite/runner.js';
40
- import {
41
- applyImplicitSubstitutions,
42
- parseSubstitutionPairs,
43
- } from './lib/audit-suite/substitutions.js';
44
- import { runAsCli } from './lib/cli-utils.js';
45
-
46
- export { parseArgv, parseAuditList } from './lib/audit-suite/cli.js';
47
- export { aggregateSummary } from './lib/audit-suite/findings.js';
48
- export {
49
- extractFrontmatter,
50
- summarizeWorkflow,
51
- } from './lib/audit-suite/frontmatter.js';
52
- // --- Re-exports preserved for back-compat with existing import sites ---
53
- // `audit-orchestrator.js` and the test suite import these from
54
- // `run-audit-suite.js`. Keep them re-exported here so the decomposition
55
- // is internal-only.
56
- export { runAuditSuite } from './lib/audit-suite/runner.js';
57
- export {
58
- applyImplicitSubstitutions,
59
- applySubstitutions,
60
- } from './lib/audit-suite/substitutions.js';
61
-
62
- function abort(message, code = 2) {
63
- process.stderr.write(message);
64
- process.exit(code);
65
- }
66
-
67
- export async function main(argv = process.argv.slice(2)) {
68
- const values = parseArgv(argv);
69
-
70
- if (values.help) {
71
- process.stdout.write(HELP);
72
- return;
73
- }
74
-
75
- if (!values.audits) {
76
- abort(`[run-audit-suite] --audits <comma-list> is required.\n${HELP}`);
77
- }
78
-
79
- const auditWorkflows = parseAuditList(values.audits);
80
- if (auditWorkflows.length === 0) {
81
- abort(
82
- `[run-audit-suite] --audits must contain at least one workflow name.\n`,
83
- );
84
- }
85
-
86
- const substitutions = parseSubstitutionPairs(values.substitution);
87
- applyImplicitSubstitutions(values, substitutions);
88
-
89
- const result = await runAuditSuite({
90
- auditWorkflows,
91
- substitutions,
92
- artifactPrefix: values.runId,
93
- });
94
- process.stdout.write(`${JSON.stringify(result)}\n`);
95
- }
96
-
97
- runAsCli(import.meta.url, main, { source: 'run-audit-suite' });
@@ -1,155 +0,0 @@
1
- #!/usr/bin/env node
2
- /* node:coverage ignore file */
3
-
4
- /**
5
- * select-audits.js — Thin CLI wrapper around the audit-suite `selectAudits`
6
- * SDK in `lib/audit-suite/`.
7
- *
8
- * Successor to the retired mandrel MCP tools. See ADR 20260424-702a
9
- * in docs/decisions.md for the migration table.
10
- *
11
- * Story #1083 (Epic #1072) moved the rule-matching logic
12
- * (`matchesFilePattern`, `matchesAnyFilePattern`, `selectAudits`) into
13
- * `lib/audit-suite/selector.js` so the pipeline barrel can re-export
14
- * the SDK without importing upward from a top-level CLI. This file now
15
- * contains only argv parsing, provider construction, JSON stdout, and
16
- * degraded-mode exit-code mapping. Callers that need the rule-matching
17
- * SDK import it directly from `lib/audit-suite/index.js`.
18
- *
19
- * Usage:
20
- * node .agents/scripts/select-audits.js \
21
- * --ticket <id> --gate <gate> [--base-branch main]
22
- *
23
- * Output: a single JSON object on stdout matching the MCP envelope:
24
- * { selectedAudits, ticketId, gate, context: { changedFilesCount, ticketTitle } }
25
- *
26
- * Exit codes:
27
- * 0 — selection succeeded
28
- * non-zero — validation or provider failure (error on stderr)
29
- */
30
-
31
- import { selectAudits } from './lib/audit-suite/index.js';
32
- import { defineFlags } from './lib/cli-args.js';
33
- import { runAsCli } from './lib/cli-utils.js';
34
- import { resolveConfig } from './lib/config-resolver.js';
35
- import { isDegraded } from './lib/degraded-mode.js';
36
- import { createProvider } from './lib/provider-factory.js';
37
-
38
- const HELP = `Usage: node .agents/scripts/select-audits.js \\
39
- --ticket <id> --gate <gate> [--base-branch main]
40
-
41
- Flags:
42
- --ticket GitHub issue number to evaluate (required).
43
- --gate Audit gate (e.g. gate1, gate2, gate3, gate4) (required).
44
- --base-branch Branch to diff against for changed-file matching (default: main).
45
- --help Show this message.
46
- `;
47
-
48
- export function parseArgv(argv) {
49
- const { values } = defineFlags(
50
- {
51
- ticket: { type: 'integer', alias: 'ticketId' },
52
- gate: { type: 'string' },
53
- 'base-branch': { type: 'string', default: 'main' },
54
- 'gate-mode': { type: 'boolean' },
55
- help: { type: 'boolean' },
56
- },
57
- argv,
58
- );
59
- return values;
60
- }
61
-
62
- /**
63
- * Orchestration body of `main` extracted as a sibling exported function so
64
- * the parse / validate / dispatch / classify-degraded ladder is unit-testable
65
- * without spawning a process. `main` becomes a thin shell: parse → call this
66
- * → render → exit. CLI surface unchanged (same flags, same exit codes, same
67
- * stdout JSON schema).
68
- *
69
- * @param {ReturnType<typeof parseArgv>} values
70
- * @param {{
71
- * resolveConfig?: () => object,
72
- * createProvider?: (config: object) => object,
73
- * selectAudits?: typeof selectAudits,
74
- * env?: Record<string, string|undefined>,
75
- * help?: string,
76
- * }} [deps]
77
- * @returns {Promise<{ exitCode: number, result: object }>}
78
- * `result.kind` is one of: `'help'`, `'validation-error'`, `'envelope'`.
79
- * `'envelope'` carries the `selectAudits` JSON the CLI prints verbatim to
80
- * stdout. Validation errors and help requests do not print to stdout.
81
- */
82
- export async function runSelectAuditsCli(values, deps = {}) {
83
- const helpText = deps.help ?? HELP;
84
- if (values.help) {
85
- return { exitCode: 0, result: { kind: 'help', text: helpText } };
86
- }
87
-
88
- const { ticketId, gate, baseBranch, gateMode } = values;
89
-
90
- if (!Number.isFinite(ticketId) || ticketId <= 0) {
91
- return {
92
- exitCode: 2,
93
- result: {
94
- kind: 'validation-error',
95
- message: '[select-audits] --ticket <id> is required.',
96
- help: helpText,
97
- },
98
- };
99
- }
100
- if (!gate) {
101
- return {
102
- exitCode: 2,
103
- result: {
104
- kind: 'validation-error',
105
- message: '[select-audits] --gate <gate> is required.',
106
- help: helpText,
107
- },
108
- };
109
- }
110
-
111
- const cfg = deps.resolveConfig ? deps.resolveConfig() : resolveConfig();
112
- const provider = deps.createProvider
113
- ? deps.createProvider(cfg)
114
- : createProvider(cfg);
115
- const env = deps.env ?? process.env;
116
- const runner = deps.selectAudits ?? selectAudits;
117
-
118
- const gateModeOpts = {
119
- argv: gateMode ? ['--gate-mode'] : [],
120
- env,
121
- };
122
- const envelope = await runner({
123
- ticketId,
124
- gate,
125
- provider,
126
- baseBranch,
127
- gateModeOpts,
128
- });
129
- return {
130
- exitCode: isDegraded(envelope) ? 1 : 0,
131
- result: { kind: 'envelope', envelope },
132
- };
133
- }
134
-
135
- export async function main(argv = process.argv.slice(2)) {
136
- const values = parseArgv(argv);
137
- const { exitCode, result } = await runSelectAuditsCli(values);
138
-
139
- if (result.kind === 'help') {
140
- process.stdout.write(result.text);
141
- return;
142
- }
143
- if (result.kind === 'validation-error') {
144
- process.stderr.write(`${result.message}\n${result.help}`);
145
- process.exit(exitCode);
146
- }
147
- // kind === 'envelope' — degraded envelopes still print to stdout so
148
- // callers can parse `degraded: true`, then exit non-zero so shell
149
- // pipelines see the soft-fail. Gate-mode throws upstream and runAsCli's
150
- // default handler exits 1.
151
- process.stdout.write(`${JSON.stringify(result.envelope)}\n`);
152
- if (exitCode !== 0) process.exit(exitCode);
153
- }
154
-
155
- runAsCli(import.meta.url, main, { source: 'select-audits' });