@ludecker/aaac 1.1.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 (25) hide show
  1. package/package.json +1 -1
  2. package/src/run-engine/advance-phase.mjs +152 -1
  3. package/src/run-engine/capability-evidence.mjs +460 -0
  4. package/src/run-engine/init-run.mjs +51 -1
  5. package/src/run-engine/lib.mjs +5 -0
  6. package/src/run-engine/verify-website-build.mjs +148 -0
  7. package/templates/cursor/aaac/capabilities/promotion-rules.json +64 -0
  8. package/templates/cursor/aaac/capabilities/registry.json +17 -15
  9. package/templates/cursor/aaac/dispatch.md +2 -2
  10. package/templates/cursor/aaac/enforcement.json +6 -3
  11. package/templates/cursor/aaac/governance/gates.json +3 -1
  12. package/templates/cursor/aaac/layers.md +3 -0
  13. package/templates/cursor/aaac/observability/telemetry.yaml +3 -0
  14. package/templates/cursor/aaac/run/schema.json +2 -0
  15. package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +152 -1
  16. package/templates/cursor/aaac/scripts/run-engine/capability-evidence.mjs +460 -0
  17. package/templates/cursor/aaac/scripts/run-engine/init-run.mjs +51 -1
  18. package/templates/cursor/aaac/scripts/run-engine/lib.mjs +5 -0
  19. package/templates/cursor/aaac/scripts/run-engine/verify-website-build.mjs +148 -0
  20. package/templates/cursor/aaac/state/capability-stats.json +5 -0
  21. package/templates/cursor/skills/shared/platform-release/SKILL.md +22 -19
  22. package/templates/cursor/skills/shared/platform-release/orchestrator/contract.yaml +27 -7
  23. package/templates/cursor/skills/shared/testing/SKILL.md +5 -0
  24. package/templates/cursor/skills/shared/verification/SKILL.md +1 -0
  25. package/templates/docs/agentic_architecture.md +236 -53
@@ -15,6 +15,11 @@ import {
15
15
  promptFromHook,
16
16
  } from "./lib.mjs";
17
17
  import { recordLog, recordDecision } from "./log.mjs";
18
+ import {
19
+ resolveCapabilitiesWithRuntime,
20
+ evaluateCapabilityRuntimePolicy,
21
+ loadObjectMaturity,
22
+ } from "./capability-evidence.mjs";
18
23
 
