@ludecker/aaac 1.0.0 → 1.1.1
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 +4 -3
- package/package.json +13 -1
- package/src/cli.mjs +39 -5
- package/src/generators/generate-commands.mjs +120 -3
- package/src/generators/generate-graph.mjs +17 -0
- package/src/lib/install.mjs +1 -0
- package/src/lib/run-engine-paths.mjs +33 -0
- package/src/run-engine/advance-phase.mjs +343 -0
- package/src/run-engine/capability-evidence.mjs +460 -0
- package/src/run-engine/debug-run.mjs +38 -0
- package/src/run-engine/gate-write.mjs +95 -0
- package/src/run-engine/init-run.mjs +215 -0
- package/src/run-engine/lib.mjs +141 -0
- package/src/run-engine/log-dump.mjs +76 -0
- package/src/run-engine/log-trace.mjs +18 -0
- package/src/run-engine/log.mjs +343 -0
- package/src/run-engine/record-task.mjs +56 -0
- package/src/run-engine/stop-check.mjs +55 -0
- package/src/run-engine/verify-website-build.mjs +148 -0
- package/templates/cursor/aaac/capabilities/promotion-rules.json +64 -0
- package/templates/cursor/aaac/capabilities/registry.json +17 -15
- package/templates/cursor/aaac/complexity.yaml +98 -0
- package/templates/cursor/aaac/contracts/commands/fix-bug.yaml +10 -3
- package/templates/cursor/aaac/contracts/commands/fix-module.yaml +41 -0
- package/templates/cursor/aaac/contracts/skills/investigation.yaml +22 -1
- package/templates/cursor/aaac/contracts/skills/planning.yaml +17 -0
- package/templates/cursor/aaac/contracts/skills/validation.yaml +9 -1
- package/templates/cursor/aaac/dispatch.md +31 -6
- package/templates/cursor/aaac/enforcement.json +25 -0
- package/templates/cursor/aaac/fitness-functions.yaml +8 -0
- package/templates/cursor/aaac/governance/gates.json +6 -2
- package/templates/cursor/aaac/graph.project.yaml +237 -5
- package/templates/cursor/aaac/layers.md +6 -1
- package/templates/cursor/aaac/lifecycle/lifecycle.json +41 -1
- package/templates/cursor/aaac/lifecycle/phases.json +1 -0
- package/templates/cursor/aaac/observability/telemetry.yaml +63 -0
- package/templates/cursor/aaac/observability/verb-debug.yaml +170 -0
- package/templates/cursor/aaac/ontology.json +10 -1
- package/templates/cursor/aaac/run/RUN.md +2 -0
- package/templates/cursor/aaac/run/schema.json +11 -0
- package/templates/cursor/aaac/scripts/generate-runtime-registry.mjs +115 -0
- package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +343 -0
- package/templates/cursor/aaac/scripts/run-engine/capability-evidence.mjs +460 -0
- package/templates/cursor/aaac/scripts/run-engine/debug-run.mjs +38 -0
- package/templates/cursor/aaac/scripts/run-engine/gate-write.mjs +95 -0
- package/templates/cursor/aaac/scripts/run-engine/init-run.mjs +215 -0
- package/templates/cursor/aaac/scripts/run-engine/lib.mjs +141 -0
- package/templates/cursor/aaac/scripts/run-engine/log-dump.mjs +76 -0
- package/templates/cursor/aaac/scripts/run-engine/log-trace.mjs +18 -0
- package/templates/cursor/aaac/scripts/run-engine/log.mjs +343 -0
- package/templates/cursor/aaac/scripts/run-engine/record-task.mjs +56 -0
- package/templates/cursor/aaac/scripts/run-engine/stop-check.mjs +55 -0
- package/templates/cursor/aaac/scripts/run-engine/verify-website-build.mjs +148 -0
- package/templates/cursor/aaac/state/capability-stats.json +5 -0
- package/templates/cursor/agents/aaac-log-debug.md +72 -0
- package/templates/cursor/agents/fix-code-path.md +27 -0
- package/templates/cursor/agents/fix-hypothesis-validate.md +26 -0
- package/templates/cursor/agents/fix-inventory-confirm.md +22 -0
- package/templates/cursor/agents/fix-recent-changes.md +22 -0
- package/templates/cursor/agents/fix-regression-scope.md +27 -0
- package/templates/cursor/agents/fix-repro-verify.md +21 -0
- package/templates/cursor/agents/fix-repro.md +29 -0
- package/templates/cursor/agents/fix-runtime-evidence.md +22 -0
- package/templates/cursor/agents/fix-test-failures.md +23 -0
- package/templates/cursor/agents/playwright-check-run.md +44 -0
- package/templates/cursor/hooks/aaac-before-submit.sh +3 -0
- package/templates/cursor/hooks/aaac-pre-tool.sh +4 -0
- package/templates/cursor/hooks/aaac-stop.sh +3 -0
- package/templates/cursor/hooks/aaac-subagent-start.sh +3 -0
- package/templates/cursor/hooks.json +30 -0
- package/templates/cursor/policies/minimal-complexity.md +101 -0
- package/templates/cursor/rules/aaac-enforcement.mdc +42 -0
- package/templates/cursor/skills/shared/execution/SKILL.md +1 -1
- package/templates/cursor/skills/shared/fitness-functions/SKILL.md +23 -7
- package/templates/cursor/skills/shared/investigation/SKILL.md +91 -18
- package/templates/cursor/skills/shared/investigation/orchestrator/SKILL.md +12 -4
- package/templates/cursor/skills/shared/planning/SKILL.md +74 -8
- package/templates/cursor/skills/shared/platform-release/SKILL.md +22 -19
- package/templates/cursor/skills/shared/platform-release/orchestrator/contract.yaml +27 -7
- package/templates/cursor/skills/shared/reporting/SKILL.md +2 -1
- package/templates/cursor/skills/shared/root-cause/SKILL.md +14 -3
- package/templates/cursor/skills/shared/testing/SKILL.md +31 -5
- package/templates/cursor/skills/shared/validation/SKILL.md +48 -13
- package/templates/cursor/skills/shared/verbs/_dispatch-utils.md +20 -1
- package/templates/cursor/skills/shared/verbs/_lifecycle.md +3 -2
- package/templates/cursor/skills/shared/verbs/check/orchestrator/SKILL.md +4 -1
- package/templates/cursor/skills/shared/verbs/create/orchestrator/SKILL.md +2 -2
- package/templates/cursor/skills/shared/verbs/fix/orchestrator/SKILL.md +21 -11
- package/templates/cursor/skills/shared/verbs/fix/orchestrator/contract.yaml +19 -4
- package/templates/cursor/skills/shared/verbs/update/orchestrator/SKILL.md +2 -2
- package/templates/cursor/skills/shared/verification/SKILL.md +3 -0
- package/templates/docs/agentic_architecture.md +236 -53
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AAAC Run manifest logging — SSOT: observability/telemetry.yaml
|
|
4
|
+
*/
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { isoNow } from "./lib.mjs";
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const AAAC_ROOT = path.resolve(__dirname, "../..");
|
|
12
|
+
|
|
13
|
+
export const LOG_LEVEL_PRIORITY = {
|
|
14
|
+
debug: 0,
|
|
15
|
+
info: 1,
|
|
16
|
+
warn: 2,
|
|
17
|
+
error: 3,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const VALID_LEVELS = new Set(Object.keys(LOG_LEVEL_PRIORITY));
|
|
21
|
+
|
|
22
|
+
export function normalizeLevel(level) {
|
|
23
|
+
const normalized = (level ?? "info").toLowerCase();
|
|
24
|
+
return VALID_LEVELS.has(normalized) ? normalized : "info";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getLogLevel() {
|
|
28
|
+
return normalizeLevel(process.env.LOG_LEVEL);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function shouldLog(level) {
|
|
32
|
+
return LOG_LEVEL_PRIORITY[normalizeLevel(level)] >= LOG_LEVEL_PRIORITY[getLogLevel()];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function recordLog(manifest, opts) {
|
|
36
|
+
const level = normalizeLevel(opts.level ?? "info");
|
|
37
|
+
const entry = {
|
|
38
|
+
at: isoNow(),
|
|
39
|
+
run_id: manifest.run_id,
|
|
40
|
+
phase: opts.phase ?? manifest.phase ?? "dispatch",
|
|
41
|
+
phase_kind: opts.phase_kind ?? manifest.phase_kind ?? "work",
|
|
42
|
+
skill: opts.skill ?? null,
|
|
43
|
+
event: opts.event,
|
|
44
|
+
detail: opts.detail ?? "",
|
|
45
|
+
level,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
manifest.log = manifest.log ?? [];
|
|
49
|
+
manifest.log.push(entry);
|
|
50
|
+
|
|
51
|
+
if (shouldLog(level)) {
|
|
52
|
+
debugPrint(entry);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return entry;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function recordDecision(manifest, opts) {
|
|
59
|
+
const entry = {
|
|
60
|
+
at: isoNow(),
|
|
61
|
+
phase: opts.phase ?? manifest.phase ?? "dispatch",
|
|
62
|
+
decision: opts.decision,
|
|
63
|
+
reason: opts.reason,
|
|
64
|
+
evidence: opts.evidence ?? "",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
manifest.decisions = manifest.decisions ?? [];
|
|
68
|
+
manifest.decisions.push(entry);
|
|
69
|
+
|
|
70
|
+
if (shouldLog("debug")) {
|
|
71
|
+
debugPrint({
|
|
72
|
+
level: "debug",
|
|
73
|
+
run_id: manifest.run_id,
|
|
74
|
+
phase: entry.phase,
|
|
75
|
+
event: "decision",
|
|
76
|
+
detail: entry.decision,
|
|
77
|
+
reason: entry.reason,
|
|
78
|
+
evidence: entry.evidence,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function debugPrint(entry) {
|
|
86
|
+
const level = normalizeLevel(entry.level ?? "info");
|
|
87
|
+
const phase = entry.phase ?? "—";
|
|
88
|
+
const event = entry.event ?? entry.decision ?? "—";
|
|
89
|
+
const detail = entry.detail ?? entry.reason ?? "";
|
|
90
|
+
const context = {};
|
|
91
|
+
|
|
92
|
+
if (entry.run_id) context.run_id = entry.run_id;
|
|
93
|
+
if (entry.skill) context.skill = entry.skill;
|
|
94
|
+
if (entry.phase_kind) context.phase_kind = entry.phase_kind;
|
|
95
|
+
if (entry.reason && entry.event === "decision") context.reason = entry.reason;
|
|
96
|
+
if (entry.evidence) context.evidence = entry.evidence;
|
|
97
|
+
|
|
98
|
+
const ctxStr = Object.keys(context).length ? ` ${JSON.stringify(context)}` : "";
|
|
99
|
+
process.stderr.write(`[${level}] [run:${phase}:${event}] ${detail}${ctxStr}\n`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function filterLogByLevel(entries, minLevel) {
|
|
103
|
+
const floor = LOG_LEVEL_PRIORITY[normalizeLevel(minLevel)];
|
|
104
|
+
return (entries ?? []).filter((e) => {
|
|
105
|
+
const entryLevel = e.level ? normalizeLevel(e.level) : "info";
|
|
106
|
+
return LOG_LEVEL_PRIORITY[entryLevel] >= floor;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function loadVerbDebugProfiles() {
|
|
111
|
+
const profilePath = path.join(AAAC_ROOT, "observability", "verb-debug.yaml");
|
|
112
|
+
try {
|
|
113
|
+
const raw = fs.readFileSync(profilePath, "utf8");
|
|
114
|
+
return parseVerbDebugYaml(raw);
|
|
115
|
+
} catch {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseVerbDebugYaml(raw) {
|
|
121
|
+
const profiles = {};
|
|
122
|
+
let current = null;
|
|
123
|
+
let section = null;
|
|
124
|
+
let eventPhase = null;
|
|
125
|
+
|
|
126
|
+
for (const line of raw.split("\n")) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
129
|
+
|
|
130
|
+
const profileMatch = trimmed.match(/^([a-z_]+):\s*$/);
|
|
131
|
+
if (profileMatch && !line.startsWith(" ")) {
|
|
132
|
+
current = profileMatch[1];
|
|
133
|
+
profiles[current] = { phases: [], swarm_minimums: {}, description: "" };
|
|
134
|
+
section = null;
|
|
135
|
+
eventPhase = null;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!current) continue;
|
|
140
|
+
|
|
141
|
+
if (trimmed === "phases:") {
|
|
142
|
+
section = "phases";
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (trimmed === "expected_events:") {
|
|
146
|
+
section = "events";
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (trimmed === "swarm_minimums:") {
|
|
150
|
+
section = "swarm_minimums";
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (trimmed.startsWith("description:")) {
|
|
154
|
+
profiles[current].description = trimmed.slice("description:".length).trim();
|
|
155
|
+
section = null;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const listItem = trimmed.match(/^- (.+)$/);
|
|
160
|
+
if (listItem && section === "phases") {
|
|
161
|
+
profiles[current].phases.push(listItem[1]);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const phaseKey = trimmed.match(/^([a-z_]+):\s*$/);
|
|
166
|
+
if (phaseKey && section === "events") {
|
|
167
|
+
eventPhase = phaseKey[1];
|
|
168
|
+
profiles[current].expected_events = profiles[current].expected_events ?? {};
|
|
169
|
+
profiles[current].expected_events[eventPhase] = [];
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (listItem && section === "events" && eventPhase) {
|
|
174
|
+
profiles[current].expected_events[eventPhase].push(listItem[1]);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const swarmEntry = trimmed.match(/^([a-z_]+):\s*(\d+)$/);
|
|
179
|
+
if (swarmEntry && section === "swarm_minimums") {
|
|
180
|
+
profiles[current].swarm_minimums[swarmEntry[1]] = Number(swarmEntry[2]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return profiles;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function swarmCountForPhase(log, phase) {
|
|
188
|
+
const entries = (log ?? []).filter((e) => e.phase === phase);
|
|
189
|
+
const launches = entries.filter(
|
|
190
|
+
(e) => e.event === "agent_spawned" || e.event === "task_launch",
|
|
191
|
+
);
|
|
192
|
+
if (launches.length) {
|
|
193
|
+
const last = launches[launches.length - 1];
|
|
194
|
+
const match = String(last.detail ?? "").match(/count=(\d+)/);
|
|
195
|
+
return match ? Number(match[1]) : launches.length;
|
|
196
|
+
}
|
|
197
|
+
const complete = entries.find((e) => e.event === "phase_complete");
|
|
198
|
+
if (complete) {
|
|
199
|
+
const swarmMatch = String(complete.detail ?? "").match(/swarm_count=(\d+)/);
|
|
200
|
+
if (swarmMatch) return Number(swarmMatch[1]);
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function formatTimeline(manifest, { minLevel = "debug" } = {}) {
|
|
206
|
+
const lines = [];
|
|
207
|
+
const logs = filterLogByLevel(manifest.log ?? [], minLevel);
|
|
208
|
+
const decisions = manifest.decisions ?? [];
|
|
209
|
+
|
|
210
|
+
const merged = [
|
|
211
|
+
...logs.map((e) => ({ ...e, kind: "log" })),
|
|
212
|
+
...decisions.map((e) => ({
|
|
213
|
+
at: e.at,
|
|
214
|
+
phase: e.phase,
|
|
215
|
+
event: `decision:${e.decision}`,
|
|
216
|
+
detail: e.reason,
|
|
217
|
+
level: "info",
|
|
218
|
+
kind: "decision",
|
|
219
|
+
evidence: e.evidence,
|
|
220
|
+
})),
|
|
221
|
+
].sort((a, b) => new Date(a.at) - new Date(b.at));
|
|
222
|
+
|
|
223
|
+
for (const entry of merged) {
|
|
224
|
+
const level = entry.level ?? "info";
|
|
225
|
+
const skill = entry.skill ? ` skill=${entry.skill}` : "";
|
|
226
|
+
const evidence = entry.evidence ? ` evidence="${entry.evidence}"` : "";
|
|
227
|
+
lines.push(
|
|
228
|
+
`${entry.at} [${level}] ${entry.phase} :: ${entry.event}${skill} — ${entry.detail}${evidence}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function buildTrace(manifest) {
|
|
236
|
+
const verb = manifest.verb ?? "unknown";
|
|
237
|
+
const profiles = loadVerbDebugProfiles();
|
|
238
|
+
const profile = profiles[verb] ?? null;
|
|
239
|
+
const log = manifest.log ?? [];
|
|
240
|
+
const decisions = manifest.decisions ?? [];
|
|
241
|
+
const sections = [];
|
|
242
|
+
|
|
243
|
+
sections.push(`# AAAC trace: ${manifest.run_id}`);
|
|
244
|
+
sections.push(`Command: /${manifest.command} Verb: ${verb} Status: ${manifest.status}`);
|
|
245
|
+
if (manifest.blocked_reason) sections.push(`Blocked: ${manifest.blocked_reason}`);
|
|
246
|
+
sections.push("");
|
|
247
|
+
|
|
248
|
+
sections.push("## Why did it do this?");
|
|
249
|
+
for (const d of decisions) {
|
|
250
|
+
sections.push(`- [${d.phase}] ${d.decision}: ${d.reason}`);
|
|
251
|
+
if (d.evidence) sections.push(` evidence: ${d.evidence}`);
|
|
252
|
+
}
|
|
253
|
+
if (!decisions.length) sections.push("- (no decisions recorded)");
|
|
254
|
+
sections.push("");
|
|
255
|
+
|
|
256
|
+
sections.push("## Which skill ran?");
|
|
257
|
+
const skillEvents = log.filter((e) => e.skill || e.event === "skill_loaded");
|
|
258
|
+
if (skillEvents.length) {
|
|
259
|
+
for (const e of skillEvents) {
|
|
260
|
+
sections.push(`- ${e.at} ${e.phase}: ${e.skill ?? e.detail}`);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
sections.push("- (no skill_loaded events — infer from phase transitions)");
|
|
264
|
+
for (const e of log.filter((x) => x.event === "phase_start")) {
|
|
265
|
+
sections.push(`- phase ${e.phase} started`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
sections.push("");
|
|
269
|
+
|
|
270
|
+
sections.push("## Why was a route chosen?");
|
|
271
|
+
const routing = decisions.filter((d) =>
|
|
272
|
+
/route|orchestrator|capability|graph|dispatch/i.test(`${d.decision} ${d.reason}`),
|
|
273
|
+
);
|
|
274
|
+
if (routing.length) {
|
|
275
|
+
for (const d of routing) sections.push(`- ${d.decision}: ${d.reason}`);
|
|
276
|
+
} else if (manifest.orchestrator) {
|
|
277
|
+
sections.push(`- orchestrator: ${manifest.orchestrator}`);
|
|
278
|
+
} else {
|
|
279
|
+
sections.push("- (see decisions and command registry entry)");
|
|
280
|
+
}
|
|
281
|
+
sections.push("");
|
|
282
|
+
|
|
283
|
+
sections.push("## Why is the run blocked?");
|
|
284
|
+
if (manifest.status === "blocked" || manifest.awaiting_approval) {
|
|
285
|
+
sections.push(`- status=${manifest.status} awaiting_approval=${manifest.awaiting_approval}`);
|
|
286
|
+
sections.push(`- ${manifest.blocked_reason ?? "gate or swarm incomplete"}`);
|
|
287
|
+
for (const e of log.filter((x) => x.event === "edit_denied" || x.event === "gate_fail").slice(-5)) {
|
|
288
|
+
sections.push(`- ${e.at} ${e.event}: ${e.detail}`);
|
|
289
|
+
}
|
|
290
|
+
} else if (manifest.status === "running") {
|
|
291
|
+
sections.push(`- Not blocked. Current phase: ${manifest.phase}`);
|
|
292
|
+
const minAgents = profile?.swarm_minimums?.[manifest.phase];
|
|
293
|
+
if (minAgents) {
|
|
294
|
+
const count = manifest.swarm?.task_launches_this_phase ?? swarmCountForPhase(log, manifest.phase);
|
|
295
|
+
sections.push(`- Swarm: ${count}/${minAgents} agents this phase`);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
sections.push(`- Run ${manifest.status}`);
|
|
299
|
+
}
|
|
300
|
+
sections.push("");
|
|
301
|
+
|
|
302
|
+
if (profile) {
|
|
303
|
+
sections.push(`## Verb profile (${verb})`);
|
|
304
|
+
if (profile.description) sections.push(profile.description);
|
|
305
|
+
sections.push(`Highlight phases: ${(profile.phases ?? []).join(" → ")}`);
|
|
306
|
+
for (const [phase, min] of Object.entries(profile.swarm_minimums ?? {})) {
|
|
307
|
+
const actual = swarmCountForPhase(log, phase);
|
|
308
|
+
sections.push(`- ${phase}: swarm ${actual}/${min} (${actual >= min ? "ok" : "INCOMPLETE"})`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
sections.push("");
|
|
313
|
+
sections.push("## Chronological timeline");
|
|
314
|
+
sections.push(formatTimeline(manifest));
|
|
315
|
+
|
|
316
|
+
return sections.join("\n");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function debugRunSummary(manifest) {
|
|
320
|
+
const log = manifest.log ?? [];
|
|
321
|
+
const phase = manifest.phase;
|
|
322
|
+
const swarmPhase = manifest.swarm?.phase ?? phase;
|
|
323
|
+
const swarmCount =
|
|
324
|
+
manifest.swarm?.task_launches_this_phase ?? swarmCountForPhase(log, swarmPhase);
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
run_id: manifest.run_id,
|
|
328
|
+
command: manifest.command,
|
|
329
|
+
verb: manifest.verb,
|
|
330
|
+
status: manifest.status,
|
|
331
|
+
phase,
|
|
332
|
+
phase_kind: manifest.phase_kind,
|
|
333
|
+
blocked_reason: manifest.blocked_reason,
|
|
334
|
+
awaiting_approval: manifest.awaiting_approval,
|
|
335
|
+
completed: manifest.completed ?? [],
|
|
336
|
+
pending: manifest.pending ?? [],
|
|
337
|
+
swarm: { phase: swarmPhase, task_launches_this_phase: swarmCount },
|
|
338
|
+
edit_allowed: manifest.enforcement?.edit_allowed ?? false,
|
|
339
|
+
last_log_entries: log.slice(-10),
|
|
340
|
+
decisions_count: (manifest.decisions ?? []).length,
|
|
341
|
+
log_count: log.length,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
loadActiveRun,
|
|
5
|
+
loadRunManifest,
|
|
6
|
+
runDir,
|
|
7
|
+
writeJson,
|
|
8
|
+
saveActiveRun,
|
|
9
|
+
isoNow,
|
|
10
|
+
conversationIdFromHook,
|
|
11
|
+
} from "./lib.mjs";
|
|
12
|
+
import { recordLog } from "./log.mjs";
|
|
13
|
+
|
|
14
|
+
let input = "";
|
|
15
|
+
process.stdin.setEncoding("utf8");
|
|
16
|
+
process.stdin.on("data", (c) => (input += c));
|
|
17
|
+
process.stdin.on("end", () => {
|
|
18
|
+
const allow = () => {
|
|
19
|
+
console.log(JSON.stringify({ permission: "allow" }));
|
|
20
|
+
process.exit(0);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let hook;
|
|
24
|
+
try {
|
|
25
|
+
hook = JSON.parse(input || "{}");
|
|
26
|
+
} catch {
|
|
27
|
+
allow();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const conversationId = conversationIdFromHook(hook);
|
|
31
|
+
if (!conversationId) allow();
|
|
32
|
+
|
|
33
|
+
const active = loadActiveRun(conversationId);
|
|
34
|
+
if (!active?.run_id) allow();
|
|
35
|
+
|
|
36
|
+
const manifest = loadRunManifest(active.run_id);
|
|
37
|
+
if (!manifest || manifest.status === "completed") allow();
|
|
38
|
+
if (manifest.conversation_id && manifest.conversation_id !== conversationId) allow();
|
|
39
|
+
|
|
40
|
+
manifest.swarm = manifest.swarm ?? {};
|
|
41
|
+
manifest.swarm.task_launches_this_phase = (manifest.swarm.task_launches_this_phase ?? 0) + 1;
|
|
42
|
+
manifest.swarm.phase = manifest.phase;
|
|
43
|
+
manifest.updated_at = isoNow();
|
|
44
|
+
|
|
45
|
+
recordLog(manifest, {
|
|
46
|
+
event: "agent_spawned",
|
|
47
|
+
phase: manifest.phase,
|
|
48
|
+
phase_kind: manifest.phase_kind,
|
|
49
|
+
detail: `count=${manifest.swarm.task_launches_this_phase}`,
|
|
50
|
+
level: "debug",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
writeJson(path.join(runDir(active.run_id), "run.json"), manifest);
|
|
54
|
+
saveActiveRun(conversationId, { ...active, task_launches_this_phase: manifest.swarm.task_launches_this_phase });
|
|
55
|
+
allow();
|
|
56
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
loadActiveRun,
|
|
5
|
+
loadRunManifest,
|
|
6
|
+
conversationIdFromHook,
|
|
7
|
+
runDir,
|
|
8
|
+
writeJson,
|
|
9
|
+
isoNow,
|
|
10
|
+
} from "./lib.mjs";
|
|
11
|
+
import { recordLog } from "./log.mjs";
|
|
12
|
+
|
|
13
|
+
let input = "";
|
|
14
|
+
process.stdin.setEncoding("utf8");
|
|
15
|
+
process.stdin.on("data", (c) => (input += c));
|
|
16
|
+
process.stdin.on("end", () => {
|
|
17
|
+
let hook = {};
|
|
18
|
+
try {
|
|
19
|
+
hook = JSON.parse(input || "{}");
|
|
20
|
+
} catch {
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const conversationId = conversationIdFromHook(hook);
|
|
25
|
+
if (!conversationId) process.exit(0);
|
|
26
|
+
|
|
27
|
+
const active = loadActiveRun(conversationId);
|
|
28
|
+
if (!active?.run_id) process.exit(0);
|
|
29
|
+
|
|
30
|
+
const manifest = loadRunManifest(active.run_id);
|
|
31
|
+
if (!manifest || manifest.status === "completed") process.exit(0);
|
|
32
|
+
|
|
33
|
+
const remaining = [manifest.phase, ...(manifest.pending ?? [])].filter(Boolean);
|
|
34
|
+
|
|
35
|
+
recordLog(manifest, {
|
|
36
|
+
event: "run_incomplete",
|
|
37
|
+
phase: manifest.phase,
|
|
38
|
+
phase_kind: manifest.phase_kind,
|
|
39
|
+
detail: `stop hook: status=${manifest.status} remaining=${remaining.join("→")}`,
|
|
40
|
+
level: "warn",
|
|
41
|
+
});
|
|
42
|
+
manifest.updated_at = isoNow();
|
|
43
|
+
writeJson(path.join(runDir(active.run_id), "run.json"), manifest);
|
|
44
|
+
|
|
45
|
+
console.log(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
followup_message: [
|
|
48
|
+
`AAAC Run ${active.run_id} incomplete (this chat). Phase: ${manifest.phase}.`,
|
|
49
|
+
`Remaining: ${remaining.join(" → ")}`,
|
|
50
|
+
`Advance: node .cursor/aaac/scripts/run-engine/advance-phase.mjs ${active.run_id} ${manifest.phase}`,
|
|
51
|
+
`Debug: node .cursor/aaac/scripts/run-engine/debug-run.mjs ${active.run_id}`,
|
|
52
|
+
].join("\n"),
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verify website static assets + production build.
|
|
4
|
+
* Used by advance-phase on create/update/fix verify completion.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node verify-website-build.mjs [--run-id <run_id>] [--skip-build]
|
|
8
|
+
*/
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { spawnSync } from "child_process";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { REPO_ROOT, runDir, isoNow, writeJson } from "./lib.mjs";
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const WEBSITE_ROOT = path.join(REPO_ROOT, "apps/website");
|
|
17
|
+
const INDEX_HTML = path.join(WEBSITE_ROOT, "index.html");
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const runIdIdx = args.indexOf("--run-id");
|
|
21
|
+
const runId = runIdIdx >= 0 ? args[runIdIdx + 1] : null;
|
|
22
|
+
const skipBuild = args.includes("--skip-build");
|
|
23
|
+
|
|
24
|
+
const results = {
|
|
25
|
+
status: "pass",
|
|
26
|
+
checked_at: isoNow(),
|
|
27
|
+
static_assets: { status: "pass", missing: [] },
|
|
28
|
+
build: { status: skipBuild ? "skipped" : "pending", command: "pnpm --filter @ludecker/website build" },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function fail(section, detail) {
|
|
32
|
+
results.status = "fail";
|
|
33
|
+
if (section === "static_assets") {
|
|
34
|
+
results.static_assets.status = "fail";
|
|
35
|
+
results.static_assets.missing.push(detail);
|
|
36
|
+
} else if (section === "build") {
|
|
37
|
+
results.build.status = "fail";
|
|
38
|
+
results.build.detail = detail;
|
|
39
|
+
}
|
|
40
|
+
console.error(`[verify-website-build] FAIL ${section}: ${detail}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveRootAsset(assetPath) {
|
|
44
|
+
const rel = assetPath.replace(/^\//, "");
|
|
45
|
+
const candidates = [
|
|
46
|
+
path.join(WEBSITE_ROOT, "public", rel),
|
|
47
|
+
path.join(WEBSITE_ROOT, rel),
|
|
48
|
+
];
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
if (fs.existsSync(candidate)) {
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function checkStaticAssets() {
|
|
58
|
+
if (!fs.existsSync(INDEX_HTML)) {
|
|
59
|
+
fail("static_assets", `missing index.html at ${INDEX_HTML}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const html = fs.readFileSync(INDEX_HTML, "utf8");
|
|
64
|
+
const rootRefs = [
|
|
65
|
+
...html.matchAll(/\b(?:href|src)="(\/[^"#?]+)"/g),
|
|
66
|
+
].map((match) => match[1]);
|
|
67
|
+
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
for (const ref of rootRefs) {
|
|
70
|
+
if (seen.has(ref) || ref.startsWith("//")) continue;
|
|
71
|
+
seen.add(ref);
|
|
72
|
+
|
|
73
|
+
const resolved = resolveRootAsset(ref);
|
|
74
|
+
if (!resolved) {
|
|
75
|
+
fail(
|
|
76
|
+
"static_assets",
|
|
77
|
+
`${ref} not found under apps/website/public/ or apps/website/ (Vite dev resolves root paths to project root)`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runBuild() {
|
|
84
|
+
if (skipBuild) return;
|
|
85
|
+
|
|
86
|
+
const proc = spawnSync(
|
|
87
|
+
"pnpm",
|
|
88
|
+
["--filter", "@ludecker/website", "build"],
|
|
89
|
+
{
|
|
90
|
+
cwd: REPO_ROOT,
|
|
91
|
+
encoding: "utf8",
|
|
92
|
+
env: { ...process.env, CI: "1" },
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (proc.status !== 0) {
|
|
97
|
+
const detail = [proc.stderr, proc.stdout].filter(Boolean).join("\n").trim();
|
|
98
|
+
results.build.status = "fail";
|
|
99
|
+
results.build.exit_code = proc.status ?? 1;
|
|
100
|
+
fail("build", detail || `exit ${proc.status}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
results.build.status = "pass";
|
|
105
|
+
results.build.exit_code = 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeArtifact() {
|
|
109
|
+
if (!runId) return;
|
|
110
|
+
|
|
111
|
+
const artifactDir = path.join(runDir(runId), "artifacts");
|
|
112
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
113
|
+
|
|
114
|
+
const yaml = [
|
|
115
|
+
`status: ${results.status}`,
|
|
116
|
+
`checked_at: ${results.checked_at}`,
|
|
117
|
+
"static_assets:",
|
|
118
|
+
` status: ${results.static_assets.status}`,
|
|
119
|
+
` missing: ${JSON.stringify(results.static_assets.missing)}`,
|
|
120
|
+
"build:",
|
|
121
|
+
` status: ${results.build.status}`,
|
|
122
|
+
` command: ${JSON.stringify(results.build.command)}`,
|
|
123
|
+
results.build.exit_code != null ? ` exit_code: ${results.build.exit_code}` : null,
|
|
124
|
+
results.build.detail ? ` detail: ${JSON.stringify(results.build.detail)}` : null,
|
|
125
|
+
]
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join("\n");
|
|
128
|
+
|
|
129
|
+
fs.writeFileSync(path.join(artifactDir, "verify.yaml"), `${yaml}\n`);
|
|
130
|
+
|
|
131
|
+
const manifestPath = path.join(runDir(runId), "run.json");
|
|
132
|
+
try {
|
|
133
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
134
|
+
manifest.artifacts = manifest.artifacts ?? {};
|
|
135
|
+
manifest.artifacts.verify = results;
|
|
136
|
+
manifest.updated_at = isoNow();
|
|
137
|
+
writeJson(manifestPath, manifest);
|
|
138
|
+
} catch {
|
|
139
|
+
// run.json may not exist in standalone invocations
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
checkStaticAssets();
|
|
144
|
+
runBuild();
|
|
145
|
+
writeArtifact();
|
|
146
|
+
|
|
147
|
+
console.log(JSON.stringify({ ok: results.status === "pass", ...results }));
|
|
148
|
+
process.exit(results.status === "pass" ? 0 : 1);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "Evidence-driven capability lifecycle promotion thresholds. State belongs to capability, not provider.",
|
|
4
|
+
"states": ["experimental", "validated", "trusted", "canonical", "deprecated"],
|
|
5
|
+
"default_state": "experimental",
|
|
6
|
+
"thresholds": {
|
|
7
|
+
"validated": {
|
|
8
|
+
"min_invocations": 10
|
|
9
|
+
},
|
|
10
|
+
"trusted": {
|
|
11
|
+
"min_invocations": 25,
|
|
12
|
+
"min_success_rate": 0.8,
|
|
13
|
+
"max_rollback_rate": 0.05,
|
|
14
|
+
"max_gate_failure_rate": 0.2
|
|
15
|
+
},
|
|
16
|
+
"canonical": {
|
|
17
|
+
"min_invocations": 100,
|
|
18
|
+
"min_success_rate": 0.95,
|
|
19
|
+
"max_rollback_rate": 0.01,
|
|
20
|
+
"max_gate_failure_rate": 0.1,
|
|
21
|
+
"manual_approval": true
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"demotion": {
|
|
25
|
+
"from_trusted": {
|
|
26
|
+
"min_invocations": 20,
|
|
27
|
+
"min_success_rate_below": 0.7
|
|
28
|
+
},
|
|
29
|
+
"to_deprecated": {
|
|
30
|
+
"manual_only": true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"fitness_scoring": {
|
|
34
|
+
"pass": 100,
|
|
35
|
+
"warning": 75,
|
|
36
|
+
"fail": 0
|
|
37
|
+
},
|
|
38
|
+
"runtime": {
|
|
39
|
+
"by_state": {
|
|
40
|
+
"experimental": {
|
|
41
|
+
"warn": true,
|
|
42
|
+
"require_approval_on": ["critical", "protected"]
|
|
43
|
+
},
|
|
44
|
+
"validated": {},
|
|
45
|
+
"trusted": {},
|
|
46
|
+
"canonical": {},
|
|
47
|
+
"deprecated": {
|
|
48
|
+
"block_execute": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"evidence_triggers": [
|
|
52
|
+
{
|
|
53
|
+
"min_invocations": 5,
|
|
54
|
+
"min_success_rate_below": 0.5,
|
|
55
|
+
"action": "require_approval"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"min_invocations": 10,
|
|
59
|
+
"min_avg_fitness_below": 60,
|
|
60
|
+
"action": "require_approval"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|