role-os 2.2.0 → 2.3.0
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/CHANGELOG.md +44 -0
- package/README.md +58 -14
- package/bin/roleos.mjs +20 -0
- package/package.json +2 -2
- package/src/artifacts.mjs +79 -1
- package/src/audit-cmd.mjs +401 -0
- package/src/brainstorm-roles.mjs +44 -1
- package/src/composite.mjs +41 -0
- package/src/dispatch.mjs +9 -83
- package/src/hooks.mjs +5 -5
- package/src/knowledge/analyze-artifact-evidence.mjs +420 -0
- package/src/knowledge/attach-bundle-to-evidence.mjs +62 -0
- package/src/knowledge/attach-bundle-to-packet.mjs +42 -0
- package/src/knowledge/fallback-policy.mjs +79 -0
- package/src/knowledge/index.mjs +14 -0
- package/src/knowledge/render-knowledge-block.mjs +215 -0
- package/src/knowledge/resolve-overlay.mjs +66 -0
- package/src/knowledge/retrieve-for-dispatch.mjs +150 -0
- package/src/mission-run.mjs +119 -2
- package/src/mission.mjs +130 -0
- package/src/packs.mjs +37 -0
- package/src/route.mjs +51 -0
- package/src/run-cmd.mjs +4 -1
- package/src/run.mjs +51 -3
- package/src/state-machine.mjs +70 -0
- package/src/swarm/build-gate.mjs +127 -0
- package/src/swarm/domain-detect.mjs +230 -0
- package/src/swarm/persist-bridge.mjs +174 -0
- package/src/swarm-cmd.mjs +424 -0
- package/src/tool-profiles.mjs +91 -0
- package/src/trial.mjs +1 -1
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit CLI — Deep Audit entry point.
|
|
3
|
+
*
|
|
4
|
+
* roleos audit Run deep audit on current repo
|
|
5
|
+
* roleos audit manifest Show or generate the audit manifest
|
|
6
|
+
* roleos audit status Show audit run progress
|
|
7
|
+
* roleos audit verify Re-verify findings against current code
|
|
8
|
+
*
|
|
9
|
+
* This is a first-class shortcut into the deep-audit mission.
|
|
10
|
+
* Under the hood it creates a mission run with dynamic dispatch.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { getMission, suggestMission } from "./mission.mjs";
|
|
16
|
+
import {
|
|
17
|
+
createRun,
|
|
18
|
+
startNextStep,
|
|
19
|
+
getRunPosition,
|
|
20
|
+
getArtifactChain,
|
|
21
|
+
generateCompletionReport,
|
|
22
|
+
formatCompletionReport,
|
|
23
|
+
} from "./mission-run.mjs";
|
|
24
|
+
import {
|
|
25
|
+
createPersistentRun, findActiveRun, listRuns, loadRun,
|
|
26
|
+
startNext, explainRun, getPosition, saveRun,
|
|
27
|
+
} from "./run.mjs";
|
|
28
|
+
|
|
29
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const MANIFEST_FILE = "audit-manifest.json";
|
|
32
|
+
|
|
33
|
+
// ── Main dispatch ────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string[]} args
|
|
37
|
+
*/
|
|
38
|
+
export async function auditCommand(args) {
|
|
39
|
+
const sub = args[0] || "run";
|
|
40
|
+
|
|
41
|
+
switch (sub) {
|
|
42
|
+
case "run":
|
|
43
|
+
case "start":
|
|
44
|
+
return cmdRun(args.slice(1));
|
|
45
|
+
case "manifest":
|
|
46
|
+
return cmdManifest(args.slice(1));
|
|
47
|
+
case "status":
|
|
48
|
+
return cmdStatus();
|
|
49
|
+
case "verify":
|
|
50
|
+
return cmdVerify();
|
|
51
|
+
case "help":
|
|
52
|
+
return cmdHelp();
|
|
53
|
+
default:
|
|
54
|
+
// If the first arg isn't a subcommand, treat everything as a task description
|
|
55
|
+
if (!["run", "start", "manifest", "status", "verify", "help"].includes(sub)) {
|
|
56
|
+
return cmdRun(args);
|
|
57
|
+
}
|
|
58
|
+
cmdHelp();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── roleos audit [run] ───────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function cmdRun(extraArgs) {
|
|
65
|
+
const cwd = process.cwd();
|
|
66
|
+
const manifestPath = join(cwd, MANIFEST_FILE);
|
|
67
|
+
|
|
68
|
+
// Check if manifest exists
|
|
69
|
+
if (!existsSync(manifestPath)) {
|
|
70
|
+
console.log("\nNo audit-manifest.json found in current directory.");
|
|
71
|
+
console.log("Generate one first with: roleos audit manifest --generate\n");
|
|
72
|
+
console.log("The manifest defines your repo's components and boundaries.");
|
|
73
|
+
console.log("Deep audit uses it to dispatch one auditor per component.\n");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
78
|
+
|
|
79
|
+
// Validate manifest shape
|
|
80
|
+
const issues = validateManifest(manifest);
|
|
81
|
+
if (issues.length > 0) {
|
|
82
|
+
console.log("\nAudit manifest has issues:\n");
|
|
83
|
+
for (const issue of issues) {
|
|
84
|
+
console.log(` - ${issue}`);
|
|
85
|
+
}
|
|
86
|
+
console.log("\nFix the manifest and re-run.\n");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const taskDesc = extraArgs.length > 0
|
|
91
|
+
? extraArgs.join(" ")
|
|
92
|
+
: `Deep audit of ${manifest.repo || "current repo"}`;
|
|
93
|
+
|
|
94
|
+
// Create a persistent run via the deep-audit mission
|
|
95
|
+
const run = createPersistentRun(taskDesc, cwd, { forceMission: "deep-audit" });
|
|
96
|
+
|
|
97
|
+
console.log(`\nDeep Audit Started`);
|
|
98
|
+
console.log(`──────────────────`);
|
|
99
|
+
console.log(`Run: ${run.id}`);
|
|
100
|
+
console.log(`Repo: ${manifest.repo || "unknown"}`);
|
|
101
|
+
console.log(`Components: ${manifest.components?.length || 0}`);
|
|
102
|
+
console.log(`Boundaries: ${manifest.boundaries?.length || 0}`);
|
|
103
|
+
console.log(`Steps: ${run.steps.length}`);
|
|
104
|
+
console.log(`\nThe audit will dispatch:`);
|
|
105
|
+
console.log(` - Component Auditor ×${manifest.components?.length || 0}`);
|
|
106
|
+
console.log(` - Test Truth Auditor ×${manifest.components?.length || 0}`);
|
|
107
|
+
console.log(` - Seam Auditor ×${manifest.boundaries?.length || 0}`);
|
|
108
|
+
console.log(` - Audit Synthesizer ×1`);
|
|
109
|
+
console.log(` - Critic Reviewer ×1`);
|
|
110
|
+
console.log(`\nRun 'roleos next' to begin the first step.`);
|
|
111
|
+
console.log(`Run 'roleos audit status' to check progress.\n`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── roleos audit manifest ────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
function cmdManifest(args) {
|
|
117
|
+
const cwd = process.cwd();
|
|
118
|
+
const manifestPath = join(cwd, MANIFEST_FILE);
|
|
119
|
+
|
|
120
|
+
if (args.includes("--generate") || args.includes("-g")) {
|
|
121
|
+
return generateManifest(cwd, manifestPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!existsSync(manifestPath)) {
|
|
125
|
+
console.log("\nNo audit-manifest.json found.");
|
|
126
|
+
console.log("Run 'roleos audit manifest --generate' to create one.\n");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
131
|
+
const issues = validateManifest(manifest);
|
|
132
|
+
|
|
133
|
+
console.log(`\nAudit Manifest: ${manifest.repo || "unknown"}`);
|
|
134
|
+
console.log(`──────────────────────────────────────────`);
|
|
135
|
+
console.log(`Version: ${manifest.version || "unknown"}`);
|
|
136
|
+
console.log(`Language: ${manifest.language || "unknown"}`);
|
|
137
|
+
console.log(`Source: ${manifest.total_source_lines || "?"} lines`);
|
|
138
|
+
console.log(`Tests: ${manifest.total_test_lines || "?"} lines`);
|
|
139
|
+
console.log(`Components: ${manifest.components?.length || 0}`);
|
|
140
|
+
console.log(`Boundaries: ${manifest.boundaries?.length || 0}`);
|
|
141
|
+
|
|
142
|
+
if (manifest.components?.length > 0) {
|
|
143
|
+
console.log(`\nComponents:`);
|
|
144
|
+
for (const c of manifest.components) {
|
|
145
|
+
const paths = c.owned_paths?.length || 0;
|
|
146
|
+
console.log(` - ${c.id}: ${c.description || ""} (${paths} paths)`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (manifest.boundaries?.length > 0) {
|
|
151
|
+
console.log(`\nBoundaries:`);
|
|
152
|
+
for (const b of manifest.boundaries) {
|
|
153
|
+
const label = b.id || `${b.from} → ${b.to}`;
|
|
154
|
+
console.log(` - ${label}: ${b.contract || b.description || ""}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (issues.length > 0) {
|
|
159
|
+
console.log(`\nIssues:`);
|
|
160
|
+
for (const issue of issues) {
|
|
161
|
+
console.log(` ! ${issue}`);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
console.log(`\nManifest is valid.`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log("");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function generateManifest(cwd, manifestPath) {
|
|
171
|
+
if (existsSync(manifestPath)) {
|
|
172
|
+
console.log(`\nManifest already exists at ${MANIFEST_FILE}.`);
|
|
173
|
+
console.log("Edit it manually or delete it to regenerate.\n");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Build a skeleton manifest by scanning src/
|
|
178
|
+
const srcDir = join(cwd, "src");
|
|
179
|
+
const components = [];
|
|
180
|
+
|
|
181
|
+
if (existsSync(srcDir)) {
|
|
182
|
+
const files = readdirSync(srcDir).filter(f => f.endsWith(".mjs") || f.endsWith(".js") || f.endsWith(".ts"));
|
|
183
|
+
// Group by common prefix
|
|
184
|
+
const seen = new Set();
|
|
185
|
+
for (const f of files) {
|
|
186
|
+
const base = f.replace(/(-cmd)?\.m?[jt]s$/, "");
|
|
187
|
+
if (seen.has(base)) continue;
|
|
188
|
+
seen.add(base);
|
|
189
|
+
|
|
190
|
+
const paths = files.filter(ff => ff.startsWith(base)).map(ff => `src/${ff}`);
|
|
191
|
+
components.push({
|
|
192
|
+
id: base,
|
|
193
|
+
description: `TODO: describe ${base}`,
|
|
194
|
+
owned_paths: paths,
|
|
195
|
+
forbidden_paths: [],
|
|
196
|
+
upstream_deps: [],
|
|
197
|
+
downstream_consumers: [],
|
|
198
|
+
public_interfaces: [],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const manifest = {
|
|
204
|
+
repo: "TODO: org/repo-name",
|
|
205
|
+
version: "0.0.0",
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
language: "TODO",
|
|
208
|
+
runtime: "TODO",
|
|
209
|
+
total_source_lines: 0,
|
|
210
|
+
total_test_lines: 0,
|
|
211
|
+
external_dependencies: 0,
|
|
212
|
+
components,
|
|
213
|
+
boundaries: [],
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
217
|
+
console.log(`\nGenerated skeleton ${MANIFEST_FILE} with ${components.length} components.`);
|
|
218
|
+
console.log("Edit the manifest to:");
|
|
219
|
+
console.log(" 1. Fill in repo, version, language, runtime");
|
|
220
|
+
console.log(" 2. Write real descriptions for each component");
|
|
221
|
+
console.log(" 3. Define boundaries between components");
|
|
222
|
+
console.log(" 4. Set upstream_deps and downstream_consumers");
|
|
223
|
+
console.log(`\nThen run 'roleos audit' to start the audit.\n`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── roleos audit status ──────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
function cmdStatus() {
|
|
229
|
+
const cwd = process.cwd();
|
|
230
|
+
|
|
231
|
+
// Find the most recent deep-audit run
|
|
232
|
+
const runs = listRuns(cwd);
|
|
233
|
+
const auditRuns = runs.filter(r =>
|
|
234
|
+
r.task.toLowerCase().includes("audit") ||
|
|
235
|
+
r.level === "mission"
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (auditRuns.length === 0) {
|
|
239
|
+
console.log("\nNo audit runs found. Start one with: roleos audit\n");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Show the most recent
|
|
244
|
+
const latest = auditRuns[0];
|
|
245
|
+
console.log(`\nLatest Audit Run`);
|
|
246
|
+
console.log(`────────────────`);
|
|
247
|
+
console.log(`ID: ${latest.id}`);
|
|
248
|
+
console.log(`Task: ${latest.task}`);
|
|
249
|
+
console.log(`Status: ${latest.status.toUpperCase()}`);
|
|
250
|
+
console.log(`Created: ${latest.createdAt}`);
|
|
251
|
+
|
|
252
|
+
const full = loadRun(cwd, latest.id);
|
|
253
|
+
if (full) {
|
|
254
|
+
const pos = getPosition(full);
|
|
255
|
+
console.log(`Progress: ${pos.progress}`);
|
|
256
|
+
|
|
257
|
+
const byStatus = {};
|
|
258
|
+
for (const s of full.steps) {
|
|
259
|
+
byStatus[s.status] = (byStatus[s.status] || 0) + 1;
|
|
260
|
+
}
|
|
261
|
+
console.log(`\nSteps:`);
|
|
262
|
+
for (const [status, count] of Object.entries(byStatus)) {
|
|
263
|
+
const icon = status === "completed" ? "[x]" :
|
|
264
|
+
status === "active" ? "[>]" :
|
|
265
|
+
status === "failed" ? "[!]" :
|
|
266
|
+
status === "blocked" ? "[-]" : "[ ]";
|
|
267
|
+
console.log(` ${icon} ${status}: ${count}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(`\nRun 'roleos explain ${latest.id}' for full detail.\n`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── roleos audit verify ──────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
function cmdVerify() {
|
|
277
|
+
const cwd = process.cwd();
|
|
278
|
+
const manifestPath = join(cwd, MANIFEST_FILE);
|
|
279
|
+
|
|
280
|
+
if (!existsSync(manifestPath)) {
|
|
281
|
+
console.log("\nNo audit-manifest.json found. Nothing to verify.\n");
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
286
|
+
const issues = validateManifest(manifest);
|
|
287
|
+
|
|
288
|
+
console.log(`\nAudit Verification`);
|
|
289
|
+
console.log(`──────────────────`);
|
|
290
|
+
|
|
291
|
+
// 1. Manifest valid
|
|
292
|
+
if (issues.length === 0) {
|
|
293
|
+
console.log(` [PASS] Manifest is valid`);
|
|
294
|
+
} else {
|
|
295
|
+
console.log(` [FAIL] Manifest has ${issues.length} issue(s)`);
|
|
296
|
+
for (const i of issues) console.log(` - ${i}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 2. Owned paths exist
|
|
300
|
+
let pathsOk = 0;
|
|
301
|
+
let pathsMissing = 0;
|
|
302
|
+
for (const c of manifest.components || []) {
|
|
303
|
+
for (const p of c.owned_paths || []) {
|
|
304
|
+
if (existsSync(join(cwd, p))) {
|
|
305
|
+
pathsOk++;
|
|
306
|
+
} else {
|
|
307
|
+
pathsMissing++;
|
|
308
|
+
if (pathsMissing <= 5) {
|
|
309
|
+
console.log(` [WARN] Missing path: ${p} (${c.id})`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (pathsMissing === 0) {
|
|
315
|
+
console.log(` [PASS] All ${pathsOk} owned paths exist`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` [WARN] ${pathsMissing} owned path(s) missing (${pathsOk} ok)`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 3. Check for audit output files
|
|
321
|
+
const auditFiles = ["AUDIT-SUMMARY.md", "AUDIT-ACTION-PLAN.md", "AUDIT-CRITIC-VERDICT.md"];
|
|
322
|
+
const existing = auditFiles.filter(f => existsSync(join(cwd, f)));
|
|
323
|
+
if (existing.length === auditFiles.length) {
|
|
324
|
+
console.log(` [PASS] All audit output files present (${existing.length}/${auditFiles.length})`);
|
|
325
|
+
} else if (existing.length > 0) {
|
|
326
|
+
console.log(` [WARN] Partial audit outputs (${existing.length}/${auditFiles.length})`);
|
|
327
|
+
const missing = auditFiles.filter(f => !existsSync(join(cwd, f)));
|
|
328
|
+
for (const f of missing) console.log(` missing: ${f}`);
|
|
329
|
+
} else {
|
|
330
|
+
console.log(` [INFO] No audit outputs yet — run 'roleos audit' first`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 4. Check parcel reports
|
|
334
|
+
const parcelFiles = readdirSync(cwd).filter(f => f.startsWith("AUDIT-PARCEL-"));
|
|
335
|
+
if (parcelFiles.length > 0) {
|
|
336
|
+
console.log(` [PASS] ${parcelFiles.length} parcel report(s) found`);
|
|
337
|
+
} else {
|
|
338
|
+
console.log(` [INFO] No parcel reports yet`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const healthy = issues.length === 0 && pathsMissing === 0;
|
|
342
|
+
console.log(`\n${healthy ? "Audit infrastructure verified." : "Some issues found — fix before re-auditing."}\n`);
|
|
343
|
+
if (!healthy) process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Help ─────────────────────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
function cmdHelp() {
|
|
349
|
+
console.log(`
|
|
350
|
+
roleos audit — Deep Audit CLI
|
|
351
|
+
|
|
352
|
+
Usage:
|
|
353
|
+
roleos audit Start a deep audit on the current repo
|
|
354
|
+
roleos audit manifest Show the audit manifest
|
|
355
|
+
roleos audit manifest --generate Generate a skeleton manifest from src/
|
|
356
|
+
roleos audit status Show audit run progress
|
|
357
|
+
roleos audit verify Verify manifest and audit outputs
|
|
358
|
+
roleos audit help Show this help
|
|
359
|
+
|
|
360
|
+
The deep audit decomposes a repo into bounded components, dispatches one
|
|
361
|
+
auditor per component, inspects seams between components, checks test truth,
|
|
362
|
+
then synthesizes into a ranked action plan.
|
|
363
|
+
|
|
364
|
+
Workflow:
|
|
365
|
+
1. roleos audit manifest --generate Create audit-manifest.json
|
|
366
|
+
2. Edit the manifest Define components and boundaries
|
|
367
|
+
3. roleos audit Start the audit run
|
|
368
|
+
4. roleos next Step through each auditor
|
|
369
|
+
5. roleos audit status Check progress
|
|
370
|
+
6. roleos audit verify Verify everything landed
|
|
371
|
+
`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── Manifest validation ──────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
function validateManifest(manifest) {
|
|
377
|
+
const issues = [];
|
|
378
|
+
|
|
379
|
+
if (!manifest.components || !Array.isArray(manifest.components)) {
|
|
380
|
+
issues.push("components must be an array");
|
|
381
|
+
} else {
|
|
382
|
+
if (manifest.components.length === 0) {
|
|
383
|
+
issues.push("components array is empty — add at least one component");
|
|
384
|
+
}
|
|
385
|
+
for (let i = 0; i < manifest.components.length; i++) {
|
|
386
|
+
const c = manifest.components[i];
|
|
387
|
+
if (!c.id) issues.push(`components[${i}] missing id`);
|
|
388
|
+
if (!c.owned_paths || c.owned_paths.length === 0) {
|
|
389
|
+
issues.push(`components[${i}] (${c.id || "?"}) has no owned_paths`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!manifest.boundaries || !Array.isArray(manifest.boundaries)) {
|
|
395
|
+
issues.push("boundaries must be an array");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return issues;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export { validateManifest };
|
package/src/brainstorm-roles.mjs
CHANGED
|
@@ -139,7 +139,10 @@ export const INPUT_PARTITIONS = {
|
|
|
139
139
|
*/
|
|
140
140
|
export function partitionBrief(request, roleName) {
|
|
141
141
|
const partition = INPUT_PARTITIONS[roleName];
|
|
142
|
-
if (!partition)
|
|
142
|
+
if (!partition) {
|
|
143
|
+
// Unknown roles receive only the topic — minimal brief, not full access
|
|
144
|
+
return request.topic !== undefined ? { topic: request.topic } : {};
|
|
145
|
+
}
|
|
143
146
|
|
|
144
147
|
const filtered = {};
|
|
145
148
|
for (const field of partition.permitted) {
|
|
@@ -702,6 +705,46 @@ export function translateToAtoms(roleName, roleOutput) {
|
|
|
702
705
|
return atoms;
|
|
703
706
|
}
|
|
704
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Map from specific claim_kind (atom layer) to broad statement kind (scout layer).
|
|
710
|
+
* This bridges the two naming conventions:
|
|
711
|
+
* - Scout findings use `.kind` ∈ {"claim", "opportunity", "risk", "tension", "unknown"}
|
|
712
|
+
* - Translated atoms use `.claim_kind` with specific types per role
|
|
713
|
+
*/
|
|
714
|
+
export const CLAIM_KIND_TO_STATEMENT_KIND = {
|
|
715
|
+
// Context Analyst → claim
|
|
716
|
+
definition: "claim", category: "claim", lineage: "claim", boundary: "claim",
|
|
717
|
+
// User Value Analyst → opportunity or claim
|
|
718
|
+
need: "opportunity", desire: "opportunity", friction: "risk", willingness: "claim",
|
|
719
|
+
// Mechanics Analyst → claim or risk
|
|
720
|
+
loop: "claim", dependency: "claim", failure_mode: "risk", mechanism: "claim",
|
|
721
|
+
// Positioning Analyst → opportunity
|
|
722
|
+
substitute: "tension", wedge: "opportunity", category_frame: "claim",
|
|
723
|
+
positioning: "claim", timing: "opportunity",
|
|
724
|
+
// Contrarian Analyst → tension
|
|
725
|
+
challenge: "tension", contradiction: "tension", overstatement: "tension", gap: "risk",
|
|
726
|
+
// Normalizer
|
|
727
|
+
adjacency: "claim", avoidance: "risk", constraint: "claim",
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Normalize an atom to include both `.claim_kind` (specific) and `.kind` (broad).
|
|
732
|
+
* Ensures atoms are compatible with both the scout validation layer and
|
|
733
|
+
* the cross-examination layer.
|
|
734
|
+
*
|
|
735
|
+
* @param {object} atom - Atom with claim_kind
|
|
736
|
+
* @returns {object} Atom with both claim_kind and kind fields
|
|
737
|
+
*/
|
|
738
|
+
export function normalizeAtomKind(atom) {
|
|
739
|
+
if (atom.kind && !atom.claim_kind) {
|
|
740
|
+
return { ...atom, claim_kind: atom.kind };
|
|
741
|
+
}
|
|
742
|
+
if (atom.claim_kind && !atom.kind) {
|
|
743
|
+
return { ...atom, kind: CLAIM_KIND_TO_STATEMENT_KIND[atom.claim_kind] || "unknown" };
|
|
744
|
+
}
|
|
745
|
+
return atom;
|
|
746
|
+
}
|
|
747
|
+
|
|
705
748
|
/**
|
|
706
749
|
* Validate that a translated atom preserves all required provenance fields.
|
|
707
750
|
*
|
package/src/composite.mjs
CHANGED
|
@@ -308,14 +308,20 @@ export function failChild(exec, category, reason) {
|
|
|
308
308
|
|
|
309
309
|
/**
|
|
310
310
|
* Find all categories that transitively depend on a given category.
|
|
311
|
+
* Uses a visited set to guard against circular dependency graphs.
|
|
311
312
|
*/
|
|
312
313
|
function findUnreachable(exec, failedCategory) {
|
|
313
314
|
const unreachable = new Set();
|
|
315
|
+
const visited = new Set();
|
|
314
316
|
let changed = true;
|
|
315
317
|
while (changed) {
|
|
316
318
|
changed = false;
|
|
317
319
|
for (const child of exec.children) {
|
|
318
320
|
if (unreachable.has(child.category)) continue;
|
|
321
|
+
if (visited.has(child.category) && !child.dependsOn.includes(failedCategory) && !child.dependsOn.some(d => unreachable.has(d))) {
|
|
322
|
+
continue; // already evaluated without being unreachable — skip
|
|
323
|
+
}
|
|
324
|
+
visited.add(child.category);
|
|
319
325
|
if (child.dependsOn.includes(failedCategory) || child.dependsOn.some(d => unreachable.has(d))) {
|
|
320
326
|
unreachable.add(child.category);
|
|
321
327
|
changed = true;
|
|
@@ -325,6 +331,41 @@ function findUnreachable(exec, failedCategory) {
|
|
|
325
331
|
return [...unreachable];
|
|
326
332
|
}
|
|
327
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Check for circular dependencies in the execution plan.
|
|
336
|
+
* Returns an array of categories involved in cycles (empty if none).
|
|
337
|
+
*
|
|
338
|
+
* @param {CompositeExecution} exec
|
|
339
|
+
* @returns {string[]}
|
|
340
|
+
*/
|
|
341
|
+
export function detectCycles(exec) {
|
|
342
|
+
const resolved = new Set();
|
|
343
|
+
const visiting = new Set();
|
|
344
|
+
const cycles = [];
|
|
345
|
+
|
|
346
|
+
function visit(category) {
|
|
347
|
+
if (resolved.has(category)) return;
|
|
348
|
+
if (visiting.has(category)) {
|
|
349
|
+
cycles.push(category);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
visiting.add(category);
|
|
353
|
+
const child = exec.children.find(c => c.category === category);
|
|
354
|
+
if (child) {
|
|
355
|
+
for (const dep of child.dependsOn) {
|
|
356
|
+
visit(dep);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
visiting.delete(category);
|
|
360
|
+
resolved.add(category);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const child of exec.children) {
|
|
364
|
+
visit(child.category);
|
|
365
|
+
}
|
|
366
|
+
return cycles;
|
|
367
|
+
}
|
|
368
|
+
|
|
328
369
|
// ── Invalidation ──────────────────────────────────────────────────────────────
|
|
329
370
|
|
|
330
371
|
/**
|
package/src/dispatch.mjs
CHANGED
|
@@ -16,85 +16,8 @@
|
|
|
16
16
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
17
17
|
import { join, resolve } from "node:path";
|
|
18
18
|
import { resolveBlocked, resolveRejected } from "./escalation.mjs";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// What tools each role is allowed to use. Sandboxing by contract.
|
|
22
|
-
|
|
23
|
-
const TOOL_PROFILES = {
|
|
24
|
-
// Core
|
|
25
|
-
"Orchestrator": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
26
|
-
"Product Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
27
|
-
"Critic Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
28
|
-
|
|
29
|
-
// Design
|
|
30
|
-
"UI Designer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
31
|
-
"Brand Guardian": ["Read", "Glob", "Grep"],
|
|
32
|
-
|
|
33
|
-
// Engineering
|
|
34
|
-
"Backend Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
35
|
-
"Frontend Developer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
36
|
-
"Test Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
37
|
-
"Performance Engineer": ["Read", "Glob", "Grep", "Bash"],
|
|
38
|
-
"Refactor Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
39
|
-
"Security Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
40
|
-
"Dependency Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
41
|
-
|
|
42
|
-
// Treatment
|
|
43
|
-
"Repo Researcher": ["Read", "Glob", "Grep", "Bash"],
|
|
44
|
-
"Repo Translator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
45
|
-
"Docs Architect": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
46
|
-
"Metadata Curator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
47
|
-
"Coverage Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
48
|
-
"Deployment Verifier": ["Read", "Glob", "Grep", "Bash"],
|
|
49
|
-
"Release Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
50
|
-
|
|
51
|
-
// Growth / Marketing
|
|
52
|
-
"Launch Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
53
|
-
"Content Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
54
|
-
"Community Manager": ["Read", "Glob", "Grep", "Write"],
|
|
55
|
-
"Support Triage Lead": ["Read", "Glob", "Grep", "Write"],
|
|
56
|
-
"Launch Copywriter": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
57
|
-
|
|
58
|
-
// Product
|
|
59
|
-
"Feedback Synthesizer": ["Read", "Glob", "Grep"],
|
|
60
|
-
"Roadmap Prioritizer": ["Read", "Glob", "Grep", "Write"],
|
|
61
|
-
"Spec Writer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
62
|
-
|
|
63
|
-
// Research
|
|
64
|
-
"UX Researcher": ["Read", "Glob", "Grep"],
|
|
65
|
-
"Competitive Analyst": ["Read", "Glob", "Grep"],
|
|
66
|
-
"Trend Researcher": ["Read", "Glob", "Grep"],
|
|
67
|
-
"User Interview Synthesizer": ["Read", "Glob", "Grep"],
|
|
68
|
-
|
|
69
|
-
// Brainstorm
|
|
70
|
-
"Context Scout": ["Read", "Glob", "Grep"],
|
|
71
|
-
"User Value Scout": ["Read", "Glob", "Grep"],
|
|
72
|
-
"Creative Leap Scout": ["Read", "Glob", "Grep"],
|
|
73
|
-
"Normalizer": ["Read", "Glob", "Grep"],
|
|
74
|
-
"Synthesizer": ["Read", "Glob", "Grep", "Write"],
|
|
75
|
-
"Product Expander": ["Read", "Glob", "Grep", "Write"],
|
|
76
|
-
"Judge": ["Read", "Glob", "Grep"],
|
|
77
|
-
"Mechanics Scout": ["Read", "Glob", "Grep"],
|
|
78
|
-
"Market Scout": ["Read", "Glob", "Grep"],
|
|
79
|
-
"Contrarian Scout": ["Read", "Glob", "Grep"],
|
|
80
|
-
"Feasibility Scout": ["Read", "Glob", "Grep"],
|
|
81
|
-
"Quality Bar Scout": ["Read", "Glob", "Grep"],
|
|
82
|
-
"Scenario Expander": ["Read", "Glob", "Grep", "Write"],
|
|
83
|
-
"Moat Expander": ["Read", "Glob", "Grep", "Write"],
|
|
84
|
-
|
|
85
|
-
// Brainstorm v0.3 analysts
|
|
86
|
-
"Context Analyst": ["Read", "Glob", "Grep"],
|
|
87
|
-
"User Value Analyst": ["Read", "Glob", "Grep"],
|
|
88
|
-
"Mechanics Analyst": ["Read", "Glob", "Grep"],
|
|
89
|
-
"Positioning Analyst": ["Read", "Glob", "Grep"],
|
|
90
|
-
"Contrarian Analyst": ["Read", "Glob", "Grep"],
|
|
91
|
-
|
|
92
|
-
// Deep Audit
|
|
93
|
-
"Component Auditor": ["Read", "Glob", "Grep"],
|
|
94
|
-
"Seam Auditor": ["Read", "Glob", "Grep"],
|
|
95
|
-
"Test Truth Auditor": ["Read", "Glob", "Grep"],
|
|
96
|
-
"Audit Synthesizer": ["Read", "Glob", "Grep", "Write"],
|
|
97
|
-
};
|
|
19
|
+
import { TOOL_PROFILES } from "./tool-profiles.mjs";
|
|
20
|
+
import { renderKnowledgeBlock, knowledgeManifestSummary } from "./knowledge/render-knowledge-block.mjs";
|
|
98
21
|
|
|
99
22
|
// ── Default role config ─────────────────────────────────────────────────────
|
|
100
23
|
|
|
@@ -120,7 +43,9 @@ export const EXEC_STATES = [
|
|
|
120
43
|
|
|
121
44
|
// ── System prompt builder ─────────────────────────────────────────────────────
|
|
122
45
|
|
|
123
|
-
function buildRolePrompt(roleName, packetContent, chainContext) {
|
|
46
|
+
function buildRolePrompt(roleName, packetContent, chainContext, packetKnowledge) {
|
|
47
|
+
const knowledgeBlock = renderKnowledgeBlock(packetKnowledge);
|
|
48
|
+
|
|
124
49
|
return `You are operating as ${roleName} in a Role-OS managed chain.
|
|
125
50
|
|
|
126
51
|
## Your Role Contract
|
|
@@ -133,7 +58,7 @@ ${packetContent}
|
|
|
133
58
|
You are step ${chainContext.stepNumber} of ${chainContext.totalSteps} in this chain.
|
|
134
59
|
${chainContext.previousRole ? `Previous role: ${chainContext.previousRole} (${chainContext.previousStatus})` : "You are the first role in this chain."}
|
|
135
60
|
${chainContext.nextRole ? `Next role: ${chainContext.nextRole}` : "You are the last role before Critic review."}
|
|
136
|
-
|
|
61
|
+
${knowledgeBlock ? `\n${knowledgeBlock}\n` : ""}
|
|
137
62
|
## Handoff Requirements
|
|
138
63
|
When you finish, produce a structured handoff:
|
|
139
64
|
1. Summary of what you did
|
|
@@ -162,7 +87,7 @@ When you finish, produce a structured handoff:
|
|
|
162
87
|
* @param {Object} [options.overrides] - Per-role config overrides
|
|
163
88
|
* @returns {DispatchManifest}
|
|
164
89
|
*/
|
|
165
|
-
export function buildDispatchManifest({ packetFile, packetContent, chainRoles, cwd, overrides = {} }) {
|
|
90
|
+
export function buildDispatchManifest({ packetFile, packetContent, chainRoles, cwd, overrides = {}, packetKnowledge = null }) {
|
|
166
91
|
const runId = `run-${Date.now()}`;
|
|
167
92
|
const steps = [];
|
|
168
93
|
|
|
@@ -184,7 +109,7 @@ export function buildDispatchManifest({ packetFile, packetContent, chainRoles, c
|
|
|
184
109
|
role: role.name,
|
|
185
110
|
pack: role.pack,
|
|
186
111
|
tools: TOOL_PROFILES[role.name] || ["Read", "Glob", "Grep"],
|
|
187
|
-
systemPrompt: buildRolePrompt(role.name, packetContent, chainContext),
|
|
112
|
+
systemPrompt: buildRolePrompt(role.name, packetContent, chainContext, packetKnowledge),
|
|
188
113
|
model: roleOverrides.model || DEFAULTS.model,
|
|
189
114
|
maxTurns: roleOverrides.maxTurns || DEFAULTS.maxTurns,
|
|
190
115
|
maxBudgetUsd: roleOverrides.maxBudgetUsd || DEFAULTS.maxBudgetUsd,
|
|
@@ -195,6 +120,7 @@ export function buildDispatchManifest({ packetFile, packetContent, chainRoles, c
|
|
|
195
120
|
from: i > 0 ? chainRoles[i - 1].role.name : null,
|
|
196
121
|
to: i < chainRoles.length - 1 ? chainRoles[i + 1].role.name : null,
|
|
197
122
|
},
|
|
123
|
+
knowledge: knowledgeManifestSummary(packetKnowledge),
|
|
198
124
|
});
|
|
199
125
|
}
|
|
200
126
|
|