19
24
  async function readStdin() {
20
25
  return new Promise((resolve) => {
@@ -64,6 +69,14 @@ const runId = `run_${date}_${slugify(parsed.command + (parsed.domain ? `-${parse
64
69
  const entry = registry.commands[parsed.command];
65
70
  fs.mkdirSync(runDir(runId), { recursive: true });
66
71
 
72
+ const runObject = entry.object ?? null;
73
+ const runVerb = entry.verb ?? parsed.command.split("-")[0];
74
+ const objectMaturity = loadObjectMaturity(runObject);
75
+ const capabilitiesResolved = resolveCapabilitiesWithRuntime(runObject, runVerb);
76
+ const capabilityRuntimePolicy = evaluateCapabilityRuntimePolicy(capabilitiesResolved, {
77
+ object_maturity: objectMaturity,
78
+ });
79
+
67
80
  const manifest = {
68
81
  run_id: runId,
69
82
  conversation_id: conversationId,
@@ -84,7 +97,9 @@ const manifest = {
84
97
  artifacts: {},
85
98
  checkpoints: [],
86
99
  log: [],
87
- capabilities_resolved: {},
100
+ capabilities_resolved: capabilitiesResolved,
101
+ capability_runtime: capabilityRuntimePolicy,
102
+ capability_runtime_approved: false,
88
103
  confidence: { architecture: null, requirements: null, scope: null },
89
104
  gates: { stack: entry.gate_stack ?? null, results: {} },
90
105
  swarm: { task_launches_this_phase: 0, phase: pending[0] },
@@ -131,6 +146,41 @@ recordDecision(manifest, {
131
146
  evidence: parsed.raw,
132
147
  });
133
148
 
149
+ for (const [capabilityId, resolution] of Object.entries(manifest.capabilities_resolved)) {
150
+ recordLog(manifest, {
151
+ event: "capability_resolved",
152
+ phase: "dispatch",
153
+ phase_kind: "work",
154
+ detail: `${capabilityId}:${(resolution.providers ?? []).map((p) => p.id).join(",")} state=${resolution.runtime?.state ?? "experimental"}`,
155
+ level: "debug",
156
+ });
157
+ }
158
+
159
+ recordLog(manifest, {
160
+ event: "capability_runtime_evaluated",
161
+ phase: "dispatch",
162
+ phase_kind: "work",
163
+ detail: `action=${capabilityRuntimePolicy.action} maturity=${objectMaturity}`,
164
+ level: "info",
165
+ });
166
+
167
+ if (capabilityRuntimePolicy.action === "warn") {
168
+ recordLog(manifest, {
169
+ event: "capability_runtime_warn",
170
+ phase: "dispatch",
171
+ phase_kind: "work",
172
+ detail: capabilityRuntimePolicy.reasons.join("; "),
173
+ level: "warn",
174
+ });
175
+ }
176
+
177
+ recordDecision(manifest, {
178
+ phase: "dispatch",
179
+ decision: "capability_runtime",
180
+ reason: capabilityRuntimePolicy.action,
181
+ evidence: capabilityRuntimePolicy.reasons.join("; ") || "allow",
182
+ });
183
+
134
184
  recordLog(manifest, {
135
185
  event: "phase_start",
136
186
  phase: pending[0],
@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
5
5
 
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  export const CURSOR_ROOT = path.resolve(__dirname, "../../..");
8
+ export const REPO_ROOT = path.resolve(CURSOR_ROOT, "..");
8
9
  export const AAAC_ROOT = path.join(CURSOR_ROOT, "aaac");
9
10
  export const STATE_ROOT = path.join(AAAC_ROOT, "state");
10
11
  export const RUNS_ROOT = path.join(STATE_ROOT, "runs");
@@ -12,6 +13,10 @@ export const ACTIVE_RUN_PATH = path.join(STATE_ROOT, "active-run.json");
12
13
  export const ACTIVE_RUNS_DIR = path.join(STATE_ROOT, "active-runs");
13
14
  export const REGISTRY_PATH = path.join(AAAC_ROOT, "runtime-registry.json");
14
15
  export const ENFORCEMENT_PATH = path.join(AAAC_ROOT, "enforcement.json");
16
+ export const ONTOLOGY_PATH = path.join(AAAC_ROOT, "ontology.json");
17
+ export const CAPABILITY_REGISTRY_PATH = path.join(AAAC_ROOT, "capabilities", "registry.json");
18
+ export const PROMOTION_RULES_PATH = path.join(AAAC_ROOT, "capabilities", "promotion-rules.json");
19
+ export const CAPABILITY_STATS_PATH = path.join(STATE_ROOT, "capability-stats.json");
15
20
 
16
21
  export function readJson(filePath, fallback = null) {
17
22
  try {
@@ -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
+ }
@@ -2,44 +2,44 @@
2
2
  "version": 2,
3
3
  "capabilities": {
4
4
  "ui-design": {
5
- "description": "Design tokens, component CSS, presentational UI",
5
+ "description": "Design tokens, component CSS, Figma alignment",
6
6
  "providers": [
7
- { "id": "component", "type": "skill", "path": "skills/shared/component" }
7
+ { "id": "ludecker-design-system", "type": "skill", "path": "skills/ludecker/design-system" }
8
8
  ]
9
9
  },
10
10
  "ux-design": {
11
- "description": "User flows, readability, navigation clarity",
11
+ "description": "Editorial readability, publish flow, navigation clarity",
12
12
  "providers": [
13
- { "id": "workflow", "type": "skill", "path": "skills/shared/workflow" }
13
+ { "id": "ludecker-user-experience", "type": "skill", "path": "skills/ludecker/user-experience" }
14
14
  ]
15
15
  },
16
16
  "api-design": {
17
17
  "description": "Contracts and validation at boundaries",
18
18
  "providers": [
19
- { "id": "integration", "type": "skill", "path": "skills/shared/integration" }
19
+ { "id": "ludecker-api-first", "type": "skill", "path": "skills/ludecker/api-first" }
20
20
  ]
21
21
  },
22
22
  "database-design": {
23
- "description": "Schema, migrations, persistence contracts",
23
+ "description": "Schema, migrations, RLS, type mirrors",
24
24
  "providers": [
25
- { "id": "schema", "type": "skill", "path": "skills/shared/schema" },
26
- { "id": "migration", "type": "skill", "path": "skills/shared/migration" }
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" }
27
27
  ]
28
28
  },
29
29
  "security": {
30
- "description": "Auth, secrets, access control — extend with project skills",
30
+ "description": "Auth, RLS, secrets, CMS gates",
31
31
  "providers": [
32
- { "id": "architecture", "type": "skill", "path": "skills/shared/architecture" }
32
+ { "id": "ludecker-security", "type": "skill", "path": "skills/ludecker/security" }
33
33
  ]
34
34
  },
35
35
  "infrastructure": {
36
- "description": "Deploy, hosting, environment",
36
+ "description": "Deploy, Render, env, hosting",
37
37
  "providers": [
38
- { "id": "platform-release", "type": "skill", "path": "skills/shared/platform-release" }
38
+ { "id": "ludecker-infrastructure", "type": "skill", "path": "skills/ludecker/infrastructure" }
39
39
  ]
40
40
  },
41
41
  "layer-boundaries": {
42
- "description": "SSOT, import direction, module layers",
42
+ "description": "SSOT, import direction, monorepo layers",
43
43
  "providers": [
44
44
  { "id": "architecture", "type": "skill", "path": "skills/shared/architecture" }
45
45
  ]
@@ -65,7 +65,8 @@
65
65
  "migration-model": {
66
66
  "description": "Migration scripts and apply procedure",
67
67
  "providers": [
68
- { "id": "migration", "type": "skill", "path": "skills/shared/migration" }
68
+ { "id": "migration", "type": "skill", "path": "skills/shared/migration" },
69
+ { "id": "supabase-mcp", "type": "mcp", "optional": true }
69
70
  ]
70
71
  },
71
72
  "workflow-model": {
@@ -101,6 +102,7 @@
101
102
  },
102
103
  "resolution": {
103
104
  "graph_skill_keys": "providers where type=skill → id maps to graph skills key",
104
- "run_record": "all providers including type=mcp recorded on Run.capabilities_resolved and decisions"
105
+ "run_record": "all providers including type=mcp recorded on Run.capabilities_resolved and decisions",
106
+ "lifecycle": "cross-run state in state/capability-stats.json; promotion thresholds in promotion-rules.json; updated by capability-evidence.mjs after each completed Run"
105
107
  }
106
108
  }
@@ -51,7 +51,7 @@ Read [graph.yaml](graph.yaml) and [ontology.json](ontology.json).
51
51
  - **Lifecycle (work):** [lifecycle/lifecycle.json](lifecycle/lifecycle.json) `verbs.*.work_phases`
52
52
  - **Gates (approval):** [governance/gates.json](governance/gates.json) — composed into runtime per `verb_runtime` in graph
53
53
  - **Maturity:** read `object_maturity.<object>` and apply `maturity_rules.<level>` (may require extra gate phases)
54
- - **Capabilities:** resolve `object_capabilities.<object>` via [capabilities/registry.json](capabilities/registry.json) — record all providers (skill + mcp) on Run
54
+ - **Capabilities:** resolve `object_capabilities.<object>` via [capabilities/registry.json](capabilities/registry.json) — `init-run.mjs` records providers on `Run.capabilities_resolved`; on completion `capability-evidence.mjs` aggregates evidence into [state/capability-stats.json](state/capability-stats.json) and evaluates [capabilities/promotion-rules.json](capabilities/promotion-rules.json)
55
55
  - **Dependencies:** [dependencies.yaml](dependencies.yaml)
56
56
  - **Fitness:** [fitness-functions.yaml](fitness-functions.yaml) — includes `minimal_complexity` for create/update/fix
57
57
  - **Complexity:** [complexity.yaml](complexity.yaml) + [minimal-complexity.md](../policies/minimal-complexity.md) for create/update/fix
@@ -144,7 +144,7 @@ Do **not** proceed until user approves in chat. On approval: log decision, set `
144
144
  1. **discover** — 4–6 parallel Task agents per [discovery/SKILL.md](../skills/shared/discovery/SKILL.md)
145
145
  2. **investigate_swarm** — 7 parallel Task agents per investigation Mode A — **one message**
146
146
  3. **root_cause** — artifact required; confidence ≥ 0.7 before plan
147
- 4. **verify** — fix verify swarm (3 parallel) per [testing/SKILL.md](../skills/shared/testing/SKILL.md); fail if `repro_status: not_fixed`
147
+ 4. **verify** — fix verify swarm (3 parallel) per [testing/SKILL.md](../skills/shared/testing/SKILL.md); **website build gate** (`verify-website-build.mjs`) must pass for create/update/fix; fail if `repro_status: not_fixed`
148
148
 
149
149
  Skipping swarms because the issue "looks simple" is a **contract violation** for `fix-module` / `fix-bug` / `fix_mode`.
150
150
 
@@ -1,8 +1,9 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "description": "AAAC runtime enforcement — SSOT for hooks and run engine",
4
4
  "edit_phases": ["execute", "sync_inventory", "persist", "write"],
5
- "artifact_write_phases": ["plan", "report"],
5
+ "artifact_write_phases": ["plan", "report", "verify"],
6
+ "verify_verbs": ["create", "update", "fix"],
6
7
  "swarm_min_agents": {
7
8
  "discover": 4,
8
9
  "investigate_swarm": 7,
@@ -13,10 +14,12 @@
13
14
  "investigate_swarm": ["artifacts/investigation.md"],
14
15
  "root_cause": ["artifacts/root_cause.yaml"],
15
16
  "plan": ["artifacts/plan.yaml"],
17
+ "verify": ["artifacts/verify.yaml"],
16
18
  "report": ["artifacts/report.md"]
17
19
  },
18
20
  "allowed_path_prefixes": {
19
- "run_artifacts": [".cursor/aaac/state/runs/", "aaac/state/runs/"]
21
+ "run_artifacts": [".cursor/aaac/state/runs/", "aaac/state/runs/"],
22
+ "write_article": [".cursor/write-article-runs/"]
20
23
  },
21
24
  "fix_commands": ["fix-module", "fix-bug", "module-fix", "bug-fix"]
22
25
  }
@@ -29,7 +29,9 @@
29
29
  "plan missing requirement_map or unjustified create",
30
30
  "impact proceed false",
31
31
  "rollback unverified",
32
- "user intent contains requires approval"
32
+ "user intent contains requires approval",
33
+ "capability runtime require_approval",
34
+ "capability state deprecated"
33
35
  ],
34
36
  "run_fields": {
35
37
  "status": "blocked",
@@ -36,6 +36,8 @@ Execution Layer
36
36
  ├─ Verb orchestrators .cursor/skills/shared/verbs/*/orchestrator/
37
37
  ├─ Shared pipeline skills .cursor/skills/shared/
38
38
  ├─ Capability registry .cursor/aaac/capabilities/registry.json
39
+ ├─ Capability promotion rules .cursor/aaac/capabilities/promotion-rules.json
40
+ ├─ Capability stats (derived) .cursor/aaac/state/capability-stats.json
39
41
  ├─ Agent specs .cursor/agents/
40
42
 
41
43
  Knowledge Layer
@@ -87,6 +89,7 @@ Policies → Ontology → Graph → Create Run
87
89
  → Lifecycle (work) + Gates (composed into Run.pending)
88
90
  → Orchestrator → Capabilities resolved (recorded on Run)
89
91
  → Execute phases → Update Run → Report
92
+ → Run completes → capability-evidence.mjs → update capability-stats.json + evaluate promotion
90
93
  ```
91
94
 
92
95
  ## Deprecated
@@ -21,6 +21,9 @@ log_on:
21
21
  - human_approval_required
22
22
  - human_approval_received
23
23
  - capability_resolved
24
+ - evidence_aggregated
25
+ - capability_promoted
26
+ - evidence_aggregation_failed
24
27
  - skill_loaded
25
28
  - doc_loaded
26
29
  - agent_spawned
@@ -22,6 +22,8 @@
22
22
  "checkpoints": [],
23
23
  "log": [],
24
24
  "capabilities_resolved": {},
25
+ "capability_evidence_processed": false,
26
+ "capability_evidence_outcomes": [],
25
27
  "confidence": {
26
28
  "architecture": null,
27
29
  "requirements": null,
@@ -5,6 +5,8 @@
5
5
  */
6
6
  import fs from "fs";
7
7
  import path from "path";
8
+ import { spawnSync } from "child_process";
9
+ import { fileURLToPath } from "url";
8
10
  import {
9
11
  loadRegistry,
10
12
  loadEnforcement,
@@ -18,6 +20,14 @@ import {
18
20
  saveActiveRun,
19
21
  } from "./lib.mjs";
20
22
  import { recordLog } from "./log.mjs";
23
+ import {
24
+ processRunEvidence,
25
+ evaluateCapabilityRuntimePolicy,
26
+ resolveCapabilitiesWithRuntime,
27
+ loadObjectMaturity,
28
+ } from "./capability-evidence.mjs";
29
+
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
31
 
22
32
  const runId = process.argv[2];
23
33
  const completedPhase = process.argv[3];
@@ -67,6 +77,46 @@ if (minAgents && launches < minAgents && !force) {
67
77
  process.exit(2);
68
78
  }
69
79
 
80
+ const verifyVerbs = enforcement.verify_verbs ?? ["create", "update", "fix"];
81
+ if (
82
+ completedPhase === "verify" &&
83
+ verifyVerbs.includes(manifest.verb) &&
84
+ !force
85
+ ) {
86
+ const verifyScript = path.join(__dirname, "verify-website-build.mjs");
87
+ const verifyRun = spawnSync("node", [verifyScript, "--run-id", runId], {
88
+ encoding: "utf8",
89
+ });
90
+ if (verifyRun.status !== 0) {
91
+ const detail =
92
+ verifyRun.stderr?.trim() ||
93
+ verifyRun.stdout?.trim() ||
94
+ "verify-website-build failed";
95
+ recordLog(manifest, {
96
+ event: "gate_fail",
97
+ phase: completedPhase,
98
+ phase_kind: manifest.phase_kind,
99
+ detail: `website verify failed: ${detail.slice(0, 500)}`,
100
+ level: "warn",
101
+ });
102
+ manifest.updated_at = isoNow();
103
+ writeJson(manifestPath, manifest);
104
+ console.error(
105
+ "Website verify failed (static assets + vite build). Fix errors, then re-run:\n" +
106
+ ` node .cursor/aaac/scripts/run-engine/verify-website-build.mjs --run-id ${runId}\n` +
107
+ detail,
108
+ );
109
+ process.exit(2);
110
+ }
111
+ recordLog(manifest, {
112
+ event: "verify_website_pass",
113
+ phase: completedPhase,
114
+ phase_kind: manifest.phase_kind,
115
+ detail: "static assets + vite build",
116
+ level: "info",
117
+ });
118
+ }
119
+
70
120
  const requiredArtifacts = enforcement.phase_artifacts?.[completedPhase] ?? [];
71
121
  for (const rel of requiredArtifacts) {
72
122
  const artifactPath = path.join(runDir(runId), rel);
@@ -119,7 +169,68 @@ recordLog(manifest, {
119
169
  level: "info",
120
170
  });
121
171
 
122
- const nextPhase = manifest.pending.shift() ?? null;
172
+ let nextPhase = manifest.pending.shift() ?? null;
173
+
174
+ if (nextPhase === "execute" && !force) {
175
+ const resolved =
176
+ manifest.capabilities_resolved &&
177
+ Object.keys(manifest.capabilities_resolved).length > 0
178
+ ? manifest.capabilities_resolved
179
+ : resolveCapabilitiesWithRuntime(manifest.object, manifest.verb);
180
+ const policy = evaluateCapabilityRuntimePolicy(resolved, {
181
+ object_maturity: loadObjectMaturity(manifest.object),
182
+ });
183
+ manifest.capability_runtime = policy;
184
+
185
+ const needsBlock =
186
+ policy.action === "block" ||
187
+ (policy.action === "require_approval" && !manifest.capability_runtime_approved);
188
+
189
+ if (needsBlock) {
190
+ manifest.pending.unshift(nextPhase);
191
+ nextPhase = null;
192
+ manifest.status = "blocked";
193
+ manifest.awaiting_approval = policy.action === "require_approval";
194
+ manifest.blocked_reason = policy.reasons.join("; ") || "capability runtime policy";
195
+ recordLog(manifest, {
196
+ event: "gate_fail",
197
+ phase: completedPhase,
198
+ phase_kind: manifest.phase_kind,
199
+ detail: `capability runtime ${policy.action}: ${manifest.blocked_reason}`,
200
+ level: "warn",
201
+ });
202
+ manifest.updated_at = isoNow();
203
+ writeJson(manifestPath, manifest);
204
+ saveActiveRun(manifest.conversation_id ?? null, {
205
+ run_id: runId,
206
+ conversation_id: manifest.conversation_id ?? null,
207
+ command: manifest.command,
208
+ phase: manifest.phase,
209
+ status: manifest.status,
210
+ task_launches_this_phase: 0,
211
+ edit_allowed: false,
212
+ started_at: manifest.created_at,
213
+ });
214
+ console.error(
215
+ `Capability runtime ${policy.action}: ${manifest.blocked_reason}. ` +
216
+ (policy.action === "require_approval"
217
+ ? "User must approve in chat; set capability_runtime_approved on Run and retry."
218
+ : "Cannot proceed to execute."),
219
+ );
220
+ process.exit(2);
221
+ }
222
+
223
+ if (policy.action === "warn") {
224
+ recordLog(manifest, {
225
+ event: "capability_runtime_warn",
226
+ phase: completedPhase,
227
+ phase_kind: manifest.phase_kind,
228
+ detail: policy.reasons.join("; "),
229
+ level: "warn",
230
+ });
231
+ }
232
+ }
233
+
123
234
  if (!nextPhase) {
124
235
  manifest.status = "completed";
125
236
  manifest.phase = "report";
@@ -131,6 +242,46 @@ if (!nextPhase) {
131
242
  detail: "all phases completed",
132
243
  level: "info",
133
244
  });
245
+
246
+ try {
247
+ const evidenceResult = processRunEvidence(runId, { manifest, skipManifestWrite: true });
248
+ if (evidenceResult.ok && !evidenceResult.skipped) {
249
+ manifest.capability_evidence_processed = true;
250
+ manifest.capability_evidence_outcomes = evidenceResult.outcomes;
251
+ if (
252
+ !manifest.capabilities_resolved ||
253
+ !Object.keys(manifest.capabilities_resolved).length
254
+ ) {
255
+ manifest.capabilities_resolved = evidenceResult.resolved;
256
+ }
257
+ recordLog(manifest, {
258
+ event: "evidence_aggregated",
259
+ phase: "report",
260
+ phase_kind: "work",
261
+ detail: `capabilities=${(evidenceResult.capabilities ?? []).join(",")}`,
262
+ level: "info",
263
+ });
264
+ for (const outcome of evidenceResult.outcomes ?? []) {
265
+ if (outcome.previous_state !== outcome.new_state) {
266
+ recordLog(manifest, {
267
+ event: "capability_promoted",
268
+ phase: "report",
269
+ phase_kind: "work",
270
+ detail: `${outcome.capability_id}:${outcome.previous_state}→${outcome.new_state}`,
271
+ level: "info",
272
+ });
273
+ }
274
+ }
275
+ }
276
+ } catch (err) {
277
+ recordLog(manifest, {
278
+ event: "evidence_aggregation_failed",
279
+ phase: "report",
280
+ phase_kind: "work",
281
+ detail: String(err.message ?? err).slice(0, 300),
282
+ level: "warn",
283
+ });
284
+ }
134
285
  } else {
135
286
  manifest.phase = nextPhase;
136
287
  manifest.phase_kind = phaseKind(nextPhase, registry);