@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.
Files changed (104) hide show
  1. package/README.md +27 -12
  2. package/package.json +9 -9
  3. package/src/cli.mjs +19 -7
  4. package/src/generators/generate-commands.mjs +25 -1
  5. package/src/generators/generate-graph.mjs +9 -1
  6. package/src/lib/install.mjs +13 -1
  7. package/src/lib/sweep-project-docs.mjs +348 -0
  8. package/src/run-engine/advance-phase.mjs +23 -0
  9. package/src/run-engine/debug-run.mjs +0 -0
  10. package/src/run-engine/gate-write.mjs +13 -0
  11. package/src/run-engine/lib.mjs +153 -5
  12. package/src/run-engine/log-dump.mjs +0 -0
  13. package/src/run-engine/log-trace.mjs +0 -0
  14. package/templates/cursor/aaac/enforcement.json +96 -5
  15. package/templates/cursor/aaac/graph.project.yaml +44 -5
  16. package/templates/cursor/aaac/lifecycle/lifecycle.json +26 -0
  17. package/templates/cursor/aaac/lifecycle/phases.json +9 -1
  18. package/templates/cursor/aaac/ontology.json +1 -0
  19. package/templates/cursor/aaac/project.config.json +36 -0
  20. package/templates/cursor/aaac/scripts/remediation/auto-check-swarm-synthesis.mjs +75 -0
  21. package/templates/cursor/aaac/scripts/remediation/auto-dispatch-queue-from-health.mjs +78 -0
  22. package/templates/cursor/aaac/scripts/remediation/bootstrap-autonomous.mjs +113 -0
  23. package/templates/cursor/aaac/scripts/remediation/capture-verify-baseline.mjs +66 -0
  24. package/templates/cursor/aaac/scripts/remediation/capture-wave-snapshot.mjs +79 -0
  25. package/templates/cursor/aaac/scripts/remediation/check-swarm-raw.template.json +26 -0
  26. package/templates/cursor/aaac/scripts/remediation/classify-fallow-issues.mjs +77 -0
  27. package/templates/cursor/aaac/scripts/remediation/classify-verify-failure.mjs +176 -0
  28. package/templates/cursor/aaac/scripts/remediation/compute-satisfaction.mjs +344 -0
  29. package/templates/cursor/aaac/scripts/remediation/debt-sweep-gate.mjs +202 -0
  30. package/templates/cursor/aaac/scripts/remediation/dispatch-rules.json +44 -0
  31. package/templates/cursor/aaac/scripts/remediation/fallow-fp-rules.json +87 -0
  32. package/templates/cursor/aaac/scripts/remediation/fallow-scan.mjs +219 -0
  33. package/templates/cursor/aaac/scripts/remediation/handle-yield.mjs +240 -0
  34. package/templates/cursor/aaac/scripts/remediation/init-campaign.mjs +211 -0
  35. package/templates/cursor/aaac/scripts/remediation/lib/autonomous-mode.mjs +63 -0
  36. package/templates/cursor/aaac/scripts/remediation/lib/campaign-focus.mjs +87 -0
  37. package/templates/cursor/aaac/scripts/remediation/lib/fallow-classifier.mjs +190 -0
  38. package/templates/cursor/aaac/scripts/remediation/lib/fallow-health-targets.mjs +56 -0
  39. package/templates/cursor/aaac/scripts/remediation/lib/fallow-metrics.mjs +119 -0
  40. package/templates/cursor/aaac/scripts/remediation/lib/invoke-cursor-agent.mjs +51 -0
  41. package/templates/cursor/aaac/scripts/remediation/lib/reconcile-run-manifest.mjs +41 -0
  42. package/templates/cursor/aaac/scripts/remediation/lib/regression-analysis.mjs +55 -0
  43. package/templates/cursor/aaac/scripts/remediation/lib/remediation-config.mjs +69 -0
  44. package/templates/cursor/aaac/scripts/remediation/lib/remediation-progress.mjs +58 -0
  45. package/templates/cursor/aaac/scripts/remediation/lib/remediation-watch-loop.mjs +168 -0
  46. package/templates/cursor/aaac/scripts/remediation/lib/runner-exec.mjs +156 -0
  47. package/templates/cursor/aaac/scripts/remediation/lib/runner-state.mjs +145 -0
  48. package/templates/cursor/aaac/scripts/remediation/lib/verify-metrics.mjs +205 -0
  49. package/templates/cursor/aaac/scripts/remediation/merge-check-swarm.mjs +257 -0
  50. package/templates/cursor/aaac/scripts/remediation/plan-waves-from-queue.mjs +85 -0
  51. package/templates/cursor/aaac/scripts/remediation/prepare-check-context.mjs +148 -0
  52. package/templates/cursor/aaac/scripts/remediation/record-fallow-fp.mjs +107 -0
  53. package/templates/cursor/aaac/scripts/remediation/record-iteration-step.mjs +56 -0
  54. package/templates/cursor/aaac/scripts/remediation/remediation-cli.mjs +157 -0
  55. package/templates/cursor/aaac/scripts/remediation/remediation-cursor-watch.sh +10 -0
  56. package/templates/cursor/aaac/scripts/remediation/remediation-runner-daemon.sh +13 -0
  57. package/templates/cursor/aaac/scripts/remediation/remediation-runner.mjs +748 -0
  58. package/templates/cursor/aaac/scripts/remediation/remediation-yield-watcher.mjs +40 -0
  59. package/templates/cursor/aaac/scripts/remediation/remediator-gate.mjs +405 -0
  60. package/templates/cursor/aaac/scripts/remediation/repair-fallow-start-baseline.mjs +118 -0
  61. package/templates/cursor/aaac/scripts/remediation/runner-health-check.mjs +164 -0
  62. package/templates/cursor/aaac/scripts/remediation/satisfaction-loop-gate.mjs +286 -0
  63. package/templates/cursor/aaac/scripts/remediation/validate-campaign-complete.mjs +191 -0
  64. package/templates/cursor/aaac/scripts/remediation/verify-remediation-iteration.mjs +112 -0
  65. package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +23 -0
  66. package/templates/cursor/aaac/scripts/run-engine/debug-run.mjs +0 -0
  67. package/templates/cursor/aaac/scripts/run-engine/gate-write.mjs +13 -0
  68. package/templates/cursor/aaac/scripts/run-engine/lib.mjs +153 -5
  69. package/templates/cursor/aaac/scripts/run-engine/log-dump.mjs +0 -0
  70. package/templates/cursor/aaac/scripts/run-engine/log-trace.mjs +0 -0
  71. package/templates/cursor/agents/doc-conformance.md +25 -0
  72. package/templates/cursor/agents/implementation-review.md +21 -0
  73. package/templates/cursor/agents/remediation-check-app-inventory.md +32 -0
  74. package/templates/cursor/agents/remediation-check-app-ssot.md +24 -0
  75. package/templates/cursor/agents/remediation-check-app-trace.md +29 -0
  76. package/templates/cursor/agents/remediation-check-architecture-boundaries.md +21 -0
  77. package/templates/cursor/agents/remediation-check-architecture-decomposition.md +25 -0
  78. package/templates/cursor/agents/remediation-check-architecture-deps.md +23 -0
  79. package/templates/cursor/agents/remediation-check-risk.md +37 -0
  80. package/templates/cursor/agents/remediation-e2e-gate.md +30 -0
  81. package/templates/cursor/agents/remediation-remediator.md +69 -0
  82. package/templates/cursor/agents/test-author.md +27 -0
  83. package/templates/cursor/commands/remediate-app.md +212 -0
  84. package/templates/cursor/hooks/aaac-before-submit.sh +0 -0
  85. package/templates/cursor/hooks/aaac-pre-tool.sh +0 -0
  86. package/templates/cursor/hooks/aaac-stop.sh +0 -0
  87. package/templates/cursor/hooks/aaac-subagent-start.sh +0 -0
  88. package/templates/cursor/rules/aaac-enforcement.mdc +10 -3
  89. package/templates/cursor/skills/shared/execution/SKILL.md +7 -3
  90. package/templates/cursor/skills/shared/governance/implementation/SKILL.md +396 -28
  91. package/templates/cursor/skills/shared/implementation-review/SKILL.md +49 -0
  92. package/templates/cursor/skills/shared/planning/SKILL.md +5 -0
  93. package/templates/cursor/skills/shared/remediation/SKILL.md +51 -0
  94. package/templates/cursor/skills/shared/remediation/babysit/SKILL.md +223 -0
  95. package/templates/cursor/skills/shared/remediation/check-swarm/SKILL.md +114 -0
  96. package/templates/cursor/skills/shared/remediation/orchestrator/SKILL.md +275 -0
  97. package/templates/cursor/skills/shared/remediation/orchestrator/contract.yaml +116 -0
  98. package/templates/cursor/skills/shared/test-authoring/SKILL.md +58 -0
  99. package/templates/cursor/skills/shared/testing/SKILL.md +6 -0
  100. package/templates/cursor/skills/shared/verbs/create/orchestrator/SKILL.md +5 -3
  101. package/templates/cursor/skills/shared/verbs/fix/orchestrator/SKILL.md +5 -3
  102. package/templates/cursor/skills/shared/verbs/update/orchestrator/SKILL.md +5 -3
  103. package/templates/cursor/skills/shared/verification/SKILL.md +5 -3
  104. package/templates/docs/agentic_architecture.md +169 -97
