@ludecker/aaac 1.1.6 → 1.2.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/package.json +9 -9
- package/src/cli.mjs +0 -0
- package/src/run-engine/debug-run.mjs +0 -0
- package/src/run-engine/log-dump.mjs +0 -0
- package/src/run-engine/log-trace.mjs +0 -0
- package/templates/cursor/aaac/enforcement.json +84 -3
- package/templates/cursor/aaac/graph.project.yaml +28 -0
- package/templates/cursor/aaac/lifecycle/lifecycle.json +14 -0
- package/templates/cursor/aaac/lifecycle/phases.json +7 -1
- package/templates/cursor/aaac/ontology.json +1 -0
- package/templates/cursor/aaac/project.config.json +36 -0
- package/templates/cursor/aaac/scripts/remediation/auto-check-swarm-synthesis.mjs +75 -0
- package/templates/cursor/aaac/scripts/remediation/auto-dispatch-queue-from-health.mjs +78 -0
- package/templates/cursor/aaac/scripts/remediation/bootstrap-autonomous.mjs +113 -0
- package/templates/cursor/aaac/scripts/remediation/capture-verify-baseline.mjs +66 -0
- package/templates/cursor/aaac/scripts/remediation/capture-wave-snapshot.mjs +79 -0
- package/templates/cursor/aaac/scripts/remediation/check-swarm-raw.template.json +26 -0
- package/templates/cursor/aaac/scripts/remediation/classify-fallow-issues.mjs +77 -0
- package/templates/cursor/aaac/scripts/remediation/classify-verify-failure.mjs +176 -0
- package/templates/cursor/aaac/scripts/remediation/compute-satisfaction.mjs +344 -0
- package/templates/cursor/aaac/scripts/remediation/debt-sweep-gate.mjs +202 -0
- package/templates/cursor/aaac/scripts/remediation/dispatch-rules.json +44 -0
- package/templates/cursor/aaac/scripts/remediation/fallow-fp-rules.json +87 -0
- package/templates/cursor/aaac/scripts/remediation/fallow-scan.mjs +219 -0
- package/templates/cursor/aaac/scripts/remediation/handle-yield.mjs +240 -0
- package/templates/cursor/aaac/scripts/remediation/init-campaign.mjs +211 -0
- package/templates/cursor/aaac/scripts/remediation/lib/autonomous-mode.mjs +63 -0
- package/templates/cursor/aaac/scripts/remediation/lib/campaign-focus.mjs +87 -0
- package/templates/cursor/aaac/scripts/remediation/lib/fallow-classifier.mjs +190 -0
- package/templates/cursor/aaac/scripts/remediation/lib/fallow-health-targets.mjs +56 -0
- package/templates/cursor/aaac/scripts/remediation/lib/fallow-metrics.mjs +119 -0
- package/templates/cursor/aaac/scripts/remediation/lib/invoke-cursor-agent.mjs +51 -0
- package/templates/cursor/aaac/scripts/remediation/lib/reconcile-run-manifest.mjs +41 -0
- package/templates/cursor/aaac/scripts/remediation/lib/regression-analysis.mjs +55 -0
- package/templates/cursor/aaac/scripts/remediation/lib/remediation-config.mjs +69 -0
- package/templates/cursor/aaac/scripts/remediation/lib/remediation-progress.mjs +58 -0
- package/templates/cursor/aaac/scripts/remediation/lib/remediation-watch-loop.mjs +168 -0
- package/templates/cursor/aaac/scripts/remediation/lib/runner-exec.mjs +156 -0
- package/templates/cursor/aaac/scripts/remediation/lib/runner-state.mjs +145 -0
- package/templates/cursor/aaac/scripts/remediation/lib/verify-metrics.mjs +205 -0
- package/templates/cursor/aaac/scripts/remediation/merge-check-swarm.mjs +257 -0
- package/templates/cursor/aaac/scripts/remediation/plan-waves-from-queue.mjs +85 -0
- package/templates/cursor/aaac/scripts/remediation/prepare-check-context.mjs +148 -0
- package/templates/cursor/aaac/scripts/remediation/record-fallow-fp.mjs +107 -0
- package/templates/cursor/aaac/scripts/remediation/record-iteration-step.mjs +56 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-cli.mjs +157 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-cursor-watch.sh +10 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-runner-daemon.sh +13 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-runner.mjs +748 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-yield-watcher.mjs +40 -0
- package/templates/cursor/aaac/scripts/remediation/remediator-gate.mjs +405 -0
- package/templates/cursor/aaac/scripts/remediation/repair-fallow-start-baseline.mjs +118 -0
- package/templates/cursor/aaac/scripts/remediation/runner-health-check.mjs +164 -0
- package/templates/cursor/aaac/scripts/remediation/satisfaction-loop-gate.mjs +286 -0
- package/templates/cursor/aaac/scripts/remediation/validate-campaign-complete.mjs +191 -0
- package/templates/cursor/aaac/scripts/remediation/verify-remediation-iteration.mjs +112 -0
- package/templates/cursor/aaac/scripts/run-engine/debug-run.mjs +0 -0
- package/templates/cursor/aaac/scripts/run-engine/log-dump.mjs +0 -0
- package/templates/cursor/aaac/scripts/run-engine/log-trace.mjs +0 -0
- package/templates/cursor/agents/remediation-check-app-inventory.md +32 -0
- package/templates/cursor/agents/remediation-check-app-ssot.md +24 -0
- package/templates/cursor/agents/remediation-check-app-trace.md +29 -0
- package/templates/cursor/agents/remediation-check-architecture-boundaries.md +21 -0
- package/templates/cursor/agents/remediation-check-architecture-decomposition.md +25 -0
- package/templates/cursor/agents/remediation-check-architecture-deps.md +23 -0
- package/templates/cursor/agents/remediation-check-risk.md +37 -0
- package/templates/cursor/agents/remediation-e2e-gate.md +30 -0
- package/templates/cursor/agents/remediation-remediator.md +69 -0
- package/templates/cursor/commands/remediate-app.md +212 -0
- package/templates/cursor/hooks/aaac-before-submit.sh +0 -0
- package/templates/cursor/hooks/aaac-pre-tool.sh +0 -0
- package/templates/cursor/hooks/aaac-stop.sh +0 -0
- package/templates/cursor/hooks/aaac-subagent-start.sh +0 -0
- package/templates/cursor/skills/shared/remediation/SKILL.md +51 -0
- package/templates/cursor/skills/shared/remediation/babysit/SKILL.md +223 -0
- package/templates/cursor/skills/shared/remediation/check-swarm/SKILL.md +114 -0
- package/templates/cursor/skills/shared/remediation/orchestrator/SKILL.md +275 -0
- package/templates/cursor/skills/shared/remediation/orchestrator/contract.yaml +116 -0
- package/templates/docs/agentic_architecture.md +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Snapshot verify metrics immediately before a cleanup wave executes.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node capture-wave-snapshot.mjs --campaign-id <id> --iteration <n> --wave-index <w>
|
|
7
|
+
*/
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { spawnSync } from "child_process";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { REPO_ROOT, isoNow, writeJson } from "../run-engine/lib.mjs";
|
|
13
|
+
import { summarizeMetrics } from "./lib/verify-metrics.mjs";
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
|
|
17
|
+
const VERIFY_SCRIPT = path.join(__dirname, "verify-remediation-iteration.mjs");
|
|
18
|
+
|
|
19
|
+
function parseArgs(argv) {
|
|
20
|
+
const out = { campaignId: null, iteration: 0, waveIndex: 0 };
|
|
21
|
+
for (let i = 0; i < argv.length; i++) {
|
|
22
|
+
if (argv[i] === "--campaign-id") out.campaignId = argv[++i];
|
|
23
|
+
else if (argv[i] === "--iteration") out.iteration = Number(argv[++i]);
|
|
24
|
+
else if (argv[i] === "--wave-index") out.waveIndex = Number(argv[++i]);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const args = parseArgs(process.argv.slice(2));
|
|
30
|
+
if (!args.campaignId) {
|
|
31
|
+
console.error("capture-wave-snapshot: --campaign-id required");
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
spawnSync(
|
|
36
|
+
process.execPath,
|
|
37
|
+
[
|
|
38
|
+
VERIFY_SCRIPT,
|
|
39
|
+
"--campaign-id",
|
|
40
|
+
args.campaignId,
|
|
41
|
+
"--iteration",
|
|
42
|
+
String(args.iteration),
|
|
43
|
+
"--mode",
|
|
44
|
+
"wave",
|
|
45
|
+
"--label",
|
|
46
|
+
`pre-wave-${args.waveIndex}`,
|
|
47
|
+
],
|
|
48
|
+
{ encoding: "utf8" },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const iterDir = path.join(
|
|
52
|
+
CAMPAIGNS_ROOT,
|
|
53
|
+
args.campaignId,
|
|
54
|
+
"iterations",
|
|
55
|
+
String(args.iteration),
|
|
56
|
+
);
|
|
57
|
+
const prePath = path.join(iterDir, `wave-${args.waveIndex}-pre.json`);
|
|
58
|
+
const verifyPath = path.join(iterDir, "verify-wave.json");
|
|
59
|
+
|
|
60
|
+
let report = null;
|
|
61
|
+
try {
|
|
62
|
+
report = JSON.parse(fs.readFileSync(verifyPath, "utf8"));
|
|
63
|
+
} catch {
|
|
64
|
+
console.error("capture-wave-snapshot: missing verify-wave.json");
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const snapshot = {
|
|
69
|
+
captured_at: isoNow(),
|
|
70
|
+
kind: "pre_wave",
|
|
71
|
+
wave_index: args.waveIndex,
|
|
72
|
+
iteration: args.iteration,
|
|
73
|
+
summary: summarizeMetrics(report),
|
|
74
|
+
metrics: report.metrics,
|
|
75
|
+
};
|
|
76
|
+
writeJson(prePath, snapshot);
|
|
77
|
+
fs.copyFileSync(verifyPath, path.join(iterDir, `wave-${args.waveIndex}-pre-verify.json`));
|
|
78
|
+
|
|
79
|
+
console.log(JSON.stringify({ ok: true, snapshot_path: prePath, summary: snapshot.summary }));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"campaign_id": "campaign_EXAMPLE",
|
|
3
|
+
"iteration": 0,
|
|
4
|
+
"collected_at": "ISO8601",
|
|
5
|
+
"agents": [
|
|
6
|
+
{
|
|
7
|
+
"agent_id": "remediation-check-app-inventory",
|
|
8
|
+
"command_mirror": "check-app",
|
|
9
|
+
"answer": "partial",
|
|
10
|
+
"confidence": "high",
|
|
11
|
+
"false_positives": [
|
|
12
|
+
{
|
|
13
|
+
"path": "src/hooks/useCryptoPriceWorker.ts",
|
|
14
|
+
"export_name": null,
|
|
15
|
+
"reason": "worker_hook_runtime",
|
|
16
|
+
"evidence": "src/hooks/useCryptoPriceWorker.ts:1"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"protected_paths": ["src/hooks/useCryptoPriceWorker.ts"],
|
|
20
|
+
"do_not_delete": [],
|
|
21
|
+
"safe_to_fix": [],
|
|
22
|
+
"findings": [],
|
|
23
|
+
"gaps": []
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Classify Fallow scan findings and persist actionable vs false-positive SSOT.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node classify-fallow-issues.mjs --campaign-id <id> --iteration <n> [--save-actionable-baseline]
|
|
7
|
+
*/
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { REPO_ROOT, isoNow, readJson, writeJson } from "../run-engine/lib.mjs";
|
|
11
|
+
import { classifyFallowScan, loadFpRules } from "./lib/fallow-classifier.mjs";
|
|
12
|
+
|
|
13
|
+
const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const out = { campaignId: null, iteration: 0, saveActionableBaseline: false };
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const a = argv[i];
|
|
19
|
+
if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
20
|
+
else if (a === "--iteration") out.iteration = Number(argv[++i]);
|
|
21
|
+
else if (a === "--save-actionable-baseline") out.saveActionableBaseline = true;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const args = parseArgs(process.argv.slice(2));
|
|
27
|
+
if (!args.campaignId) {
|
|
28
|
+
console.error("classify-fallow-issues: --campaign-id required");
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const campaignDir = path.join(CAMPAIGNS_ROOT, args.campaignId);
|
|
33
|
+
const iterDir = path.join(campaignDir, "iterations", String(args.iteration));
|
|
34
|
+
const scanPath = path.join(iterDir, "fallow-scan.json");
|
|
35
|
+
const scan = readJson(scanPath, null);
|
|
36
|
+
|
|
37
|
+
if (!scan) {
|
|
38
|
+
console.error(`classify-fallow-issues: missing ${scanPath}`);
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const classification = classifyFallowScan({
|
|
43
|
+
scan,
|
|
44
|
+
campaignDir,
|
|
45
|
+
rules: loadFpRules(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const outPath = path.join(iterDir, "fallow-classification.json");
|
|
49
|
+
writeJson(outPath, classification);
|
|
50
|
+
|
|
51
|
+
const actionableBaselinePath = path.join(campaignDir, "fallow-start-actionable-baseline.json");
|
|
52
|
+
if (args.saveActionableBaseline && !fs.existsSync(actionableBaselinePath)) {
|
|
53
|
+
writeJson(actionableBaselinePath, {
|
|
54
|
+
actionable_total: classification.summary.actionable_total,
|
|
55
|
+
raw_total: classification.summary.raw_total,
|
|
56
|
+
false_positive_total: classification.summary.false_positive_total,
|
|
57
|
+
recorded_at: isoNow(),
|
|
58
|
+
immutable: true,
|
|
59
|
+
source: "classify-fallow-issues",
|
|
60
|
+
iteration: args.iteration,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const campaign = readJson(path.join(campaignDir, "campaign.json"), null);
|
|
65
|
+
if (campaign) {
|
|
66
|
+
campaign.current = campaign.current ?? {};
|
|
67
|
+
campaign.current.fallow_raw_total = classification.summary.raw_total;
|
|
68
|
+
campaign.current.fallow_actionable_total = classification.summary.actionable_total;
|
|
69
|
+
campaign.current.fallow_false_positive_total = classification.summary.false_positive_total;
|
|
70
|
+
campaign.updated_at = isoNow();
|
|
71
|
+
writeJson(path.join(campaignDir, "campaign.json"), campaign);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const journal = `\n- **Fallow classified** iter ${args.iteration}: raw=${classification.summary.raw_total}, actionable=${classification.summary.actionable_total}, false_positive=${classification.summary.false_positive_total}, review=${classification.summary.review_total}\n`;
|
|
75
|
+
fs.appendFileSync(path.join(campaignDir, "journal.md"), journal);
|
|
76
|
+
|
|
77
|
+
console.log(JSON.stringify({ ok: true, classification: classification.summary, path: outPath }));
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Classify verify-remediation report failures → fix-module / fix-bug handoffs.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node classify-verify-failure.mjs --report <path> --campaign-id <id> [--iteration n] [--wave-index n] [--attempt n]
|
|
7
|
+
*/
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { REPO_ROOT, readJson } from "../run-engine/lib.mjs";
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const RULES_PATH = path.join(__dirname, "dispatch-rules.json");
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const out = {
|
|
18
|
+
reportPath: null,
|
|
19
|
+
campaignId: "",
|
|
20
|
+
iteration: 0,
|
|
21
|
+
waveIndex: null,
|
|
22
|
+
attempt: 1,
|
|
23
|
+
};
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
if (a === "--report") out.reportPath = argv[++i];
|
|
27
|
+
else if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
28
|
+
else if (a === "--iteration") out.iteration = Number(argv[++i]);
|
|
29
|
+
else if (a === "--wave-index") out.waveIndex = Number(argv[++i]);
|
|
30
|
+
else if (a === "--attempt") out.attempt = Number(argv[++i]);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadRules() {
|
|
36
|
+
return readJson(RULES_PATH, { layers: {}, evidence_priority: [] });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readLogFile(logPath) {
|
|
40
|
+
if (!logPath || !fs.existsSync(logPath)) return "";
|
|
41
|
+
try {
|
|
42
|
+
return fs.readFileSync(logPath, "utf8");
|
|
43
|
+
} catch {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function evidenceText(step) {
|
|
49
|
+
if (!step) return "";
|
|
50
|
+
const fromLog = readLogFile(step.log_path);
|
|
51
|
+
if (fromLog) return fromLog;
|
|
52
|
+
return [
|
|
53
|
+
step.stdout_full,
|
|
54
|
+
step.stderr_full,
|
|
55
|
+
step.stderr_tail,
|
|
56
|
+
step.stdout_tail,
|
|
57
|
+
step.detail,
|
|
58
|
+
]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join("\n");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractFilePaths(text) {
|
|
64
|
+
const paths = new Set();
|
|
65
|
+
const patterns = [
|
|
66
|
+
/(?:frontend|backend)\/[^\s:'"]+\.(?:tsx?|jsx?|go)/g,
|
|
67
|
+
/src\/[^\s:'"]+\.(?:tsx?|jsx?)/g,
|
|
68
|
+
/internal\/[^\s:'"]+\.go/g,
|
|
69
|
+
/[^\s('"]+\.(?:test|spec)\.(?:tsx?|jsx?)/g,
|
|
70
|
+
];
|
|
71
|
+
for (const re of patterns) {
|
|
72
|
+
for (const m of text.matchAll(re)) {
|
|
73
|
+
paths.add(m[0].replace(/^[('"]+/, ""));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [...paths];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function inferDomain(layer, evidence, ruleDomain) {
|
|
80
|
+
const paths = extractFilePaths(evidence);
|
|
81
|
+
if (paths.some((p) => p.startsWith("backend/") || p.includes("/internal/") || p.endsWith(".go"))) {
|
|
82
|
+
return "backend";
|
|
83
|
+
}
|
|
84
|
+
if (paths.some((p) => p.startsWith("frontend/") || p.startsWith("src/"))) {
|
|
85
|
+
return "frontend";
|
|
86
|
+
}
|
|
87
|
+
return ruleDomain ?? "frontend";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function pickCommand(layer, rule, evidence) {
|
|
91
|
+
if (layer === "playwright" && /frontend not reachable|launch via/i.test(evidence)) {
|
|
92
|
+
return { command: null, level: "infrastructure", handoff: rule.layers?.playwright_infra?.handoff ?? "/launch-se100" };
|
|
93
|
+
}
|
|
94
|
+
if (layer === "playwright" && /does not provide an export|Failed to fetch dynamically imported module|Cannot find module/i.test(evidence)) {
|
|
95
|
+
return { command: rule.module_import_command ?? "fix-module", level: "code" };
|
|
96
|
+
}
|
|
97
|
+
if (layer === "vitest" && /\.(test|spec)\.(tsx?|jsx?)/.test(evidence) && !/src\/(?!.*\.test\.)/.test(evidence.split("\n")[0] ?? "")) {
|
|
98
|
+
return { command: rule.test_file_command ?? rule.command, level: "test" };
|
|
99
|
+
}
|
|
100
|
+
return { command: rule.command, level: rule.level ?? "code" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function fillTemplate(template, vars) {
|
|
104
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? ""));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function classifyReport(report, ctx) {
|
|
108
|
+
const rules = loadRules();
|
|
109
|
+
const failedLayers = rules.evidence_priority.filter((layer) => report[layer]?.status === "fail");
|
|
110
|
+
const handoffs = [];
|
|
111
|
+
|
|
112
|
+
for (const layer of failedLayers) {
|
|
113
|
+
const rule = rules.layers[layer];
|
|
114
|
+
if (!rule) continue;
|
|
115
|
+
const evidence = evidenceText(report[layer]);
|
|
116
|
+
const picked = pickCommand(layer, rule, evidence);
|
|
117
|
+
if (picked.level === "infrastructure") {
|
|
118
|
+
handoffs.push({
|
|
119
|
+
layer,
|
|
120
|
+
level: "infrastructure",
|
|
121
|
+
command: null,
|
|
122
|
+
domain: null,
|
|
123
|
+
intent: null,
|
|
124
|
+
handoff: picked.handoff,
|
|
125
|
+
evidence: evidence.slice(0, 16000),
|
|
126
|
+
evidence_truncated: evidence.length > 16000,
|
|
127
|
+
log_path: report[layer]?.log_path ?? null,
|
|
128
|
+
file_paths: extractFilePaths(evidence),
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const domain = inferDomain(layer, evidence, rule.domain);
|
|
133
|
+
const intent = fillTemplate(rule.intent_template, {
|
|
134
|
+
campaign_id: ctx.campaignId,
|
|
135
|
+
iteration: ctx.iteration,
|
|
136
|
+
wave_index: ctx.waveIndex ?? "n/a",
|
|
137
|
+
attempt: ctx.attempt,
|
|
138
|
+
evidence: evidence.slice(0, 16000),
|
|
139
|
+
});
|
|
140
|
+
handoffs.push({
|
|
141
|
+
layer,
|
|
142
|
+
level: picked.level,
|
|
143
|
+
command: picked.command,
|
|
144
|
+
domain,
|
|
145
|
+
intent,
|
|
146
|
+
slash_command: `/${picked.command} ${domain} "${intent.replace(/"/g, '\\"').slice(0, 500)}…"`,
|
|
147
|
+
evidence: evidence.slice(0, 16000),
|
|
148
|
+
evidence_truncated: evidence.length > 16000,
|
|
149
|
+
log_path: report[layer]?.log_path ?? null,
|
|
150
|
+
file_paths: extractFilePaths(evidence),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
status: handoffs.length ? "fail" : "pass",
|
|
156
|
+
failed_layers: failedLayers,
|
|
157
|
+
handoffs,
|
|
158
|
+
primary: handoffs.find((h) => h.command) ?? handoffs[0] ?? null,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const args = parseArgs(process.argv.slice(2));
|
|
163
|
+
if (!args.reportPath) {
|
|
164
|
+
console.error("classify-verify-failure: --report required");
|
|
165
|
+
process.exit(2);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const report = readJson(args.reportPath, null);
|
|
169
|
+
if (!report) {
|
|
170
|
+
console.error(`classify-verify-failure: cannot read report ${args.reportPath}`);
|
|
171
|
+
process.exit(2);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const classification = classifyReport(report, args);
|
|
175
|
+
console.log(JSON.stringify({ ok: true, classification }));
|
|
176
|
+
process.exit(classification.status === "pass" ? 0 : 1);
|