@mmerterden/multi-agent-pipeline 20.8.2 → 20.9.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/CHANGELOG.md +43 -0
- package/docs/facts.json +1 -1
- package/install/_common.mjs +49 -0
- package/install/_dev-only-files.mjs +1 -0
- package/install/catalog-history.json +1 -0
- package/install/claude.mjs +13 -7
- package/manifest.json +40 -30
- package/package.json +3 -2
- package/pipeline/lib/claude-md-links.mjs +328 -0
- package/pipeline/lib/owned-path-gate.mjs +699 -0
- package/pipeline/lib/repo-profile-derive.mjs +1771 -0
- package/pipeline/lib/repo-profile.mjs +780 -0
- package/pipeline/lib/stack-detect.sh +59 -19
- package/pipeline/lib/unattended.mjs +17 -0
- package/pipeline/multi-agent-refs/features/repo-profile.md +96 -0
- package/pipeline/multi-agent-refs/features/review-decision.md +18 -13
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +179 -33
- package/pipeline/multi-agent-refs/outside-the-pipeline.md +33 -11
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +26 -12
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +24 -13
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +16 -4
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +8 -0
- package/pipeline/rules/outside-the-pipeline.md +6 -1
- package/pipeline/schemas/agent-state.schema.json +66 -2
- package/pipeline/schemas/phases.json +4 -4
- package/pipeline/schemas/repo-profile.schema.json +1107 -0
- package/pipeline/schemas/token-budget.json +4 -4
- package/pipeline/scripts/agent-guard.py +30 -0
- package/pipeline/scripts/owned-path-gate.mjs +205 -0
- package/pipeline/scripts/pre-commit-check.sh +151 -1
- package/pipeline/scripts/repo-profile.mjs +244 -0
- package/pipeline/scripts/review-decision-gate.mjs +42 -18
- package/pipeline/scripts/skill-candidates.mjs +882 -0
- package/pipeline/scripts/unattended_policy.py +90 -0
- package/pipeline/scripts/usage-report.mjs +36 -6
- package/pipeline/skills/.skill-manifest.json +1 -1
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* failing-test its `verification` (Step 3.7, verify-by-test) is
|
|
14
14
|
* `confirmed` and `evidencePath` is a non-empty log inside the
|
|
15
15
|
* checkout that shows a failing test and no pass;
|
|
16
|
-
* test-integrity it is the finding test-integrity-gate.mjs produced
|
|
17
|
-
* file
|
|
16
|
+
* test-integrity it is the finding test-integrity-gate.mjs produced: the
|
|
17
|
+
* same file and the same tag or fingerprint, a command's
|
|
18
|
+
* output and not a model's;
|
|
19
|
+
* owned-path it is the finding owned-path-gate.mjs produced, matched
|
|
20
|
+
* the same way, for the same reason.
|
|
18
21
|
*
|
|
19
22
|
* Anything else is lowered to `important`, stays in `accepted[]` (triage judged
|
|
20
23
|
* it real and in scope, so the rework still fixes it) and is listed with its
|
|
@@ -40,14 +43,15 @@
|
|
|
40
43
|
*
|
|
41
44
|
* Usage:
|
|
42
45
|
* review-decision-gate.mjs <triage.json> [--state <agent-state.json>] [--iteration <n>]
|
|
43
|
-
* [--source <reviewer-output.json>]... [--integrity <
|
|
46
|
+
* [--source <reviewer-output.json>]... [--integrity <gate-findings.json>]...
|
|
44
47
|
* [--repo <dir>] [--dry-run] [--json]
|
|
45
48
|
* review-decision-gate.mjs --rebuttal-allowed [--state <agent-state.json>] [--json]
|
|
46
49
|
*
|
|
47
50
|
* Exit (gates active): 0 decided · 1 a rebuttal round ran, or --rebuttal-allowed refused ·
|
|
48
51
|
* 2 usage · 3 unreadable input, or an accepted blocker with no reviewer record (an empty
|
|
49
|
-
* `reviewers: []` is no record
|
|
50
|
-
* and a missing or empty --source is an audit
|
|
52
|
+
* `reviewers: []` is no record, and a declared --integrity file that does not exist). An
|
|
53
|
+
* empty --integrity file is no findings, and a missing or empty --source is an audit
|
|
54
|
+
* that did not run.
|
|
51
55
|
* Attended: 0, and 2 on a usage error.
|
|
52
56
|
*
|
|
53
57
|
* @module pipeline/scripts/review-decision-gate
|
|
@@ -171,6 +175,9 @@ export function rebuttalRecorded(reviewers) {
|
|
|
171
175
|
);
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
/** Tags of findings a deterministic gate produced, and the basis each one records. */
|
|
179
|
+
const GATE_BASIS = { test_integrity: "test-integrity", owned_path: "owned-path" };
|
|
180
|
+
|
|
174
181
|
/**
|
|
175
182
|
* Apply the decision rule. Pure: the input is not mutated.
|
|
176
183
|
*
|
|
@@ -181,7 +188,7 @@ export function decide({ triage, reviewers, integrity = [], root }) {
|
|
|
181
188
|
const next = structuredClone(triage);
|
|
182
189
|
const sources = (Array.isArray(reviewers) ? reviewers : []).map(blockingOf);
|
|
183
190
|
const gateFindings = (Array.isArray(integrity) ? integrity : []).filter(
|
|
184
|
-
(f) => f && f.tag
|
|
191
|
+
(f) => f && GATE_BASIS[f.tag],
|
|
185
192
|
);
|
|
186
193
|
const kept = [];
|
|
187
194
|
const downgraded = [];
|
|
@@ -198,8 +205,13 @@ export function decide({ triage, reviewers, integrity = [], root }) {
|
|
|
198
205
|
kept.push({ ...id, basis: "corroborated", sources: count });
|
|
199
206
|
continue;
|
|
200
207
|
}
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
const gate = gateFindings.find(
|
|
209
|
+
(g) =>
|
|
210
|
+
normalizeFile(g.file) === normalizeFile(f.file) &&
|
|
211
|
+
(f.tag === g.tag || fingerprintOf(f) === fingerprintOf(g)),
|
|
212
|
+
);
|
|
213
|
+
if (gate) {
|
|
214
|
+
kept.push({ ...id, basis: GATE_BASIS[gate.tag], sources: count });
|
|
203
215
|
continue;
|
|
204
216
|
}
|
|
205
217
|
const v = f.verification;
|
|
@@ -236,38 +248,50 @@ function readJson(p, what) {
|
|
|
236
248
|
}
|
|
237
249
|
|
|
238
250
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
251
|
+
* A deterministic gate's findings (Step 1.76 test-integrity, the owned-path
|
|
252
|
+
* gate). An empty file is the gate's own "no findings" output. Both gates
|
|
253
|
+
* always write their file, so a declared file that does not exist means the
|
|
254
|
+
* gate failed before writing, and that is an error rather than no findings;
|
|
255
|
+
* so is content that is not JSON.
|
|
241
256
|
*/
|
|
242
257
|
function readIntegrity(p) {
|
|
243
|
-
if (!
|
|
258
|
+
if (!existsSync(p)) {
|
|
259
|
+
throw new Error(`gate findings ${p} do not exist: the gate did not run or failed`);
|
|
260
|
+
}
|
|
244
261
|
let body;
|
|
245
262
|
try {
|
|
246
263
|
body = readFileSync(p, "utf8");
|
|
247
264
|
} catch (err) {
|
|
248
|
-
throw new Error(`cannot read
|
|
265
|
+
throw new Error(`cannot read gate findings ${p}: ${err.message}`, { cause: err });
|
|
249
266
|
}
|
|
250
267
|
if (!body.trim()) return [];
|
|
251
268
|
try {
|
|
252
269
|
const doc = JSON.parse(body);
|
|
253
270
|
return Array.isArray(doc?.findings) ? doc.findings : [];
|
|
254
271
|
} catch (err) {
|
|
255
|
-
throw new Error(`cannot read
|
|
272
|
+
throw new Error(`cannot read gate findings ${p}: ${err.message}`, { cause: err });
|
|
256
273
|
}
|
|
257
274
|
}
|
|
258
275
|
|
|
259
276
|
function parse(argv) {
|
|
260
|
-
const opts = {
|
|
261
|
-
|
|
277
|
+
const opts = {
|
|
278
|
+
source: [],
|
|
279
|
+
integrity: [],
|
|
280
|
+
json: false,
|
|
281
|
+
dryRun: false,
|
|
282
|
+
rebuttalAllowed: false,
|
|
283
|
+
triage: null,
|
|
284
|
+
};
|
|
285
|
+
const single = new Set(["--state", "--iteration", "--repo"]);
|
|
262
286
|
for (let i = 0; i < argv.length; i++) {
|
|
263
287
|
const a = argv[i];
|
|
264
288
|
if (a === "--json") opts.json = true;
|
|
265
289
|
else if (a === "--dry-run") opts.dryRun = true;
|
|
266
290
|
else if (a === "--rebuttal-allowed") opts.rebuttalAllowed = true;
|
|
267
|
-
else if (a === "--source" || single.has(a)) {
|
|
291
|
+
else if (a === "--source" || a === "--integrity" || single.has(a)) {
|
|
268
292
|
const v = argv[++i];
|
|
269
293
|
if (v === undefined) throw new Error(`${a} needs a value`);
|
|
270
|
-
if (a === "--source") opts.
|
|
294
|
+
if (a === "--source" || a === "--integrity") opts[a.slice(2)].push(v);
|
|
271
295
|
else opts[a.slice(2)] = v;
|
|
272
296
|
} else if (a.startsWith("--")) throw new Error(`unexpected argument ${a}`);
|
|
273
297
|
else if (opts.triage) throw new Error(`one triage file only, got ${a} too`);
|
|
@@ -341,7 +365,7 @@ async function main(argv) {
|
|
|
341
365
|
throw new Error(`${opts.triage} is not a triage output (no accepted[])`);
|
|
342
366
|
}
|
|
343
367
|
const reviewers = reviewerRecord(opts, state);
|
|
344
|
-
const integrity =
|
|
368
|
+
const integrity = opts.integrity.flatMap(readIntegrity);
|
|
345
369
|
if (rebuttalRecorded(reviewers)) {
|
|
346
370
|
result = {
|
|
347
371
|
gate: GATE,
|