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
|
@@ -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' });
|