@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,748 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shell runner for /remediate-app — drives scriptable phases, yields for agent work.
|
|
4
|
+
*
|
|
5
|
+
* Modes:
|
|
6
|
+
* --status Print runner + campaign state (JSON)
|
|
7
|
+
* --tick Run one automated step (may yield)
|
|
8
|
+
* --until-yield Loop --tick until agent yield, complete, or blocked
|
|
9
|
+
* --ack-yield Mark agent work done; resume automated steps
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 complete (threshold met or max iterations — report allowed)
|
|
13
|
+
* 1 blocked / fatal
|
|
14
|
+
* 2 runtime error
|
|
15
|
+
* 3 yield — agent must act (see runner-yield.json)
|
|
16
|
+
* 10 progressed (internal; --until-yield continues)
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* node remediation-runner.mjs --run-id <id> --campaign-id <id> [--tick|--until-yield|--ack-yield|--status]
|
|
20
|
+
*/
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import {
|
|
24
|
+
EXIT,
|
|
25
|
+
PHASES,
|
|
26
|
+
campaignDir,
|
|
27
|
+
clearYield,
|
|
28
|
+
defaultRunnerState,
|
|
29
|
+
emitResult,
|
|
30
|
+
fail,
|
|
31
|
+
iterDir,
|
|
32
|
+
loadCampaign,
|
|
33
|
+
loadManifest,
|
|
34
|
+
loadRunnerState,
|
|
35
|
+
loadYield,
|
|
36
|
+
runArtifactsDir,
|
|
37
|
+
runnerStatePath,
|
|
38
|
+
saveCampaign,
|
|
39
|
+
saveRunnerState,
|
|
40
|
+
syncRunnerFromManifest,
|
|
41
|
+
writeYield,
|
|
42
|
+
yieldArtifactPath,
|
|
43
|
+
} from "./lib/runner-state.mjs";
|
|
44
|
+
import {
|
|
45
|
+
advancePhase,
|
|
46
|
+
journal,
|
|
47
|
+
parseDispatchQueueYaml,
|
|
48
|
+
runNode,
|
|
49
|
+
writeRunArtifact,
|
|
50
|
+
} from "./lib/runner-exec.mjs";
|
|
51
|
+
import {
|
|
52
|
+
reconcileRemediationRun,
|
|
53
|
+
} from "./lib/reconcile-run-manifest.mjs";
|
|
54
|
+
|
|
55
|
+
const REQUIRED_SWARM_AGENTS = 7;
|
|
56
|
+
const CHECK_SWARM_AGENTS = [
|
|
57
|
+
"remediation-check-app-inventory",
|
|
58
|
+
"remediation-check-app-ssot",
|
|
59
|
+
"remediation-check-app-trace",
|
|
60
|
+
"remediation-check-architecture-boundaries",
|
|
61
|
+
"remediation-check-architecture-deps",
|
|
62
|
+
"remediation-check-architecture-decomposition",
|
|
63
|
+
"remediation-check-risk",
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
function parseArgs(argv) {
|
|
67
|
+
const out = {
|
|
68
|
+
runId: null,
|
|
69
|
+
campaignId: null,
|
|
70
|
+
mode: "tick",
|
|
71
|
+
scope: null,
|
|
72
|
+
intent: null,
|
|
73
|
+
ackType: null,
|
|
74
|
+
};
|
|
75
|
+
for (let i = 0; i < argv.length; i++) {
|
|
76
|
+
const a = argv[i];
|
|
77
|
+
if (a === "--run-id") out.runId = argv[++i];
|
|
78
|
+
else if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
79
|
+
else if (a === "--scope") out.scope = argv[++i];
|
|
80
|
+
else if (a === "--intent") out.intent = argv[++i];
|
|
81
|
+
else if (a === "--status") out.mode = "status";
|
|
82
|
+
else if (a === "--tick") out.mode = "tick";
|
|
83
|
+
else if (a === "--until-yield") out.mode = "until-yield";
|
|
84
|
+
else if (a === "--ack-yield") {
|
|
85
|
+
out.mode = "ack-yield";
|
|
86
|
+
out.ackType = argv[++i];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function ensureRunnerState(args, campaign, manifest) {
|
|
93
|
+
let state = loadRunnerState(args.campaignId);
|
|
94
|
+
if (!state) {
|
|
95
|
+
state = defaultRunnerState({
|
|
96
|
+
runId: args.runId,
|
|
97
|
+
campaignId: args.campaignId,
|
|
98
|
+
iteration: campaign.iteration ?? 0,
|
|
99
|
+
phase: manifest?.phase ?? "campaign_init",
|
|
100
|
+
});
|
|
101
|
+
saveRunnerState(state);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Runner state owns phase while campaign is running; reconcile manifest from runner.
|
|
105
|
+
if (manifest?.command === "remediate-app" && campaign.status === "running") {
|
|
106
|
+
reconcileRemediationRun(args.runId, state);
|
|
107
|
+
} else {
|
|
108
|
+
syncRunnerFromManifest(state, manifest);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
state.run_id = args.runId;
|
|
112
|
+
state.iteration = campaign.iteration ?? state.iteration;
|
|
113
|
+
saveRunnerState(state);
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setYield(state, yieldPayload) {
|
|
118
|
+
state.status = "yielded";
|
|
119
|
+
state.yield = yieldPayload;
|
|
120
|
+
writeYield(state.campaign_id, yieldPayload);
|
|
121
|
+
saveRunnerState(state);
|
|
122
|
+
emitResult(state, { action: "yield" });
|
|
123
|
+
process.exit(EXIT.yield_agent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function markProgress(state, campaign) {
|
|
127
|
+
const satPath = path.join(iterDir(state.campaign_id, state.iteration), "satisfaction.json");
|
|
128
|
+
const sat = fs.existsSync(satPath)
|
|
129
|
+
? JSON.parse(fs.readFileSync(satPath, "utf8"))
|
|
130
|
+
: null;
|
|
131
|
+
const score = sat?.score ?? campaign.current?.satisfaction_score ?? null;
|
|
132
|
+
const clones = campaign.current?.fallow_dupes_clone_groups ?? null;
|
|
133
|
+
if (score !== state.last_score || clones !== state.last_clone_groups) {
|
|
134
|
+
state.last_score = score;
|
|
135
|
+
state.last_clone_groups = clones;
|
|
136
|
+
state.last_progress_at = new Date().toISOString();
|
|
137
|
+
state.stall_count = 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function advanceRunnerPhase(runId, completedPhase, runnerState) {
|
|
142
|
+
reconcileRemediationRun(runId, {
|
|
143
|
+
...runnerState,
|
|
144
|
+
phase: completedPhase,
|
|
145
|
+
});
|
|
146
|
+
const adv = advancePhase(runId, completedPhase, { force: true });
|
|
147
|
+
if (!adv.ok) {
|
|
148
|
+
fail(`advance ${completedPhase} failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
149
|
+
}
|
|
150
|
+
const manifest = loadManifest(runId);
|
|
151
|
+
runnerState.phase = manifest?.phase ?? completedPhase;
|
|
152
|
+
return runnerState.phase;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function readPlanWaves(runId, campaignId) {
|
|
156
|
+
const runPlan = path.join(runArtifactsDir(runId), "plan_waves.yaml");
|
|
157
|
+
const campPlan = path.join(campaignDir(campaignId), "artifacts", "plan_waves.yaml");
|
|
158
|
+
const text = fs.existsSync(runPlan)
|
|
159
|
+
? fs.readFileSync(runPlan, "utf8")
|
|
160
|
+
: fs.existsSync(campPlan)
|
|
161
|
+
? fs.readFileSync(campPlan, "utf8")
|
|
162
|
+
: null;
|
|
163
|
+
if (!text) return [];
|
|
164
|
+
return parseDispatchQueueYaml(text).map((w, index) => ({ ...w, index }));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function waveCount(runId, campaignId, campaign) {
|
|
168
|
+
const waves = readPlanWaves(runId, campaignId);
|
|
169
|
+
if (waves.length) return waves.length;
|
|
170
|
+
return campaign.config?.max_waves_per_iteration ?? 3;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function checkSwarmRawReady(campaignId, iteration) {
|
|
174
|
+
const rawPath = path.join(iterDir(campaignId, iteration), "check-swarm-raw.json");
|
|
175
|
+
if (!fs.existsSync(rawPath)) return { ok: false, reason: "missing_check_swarm_raw" };
|
|
176
|
+
const raw = JSON.parse(fs.readFileSync(rawPath, "utf8"));
|
|
177
|
+
const agents = raw.agents ?? [];
|
|
178
|
+
if (agents.length < REQUIRED_SWARM_AGENTS) {
|
|
179
|
+
return { ok: false, reason: "insufficient_agents", count: agents.length };
|
|
180
|
+
}
|
|
181
|
+
return { ok: true, raw };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function remediatorHandoffPath(campaignId, iteration, attempt) {
|
|
185
|
+
return path.join(
|
|
186
|
+
iterDir(campaignId, iteration),
|
|
187
|
+
`remediator-handoff-attempt-${attempt}.json`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function handleCampaignInit(state, campaign, manifest, args) {
|
|
192
|
+
const initArgs = [
|
|
193
|
+
"--run-id",
|
|
194
|
+
args.runId,
|
|
195
|
+
"--campaign-id",
|
|
196
|
+
state.campaign_id,
|
|
197
|
+
"--scope",
|
|
198
|
+
args.scope ?? campaign.scope ?? "whole-repo",
|
|
199
|
+
];
|
|
200
|
+
if (args.intent) initArgs.push("--intent", args.intent);
|
|
201
|
+
if (campaign.status === "running" && campaign.iteration > 0) {
|
|
202
|
+
initArgs.push("--resume", state.campaign_id);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const init = runNode("init-campaign.mjs", initArgs);
|
|
206
|
+
if (!init.ok && init.status !== 0) {
|
|
207
|
+
fail(`init-campaign failed: ${init.stderr || init.stdout}`, EXIT.runtime_error);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const baseline = runNode("capture-verify-baseline.mjs", [
|
|
211
|
+
"--campaign-id",
|
|
212
|
+
state.campaign_id,
|
|
213
|
+
"--run-id",
|
|
214
|
+
args.runId,
|
|
215
|
+
]);
|
|
216
|
+
if (!baseline.ok) {
|
|
217
|
+
fail(`capture-verify-baseline failed: ${baseline.stderr}`, EXIT.runtime_error);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
writeRunArtifact(args.runId, "campaign.json", {
|
|
221
|
+
campaign_id: state.campaign_id,
|
|
222
|
+
iteration: campaign.iteration,
|
|
223
|
+
config: campaign.config,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const adv = advancePhase(args.runId, "campaign_init", { force: true });
|
|
227
|
+
if (!adv.ok) fail(`advance campaign_init failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
228
|
+
state.phase = advanceRunnerPhase(args.runId, "campaign_init", state);
|
|
229
|
+
state.substep = null;
|
|
230
|
+
state.status = "running";
|
|
231
|
+
saveRunnerState(state);
|
|
232
|
+
return EXIT.progressed;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handleScan(state, campaign, args) {
|
|
236
|
+
const n = state.iteration;
|
|
237
|
+
const startBaseline = path.join(campaignDir(state.campaign_id), "fallow-start-baseline.json");
|
|
238
|
+
const scanArgs = ["--campaign-id", state.campaign_id, "--iteration", String(n)];
|
|
239
|
+
if (!fs.existsSync(startBaseline) && n === 0) scanArgs.push("--save-baseline");
|
|
240
|
+
|
|
241
|
+
const scan = runNode("fallow-scan.mjs", scanArgs);
|
|
242
|
+
if (!scan.ok) fail(`fallow-scan failed: ${scan.stderr}`, EXIT.runtime_error);
|
|
243
|
+
|
|
244
|
+
const classify = runNode("classify-fallow-issues.mjs", [
|
|
245
|
+
"--campaign-id",
|
|
246
|
+
state.campaign_id,
|
|
247
|
+
"--iteration",
|
|
248
|
+
String(n),
|
|
249
|
+
]);
|
|
250
|
+
if (!classify.ok) fail(`classify-fallow-issues failed: ${classify.stderr}`, EXIT.runtime_error);
|
|
251
|
+
|
|
252
|
+
const adv = advancePhase(args.runId, "scan", { force: true });
|
|
253
|
+
if (!adv.ok) fail(`advance scan failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
254
|
+
state.phase = advanceRunnerPhase(args.runId, "scan", state);
|
|
255
|
+
state.substep = "prepare";
|
|
256
|
+
saveRunnerState(state);
|
|
257
|
+
return EXIT.progressed;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function handleCheckSwarm(state, args) {
|
|
261
|
+
const n = state.iteration;
|
|
262
|
+
|
|
263
|
+
if (state.substep === "prepare" || !state.substep) {
|
|
264
|
+
const prep = runNode("prepare-check-context.mjs", [
|
|
265
|
+
"--campaign-id",
|
|
266
|
+
state.campaign_id,
|
|
267
|
+
"--iteration",
|
|
268
|
+
String(n),
|
|
269
|
+
"--run-id",
|
|
270
|
+
args.runId,
|
|
271
|
+
]);
|
|
272
|
+
if (!prep.ok) fail(`prepare-check-context failed: ${prep.stderr}`, EXIT.runtime_error);
|
|
273
|
+
state.substep = "agents";
|
|
274
|
+
saveRunnerState(state);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (state.substep === "agents") {
|
|
278
|
+
const rawCheck = checkSwarmRawReady(state.campaign_id, n);
|
|
279
|
+
if (!rawCheck.ok) {
|
|
280
|
+
setYield(state, {
|
|
281
|
+
type: "check_swarm",
|
|
282
|
+
phase: "check_swarm",
|
|
283
|
+
iteration: n,
|
|
284
|
+
reason: rawCheck.reason,
|
|
285
|
+
required_agents: CHECK_SWARM_AGENTS,
|
|
286
|
+
artifacts_required: [`iterations/${n}/check-swarm-raw.json`],
|
|
287
|
+
skill: ".cursor/skills/shared/remediation/check-swarm/SKILL.md",
|
|
288
|
+
instructions:
|
|
289
|
+
"Launch 7 parallel readonly Task agents per check-swarm SKILL. Collect JSON into check-swarm-raw.json, then run --ack-yield check_swarm.",
|
|
290
|
+
resume_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${state.campaign_id} --ack-yield check_swarm`,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
state.substep = "merge";
|
|
294
|
+
saveRunnerState(state);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (state.substep === "merge") {
|
|
298
|
+
const merge = runNode("merge-check-swarm.mjs", [
|
|
299
|
+
"--campaign-id",
|
|
300
|
+
state.campaign_id,
|
|
301
|
+
"--iteration",
|
|
302
|
+
String(n),
|
|
303
|
+
"--run-id",
|
|
304
|
+
args.runId,
|
|
305
|
+
]);
|
|
306
|
+
if (!merge.ok) fail(`merge-check-swarm failed: ${merge.stderr}`, EXIT.runtime_error);
|
|
307
|
+
|
|
308
|
+
const adv = advancePhase(args.runId, "check_swarm", { force: true });
|
|
309
|
+
if (!adv.ok) fail(`advance check_swarm failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
310
|
+
state.phase = advanceRunnerPhase(args.runId, "check_swarm", state);
|
|
311
|
+
state.substep = null;
|
|
312
|
+
saveRunnerState(state);
|
|
313
|
+
return EXIT.progressed;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return EXIT.progressed;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function handlePlanWaves(state, campaign, args) {
|
|
320
|
+
const queuePath = path.join(campaignDir(state.campaign_id), "dispatch-queue.yaml");
|
|
321
|
+
if (!fs.existsSync(queuePath)) {
|
|
322
|
+
setYield(state, {
|
|
323
|
+
type: "dispatch_queue",
|
|
324
|
+
phase: "plan_waves",
|
|
325
|
+
iteration: state.iteration,
|
|
326
|
+
reason: "missing_dispatch_queue",
|
|
327
|
+
artifacts_required: ["dispatch-queue.yaml"],
|
|
328
|
+
instructions:
|
|
329
|
+
"Write dispatch-queue.yaml from check_swarm merge (waves with command, intent, risk). Then --ack-yield dispatch_queue.",
|
|
330
|
+
resume_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${state.campaign_id} --ack-yield dispatch_queue`,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const plan = runNode("plan-waves-from-queue.mjs", [
|
|
335
|
+
"--campaign-id",
|
|
336
|
+
state.campaign_id,
|
|
337
|
+
"--run-id",
|
|
338
|
+
args.runId,
|
|
339
|
+
"--iteration",
|
|
340
|
+
String(state.iteration),
|
|
341
|
+
]);
|
|
342
|
+
if (!plan.ok) fail(`plan-waves-from-queue failed: ${plan.stderr}`, EXIT.runtime_error);
|
|
343
|
+
|
|
344
|
+
const adv = advancePhase(args.runId, "plan_waves", { force: true });
|
|
345
|
+
if (!adv.ok) fail(`advance plan_waves failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
346
|
+
state.phase = advanceRunnerPhase(args.runId, "plan_waves", state);
|
|
347
|
+
state.substep = "wave_fix";
|
|
348
|
+
state.wave_index = 0;
|
|
349
|
+
state.attempt = 1;
|
|
350
|
+
saveRunnerState(state);
|
|
351
|
+
return EXIT.progressed;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function handleExecute(state, campaign, args) {
|
|
355
|
+
const totalWaves = waveCount(args.runId, state.campaign_id, campaign);
|
|
356
|
+
const w = state.wave_index;
|
|
357
|
+
|
|
358
|
+
if (w >= totalWaves) {
|
|
359
|
+
const execJson = path.join(runArtifactsDir(args.runId), "execute_waves.json");
|
|
360
|
+
const execYaml = path.join(runArtifactsDir(args.runId), "execute_waves.yaml");
|
|
361
|
+
if (!fs.existsSync(execYaml)) {
|
|
362
|
+
const wavesDone = fs.existsSync(execJson)
|
|
363
|
+
? JSON.parse(fs.readFileSync(execJson, "utf8"))
|
|
364
|
+
: { waves: [] };
|
|
365
|
+
fs.writeFileSync(
|
|
366
|
+
execYaml,
|
|
367
|
+
`campaign_id: ${state.campaign_id}\niteration: ${state.iteration}\nwaves: []\n`,
|
|
368
|
+
);
|
|
369
|
+
if (!fs.existsSync(execJson)) {
|
|
370
|
+
writeRunArtifact(args.runId, "execute_waves.json", {
|
|
371
|
+
campaign_id: state.campaign_id,
|
|
372
|
+
iteration: state.iteration,
|
|
373
|
+
waves: wavesDone.waves ?? [],
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const adv = advancePhase(args.runId, "execute", { force: true });
|
|
378
|
+
if (!adv.ok) fail(`advance execute failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
379
|
+
state.phase = advanceRunnerPhase(args.runId, "execute", state);
|
|
380
|
+
state.substep = null;
|
|
381
|
+
state.attempt = 1;
|
|
382
|
+
saveRunnerState(state);
|
|
383
|
+
return EXIT.progressed;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const waves = readPlanWaves(args.runId, state.campaign_id);
|
|
387
|
+
const wave = waves[w] ?? {
|
|
388
|
+
index: w,
|
|
389
|
+
command: "fix-module",
|
|
390
|
+
intent: `Remediation wave ${w} — see dispatch-queue.yaml`,
|
|
391
|
+
risk: "low",
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
if (state.substep === "wave_fix") {
|
|
395
|
+
setYield(state, {
|
|
396
|
+
type: "execute_wave",
|
|
397
|
+
phase: "execute",
|
|
398
|
+
iteration: state.iteration,
|
|
399
|
+
wave_index: w,
|
|
400
|
+
wave_total: totalWaves,
|
|
401
|
+
command: wave.command,
|
|
402
|
+
intent: wave.intent,
|
|
403
|
+
risk: wave.risk,
|
|
404
|
+
instructions: `Run inline ${wave.command} (no nested AAAC Run). Respect protected_paths.yaml. Test-only waves need test_execute phase or mark degraded. Then --ack-yield execute_wave.`,
|
|
405
|
+
artifacts_required: [`artifacts/execute_waves.json`],
|
|
406
|
+
resume_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${state.campaign_id} --ack-yield execute_wave`,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (state.substep === "wave_verify") {
|
|
411
|
+
runNode("capture-wave-snapshot.mjs", [
|
|
412
|
+
"--campaign-id",
|
|
413
|
+
state.campaign_id,
|
|
414
|
+
"--iteration",
|
|
415
|
+
String(state.iteration),
|
|
416
|
+
"--wave-index",
|
|
417
|
+
String(w),
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
const gate = runNode("remediator-gate.mjs", [
|
|
421
|
+
"--campaign-id",
|
|
422
|
+
state.campaign_id,
|
|
423
|
+
"--iteration",
|
|
424
|
+
String(state.iteration),
|
|
425
|
+
"--mode",
|
|
426
|
+
"wave",
|
|
427
|
+
"--wave-index",
|
|
428
|
+
String(w),
|
|
429
|
+
"--run-id",
|
|
430
|
+
args.runId,
|
|
431
|
+
"--attempt",
|
|
432
|
+
String(state.attempt),
|
|
433
|
+
]);
|
|
434
|
+
|
|
435
|
+
if (gate.status === 0) {
|
|
436
|
+
journal(
|
|
437
|
+
state.campaign_id,
|
|
438
|
+
`- Runner wave ${w} **promoted** iter ${state.iteration}`,
|
|
439
|
+
);
|
|
440
|
+
state.wave_index = w + 1;
|
|
441
|
+
state.substep = "wave_fix";
|
|
442
|
+
state.attempt = 1;
|
|
443
|
+
saveRunnerState(state);
|
|
444
|
+
return EXIT.progressed;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (gate.status === 3 && gate.json) {
|
|
448
|
+
setYield(state, {
|
|
449
|
+
type: "remediator",
|
|
450
|
+
phase: "execute",
|
|
451
|
+
subphase: "wave",
|
|
452
|
+
iteration: state.iteration,
|
|
453
|
+
wave_index: w,
|
|
454
|
+
attempt: state.attempt,
|
|
455
|
+
gate_mode: "regression",
|
|
456
|
+
handoff: gate.json.handoff,
|
|
457
|
+
retry_command: gate.json.retry_command,
|
|
458
|
+
instructions:
|
|
459
|
+
"Exit 3 = remediate and retry. Run remediation-remediator handoff inline. Then --ack-yield remediator.",
|
|
460
|
+
resume_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${state.campaign_id} --ack-yield remediator`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (gate.status === 1) {
|
|
465
|
+
state.status = "blocked";
|
|
466
|
+
saveRunnerState(state);
|
|
467
|
+
emitResult(state, { action: "blocked", gate: gate.json });
|
|
468
|
+
process.exit(EXIT.blocked);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
fail(`remediator-gate wave failed: ${gate.stderr}`, EXIT.runtime_error);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return EXIT.progressed;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function handleDebtSweep(state, args) {
|
|
478
|
+
const gate = runNode("debt-sweep-gate.mjs", [
|
|
479
|
+
"--campaign-id",
|
|
480
|
+
state.campaign_id,
|
|
481
|
+
"--iteration",
|
|
482
|
+
String(state.iteration),
|
|
483
|
+
"--run-id",
|
|
484
|
+
args.runId,
|
|
485
|
+
"--round",
|
|
486
|
+
"1",
|
|
487
|
+
"--attempt",
|
|
488
|
+
String(state.attempt),
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
if (gate.status === 0) {
|
|
492
|
+
const adv = advancePhase(args.runId, "debt_sweep", { force: true });
|
|
493
|
+
if (!adv.ok) fail(`advance debt_sweep failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
494
|
+
state.phase = advanceRunnerPhase(args.runId, "debt_sweep", state);
|
|
495
|
+
state.substep = null;
|
|
496
|
+
state.attempt = 1;
|
|
497
|
+
saveRunnerState(state);
|
|
498
|
+
return EXIT.progressed;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (gate.status === 3 && gate.json) {
|
|
502
|
+
setYield(state, {
|
|
503
|
+
type: "remediator",
|
|
504
|
+
phase: "debt_sweep",
|
|
505
|
+
iteration: state.iteration,
|
|
506
|
+
attempt: state.attempt,
|
|
507
|
+
gate_mode: "strict",
|
|
508
|
+
handoff: gate.json.handoff ?? gate.json.primary,
|
|
509
|
+
retry_command: gate.json.retry_command,
|
|
510
|
+
instructions:
|
|
511
|
+
"Debt sweep exit 3 — fix strict verify failures inline, then --ack-yield remediator.",
|
|
512
|
+
resume_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${state.campaign_id} --ack-yield remediator`,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (gate.status === 1) {
|
|
517
|
+
state.status = "blocked";
|
|
518
|
+
saveRunnerState(state);
|
|
519
|
+
emitResult(state, { action: "blocked", gate: gate.json });
|
|
520
|
+
process.exit(EXIT.blocked);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
fail(`debt-sweep-gate failed: ${gate.stderr}`, EXIT.runtime_error);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function handleSatisfactionGate(state, campaign, args) {
|
|
527
|
+
const compute = runNode("compute-satisfaction.mjs", [
|
|
528
|
+
"--campaign-id",
|
|
529
|
+
state.campaign_id,
|
|
530
|
+
"--iteration",
|
|
531
|
+
String(state.iteration),
|
|
532
|
+
]);
|
|
533
|
+
if (!compute.ok) fail(`compute-satisfaction failed: ${compute.stderr}`, EXIT.runtime_error);
|
|
534
|
+
|
|
535
|
+
const loop = runNode("satisfaction-loop-gate.mjs", [
|
|
536
|
+
"--campaign-id",
|
|
537
|
+
state.campaign_id,
|
|
538
|
+
"--iteration",
|
|
539
|
+
String(state.iteration),
|
|
540
|
+
"--run-id",
|
|
541
|
+
args.runId,
|
|
542
|
+
"--advance",
|
|
543
|
+
]);
|
|
544
|
+
|
|
545
|
+
if (loop.status === 0) {
|
|
546
|
+
const adv = advancePhase(args.runId, "satisfaction_gate", { force: true });
|
|
547
|
+
if (!adv.ok) fail(`advance satisfaction_gate failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
548
|
+
state.phase = advanceRunnerPhase(args.runId, "satisfaction_gate", state);
|
|
549
|
+
state.status = "complete";
|
|
550
|
+
state.substep = null;
|
|
551
|
+
saveRunnerState(state);
|
|
552
|
+
clearYield(state.campaign_id);
|
|
553
|
+
emitResult(state, { action: "complete", gate: loop.json });
|
|
554
|
+
process.exit(EXIT.complete);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (loop.status === 3) {
|
|
558
|
+
campaign = loadCampaign(state.campaign_id);
|
|
559
|
+
state.iteration = campaign?.iteration ?? state.iteration + 1;
|
|
560
|
+
state.phase = "scan";
|
|
561
|
+
state.substep = null;
|
|
562
|
+
state.wave_index = 0;
|
|
563
|
+
state.attempt = 1;
|
|
564
|
+
state.status = "running";
|
|
565
|
+
saveRunnerState(state);
|
|
566
|
+
journal(
|
|
567
|
+
state.campaign_id,
|
|
568
|
+
`- Runner **CONTINUE** → iteration ${state.iteration} (score below threshold)`,
|
|
569
|
+
);
|
|
570
|
+
return EXIT.progressed;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
fail(`satisfaction-loop-gate failed: ${loop.stderr}`, EXIT.runtime_error);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function handleReport(state, args) {
|
|
577
|
+
const validate = runNode("validate-campaign-complete.mjs", [
|
|
578
|
+
"--campaign-id",
|
|
579
|
+
state.campaign_id,
|
|
580
|
+
"--iteration",
|
|
581
|
+
String(state.iteration),
|
|
582
|
+
"--require-debt-sweep",
|
|
583
|
+
"--require-satisfaction-loop",
|
|
584
|
+
]);
|
|
585
|
+
if (!validate.ok) {
|
|
586
|
+
fail(`validate-campaign-complete failed: ${validate.stderr}`, EXIT.blocked);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
setYield(state, {
|
|
590
|
+
type: "report",
|
|
591
|
+
phase: "report",
|
|
592
|
+
iteration: state.iteration,
|
|
593
|
+
instructions:
|
|
594
|
+
"Write artifacts/report.md from campaign journal + satisfaction-history. Advance phase report and mark Run completed.",
|
|
595
|
+
resume_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${state.campaign_id} --ack-yield report`,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function ackYield(state, args, ackType) {
|
|
600
|
+
const pending = loadYield(state.campaign_id);
|
|
601
|
+
if (!pending && state.status !== "yielded") {
|
|
602
|
+
fail("No pending yield to acknowledge", EXIT.runtime_error);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
clearYield(state.campaign_id);
|
|
606
|
+
state.status = "running";
|
|
607
|
+
state.yield = null;
|
|
608
|
+
state.tick_count += 1;
|
|
609
|
+
|
|
610
|
+
switch (ackType) {
|
|
611
|
+
case "check_swarm": {
|
|
612
|
+
const check = checkSwarmRawReady(state.campaign_id, state.iteration);
|
|
613
|
+
if (!check.ok) fail(`check_swarm ack failed: ${check.reason}`, EXIT.runtime_error);
|
|
614
|
+
state.substep = "merge";
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case "dispatch_queue":
|
|
618
|
+
state.substep = null;
|
|
619
|
+
break;
|
|
620
|
+
case "execute_wave":
|
|
621
|
+
state.substep = "wave_verify";
|
|
622
|
+
break;
|
|
623
|
+
case "remediator":
|
|
624
|
+
state.attempt += 1;
|
|
625
|
+
if (state.phase === "execute") state.substep = "wave_verify";
|
|
626
|
+
break;
|
|
627
|
+
case "report": {
|
|
628
|
+
const adv = advancePhase(args.runId, "report", { force: true });
|
|
629
|
+
if (!adv.ok) fail(`advance report failed: ${adv.stderr}`, EXIT.runtime_error);
|
|
630
|
+
state.status = "complete";
|
|
631
|
+
state.phase = "report";
|
|
632
|
+
saveRunnerState(state);
|
|
633
|
+
emitResult(state, { action: "complete" });
|
|
634
|
+
process.exit(EXIT.complete);
|
|
635
|
+
}
|
|
636
|
+
default:
|
|
637
|
+
fail(`Unknown ack type: ${ackType}`, EXIT.runtime_error);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
saveRunnerState(state);
|
|
641
|
+
emitResult(state, { action: "ack", ack_type: ackType });
|
|
642
|
+
process.exit(EXIT.progressed);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function runTick(state, campaign, manifest, args) {
|
|
646
|
+
markProgress(state, campaign);
|
|
647
|
+
state.tick_count += 1;
|
|
648
|
+
|
|
649
|
+
const manifestPhase = state.phase;
|
|
650
|
+
|
|
651
|
+
switch (manifestPhase) {
|
|
652
|
+
case "campaign_init":
|
|
653
|
+
return handleCampaignInit(state, campaign, manifest, args);
|
|
654
|
+
case "scan":
|
|
655
|
+
return handleScan(state, campaign, args);
|
|
656
|
+
case "check_swarm":
|
|
657
|
+
return handleCheckSwarm(state, args);
|
|
658
|
+
case "plan_waves":
|
|
659
|
+
return handlePlanWaves(state, campaign, args);
|
|
660
|
+
case "execute":
|
|
661
|
+
return handleExecute(state, campaign, args);
|
|
662
|
+
case "debt_sweep":
|
|
663
|
+
return handleDebtSweep(state, args);
|
|
664
|
+
case "satisfaction_gate":
|
|
665
|
+
return handleSatisfactionGate(state, campaign, args);
|
|
666
|
+
case "report":
|
|
667
|
+
handleReport(state, args);
|
|
668
|
+
return EXIT.yield_agent;
|
|
669
|
+
default:
|
|
670
|
+
fail(`Unknown phase: ${state.phase}`, EXIT.runtime_error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function printStatus(state, campaign) {
|
|
675
|
+
const payload = {
|
|
676
|
+
runner_state_path: runnerStatePath(state.campaign_id),
|
|
677
|
+
yield_path: yieldArtifactPath(state.campaign_id),
|
|
678
|
+
campaign_status: campaign.status,
|
|
679
|
+
config: campaign.config,
|
|
680
|
+
current: campaign.current,
|
|
681
|
+
runner: state,
|
|
682
|
+
yield: loadYield(state.campaign_id),
|
|
683
|
+
};
|
|
684
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const args = parseArgs(process.argv.slice(2));
|
|
688
|
+
if (!args.runId || !args.campaignId) {
|
|
689
|
+
console.error(
|
|
690
|
+
"Usage: remediation-runner.mjs --run-id <id> --campaign-id <id> [--tick|--until-yield|--ack-yield <type>|--status]",
|
|
691
|
+
);
|
|
692
|
+
process.exit(2);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const campaign = loadCampaign(args.campaignId);
|
|
696
|
+
if (!campaign) fail(`Campaign not found: ${args.campaignId}`, EXIT.runtime_error);
|
|
697
|
+
|
|
698
|
+
const manifest = loadManifest(args.runId);
|
|
699
|
+
const state = ensureRunnerState(args, campaign, manifest);
|
|
700
|
+
|
|
701
|
+
if (args.mode === "status") {
|
|
702
|
+
printStatus(state, campaign);
|
|
703
|
+
process.exit(0);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (args.mode === "ack-yield") {
|
|
707
|
+
if (!args.ackType) fail("--ack-yield requires a type", EXIT.runtime_error);
|
|
708
|
+
ackYield(state, args, args.ackType);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (args.mode === "tick") {
|
|
712
|
+
const code = runTick(state, campaign, manifest, args);
|
|
713
|
+
emitResult(loadRunnerState(args.campaignId), { action: "tick" });
|
|
714
|
+
process.exit(code === EXIT.yield_agent ? EXIT.yield_agent : code);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (args.mode === "until-yield") {
|
|
718
|
+
const maxTicks = 500;
|
|
719
|
+
for (let i = 0; i < maxTicks; i++) {
|
|
720
|
+
const freshCampaign = loadCampaign(args.campaignId);
|
|
721
|
+
const freshManifest = loadManifest(args.runId);
|
|
722
|
+
const freshState = ensureRunnerState(args, freshCampaign, freshManifest);
|
|
723
|
+
if (freshState.status === "complete") {
|
|
724
|
+
emitResult(freshState, { action: "complete" });
|
|
725
|
+
process.exit(EXIT.complete);
|
|
726
|
+
}
|
|
727
|
+
if (freshState.status === "blocked") {
|
|
728
|
+
emitResult(freshState, { action: "blocked" });
|
|
729
|
+
process.exit(EXIT.blocked);
|
|
730
|
+
}
|
|
731
|
+
if (freshState.status === "yielded") {
|
|
732
|
+
emitResult(freshState, { action: "yield" });
|
|
733
|
+
process.exit(EXIT.yield_agent);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
const code = runTick(freshState, freshCampaign, freshManifest, args);
|
|
738
|
+
if (code === EXIT.yield_agent) process.exit(EXIT.yield_agent);
|
|
739
|
+
if (code === EXIT.complete) process.exit(EXIT.complete);
|
|
740
|
+
if (code === EXIT.blocked) process.exit(EXIT.blocked);
|
|
741
|
+
} catch (err) {
|
|
742
|
+
fail(String(err?.message ?? err), EXIT.runtime_error);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
fail(`until-yield exceeded ${maxTicks} ticks`, EXIT.blocked);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
fail(`Unknown mode: ${args.mode}`, EXIT.runtime_error);
|