@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.
Files changed (92) hide show
  1. package/README.md +4 -3
  2. package/package.json +13 -1
  3. package/src/cli.mjs +39 -5
  4. package/src/generators/generate-commands.mjs +120 -3
  5. package/src/generators/generate-graph.mjs +17 -0
  6. package/src/lib/install.mjs +1 -0
  7. package/src/lib/run-engine-paths.mjs +33 -0
  8. package/src/run-engine/advance-phase.mjs +343 -0
  9. package/src/run-engine/capability-evidence.mjs +460 -0
  10. package/src/run-engine/debug-run.mjs +38 -0
  11. package/src/run-engine/gate-write.mjs +95 -0
  12. package/src/run-engine/init-run.mjs +215 -0
  13. package/src/run-engine/lib.mjs +141 -0
  14. package/src/run-engine/log-dump.mjs +76 -0
  15. package/src/run-engine/log-trace.mjs +18 -0
  16. package/src/run-engine/log.mjs +343 -0
  17. package/src/run-engine/record-task.mjs +56 -0
  18. package/src/run-engine/stop-check.mjs +55 -0
  19. package/src/run-engine/verify-website-build.mjs +148 -0
  20. package/templates/cursor/aaac/capabilities/promotion-rules.json +64 -0
  21. package/templates/cursor/aaac/capabilities/registry.json +17 -15
  22. package/templates/cursor/aaac/complexity.yaml +98 -0
  23. package/templates/cursor/aaac/contracts/commands/fix-bug.yaml +10 -3
  24. package/templates/cursor/aaac/contracts/commands/fix-module.yaml +41 -0
  25. package/templates/cursor/aaac/contracts/skills/investigation.yaml +22 -1
  26. package/templates/cursor/aaac/contracts/skills/planning.yaml +17 -0
  27. package/templates/cursor/aaac/contracts/skills/validation.yaml +9 -1
  28. package/templates/cursor/aaac/dispatch.md +31 -6
  29. package/templates/cursor/aaac/enforcement.json +25 -0
  30. package/templates/cursor/aaac/fitness-functions.yaml +8 -0
  31. package/templates/cursor/aaac/governance/gates.json +6 -2
  32. package/templates/cursor/aaac/graph.project.yaml +237 -5
  33. package/templates/cursor/aaac/layers.md +6 -1
  34. package/templates/cursor/aaac/lifecycle/lifecycle.json +41 -1
  35. package/templates/cursor/aaac/lifecycle/phases.json +1 -0
  36. package/templates/cursor/aaac/observability/telemetry.yaml +63 -0
  37. package/templates/cursor/aaac/observability/verb-debug.yaml +170 -0
  38. package/templates/cursor/aaac/ontology.json +10 -1
  39. package/templates/cursor/aaac/run/RUN.md +2 -0
  40. package/templates/cursor/aaac/run/schema.json +11 -0
  41. package/templates/cursor/aaac/scripts/generate-runtime-registry.mjs +115 -0
  42. package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +343 -0
  43. package/templates/cursor/aaac/scripts/run-engine/capability-evidence.mjs +460 -0
  44. package/templates/cursor/aaac/scripts/run-engine/debug-run.mjs +38 -0
  45. package/templates/cursor/aaac/scripts/run-engine/gate-write.mjs +95 -0
  46. package/templates/cursor/aaac/scripts/run-engine/init-run.mjs +215 -0
  47. package/templates/cursor/aaac/scripts/run-engine/lib.mjs +141 -0
  48. package/templates/cursor/aaac/scripts/run-engine/log-dump.mjs +76 -0
  49. package/templates/cursor/aaac/scripts/run-engine/log-trace.mjs +18 -0
  50. package/templates/cursor/aaac/scripts/run-engine/log.mjs +343 -0
  51. package/templates/cursor/aaac/scripts/run-engine/record-task.mjs +56 -0
  52. package/templates/cursor/aaac/scripts/run-engine/stop-check.mjs +55 -0
  53. package/templates/cursor/aaac/scripts/run-engine/verify-website-build.mjs +148 -0
  54. package/templates/cursor/aaac/state/capability-stats.json +5 -0
  55. package/templates/cursor/agents/aaac-log-debug.md +72 -0
  56. package/templates/cursor/agents/fix-code-path.md +27 -0
  57. package/templates/cursor/agents/fix-hypothesis-validate.md +26 -0
  58. package/templates/cursor/agents/fix-inventory-confirm.md +22 -0
  59. package/templates/cursor/agents/fix-recent-changes.md +22 -0
  60. package/templates/cursor/agents/fix-regression-scope.md +27 -0
  61. package/templates/cursor/agents/fix-repro-verify.md +21 -0
  62. package/templates/cursor/agents/fix-repro.md +29 -0
  63. package/templates/cursor/agents/fix-runtime-evidence.md +22 -0
  64. package/templates/cursor/agents/fix-test-failures.md +23 -0
  65. package/templates/cursor/agents/playwright-check-run.md +44 -0
  66. package/templates/cursor/hooks/aaac-before-submit.sh +3 -0
  67. package/templates/cursor/hooks/aaac-pre-tool.sh +4 -0
  68. package/templates/cursor/hooks/aaac-stop.sh +3 -0
  69. package/templates/cursor/hooks/aaac-subagent-start.sh +3 -0
  70. package/templates/cursor/hooks.json +30 -0
  71. package/templates/cursor/policies/minimal-complexity.md +101 -0
  72. package/templates/cursor/rules/aaac-enforcement.mdc +42 -0
  73. package/templates/cursor/skills/shared/execution/SKILL.md +1 -1
  74. package/templates/cursor/skills/shared/fitness-functions/SKILL.md +23 -7
  75. package/templates/cursor/skills/shared/investigation/SKILL.md +91 -18
  76. package/templates/cursor/skills/shared/investigation/orchestrator/SKILL.md +12 -4
  77. package/templates/cursor/skills/shared/planning/SKILL.md +74 -8
  78. package/templates/cursor/skills/shared/platform-release/SKILL.md +22 -19
  79. package/templates/cursor/skills/shared/platform-release/orchestrator/contract.yaml +27 -7
  80. package/templates/cursor/skills/shared/reporting/SKILL.md +2 -1
  81. package/templates/cursor/skills/shared/root-cause/SKILL.md +14 -3
  82. package/templates/cursor/skills/shared/testing/SKILL.md +31 -5
  83. package/templates/cursor/skills/shared/validation/SKILL.md +48 -13
  84. package/templates/cursor/skills/shared/verbs/_dispatch-utils.md +20 -1
  85. package/templates/cursor/skills/shared/verbs/_lifecycle.md +3 -2
  86. package/templates/cursor/skills/shared/verbs/check/orchestrator/SKILL.md +4 -1
  87. package/templates/cursor/skills/shared/verbs/create/orchestrator/SKILL.md +2 -2
  88. package/templates/cursor/skills/shared/verbs/fix/orchestrator/SKILL.md +21 -11
  89. package/templates/cursor/skills/shared/verbs/fix/orchestrator/contract.yaml +19 -4
  90. package/templates/cursor/skills/shared/verbs/update/orchestrator/SKILL.md +2 -2
  91. package/templates/cursor/skills/shared/verification/SKILL.md +3 -0
  92. 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,5 @@
