@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.
- package/package.json +1 -1
- package/src/run-engine/advance-phase.mjs +152 -1
- package/src/run-engine/capability-evidence.mjs +460 -0
- package/src/run-engine/init-run.mjs +51 -1
- package/src/run-engine/lib.mjs +5 -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/dispatch.md +2 -2
- package/templates/cursor/aaac/enforcement.json +6 -3
- package/templates/cursor/aaac/governance/gates.json +3 -1
- package/templates/cursor/aaac/layers.md +3 -0
- package/templates/cursor/aaac/observability/telemetry.yaml +3 -0
- package/templates/cursor/aaac/run/schema.json +2 -0
- package/templates/cursor/aaac/scripts/run-engine/advance-phase.mjs +152 -1
- package/templates/cursor/aaac/scripts/run-engine/capability-evidence.mjs +460 -0
- package/templates/cursor/aaac/scripts/run-engine/init-run.mjs +51 -1
- package/templates/cursor/aaac/scripts/run-engine/lib.mjs +5 -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/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/testing/SKILL.md +5 -0
- package/templates/cursor/skills/shared/verification/SKILL.md +1 -0
- 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],
|
package/src/run-engine/lib.mjs
CHANGED
|
@@ -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,
|
|
5
|
+
"description": "Design tokens, component CSS, Figma alignment",
|
|
6
6
|
"providers": [
|
|
7
|
-
{ "id": "
|
|
7
|
+
{ "id": "ludecker-design-system", "type": "skill", "path": "skills/ludecker/design-system" }
|
|
8
8
|
]
|
|
9
9
|
},
|
|
10
10
|
"ux-design": {
|
|
11
|
-
"description": "
|
|
11
|
+
"description": "Editorial readability, publish flow, navigation clarity",
|
|
12
12
|
"providers": [
|
|
13
|
-
{ "id": "
|
|
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": "
|
|
19
|
+
{ "id": "ludecker-api-first", "type": "skill", "path": "skills/ludecker/api-first" }
|
|
20
20
|
]
|
|
21
21
|
},
|
|
22
22
|
"database-design": {
|
|
23
|
-
"description": "Schema, migrations,
|
|
23
|
+
"description": "Schema, migrations, RLS, type mirrors",
|
|
24
24
|
"providers": [
|
|
25
|
-
{ "id": "schema", "type": "skill", "path": "skills/
|
|
26
|
-
{ "id": "
|
|
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,
|
|
30
|
+
"description": "Auth, RLS, secrets, CMS gates",
|
|
31
31
|
"providers": [
|
|
32
|
-
{ "id": "
|
|
32
|
+
{ "id": "ludecker-security", "type": "skill", "path": "skills/ludecker/security" }
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
"infrastructure": {
|
|
36
|
-
"description": "Deploy,
|
|
36
|
+
"description": "Deploy, Render, env, hosting",
|
|
37
37
|
"providers": [
|
|
38
|
-
{ "id": "
|
|
38
|
+
{ "id": "ludecker-infrastructure", "type": "skill", "path": "skills/ludecker/infrastructure" }
|
|
39
39
|
]
|
|
40
40
|
},
|
|
41
41
|
"layer-boundaries": {
|
|
42
|
-
"description": "SSOT, import direction,
|
|
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) —
|
|
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":
|
|
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
|
|
@@ -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
|
-
|
|
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);
|