role-os 2.2.0 → 2.2.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/CHANGELOG.md +21 -0
- package/README.md +12 -3
- package/bin/roleos.mjs +9 -0
- package/package.json +1 -1
- package/src/artifacts.mjs +9 -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 +1 -79
- package/src/hooks.mjs +5 -5
- package/src/mission-run.mjs +5 -0
- package/src/run.mjs +9 -2
- package/src/state-machine.mjs +70 -0
- package/src/tool-profiles.mjs +82 -0
- package/src/trial.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.1
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`roleos audit` CLI** — first-class entry point for deep audit with subcommands: `audit`, `audit manifest`, `audit manifest --generate`, `audit status`, `audit verify`
|
|
7
|
+
- **Shared state machine** (`src/state-machine.mjs`) — canonical step/run transitions shared by both runners
|
|
8
|
+
- **Shared tool profiles** (`src/tool-profiles.mjs`) — extracted from dispatch.mjs to break trial→dispatch coupling
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **P3-1:** Cycle detection in composite execution (`detectCycles` + visited-set guard in `findUnreachable`)
|
|
12
|
+
- **P3-2:** Dual-active guard in `startNext`/`startNextStep` prevents two steps active simultaneously
|
|
13
|
+
- **P3-3:** Atomic persistence — `saveRun` writes to temp file then renames
|
|
14
|
+
- **P4-1:** Dependency Auditor has own artifact contract (`dependency-audit`), pack handoff corrected
|
|
15
|
+
- **P4-2:** `partitionBrief` returns topic-only for unknown roles instead of full brief
|
|
16
|
+
- **P4-3:** Atom kind normalization layer bridges scout `.kind` and atom `.claim_kind`
|
|
17
|
+
- **P4-4:** `/dev/stdin` → `readFileSync(0)` for Windows compatibility in all 5 hooks
|
|
18
|
+
- **P4-5:** TOOL_PROFILES extracted to shared module, eliminating trial→dispatch coupling
|
|
19
|
+
- Node 18 compatibility fix for `import.meta.dirname` in deep-audit-proof test
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- 18 new tests (audit-cmd, audit-p5, deep-audit-proof) — total: 954
|
|
23
|
+
|
|
3
24
|
## 2.2.0
|
|
4
25
|
|
|
5
26
|
### Added
|
package/README.md
CHANGED
|
@@ -134,6 +134,12 @@ roleos complete artifact.md # Complete with artifact
|
|
|
134
134
|
roleos explain # Show full state
|
|
135
135
|
roleos report # Completion report
|
|
136
136
|
|
|
137
|
+
# Deep audit:
|
|
138
|
+
roleos audit manifest --generate # Create audit-manifest.json
|
|
139
|
+
roleos audit # Start component-level deep audit
|
|
140
|
+
roleos audit status # Check audit progress
|
|
141
|
+
roleos audit verify # Verify manifest and outputs
|
|
142
|
+
|
|
137
143
|
# Or go manual:
|
|
138
144
|
roleos start "fix the crash" # Entry decision only (no run)
|
|
139
145
|
roleos packet new feature
|
|
@@ -207,18 +213,21 @@ role-os/
|
|
|
207
213
|
entry-cmd.mjs ← `roleos start` CLI command
|
|
208
214
|
run.mjs ← Persistent run engine: create → step → pause → resume → report
|
|
209
215
|
run-cmd.mjs ← `roleos run/resume/next/explain/complete/fail` + interventions
|
|
210
|
-
mission.mjs ←
|
|
216
|
+
mission.mjs ← 8 named mission types (feature, bugfix, treatment, docs, security, research, brainstorm, deep-audit)
|
|
211
217
|
mission-run.mjs ← Mission runner: create → step → complete → report
|
|
212
218
|
mission-cmd.mjs ← `roleos mission` CLI commands
|
|
219
|
+
audit-cmd.mjs ← `roleos audit` — deep audit entry point with manifest generation
|
|
213
220
|
route.mjs ← 54-role routing + dynamic chain builder
|
|
214
221
|
packs.mjs ← 9 calibrated team packs + auto-selection
|
|
215
222
|
conflicts.mjs ← 4-pass conflict detection
|
|
216
223
|
escalation.mjs ← Auto-routing for blocked/rejected/split
|
|
217
224
|
evidence.mjs ← Structured evidence + role-aware requirements
|
|
218
225
|
dispatch.mjs ← Runtime dispatch manifests for multi-claude
|
|
226
|
+
tool-profiles.mjs ← Per-role tool sandboxing (shared by dispatch + trial)
|
|
227
|
+
state-machine.mjs ← Canonical step/run transition maps
|
|
219
228
|
artifacts.mjs ← Per-role artifact contracts + pack handoffs
|
|
220
229
|
decompose.mjs ← Composite task detection + splitting
|
|
221
|
-
composite.mjs ← Dependency-ordered execution + recovery
|
|
230
|
+
composite.mjs ← Dependency-ordered execution + recovery + cycle detection
|
|
222
231
|
replan.mjs ← Mid-run adaptive replanning
|
|
223
232
|
calibration.mjs ← Outcome recording + weight tuning
|
|
224
233
|
hooks.mjs ← 5 lifecycle hooks for runtime enforcement
|
|
@@ -226,7 +235,7 @@ role-os/
|
|
|
226
235
|
brainstorm.mjs ← Evidence modes, request validation, finding/synthesis/judge schemas
|
|
227
236
|
brainstorm-roles.mjs ← Role-native schemas, input partitioning, blindspot enforcement, cross-exam
|
|
228
237
|
brainstorm-render.mjs ← Two-layer rendering: lexical bans, render schemas, debate transcript
|
|
229
|
-
test/ ←
|
|
238
|
+
test/ ← 954 tests across 33 test files
|
|
230
239
|
starter-pack/ ← Drop-in role contracts, policies, schemas, workflows
|
|
231
240
|
```
|
|
232
241
|
|
package/bin/roleos.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { packsCommand } from "../src/packs-cmd.mjs";
|
|
|
12
12
|
import { scaffoldClaude, doctor, formatDoctor } from "../src/session.mjs";
|
|
13
13
|
import { artifactsCommand } from "../src/artifacts-cmd.mjs";
|
|
14
14
|
import { missionCommand } from "../src/mission-cmd.mjs";
|
|
15
|
+
import { auditCommand } from "../src/audit-cmd.mjs";
|
|
15
16
|
import { startCommand } from "../src/entry-cmd.mjs";
|
|
16
17
|
import {
|
|
17
18
|
runCommand, resumeCommand, nextCommand, explainCommand,
|
|
@@ -59,6 +60,11 @@ Usage:
|
|
|
59
60
|
roleos artifacts show <role> Show artifact contract for a role
|
|
60
61
|
roleos artifacts validate <role> <file> Validate a file against a contract
|
|
61
62
|
roleos artifacts chain <pack> Show pack handoff flow
|
|
63
|
+
roleos audit Start a deep audit on the current repo
|
|
64
|
+
roleos audit manifest Show the audit manifest
|
|
65
|
+
roleos audit manifest --generate Generate a skeleton manifest from src/
|
|
66
|
+
roleos audit status Show audit run progress
|
|
67
|
+
roleos audit verify Verify manifest and audit outputs
|
|
62
68
|
roleos mission list List all missions
|
|
63
69
|
roleos mission show <key> Show full mission detail
|
|
64
70
|
roleos mission suggest <text> Suggest a mission for a task
|
|
@@ -181,6 +187,9 @@ try {
|
|
|
181
187
|
case "friction":
|
|
182
188
|
await frictionCommand(args);
|
|
183
189
|
break;
|
|
190
|
+
case "audit":
|
|
191
|
+
await auditCommand(args);
|
|
192
|
+
break;
|
|
184
193
|
case "mission":
|
|
185
194
|
await missionCommand(args);
|
|
186
195
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "role-os",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Role OS — a multi-Claude operating system where 54 specialized roles execute work through contracts, conflict detection, escalation, and structured evidence. 9 team packs, 8 missions including deep audit with manifest-scaled dynamic dispatch and brainstorm with traceable disagreement.",
|
|
5
5
|
"homepage": "https://mcp-tool-shop-org.github.io/role-os/",
|
|
6
6
|
"bugs": {
|
package/src/artifacts.mjs
CHANGED
|
@@ -106,6 +106,14 @@ export const ROLE_ARTIFACT_CONTRACTS = {
|
|
|
106
106
|
consumedBy: ["Backend Engineer", "Coverage Auditor", "Security Reviewer"],
|
|
107
107
|
completionRule: "Entrypoints listed. Module responsibilities described. Commands documented.",
|
|
108
108
|
},
|
|
109
|
+
"Dependency Auditor": {
|
|
110
|
+
artifactType: "dependency-audit",
|
|
111
|
+
requiredSections: ["vulnerability-summary", "outdated-inventory"],
|
|
112
|
+
optionalSections: ["supply-chain-risks", "update-recommendations", "license-audit"],
|
|
113
|
+
requiredEvidence: [],
|
|
114
|
+
consumedBy: ["Critic Reviewer", "Security Reviewer"],
|
|
115
|
+
completionRule: "Vulnerabilities triaged. Outdated deps inventoried with severity.",
|
|
116
|
+
},
|
|
109
117
|
"Metadata Curator": {
|
|
110
118
|
artifactType: "metadata-audit",
|
|
111
119
|
requiredSections: ["manifest-audit", "registry-alignment"],
|
|
@@ -380,7 +388,7 @@ export const PACK_HANDOFF_CONTRACTS = {
|
|
|
380
388
|
security: {
|
|
381
389
|
flow: [
|
|
382
390
|
{ role: "Security Reviewer", produces: "security-findings", consumedBy: "Critic Reviewer" },
|
|
383
|
-
{ role: "Dependency Auditor", produces: "
|
|
391
|
+
{ role: "Dependency Auditor", produces: "dependency-audit", consumedBy: "Critic Reviewer" },
|
|
384
392
|
{ role: "Critic Reviewer", produces: "verdict", consumedBy: null },
|
|
385
393
|
],
|
|
386
394
|
},
|
|
@@ -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,7 @@
|
|
|
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";
|
|
98
20
|
|
|
99
21
|
// ── Default role config ─────────────────────────────────────────────────────
|
|
100
22
|
|
package/src/hooks.mjs
CHANGED
|
@@ -322,7 +322,7 @@ function generateSessionStartScript() {
|
|
|
322
322
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
323
323
|
import { join } from "node:path";
|
|
324
324
|
|
|
325
|
-
const input = JSON.parse(readFileSync(
|
|
325
|
+
const input = JSON.parse(readFileSync(0, "utf-8").toString() || "{}");
|
|
326
326
|
const cwd = input.cwd || process.cwd();
|
|
327
327
|
const stateDir = join(cwd, ".claude", "hooks");
|
|
328
328
|
mkdirSync(stateDir, { recursive: true });
|
|
@@ -356,7 +356,7 @@ function generatePromptSubmitScript() {
|
|
|
356
356
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
357
357
|
import { join } from "node:path";
|
|
358
358
|
|
|
359
|
-
const input = JSON.parse(readFileSync(
|
|
359
|
+
const input = JSON.parse(readFileSync(0, "utf-8").toString() || "{}");
|
|
360
360
|
const cwd = input.cwd || process.cwd();
|
|
361
361
|
const statePath = join(cwd, ".claude", "hooks", "session-state.json");
|
|
362
362
|
|
|
@@ -389,7 +389,7 @@ function generatePreToolUseScript() {
|
|
|
389
389
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
390
390
|
import { join } from "node:path";
|
|
391
391
|
|
|
392
|
-
const input = JSON.parse(readFileSync(
|
|
392
|
+
const input = JSON.parse(readFileSync(0, "utf-8").toString() || "{}");
|
|
393
393
|
const cwd = input.cwd || process.cwd();
|
|
394
394
|
const statePath = join(cwd, ".claude", "hooks", "session-state.json");
|
|
395
395
|
|
|
@@ -421,7 +421,7 @@ function generateSubagentStartScript() {
|
|
|
421
421
|
import { readFileSync, existsSync } from "node:fs";
|
|
422
422
|
import { join } from "node:path";
|
|
423
423
|
|
|
424
|
-
const input = JSON.parse(readFileSync(
|
|
424
|
+
const input = JSON.parse(readFileSync(0, "utf-8").toString() || "{}");
|
|
425
425
|
const cwd = input.cwd || process.cwd();
|
|
426
426
|
const statePath = join(cwd, ".claude", "hooks", "session-state.json");
|
|
427
427
|
|
|
@@ -444,7 +444,7 @@ function generateStopScript() {
|
|
|
444
444
|
import { readFileSync, existsSync } from "node:fs";
|
|
445
445
|
import { join } from "node:path";
|
|
446
446
|
|
|
447
|
-
const input = JSON.parse(readFileSync(
|
|
447
|
+
const input = JSON.parse(readFileSync(0, "utf-8").toString() || "{}");
|
|
448
448
|
const cwd = input.cwd || process.cwd();
|
|
449
449
|
const statePath = join(cwd, ".claude", "hooks", "session-state.json");
|
|
450
450
|
|
package/src/mission-run.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { MISSIONS, getMission, validateMission } from "./mission.mjs";
|
|
13
13
|
import { validateArtifact } from "./artifacts.mjs";
|
|
14
|
+
import { STEP_TRANSITIONS, isValidStepTransition } from "./state-machine.mjs";
|
|
14
15
|
|
|
15
16
|
let _runCounter = 0;
|
|
16
17
|
|
|
@@ -198,6 +199,10 @@ function buildDynamicSteps(mission, manifest) {
|
|
|
198
199
|
* @returns {MissionStep|null} The started step, or null if no pending steps
|
|
199
200
|
*/
|
|
200
201
|
export function startNextStep(run) {
|
|
202
|
+
// Guard: refuse to activate a new step if one is already active (prevents dual-active)
|
|
203
|
+
const alreadyActive = run.steps.find((s) => s.status === "active");
|
|
204
|
+
if (alreadyActive) return null;
|
|
205
|
+
|
|
201
206
|
const next = run.steps.find((s) => s.status === "pending");
|
|
202
207
|
if (!next) return null;
|
|
203
208
|
|
package/src/run.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Interventions: reroute, split, escalate, retry, block, reopen
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "node:fs";
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { decideEntry } from "./entry.mjs";
|
|
18
18
|
import { getMission } from "./mission.mjs";
|
|
@@ -285,6 +285,10 @@ function guessArtifact(roleName) {
|
|
|
285
285
|
* @returns {RunStep|null}
|
|
286
286
|
*/
|
|
287
287
|
export function startNext(run, cwd) {
|
|
288
|
+
// Guard: refuse to activate a new step if one is already active (prevents dual-active)
|
|
289
|
+
const alreadyActive = run.steps.find(s => s.status === "active");
|
|
290
|
+
if (alreadyActive) return null;
|
|
291
|
+
|
|
288
292
|
const next = run.steps.find(s => s.status === "pending");
|
|
289
293
|
if (!next) return null;
|
|
290
294
|
|
|
@@ -854,7 +858,10 @@ export function formatReport(report) {
|
|
|
854
858
|
export function saveRun(cwd, run) {
|
|
855
859
|
const dir = runsDir(cwd);
|
|
856
860
|
mkdirSync(dir, { recursive: true });
|
|
857
|
-
|
|
861
|
+
const target = runPath(cwd, run.id);
|
|
862
|
+
const tmp = target + ".tmp";
|
|
863
|
+
writeFileSync(tmp, JSON.stringify(run, null, 2));
|
|
864
|
+
renameSync(tmp, target);
|
|
858
865
|
}
|
|
859
866
|
|
|
860
867
|
/**
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared State Machine — valid transitions for run steps.
|
|
3
|
+
*
|
|
4
|
+
* Both run.mjs (persistent runner) and mission-run.mjs (mission runner)
|
|
5
|
+
* use the same step lifecycle. This module defines the canonical transitions
|
|
6
|
+
* so they cannot diverge.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Valid step status transitions.
|
|
11
|
+
* Key: current status. Value: array of allowed next statuses.
|
|
12
|
+
*/
|
|
13
|
+
export const STEP_TRANSITIONS = {
|
|
14
|
+
pending: ["active"],
|
|
15
|
+
active: ["completed", "partial", "failed", "blocked"],
|
|
16
|
+
completed: ["pending"], // re-opened by escalation
|
|
17
|
+
partial: ["pending"], // retried
|
|
18
|
+
failed: ["pending"], // retried
|
|
19
|
+
blocked: ["pending"], // unblocked
|
|
20
|
+
skipped: [], // terminal
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Valid run status transitions.
|
|
25
|
+
*/
|
|
26
|
+
export const RUN_TRANSITIONS = {
|
|
27
|
+
planning: ["running"],
|
|
28
|
+
running: ["paused", "completed", "partial", "failed"],
|
|
29
|
+
paused: ["running"],
|
|
30
|
+
completed: ["paused"], // re-opened by escalation/reopen
|
|
31
|
+
partial: ["paused"], // retried
|
|
32
|
+
failed: ["paused"], // retried
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if a step transition is valid.
|
|
37
|
+
* @param {string} from - Current status
|
|
38
|
+
* @param {string} to - Desired status
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export function isValidStepTransition(from, to) {
|
|
42
|
+
const allowed = STEP_TRANSITIONS[from];
|
|
43
|
+
if (!allowed) return false;
|
|
44
|
+
return allowed.includes(to);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a run transition is valid.
|
|
49
|
+
* @param {string} from - Current status
|
|
50
|
+
* @param {string} to - Desired status
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
export function isValidRunTransition(from, to) {
|
|
54
|
+
const allowed = RUN_TRANSITIONS[from];
|
|
55
|
+
if (!allowed) return false;
|
|
56
|
+
return allowed.includes(to);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Assert a step transition is valid, or throw.
|
|
61
|
+
* @param {string} from
|
|
62
|
+
* @param {string} to
|
|
63
|
+
* @param {string} [context] - Description for error message
|
|
64
|
+
*/
|
|
65
|
+
export function assertStepTransition(from, to, context = "") {
|
|
66
|
+
if (!isValidStepTransition(from, to)) {
|
|
67
|
+
const prefix = context ? `${context}: ` : "";
|
|
68
|
+
throw new Error(`${prefix}Invalid step transition "${from}" → "${to}"`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Profiles — per-role tool sandboxing.
|
|
3
|
+
*
|
|
4
|
+
* Extracted to a shared module so that both dispatch.mjs and trial.mjs
|
|
5
|
+
* can import it without creating a circular dependency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const TOOL_PROFILES = {
|
|
9
|
+
// Core
|
|
10
|
+
"Orchestrator": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
11
|
+
"Product Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
12
|
+
"Critic Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
13
|
+
|
|
14
|
+
// Design
|
|
15
|
+
"UI Designer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
16
|
+
"Brand Guardian": ["Read", "Glob", "Grep"],
|
|
17
|
+
|
|
18
|
+
// Engineering
|
|
19
|
+
"Backend Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
20
|
+
"Frontend Developer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
21
|
+
"Test Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
22
|
+
"Performance Engineer": ["Read", "Glob", "Grep", "Bash"],
|
|
23
|
+
"Refactor Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
24
|
+
"Security Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
25
|
+
"Dependency Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
26
|
+
|
|
27
|
+
// Treatment
|
|
28
|
+
"Repo Researcher": ["Read", "Glob", "Grep", "Bash"],
|
|
29
|
+
"Repo Translator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
30
|
+
"Docs Architect": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
31
|
+
"Metadata Curator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
32
|
+
"Coverage Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
33
|
+
"Deployment Verifier": ["Read", "Glob", "Grep", "Bash"],
|
|
34
|
+
"Release Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
35
|
+
|
|
36
|
+
// Growth / Marketing
|
|
37
|
+
"Launch Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
38
|
+
"Content Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
39
|
+
"Community Manager": ["Read", "Glob", "Grep", "Write"],
|
|
40
|
+
"Support Triage Lead": ["Read", "Glob", "Grep", "Write"],
|
|
41
|
+
"Launch Copywriter": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
42
|
+
|
|
43
|
+
// Product
|
|
44
|
+
"Feedback Synthesizer": ["Read", "Glob", "Grep"],
|
|
45
|
+
"Roadmap Prioritizer": ["Read", "Glob", "Grep", "Write"],
|
|
46
|
+
"Spec Writer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
47
|
+
|
|
48
|
+
// Research
|
|
49
|
+
"UX Researcher": ["Read", "Glob", "Grep"],
|
|
50
|
+
"Competitive Analyst": ["Read", "Glob", "Grep"],
|
|
51
|
+
"Trend Researcher": ["Read", "Glob", "Grep"],
|
|
52
|
+
"User Interview Synthesizer": ["Read", "Glob", "Grep"],
|
|
53
|
+
|
|
54
|
+
// Brainstorm
|
|
55
|
+
"Context Scout": ["Read", "Glob", "Grep"],
|
|
56
|
+
"User Value Scout": ["Read", "Glob", "Grep"],
|
|
57
|
+
"Creative Leap Scout": ["Read", "Glob", "Grep"],
|
|
58
|
+
"Normalizer": ["Read", "Glob", "Grep"],
|
|
59
|
+
"Synthesizer": ["Read", "Glob", "Grep", "Write"],
|
|
60
|
+
"Product Expander": ["Read", "Glob", "Grep", "Write"],
|
|
61
|
+
"Judge": ["Read", "Glob", "Grep"],
|
|
62
|
+
"Mechanics Scout": ["Read", "Glob", "Grep"],
|
|
63
|
+
"Market Scout": ["Read", "Glob", "Grep"],
|
|
64
|
+
"Contrarian Scout": ["Read", "Glob", "Grep"],
|
|
65
|
+
"Feasibility Scout": ["Read", "Glob", "Grep"],
|
|
66
|
+
"Quality Bar Scout": ["Read", "Glob", "Grep"],
|
|
67
|
+
"Scenario Expander": ["Read", "Glob", "Grep", "Write"],
|
|
68
|
+
"Moat Expander": ["Read", "Glob", "Grep", "Write"],
|
|
69
|
+
|
|
70
|
+
// Brainstorm v0.3 analysts
|
|
71
|
+
"Context Analyst": ["Read", "Glob", "Grep"],
|
|
72
|
+
"User Value Analyst": ["Read", "Glob", "Grep"],
|
|
73
|
+
"Mechanics Analyst": ["Read", "Glob", "Grep"],
|
|
74
|
+
"Positioning Analyst": ["Read", "Glob", "Grep"],
|
|
75
|
+
"Contrarian Analyst": ["Read", "Glob", "Grep"],
|
|
76
|
+
|
|
77
|
+
// Deep Audit
|
|
78
|
+
"Component Auditor": ["Read", "Glob", "Grep"],
|
|
79
|
+
"Seam Auditor": ["Read", "Glob", "Grep"],
|
|
80
|
+
"Test Truth Auditor": ["Read", "Glob", "Grep"],
|
|
81
|
+
"Audit Synthesizer": ["Read", "Glob", "Grep", "Write"],
|
|
82
|
+
};
|
package/src/trial.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { ROLE_CATALOG, scoreRole, MIN_SCORE_THRESHOLD } from "./route.mjs";
|
|
13
|
-
import { TOOL_PROFILES } from "./
|
|
13
|
+
import { TOOL_PROFILES } from "./tool-profiles.mjs";
|
|
14
14
|
import { getRequirements } from "./evidence.mjs";
|
|
15
15
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
16
16
|
import { join, resolve } from "node:path";
|