@ludecker/aaac 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -12
- package/package.json +9 -9
- package/src/cli.mjs +19 -7
- package/src/generators/generate-commands.mjs +25 -1
- package/src/generators/generate-graph.mjs +9 -1
- package/src/lib/install.mjs +13 -1
- package/src/lib/sweep-project-docs.mjs +348 -0
- package/src/run-engine/advance-phase.mjs +23 -0
- package/src/run-engine/debug-run.mjs +0 -0
- package/src/run-engine/gate-write.mjs +13 -0
- package/src/run-engine/lib.mjs +153 -5
- package/src/run-engine/log-dump.mjs +0 -0
- package/src/run-engine/log-trace.mjs +0 -0
- package/templates/cursor/aaac/enforcement.json +96 -5
- package/templates/cursor/aaac/graph.project.yaml +44 -5
- package/templates/cursor/aaac/lifecycle/lifecycle.json +26 -0
- package/templates/cursor/aaac/lifecycle/phases.json +9 -1
- package/templates/cursor/aaac/ontology.json +1 -0
- package/templates/cursor/aaac/project.config.json +36 -0
- package/templates/cursor/aaac/scripts/remediation/auto-check-swarm-synthesis.mjs +75 -0
- package/templates/cursor/aaac/scripts/remediation/auto-dispatch-queue-from-health.mjs +78 -0
- package/templates/cursor/aaac/scripts/remediation/bootstrap-autonomous.mjs +113 -0
- package/templates/cursor/aaac/scripts/remediation/capture-verify-baseline.mjs +66 -0
- package/templates/cursor/aaac/scripts/remediation/capture-wave-snapshot.mjs +79 -0
- package/templates/cursor/aaac/scripts/remediation/check-swarm-raw.template.json +26 -0
- package/templates/cursor/aaac/scripts/remediation/classify-fallow-issues.mjs +77 -0
- package/templates/cursor/aaac/scripts/remediation/classify-verify-failure.mjs +176 -0
- package/templates/cursor/aaac/scripts/remediation/compute-satisfaction.mjs +344 -0
- package/templates/cursor/aaac/scripts/remediation/debt-sweep-gate.mjs +202 -0
- package/templates/cursor/aaac/scripts/remediation/dispatch-rules.json +44 -0
- package/templates/cursor/aaac/scripts/remediation/fallow-fp-rules.json +87 -0
- package/templates/cursor/aaac/scripts/remediation/fallow-scan.mjs +219 -0
- package/templates/cursor/aaac/scripts/remediation/handle-yield.mjs +240 -0
- package/templates/cursor/aaac/scripts/remediation/init-campaign.mjs +211 -0
- package/templates/cursor/aaac/scripts/remediation/lib/autonomous-mode.mjs +63 -0
- package/templates/cursor/aaac/scripts/remediation/lib/campaign-focus.mjs +87 -0
- package/templates/cursor/aaac/scripts/remediation/lib/fallow-classifier.mjs +190 -0
- package/templates/cursor/aaac/scripts/remediation/lib/fallow-health-targets.mjs +56 -0
- package/templates/cursor/aaac/scripts/remediation/lib/fallow-metrics.mjs +119 -0
- package/templates/cursor/aaac/scripts/remediation/lib/invoke-cursor-agent.mjs +51 -0
- package/templates/cursor/aaac/scripts/remediation/lib/reconcile-run-manifest.mjs +41 -0
- package/templates/cursor/aaac/scripts/remediation/lib/regression-analysis.mjs +55 -0
- package/templates/cursor/aaac/scripts/remediation/lib/remediation-config.mjs +69 -0
- package/templates/cursor/aaac/scripts/remediation/lib/remediation-progress.mjs +58 -0
- package/templates/cursor/aaac/scripts/remediation/lib/remediation-watch-loop.mjs +168 -0
- package/templates/cursor/aaac/scripts/remediation/lib/runner-exec.mjs +156 -0
- package/templates/cursor/aaac/scripts/remediation/lib/runner-state.mjs +145 -0
- package/templates/cursor/aaac/scripts/remediation/lib/verify-metrics.mjs +205 -0
- package/templates/cursor/aaac/scripts/remediation/merge-check-swarm.mjs +257 -0
- package/templates/cursor/aaac/scripts/remediation/plan-waves-from-queue.mjs +85 -0
- package/templates/cursor/aaac/scripts/remediation/prepare-check-context.mjs +148 -0
- package/templates/cursor/aaac/scripts/remediation/record-fallow-fp.mjs +107 -0
- package/templates/cursor/aaac/scripts/remediation/record-iteration-step.mjs +56 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-cli.mjs +157 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-cursor-watch.sh +10 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-runner-daemon.sh +13 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-runner.mjs +748 -0
- package/templates/cursor/aaac/scripts/remediation/remediation-yield-watcher.mjs +40 -0
- package/templates/cursor/aaac/scripts/remediation/remediator-gate.mjs +405 -0
- package/templates/cursor/aaac/scripts/remediation/repair-fallow-start-baseline.mjs +118 -0
- package/templates/cursor/aaac/scripts/remediation/runner-health-check.mjs +164 -0
- package/templates/cursor/aaac/scripts/remediation/satisfaction-loop-gate.mjs +286 -0
- package/templates/cursor/aaac/scripts/remediation/validate-campaign-complete.mjs +191 -0
- package/templates/cursor/aaac/scripts/remediation/verify-remediation-iteration.mjs +112 -0
- package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +23 -0
- package/templates/cursor/aaac/scripts/run-engine/debug-run.mjs +0 -0
- package/templates/cursor/aaac/scripts/run-engine/gate-write.mjs +13 -0
- package/templates/cursor/aaac/scripts/run-engine/lib.mjs +153 -5
- package/templates/cursor/aaac/scripts/run-engine/log-dump.mjs +0 -0
- package/templates/cursor/aaac/scripts/run-engine/log-trace.mjs +0 -0
- package/templates/cursor/agents/doc-conformance.md +25 -0
- package/templates/cursor/agents/implementation-review.md +21 -0
- package/templates/cursor/agents/remediation-check-app-inventory.md +32 -0
- package/templates/cursor/agents/remediation-check-app-ssot.md +24 -0
- package/templates/cursor/agents/remediation-check-app-trace.md +29 -0
- package/templates/cursor/agents/remediation-check-architecture-boundaries.md +21 -0
- package/templates/cursor/agents/remediation-check-architecture-decomposition.md +25 -0
- package/templates/cursor/agents/remediation-check-architecture-deps.md +23 -0
- package/templates/cursor/agents/remediation-check-risk.md +37 -0
- package/templates/cursor/agents/remediation-e2e-gate.md +30 -0
- package/templates/cursor/agents/remediation-remediator.md +69 -0
- package/templates/cursor/agents/test-author.md +27 -0
- package/templates/cursor/commands/remediate-app.md +212 -0
- package/templates/cursor/hooks/aaac-before-submit.sh +0 -0
- package/templates/cursor/hooks/aaac-pre-tool.sh +0 -0
- package/templates/cursor/hooks/aaac-stop.sh +0 -0
- package/templates/cursor/hooks/aaac-subagent-start.sh +0 -0
- package/templates/cursor/rules/aaac-enforcement.mdc +10 -3
- package/templates/cursor/skills/shared/execution/SKILL.md +7 -3
- package/templates/cursor/skills/shared/governance/implementation/SKILL.md +396 -28
- package/templates/cursor/skills/shared/implementation-review/SKILL.md +49 -0
- package/templates/cursor/skills/shared/planning/SKILL.md +5 -0
- package/templates/cursor/skills/shared/remediation/SKILL.md +51 -0
- package/templates/cursor/skills/shared/remediation/babysit/SKILL.md +223 -0
- package/templates/cursor/skills/shared/remediation/check-swarm/SKILL.md +114 -0
- package/templates/cursor/skills/shared/remediation/orchestrator/SKILL.md +275 -0
- package/templates/cursor/skills/shared/remediation/orchestrator/contract.yaml +116 -0
- package/templates/cursor/skills/shared/test-authoring/SKILL.md +58 -0
- package/templates/cursor/skills/shared/testing/SKILL.md +6 -0
- package/templates/cursor/skills/shared/verbs/create/orchestrator/SKILL.md +5 -3
- package/templates/cursor/skills/shared/verbs/fix/orchestrator/SKILL.md +5 -3
- package/templates/cursor/skills/shared/verbs/update/orchestrator/SKILL.md +5 -3
- package/templates/cursor/skills/shared/verification/SKILL.md +5 -3
- package/templates/docs/agentic_architecture.md +169 -97
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Multi-layer verification gate for remediation campaigns.
|
|
4
|
+
*
|
|
5
|
+
* Modes:
|
|
6
|
+
* wave — fast gate after each fix wave (typecheck, vitest, go test)
|
|
7
|
+
* iteration — full gate (+ build + Playwright)
|
|
8
|
+
* debt — strict full gate (same layers as iteration; used by debt_sweep)
|
|
9
|
+
* strict — alias for debt
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node verify-remediation-iteration.mjs --campaign-id <id> --iteration <n> \
|
|
13
|
+
* --mode wave|iteration|debt [--run-id <run_id>] [--label <suffix>]
|
|
14
|
+
*/
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { spawnSync } from "child_process";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
import { REPO_ROOT, isoNow, writeJson, runDir } from "../run-engine/lib.mjs";
|
|
20
|
+
import {
|
|
21
|
+
runVerifySteps,
|
|
22
|
+
writeVerifyLogs,
|
|
23
|
+
} from "./lib/verify-metrics.mjs";
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const CAMPAIGNS_ROOT = path.join(REPO_ROOT, ".cursor/aaac/state/campaigns");
|
|
27
|
+
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
const out = { campaignId: null, iteration: 0, mode: "iteration", runId: null, label: null };
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
const a = argv[i];
|
|
32
|
+
if (a === "--campaign-id") out.campaignId = argv[++i];
|
|
33
|
+
else if (a === "--iteration") out.iteration = Number(argv[++i]);
|
|
34
|
+
else if (a === "--mode") out.mode = argv[++i];
|
|
35
|
+
else if (a === "--run-id") out.runId = argv[++i];
|
|
36
|
+
else if (a === "--label") out.label = argv[++i];
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function appendJournal(campaignId, text) {
|
|
42
|
+
fs.appendFileSync(path.join(CAMPAIGNS_ROOT, campaignId, "journal.md"), text);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const args = parseArgs(process.argv.slice(2));
|
|
46
|
+
if (!args.campaignId) {
|
|
47
|
+
console.error("verify-remediation-iteration: --campaign-id required");
|
|
48
|
+
process.exit(2);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const verifyMode = args.mode === "strict" ? "debt" : args.mode;
|
|
52
|
+
const iterDir = path.join(CAMPAIGNS_ROOT, args.campaignId, "iterations", String(args.iteration));
|
|
53
|
+
const logDir = path.join(iterDir, "verify-logs");
|
|
54
|
+
fs.mkdirSync(iterDir, { recursive: true });
|
|
55
|
+
|
|
56
|
+
const stepMode = verifyMode === "wave" ? "wave" : "debt";
|
|
57
|
+
const report = await runVerifySteps(stepMode);
|
|
58
|
+
report.iteration = args.iteration;
|
|
59
|
+
report.campaign_id = args.campaignId;
|
|
60
|
+
report.label = args.label;
|
|
61
|
+
|
|
62
|
+
writeVerifyLogs(report, logDir, args.label ?? verifyMode);
|
|
63
|
+
|
|
64
|
+
const outName =
|
|
65
|
+
args.label != null
|
|
66
|
+
? `verify-${args.label}.json`
|
|
67
|
+
: verifyMode === "wave"
|
|
68
|
+
? "verify-wave.json"
|
|
69
|
+
: verifyMode === "debt"
|
|
70
|
+
? "verify-debt.json"
|
|
71
|
+
: "verify-iteration.json";
|
|
72
|
+
const outPath = path.join(iterDir, outName);
|
|
73
|
+
writeJson(outPath, report);
|
|
74
|
+
|
|
75
|
+
if (report.status === "fail") {
|
|
76
|
+
const classify = spawnSync(
|
|
77
|
+
process.execPath,
|
|
78
|
+
[
|
|
79
|
+
path.join(__dirname, "classify-verify-failure.mjs"),
|
|
80
|
+
"--report",
|
|
81
|
+
outPath,
|
|
82
|
+
"--campaign-id",
|
|
83
|
+
args.campaignId,
|
|
84
|
+
"--iteration",
|
|
85
|
+
String(args.iteration),
|
|
86
|
+
],
|
|
87
|
+
{ encoding: "utf8" },
|
|
88
|
+
);
|
|
89
|
+
try {
|
|
90
|
+
const line = classify.stdout.trim().split("\n").pop();
|
|
91
|
+
report.failure_classification = JSON.parse(line)?.classification ?? null;
|
|
92
|
+
writeJson(outPath, report);
|
|
93
|
+
} catch {
|
|
94
|
+
report.failure_classification = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
appendJournal(
|
|
99
|
+
args.campaignId,
|
|
100
|
+
`- Verify **${verifyMode}**${args.label ? ` (${args.label})` : ""} iter ${args.iteration}: **${report.status.toUpperCase()}** — total_errors=${report.metrics?.total_errors ?? 0}\n`,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (args.runId) {
|
|
104
|
+
const artifactName = args.label
|
|
105
|
+
? `verify_${args.label}_iter_${args.iteration}.json`
|
|
106
|
+
: `verify_${verifyMode}_iter_${args.iteration}.json`;
|
|
107
|
+
writeJson(path.join(runDir(args.runId), "artifacts", artifactName), report);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const strictPass = report.status === "pass" && (report.metrics?.total_errors ?? 0) === 0;
|
|
111
|
+
console.log(JSON.stringify({ ok: strictPass, report, strict_pass: strictPass }));
|
|
112
|
+
process.exit(strictPass ? 0 : 1);
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isEditPhase,
|
|
18
18
|
isGatePhase,
|
|
19
19
|
resolveSwarmMinimum,
|
|
20
|
+
validatePhaseArtifactContent,
|
|
20
21
|
writeJson,
|
|
21
22
|
saveActiveRun,
|
|
22
23
|
} from "./lib.mjs";
|
|
@@ -132,6 +133,28 @@ for (const rel of requiredArtifacts) {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
if (!force) {
|
|
137
|
+
const contentGate = validatePhaseArtifactContent(
|
|
138
|
+
runId,
|
|
139
|
+
completedPhase,
|
|
140
|
+
manifest,
|
|
141
|
+
enforcement,
|
|
142
|
+
);
|
|
143
|
+
if (!contentGate.ok) {
|
|
144
|
+
recordLog(manifest, {
|
|
145
|
+
event: "gate_fail",
|
|
146
|
+
phase: completedPhase,
|
|
147
|
+
phase_kind: manifest.phase_kind,
|
|
148
|
+
detail: contentGate.reason,
|
|
149
|
+
level: "warn",
|
|
150
|
+
});
|
|
151
|
+
manifest.updated_at = isoNow();
|
|
152
|
+
writeJson(manifestPath, manifest);
|
|
153
|
+
console.error(contentGate.reason);
|
|
154
|
+
process.exit(2);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
135
158
|
const now = isoNow();
|
|
136
159
|
const completedIsGate = isGatePhase(completedPhase, registry);
|
|
137
160
|
|
|
File without changes
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
loadEnforcement,
|
|
8
8
|
isEditPhase,
|
|
9
9
|
isArtifactPath,
|
|
10
|
+
isPathAllowedForPhase,
|
|
10
11
|
conversationIdFromHook,
|
|
11
12
|
runDir,
|
|
12
13
|
writeJson,
|
|
@@ -86,6 +87,18 @@ process.stdin.on("end", () => {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
if (isEditPhase(manifest.phase, enforcement)) {
|
|
90
|
+
if (filePath && !isPathAllowedForPhase(filePath, manifest.phase, enforcement)) {
|
|
91
|
+
persistEditEvent(
|
|
92
|
+
manifest,
|
|
93
|
+
active.run_id,
|
|
94
|
+
"edit_denied",
|
|
95
|
+
`${toolName} path not allowed in phase ${manifest.phase}: ${filePath}`,
|
|
96
|
+
);
|
|
97
|
+
deny(
|
|
98
|
+
`AAAC: ${manifest.phase} phase cannot edit this path. Run: ${active.run_id}`,
|
|
99
|
+
`Phase "${manifest.phase}" scope violation${filePath ? `: ${filePath}` : ""}. Use test_execute for tests; execute for prod code only.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
89
102
|
persistEditEvent(manifest, active.run_id, "edit_allowed", `${toolName} in phase ${manifest.phase}`);
|
|
90
103
|
allow();
|
|
91
104
|
}
|
|
@@ -123,6 +123,28 @@ export function isEditPhase(phase, enforcement) {
|
|
|
123
123
|
return enforcement.edit_phases.includes(phase);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/** Test/spec file paths — used for writer vs tester phase scoping. */
|
|
127
|
+
export function isTestPath(filePath) {
|
|
128
|
+
if (!filePath) return false;
|
|
129
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
130
|
+
return (
|
|
131
|
+
/\.(test|spec)\.(mjs|cjs|js|ts|tsx)$/.test(normalized) ||
|
|
132
|
+
/(?:^|\/)__tests__(?:\/|$)/.test(normalized) ||
|
|
133
|
+
/(?:^|\/)tests\/(?:unit|integration|e2e|fixtures)\//.test(normalized)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Phase-scoped edit rules from enforcement.phase_edit_scopes (v3+). */
|
|
138
|
+
export function isPathAllowedForPhase(filePath, phase, enforcement) {
|
|
139
|
+
if (!filePath) return true;
|
|
140
|
+
const scopes = enforcement.phase_edit_scopes?.[phase];
|
|
141
|
+
if (!scopes) return true;
|
|
142
|
+
const isTest = isTestPath(filePath);
|
|
143
|
+
if (scopes.deny_test_paths && isTest) return false;
|
|
144
|
+
if (scopes.test_paths_only && !isTest) return false;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
126
148
|
export function isArtifactPath(filePath, enforcement) {
|
|
127
149
|
const normalized = filePath.replace(/\\/g, "/");
|
|
128
150
|
const prefixes = [
|
|
@@ -138,11 +160,22 @@ export function phaseKind(phase, registry) {
|
|
|
138
160
|
|
|
139
161
|
/** Swarm minimum for completed phase — check verb uses check_swarm on discover. */
|
|
140
162
|
export function resolveSwarmMinimum(completedPhase, manifest, enforcement) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
const mutating = enforcement.mutating_verbs ?? ["create", "update", "fix"];
|
|
164
|
+
const isMutating =
|
|
165
|
+
mutating.includes(manifest.verb) ||
|
|
166
|
+
enforcement.fix_commands?.includes(manifest.command);
|
|
167
|
+
|
|
168
|
+
if (completedPhase === "verify" && isMutating) {
|
|
169
|
+
return (
|
|
170
|
+
enforcement.swarm_min_agents?.verify ??
|
|
171
|
+
enforcement.swarm_min_agents?.verify_fix
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (completedPhase === "test_execute" && isMutating) {
|
|
175
|
+
return enforcement.swarm_min_agents?.test_execute;
|
|
176
|
+
}
|
|
177
|
+
if (completedPhase === "review_swarm" && isMutating) {
|
|
178
|
+
return enforcement.swarm_min_agents?.review_swarm;
|
|
146
179
|
}
|
|
147
180
|
if (completedPhase === "discover" && manifest.verb === "check") {
|
|
148
181
|
return (
|
|
@@ -189,3 +222,118 @@ export function clearActiveRun(conversationId) {
|
|
|
189
222
|
// already cleared
|
|
190
223
|
}
|
|
191
224
|
}
|
|
225
|
+
|
|
226
|
+
export function isMutatingVerb(manifest, enforcement) {
|
|
227
|
+
const mutating = enforcement.mutating_verbs ?? ["create", "update", "fix"];
|
|
228
|
+
return (
|
|
229
|
+
mutating.includes(manifest.verb) ||
|
|
230
|
+
(enforcement.fix_commands ?? []).includes(manifest.command)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** List items under a YAML field (lines starting with `-` before next top-level key). */
|
|
235
|
+
export function readYamlListField(content, fieldName) {
|
|
236
|
+
if (!content) return [];
|
|
237
|
+
const lines = content.split("\n");
|
|
238
|
+
const start = lines.findIndex((line) => line.startsWith(`${fieldName}:`));
|
|
239
|
+
if (start < 0) return [];
|
|
240
|
+
|
|
241
|
+
const inline = lines[start].slice(`${fieldName}:`.length).trim();
|
|
242
|
+
if (inline === "[]") return [];
|
|
243
|
+
if (inline && !inline.startsWith("-")) return [inline];
|
|
244
|
+
|
|
245
|
+
const items = [];
|
|
246
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
247
|
+
const line = lines[i];
|
|
248
|
+
if (/^\S/.test(line) && line.trim()) break;
|
|
249
|
+
const itemMatch = line.match(/^\s+-\s+(.*)$/);
|
|
250
|
+
if (itemMatch) items.push(itemMatch[1].trim());
|
|
251
|
+
}
|
|
252
|
+
return items;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function readYamlScalarField(content, fieldName) {
|
|
256
|
+
if (!content) return null;
|
|
257
|
+
const match = content.match(new RegExp(`^${fieldName}:\\s*(.+)$`, "m"));
|
|
258
|
+
if (!match) return null;
|
|
259
|
+
return match[1].trim().replace(/^["']|["']$/g, "");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function hasYamlField(content, fieldName) {
|
|
263
|
+
if (!content) return false;
|
|
264
|
+
return new RegExp(`^${fieldName}:`, "m").test(content);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function planRequiresTests(planContent) {
|
|
268
|
+
if (!planContent) return false;
|
|
269
|
+
if (hasYamlField(planContent, "tests_to_add")) {
|
|
270
|
+
return readYamlListField(planContent, "tests_to_add").length > 0;
|
|
271
|
+
}
|
|
272
|
+
return /^\s*create:[\s\S]*?^\s+-\s+path:.*\/lib\//m.test(planContent);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function validatePhaseArtifactContent(runId, completedPhase, manifest, enforcement) {
|
|
276
|
+
if (!isMutatingVerb(manifest, enforcement)) {
|
|
277
|
+
return { ok: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const planPath = path.join(runDir(runId), "artifacts/plan.yaml");
|
|
281
|
+
const planContent = fs.existsSync(planPath)
|
|
282
|
+
? fs.readFileSync(planPath, "utf8")
|
|
283
|
+
: "";
|
|
284
|
+
|
|
285
|
+
if (completedPhase === "plan") {
|
|
286
|
+
if (!hasYamlField(planContent, "tests_to_add")) {
|
|
287
|
+
return {
|
|
288
|
+
ok: false,
|
|
289
|
+
reason:
|
|
290
|
+
"plan.yaml must include tests_to_add (behaviors to cover, or tests_to_add: [] when no tests are needed)",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return { ok: true };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (completedPhase === "test_execute") {
|
|
297
|
+
const testPlanPath = path.join(runDir(runId), "artifacts/test_plan.yaml");
|
|
298
|
+
const testPlanContent = fs.existsSync(testPlanPath)
|
|
299
|
+
? fs.readFileSync(testPlanPath, "utf8")
|
|
300
|
+
: "";
|
|
301
|
+
|
|
302
|
+
const filesWritten = readYamlListField(testPlanContent, "files_written");
|
|
303
|
+
const skippedReason = readYamlScalarField(testPlanContent, "skipped_reason");
|
|
304
|
+
const testsRequired = planRequiresTests(planContent);
|
|
305
|
+
|
|
306
|
+
if (/status:\s*deferred/i.test(testPlanContent) && filesWritten.length === 0) {
|
|
307
|
+
return {
|
|
308
|
+
ok: false,
|
|
309
|
+
reason:
|
|
310
|
+
"test_plan.yaml cannot defer tests — author test files in test_execute (files_written required)",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (testsRequired && filesWritten.length === 0) {
|
|
315
|
+
return {
|
|
316
|
+
ok: false,
|
|
317
|
+
reason:
|
|
318
|
+
"plan.yaml tests_to_add requires non-empty test_plan.files_written — launch test-author Task in test_execute",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
hasYamlField(planContent, "tests_to_add") &&
|
|
324
|
+
/tests_to_add:\s*\[\]/m.test(planContent) &&
|
|
325
|
+
filesWritten.length === 0 &&
|
|
326
|
+
!skippedReason
|
|
327
|
+
) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
reason:
|
|
331
|
+
"tests_to_add is empty — test_plan.yaml must include skipped_reason explaining why no tests were authored",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { ok: true };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return { ok: true };
|
|
339
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Agent: doc-conformance
|
|
2
|
+
|
|
3
|
+
**Readonly.**
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Compare implementation diff against supporting docs and policies — not layer boundaries (see boundary-review).
|
|
8
|
+
|
|
9
|
+
## Sources (read before judging)
|
|
10
|
+
|
|
11
|
+
- [docs/master_rules.md](../../docs/master_rules.md)
|
|
12
|
+
- [docs/architecture.md](../../docs/architecture.md) when present
|
|
13
|
+
- Domain inventory under `.cursor/domains/<slug>/update/inventory/` when available
|
|
14
|
+
- [.cursor/policies/](../../.cursor/policies/)
|
|
15
|
+
|
|
16
|
+
## Check
|
|
17
|
+
|
|
18
|
+
- SSOT violations (duplicated constants, mirrored state)
|
|
19
|
+
- Undocumented exceptions to master rules
|
|
20
|
+
- Plan `requirement_map` entries satisfied in code
|
|
21
|
+
- Missing validation at boundaries when plan promised schemas
|
|
22
|
+
|
|
23
|
+
## Return
|
|
24
|
+
|
|
25
|
+
Findings, Evidence (`path:line`), Severity (critical | suggestion), Confidence.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Agent: implementation-review
|
|
2
|
+
|
|
3
|
+
**Readonly.**
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Independent post-execute review of the diff — **not** the agent that wrote the code. Spot-check that the change matches plan and does not introduce obvious defects.
|
|
8
|
+
|
|
9
|
+
## Check
|
|
10
|
+
|
|
11
|
+
- Plan `paths_to_touch` vs actual diff scope
|
|
12
|
+
- No drive-by refactors outside plan
|
|
13
|
+
- Error paths logged, not swallowed
|
|
14
|
+
- Async flows use explicit state machines where plan required
|
|
15
|
+
- Size budgets not violated on touched files (flag if file grew past 80% budget)
|
|
16
|
+
|
|
17
|
+
## Return
|
|
18
|
+
|
|
19
|
+
Findings, Evidence (`path:line`), Severity (critical | suggestion), Confidence.
|
|
20
|
+
|
|
21
|
+
**Blocking:** any **critical** finding must be fixed before `report` on mutating verbs.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Agent: remediation-check-app-inventory
|
|
2
|
+
|
|
3
|
+
**Readonly.** Mirrors `/check-app` discover phase for Fallow remediation.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Map Fallow `unused_files` and `review` inventory to **live app surfaces**: Vite entry points, workers (`src/workers/**`), hooks (`*Worker*`), overlay renderer barrels, lazy routes, Playwright-critical imports.
|
|
8
|
+
|
|
9
|
+
## Inputs (mandatory)
|
|
10
|
+
|
|
11
|
+
- `iterations/{n}/check-context.json`
|
|
12
|
+
- `iterations/{n}/fallow-scan.json`
|
|
13
|
+
- `frontend/.fallowrc.json` (`dynamicallyLoaded`)
|
|
14
|
+
|
|
15
|
+
## Commands (run as needed)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd frontend && fallow list --entry-points --format json --quiet 2>/dev/null || true
|
|
19
|
+
cd frontend && fallow dead-code --format json --quiet --trace-file <path> 2>/dev/null || true
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Return
|
|
23
|
+
|
|
24
|
+
Structured JSON block (see [check-swarm SKILL](../skills/shared/remediation/check-swarm/SKILL.md)) plus:
|
|
25
|
+
|
|
26
|
+
- **Answer** — are flagged unused files actually unreachable from app runtime?
|
|
27
|
+
- **protected_paths** — paths waves must never delete
|
|
28
|
+
- **false_positives** — with `reason` + `evidence` (`path:line`)
|
|
29
|
+
|
|
30
|
+
## Confidence
|
|
31
|
+
|
|
32
|
+
high | medium | low
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Agent: remediation-check-app-ssot
|
|
2
|
+
|
|
3
|
+
**Readonly.** Mirrors `/check-app` SSOT trace for Fallow remediation.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
For each Fallow `review` / `true_positive` export: who owns the symbol? Is it consumed via barrel re-export, dynamic import, worker postMessage, or external package API?
|
|
8
|
+
|
|
9
|
+
## Inputs (mandatory)
|
|
10
|
+
|
|
11
|
+
- `iterations/{n}/check-context.json` — `fallow.inventory`
|
|
12
|
+
- `fallow-false-positives.json` (campaign registry)
|
|
13
|
+
|
|
14
|
+
## Method
|
|
15
|
+
|
|
16
|
+
1. Grep importers for top `review` exports
|
|
17
|
+
2. Check barrel `index.ts` re-export chains
|
|
18
|
+
3. Flag provider interface methods (`name`, `createInvoice`) as **protected** not dead
|
|
19
|
+
|
|
20
|
+
## Return
|
|
21
|
+
|
|
22
|
+
JSON block with `false_positives`, `do_not_delete`, `safe_to_fix`, `findings`, `gaps`.
|
|
23
|
+
|
|
24
|
+
Set `command_mirror: "check-app"`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Agent: remediation-check-app-trace
|
|
2
|
+
|
|
3
|
+
**Readonly.** Mirrors `/check-app` capability trace for Fallow remediation.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Run Fallow trace CLI for every item in `check-context.fallow.top_review_for_trace`. Confirm whether static unused = actually unreachable.
|
|
8
|
+
|
|
9
|
+
## Commands (mandatory for each review item)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd frontend && fallow dead-code --format json --quiet --trace-file <path> 2>/dev/null || true
|
|
13
|
+
cd frontend && fallow dead-code --format json --quiet --trace <path>:<export> 2>/dev/null || true
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Classification rules
|
|
17
|
+
|
|
18
|
+
| Trace result | Classification |
|
|
19
|
+
|--------------|----------------|
|
|
20
|
+
| Entry-point or dynamically loaded | `false_positive` |
|
|
21
|
+
| Re-export chain to live entry | `false_positive` |
|
|
22
|
+
| Zero importers, not entry | `true_positive` or `safe_to_fix` |
|
|
23
|
+
| Ambiguous (test-only import) | `review` |
|
|
24
|
+
|
|
25
|
+
## Return
|
|
26
|
+
|
|
27
|
+
JSON block with per-path trace summary in `findings`. Populate `false_positives` for confirmed runtime paths.
|
|
28
|
+
|
|
29
|
+
Set `command_mirror: "check-app"`, `agent_id: "remediation-check-app-trace"`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Agent: remediation-check-architecture-boundaries
|
|
2
|
+
|
|
3
|
+
**Readonly.** Mirrors `/check-architecture` boundary review for remediation waves.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Evaluate whether proposed Fallow deletions or dupes extractions would cross layer boundaries (UI→fetch, domain→infrastructure, worker↔main SSOT violations).
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
- `check-context.json`
|
|
12
|
+
- `docs/architecture.md` (if present)
|
|
13
|
+
- Fallow `boundary_violations` from dead-code scan
|
|
14
|
+
|
|
15
|
+
## Return
|
|
16
|
+
|
|
17
|
+
JSON block: `command_mirror: "check-architecture"`. List boundary risks in `findings`. Add blast-radius paths to `protected_paths` / `do_not_delete`.
|
|
18
|
+
|
|
19
|
+
## Severity
|
|
20
|
+
|
|
21
|
+
critical = deletion would break boundary; suggestion = refactor-only
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Agent: remediation-check-architecture-decomposition
|
|
2
|
+
|
|
3
|
+
**Readonly.** Mirrors `/check-architecture` system decomposition for dupes remediation.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Classify dupes clone families (operations/, workers/, e2e specs) into:
|
|
8
|
+
|
|
9
|
+
- **extract-shared** — safe consolidation target
|
|
10
|
+
- **main-worker mirror** — do not delete one side; extract to shared module first
|
|
11
|
+
- **test-only dupes** — low risk extract
|
|
12
|
+
- **intentional parallel** — mark protected (e.g. provider adapters)
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
- `check-context.dupes_top_groups`
|
|
17
|
+
- `fallow-dupes.json` clone_groups
|
|
18
|
+
|
|
19
|
+
## Return
|
|
20
|
+
|
|
21
|
+
JSON block: `command_mirror: "check-architecture"`. Dupes safe targets in `safe_to_fix`. Mirrored paths in `protected_paths`.
|
|
22
|
+
|
|
23
|
+
## Anti-pattern
|
|
24
|
+
|
|
25
|
+
Never recommend deleting `src/lib/**` because worker has a copy — recommend shared extract wave instead.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Agent: remediation-check-architecture-deps
|
|
2
|
+
|
|
3
|
+
**Readonly.** Mirrors `/check-architecture` dependency analysis for remediation.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
For top `true_positive` file deletions and large dupes groups: compute fan-in, import cycles, and downstream test breakage risk.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
- `check-context.json` — `dupes_top_groups`, `fallow.inventory.true_positive`
|
|
12
|
+
- `fallow dead-code` circular_dependencies list
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd frontend && fallow dead-code --format json --quiet --trace-file <path> 2>/dev/null || true
|
|
18
|
+
cd frontend && fallow dupes --format json --quiet --trace <path>:<line> 2>/dev/null || true
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Return
|
|
22
|
+
|
|
23
|
+
JSON block: `command_mirror: "check-architecture"`. High fan-in paths → `protected_paths`. Isolated leaves → `safe_to_fix`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Agent: remediation-check-risk
|
|
2
|
+
|
|
3
|
+
**Readonly.** Remediation guard — consolidates FP traps before fix waves.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
Final pass on all Fallow layers (dead-code, dupes, health). Confirm or reject classifications from other swarm agents. **This agent owns the FP registry update.**
|
|
8
|
+
|
|
9
|
+
## Mandatory actions
|
|
10
|
+
|
|
11
|
+
1. Read `check-context.json` + `fallow-classification.json`
|
|
12
|
+
2. Cross-check other agents' `false_positives` proposals
|
|
13
|
+
3. Write batch file for merge script OR confirm parent will run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node .cursor/aaac/scripts/remediation/record-fallow-fp.mjs \
|
|
17
|
+
--campaign-id <id> --from-json iterations/<n>/check-swarm-fp-batch.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Known FP patterns (always verify)
|
|
21
|
+
|
|
22
|
+
| Pattern | Reason |
|
|
23
|
+
|---------|--------|
|
|
24
|
+
| `src/hooks/*Worker*.ts` | worker_hook_runtime |
|
|
25
|
+
| `src/workers/**` | dynamically_loaded |
|
|
26
|
+
| `src/overlays/renderers/*/index.ts` | overlay_renderer_barrel |
|
|
27
|
+
| `LayoutSaveQueue.enqueue/cancel` | framework lifecycle |
|
|
28
|
+
| `AtlosPaymentProvider.name/createInvoice` | provider interface |
|
|
29
|
+
| `src/operations/categories/**` dupes | boilerplate — extract, don't delete ops |
|
|
30
|
+
|
|
31
|
+
## Return
|
|
32
|
+
|
|
33
|
+
JSON block with complete `false_positives[]`, `protected_paths[]`, `do_not_delete[]`. Set `agent_id: "remediation-check-risk"`.
|
|
34
|
+
|
|
35
|
+
## Rule
|
|
36
|
+
|
|
37
|
+
When uncertain → `review` + `protected_paths`, never `true_positive` delete.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Agent: remediation-e2e-gate
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
|
|
5
|
+
Run the full iteration verification gate for a remediation campaign and return structured pass/fail.
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
1. Confirm `SE100_BASE_URL` (default `http://localhost:5173`) is reachable
|
|
10
|
+
2. Run:
|
|
11
|
+
```bash
|
|
12
|
+
node .cursor/aaac/scripts/remediation/verify-remediation-iteration.mjs \
|
|
13
|
+
--campaign-id <campaign_id> --iteration <n> --mode iteration --run-id <run_id>
|
|
14
|
+
```
|
|
15
|
+
3. Read output JSON — report each layer status
|
|
16
|
+
|
|
17
|
+
## Return
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
status: pass | fail
|
|
21
|
+
layers:
|
|
22
|
+
typecheck: pass | fail
|
|
23
|
+
vitest: pass | fail
|
|
24
|
+
go_test: pass | fail | skipped
|
|
25
|
+
build: pass | fail
|
|
26
|
+
playwright: pass | fail
|
|
27
|
+
artifact_path: .cursor/aaac/state/campaigns/{id}/iterations/{n}/verify-iteration.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
On fail: include `stderr_tail` excerpts and whether rollback is recommended.
|