@@ -0,0 +1,69 @@
1
+ /**
2
+ * SSOT for /remediate-app project wiring — reads `.cursor/aaac/project.config.json`.
3
+ */
4
+ import path from "path";
5
+ import { REPO_ROOT, readJson } from "../../run-engine/lib.mjs";
6
+
7
+ const CONFIG_PATH = path.join(REPO_ROOT, ".cursor/aaac/project.config.json");
8
+
9
+ const DEFAULT_LAYERS = [
10
+ {
11
+ id: "typecheck",
12
+ command: "npm",
13
+ args: ["run", "typecheck"],
14
+ cwd: ".",
15
+ optional: true,
16
+ },
17
+ {
18
+ id: "vitest",
19
+ command: "npm",
20
+ args: ["test"],
21
+ cwd: ".",
22
+ optional: true,
23
+ },
24
+ {
25
+ id: "build",
26
+ command: "npm",
27
+ args: ["run", "build"],
28
+ cwd: ".",
29
+ optional: true,
30
+ },
31
+ ];
32
+
33
+ export function loadRemediationConfig() {
34
+ const project = readJson(CONFIG_PATH, {});
35
+ const remediation = project.remediation ?? {};
36
+ const verify = remediation.verify ?? {};
37
+
38
+ const fallowCwd = remediation.fallow_cwd ?? remediation.scan_root ?? ".";
39
+ const scanRoot = remediation.scan_root ?? fallowCwd;
40
+
41
+ return {
42
+ fallow_cwd: fallowCwd,
43
+ scan_root: scanRoot,
44
+ verify: {
45
+ layers: Array.isArray(verify.layers) && verify.layers.length > 0 ? verify.layers : DEFAULT_LAYERS,
46
+ playwright: {
47
+ enabled: false,
48
+ config: null,
49
+ cwd: ".",
50
+ ...(verify.playwright ?? {}),
51
+ },
52
+ dev_server: {
53
+ url: "http://localhost:3000",
54
+ launch_hint:
55
+ "Start your dev server before debt sweep / Playwright gates (see project.config.json remediation.verify.dev_server).",
56
+ ...(verify.dev_server ?? {}),
57
+ },
58
+ strict_modes: verify.strict_modes ?? ["iteration", "strict", "debt"],
59
+ },
60
+ };
61
+ }
62
+
63
+ export function resolveLayerKeys(config = loadRemediationConfig()) {
64
+ const keys = config.verify.layers.map((layer) => layer.id);
65
+ if (config.verify.playwright?.enabled) {
66
+ keys.push("playwright");
67
+ }
68
+ return keys;
69
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Human-readable progress snapshots for remediation CLI / Cursor monitoring.
3
+ */
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { campaignDir, loadRunnerState, loadYield } from "./runner-state.mjs";
7
+ import { isoNow, readJson, writeJson } from "../../run-engine/lib.mjs";
8
+
9
+ export function loadSatisfaction(campaignId, iteration) {
10
+ const p = path.join(campaignDir(campaignId), "iterations", String(iteration), "satisfaction.json");
11
+ return fs.existsSync(p) ? readJson(p, null) : null;
12
+ }
13
+
14
+ export function buildProgressSnapshot(campaignId, runId, extra = {}) {
15
+ const campaign = readJson(path.join(campaignDir(campaignId), "campaign.json"), null);
16
+ const runner = loadRunnerState(campaignId);
17
+ const yieldPayload = loadYield(campaignId);
18
+ const iteration = campaign?.iteration ?? runner?.iteration ?? 0;
19
+ const satisfaction = loadSatisfaction(campaignId, iteration);
20
+
21
+ return {
22
+ at: isoNow(),
23
+ campaign_id: campaignId,
24
+ run_id: runId,
25
+ iteration,
26
+ phase: runner?.phase ?? null,
27
+ substep: runner?.substep ?? null,
28
+ wave_index: runner?.wave_index ?? null,
29
+ runner_status: runner?.status ?? null,
30
+ yield_type: yieldPayload?.type ?? null,
31
+ satisfaction_score: satisfaction?.score ?? campaign?.current?.satisfaction_score ?? null,
32
+ satisfaction_threshold: campaign?.config?.satisfaction_threshold ?? null,
33
+ health_score: campaign?.current?.fallow_health_score ?? null,
34
+ clone_groups: campaign?.current?.fallow_dupes_clone_groups ?? null,
35
+ intent: campaign?.intent ?? null,
36
+ ...extra,
37
+ };
38
+ }
39
+
40
+ export function writeProgressArtifact(campaignId, snapshot) {
41
+ const out = path.join(campaignDir(campaignId), "progress.json");
42
+ writeJson(out, snapshot);
43
+ return out;
44
+ }
45
+
46
+ export function formatProgressLine(snapshot, event) {
47
+ const score = snapshot.satisfaction_score ?? "?";
48
+ const threshold = snapshot.satisfaction_threshold ?? "?";
49
+ const health = snapshot.health_score ?? "?";
50
+ const phase = [snapshot.phase, snapshot.substep].filter(Boolean).join("/") || "—";
51
+ const wave = snapshot.wave_index != null && snapshot.phase === "execute" ? ` wave ${snapshot.wave_index}` : "";
52
+ const yieldHint = snapshot.yield_type ? ` → yield ${snapshot.yield_type}` : "";
53
+ const eventHint = event ? ` [${event}]` : "";
54
+ return (
55
+ `[remediate] iter ${snapshot.iteration} | ${phase}${wave} | ` +
56
+ `score ${score}/${threshold} | health ${health}${yieldHint}${eventHint}`
57
+ );
58
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Shared remediation watch loop — used by yield-watcher and remediation-cli.
3
+ */
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { isoNow, writeJson } from "../../run-engine/lib.mjs";
7
+ import { campaignDir, loadCampaign, saveCampaign } from "./runner-state.mjs";
8
+ import { isGoalAchieved } from "./campaign-focus.mjs";
9
+ import { runNode } from "./runner-exec.mjs";
10
+ import {
11
+ buildProgressSnapshot,
12
+ formatProgressLine,
13
+ loadSatisfaction,
14
+ writeProgressArtifact,
15
+ } from "./remediation-progress.mjs";
16
+
17
+ const RUNNER = "remediation-runner.mjs";
18
+ const HANDLE = "handle-yield.mjs";
19
+ const HEALTH = "runner-health-check.mjs";
20
+
21
+ function extendMaxIterationsIfNeeded(campaign, satisfaction) {
22
+ const threshold = campaign.config?.satisfaction_threshold ?? 85;
23
+ const score = satisfaction?.score ?? campaign.current?.satisfaction_score ?? 0;
24
+ if (score >= threshold) return false;
25
+ const iter = campaign.iteration ?? 0;
26
+ const max = campaign.config?.max_iterations ?? 5;
27
+ if (iter + 1 < max) return false;
28
+ campaign.config.max_iterations = max + 25;
29
+ campaign.status = "running";
30
+ saveCampaign(campaign);
31
+ return true;
32
+ }
33
+
34
+ function sleep(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+
38
+ /**
39
+ * @param {object} args
40
+ * @param {string} args.runId
41
+ * @param {string} args.campaignId
42
+ * @param {number} [args.pollMs]
43
+ * @param {number} [args.maxRetries]
44
+ * @param {object} [args.reporter]
45
+ */
46
+ export async function runRemediationWatchLoop(args) {
47
+ const pollMs = args.pollMs ?? 5000;
48
+ const maxRetries = args.maxRetries ?? 5;
49
+ const reporter = args.reporter ?? {};
50
+ const emit = (event, detail = {}) => reporter.onEvent?.(event, detail);
51
+ const progress = (event, extra = {}) => {
52
+ const snap = buildProgressSnapshot(args.campaignId, args.runId, { event, ...extra });
53
+ writeProgressArtifact(args.campaignId, snap);
54
+ reporter.onProgress?.(snap, event);
55
+ return snap;
56
+ };
57
+
58
+ const statePath = path.join(campaignDir(args.campaignId), "watcher-state.json");
59
+ let watcherState = fs.existsSync(statePath)
60
+ ? JSON.parse(fs.readFileSync(statePath, "utf8"))
61
+ : { started_at: isoNow(), cycles: 0, failures: 0 };
62
+ watcherState.status = "running";
63
+ writeJson(statePath, watcherState);
64
+
65
+ emit("start", { run_id: args.runId, campaign_id: args.campaignId });
66
+ progress("start");
67
+
68
+ while (true) {
69
+ const campaign = loadCampaign(args.campaignId);
70
+ if (!campaign) {
71
+ emit("error", { message: "campaign missing" });
72
+ process.exit(2);
73
+ }
74
+
75
+ const satisfaction = loadSatisfaction(args.campaignId, campaign.iteration ?? 0);
76
+ if (isGoalAchieved(campaign, satisfaction)) {
77
+ progress("goal_achieved", { satisfaction_score: satisfaction?.score ?? campaign.current?.satisfaction_score });
78
+ watcherState.status = "complete";
79
+ watcherState.completed_at = isoNow();
80
+ writeJson(statePath, watcherState);
81
+ emit("goal_achieved");
82
+ return 0;
83
+ }
84
+
85
+ if (extendMaxIterationsIfNeeded(campaign, satisfaction)) {
86
+ progress("extend_max_iterations");
87
+ }
88
+
89
+ runNode(HEALTH, ["--campaign-id", args.campaignId]);
90
+ progress("health_ok");
91
+
92
+ const runner = runNode(RUNNER, [
93
+ "--run-id", args.runId,
94
+ "--campaign-id", args.campaignId,
95
+ "--until-yield",
96
+ ]);
97
+
98
+ watcherState.cycles += 1;
99
+ writeJson(statePath, watcherState);
100
+
101
+ if (runner.status === 10) {
102
+ progress("runner_progressed");
103
+ continue;
104
+ }
105
+
106
+ if (runner.status === 0) {
107
+ progress("runner_complete");
108
+ watcherState.status = "complete";
109
+ watcherState.completed_at = isoNow();
110
+ writeJson(statePath, watcherState);
111
+ emit("runner_complete");
112
+ return 0;
113
+ }
114
+
115
+ if (runner.status === 3) {
116
+ const yieldType = runner.json?.yield?.type ?? "unknown";
117
+ progress("yield", { yield_type: yieldType });
118
+ emit("yield", { yield_type: yieldType, runner: runner.json });
119
+
120
+ let handled = false;
121
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
122
+ progress("handle_start", { yield_type: yieldType, attempt });
123
+ const handle = runNode(HANDLE, ["--run-id", args.runId, "--campaign-id", args.campaignId]);
124
+ if (!handle.ok) {
125
+ emit("handle_failed", { attempt, stderr: handle.stderr.slice(0, 300) });
126
+ watcherState.failures += 1;
127
+ writeJson(statePath, watcherState);
128
+ progress("handle_failed", { attempt });
129
+ await sleep(pollMs * attempt);
130
+ continue;
131
+ }
132
+ const ackType = handle.json?.ack_type;
133
+ const ack = runNode(RUNNER, [
134
+ "--run-id", args.runId,
135
+ "--campaign-id", args.campaignId,
136
+ "--ack-yield", ackType,
137
+ ]);
138
+ if (ack.status === 0 || ack.status === 10 || ack.ok) {
139
+ handled = true;
140
+ progress("yield_acked", { ack_type: ackType, attempt });
141
+ emit("yield_acked", { ack_type: ackType, attempt });
142
+ break;
143
+ }
144
+ progress("ack_failed", { ack_type: ackType, attempt });
145
+ await sleep(pollMs);
146
+ }
147
+ if (!handled) {
148
+ progress("yield_stuck");
149
+ emit("yield_stuck");
150
+ await sleep(pollMs * 2);
151
+ }
152
+ continue;
153
+ }
154
+
155
+ if (runner.status === 1) {
156
+ progress("blocked");
157
+ emit("blocked", { stderr: runner.stderr.slice(0, 300) });
158
+ await sleep(pollMs * 3);
159
+ continue;
160
+ }
161
+
162
+ progress("runner_error", { exit: runner.status });
163
+ emit("runner_error", { exit: runner.status, stderr: runner.stderr.slice(0, 300) });
164
+ watcherState.failures += 1;
165
+ writeJson(statePath, watcherState);
166
+ await sleep(pollMs * 2);
167
+ }
168
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Subprocess helpers for remediation runner.
3
+ */
4
+ import path from "path";
5
+ import { spawnSync } from "child_process";
6
+ import { fileURLToPath } from "url";
7
+ import fs from "fs";
8
+ import { REPO_ROOT, isoNow, writeJson } from "../../run-engine/lib.mjs";
9
+ import { runArtifactsDir } from "./runner-state.mjs";
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const SCRIPTS = path.join(__dirname, "..");
13
+ const ADVANCE = path.join(REPO_ROOT, ".cursor/aaac/scripts/run-engine/advance-phase.mjs");
14
+
15
+ export function runNode(scriptName, args = [], { cwd = REPO_ROOT } = {}) {
16
+ const scriptPath = path.join(SCRIPTS, scriptName);
17
+ const result = spawnSync(process.execPath, [scriptPath, ...args], {
18
+ encoding: "utf8",
19
+ cwd,
20
+ });
21
+ let json = null;
22
+ const lines = (result.stdout ?? "").trim().split("\n").filter(Boolean);
23
+ for (let i = lines.length - 1; i >= 0; i--) {
24
+ try {
25
+ json = JSON.parse(lines[i]);
26
+ break;
27
+ } catch {
28
+ /* continue */
29
+ }
30
+ }
31
+ return {
32
+ ok: result.status === 0,
33
+ status: result.status ?? 1,
34
+ stdout: result.stdout ?? "",
35
+ stderr: result.stderr ?? "",
36
+ json,
37
+ };
38
+ }
39
+
40
+ export function advancePhase(runId, completedPhase, { force = false } = {}) {
41
+ const args = [ADVANCE, runId, completedPhase];
42
+ if (force) args.push("--force");
43
+ const result = spawnSync(process.execPath, args, { encoding: "utf8" });
44
+ return {
45
+ ok: result.status === 0,
46
+ status: result.status ?? 1,
47
+ stdout: result.stdout ?? "",
48
+ stderr: result.stderr ?? "",
49
+ };
50
+ }
51
+
52
+ export function copyFileIfExists(src, dest) {
53
+ if (!fs.existsSync(src)) return false;
54
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
55
+ fs.copyFileSync(src, dest);
56
+ return true;
57
+ }
58
+
59
+ export function writeRunArtifact(runId, rel, content) {
60
+ const dest = path.join(runArtifactsDir(runId), rel);
61
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
62
+ if (typeof content === "string") {
63
+ fs.writeFileSync(dest, content);
64
+ } else {
65
+ writeJson(dest, content);
66
+ }
67
+ return dest;
68
+ }
69
+
70
+ export function parseDispatchQueueYaml(text) {
71
+ const waves = [];
72
+ const lines = text.split("\n");
73
+ let inWaves = false;
74
+ let current = null;
75
+
76
+ for (const raw of lines) {
77
+ const line = raw.trimEnd();
78
+ if (line === "waves:") {
79
+ inWaves = true;
80
+ continue;
81
+ }
82
+ if (!inWaves) continue;
83
+ if (line.startsWith("- priority:")) {
84
+ if (current) waves.push(current);
85
+ current = { priority: Number(line.split(":")[1].trim()) };
86
+ continue;
87
+ }
88
+ if (!current) continue;
89
+ if (line.startsWith("command:")) {
90
+ current.command = line.slice("command:".length).trim();
91
+ } else if (line.startsWith("intent:")) {
92
+ current.intent = line.slice("intent:".length).trim();
93
+ } else if (line.startsWith("risk:")) {
94
+ current.risk = line.slice("risk:".length).trim();
95
+ } else if (line.startsWith("est_clone_groups_delta:")) {
96
+ current.est_clone_groups_delta = Number(line.split(":")[1].trim());
97
+ } else if (line.startsWith("intent: >-")) {
98
+ current.intent = "";
99
+ current._intentBlock = true;
100
+ } else if (current._intentBlock && line && !line.includes(":")) {
101
+ current.intent = `${current.intent} ${line.trim()}`.trim();
102
+ } else if (line.includes(":") && !line.startsWith(" ")) {
103
+ current._intentBlock = false;
104
+ }
105
+ }
106
+ if (current) waves.push(current);
107
+ return waves.map((w, index) => ({
108
+ index,
109
+ priority: w.priority ?? index + 1,
110
+ command: w.command ?? "fix-module",
111
+ intent: (w.intent ?? "").trim(),
112
+ risk: w.risk ?? "low",
113
+ est_clone_groups_delta: w.est_clone_groups_delta ?? null,
114
+ status: "pending",
115
+ }));
116
+ }
117
+
118
+ export function buildPlanWavesYaml({ campaign, waves, source = "dispatch-queue.yaml" }) {
119
+ const header = [
120
+ `# Iteration ${campaign.iteration} plan waves`,
121
+ "",
122
+ `campaign_id: ${campaign.campaign_id}`,
123
+ `iteration: ${campaign.iteration}`,
124
+ `scope: ${campaign.scope ?? "whole-repo"}`,
125
+ `max_waves: ${campaign.config?.max_waves_per_iteration ?? 3}`,
126
+ `source: ${source}`,
127
+ "",
128
+ "waves:",
129
+ ];
130
+ const body = waves.flatMap((w) => [
131
+ ` - index: ${w.index}`,
132
+ ` priority: ${w.priority}`,
133
+ ` command: ${w.command}`,
134
+ ` status: pending`,
135
+ ` risk: ${w.risk}`,
136
+ w.est_clone_groups_delta != null
137
+ ? ` est_clone_groups_delta: ${w.est_clone_groups_delta}`
138
+ : null,
139
+ ` intent: >-`,
140
+ ` ${w.intent}`,
141
+ "",
142
+ ].filter(Boolean));
143
+ return `${header.join("\n")}\n${body.join("\n")}`;
144
+ }
145
+
146
+ export function journal(campaignId, line) {
147
+ const journalPath = path.join(
148
+ REPO_ROOT,
149
+ ".cursor/aaac/state/campaigns",
150
+ campaignId,
151
+ "journal.md",
152
+ );
153
+ fs.appendFileSync(journalPath, `\n${line}\n`);
154
+ }
155
+
156
+ export { isoNow };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * SSOT helpers for remediation shell runner state.
3
+ */
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import {
7
+ REPO_ROOT,
8
+ isoNow,
9
+ readJson,
10
+ writeJson,
11
+ loadRunManifest,
12
+ runDir,
13
+ } from "../../run-engine/lib.mjs";
14
+
15
+ export const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
16
+ export const RUNNER_VERSION = 1;
17
+
18
+ /** Runner exit codes (documented in babysit skill + remediate-app command). */
19
+ export const EXIT = {
20
+ complete: 0,
21
+ blocked: 1,
22
+ runtime_error: 2,
23
+ yield_agent: 3,
24
+ progressed: 10,
25
+ };
26
+
27
+ export const PHASES = [
28
+ "campaign_init",
29
+ "scan",
30
+ "check_swarm",
31
+ "plan_waves",
32
+ "execute",
33
+ "debt_sweep",
34
+ "satisfaction_gate",
35
+ "report",
36
+ ];
37
+
38
+ export function campaignDir(campaignId) {
39
+ return path.join(CAMPAIGNS_ROOT, campaignId);
40
+ }
41
+
42
+ export function iterDir(campaignId, iteration) {
43
+ return path.join(campaignDir(campaignId), "iterations", String(iteration));
44
+ }
45
+
46
+ export function runnerStatePath(campaignId) {
47
+ return path.join(campaignDir(campaignId), "runner-state.json");
48
+ }
49
+
50
+ export function yieldArtifactPath(campaignId) {
51
+ return path.join(campaignDir(campaignId), "runner-yield.json");
52
+ }
53
+
54
+ export function loadCampaign(campaignId) {
55
+ return readJson(path.join(campaignDir(campaignId), "campaign.json"), null);
56
+ }
57
+
58
+ export function saveCampaign(campaign) {
59
+ campaign.updated_at = isoNow();
60
+ writeJson(path.join(campaignDir(campaign.campaign_id), "campaign.json"), campaign);
61
+ }
62
+
63
+ export function defaultRunnerState({ runId, campaignId, iteration = 0, phase = "campaign_init" }) {
64
+ return {
65
+ version: RUNNER_VERSION,
66
+ run_id: runId,
67
+ campaign_id: campaignId,
68
+ status: "running",
69
+ phase,
70
+ substep: null,
71
+ iteration,
72
+ wave_index: 0,
73
+ attempt: 1,
74
+ tick_count: 0,
75
+ stall_count: 0,
76
+ last_score: null,
77
+ last_clone_groups: null,
78
+ last_progress_at: isoNow(),
79
+ yield: null,
80
+ started_at: isoNow(),
81
+ updated_at: isoNow(),
82
+ };
83
+ }
84
+
85
+ export function loadRunnerState(campaignId) {
86
+ return readJson(runnerStatePath(campaignId), null);
87
+ }
88
+
89
+ export function saveRunnerState(state) {
90
+ state.updated_at = isoNow();
91
+ writeJson(runnerStatePath(state.campaign_id), state);
92
+ }
93
+
94
+ export function writeYield(campaignId, yieldPayload) {
95
+ writeJson(yieldArtifactPath(campaignId), {
96
+ ...yieldPayload,
97
+ at: isoNow(),
98
+ });
99
+ }
100
+
101
+ export function clearYield(campaignId) {
102
+ const p = yieldArtifactPath(campaignId);
103
+ if (fs.existsSync(p)) fs.unlinkSync(p);
104
+ }
105
+
106
+ export function loadYield(campaignId) {
107
+ return readJson(yieldArtifactPath(campaignId), null);
108
+ }
109
+
110
+ export function syncRunnerFromManifest(state, manifest) {
111
+ if (!manifest) return state;
112
+ state.phase = manifest.phase ?? state.phase;
113
+ state.iteration = manifest.campaign_iteration ?? state.iteration;
114
+ return state;
115
+ }
116
+
117
+ export function loadManifest(runId) {
118
+ return loadRunManifest(runId);
119
+ }
120
+
121
+ export function runArtifactsDir(runId) {
122
+ return path.join(runDir(runId), "artifacts");
123
+ }
124
+
125
+ export function emitResult(state, extra = {}) {
126
+ const payload = {
127
+ ok: true,
128
+ status: state.status,
129
+ phase: state.phase,
130
+ substep: state.substep,
131
+ iteration: state.iteration,
132
+ wave_index: state.wave_index,
133
+ attempt: state.attempt,
134
+ tick_count: state.tick_count,
135
+ yield: state.yield,
136
+ ...extra,
137
+ };
138
+ console.log(JSON.stringify(payload));
139
+ return payload;
140
+ }
141
+
142
+ export function fail(message, code = EXIT.runtime_error) {
143
+ console.error(message);
144
+ process.exit(code);
145
+ }