role-os 1.2.0 → 1.4.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 +58 -0
- package/README.md +9 -2
- package/bin/roleos.mjs +23 -1
- package/package.json +1 -1
- package/src/calibration.mjs +292 -0
- package/src/composite.mjs +454 -0
- package/src/decompose.mjs +311 -0
- package/src/packs.mjs +33 -5
- package/src/replan.mjs +404 -0
- package/src/session.mjs +337 -0
package/src/session.mjs
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Spine — Phase Q (v1.4.0)
|
|
3
|
+
*
|
|
4
|
+
* Makes Role-OS the session substrate, not just a package.
|
|
5
|
+
* Scaffolds CLAUDE.md instructions, skills, and hooks so that
|
|
6
|
+
* Claude Code enters every session through role-os routing.
|
|
7
|
+
*
|
|
8
|
+
* Extension points used:
|
|
9
|
+
* - CLAUDE.md: project instructions loaded at session start
|
|
10
|
+
* - .claude/commands/: slash commands (skills) invokable by user or auto-matched
|
|
11
|
+
* - Hooks: lifecycle events configured in settings
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { writeFileSafe } from "./fs-utils.mjs";
|
|
17
|
+
|
|
18
|
+
// ── roleos init claude ────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scaffold Claude Code integration files into a repo.
|
|
22
|
+
* Creates: CLAUDE.md addition, /roleos-route command, and session guidance.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} cwd - Working directory
|
|
25
|
+
* @param {object} [options]
|
|
26
|
+
* @param {boolean} [options.force] - Overwrite existing files
|
|
27
|
+
* @returns {{ created: string[], skipped: string[] }}
|
|
28
|
+
*/
|
|
29
|
+
export function scaffoldClaude(cwd, options = {}) {
|
|
30
|
+
const created = [];
|
|
31
|
+
const skipped = [];
|
|
32
|
+
|
|
33
|
+
// 1. CLAUDE.md — session entry instructions
|
|
34
|
+
const claudeMd = join(cwd, "CLAUDE.md");
|
|
35
|
+
const claudeContent = generateClaudeMd();
|
|
36
|
+
if (!existsSync(claudeMd) || options.force) {
|
|
37
|
+
writeFileSync(claudeMd, claudeContent);
|
|
38
|
+
created.push("CLAUDE.md");
|
|
39
|
+
} else {
|
|
40
|
+
// Append role-os section if not already present
|
|
41
|
+
const existing = readFileSync(claudeMd, "utf-8");
|
|
42
|
+
if (!existing.includes("## Role OS")) {
|
|
43
|
+
writeFileSync(claudeMd, existing + "\n\n" + claudeContent);
|
|
44
|
+
created.push("CLAUDE.md (appended)");
|
|
45
|
+
} else {
|
|
46
|
+
skipped.push("CLAUDE.md (Role OS section already present)");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Slash command: /roleos-route
|
|
51
|
+
const cmdDir = join(cwd, ".claude", "commands");
|
|
52
|
+
mkdirSync(cmdDir, { recursive: true });
|
|
53
|
+
const routeCmd = join(cmdDir, "roleos-route.md");
|
|
54
|
+
if (!existsSync(routeCmd) || options.force) {
|
|
55
|
+
writeFileSync(routeCmd, generateRouteCommand());
|
|
56
|
+
created.push(".claude/commands/roleos-route.md");
|
|
57
|
+
} else {
|
|
58
|
+
skipped.push(".claude/commands/roleos-route.md");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 3. Slash command: /roleos-review
|
|
62
|
+
const reviewCmd = join(cmdDir, "roleos-review.md");
|
|
63
|
+
if (!existsSync(reviewCmd) || options.force) {
|
|
64
|
+
writeFileSync(reviewCmd, generateReviewCommand());
|
|
65
|
+
created.push(".claude/commands/roleos-review.md");
|
|
66
|
+
} else {
|
|
67
|
+
skipped.push(".claude/commands/roleos-review.md");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 4. Slash command: /roleos-status
|
|
71
|
+
const statusCmd = join(cmdDir, "roleos-status.md");
|
|
72
|
+
if (!existsSync(statusCmd) || options.force) {
|
|
73
|
+
writeFileSync(statusCmd, generateStatusCommand());
|
|
74
|
+
created.push(".claude/commands/roleos-status.md");
|
|
75
|
+
} else {
|
|
76
|
+
skipped.push(".claude/commands/roleos-status.md");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { created, skipped };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── roleos doctor ─────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @typedef {Object} DoctorCheck
|
|
86
|
+
* @property {string} name
|
|
87
|
+
* @property {"pass"|"fail"|"warn"} status
|
|
88
|
+
* @property {string} detail
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Verify that a repo is correctly wired for Role-OS session integration.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} cwd
|
|
95
|
+
* @returns {{ checks: DoctorCheck[], healthy: boolean }}
|
|
96
|
+
*/
|
|
97
|
+
export function doctor(cwd) {
|
|
98
|
+
const checks = [];
|
|
99
|
+
|
|
100
|
+
// Check 1: .claude/ directory exists
|
|
101
|
+
const claudeDir = join(cwd, ".claude");
|
|
102
|
+
checks.push({
|
|
103
|
+
name: ".claude/ directory",
|
|
104
|
+
status: existsSync(claudeDir) ? "pass" : "fail",
|
|
105
|
+
detail: existsSync(claudeDir) ? "exists" : "missing — run roleos init first",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Check 2: CLAUDE.md exists and has Role OS section
|
|
109
|
+
const claudeMd = join(cwd, "CLAUDE.md");
|
|
110
|
+
if (existsSync(claudeMd)) {
|
|
111
|
+
const content = readFileSync(claudeMd, "utf-8");
|
|
112
|
+
if (content.includes("## Role OS")) {
|
|
113
|
+
checks.push({ name: "CLAUDE.md Role OS section", status: "pass", detail: "present" });
|
|
114
|
+
} else {
|
|
115
|
+
checks.push({ name: "CLAUDE.md Role OS section", status: "warn", detail: "CLAUDE.md exists but has no Role OS section — run roleos init claude" });
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
checks.push({ name: "CLAUDE.md", status: "fail", detail: "missing — run roleos init claude" });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check 3: /roleos-route command exists
|
|
122
|
+
const routeCmd = join(cwd, ".claude", "commands", "roleos-route.md");
|
|
123
|
+
checks.push({
|
|
124
|
+
name: "/roleos-route command",
|
|
125
|
+
status: existsSync(routeCmd) ? "pass" : "fail",
|
|
126
|
+
detail: existsSync(routeCmd) ? "exists" : "missing — run roleos init claude",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Check 4: Context files exist
|
|
130
|
+
const contextDir = join(cwd, ".claude", "context");
|
|
131
|
+
const contextFiles = ["product-brief.md", "repo-map.md", "brand-rules.md", "current-priorities.md"];
|
|
132
|
+
const filledContext = contextFiles.filter(f => {
|
|
133
|
+
const path = join(contextDir, f);
|
|
134
|
+
if (!existsSync(path)) return false;
|
|
135
|
+
const content = readFileSync(path, "utf-8");
|
|
136
|
+
// Check if it's still a template (all comments, no real content)
|
|
137
|
+
const lines = content.split("\n").filter(l => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("<!--") && !l.trim().startsWith("//"));
|
|
138
|
+
return lines.length > 2;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (filledContext.length === 4) {
|
|
142
|
+
checks.push({ name: "context files", status: "pass", detail: "all 4 filled" });
|
|
143
|
+
} else if (filledContext.length > 0) {
|
|
144
|
+
checks.push({ name: "context files", status: "warn", detail: `${filledContext.length}/4 filled — empty context reduces routing quality` });
|
|
145
|
+
} else {
|
|
146
|
+
checks.push({ name: "context files", status: "fail", detail: "no context files filled — routing will be low-confidence" });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check 5: Role contracts exist
|
|
150
|
+
const agentsDir = join(cwd, ".claude", "agents");
|
|
151
|
+
if (existsSync(agentsDir)) {
|
|
152
|
+
checks.push({ name: "role contracts", status: "pass", detail: "agents/ directory exists" });
|
|
153
|
+
} else {
|
|
154
|
+
checks.push({ name: "role contracts", status: "fail", detail: "no agents/ directory — run roleos init first" });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check 6: Packets directory exists
|
|
158
|
+
const packetsDir = join(cwd, ".claude", "packets");
|
|
159
|
+
checks.push({
|
|
160
|
+
name: "packets directory",
|
|
161
|
+
status: existsSync(packetsDir) ? "pass" : "warn",
|
|
162
|
+
detail: existsSync(packetsDir) ? "exists" : "no packets yet — run roleos packet new",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const healthy = checks.every(c => c.status !== "fail");
|
|
166
|
+
|
|
167
|
+
return { checks, healthy };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Route card ────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generate a route card — the session header artifact that proves
|
|
174
|
+
* role-os was engaged.
|
|
175
|
+
*
|
|
176
|
+
* @param {object} routeResult
|
|
177
|
+
* @returns {string} Markdown route card
|
|
178
|
+
*/
|
|
179
|
+
export function generateRouteCard(routeResult) {
|
|
180
|
+
const lines = [
|
|
181
|
+
`## Route Card`,
|
|
182
|
+
``,
|
|
183
|
+
`| Field | Value |`,
|
|
184
|
+
`|-------|-------|`,
|
|
185
|
+
`| Task type | ${routeResult.type || "unknown"} |`,
|
|
186
|
+
`| Pack | ${routeResult.pack || "free routing"} |`,
|
|
187
|
+
`| Pack confidence | ${routeResult.packConfidence || "n/a"} |`,
|
|
188
|
+
`| Composite | ${routeResult.isComposite ? "yes — " + routeResult.compositeReason : "no"} |`,
|
|
189
|
+
`| Chain | ${routeResult.chain || "pending"} |`,
|
|
190
|
+
`| Confidence | ${routeResult.confidence || "unknown"} |`,
|
|
191
|
+
`| Routed at | ${new Date().toISOString()} |`,
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
if (routeResult.mismatch) {
|
|
195
|
+
lines.push(`| Mismatch | ${routeResult.mismatch} |`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
lines.push(``);
|
|
199
|
+
lines.push(`**Why:** ${routeResult.reason || "Scored against packet content."}`);
|
|
200
|
+
|
|
201
|
+
if (routeResult.successArtifact) {
|
|
202
|
+
lines.push(``);
|
|
203
|
+
lines.push(`**Success artifact:** ${routeResult.successArtifact}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return lines.join("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── Format doctor output ──────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {{ checks: DoctorCheck[], healthy: boolean }} result
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
export function formatDoctor(result) {
|
|
216
|
+
const lines = [
|
|
217
|
+
`\nRole OS Doctor`,
|
|
218
|
+
`──────────────`,
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
for (const check of result.checks) {
|
|
222
|
+
const icon = check.status === "pass" ? "✓" : check.status === "warn" ? "!" : "✗";
|
|
223
|
+
lines.push(` ${icon} ${check.name}: ${check.detail}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
lines.push(``);
|
|
227
|
+
lines.push(result.healthy
|
|
228
|
+
? `Healthy — Role OS is wired correctly.`
|
|
229
|
+
: `Unhealthy — run roleos init claude to fix.`
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ── Generators ────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
function generateClaudeMd() {
|
|
238
|
+
return `## Role OS
|
|
239
|
+
|
|
240
|
+
Before starting non-trivial work in this repo, route the task through Role OS:
|
|
241
|
+
|
|
242
|
+
1. Run \`/roleos-route\` or \`roleos route <packet-file>\` to classify the task
|
|
243
|
+
2. Follow the recommended pack or role chain
|
|
244
|
+
3. Use structured handoffs between roles
|
|
245
|
+
4. Review with evidence-based verdicts
|
|
246
|
+
|
|
247
|
+
Role OS provides 31 specialized roles across 8 packs (engineering, design, product, research, growth, treatment, marketing, core). It detects broken chains, auto-routes recovery, and requires structured evidence in every verdict.
|
|
248
|
+
|
|
249
|
+
If the task is composite (feature + docs + launch), Role OS will recommend splitting into child packets with dependency ordering.
|
|
250
|
+
|
|
251
|
+
**Route card required:** Every substantial task should produce a route card at the start showing task type, chosen pack/role, confidence, and expected success artifact. If no route card exists, Role OS was not engaged.
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function generateRouteCommand() {
|
|
256
|
+
return `# Route Task Through Role OS
|
|
257
|
+
|
|
258
|
+
Classify the current task and recommend the right team.
|
|
259
|
+
|
|
260
|
+
## Steps
|
|
261
|
+
|
|
262
|
+
1. Read the task description or packet file
|
|
263
|
+
2. Run \`roleos route\` against it (or reason through the routing logic)
|
|
264
|
+
3. Emit a **route card** with:
|
|
265
|
+
- Task type (feature / bugfix / docs / research / security / launch / treatment)
|
|
266
|
+
- Recommended pack or free-routing chain
|
|
267
|
+
- Why this pack/chain was chosen
|
|
268
|
+
- Whether the task is composite (needs splitting)
|
|
269
|
+
- Expected success artifact
|
|
270
|
+
- Confidence level
|
|
271
|
+
|
|
272
|
+
## Route card format
|
|
273
|
+
|
|
274
|
+
\`\`\`
|
|
275
|
+
## Route Card
|
|
276
|
+
|
|
277
|
+
| Field | Value |
|
|
278
|
+
|-------|-------|
|
|
279
|
+
| Task type | feature |
|
|
280
|
+
| Pack | feature (high confidence) |
|
|
281
|
+
| Composite | no |
|
|
282
|
+
| Chain | Product Strategist → Spec Writer → Backend Engineer → Test Engineer → Critic Reviewer |
|
|
283
|
+
| Confidence | high |
|
|
284
|
+
|
|
285
|
+
**Why:** Packet contains implementation scope with clear deliverable type.
|
|
286
|
+
**Success artifact:** Working implementation with tests and Critic verdict.
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
## Rules
|
|
290
|
+
- If confidence is low, say so — do not force a pack
|
|
291
|
+
- If composite, recommend splitting before execution
|
|
292
|
+
- If the task is trivial (< 3 steps), skip routing and just do it
|
|
293
|
+
- The route card is the proof Role OS was engaged
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function generateReviewCommand() {
|
|
298
|
+
return `# Review Work Through Role OS
|
|
299
|
+
|
|
300
|
+
Review the completed work using Role OS structured verdict.
|
|
301
|
+
|
|
302
|
+
## Steps
|
|
303
|
+
|
|
304
|
+
1. Identify which role should review (usually Critic Reviewer)
|
|
305
|
+
2. Check the work against the original packet's done definition
|
|
306
|
+
3. Produce a structured verdict:
|
|
307
|
+
- **Verdict:** accept / accept-with-notes / reject / blocked
|
|
308
|
+
- **Evidence:** specific items supporting the verdict
|
|
309
|
+
- **Gaps:** what's missing, if anything
|
|
310
|
+
- **Next owner:** who receives this next
|
|
311
|
+
|
|
312
|
+
## Rules
|
|
313
|
+
- Tie every verdict to specific evidence, not impressions
|
|
314
|
+
- If evidence is insufficient to judge, say so
|
|
315
|
+
- Reject honestly — do not approve weak work to be nice
|
|
316
|
+
- For reject/blocked: state what must change and who should do it
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function generateStatusCommand() {
|
|
321
|
+
return `# Role OS Status
|
|
322
|
+
|
|
323
|
+
Check the current state of Role OS work in this repo.
|
|
324
|
+
|
|
325
|
+
## Steps
|
|
326
|
+
|
|
327
|
+
1. Run \`roleos status\` to see active packets, verdicts, and context health
|
|
328
|
+
2. Report:
|
|
329
|
+
- Active/blocked/completed packets
|
|
330
|
+
- Recent verdicts
|
|
331
|
+
- Context file health
|
|
332
|
+
- Any conflicts or escalations
|
|
333
|
+
|
|
334
|
+
## If no packets exist
|
|
335
|
+
Role OS has not been engaged in this session. Consider running \`/roleos-route\` first.
|
|
336
|
+
`;
|
|
337
|
+
}
|