@ludecker/aaac 1.1.5 → 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/README.md +27 -12
- package/package.json +9 -9
- package/src/cli.mjs +19 -7
- package/src/generators/generate-commands.mjs +25 -1
- package/src/generators/generate-graph.mjs +9 -1
- package/src/lib/install.mjs +13 -1
- package/src/lib/sweep-project-docs.mjs +348 -0
- package/src/run-engine/advance-phase.mjs +23 -0
- package/src/run-engine/debug-run.mjs +0 -0
- package/src/run-engine/gate-write.mjs +13 -0
- package/src/run-engine/lib.mjs +153 -5
- package/src/run-engine/log-dump.mjs +0 -0
- package/src/run-engine/log-trace.mjs +0 -0
- package/templates/cursor/aaac/enforcement.json +96 -5
- package/templates/cursor/aaac/graph.project.yaml +44 -5
- package/templates/cursor/aaac/lifecycle/lifecycle.json +26 -0
- package/templates/cursor/aaac/lifecycle/phases.json +9 -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/advance-phase.mjs +23 -0
- package/templates/cursor/aaac/scripts/run-engine/debug-run.mjs +0 -0
- package/templates/cursor/aaac/scripts/run-engine/gate-write.mjs +13 -0
- package/templates/cursor/aaac/scripts/run-engine/lib.mjs +153 -5
- 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/doc-conformance.md +25 -0
- package/templates/cursor/agents/implementation-review.md +21 -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/agents/test-author.md +27 -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/rules/aaac-enforcement.mdc +10 -3
- package/templates/cursor/skills/shared/execution/SKILL.md +7 -3
- package/templates/cursor/skills/shared/governance/implementation/SKILL.md +396 -28
- package/templates/cursor/skills/shared/implementation-review/SKILL.md +49 -0
- package/templates/cursor/skills/shared/planning/SKILL.md +5 -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/cursor/skills/shared/test-authoring/SKILL.md +58 -0
- package/templates/cursor/skills/shared/testing/SKILL.md +6 -0
- package/templates/cursor/skills/shared/verbs/create/orchestrator/SKILL.md +5 -3
- package/templates/cursor/skills/shared/verbs/fix/orchestrator/SKILL.md +5 -3
- package/templates/cursor/skills/shared/verbs/update/orchestrator/SKILL.md +5 -3
- package/templates/cursor/skills/shared/verification/SKILL.md +5 -3
- package/templates/docs/agentic_architecture.md +169 -97
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Continuous remediation loop (machine sentinel logs).
|
|
4
|
+
* Prefer remediation-cli.mjs watch for human / Cursor terminal monitoring.
|
|
5
|
+
*/
|
|
6
|
+
import { isoNow } from "../run-engine/lib.mjs";
|
|
7
|
+
import { runRemediationWatchLoop } from "./lib/remediation-watch-loop.mjs";
|
|
8
|
+
|
|
9
|
+
const SENTINEL = "AGENT_REMEDIATION_WATCHER";
|
|
10
|
+
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
const out = { runId: null, campaignId: null, pollMs: 5000, maxRetries: 5 };
|
|
13
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14
|
+
const a = argv[i];
|
|
15
|
+
if (a === "--run-id") out.runId = argv[++i];
|
|
16
|
+
else if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
17
|
+
else if (a === "--poll-ms") out.pollMs = Number(argv[++i]);
|
|
18
|
+
else if (a === "--max-retries") out.maxRetries = Number(argv[++i]);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function log(event, detail = {}) {
|
|
24
|
+
const line = JSON.stringify({ at: isoNow(), event, ...detail });
|
|
25
|
+
console.log(`${SENTINEL} ${line}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const args = parseArgs(process.argv.slice(2));
|
|
29
|
+
if (!args.runId || !args.campaignId) {
|
|
30
|
+
console.error("Usage: remediation-yield-watcher.mjs --run-id <id> --campaign-id <id>");
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
runRemediationWatchLoop({
|
|
35
|
+
...args,
|
|
36
|
+
reporter: { onEvent: (event, detail) => log(event, detail) },
|
|
37
|
+
}).then((code) => process.exit(code)).catch((err) => {
|
|
38
|
+
log("fatal", { message: String(err?.message ?? err) });
|
|
39
|
+
process.exit(2);
|
|
40
|
+
});
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agentic OS remediator sub-loop with two-tier validation:
|
|
4
|
+
* wave — regression gate (pre-existing debt does not block wave promotion)
|
|
5
|
+
* debt — strict gate (all layers must pass; used by debt_sweep phase)
|
|
6
|
+
* iteration — strict (legacy alias for debt within an iteration)
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 — promote (wave regression-clean OR strict pass OR wave deferred to debt_sweep)
|
|
10
|
+
* 1 — blocked (debt/infra exhausted — campaign cannot satisfy)
|
|
11
|
+
* 2 — runtime error
|
|
12
|
+
* 3 — remediate required (agent MUST fix and re-run; never treat as stop)
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node remediator-gate.mjs --campaign-id <id> --iteration <n> --mode wave|debt|iteration \
|
|
16
|
+
* [--wave-index <w>] [--run-id <run_id>] [--attempt <n>] [--skip-verify]
|
|
17
|
+
*/
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import { spawnSync } from "child_process";
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
import { REPO_ROOT, isoNow, readJson, writeJson, runDir } from "../run-engine/lib.mjs";
|
|
23
|
+
import { analyzeRegression } from "./lib/regression-analysis.mjs";
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
|
|
27
|
+
const VERIFY_SCRIPT = path.join(__dirname, "verify-remediation-iteration.mjs");
|
|
28
|
+
const CLASSIFY_SCRIPT = path.join(__dirname, "classify-verify-failure.mjs");
|
|
29
|
+
const RULES = readJson(path.join(__dirname, "dispatch-rules.json"), {});
|
|
30
|
+
|
|
31
|
+
function parseArgs(argv) {
|
|
32
|
+
const out = {
|
|
33
|
+
campaignId: null,
|
|
34
|
+
iteration: 0,
|
|
35
|
+
mode: "wave",
|
|
36
|
+
waveIndex: null,
|
|
37
|
+
runId: null,
|
|
38
|
+
attempt: 1,
|
|
39
|
+
skipVerify: false,
|
|
40
|
+
};
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const a = argv[i];
|
|
43
|
+
if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
44
|
+
else if (a === "--iteration") out.iteration = Number(argv[++i]);
|
|
45
|
+
else if (a === "--mode") out.mode = argv[++i];
|
|
46
|
+
else if (a === "--wave-index") out.waveIndex = Number(argv[++i]);
|
|
47
|
+
else if (a === "--run-id") out.runId = argv[++i];
|
|
48
|
+
else if (a === "--attempt") out.attempt = Number(argv[++i]);
|
|
49
|
+
else if (a === "--skip-verify") out.skipVerify = true;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function campaignDir(id) {
|
|
55
|
+
return path.join(CAMPAIGNS_ROOT, id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function iterDir(campaignId, iteration) {
|
|
59
|
+
return path.join(campaignDir(campaignId), "iterations", String(iteration));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function remediatorStatePath(campaignId, iteration, mode, waveIndex) {
|
|
63
|
+
const base = iterDir(campaignId, iteration);
|
|
64
|
+
const key = mode === "wave" && waveIndex != null ? `wave-${waveIndex}` : mode;
|
|
65
|
+
return path.join(base, `remediator-loop-${key}.json`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function appendJournal(campaignId, text) {
|
|
69
|
+
fs.appendFileSync(path.join(campaignDir(campaignId), "journal.md"), text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function gateMode(campaign, mode) {
|
|
73
|
+
if (mode === "wave") {
|
|
74
|
+
return campaign?.config?.wave_gate_mode ?? "regression";
|
|
75
|
+
}
|
|
76
|
+
return "strict";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function maxAttempts(campaign, mode) {
|
|
80
|
+
const cfg = campaign?.config ?? {};
|
|
81
|
+
if (mode === "debt" || mode === "iteration") {
|
|
82
|
+
return cfg.max_remediator_attempts_per_debt_round ?? cfg.max_remediator_attempts_per_iteration ?? RULES.defaults?.max_remediator_attempts_per_iteration ?? 3;
|
|
83
|
+
}
|
|
84
|
+
return cfg.max_remediator_attempts_per_wave ?? RULES.defaults?.max_remediator_attempts_per_wave ?? 3;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function verifyFileForMode(dir, mode) {
|
|
88
|
+
if (mode === "wave") return path.join(dir, "verify-wave.json");
|
|
89
|
+
if (mode === "debt") return path.join(dir, "verify-debt.json");
|
|
90
|
+
return path.join(dir, "verify-iteration.json");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function runVerify(args) {
|
|
94
|
+
const verifyMode = args.mode === "iteration" ? "debt" : args.mode;
|
|
95
|
+
const verifyArgs = [
|
|
96
|
+
VERIFY_SCRIPT,
|
|
97
|
+
"--campaign-id",
|
|
98
|
+
args.campaignId,
|
|
99
|
+
"--iteration",
|
|
100
|
+
String(args.iteration),
|
|
101
|
+
"--mode",
|
|
102
|
+
verifyMode,
|
|
103
|
+
];
|
|
104
|
+
if (args.runId) verifyArgs.push("--run-id", args.runId);
|
|
105
|
+
const result = spawnSync(process.execPath, verifyArgs, { encoding: "utf8" });
|
|
106
|
+
let parsed = null;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(result.stdout.trim().split("\n").pop());
|
|
109
|
+
} catch {
|
|
110
|
+
parsed = { ok: false, parse_error: true };
|
|
111
|
+
}
|
|
112
|
+
return { exitCode: result.status, parsed, stdout: result.stdout, stderr: result.stderr };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function classify(reportPath, ctx, layersFilter = null) {
|
|
116
|
+
const classifyArgs = [
|
|
117
|
+
CLASSIFY_SCRIPT,
|
|
118
|
+
"--report",
|
|
119
|
+
reportPath,
|
|
120
|
+
"--campaign-id",
|
|
121
|
+
ctx.campaignId,
|
|
122
|
+
"--iteration",
|
|
123
|
+
String(ctx.iteration),
|
|
124
|
+
"--attempt",
|
|
125
|
+
String(ctx.attempt),
|
|
126
|
+
];
|
|
127
|
+
if (ctx.waveIndex != null) classifyArgs.push("--wave-index", String(ctx.waveIndex));
|
|
128
|
+
const result = spawnSync(process.execPath, classifyArgs, { encoding: "utf8" });
|
|
129
|
+
try {
|
|
130
|
+
const out = JSON.parse(result.stdout.trim());
|
|
131
|
+
if (layersFilter?.length && out.classification) {
|
|
132
|
+
out.classification.handoffs = (out.classification.handoffs ?? []).filter((h) =>
|
|
133
|
+
layersFilter.includes(h.layer),
|
|
134
|
+
);
|
|
135
|
+
out.classification.failed_layers = (out.classification.failed_layers ?? []).filter((l) =>
|
|
136
|
+
layersFilter.includes(l),
|
|
137
|
+
);
|
|
138
|
+
out.classification.primary =
|
|
139
|
+
out.classification.handoffs.find((h) => h.command) ?? out.classification.handoffs[0] ?? null;
|
|
140
|
+
out.classification.status = out.classification.handoffs.length ? "fail" : "pass";
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
} catch {
|
|
144
|
+
return { ok: false, error: "classify parse failed", stderr: result.stderr };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function writeHandoffArtifact(dir, attempt, payload) {
|
|
149
|
+
const handoffPath = path.join(dir, `remediator-handoff-attempt-${attempt}.json`);
|
|
150
|
+
writeJson(handoffPath, payload);
|
|
151
|
+
return handoffPath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function loadRegressionContext(campaignId, iteration, waveIndex) {
|
|
155
|
+
const dir = iterDir(campaignId, iteration);
|
|
156
|
+
const campaignRoot = campaignDir(campaignId);
|
|
157
|
+
const preWave = waveIndex != null ? readJson(path.join(dir, `wave-${waveIndex}-pre.json`), null) : null;
|
|
158
|
+
const campaignBaseline = readJson(path.join(campaignRoot, "verify-baseline.json"), null);
|
|
159
|
+
return { preWave, campaignBaseline };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function promotePayload({ args, verifyFile, statePath, loopState, extra }) {
|
|
163
|
+
loopState.status = "promoted";
|
|
164
|
+
loopState.promoted_at = isoNow();
|
|
165
|
+
loopState.final_attempt = args.attempt;
|
|
166
|
+
writeJson(statePath, loopState);
|
|
167
|
+
|
|
168
|
+
const output = {
|
|
169
|
+
action: extra?.action ?? "promote",
|
|
170
|
+
status: "pass",
|
|
171
|
+
attempt: args.attempt,
|
|
172
|
+
verify_path: verifyFile,
|
|
173
|
+
loop_state_path: statePath,
|
|
174
|
+
campaign_must_continue: extra?.campaign_must_continue ?? false,
|
|
175
|
+
...extra,
|
|
176
|
+
};
|
|
177
|
+
if (args.runId) {
|
|
178
|
+
writeJson(path.join(runDir(args.runId), "artifacts", `remediator_gate_${args.mode}.json`), output);
|
|
179
|
+
}
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const args = parseArgs(process.argv.slice(2));
|
|
184
|
+
if (!args.campaignId) {
|
|
185
|
+
console.error("remediator-gate: --campaign-id required");
|
|
186
|
+
process.exit(2);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const campaign = readJson(path.join(campaignDir(args.campaignId), "campaign.json"), {});
|
|
190
|
+
const mode = args.mode === "iteration" ? "debt" : args.mode;
|
|
191
|
+
const gMode = gateMode(campaign, mode === "wave" ? "wave" : "debt");
|
|
192
|
+
const max = maxAttempts(campaign, mode);
|
|
193
|
+
const dir = iterDir(args.campaignId, args.iteration);
|
|
194
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
195
|
+
|
|
196
|
+
const verifyFile = verifyFileForMode(dir, mode);
|
|
197
|
+
|
|
198
|
+
if (!args.skipVerify) {
|
|
199
|
+
runVerify({ ...args, mode });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!fs.existsSync(verifyFile)) {
|
|
203
|
+
console.error("remediator-gate: verify report missing after run");
|
|
204
|
+
process.exit(2);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const report = readJson(verifyFile, {});
|
|
208
|
+
const statePath = remediatorStatePath(args.campaignId, args.iteration, mode, args.waveIndex);
|
|
209
|
+
let loopState = readJson(statePath, {
|
|
210
|
+
mode,
|
|
211
|
+
wave_index: args.waveIndex,
|
|
212
|
+
gate_mode: gMode,
|
|
213
|
+
attempts: [],
|
|
214
|
+
status: "running",
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const { preWave, campaignBaseline } = loadRegressionContext(
|
|
218
|
+
args.campaignId,
|
|
219
|
+
args.iteration,
|
|
220
|
+
args.waveIndex,
|
|
221
|
+
);
|
|
222
|
+
const regression = analyzeRegression({
|
|
223
|
+
current: report,
|
|
224
|
+
preWave: preWave ?? campaignBaseline,
|
|
225
|
+
campaignBaseline,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const strictPass = regression.strict_pass;
|
|
229
|
+
const isWaveRegression = mode === "wave" && gMode === "regression";
|
|
230
|
+
|
|
231
|
+
if (strictPass || (isWaveRegression && !regression.introduced_regression)) {
|
|
232
|
+
const debtRemaining = regression.debt_remaining;
|
|
233
|
+
const action = isWaveRegression && debtRemaining ? "promote_wave" : "promote";
|
|
234
|
+
const output = promotePayload({
|
|
235
|
+
args: { ...args, mode },
|
|
236
|
+
verifyFile,
|
|
237
|
+
statePath,
|
|
238
|
+
loopState,
|
|
239
|
+
extra: {
|
|
240
|
+
action,
|
|
241
|
+
gate_mode: gMode,
|
|
242
|
+
introduced_regression: regression.introduced_regression,
|
|
243
|
+
debt_remaining: debtRemaining,
|
|
244
|
+
campaign_must_continue: debtRemaining || isWaveRegression,
|
|
245
|
+
regression_analysis: regression,
|
|
246
|
+
message: isWaveRegression && debtRemaining
|
|
247
|
+
? "Wave promoted — no new regression; pre-existing debt deferred to debt_sweep"
|
|
248
|
+
: "All verification layers pass",
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
appendJournal(
|
|
253
|
+
args.campaignId,
|
|
254
|
+
`- Remediator gate **${action}** ${mode} iter ${args.iteration}${args.waveIndex != null ? ` wave ${args.waveIndex}` : ""} (attempt ${args.attempt}, debt_remaining=${debtRemaining})\n`,
|
|
255
|
+
);
|
|
256
|
+
console.log(JSON.stringify(output));
|
|
257
|
+
process.exit(0);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const layersFilter =
|
|
261
|
+
isWaveRegression && regression.introduced_layers?.length
|
|
262
|
+
? regression.introduced_layers
|
|
263
|
+
: null;
|
|
264
|
+
|
|
265
|
+
const classificationResult = classify(verifyFile, args, layersFilter);
|
|
266
|
+
const classification = classificationResult.classification ?? { handoffs: [], primary: null };
|
|
267
|
+
|
|
268
|
+
const infra = classification.handoffs?.find((h) => h.level === "infrastructure");
|
|
269
|
+
if (infra) {
|
|
270
|
+
const payload = {
|
|
271
|
+
action: "infrastructure",
|
|
272
|
+
status: "blocked",
|
|
273
|
+
attempt: args.attempt,
|
|
274
|
+
handoff: infra.handoff,
|
|
275
|
+
layer: infra.layer,
|
|
276
|
+
verify_path: verifyFile,
|
|
277
|
+
campaign_must_continue: false,
|
|
278
|
+
message: "Infrastructure prerequisite failed — run handoff then retry",
|
|
279
|
+
};
|
|
280
|
+
writeHandoffArtifact(dir, args.attempt, payload);
|
|
281
|
+
appendJournal(args.campaignId, `- Remediator gate: **INFRA** ${infra.handoff}\n`);
|
|
282
|
+
console.log(JSON.stringify(payload));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const attemptRecord = {
|
|
287
|
+
attempt: args.attempt,
|
|
288
|
+
at: isoNow(),
|
|
289
|
+
gate_mode: gMode,
|
|
290
|
+
introduced_regression: regression.introduced_regression,
|
|
291
|
+
failed_layers: classification.failed_layers ?? [],
|
|
292
|
+
primary: classification.primary,
|
|
293
|
+
verify_path: verifyFile,
|
|
294
|
+
regression_analysis: regression,
|
|
295
|
+
};
|
|
296
|
+
loopState.attempts.push(attemptRecord);
|
|
297
|
+
loopState.updated_at = isoNow();
|
|
298
|
+
writeJson(statePath, loopState);
|
|
299
|
+
|
|
300
|
+
if (args.attempt >= max) {
|
|
301
|
+
if (isWaveRegression) {
|
|
302
|
+
loopState.status = "deferred_to_debt_sweep";
|
|
303
|
+
loopState.deferred_at = isoNow();
|
|
304
|
+
loopState.deferred_reason = "max_remediator_attempts_wave_regression";
|
|
305
|
+
writeJson(statePath, loopState);
|
|
306
|
+
|
|
307
|
+
const payload = promotePayload({
|
|
308
|
+
args: { ...args, mode },
|
|
309
|
+
verifyFile,
|
|
310
|
+
statePath,
|
|
311
|
+
loopState,
|
|
312
|
+
extra: {
|
|
313
|
+
action: "defer_to_debt_sweep",
|
|
314
|
+
gate_mode: gMode,
|
|
315
|
+
reason: "max_remediator_attempts",
|
|
316
|
+
attempt: args.attempt,
|
|
317
|
+
max_attempts: max,
|
|
318
|
+
classification,
|
|
319
|
+
campaign_must_continue: true,
|
|
320
|
+
message: "Wave regression not fixed in max attempts — deferred to debt_sweep; continue remaining waves",
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
writeHandoffArtifact(dir, args.attempt, payload);
|
|
324
|
+
appendJournal(
|
|
325
|
+
args.campaignId,
|
|
326
|
+
`- Remediator wave **DEFERRED** to debt_sweep after ${args.attempt}/${max} attempts\n`,
|
|
327
|
+
);
|
|
328
|
+
console.log(JSON.stringify(payload));
|
|
329
|
+
process.exit(0);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
loopState.status = "blocked";
|
|
333
|
+
loopState.blocked_at = isoNow();
|
|
334
|
+
loopState.blocked_reason = "max_remediator_attempts";
|
|
335
|
+
writeJson(statePath, loopState);
|
|
336
|
+
|
|
337
|
+
const payload = {
|
|
338
|
+
action: "block",
|
|
339
|
+
status: "fail",
|
|
340
|
+
reason: "max_remediator_attempts",
|
|
341
|
+
attempt: args.attempt,
|
|
342
|
+
max_attempts: max,
|
|
343
|
+
gate_mode: gMode,
|
|
344
|
+
classification,
|
|
345
|
+
verify_path: verifyFile,
|
|
346
|
+
loop_state_path: statePath,
|
|
347
|
+
campaign_must_continue: false,
|
|
348
|
+
manual_handoff: classification.primary?.slash_command ?? null,
|
|
349
|
+
};
|
|
350
|
+
writeHandoffArtifact(dir, args.attempt, payload);
|
|
351
|
+
appendJournal(
|
|
352
|
+
args.campaignId,
|
|
353
|
+
`- Remediator gate **BLOCKED** after ${args.attempt}/${max} attempts (${(classification.failed_layers ?? []).join(", ")})\n`,
|
|
354
|
+
);
|
|
355
|
+
if (args.runId) {
|
|
356
|
+
writeJson(path.join(runDir(args.runId), "artifacts", `remediator_gate_${mode}.json`), payload);
|
|
357
|
+
}
|
|
358
|
+
console.log(JSON.stringify(payload));
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const primary = classification.primary;
|
|
363
|
+
const payload = {
|
|
364
|
+
action: "remediate",
|
|
365
|
+
status: "fail",
|
|
366
|
+
attempt: args.attempt,
|
|
367
|
+
max_attempts: max,
|
|
368
|
+
next_attempt: args.attempt + 1,
|
|
369
|
+
gate_mode: gMode,
|
|
370
|
+
introduced_regression: regression.introduced_regression,
|
|
371
|
+
introduced_layers: regression.introduced_layers,
|
|
372
|
+
failed_layers: classification.failed_layers,
|
|
373
|
+
campaign_must_continue: true,
|
|
374
|
+
orchestrator_must_not_stop: true,
|
|
375
|
+
orchestrator_must_not_set_blocked: true,
|
|
376
|
+
handoff: primary
|
|
377
|
+
? {
|
|
378
|
+
command: primary.command,
|
|
379
|
+
domain: primary.domain,
|
|
380
|
+
intent: primary.intent,
|
|
381
|
+
layer: primary.layer,
|
|
382
|
+
level: primary.level,
|
|
383
|
+
file_paths: primary.file_paths,
|
|
384
|
+
log_path: report[primary.layer]?.log_path ?? null,
|
|
385
|
+
}
|
|
386
|
+
: null,
|
|
387
|
+
all_handoffs: classification.handoffs,
|
|
388
|
+
verify_path: verifyFile,
|
|
389
|
+
loop_state_path: statePath,
|
|
390
|
+
regression_analysis: regression,
|
|
391
|
+
retry_command: `node .cursor/aaac/scripts/remediation/remediator-gate.mjs --campaign-id ${args.campaignId} --iteration ${args.iteration} --mode ${mode}${args.waveIndex != null ? ` --wave-index ${args.waveIndex}` : ""}${args.runId ? ` --run-id ${args.runId}` : ""} --attempt ${args.attempt + 1}`,
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const handoffPath = writeHandoffArtifact(dir, args.attempt, payload);
|
|
395
|
+
appendJournal(
|
|
396
|
+
args.campaignId,
|
|
397
|
+
`- Remediator attempt ${args.attempt}/${max}: **${primary?.command ?? "unknown"}** (${primary?.layer}) — handoff \`${handoffPath}\` — **CONTINUE** (exit 3 ≠ stop)\n`,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
if (args.runId) {
|
|
401
|
+
writeJson(path.join(runDir(args.runId), "artifacts", `remediator_handoff_attempt_${args.attempt}.json`), payload);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
console.log(JSON.stringify(payload));
|
|
405
|
+
process.exit(3);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-time repair for campaigns whose Fallow start baselines were overwritten or missing layers.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node repair-fallow-start-baseline.mjs --campaign-id <id> --total <n> [--recorded-at <iso>]
|
|
7
|
+
* node repair-fallow-start-baseline.mjs --campaign-id <id> --dupes-clone-groups <n>
|
|
8
|
+
* node repair-fallow-start-baseline.mjs --campaign-id <id> --health-score <n>
|
|
9
|
+
*/
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { REPO_ROOT, isoNow, readJson, writeJson } from "../run-engine/lib.mjs";
|
|
13
|
+
|
|
14
|
+
const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const out = {
|
|
18
|
+
campaignId: null,
|
|
19
|
+
total: null,
|
|
20
|
+
dupesCloneGroups: null,
|
|
21
|
+
healthScore: null,
|
|
22
|
+
recordedAt: null,
|
|
23
|
+
};
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
27
|
+
else if (a === "--total") out.total = Number(argv[++i]);
|
|
28
|
+
else if (a === "--dupes-clone-groups") out.dupesCloneGroups = Number(argv[++i]);
|
|
29
|
+
else if (a === "--health-score") out.healthScore = Number(argv[++i]);
|
|
30
|
+
else if (a === "--recorded-at") out.recordedAt = argv[++i];
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function repairLayer(cDir, filename, field, value, layer) {
|
|
36
|
+
const startPath = path.join(cDir, filename);
|
|
37
|
+
const existing = readJson(startPath, null);
|
|
38
|
+
if (existing?.immutable && existing[field] != null) {
|
|
39
|
+
return { skipped: true, reason: `${filename} already immutable`, baseline: existing };
|
|
40
|
+
}
|
|
41
|
+
const baseline = {
|
|
42
|
+
[field]: value,
|
|
43
|
+
fallow_scan_path: existing?.fallow_scan_path ?? null,
|
|
44
|
+
recorded_at: args.recordedAt ?? existing?.recorded_at ?? isoNow(),
|
|
45
|
+
immutable: true,
|
|
46
|
+
source: "repair-fallow-start-baseline",
|
|
47
|
+
layer,
|
|
48
|
+
repaired_at: isoNow(),
|
|
49
|
+
};
|
|
50
|
+
writeJson(startPath, baseline);
|
|
51
|
+
return { skipped: false, baseline };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const args = parseArgs(process.argv.slice(2));
|
|
55
|
+
if (!args.campaignId) {
|
|
56
|
+
console.error(
|
|
57
|
+
"repair-fallow-start-baseline: --campaign-id required; pass at least one of --total, --dupes-clone-groups, --health-score",
|
|
58
|
+
);
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
args.total == null &&
|
|
64
|
+
args.dupesCloneGroups == null &&
|
|
65
|
+
args.healthScore == null
|
|
66
|
+
) {
|
|
67
|
+
console.error("repair-fallow-start-baseline: at least one metric required");
|
|
68
|
+
process.exit(2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const cDir = path.join(CAMPAIGNS_ROOT, args.campaignId);
|
|
72
|
+
const results = {};
|
|
73
|
+
|
|
74
|
+
if (args.total != null && !Number.isNaN(args.total)) {
|
|
75
|
+
results.dead_code = repairLayer(
|
|
76
|
+
cDir,
|
|
77
|
+
"fallow-start-baseline.json",
|
|
78
|
+
"fallow_total_issues",
|
|
79
|
+
args.total,
|
|
80
|
+
"dead-code",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (args.dupesCloneGroups != null && !Number.isNaN(args.dupesCloneGroups)) {
|
|
85
|
+
results.dupes = repairLayer(
|
|
86
|
+
cDir,
|
|
87
|
+
"fallow-start-dupes-baseline.json",
|
|
88
|
+
"clone_groups",
|
|
89
|
+
args.dupesCloneGroups,
|
|
90
|
+
"dupes",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (args.healthScore != null && !Number.isNaN(args.healthScore)) {
|
|
95
|
+
results.health = repairLayer(
|
|
96
|
+
cDir,
|
|
97
|
+
"fallow-start-health-baseline.json",
|
|
98
|
+
"health_score",
|
|
99
|
+
args.healthScore,
|
|
100
|
+
"health",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const campaignPath = path.join(cDir, "campaign.json");
|
|
105
|
+
const campaign = readJson(campaignPath, null);
|
|
106
|
+
if (campaign) {
|
|
107
|
+
campaign.baseline = { ...campaign.baseline };
|
|
108
|
+
for (const r of Object.values(results)) {
|
|
109
|
+
if (!r.skipped) Object.assign(campaign.baseline, r.baseline);
|
|
110
|
+
}
|
|
111
|
+
campaign.updated_at = isoNow();
|
|
112
|
+
writeJson(campaignPath, campaign);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const journal = `\n- **Fallow baselines repaired** — ${JSON.stringify(results)}\n`;
|
|
116
|
+
fs.appendFileSync(path.join(cDir, "journal.md"), journal);
|
|
117
|
+
|
|
118
|
+
console.log(JSON.stringify({ ok: true, results }));
|