@ludecker/aaac 1.1.0 → 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.
- package/package.json +1 -1
- package/src/generators/generate-commands.mjs +17 -9
- package/src/run-engine/advance-phase.mjs +152 -1
- package/src/run-engine/capability-evidence.mjs +460 -0
- package/src/run-engine/gate-write.mjs +14 -2
- package/src/run-engine/init-run.mjs +92 -1
- package/src/run-engine/lib.mjs +38 -0
- package/src/run-engine/record-task.mjs +7 -1
- package/src/run-engine/stop-check.mjs +7 -1
- package/src/run-engine/verify-website-build.mjs +185 -0
- package/templates/cursor/aaac/capabilities/promotion-rules.json +64 -0
- package/templates/cursor/aaac/capabilities/registry.json +11 -11
- 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/graph.project.yaml +4 -204
- package/templates/cursor/aaac/layers.md +3 -0
- package/templates/cursor/aaac/observability/telemetry.yaml +3 -0
- package/templates/cursor/aaac/ontology.md +17 -32
- package/templates/cursor/aaac/project.config.json +4 -1
- package/templates/cursor/aaac/run/schema.json +5 -1
- 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/gate-write.mjs +14 -2
- package/templates/cursor/aaac/scripts/run-engine/init-run.mjs +92 -1
- package/templates/cursor/aaac/scripts/run-engine/lib.mjs +38 -0
- package/templates/cursor/aaac/scripts/run-engine/record-task.mjs +7 -1
- package/templates/cursor/aaac/scripts/run-engine/stop-check.mjs +7 -1
- package/templates/cursor/aaac/scripts/run-engine/verify-website-build.mjs +185 -0
- package/templates/cursor/aaac/state/capability-stats.json +5 -0
- package/templates/cursor/agents/playwright-check-run.md +8 -26
- package/templates/cursor/agents/release-git.md +2 -2
- package/templates/cursor/agents/unit-test-run.md +3 -7
- package/templates/cursor/skills/shared/governance/implementation/SKILL.md +25 -396
- 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/verbs/check/orchestrator/SKILL.md +1 -1
- package/templates/cursor/skills/shared/verification/SKILL.md +2 -1
- package/templates/docs/agentic_architecture.md +163 -60
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Capability evidence loop — resolve capabilities, extract run evidence,
|
|
4
|
+
* aggregate cross-run stats, evaluate deterministic lifecycle state.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import {
|
|
10
|
+
AAAC_ROOT,
|
|
11
|
+
STATE_ROOT,
|
|
12
|
+
readJson,
|
|
13
|
+
writeJson,
|
|
14
|
+
runDir,
|
|
15
|
+
loadRunManifest,
|
|
16
|
+
isoNow,
|
|
17
|
+
} from "./lib.mjs";
|
|
18
|
+
|
|
19
|
+
export const ONTOLOGY_PATH = path.join(AAAC_ROOT, "ontology.json");
|
|
20
|
+
export const CAPABILITY_REGISTRY_PATH = path.join(AAAC_ROOT, "capabilities", "registry.json");
|
|
21
|
+
export const PROMOTION_RULES_PATH = path.join(AAAC_ROOT, "capabilities", "promotion-rules.json");
|
|
22
|
+
export const CAPABILITY_STATS_PATH = path.join(STATE_ROOT, "capability-stats.json");
|
|
23
|
+
|
|
24
|
+
const STATE_ORDER = ["experimental", "validated", "trusted", "canonical", "deprecated"];
|
|
25
|
+
|
|
26
|
+
export function loadOntology() {
|
|
27
|
+
return readJson(ONTOLOGY_PATH, { object_capabilities: {}, object_capability_verbs: {} });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadCapabilityRegistry() {
|
|
31
|
+
return readJson(CAPABILITY_REGISTRY_PATH, { capabilities: {} });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function loadPromotionRules() {
|
|
35
|
+
return readJson(PROMOTION_RULES_PATH, {
|
|
36
|
+
default_state: "experimental",
|
|
37
|
+
thresholds: {},
|
|
38
|
+
demotion: {},
|
|
39
|
+
fitness_scoring: { pass: 100, warning: 75, fail: 0 },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function loadCapabilityStats() {
|
|
44
|
+
return readJson(CAPABILITY_STATS_PATH, {
|
|
45
|
+
version: 1,
|
|
46
|
+
updated_at: null,
|
|
47
|
+
capabilities: {},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function saveCapabilityStats(stats) {
|
|
52
|
+
stats.updated_at = isoNow();
|
|
53
|
+
writeJson(CAPABILITY_STATS_PATH, stats);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function capabilityIdsForObject(object, verb, ontology = loadOntology()) {
|
|
57
|
+
if (!object) return [];
|
|
58
|
+
const base = ontology.object_capabilities?.[object] ?? [];
|
|
59
|
+
const verbExtras = ontology.object_capability_verbs?.[object]?.[verb] ?? [];
|
|
60
|
+
return [...new Set([...base, ...verbExtras])];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function loadObjectMaturity(object) {
|
|
64
|
+
if (!object) return "evolving";
|
|
65
|
+
const ontology = loadOntology();
|
|
66
|
+
return ontology.object_maturity?.[object] ?? "evolving";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveCapabilitiesForObject(object, verb) {
|
|
70
|
+
const ontology = loadOntology();
|
|
71
|
+
const registry = loadCapabilityRegistry();
|
|
72
|
+
const ids = capabilityIdsForObject(object, verb, ontology);
|
|
73
|
+
const resolved = {};
|
|
74
|
+
|
|
75
|
+
for (const capabilityId of ids) {
|
|
76
|
+
const entry = registry.capabilities?.[capabilityId];
|
|
77
|
+
resolved[capabilityId] = {
|
|
78
|
+
providers: entry?.providers ?? [],
|
|
79
|
+
source: `object ${object}${verb ? ` verb ${verb}` : ""}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return resolved;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveCapabilitiesWithRuntime(object, verb) {
|
|
87
|
+
const resolved = resolveCapabilitiesForObject(object, verb);
|
|
88
|
+
const store = loadCapabilityStats();
|
|
89
|
+
const rules = loadPromotionRules();
|
|
90
|
+
const defaultState = rules.default_state ?? "experimental";
|
|
91
|
+
|
|
92
|
+
for (const [capabilityId, resolution] of Object.entries(resolved)) {
|
|
93
|
+
const entry = store.capabilities[capabilityId];
|
|
94
|
+
const state = entry?.state ?? defaultState;
|
|
95
|
+
const rates = entry ? computeRates(entry.stats) : null;
|
|
96
|
+
resolved[capabilityId] = {
|
|
97
|
+
...resolution,
|
|
98
|
+
runtime: {
|
|
99
|
+
state,
|
|
100
|
+
invocations: entry?.stats?.invocations ?? 0,
|
|
101
|
+
success_rate: rates?.success_rate ?? null,
|
|
102
|
+
avg_fitness: rates?.avg_fitness ?? null,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function evaluateCapabilityRuntimePolicy(resolved, context = {}) {
|
|
111
|
+
const rules = loadPromotionRules();
|
|
112
|
+
const runtime = rules.runtime ?? {};
|
|
113
|
+
const byState = runtime.by_state ?? {};
|
|
114
|
+
const evidenceTriggers = runtime.evidence_triggers ?? [];
|
|
115
|
+
const objectMaturity = context.object_maturity ?? "evolving";
|
|
116
|
+
|
|
117
|
+
const reasons = [];
|
|
118
|
+
let action = "allow";
|
|
119
|
+
|
|
120
|
+
const rank = { allow: 0, warn: 1, require_approval: 2, block: 3 };
|
|
121
|
+
const raise = (next, reason) => {
|
|
122
|
+
if (rank[next] > rank[action]) action = next;
|
|
123
|
+
reasons.push(reason);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
for (const [capabilityId, resolution] of Object.entries(resolved)) {
|
|
127
|
+
const state = resolution.runtime?.state ?? rules.default_state ?? "experimental";
|
|
128
|
+
const statePolicy = byState[state] ?? {};
|
|
129
|
+
|
|
130
|
+
if (statePolicy.block_execute) {
|
|
131
|
+
raise("block", `${capabilityId}: state ${state} blocks execute`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const requireOn = statePolicy.require_approval_on ?? [];
|
|
135
|
+
if (requireOn.includes(objectMaturity) || requireOn.includes("all")) {
|
|
136
|
+
raise(
|
|
137
|
+
"require_approval",
|
|
138
|
+
`${capabilityId}: ${state} on ${objectMaturity} object requires approval`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (statePolicy.warn) {
|
|
143
|
+
raise("warn", `${capabilityId}: ${state} — proceed with caution`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const invocations = resolution.runtime?.invocations ?? 0;
|
|
147
|
+
const successRate = resolution.runtime?.success_rate;
|
|
148
|
+
const avgFitness = resolution.runtime?.avg_fitness;
|
|
149
|
+
|
|
150
|
+
for (const trigger of evidenceTriggers) {
|
|
151
|
+
if (invocations < (trigger.min_invocations ?? 0)) continue;
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
successRate != null &&
|
|
155
|
+
trigger.min_success_rate_below != null &&
|
|
156
|
+
successRate < trigger.min_success_rate_below
|
|
157
|
+
) {
|
|
158
|
+
raise(
|
|
159
|
+
trigger.action === "block" ? "block" : "require_approval",
|
|
160
|
+
`${capabilityId}: success_rate ${(successRate * 100).toFixed(1)}% below threshold`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
avgFitness != null &&
|
|
166
|
+
trigger.min_avg_fitness_below != null &&
|
|
167
|
+
avgFitness < trigger.min_avg_fitness_below
|
|
168
|
+
) {
|
|
169
|
+
raise(
|
|
170
|
+
trigger.action === "block" ? "block" : "require_approval",
|
|
171
|
+
`${capabilityId}: avg_fitness ${avgFitness.toFixed(1)} below threshold`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { action, reasons, object_maturity: objectMaturity };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseSimpleYaml(content) {
|
|
181
|
+
const result = {};
|
|
182
|
+
let currentKey = null;
|
|
183
|
+
let currentIndent = 0;
|
|
184
|
+
|
|
185
|
+
for (const line of content.split("\n")) {
|
|
186
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
187
|
+
const indent = line.search(/\S/);
|
|
188
|
+
const trimmed = line.trim();
|
|
189
|
+
|
|
190
|
+
if (trimmed.endsWith(":") && !trimmed.includes(": ")) {
|
|
191
|
+
currentKey = trimmed.slice(0, -1);
|
|
192
|
+
currentIndent = indent;
|
|
193
|
+
result[currentKey] = {};
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const colon = trimmed.indexOf(": ");
|
|
198
|
+
if (colon === -1) continue;
|
|
199
|
+
|
|
200
|
+
const key = trimmed.slice(0, colon).trim();
|
|
201
|
+
const value = trimmed.slice(colon + 2).trim().replace(/^["']|["']$/g, "");
|
|
202
|
+
|
|
203
|
+
if (currentKey && indent > currentIndent) {
|
|
204
|
+
result[currentKey][key] = value;
|
|
205
|
+
} else {
|
|
206
|
+
result[key] = value;
|
|
207
|
+
currentKey = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function fitnessScoreFromArtifact(artifactsDir, scoring) {
|
|
215
|
+
const fitnessPath = path.join(artifactsDir, "fitness-functions.yaml");
|
|
216
|
+
if (!fs.existsSync(fitnessPath)) return null;
|
|
217
|
+
|
|
218
|
+
const content = fs.readFileSync(fitnessPath, "utf8");
|
|
219
|
+
const scores = [];
|
|
220
|
+
const scoreSection = content.match(/^score:\n([\s\S]*?)(?:\n\w|$)/m);
|
|
221
|
+
if (!scoreSection) return null;
|
|
222
|
+
|
|
223
|
+
for (const line of scoreSection[1].split("\n")) {
|
|
224
|
+
const match = line.match(/^\s+(\w+):\s+(pass|warning|fail)/);
|
|
225
|
+
if (match) {
|
|
226
|
+
scores.push(scoring[match[2]] ?? 0);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!scores.length) return null;
|
|
231
|
+
return scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function extractRunEvidence(manifest, artifactsDir) {
|
|
235
|
+
const log = manifest.log ?? [];
|
|
236
|
+
const gateFails = log.filter((e) => e.event === "gate_fail").length;
|
|
237
|
+
const gatePasses = log.filter((e) => e.event === "gate_pass").length;
|
|
238
|
+
const rules = loadPromotionRules();
|
|
239
|
+
const fitnessScore = fitnessScoreFromArtifact(artifactsDir, rules.fitness_scoring ?? {});
|
|
240
|
+
|
|
241
|
+
const success = manifest.status === "completed";
|
|
242
|
+
const failure = manifest.status === "failed";
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
success,
|
|
246
|
+
failure,
|
|
247
|
+
rollback: failure,
|
|
248
|
+
gate_failures: gateFails,
|
|
249
|
+
gate_passes: gatePasses,
|
|
250
|
+
fitness_score: fitnessScore,
|
|
251
|
+
run_id: manifest.run_id,
|
|
252
|
+
command: manifest.command,
|
|
253
|
+
object: manifest.object,
|
|
254
|
+
verb: manifest.verb,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function emptyCapabilityEntry(state = "experimental") {
|
|
259
|
+
return {
|
|
260
|
+
state,
|
|
261
|
+
stats: {
|
|
262
|
+
invocations: 0,
|
|
263
|
+
successes: 0,
|
|
264
|
+
failures: 0,
|
|
265
|
+
rollbacks: 0,
|
|
266
|
+
gate_passes: 0,
|
|
267
|
+
gate_failures: 0,
|
|
268
|
+
avg_fitness_score: null,
|
|
269
|
+
fitness_samples: 0,
|
|
270
|
+
},
|
|
271
|
+
gate_history: { passed: 0, failed: 0 },
|
|
272
|
+
history: { promoted: [] },
|
|
273
|
+
overrides: null,
|
|
274
|
+
last_run_id: null,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function computeRates(stats) {
|
|
279
|
+
const invocations = stats.invocations || 0;
|
|
280
|
+
if (!invocations) {
|
|
281
|
+
return {
|
|
282
|
+
success_rate: 0,
|
|
283
|
+
rollback_rate: 0,
|
|
284
|
+
gate_failure_rate: 0,
|
|
285
|
+
avg_fitness: stats.avg_fitness_score,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
success_rate: stats.successes / invocations,
|
|
291
|
+
rollback_rate: stats.rollbacks / invocations,
|
|
292
|
+
gate_failure_rate: stats.gate_failures / (stats.gate_passes + stats.gate_failures || 1),
|
|
293
|
+
avg_fitness: stats.avg_fitness_score,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function meetsThreshold(stats, rates, threshold) {
|
|
298
|
+
if (threshold.min_invocations != null && stats.invocations < threshold.min_invocations) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
if (threshold.min_success_rate != null && rates.success_rate < threshold.min_success_rate) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
if (threshold.max_rollback_rate != null && rates.rollback_rate > threshold.max_rollback_rate) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
threshold.max_gate_failure_rate != null &&
|
|
309
|
+
rates.gate_failure_rate > threshold.max_gate_failure_rate
|
|
310
|
+
) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function evaluateState(entry, rules = loadPromotionRules()) {
|
|
317
|
+
if (entry.overrides?.state) return entry.overrides.state;
|
|
318
|
+
|
|
319
|
+
const rates = computeRates(entry.stats);
|
|
320
|
+
const thresholds = rules.thresholds ?? {};
|
|
321
|
+
const demotion = rules.demotion?.from_trusted;
|
|
322
|
+
|
|
323
|
+
if (
|
|
324
|
+
entry.state === "trusted" &&
|
|
325
|
+
demotion &&
|
|
326
|
+
entry.stats.invocations >= (demotion.min_invocations ?? 0) &&
|
|
327
|
+
rates.success_rate < (demotion.min_success_rate_below ?? 0)
|
|
328
|
+
) {
|
|
329
|
+
return "validated";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const candidates = ["canonical", "trusted", "validated"];
|
|
333
|
+
for (const state of candidates) {
|
|
334
|
+
const threshold = thresholds[state];
|
|
335
|
+
if (!threshold) continue;
|
|
336
|
+
if (threshold.manual_approval && !entry.overrides?.approved_by) continue;
|
|
337
|
+
if (meetsThreshold(entry.stats, rates, threshold)) return state;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return rules.default_state ?? "experimental";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function updateCapabilityFromEvidence(store, capabilityId, evidence) {
|
|
344
|
+
const entry = store.capabilities[capabilityId] ?? emptyCapabilityEntry();
|
|
345
|
+
const stats = entry.stats;
|
|
346
|
+
|
|
347
|
+
stats.invocations += 1;
|
|
348
|
+
if (evidence.success) stats.successes += 1;
|
|
349
|
+
if (evidence.failure) stats.failures += 1;
|
|
350
|
+
if (evidence.rollback) stats.rollbacks += 1;
|
|
351
|
+
stats.gate_passes += evidence.gate_passes;
|
|
352
|
+
stats.gate_failures += evidence.gate_failures;
|
|
353
|
+
entry.gate_history.passed += evidence.gate_passes;
|
|
354
|
+
entry.gate_history.failed += evidence.gate_failures;
|
|
355
|
+
|
|
356
|
+
if (evidence.fitness_score != null) {
|
|
357
|
+
const n = stats.fitness_samples;
|
|
358
|
+
stats.avg_fitness_score =
|
|
359
|
+
stats.avg_fitness_score == null
|
|
360
|
+
? evidence.fitness_score
|
|
361
|
+
: (stats.avg_fitness_score * n + evidence.fitness_score) / (n + 1);
|
|
362
|
+
stats.fitness_samples = n + 1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const previousState = entry.state;
|
|
366
|
+
const rules = loadPromotionRules();
|
|
367
|
+
entry.state = evaluateState(entry, rules);
|
|
368
|
+
if (entry.state !== previousState && !entry.history.promoted.includes(entry.state)) {
|
|
369
|
+
entry.history.promoted.push(entry.state);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
entry.last_run_id = evidence.run_id;
|
|
373
|
+
store.capabilities[capabilityId] = entry;
|
|
374
|
+
return { previousState, newState: entry.state, entry };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function processRunEvidence(runId, options = {}) {
|
|
378
|
+
const manifest = options.manifest ?? loadRunManifest(runId);
|
|
379
|
+
if (!manifest) {
|
|
380
|
+
return { ok: false, error: `Run not found: ${runId}` };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (manifest.capability_evidence_processed && !options.force) {
|
|
384
|
+
return { ok: true, skipped: true, reason: "already_processed" };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const artifactsDir = path.join(runDir(runId), "artifacts");
|
|
388
|
+
const evidence = extractRunEvidence(manifest, artifactsDir);
|
|
389
|
+
|
|
390
|
+
const resolved =
|
|
391
|
+
manifest.capabilities_resolved &&
|
|
392
|
+
Object.keys(manifest.capabilities_resolved).length > 0
|
|
393
|
+
? manifest.capabilities_resolved
|
|
394
|
+
: resolveCapabilitiesForObject(manifest.object, manifest.verb);
|
|
395
|
+
|
|
396
|
+
const capabilityIds = Object.keys(resolved);
|
|
397
|
+
if (!capabilityIds.length) {
|
|
398
|
+
return { ok: true, skipped: true, reason: "no_capabilities" };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const store = loadCapabilityStats();
|
|
402
|
+
const outcomes = [];
|
|
403
|
+
|
|
404
|
+
for (const capabilityId of capabilityIds) {
|
|
405
|
+
const result = updateCapabilityFromEvidence(store, capabilityId, evidence);
|
|
406
|
+
outcomes.push({
|
|
407
|
+
capability_id: capabilityId,
|
|
408
|
+
previous_state: result.previousState,
|
|
409
|
+
new_state: result.newState,
|
|
410
|
+
stats: result.entry.stats,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
saveCapabilityStats(store);
|
|
415
|
+
|
|
416
|
+
const result = {
|
|
417
|
+
ok: true,
|
|
418
|
+
run_id: runId,
|
|
419
|
+
outcomes,
|
|
420
|
+
capabilities: capabilityIds,
|
|
421
|
+
resolved,
|
|
422
|
+
evidence,
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
if (!options.skipManifestWrite) {
|
|
426
|
+
manifest.capability_evidence_processed = true;
|
|
427
|
+
manifest.capability_evidence_outcomes = outcomes;
|
|
428
|
+
if (!manifest.capabilities_resolved || !Object.keys(manifest.capabilities_resolved).length) {
|
|
429
|
+
manifest.capabilities_resolved = resolved;
|
|
430
|
+
}
|
|
431
|
+
manifest.updated_at = isoNow();
|
|
432
|
+
writeJson(path.join(runDir(runId), "run.json"), manifest);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function main() {
|
|
439
|
+
const args = process.argv.slice(2);
|
|
440
|
+
const runIdIdx = args.indexOf("--run-id");
|
|
441
|
+
const runId = runIdIdx >= 0 ? args[runIdIdx + 1] : args[0];
|
|
442
|
+
const force = args.includes("--force");
|
|
443
|
+
|
|
444
|
+
if (!runId) {
|
|
445
|
+
console.error("Usage: capability-evidence.mjs --run-id <run_id> [--force]");
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const result = processRunEvidence(runId, { force });
|
|
450
|
+
console.log(JSON.stringify(result));
|
|
451
|
+
process.exit(result.ok ? 0 : 1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const isMain =
|
|
455
|
+
process.argv[1] &&
|
|
456
|
+
path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
457
|
+
|
|
458
|
+
if (isMain) {
|
|
459
|
+
main();
|
|
460
|
+
}
|
|
@@ -53,10 +53,22 @@ process.stdin.on("end", () => {
|
|
|
53
53
|
if (!conversationId) allow();
|
|
54
54
|
|
|
55
55
|
const active = loadActiveRun(conversationId);
|
|
56
|
-
if (
|
|
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 (
|
|
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,10 +11,20 @@ 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";
|
|
17
22
|
import { recordLog, recordDecision } from "./log.mjs";
|
|
23
|
+
import {
|
|
24
|
+
resolveCapabilitiesWithRuntime,
|
|
25
|
+
evaluateCapabilityRuntimePolicy,
|
|
26
|
+
loadObjectMaturity,
|
|
27
|
+
} from "./capability-evidence.mjs";
|
|
18
28
|
|
|
19
29
|
async function readStdin() {
|
|
20
30
|
return new Promise((resolve) => {
|
|
@@ -36,6 +46,42 @@ try {
|
|
|
36
46
|
|
|
37
47
|
const prompt = process.argv[2] ?? promptFromHook(hook);
|
|
38
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
|
+
|
|
39
85
|
const parsed = parseAaacPrompt(prompt);
|
|
40
86
|
|
|
41
87
|
if (!parsed) {
|
|
@@ -64,6 +110,14 @@ const runId = `run_${date}_${slugify(parsed.command + (parsed.domain ? `-${parse
|
|
|
64
110
|
const entry = registry.commands[parsed.command];
|
|
65
111
|
fs.mkdirSync(runDir(runId), { recursive: true });
|
|
66
112
|
|
|
113
|
+
const runObject = entry.object ?? null;
|
|
114
|
+
const runVerb = entry.verb ?? parsed.command.split("-")[0];
|
|
115
|
+
const objectMaturity = loadObjectMaturity(runObject);
|
|
116
|
+
const capabilitiesResolved = resolveCapabilitiesWithRuntime(runObject, runVerb);
|
|
117
|
+
const capabilityRuntimePolicy = evaluateCapabilityRuntimePolicy(capabilitiesResolved, {
|
|
118
|
+
object_maturity: objectMaturity,
|
|
119
|
+
});
|
|
120
|
+
|
|
67
121
|
const manifest = {
|
|
68
122
|
run_id: runId,
|
|
69
123
|
conversation_id: conversationId,
|
|
@@ -84,7 +138,9 @@ const manifest = {
|
|
|
84
138
|
artifacts: {},
|
|
85
139
|
checkpoints: [],
|
|
86
140
|
log: [],
|
|
87
|
-
capabilities_resolved:
|
|
141
|
+
capabilities_resolved: capabilitiesResolved,
|
|
142
|
+
capability_runtime: capabilityRuntimePolicy,
|
|
143
|
+
capability_runtime_approved: false,
|
|
88
144
|
confidence: { architecture: null, requirements: null, scope: null },
|
|
89
145
|
gates: { stack: entry.gate_stack ?? null, results: {} },
|
|
90
146
|
swarm: { task_launches_this_phase: 0, phase: pending[0] },
|
|
@@ -131,6 +187,41 @@ recordDecision(manifest, {
|
|
|
131
187
|
evidence: parsed.raw,
|
|
132
188
|
});
|
|
133
189
|
|
|
190
|
+
for (const [capabilityId, resolution] of Object.entries(manifest.capabilities_resolved)) {
|
|
191
|
+
recordLog(manifest, {
|
|
192
|
+
event: "capability_resolved",
|
|
193
|
+
phase: "dispatch",
|
|
194
|
+
phase_kind: "work",
|
|
195
|
+
detail: `${capabilityId}:${(resolution.providers ?? []).map((p) => p.id).join(",")} state=${resolution.runtime?.state ?? "experimental"}`,
|
|
196
|
+
level: "debug",
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
recordLog(manifest, {
|
|
201
|
+
event: "capability_runtime_evaluated",
|
|
202
|
+
phase: "dispatch",
|
|
203
|
+
phase_kind: "work",
|
|
204
|
+
detail: `action=${capabilityRuntimePolicy.action} maturity=${objectMaturity}`,
|
|
205
|
+
level: "info",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (capabilityRuntimePolicy.action === "warn") {
|
|
209
|
+
recordLog(manifest, {
|
|
210
|
+
event: "capability_runtime_warn",
|
|
211
|
+
phase: "dispatch",
|
|
212
|
+
phase_kind: "work",
|
|
213
|
+
detail: capabilityRuntimePolicy.reasons.join("; "),
|
|
214
|
+
level: "warn",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
recordDecision(manifest, {
|
|
219
|
+
phase: "dispatch",
|
|
220
|
+
decision: "capability_runtime",
|
|
221
|
+
reason: capabilityRuntimePolicy.action,
|
|
222
|
+
evidence: capabilityRuntimePolicy.reasons.join("; ") || "allow",
|
|
223
|
+
});
|
|
224
|
+
|
|
134
225
|
recordLog(manifest, {
|
|
135
226
|
event: "phase_start",
|
|
136
227
|
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 {
|
|
@@ -134,3 +139,36 @@ export function phaseKind(phase, registry) {
|
|
|
134
139
|
export function promptFromHook(hook) {
|
|
135
140
|
return hook?.prompt ?? hook?.text ?? hook?.content ?? "";
|
|
136
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 (
|
|
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 (
|
|
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
|
|