@ludecker/aaac 1.1.1 → 1.1.2

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/generators/generate-commands.mjs +17 -9
  3. package/src/run-engine/advance-phase.mjs +2 -2
  4. package/src/run-engine/gate-write.mjs +14 -2
  5. package/src/run-engine/init-run.mjs +41 -0
  6. package/src/run-engine/lib.mjs +33 -0
  7. package/src/run-engine/record-task.mjs +7 -1
  8. package/src/run-engine/stop-check.mjs +7 -1
  9. package/src/run-engine/verify-website-build.mjs +67 -30
  10. package/templates/cursor/aaac/capabilities/registry.json +14 -16
  11. package/templates/cursor/aaac/graph.project.yaml +4 -204
  12. package/templates/cursor/aaac/ontology.md +17 -32
  13. package/templates/cursor/aaac/project.config.json +4 -1
  14. package/templates/cursor/aaac/run/schema.json +3 -1
  15. package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +2 -2
  16. package/templates/cursor/aaac/scripts/run-engine/gate-write.mjs +14 -2
  17. package/templates/cursor/aaac/scripts/run-engine/init-run.mjs +41 -0
  18. package/templates/cursor/aaac/scripts/run-engine/lib.mjs +33 -0
  19. package/templates/cursor/aaac/scripts/run-engine/record-task.mjs +7 -1
  20. package/templates/cursor/aaac/scripts/run-engine/stop-check.mjs +7 -1
  21. package/templates/cursor/aaac/scripts/run-engine/verify-website-build.mjs +67 -30
  22. package/templates/cursor/agents/playwright-check-run.md +8 -26
  23. package/templates/cursor/agents/release-git.md +2 -2
  24. package/templates/cursor/agents/unit-test-run.md +3 -7
  25. package/templates/cursor/skills/shared/governance/implementation/SKILL.md +25 -396
  26. package/templates/cursor/skills/shared/testing/SKILL.md +2 -2
  27. package/templates/cursor/skills/shared/verbs/check/orchestrator/SKILL.md +1 -1
  28. package/templates/cursor/skills/shared/verification/SKILL.md +2 -2
  29. package/templates/docs/agentic_architecture.md +86 -166
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ludecker/aaac",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Agentic Architecture as Code (AAAC) — installable Cursor agent framework",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,10 +50,13 @@ function isFixCommand(cmd, entry) {
50
50
  function writeFixCmd(cmd, entry = null) {
51
51
  const canonical = entry?.alias ?? cmd;
52
52
  const isBug = canonical === "fix-bug" || cmd === "bug-fix";
53
- const resolver = isBug ? "fix-bug-by-slug" : "fix-domain-by-slug";
54
- const domainRequired = isBug
55
- ? "Domain slug **recommended** (`cms`, `ui`, `database`, `aaac`)."
56
- : "**Domain slug required** (`cms`, `ui`, `database`, `aaac`).";
53
+ const hasResolver = Boolean(entry?.resolver);
54
+ const domainLine = hasResolver
55
+ ? "Domain slug **recommended** when your project overlay defines domain resolvers (see `graph.project.yaml`)."
56
+ : "Domain slug optional.";
57
+ const dispatchLine = hasResolver
58
+ ? `3. resolver **\`${entry.resolver}\`** (project overlay in \`graph.project.yaml\`)`
59
+ : `3. [verb-fix orchestrator](../skills/shared/verbs/fix/orchestrator/SKILL.md) — object \`${isBug ? "feature" : "module"}\``;
57
60
  const aliasLine =
58
61
  cmd !== canonical
59
62
  ? `\n\n> Alias → \`/${canonical}\`. See [${canonical}.md](${canonical}.md).\n`
@@ -71,10 +74,10 @@ AAAC: \`/${cmd} <domain> "<intent>"\`
71
74
 
72
75
  1. [.cursor/aaac/dispatch.md](../aaac/dispatch.md)
73
76
  2. [.cursor/aaac/graph.yaml](../aaac/graph.yaml) — **\`${canonical}\`**
74
- 3. resolver **\`${resolver}\`** → \`cms-fix-bug\` | \`ui-fix-bug\` | \`database-fix-bug\` | \`aaac-fix-bug\`
75
- 4. [investigation/SKILL.md](../skills/shared/investigation/SKILL.md) Mode A + domain \`fix_mode\`
77
+ ${dispatchLine}
78
+ 4. [investigation/SKILL.md](../skills/shared/investigation/SKILL.md) Mode A${hasResolver ? " + domain `fix_mode` when resolver routes to a domain orchestrator" : ""}
76
79
 
77
- ${domainRequired}
80
+ ${domainLine}
78
81
 
79
82
  ## Swarm (mandatory)
80
83
 
@@ -90,8 +93,8 @@ Contract: [${canonical}.yaml](../aaac/contracts/commands/${canonical}.yaml)
90
93
  ## Example
91
94
 
92
95
  \`\`\`text
93
- /${cmd} cms "Getting Started nav missing published guides"
94
- /${cmd} ui "DocsNav section state lost on route change"
96
+ /${cmd} payments "Webhook handler drops events on retry"
97
+ /${cmd} api "Auth middleware returns 500 on expired token"
95
98
  \`\`\`
96
99
  `;
97
100
  fs.writeFileSync(path.join(commandsDir, `${cmd}.md`), body);
@@ -247,6 +250,11 @@ const written = new Set();
247
250
 
248
251
  for (const [cmd, entry] of Object.entries(command_overrides)) {
249
252
  if (KEEP_EXTRA.has(`${cmd}.md`)) continue;
253
+ if (isFixCommand(cmd, entry)) {
254
+ writeFixCmd(cmd, entry);
255
+ written.add(cmd);
256
+ continue;
257
+ }
250
258
  writeCmd(cmd, entry);
251
259
  written.add(cmd);
252
260
  }
@@ -102,7 +102,7 @@ if (
102
102
  manifest.updated_at = isoNow();
103
103
  writeJson(manifestPath, manifest);
104
104
  console.error(
105
- "Website verify failed (static assets + vite build). Fix errors, then re-run:\n" +
105
+ "App verify failed (see project.config.json verify). Fix errors, then re-run:\n" +
106
106
  ` node .cursor/aaac/scripts/run-engine/verify-website-build.mjs --run-id ${runId}\n` +
107
107
  detail,
108
108
  );
@@ -112,7 +112,7 @@ if (
112
112
  event: "verify_website_pass",
113
113
  phase: completedPhase,
114
114
  phase_kind: manifest.phase_kind,
115
- detail: "static assets + vite build",
115
+ detail: "app verify gate",
116
116
  level: "info",
117
117
  });
118
118
  }
@@ -53,10 +53,22 @@ process.stdin.on("end", () => {
53
53
  if (!conversationId) allow();
54
54
 
55
55
  const active = loadActiveRun(conversationId);
56
- if (!active?.run_id || active.status === "completed") allow();
56
+ if (
57
+ !active?.run_id ||
58
+ active.status === "completed" ||
59
+ active.status === "cancelled"
60
+ ) {
61
+ allow();
62
+ }
57
63
 
58
64
  const manifest = loadRunManifest(active.run_id);
59
- if (!manifest || manifest.status === "completed") allow();
65
+ if (
66
+ !manifest ||
67
+ manifest.status === "completed" ||
68
+ manifest.status === "cancelled"
69
+ ) {
70
+ allow();
71
+ }
60
72
  if (manifest.conversation_id && manifest.conversation_id !== conversationId) allow();
61
73
 
62
74
  const enforcement = loadEnforcement();
@@ -11,6 +11,11 @@ import {
11
11
  phaseKind,
12
12
  writeJson,
13
13
  saveActiveRun,
14
+ loadActiveRun,
15
+ loadRunManifest,
16
+ clearActiveRun,
17
+ cancelRunManifest,
18
+ isUserStopIntent,
14
19
  conversationIdFromHook,
15
20
  promptFromHook,
16
21
  } from "./lib.mjs";
@@ -41,6 +46,42 @@ try {
41
46
 
42
47
  const prompt = process.argv[2] ?? promptFromHook(hook);
43
48
  const conversationId = conversationIdFromHook(hook);
49
+
50
+ if (isUserStopIntent(prompt) && conversationId) {
51
+ const active = loadActiveRun(conversationId);
52
+ let cancelledRunId = null;
53
+ if (active?.run_id) {
54
+ const existing = loadRunManifest(active.run_id);
55
+ if (
56
+ existing &&
57
+ existing.status !== "completed" &&
58
+ existing.status !== "cancelled"
59
+ ) {
60
+ cancelRunManifest(existing, prompt.trim());
61
+ recordLog(existing, {
62
+ event: "run_cancelled",
63
+ phase: existing.phase,
64
+ phase_kind: existing.phase_kind,
65
+ detail: `user stop: ${prompt.trim()}`,
66
+ level: "info",
67
+ });
68
+ recordDecision(existing, {
69
+ phase: existing.phase ?? "dispatch",
70
+ decision: "user_stop",
71
+ reason: "User requested stop",
72
+ evidence: prompt.trim(),
73
+ });
74
+ writeJson(`${runDir(active.run_id)}/run.json`, existing);
75
+ cancelledRunId = active.run_id;
76
+ }
77
+ clearActiveRun(conversationId);
78
+ }
79
+ console.log(
80
+ JSON.stringify({ ok: true, aaac: false, cancelled: cancelledRunId }),
81
+ );
82
+ process.exit(0);
83
+ }
84
+
44
85
  const parsed = parseAaacPrompt(prompt);
45
86
 
46
87
  if (!parsed) {
@@ -139,3 +139,36 @@ export function phaseKind(phase, registry) {
139
139
  export function promptFromHook(hook) {
140
140
  return hook?.prompt ?? hook?.text ?? hook?.content ?? "";
141
141
  }
142
+
143
+ /** User explicitly asked to halt the current Run (short prompts only). */
144
+ export function isUserStopIntent(text) {
145
+ if (!text || typeof text !== "string") return false;
146
+ const trimmed = text.trim();
147
+ if (trimmed.length > 60) return false;
148
+ return (
149
+ /^(stop|cancel|abort)([.!?]*)$/i.test(trimmed) ||
150
+ /^(please\s+)?(stop|cancel|abort)([.!?]*)$/i.test(trimmed) ||
151
+ /^(stop|cancel|abort)\s+(the\s+)?run([.!?]*)$/i.test(trimmed)
152
+ );
153
+ }
154
+
155
+ export function cancelRunManifest(manifest, evidence = "user_stop") {
156
+ manifest.status = "cancelled";
157
+ manifest.awaiting_approval = false;
158
+ manifest.blocked_reason = null;
159
+ manifest.updated_at = isoNow();
160
+ if (manifest.enforcement) {
161
+ manifest.enforcement.edit_allowed = true;
162
+ }
163
+ return manifest;
164
+ }
165
+
166
+ export function clearActiveRun(conversationId) {
167
+ if (!conversationId) return;
168
+ const filePath = activeRunPath(conversationId);
169
+ try {
170
+ fs.unlinkSync(filePath);
171
+ } catch {
172
+ // already cleared
173
+ }
174
+ }
@@ -34,7 +34,13 @@ process.stdin.on("end", () => {
34
34
  if (!active?.run_id) allow();
35
35
 
36
36
  const manifest = loadRunManifest(active.run_id);
37
- if (!manifest || manifest.status === "completed") allow();
37
+ if (
38
+ !manifest ||
39
+ manifest.status === "completed" ||
40
+ manifest.status === "cancelled"
41
+ ) {
42
+ allow();
43
+ }
38
44
  if (manifest.conversation_id && manifest.conversation_id !== conversationId) allow();
39
45
 
40
46
  manifest.swarm = manifest.swarm ?? {};
@@ -28,7 +28,13 @@ process.stdin.on("end", () => {
28
28
  if (!active?.run_id) process.exit(0);
29
29
 
30
30
  const manifest = loadRunManifest(active.run_id);
31
- if (!manifest || manifest.status === "completed") process.exit(0);
31
+ if (
32
+ !manifest ||
33
+ manifest.status === "completed" ||
34
+ manifest.status === "cancelled"
35
+ ) {
36
+ process.exit(0);
37
+ }
32
38
 
33
39
  const remaining = [manifest.phase, ...(manifest.pending ?? [])].filter(Boolean);
34
40
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Verify website static assets + production build.
4
- * Used by advance-phase on create/update/fix verify completion.
3
+ * Verify app static assets + production build (project overlay).
4
+ * Skips when `.cursor/aaac/project.config.json` has `verify.enabled: false`.
5
5
  *
6
6
  * Usage:
7
7
  * node verify-website-build.mjs [--run-id <run_id>] [--skip-build]
@@ -9,23 +9,42 @@
9
9
  import fs from "fs";
10
10
  import path from "path";
11
11
  import { spawnSync } from "child_process";
12
- import { fileURLToPath } from "url";
13
- import { REPO_ROOT, runDir, isoNow, writeJson } from "./lib.mjs";
12
+ import { runDir, isoNow, writeJson, readJson } from "./lib.mjs";
14
13
 
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");
14
+ const PROJECT_ROOT = process.cwd();
18
15
 
19
16
  const args = process.argv.slice(2);
20
17
  const runIdIdx = args.indexOf("--run-id");
21
18
  const runId = runIdIdx >= 0 ? args[runIdIdx + 1] : null;
22
19
  const skipBuild = args.includes("--skip-build");
23
20
 
21
+ function loadVerifyConfig() {
22
+ const configPath = path.join(PROJECT_ROOT, ".cursor/aaac/project.config.json");
23
+ const config = readJson(configPath, { verify: { enabled: false } });
24
+ const verify = config.verify ?? { enabled: false };
25
+ if (!verify.enabled) {
26
+ return { enabled: false };
27
+ }
28
+ const appRootRel = verify.app_root ?? "apps/website";
29
+ const indexRel =
30
+ verify.index_html ?? path.join(appRootRel, "index.html").replace(/\\/g, "/");
31
+ const appRoot = path.join(PROJECT_ROOT, appRootRel);
32
+ const indexHtml = path.join(PROJECT_ROOT, indexRel);
33
+ const build = verify.build ?? { command: "pnpm", args: ["run", "build"] };
34
+ return { enabled: true, appRoot, indexHtml, build, appRootRel };
35
+ }
36
+
37
+ const verifyConfig = loadVerifyConfig();
38
+
24
39
  const results = {
25
40
  status: "pass",
26
41
  checked_at: isoNow(),
27
42
  static_assets: { status: "pass", missing: [] },
28
- build: { status: skipBuild ? "skipped" : "pending", command: "pnpm --filter @ludecker/website build" },
43
+ build: {
44
+ status: skipBuild ? "skipped" : "pending",
45
+ command: null,
46
+ },
47
+ verify_config: verifyConfig.enabled ? "enabled" : "disabled",
29
48
  };
30
49
 
31
50
  function fail(section, detail) {
@@ -40,11 +59,11 @@ function fail(section, detail) {
40
59
  console.error(`[verify-website-build] FAIL ${section}: ${detail}`);
41
60
  }
42
61
 
43
- function resolveRootAsset(assetPath) {
62
+ function resolveRootAsset(assetPath, websiteRoot, appRootRel) {
44
63
  const rel = assetPath.replace(/^\//, "");
45
64
  const candidates = [
46
- path.join(WEBSITE_ROOT, "public", rel),
47
- path.join(WEBSITE_ROOT, rel),
65
+ path.join(websiteRoot, "public", rel),
66
+ path.join(websiteRoot, rel),
48
67
  ];
49
68
  for (const candidate of candidates) {
50
69
  if (fs.existsSync(candidate)) {
@@ -54,13 +73,13 @@ function resolveRootAsset(assetPath) {
54
73
  return null;
55
74
  }
56
75
 
57
- function checkStaticAssets() {
58
- if (!fs.existsSync(INDEX_HTML)) {
59
- fail("static_assets", `missing index.html at ${INDEX_HTML}`);
76
+ function checkStaticAssets(indexHtml, websiteRoot, appRootRel) {
77
+ if (!fs.existsSync(indexHtml)) {
78
+ fail("static_assets", `missing index.html at ${indexHtml}`);
60
79
  return;
61
80
  }
62
81
 
63
- const html = fs.readFileSync(INDEX_HTML, "utf8");
82
+ const html = fs.readFileSync(indexHtml, "utf8");
64
83
  const rootRefs = [
65
84
  ...html.matchAll(/\b(?:href|src)="(\/[^"#?]+)"/g),
66
85
  ].map((match) => match[1]);
@@ -70,28 +89,28 @@ function checkStaticAssets() {
70
89
  if (seen.has(ref) || ref.startsWith("//")) continue;
71
90
  seen.add(ref);
72
91
 
73
- const resolved = resolveRootAsset(ref);
92
+ const resolved = resolveRootAsset(ref, websiteRoot, appRootRel);
74
93
  if (!resolved) {
75
94
  fail(
76
95
  "static_assets",
77
- `${ref} not found under apps/website/public/ or apps/website/ (Vite dev resolves root paths to project root)`,
96
+ `${ref} not found under ${appRootRel}/public/ or ${appRootRel}/`,
78
97
  );
79
98
  }
80
99
  }
81
100
  }
82
101
 
83
- function runBuild() {
102
+ function runBuild(build) {
84
103
  if (skipBuild) return;
85
104
 
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
- );
105
+ const command = build.command ?? "pnpm";
106
+ const buildArgs = build.args ?? ["run", "build"];
107
+ results.build.command = [command, ...buildArgs].join(" ");
108
+
109
+ const proc = spawnSync(command, buildArgs, {
110
+ cwd: build.cwd ? path.join(PROJECT_ROOT, build.cwd) : PROJECT_ROOT,
111
+ encoding: "utf8",
112
+ env: { ...process.env, CI: "1" },
113
+ });
95
114
 
96
115
  if (proc.status !== 0) {
97
116
  const detail = [proc.stderr, proc.stdout].filter(Boolean).join("\n").trim();
@@ -114,12 +133,13 @@ function writeArtifact() {
114
133
  const yaml = [
115
134
  `status: ${results.status}`,
116
135
  `checked_at: ${results.checked_at}`,
136
+ `verify_config: ${results.verify_config}`,
117
137
  "static_assets:",
118
138
  ` status: ${results.static_assets.status}`,
119
139
  ` missing: ${JSON.stringify(results.static_assets.missing)}`,
120
140
  "build:",
121
141
  ` status: ${results.build.status}`,
122
- ` command: ${JSON.stringify(results.build.command)}`,
142
+ results.build.command ? ` command: ${JSON.stringify(results.build.command)}` : null,
123
143
  results.build.exit_code != null ? ` exit_code: ${results.build.exit_code}` : null,
124
144
  results.build.detail ? ` detail: ${JSON.stringify(results.build.detail)}` : null,
125
145
  ]
@@ -140,8 +160,25 @@ function writeArtifact() {
140
160
  }
141
161
  }
142
162
 
143
- checkStaticAssets();
144
- runBuild();
163
+ if (!verifyConfig.enabled) {
164
+ results.static_assets.status = "skipped";
165
+ results.build.status = "skipped";
166
+ results.build.command = null;
167
+ writeArtifact();
168
+ console.log(JSON.stringify({ ok: true, skipped: true, reason: "verify.disabled", ...results }));
169
+ process.exit(0);
170
+ }
171
+
172
+ results.build.command = skipBuild
173
+ ? null
174
+ : [verifyConfig.build.command, ...(verifyConfig.build.args ?? [])].join(" ");
175
+
176
+ checkStaticAssets(
177
+ verifyConfig.indexHtml,
178
+ verifyConfig.appRoot,
179
+ verifyConfig.appRootRel,
180
+ );
181
+ runBuild(verifyConfig.build);
145
182
  writeArtifact();
146
183
 
147
184
  console.log(JSON.stringify({ ok: results.status === "pass", ...results }));
@@ -2,44 +2,43 @@
2
2
  "version": 2,
3
3
  "capabilities": {
4
4
  "ui-design": {
5
- "description": "Design tokens, component CSS, Figma alignment",
5
+ "description": "Presentational components, token-based styling",
6
6
  "providers": [
7
- { "id": "ludecker-design-system", "type": "skill", "path": "skills/ludecker/design-system" }
7
+ { "id": "component", "type": "skill", "path": "skills/shared/component" }
8
8
  ]
9
9
  },
10
10
  "ux-design": {
11
- "description": "Editorial readability, publish flow, navigation clarity",
11
+ "description": "Editorial readability, navigation clarity",
12
12
  "providers": [
13
- { "id": "ludecker-user-experience", "type": "skill", "path": "skills/ludecker/user-experience" }
13
+ { "id": "documentation", "type": "skill", "path": "skills/shared/documentation" }
14
14
  ]
15
15
  },
16
16
  "api-design": {
17
17
  "description": "Contracts and validation at boundaries",
18
18
  "providers": [
19
- { "id": "ludecker-api-first", "type": "skill", "path": "skills/ludecker/api-first" }
19
+ { "id": "integration", "type": "skill", "path": "skills/shared/integration" }
20
20
  ]
21
21
  },
22
22
  "database-design": {
23
- "description": "Schema, migrations, RLS, type mirrors",
23
+ "description": "Schema, migrations, persistence boundaries",
24
24
  "providers": [
25
- { "id": "ludecker-database-schema", "type": "skill", "path": "skills/ludecker/database-schema" },
26
- { "id": "supabase-mcp", "type": "mcp", "optional": true, "note": "Apply migrations and RLS via Supabase MCP" }
25
+ { "id": "schema", "type": "skill", "path": "skills/shared/schema" }
27
26
  ]
28
27
  },
29
28
  "security": {
30
- "description": "Auth, RLS, secrets, CMS gates",
29
+ "description": "Auth, secrets, access control at boundaries",
31
30
  "providers": [
32
- { "id": "ludecker-security", "type": "skill", "path": "skills/ludecker/security" }
31
+ { "id": "architecture", "type": "skill", "path": "skills/shared/architecture" }
33
32
  ]
34
33
  },
35
34
  "infrastructure": {
36
- "description": "Deploy, Render, env, hosting",
35
+ "description": "Deploy, hosting, environment configuration",
37
36
  "providers": [
38
- { "id": "ludecker-infrastructure", "type": "skill", "path": "skills/ludecker/infrastructure" }
37
+ { "id": "integration", "type": "skill", "path": "skills/shared/integration" }
39
38
  ]
40
39
  },
41
40
  "layer-boundaries": {
42
- "description": "SSOT, import direction, monorepo layers",
41
+ "description": "SSOT, import direction, module layers",
43
42
  "providers": [
44
43
  { "id": "architecture", "type": "skill", "path": "skills/shared/architecture" }
45
44
  ]
@@ -65,8 +64,7 @@
65
64
  "migration-model": {
66
65
  "description": "Migration scripts and apply procedure",
67
66
  "providers": [
68
- { "id": "migration", "type": "skill", "path": "skills/shared/migration" },
69
- { "id": "supabase-mcp", "type": "mcp", "optional": true }
67
+ { "id": "migration", "type": "skill", "path": "skills/shared/migration" }
70
68
  ]
71
69
  },
72
70
  "workflow-model": {
@@ -76,7 +74,7 @@
76
74
  ]
77
75
  },
78
76
  "integration-model": {
79
- "description": "API routes, webhooks, MCP adapters",
77
+ "description": "API routes, webhooks, external adapters",
80
78
  "providers": [
81
79
  { "id": "integration", "type": "skill", "path": "skills/shared/integration" }
82
80
  ]