role-os 1.8.0 → 2.0.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,405 @@
1
+ /**
2
+ * Run CLI Commands — Phase U (v2.0.0)
3
+ *
4
+ * roleos run "<task>" — create and start a run
5
+ * roleos run list — list all runs
6
+ * roleos run show <id> — show run detail
7
+ * roleos resume — resume the active run
8
+ * roleos next — start the next step (or show what's next)
9
+ * roleos explain — explain current run state
10
+ * roleos complete <artifact> [note] — complete the active step
11
+ * roleos fail <partial|failed> <reason> — fail the active step
12
+ * roleos retry <step-index> — retry a failed step
13
+ * roleos reroute <step-index> <role> <reason> — reroute a step
14
+ * roleos escalate <from> <to> <trigger> <action> — escalate
15
+ * roleos block <step-index> <reason> — block a step
16
+ * roleos reopen <step-index> <reason> — reopen a completed step
17
+ * roleos report — generate completion report
18
+ * roleos friction — measure operator friction
19
+ */
20
+
21
+ import {
22
+ createPersistentRun, startNext, completeCurrentStep, failCurrentStep,
23
+ pauseRun, resumeRun, reroute, escalate, retry, blockStep, reopenStep,
24
+ getPosition, explainRun, formatNext, generateReport, formatReport,
25
+ loadRun, listRuns, findActiveRun, measureFriction, saveRun,
26
+ } from "./run.mjs";
27
+
28
+ // ── Helpers ──────────────────────────────────────────────────────────────────
29
+
30
+ function getCwd() {
31
+ return process.cwd();
32
+ }
33
+
34
+ function requireActiveRun(cwd) {
35
+ const run = findActiveRun(cwd);
36
+ if (!run) {
37
+ const err = new Error("No active run found. Start one with: roleos run \"<task>\"");
38
+ err.exitCode = 1;
39
+ throw err;
40
+ }
41
+ return run;
42
+ }
43
+
44
+ // ── roleos run ───────────────────────────────────────────────────────────────
45
+
46
+ export async function runCommand(args) {
47
+ const cwd = getCwd();
48
+
49
+ if (!args || args.length === 0) {
50
+ console.log("Usage: roleos run \"<task description>\"");
51
+ console.log(" roleos run list");
52
+ console.log(" roleos run show <id>");
53
+ return;
54
+ }
55
+
56
+ const sub = args[0];
57
+
58
+ // roleos run list
59
+ if (sub === "list") {
60
+ const runs = listRuns(cwd);
61
+ if (runs.length === 0) {
62
+ console.log("No runs found.");
63
+ return;
64
+ }
65
+ console.log("Runs:");
66
+ for (const r of runs) {
67
+ const statusIcon = r.status === "completed" ? "[x]" :
68
+ r.status === "running" ? "[>]" :
69
+ r.status === "paused" ? "[=]" :
70
+ r.status === "failed" ? "[!]" :
71
+ r.status === "partial" ? "[~]" : "[ ]";
72
+ const task = r.task.length > 60 ? r.task.slice(0, 57) + "..." : r.task;
73
+ console.log(` ${statusIcon} ${r.id} ${r.level} ${task}`);
74
+ }
75
+ return;
76
+ }
77
+
78
+ // roleos run show <id>
79
+ if (sub === "show") {
80
+ const id = args[1];
81
+ if (!id) {
82
+ const err = new Error("Usage: roleos run show <run-id>");
83
+ err.exitCode = 1;
84
+ throw err;
85
+ }
86
+ const run = loadRun(cwd, id);
87
+ if (!run) {
88
+ const err = new Error(`Run "${id}" not found`);
89
+ err.exitCode = 1;
90
+ throw err;
91
+ }
92
+ console.log(explainRun(run));
93
+ return;
94
+ }
95
+
96
+ // roleos run "<task>"
97
+ const task = args.join(" ");
98
+ const opts = {};
99
+
100
+ // Parse --mission= and --pack= flags
101
+ for (const arg of args) {
102
+ if (arg.startsWith("--mission=")) {
103
+ opts.forceMission = arg.slice("--mission=".length);
104
+ } else if (arg.startsWith("--pack=")) {
105
+ opts.forcePack = arg.slice("--pack=".length);
106
+ }
107
+ }
108
+
109
+ // Strip flags from task description
110
+ const taskText = args.filter(a => !a.startsWith("--")).join(" ");
111
+
112
+ const run = createPersistentRun(taskText, cwd, opts);
113
+
114
+ console.log(`Created run: ${run.id}`);
115
+ console.log(`Entry: ${run.entryLevel.toUpperCase()}`);
116
+ if (run.missionKey) console.log(`Mission: ${run.missionKey}`);
117
+ if (run.packKey) console.log(`Pack: ${run.packKey}`);
118
+ console.log(`Steps: ${run.steps.length}`);
119
+ console.log("");
120
+
121
+ // Auto-start the first step
122
+ const step = startNext(run, cwd);
123
+ if (step) {
124
+ console.log(`Started step 0: ${step.role} → ${step.produces}`);
125
+ console.log("");
126
+ if (step.guidance) console.log(step.guidance);
127
+ }
128
+ }
129
+
130
+ // ── roleos resume ────────────────────────────────────────────────────────────
131
+
132
+ export async function resumeCommand(args) {
133
+ const cwd = getCwd();
134
+
135
+ let run;
136
+ if (args && args[0]) {
137
+ run = loadRun(cwd, args[0]);
138
+ if (!run) {
139
+ const err = new Error(`Run "${args[0]}" not found`);
140
+ err.exitCode = 1;
141
+ throw err;
142
+ }
143
+ } else {
144
+ run = requireActiveRun(cwd);
145
+ }
146
+
147
+ if (run.status === "paused") {
148
+ const step = resumeRun(run, cwd);
149
+ console.log(`Resumed run: ${run.id}`);
150
+ if (step) {
151
+ console.log(`Active: step ${step.index} — ${step.role} → ${step.produces}`);
152
+ if (step.guidance) {
153
+ console.log("");
154
+ console.log(step.guidance);
155
+ }
156
+ }
157
+ } else if (run.status === "running") {
158
+ const pos = getPosition(run);
159
+ if (pos.activeStep) {
160
+ console.log(`Run already active: step ${pos.activeStep.index} — ${pos.activeStep.role}`);
161
+ if (pos.activeStep.guidance) {
162
+ console.log("");
163
+ console.log(pos.activeStep.guidance);
164
+ }
165
+ }
166
+ } else {
167
+ console.log(`Run is "${run.status}" — cannot resume.`);
168
+ }
169
+ }
170
+
171
+ // ── roleos next ──────────────────────────────────────────────────────────────
172
+
173
+ export async function nextCommand(args) {
174
+ const cwd = getCwd();
175
+ const run = requireActiveRun(cwd);
176
+ const pos = getPosition(run);
177
+
178
+ // If there's an active step, just show it
179
+ if (pos.activeStep) {
180
+ console.log(formatNext(run));
181
+ return;
182
+ }
183
+
184
+ // If there's a next pending step, start it
185
+ if (pos.nextStep) {
186
+ const step = startNext(run, cwd);
187
+ if (step) {
188
+ console.log(`Started step ${step.index}: ${step.role} → ${step.produces}`);
189
+ console.log(`Progress: ${pos.completedCount + 0}/${pos.totalSteps}`);
190
+ if (step.guidance) {
191
+ console.log("");
192
+ console.log(step.guidance);
193
+ }
194
+ }
195
+ return;
196
+ }
197
+
198
+ console.log(formatNext(run));
199
+ }
200
+
201
+ // ── roleos explain ───────────────────────────────────────────────────────────
202
+
203
+ export async function explainCommand(args) {
204
+ const cwd = getCwd();
205
+
206
+ let run;
207
+ if (args && args[0]) {
208
+ run = loadRun(cwd, args[0]);
209
+ if (!run) {
210
+ const err = new Error(`Run "${args[0]}" not found`);
211
+ err.exitCode = 1;
212
+ throw err;
213
+ }
214
+ } else {
215
+ run = requireActiveRun(cwd);
216
+ }
217
+
218
+ console.log(explainRun(run));
219
+ }
220
+
221
+ // ── roleos complete ──────────────────────────────────────────────────────────
222
+
223
+ export async function completeCommand(args) {
224
+ if (!args || args.length === 0) {
225
+ const err = new Error("Usage: roleos complete <artifact-reference> [note]");
226
+ err.exitCode = 1;
227
+ throw err;
228
+ }
229
+
230
+ const cwd = getCwd();
231
+ const run = requireActiveRun(cwd);
232
+ const artifact = args[0];
233
+ const note = args.slice(1).join(" ") || null;
234
+
235
+ const step = completeCurrentStep(run, artifact, note, cwd);
236
+ console.log(`Completed step ${step.index}: ${step.role} → ${step.produces}`);
237
+
238
+ const pos = getPosition(run);
239
+ if (pos.nextStep) {
240
+ console.log(`Next: step ${pos.nextStep.index} — ${pos.nextStep.role} → ${pos.nextStep.produces}`);
241
+ console.log(`Run \`roleos next\` to start.`);
242
+ } else if (run.status === "completed") {
243
+ console.log("All steps completed. Run `roleos report` for the completion report.");
244
+ }
245
+ }
246
+
247
+ // ── roleos fail ──────────────────────────────────────────────────────────────
248
+
249
+ export async function failCommand(args) {
250
+ if (!args || args.length < 2) {
251
+ const err = new Error("Usage: roleos fail <partial|failed> <reason>");
252
+ err.exitCode = 1;
253
+ throw err;
254
+ }
255
+
256
+ const cwd = getCwd();
257
+ const run = requireActiveRun(cwd);
258
+ const status = args[0];
259
+ const reason = args.slice(1).join(" ");
260
+
261
+ const step = failCurrentStep(run, status, reason, cwd);
262
+ console.log(`Step ${step.index} (${step.role}): ${status}`);
263
+ console.log(`Reason: ${reason}`);
264
+
265
+ const blocked = run.steps.filter(s => s.status === "blocked");
266
+ if (blocked.length > 0) {
267
+ console.log(`Blocked ${blocked.length} downstream step(s).`);
268
+ }
269
+ }
270
+
271
+ // ── Intervention commands ────────────────────────────────────────────────────
272
+
273
+ export async function retryCommand(args) {
274
+ if (!args || args.length === 0) {
275
+ const err = new Error("Usage: roleos retry <step-index>");
276
+ err.exitCode = 1;
277
+ throw err;
278
+ }
279
+
280
+ const cwd = getCwd();
281
+ const run = requireActiveRun(cwd);
282
+ const stepIndex = parseInt(args[0], 10);
283
+
284
+ retry(run, stepIndex, cwd);
285
+ console.log(`Retried step ${stepIndex} (${run.steps[stepIndex].role}). Run \`roleos next\` to start.`);
286
+ }
287
+
288
+ export async function rerouteCommand(args) {
289
+ if (!args || args.length < 3) {
290
+ const err = new Error("Usage: roleos reroute <step-index> <new-role> <reason>");
291
+ err.exitCode = 1;
292
+ throw err;
293
+ }
294
+
295
+ const cwd = getCwd();
296
+ const run = requireActiveRun(cwd);
297
+ const stepIndex = parseInt(args[0], 10);
298
+ const newRole = args[1];
299
+ const reason = args.slice(2).join(" ");
300
+
301
+ reroute(run, stepIndex, newRole, reason, cwd);
302
+ console.log(`Rerouted step ${stepIndex} to ${newRole}.`);
303
+ }
304
+
305
+ export async function escalateCommand(args) {
306
+ if (!args || args.length < 4) {
307
+ const err = new Error("Usage: roleos escalate <from-role> <to-role> <trigger> <action>");
308
+ err.exitCode = 1;
309
+ throw err;
310
+ }
311
+
312
+ const cwd = getCwd();
313
+ const run = requireActiveRun(cwd);
314
+ const [from, to, trigger, ...rest] = args;
315
+ const action = rest.join(" ");
316
+
317
+ const result = escalate(run, from, to, trigger, action, cwd);
318
+ if (result.reopened) {
319
+ console.log(`Escalated: ${from} → ${to}. Step re-opened for ${trigger}.`);
320
+ } else {
321
+ console.log(`Escalation recorded: ${from} → ${to}.`);
322
+ if (result.warning) console.log(`Warning: ${result.warning}`);
323
+ }
324
+ }
325
+
326
+ export async function blockCommand(args) {
327
+ if (!args || args.length < 2) {
328
+ const err = new Error("Usage: roleos block <step-index> <reason>");
329
+ err.exitCode = 1;
330
+ throw err;
331
+ }
332
+
333
+ const cwd = getCwd();
334
+ const run = requireActiveRun(cwd);
335
+ const stepIndex = parseInt(args[0], 10);
336
+ const reason = args.slice(1).join(" ");
337
+
338
+ blockStep(run, stepIndex, reason, cwd);
339
+ console.log(`Blocked step ${stepIndex} (${run.steps[stepIndex].role}): ${reason}`);
340
+ }
341
+
342
+ export async function reopenCommand(args) {
343
+ if (!args || args.length < 2) {
344
+ const err = new Error("Usage: roleos reopen <step-index> <reason>");
345
+ err.exitCode = 1;
346
+ throw err;
347
+ }
348
+
349
+ const cwd = getCwd();
350
+ const run = requireActiveRun(cwd);
351
+ const stepIndex = parseInt(args[0], 10);
352
+ const reason = args.slice(1).join(" ");
353
+
354
+ reopenStep(run, stepIndex, reason, cwd);
355
+ console.log(`Re-opened step ${stepIndex} (${run.steps[stepIndex].role}): ${reason}`);
356
+ }
357
+
358
+ // ── roleos report ────────────────────────────────────────────────────────────
359
+
360
+ export async function reportCommand(args) {
361
+ const cwd = getCwd();
362
+ let run;
363
+
364
+ if (args && args[0]) {
365
+ run = loadRun(cwd, args[0]);
366
+ if (!run) {
367
+ const err = new Error(`Run "${args[0]}" not found`);
368
+ err.exitCode = 1;
369
+ throw err;
370
+ }
371
+ } else {
372
+ run = requireActiveRun(cwd);
373
+ }
374
+
375
+ const report = generateReport(run);
376
+ saveRun(cwd, run);
377
+ console.log(formatReport(report));
378
+ }
379
+
380
+ // ── roleos friction ──────────────────────────────────────────────────────────
381
+
382
+ export async function frictionCommand(args) {
383
+ const cwd = getCwd();
384
+ let run;
385
+
386
+ if (args && args[0]) {
387
+ run = loadRun(cwd, args[0]);
388
+ if (!run) {
389
+ const err = new Error(`Run "${args[0]}" not found`);
390
+ err.exitCode = 1;
391
+ throw err;
392
+ }
393
+ } else {
394
+ run = requireActiveRun(cwd);
395
+ }
396
+
397
+ const friction = measureFriction(run);
398
+ console.log("# Friction Report");
399
+ console.log(`Total touches: ${friction.totalTouches}`);
400
+ console.log(` Interventions: ${friction.interventions}`);
401
+ console.log(` Escalations: ${friction.escalations}`);
402
+ console.log(` Manual steps: ${friction.manualSteps}`);
403
+ console.log(` Steps with notes: ${friction.stepsWithNotes}`);
404
+ console.log(`Friction score: ${friction.frictionScore.toUpperCase()}`);
405
+ }