@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,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build dispatch-queue.yaml from Fallow health targets + campaign focus.
4
+ */
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { campaignDir, loadCampaign } from "./lib/runner-state.mjs";
8
+ import { loadCampaignContext, normalizeRepoPath } from "./lib/campaign-focus.mjs";
9
+ import {
10
+ fetchHealthTargets,
11
+ filterTargetsForWaves,
12
+ targetToWaveIntent,
13
+ } from "./lib/fallow-health-targets.mjs";
14
+ import { journal } from "./lib/runner-exec.mjs";
15
+
16
+ function parseArgs(argv) {
17
+ const out = { campaignId: null, iteration: null };
18
+ for (let i = 0; i < argv.length; i++) {
19
+ if (argv[i] === "--campaign-id") out.campaignId = argv[++i];
20
+ else if (argv[i] === "--iteration") out.iteration = Number(argv[++i]);
21
+ }
22
+ return out;
23
+ }
24
+
25
+ const args = parseArgs(process.argv.slice(2));
26
+ if (!args.campaignId) {
27
+ console.error("--campaign-id required");
28
+ process.exit(2);
29
+ }
30
+
31
+ const ctx = loadCampaignContext(args.campaignId);
32
+ const campaign = ctx.campaign;
33
+ const iteration = args.iteration ?? campaign.iteration ?? 0;
34
+ const maxWaves = campaign.config?.max_waves_per_iteration ?? 3;
35
+
36
+ const { targets } = fetchHealthTargets({ scope: ctx.scope, limit: 20 });
37
+ const filtered = filterTargetsForWaves(targets, {
38
+ protected_paths: ctx.protected_paths,
39
+ defer_high_fan_in: ctx.focus.defer_high_fan_in,
40
+ }).slice(0, maxWaves);
41
+
42
+ if (filtered.length === 0) {
43
+ console.error("No health targets available for waves");
44
+ process.exit(2);
45
+ }
46
+
47
+ const lines = [
48
+ `# iteration ${iteration} health waves — auto from Fallow targets`,
49
+ `iteration: ${iteration}`,
50
+ `campaign_id: ${args.campaignId}`,
51
+ `scope: ${ctx.scope}`,
52
+ `intent_focus: health`,
53
+ "",
54
+ "protected_paths:",
55
+ ...ctx.protected_paths.map((p) => ` - ${p}`),
56
+ "",
57
+ "waves:",
58
+ ];
59
+
60
+ for (let i = 0; i < filtered.length; i++) {
61
+ const t = filtered[i];
62
+ const risk = t.effort === "high" ? "medium" : "low";
63
+ lines.push(`- priority: ${i + 1}`);
64
+ lines.push(` command: fix-module`);
65
+ lines.push(` intent: ${targetToWaveIntent(t)}`);
66
+ lines.push(` risk: ${risk}`);
67
+ }
68
+
69
+ const yaml = `${lines.join("\n")}\n`;
70
+ const dest = path.join(campaignDir(args.campaignId), "dispatch-queue.yaml");
71
+ const artifact = path.join(campaignDir(args.campaignId), "artifacts", "dispatch-queue.yaml");
72
+ fs.writeFileSync(dest, yaml);
73
+ fs.mkdirSync(path.dirname(artifact), { recursive: true });
74
+ fs.writeFileSync(artifact, yaml);
75
+
76
+ journal(args.campaignId, `- **Auto dispatch-queue** iter ${iteration}: ${filtered.length} health wave(s)`);
77
+
78
+ console.log(JSON.stringify({ ok: true, waves: filtered.length, paths: filtered.map((t) => t.path) }));
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Write run + campaign artifacts when autonomous mode is active.
4
+ * Called from init-campaign.mjs — orchestrator MUST follow bootstrap.next_action.
5
+ *
6
+ * Usage:
7
+ * node bootstrap-autonomous.mjs --run-id <id> --campaign-id <id>
8
+ */
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { REPO_ROOT, isoNow, readJson, runDir, writeJson } from "../run-engine/lib.mjs";
12
+ import {
13
+ autonomousBootstrapCommands,
14
+ BABYSIT_SKILL,
15
+ } from "./lib/autonomous-mode.mjs";
16
+ import { loadRunnerState, loadYield, runnerStatePath, yieldArtifactPath } from "./lib/runner-state.mjs";
17
+
18
+ function parseArgs(argv) {
19
+ const out = { runId: null, campaignId: null };
20
+ for (let i = 0; i < argv.length; i++) {
21
+ const a = argv[i];
22
+ if (a === "--run-id") out.runId = argv[++i];
23
+ else if (a === "--campaign-id") out.campaignId = argv[++i];
24
+ }
25
+ return out;
26
+ }
27
+
28
+ const args = parseArgs(process.argv.slice(2));
29
+ if (!args.runId || !args.campaignId) {
30
+ console.error("bootstrap-autonomous: --run-id and --campaign-id required");
31
+ process.exit(2);
32
+ }
33
+
34
+ const campaignPath = path.join(
35
+ REPO_ROOT,
36
+ ".cursor/aaac/state/campaigns",
37
+ args.campaignId,
38
+ "campaign.json",
39
+ );
40
+ const campaign = readJson(campaignPath, null);
41
+ if (!campaign?.config?.autonomous) {
42
+ console.log(JSON.stringify({ ok: true, autonomous: false, skipped: true }));
43
+ process.exit(0);
44
+ }
45
+
46
+ const commands = autonomousBootstrapCommands(args.runId, args.campaignId);
47
+ const runnerState = loadRunnerState(args.campaignId);
48
+ const yieldPayload = loadYield(args.campaignId);
49
+
50
+ const bootstrap = {
51
+ ok: true,
52
+ autonomous: true,
53
+ autonomous_reason: campaign.config.autonomous_reason,
54
+ campaign_id: args.campaignId,
55
+ run_id: args.runId,
56
+ iteration: campaign.iteration,
57
+ satisfaction_threshold: campaign.config.satisfaction_threshold,
58
+ orchestrator_mode: "shell_runner_yield_watcher",
59
+ skill_required: BABYSIT_SKILL,
60
+ orchestrator_must_not: [
61
+ "walk_phases_manually_in_chat",
62
+ "end_turn_before_runner_exit_0",
63
+ "report_when_satisfaction_below_threshold",
64
+ ],
65
+ loop: [
66
+ "read_babysit_skill",
67
+ "runner_health_check",
68
+ "remediation_yield_watcher",
69
+ "if_exit_3_handle_yield_then_ack_yield",
70
+ "repeat_until_exit_0",
71
+ ],
72
+ commands,
73
+ runner_state_path: runnerStatePath(args.campaignId),
74
+ yield_path: yieldArtifactPath(args.campaignId),
75
+ current_runner: runnerState,
76
+ pending_yield: yieldPayload,
77
+ next_action: yieldPayload
78
+ ? {
79
+ type: "handle_yield",
80
+ yield_type: yieldPayload.type,
81
+ ack_command: `node .cursor/aaac/scripts/remediation/remediation-runner.mjs --run-id ${args.runId} --campaign-id ${args.campaignId} --ack-yield ${yieldPayload.type}`,
82
+ }
83
+ : {
84
+ type: "run_until_yield",
85
+ command: commands.runner_until_yield,
86
+ },
87
+ at: isoNow(),
88
+ };
89
+
90
+ const runArtifact = path.join(runDir(args.runId), "artifacts", "autonomous_bootstrap.json");
91
+ writeJson(runArtifact, bootstrap);
92
+
93
+ const campaignArtifact = path.join(
94
+ REPO_ROOT,
95
+ ".cursor/aaac/state/campaigns",
96
+ args.campaignId,
97
+ "autonomous-bootstrap.json",
98
+ );
99
+ writeJson(campaignArtifact, bootstrap);
100
+
101
+ appendJournal(args.campaignId, `- **Autonomous mode** — ${campaign.config.autonomous_reason}; bootstrap written`);
102
+
103
+ console.log(JSON.stringify(bootstrap));
104
+
105
+ function appendJournal(campaignId, line) {
106
+ const journalPath = path.join(
107
+ REPO_ROOT,
108
+ ".cursor/aaac/state/campaigns",
109
+ campaignId,
110
+ "journal.md",
111
+ );
112
+ fs.appendFileSync(journalPath, `\n${line}\n`);
113
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Capture campaign verify baseline at start (before any waves).
4
+ *
5
+ * Usage:
6
+ * node capture-verify-baseline.mjs --campaign-id <id> [--run-id <run_id>]
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { REPO_ROOT, isoNow, readJson, writeJson, runDir } from "../run-engine/lib.mjs";
11
+ import {
12
+ runVerifySteps,
13
+ writeVerifyLogs,
14
+ summarizeMetrics,
15
+ } from "./lib/verify-metrics.mjs";
16
+
17
+ const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
18
+
19
+ function parseArgs(argv) {
20
+ const out = { campaignId: null, runId: null };
21
+ for (let i = 0; i < argv.length; i++) {
22
+ if (argv[i] === "--campaign-id") out.campaignId = argv[++i];
23
+ else if (argv[i] === "--run-id") out.runId = argv[++i];
24
+ }
25
+ return out;
26
+ }
27
+
28
+ const args = parseArgs(process.argv.slice(2));
29
+ if (!args.campaignId) {
30
+ console.error("capture-verify-baseline: --campaign-id required");
31
+ process.exit(2);
32
+ }
33
+
34
+ const campaignDir = path.join(CAMPAIGNS_ROOT, args.campaignId);
35
+ const baselineDir = path.join(campaignDir, "baseline");
36
+ fs.mkdirSync(baselineDir, { recursive: true });
37
+
38
+ const report = await runVerifySteps("wave");
39
+ writeVerifyLogs(report, baselineDir, "baseline");
40
+
41
+ const snapshot = {
42
+ captured_at: isoNow(),
43
+ kind: "campaign_verify_baseline",
44
+ summary: summarizeMetrics(report),
45
+ metrics: report.metrics,
46
+ report_path: path.join(baselineDir, "verify-baseline.json"),
47
+ };
48
+
49
+ writeJson(snapshot.report_path, report);
50
+ writeJson(path.join(campaignDir, "verify-baseline.json"), snapshot);
51
+
52
+ const campaign = readJson(path.join(campaignDir, "campaign.json"), {});
53
+ if (campaign) {
54
+ campaign.verify_baseline = snapshot.summary;
55
+ campaign.updated_at = isoNow();
56
+ writeJson(path.join(campaignDir, "campaign.json"), campaign);
57
+ }
58
+
59
+ if (args.runId) {
60
+ writeJson(path.join(runDir(args.runId), "artifacts", "verify_baseline.json"), snapshot);
61
+ }
62
+
63
+ const journal = `\n- **Verify baseline captured** — total_errors=${snapshot.summary.total_errors} (typecheck ${snapshot.summary.layers.typecheck.error_count}, vitest ${snapshot.summary.layers.vitest.error_count})\n`;
64
+ fs.appendFileSync(path.join(campaignDir, "journal.md"), journal);
65
+
66
+ console.log(JSON.stringify({ ok: true, baseline: snapshot }));
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Snapshot verify metrics immediately before a cleanup wave executes.
4
+ *
5
+ * Usage:
6
+ * node capture-wave-snapshot.mjs --campaign-id <id> --iteration <n> --wave-index <w>
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { spawnSync } from "child_process";
11
+ import { fileURLToPath } from "url";
12
+ import { REPO_ROOT, isoNow, writeJson } from "../run-engine/lib.mjs";
13
+ import { summarizeMetrics } from "./lib/verify-metrics.mjs";
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
17
+ const VERIFY_SCRIPT = path.join(__dirname, "verify-remediation-iteration.mjs");
18
+
19
+ function parseArgs(argv) {
20
+ const out = { campaignId: null, iteration: 0, waveIndex: 0 };
21
+ for (let i = 0; i < argv.length; i++) {
22
+ if (argv[i] === "--campaign-id") out.campaignId = argv[++i];
23
+ else if (argv[i] === "--iteration") out.iteration = Number(argv[++i]);
24
+ else if (argv[i] === "--wave-index") out.waveIndex = Number(argv[++i]);
25
+ }
26
+ return out;
27
+ }
28
+
29
+ const args = parseArgs(process.argv.slice(2));
30
+ if (!args.campaignId) {
31
+ console.error("capture-wave-snapshot: --campaign-id required");
32
+ process.exit(2);
33
+ }
34
+
35
+ spawnSync(
36
+ process.execPath,
37
+ [
38
+ VERIFY_SCRIPT,
39
+ "--campaign-id",
40
+ args.campaignId,
41
+ "--iteration",
42
+ String(args.iteration),
43
+ "--mode",
44
+ "wave",
45
+ "--label",
46
+ `pre-wave-${args.waveIndex}`,
47
+ ],
48
+ { encoding: "utf8" },
49
+ );
50
+
51
+ const iterDir = path.join(
52
+ CAMPAIGNS_ROOT,
53
+ args.campaignId,
54
+ "iterations",
55
+ String(args.iteration),
56
+ );
57
+ const prePath = path.join(iterDir, `wave-${args.waveIndex}-pre.json`);
58
+ const verifyPath = path.join(iterDir, "verify-wave.json");
59
+
60
+ let report = null;
61
+ try {
62
+ report = JSON.parse(fs.readFileSync(verifyPath, "utf8"));
63
+ } catch {
64
+ console.error("capture-wave-snapshot: missing verify-wave.json");
65
+ process.exit(2);
66
+ }
67
+
68
+ const snapshot = {
69
+ captured_at: isoNow(),
70
+ kind: "pre_wave",
71
+ wave_index: args.waveIndex,
72
+ iteration: args.iteration,
73
+ summary: summarizeMetrics(report),
74
+ metrics: report.metrics,
75
+ };
76
+ writeJson(prePath, snapshot);
77
+ fs.copyFileSync(verifyPath, path.join(iterDir, `wave-${args.waveIndex}-pre-verify.json`));
78
+
79
+ console.log(JSON.stringify({ ok: true, snapshot_path: prePath, summary: snapshot.summary }));
@@ -0,0 +1,26 @@
1
+ {
2
+ "campaign_id": "campaign_EXAMPLE",
3
+ "iteration": 0,
4
+ "collected_at": "ISO8601",
5
+ "agents": [
6
+ {
7
+ "agent_id": "remediation-check-app-inventory",
8
+ "command_mirror": "check-app",
9
+ "answer": "partial",
10
+ "confidence": "high",
11
+ "false_positives": [
12
+ {
13
+ "path": "src/hooks/useCryptoPriceWorker.ts",
14
+ "export_name": null,
15
+ "reason": "worker_hook_runtime",
16
+ "evidence": "src/hooks/useCryptoPriceWorker.ts:1"
17
+ }
18
+ ],
19
+ "protected_paths": ["src/hooks/useCryptoPriceWorker.ts"],
20
+ "do_not_delete": [],
21
+ "safe_to_fix": [],
22
+ "findings": [],
23
+ "gaps": []
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Classify Fallow scan findings and persist actionable vs false-positive SSOT.
4
+ *
5
+ * Usage:
6
+ * node classify-fallow-issues.mjs --campaign-id <id> --iteration <n> [--save-actionable-baseline]
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { REPO_ROOT, isoNow, readJson, writeJson } from "../run-engine/lib.mjs";
11
+ import { classifyFallowScan, loadFpRules } from "./lib/fallow-classifier.mjs";
12
+
13
+ const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
14
+
15
+ function parseArgs(argv) {
16
+ const out = { campaignId: null, iteration: 0, saveActionableBaseline: false };
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a === "--campaign-id") out.campaignId = argv[++i];
20
+ else if (a === "--iteration") out.iteration = Number(argv[++i]);
21
+ else if (a === "--save-actionable-baseline") out.saveActionableBaseline = true;
22
+ }
23
+ return out;
24
+ }
25
+
26
+ const args = parseArgs(process.argv.slice(2));
27
+ if (!args.campaignId) {
28
+ console.error("classify-fallow-issues: --campaign-id required");
29
+ process.exit(2);
30
+ }
31
+
32
+ const campaignDir = path.join(CAMPAIGNS_ROOT, args.campaignId);
33
+ const iterDir = path.join(campaignDir, "iterations", String(args.iteration));
34
+ const scanPath = path.join(iterDir, "fallow-scan.json");
35
+ const scan = readJson(scanPath, null);
36
+
37
+ if (!scan) {
38
+ console.error(`classify-fallow-issues: missing ${scanPath}`);
39
+ process.exit(2);
40
+ }
41
+
42
+ const classification = classifyFallowScan({
43
+ scan,
44
+ campaignDir,
45
+ rules: loadFpRules(),
46
+ });
47
+
48
+ const outPath = path.join(iterDir, "fallow-classification.json");
49
+ writeJson(outPath, classification);
50
+
51
+ const actionableBaselinePath = path.join(campaignDir, "fallow-start-actionable-baseline.json");
52
+ if (args.saveActionableBaseline && !fs.existsSync(actionableBaselinePath)) {
53
+ writeJson(actionableBaselinePath, {
54
+ actionable_total: classification.summary.actionable_total,
55
+ raw_total: classification.summary.raw_total,
56
+ false_positive_total: classification.summary.false_positive_total,
57
+ recorded_at: isoNow(),
58
+ immutable: true,
59
+ source: "classify-fallow-issues",
60
+ iteration: args.iteration,
61
+ });
62
+ }
63
+
64
+ const campaign = readJson(path.join(campaignDir, "campaign.json"), null);
65
+ if (campaign) {
66
+ campaign.current = campaign.current ?? {};
67
+ campaign.current.fallow_raw_total = classification.summary.raw_total;
68
+ campaign.current.fallow_actionable_total = classification.summary.actionable_total;
69
+ campaign.current.fallow_false_positive_total = classification.summary.false_positive_total;
70
+ campaign.updated_at = isoNow();
71
+ writeJson(path.join(campaignDir, "campaign.json"), campaign);
72
+ }
73
+
74
+ const journal = `\n- **Fallow classified** iter ${args.iteration}: raw=${classification.summary.raw_total}, actionable=${classification.summary.actionable_total}, false_positive=${classification.summary.false_positive_total}, review=${classification.summary.review_total}\n`;
75
+ fs.appendFileSync(path.join(campaignDir, "journal.md"), journal);
76
+
77
+ console.log(JSON.stringify({ ok: true, classification: classification.summary, path: outPath }));
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Classify verify-remediation report failures → fix-module / fix-bug handoffs.
4
+ *
5
+ * Usage:
6
+ * node classify-verify-failure.mjs --report <path> --campaign-id <id> [--iteration n] [--wave-index n] [--attempt n]
7
+ */
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { REPO_ROOT, readJson } from "../run-engine/lib.mjs";
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const RULES_PATH = path.join(__dirname, "dispatch-rules.json");
15
+
16
+ function parseArgs(argv) {
17
+ const out = {
18
+ reportPath: null,
19
+ campaignId: "",
20
+ iteration: 0,
21
+ waveIndex: null,
22
+ attempt: 1,
23
+ };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a === "--report") out.reportPath = argv[++i];
27
+ else if (a === "--campaign-id") out.campaignId = argv[++i];
28
+ else if (a === "--iteration") out.iteration = Number(argv[++i]);
29
+ else if (a === "--wave-index") out.waveIndex = Number(argv[++i]);
30
+ else if (a === "--attempt") out.attempt = Number(argv[++i]);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ function loadRules() {
36
+ return readJson(RULES_PATH, { layers: {}, evidence_priority: [] });
37
+ }
38
+
39
+ function readLogFile(logPath) {
40
+ if (!logPath || !fs.existsSync(logPath)) return "";
41
+ try {
42
+ return fs.readFileSync(logPath, "utf8");
43
+ } catch {
44
+ return "";
45
+ }
46
+ }
47
+
48
+ function evidenceText(step) {
49
+ if (!step) return "";
50
+ const fromLog = readLogFile(step.log_path);
51
+ if (fromLog) return fromLog;
52
+ return [
53
+ step.stdout_full,
54
+ step.stderr_full,
55
+ step.stderr_tail,
56
+ step.stdout_tail,
57
+ step.detail,
58
+ ]
59
+ .filter(Boolean)
60
+ .join("\n");
61
+ }
62
+
63
+ function extractFilePaths(text) {
64
+ const paths = new Set();
65
+ const patterns = [
66
+ /(?:frontend|backend)\/[^\s:'"]+\.(?:tsx?|jsx?|go)/g,
67
+ /src\/[^\s:'"]+\.(?:tsx?|jsx?)/g,
68
+ /internal\/[^\s:'"]+\.go/g,
69
+ /[^\s('"]+\.(?:test|spec)\.(?:tsx?|jsx?)/g,
70
+ ];
71
+ for (const re of patterns) {
72
+ for (const m of text.matchAll(re)) {
73
+ paths.add(m[0].replace(/^[('"]+/, ""));
74
+ }
75
+ }
76
+ return [...paths];
77
+ }
78
+
79
+ function inferDomain(layer, evidence, ruleDomain) {
80
+ const paths = extractFilePaths(evidence);
81
+ if (paths.some((p) => p.startsWith("backend/") || p.includes("/internal/") || p.endsWith(".go"))) {
82
+ return "backend";
83
+ }
84
+ if (paths.some((p) => p.startsWith("frontend/") || p.startsWith("src/"))) {
85
+ return "frontend";
86
+ }
87
+ return ruleDomain ?? "frontend";
88
+ }
89
+
90
+ function pickCommand(layer, rule, evidence) {
91
+ if (layer === "playwright" && /frontend not reachable|launch via/i.test(evidence)) {
92
+ return { command: null, level: "infrastructure", handoff: rule.layers?.playwright_infra?.handoff ?? "/launch-se100" };
93
+ }
94
+ if (layer === "playwright" && /does not provide an export|Failed to fetch dynamically imported module|Cannot find module/i.test(evidence)) {
95
+ return { command: rule.module_import_command ?? "fix-module", level: "code" };
96
+ }
97
+ if (layer === "vitest" && /\.(test|spec)\.(tsx?|jsx?)/.test(evidence) && !/src\/(?!.*\.test\.)/.test(evidence.split("\n")[0] ?? "")) {
98
+ return { command: rule.test_file_command ?? rule.command, level: "test" };
99
+ }
100
+ return { command: rule.command, level: rule.level ?? "code" };
101
+ }
102
+
103
+ function fillTemplate(template, vars) {
104
+ return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? ""));
105
+ }
106
+
107
+ function classifyReport(report, ctx) {
108
+ const rules = loadRules();
109
+ const failedLayers = rules.evidence_priority.filter((layer) => report[layer]?.status === "fail");
110
+ const handoffs = [];
111
+
112
+ for (const layer of failedLayers) {
113
+ const rule = rules.layers[layer];
114
+ if (!rule) continue;
115
+ const evidence = evidenceText(report[layer]);
116
+ const picked = pickCommand(layer, rule, evidence);
117
+ if (picked.level === "infrastructure") {
118
+ handoffs.push({
119
+ layer,
120
+ level: "infrastructure",
121
+ command: null,
122
+ domain: null,
123
+ intent: null,
124
+ handoff: picked.handoff,
125
+ evidence: evidence.slice(0, 16000),
126
+ evidence_truncated: evidence.length > 16000,
127
+ log_path: report[layer]?.log_path ?? null,
128
+ file_paths: extractFilePaths(evidence),
129
+ });
130
+ continue;
131
+ }
132
+ const domain = inferDomain(layer, evidence, rule.domain);
133
+ const intent = fillTemplate(rule.intent_template, {
134
+ campaign_id: ctx.campaignId,
135
+ iteration: ctx.iteration,
136
+ wave_index: ctx.waveIndex ?? "n/a",
137
+ attempt: ctx.attempt,
138
+ evidence: evidence.slice(0, 16000),
139
+ });
140
+ handoffs.push({
141
+ layer,
142
+ level: picked.level,
143
+ command: picked.command,
144
+ domain,
145
+ intent,
146
+ slash_command: `/${picked.command} ${domain} "${intent.replace(/"/g, '\\"').slice(0, 500)}…"`,
147
+ evidence: evidence.slice(0, 16000),
148
+ evidence_truncated: evidence.length > 16000,
149
+ log_path: report[layer]?.log_path ?? null,
150
+ file_paths: extractFilePaths(evidence),
151
+ });
152
+ }
153
+
154
+ return {
155
+ status: handoffs.length ? "fail" : "pass",
156
+ failed_layers: failedLayers,
157
+ handoffs,
158
+ primary: handoffs.find((h) => h.command) ?? handoffs[0] ?? null,
159
+ };
160
+ }
161
+
162
+ const args = parseArgs(process.argv.slice(2));
163
+ if (!args.reportPath) {
164
+ console.error("classify-verify-failure: --report required");
165
+ process.exit(2);
166
+ }
167
+
168
+ const report = readJson(args.reportPath, null);
169
+ if (!report) {
170
+ console.error(`classify-verify-failure: cannot read report ${args.reportPath}`);
171
+ process.exit(2);
172
+ }
173
+
174
+ const classification = classifyReport(report, args);
175
+ console.log(JSON.stringify({ ok: true, classification }));
176
+ process.exit(classification.status === "pass" ? 0 : 1);