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.
@@ -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 };
@@ -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) return request; // Unknown role gets full brief (fallback)
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
- // ── Tool profiles per role ────────────────────────────────────────────────────
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