1
+ {
2
+ "version": 1,
3
+ "updated_at": null,
4
+ "capabilities": {}
5
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: aaac-log-debug
3
+ description: Debug blocked or failed AAAC create/update/fix/check Runs using manifest log tools.
4
+ ---
5
+
6
+ # AAAC Log Debug Agent
7
+
8
+ Use when a `/create-*`, `/update-*`, `/fix-*`, or `/check-*` Run is blocked, incomplete, or behaving unexpectedly.
9
+
10
+ ## SSOT
11
+
12
+ - Run manifest: `.cursor/aaac/state/runs/{run_id}/run.json`
13
+ - Telemetry events: `.cursor/aaac/observability/telemetry.yaml`
14
+ - Verb profiles: `.cursor/aaac/observability/verb-debug.yaml`
15
+
16
+ All observability lives on the Run manifest — never create standalone markdown debug logs.
17
+
18
+ ## Quick triage
19
+
20
+ 1. Find the run id from the chat hook message or `.cursor/aaac/state/active-runs/{conversation_id}.json`.
21
+ 2. One-shot status:
22
+
23
+ ```bash
24
+ node .cursor/aaac/scripts/run-engine/debug-run.mjs <run_id>
25
+ ```
26
+
27
+ 3. Full timeline:
28
+
29
+ ```bash
30
+ node .cursor/aaac/scripts/run-engine/log-dump.mjs <run_id> --format pretty
31
+ ```
32
+
33
+ 4. Answer "why did it do X?":
34
+
35
+ ```bash
36
+ node .cursor/aaac/scripts/run-engine/log-trace.mjs <run_id>
37
+ ```
38
+
39
+ Or via npm CLI when installed:
40
+
41
+ ```bash
42
+ aaac debug-run <run_id>
43
+ aaac log-dump <run_id> --format timeline
44
+ ```
45
+
46
+ ## What to look for
47
+
48
+ | Symptom | Log events | Action |
49
+ |---------|------------|--------|
50
+ | Edits denied | `edit_denied` | Advance to `execute` phase first |
51
+ | Swarm blocked | `gate_fail` + `swarm_count` | Launch more Task subagents; check verb-debug swarm_minimums |
52
+ | Missing artifact | `gate_fail` + `missing artifact` | Write required file under `artifacts/` |
53
+ | Stuck at gate | `gate_blocked`, `awaiting_approval` | Complete gate skill; user approval if blocked |
54
+ | Wrong route | `decisions[]`, `graph_resolved` | Check orchestrator in registry |
55
+
56
+ ## Verb-specific checks
57
+
58
+ - **create / update**: `discover` needs 4 agents; phases `investigate_lite` → `plan` → gates → `execute`.
59
+ - **fix**: `investigate_swarm` needs 7 agents; `verify` needs 3 (`verify_fix`); `root_cause` artifact required.
60
+ - **check** (readonly): `discover` needs 4 run-engine agents + 3 explore agents in `check_swarm`; no `execute` — `edit_denied` is expected for all code paths.
61
+
62
+ ## Environment
63
+
64
+ Set `LOG_LEVEL=debug` when running run-engine scripts locally for stderr structured output:
65
+
66
+ ```
67
+ [level] [run:phase:event] detail {"run_id":"..."}
68
+ ```
69
+
70
+ ## Report back
71
+
72
+ Summarize: run_id, phase, blocked_reason, swarm counts vs minimums, last 3 log events, and recommended next command (`advance-phase.mjs` or spawn agents).