role-os 1.7.0 → 1.8.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.0
4
+
5
+ ### Added
6
+
7
+ #### Mission Library (Phase S — Mission Hardening)
8
+ - 6 named, repeatable mission types: feature-ship, bugfix, treatment, docs-release, security-hardening, research-launch
9
+ - Each mission declares: pack, role chain, artifact flow, escalation branches, honest-partial definition, stop conditions, dispatch defaults, trial evidence
10
+ - Mission runner: create → step through → complete/fail → generate completion report
11
+ - Completion proof reporter with honest-partial and formatted text output
12
+ - `roleos mission list` — list all missions
13
+ - `roleos mission show <key>` — full mission detail
14
+ - `roleos mission suggest <text>` — signal-based mission suggestion
15
+ - `roleos mission validate [key]` — validate mission wiring against packs/roles
16
+
17
+ #### Mission Runner Engine
18
+ - `createRun()` — instantiate a mission with tracked steps
19
+ - `startNextStep()` / `completeStep()` / `failStep()` — step lifecycle
20
+ - `recordEscalation()` — re-opens completed steps on escalation loops
21
+ - `getRunPosition()` / `getArtifactChain()` — run introspection
22
+ - `generateCompletionReport()` / `formatCompletionReport()` — honest outcome reporting
23
+
24
+ ### Evidence
25
+ - 465 tests, zero failures (67 new)
26
+ - All 6 missions validate against live pack/role catalog
27
+ - Full lifecycle tests: end-to-end runs, escalation loops, partial completions, failure reporting
28
+
3
29
  ## 1.7.0
4
30
 
5
31
  ### Added
package/bin/roleos.mjs CHANGED
@@ -11,6 +11,7 @@ import { statusCommand } from "../src/status.mjs";
11
11
  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
+ import { missionCommand } from "../src/mission-cmd.mjs";
14
15
 
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
17
  const VERSION = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
@@ -35,6 +36,10 @@ Usage:
35
36
  roleos artifacts show <role> Show artifact contract for a role
36
37
  roleos artifacts validate <role> <file> Validate a file against a contract
37
38
  roleos artifacts chain <pack> Show pack handoff flow
39
+ roleos mission list List all missions
40
+ roleos mission show <key> Show full mission detail
41
+ roleos mission suggest <text> Suggest a mission for a task
42
+ roleos mission validate [key] Validate mission wiring
38
43
  roleos doctor Verify repo is wired for Role OS sessions
39
44
  roleos help Show this help
40
45
 
@@ -111,6 +116,9 @@ try {
111
116
  case "artifacts":
112
117
  await artifactsCommand(args);
113
118
  break;
119
+ case "mission":
120
+ await missionCommand(args);
121
+ break;
114
122
  case "help":
115
123
  case "--help":
116
124
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "role-os",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Role OS — a multi-Claude operating system where 31 specialized roles execute work through contracts, conflict detection, escalation, and structured evidence. 7 proven team packs for common task families.",
5
5
  "homepage": "https://mcp-tool-shop-org.github.io/role-os/",
6
6
  "bugs": {
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Mission CLI — Phase S (v1.8.0)
3
+ *
4
+ * roleos mission list List all missions
5
+ * roleos mission show <key> Show full mission detail
6
+ * roleos mission suggest <text> Suggest a mission for a task
7
+ * roleos mission validate Validate all missions are properly wired
8
+ * roleos mission validate <key> Validate a specific mission
9
+ */
10
+
11
+ import {
12
+ listMissions,
13
+ getMission,
14
+ suggestMission,
15
+ validateMission,
16
+ validateAllMissions,
17
+ MISSIONS,
18
+ } from "./mission.mjs";
19
+
20
+ /**
21
+ * @param {string[]} args
22
+ */
23
+ export async function missionCommand(args) {
24
+ const sub = args[0];
25
+
26
+ switch (sub) {
27
+ case "list":
28
+ return cmdList();
29
+ case "show":
30
+ return cmdShow(args[1]);
31
+ case "suggest":
32
+ return cmdSuggest(args.slice(1).join(" "));
33
+ case "validate":
34
+ return cmdValidate(args[1]);
35
+ default: {
36
+ const err = new Error(
37
+ `Usage: roleos mission <list|show|suggest|validate>\n` +
38
+ ` list List all missions\n` +
39
+ ` show <key> Show full mission detail\n` +
40
+ ` suggest <text> Suggest a mission for a task description\n` +
41
+ ` validate [key] Validate mission wiring`
42
+ );
43
+ err.exitCode = 1;
44
+ err.hint = "Run 'roleos mission list' to see available missions.";
45
+ throw err;
46
+ }
47
+ }
48
+ }
49
+
50
+ function cmdList() {
51
+ const missions = listMissions();
52
+ console.log("\nMission Library\n");
53
+ console.log(" Key Pack Roles Description");
54
+ console.log(" " + "-".repeat(78));
55
+ for (const m of missions) {
56
+ const key = m.key.padEnd(20);
57
+ const pack = m.pack.padEnd(10);
58
+ const roles = String(m.roleCount).padEnd(6);
59
+ console.log(` ${key} ${pack} ${roles} ${m.description}`);
60
+ }
61
+ console.log(`\n ${missions.length} missions. Run 'roleos mission show <key>' for details.\n`);
62
+ }
63
+
64
+ function cmdShow(key) {
65
+ if (!key) {
66
+ const err = new Error("Usage: roleos mission show <key>");
67
+ err.exitCode = 1;
68
+ err.hint = `Available: ${Object.keys(MISSIONS).join(", ")}`;
69
+ throw err;
70
+ }
71
+
72
+ const mission = getMission(key);
73
+ if (!mission) {
74
+ const err = new Error(`Mission "${key}" not found`);
75
+ err.exitCode = 1;
76
+ err.hint = `Available: ${Object.keys(MISSIONS).join(", ")}`;
77
+ throw err;
78
+ }
79
+
80
+ console.log(`\n# ${mission.name}`);
81
+ console.log(`\n${mission.description}`);
82
+ console.log(`\nPack: ${mission.pack}`);
83
+ console.log(`Entry: ${mission.entryPath}`);
84
+
85
+ console.log("\n## Role Chain");
86
+ for (let i = 0; i < mission.roleChain.length; i++) {
87
+ const arrow = i < mission.roleChain.length - 1 ? " →" : "";
88
+ console.log(` ${i + 1}. ${mission.roleChain[i]}${arrow}`);
89
+ }
90
+
91
+ console.log("\n## Artifact Flow");
92
+ for (const step of mission.artifactFlow) {
93
+ const to = step.consumedBy ? ` → ${step.consumedBy}` : " (terminal)";
94
+ console.log(` ${step.role} produces "${step.produces}"${to}`);
95
+ }
96
+
97
+ console.log("\n## Escalation Branches");
98
+ for (const branch of mission.escalationBranches) {
99
+ console.log(` [${branch.trigger}] ${branch.from} → ${branch.to}: ${branch.action}`);
100
+ }
101
+
102
+ console.log("\n## Honest Partial");
103
+ console.log(` ${mission.honestPartial}`);
104
+
105
+ console.log("\n## Stop Conditions");
106
+ for (const cond of mission.stopConditions) {
107
+ console.log(` - ${cond}`);
108
+ }
109
+
110
+ console.log(`\n## Dispatch Defaults`);
111
+ const d = mission.dispatchDefaults;
112
+ console.log(` Model: ${d.model} | Max turns: ${d.maxTurns} | Budget: $${d.maxBudgetUsd}`);
113
+
114
+ if (mission.trialEvidence) {
115
+ console.log(`\n## Trial Evidence`);
116
+ console.log(` ${mission.trialEvidence}`);
117
+ }
118
+
119
+ console.log("");
120
+ }
121
+
122
+ function cmdSuggest(text) {
123
+ if (!text) {
124
+ const err = new Error("Usage: roleos mission suggest <task description>");
125
+ err.exitCode = 1;
126
+ err.hint = "Describe what you want to do and I'll suggest a mission.";
127
+ throw err;
128
+ }
129
+
130
+ const suggestion = suggestMission(text);
131
+ if (!suggestion) {
132
+ console.log("\nNo mission matched. Task may need manual routing.\n");
133
+ console.log("Available missions:");
134
+ for (const key of Object.keys(MISSIONS)) {
135
+ console.log(` - ${key}: ${MISSIONS[key].description}`);
136
+ }
137
+ console.log("");
138
+ return;
139
+ }
140
+
141
+ const mission = getMission(suggestion.mission);
142
+ console.log(`\nSuggested mission: ${suggestion.mission}`);
143
+ console.log(`Name: ${mission.name}`);
144
+ console.log(`Confidence: ${suggestion.confidence}`);
145
+ console.log(`Reason: ${suggestion.reason}`);
146
+ console.log(`Pack: ${mission.pack}`);
147
+ console.log(`Chain: ${mission.roleChain.join(" → ")}`);
148
+ console.log(`\nRun 'roleos mission show ${suggestion.mission}' for full details.\n`);
149
+ }
150
+
151
+ function cmdValidate(key) {
152
+ if (key) {
153
+ const result = validateMission(key);
154
+ if (result.valid) {
155
+ console.log(`\n[PASS] Mission "${key}" is properly wired.\n`);
156
+ } else {
157
+ console.log(`\n[FAIL] Mission "${key}" has issues:`);
158
+ for (const issue of result.issues) {
159
+ console.log(` - ${issue}`);
160
+ }
161
+ console.log("");
162
+ process.exit(1);
163
+ }
164
+ } else {
165
+ const { allValid, results } = validateAllMissions();
166
+ console.log("\nMission Validation\n");
167
+ for (const [k, r] of Object.entries(results)) {
168
+ const icon = r.valid ? "[PASS]" : "[FAIL]";
169
+ console.log(` ${icon} ${k}`);
170
+ if (!r.valid) {
171
+ for (const issue of r.issues) {
172
+ console.log(` - ${issue}`);
173
+ }
174
+ }
175
+ }
176
+ console.log(`\n ${Object.keys(results).length} missions checked. ${allValid ? "All valid." : "Some issues found."}\n`);
177
+ if (!allValid) process.exit(1);
178
+ }
179
+ }
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Mission Runner — Phase S (v1.8.0)
3
+ *
4
+ * Executes a mission through the full Role OS stack:
5
+ * mission → packet → route → pack → artifacts → review → completion report
6
+ *
7
+ * The runner does NOT execute code — it orchestrates the contract chain
8
+ * and tracks state so that operators (human or agent) know exactly
9
+ * where they are, what's been produced, and what's next.
10
+ */
11
+
12
+ import { MISSIONS, getMission, validateMission } from "./mission.mjs";
13
+ import { TEAM_PACKS } from "./packs.mjs";
14
+ import { validateArtifact, ROLE_ARTIFACT_CONTRACTS } from "./artifacts.mjs";
15
+
16
+ let _runCounter = 0;
17
+
18
+ // ── Run states ──────────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * @typedef {"pending"|"active"|"completed"|"partial"|"failed"|"escalated"} StepStatus
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} MissionStep
26
+ * @property {string} role
27
+ * @property {string} produces - artifact type
28
+ * @property {string|null} consumedBy - next role or null
29
+ * @property {StepStatus} status
30
+ * @property {string|null} artifact - produced artifact content/reference
31
+ * @property {string|null} note - operator note
32
+ * @property {string|null} startedAt
33
+ * @property {string|null} completedAt
34
+ */
35
+
36
+ /**
37
+ * @typedef {"planning"|"running"|"completed"|"partial"|"failed"} RunStatus
38
+ */
39
+
40
+ /**
41
+ * @typedef {Object} MissionRun
42
+ * @property {string} id
43
+ * @property {string} missionKey
44
+ * @property {string} taskDescription
45
+ * @property {RunStatus} status
46
+ * @property {MissionStep[]} steps
47
+ * @property {Array<{from: string, to: string, trigger: string, action: string, timestamp: string}>} escalations
48
+ * @property {string} startedAt
49
+ * @property {string|null} completedAt
50
+ * @property {object|null} completionReport
51
+ */
52
+
53
+ // ── Create a run ────────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * Create a new mission run from a mission key and task description.
57
+ *
58
+ * @param {string} missionKey
59
+ * @param {string} taskDescription
60
+ * @returns {MissionRun}
61
+ */
62
+ export function createRun(missionKey, taskDescription) {
63
+ const mission = getMission(missionKey);
64
+ if (!mission) {
65
+ throw new Error(`Mission "${missionKey}" not found. Available: ${Object.keys(MISSIONS).join(", ")}`);
66
+ }
67
+
68
+ const validation = validateMission(missionKey);
69
+ if (!validation.valid) {
70
+ throw new Error(`Mission "${missionKey}" has issues: ${validation.issues.join("; ")}`);
71
+ }
72
+
73
+ const id = `${missionKey}-${Date.now()}-${++_runCounter}`;
74
+
75
+ const steps = mission.artifactFlow.map((step) => ({
76
+ role: step.role,
77
+ produces: step.produces,
78
+ consumedBy: step.consumedBy,
79
+ status: "pending",
80
+ artifact: null,
81
+ note: null,
82
+ startedAt: null,
83
+ completedAt: null,
84
+ }));
85
+
86
+ return {
87
+ id,
88
+ missionKey,
89
+ taskDescription,
90
+ status: "planning",
91
+ steps,
92
+ escalations: [],
93
+ startedAt: new Date().toISOString(),
94
+ completedAt: null,
95
+ completionReport: null,
96
+ };
97
+ }
98
+
99
+ // ── Step through a run ──────────────────────────────────────────────────────
100
+
101
+ /**
102
+ * Start the next pending step.
103
+ * @param {MissionRun} run
104
+ * @returns {MissionStep|null} The started step, or null if no pending steps
105
+ */
106
+ export function startNextStep(run) {
107
+ const next = run.steps.find((s) => s.status === "pending");
108
+ if (!next) return null;
109
+
110
+ next.status = "active";
111
+ next.startedAt = new Date().toISOString();
112
+ run.status = "running";
113
+
114
+ return next;
115
+ }
116
+
117
+ /**
118
+ * Complete the current active step with an artifact.
119
+ * @param {MissionRun} run
120
+ * @param {string} artifact - The produced artifact (content or reference)
121
+ * @param {string} [note] - Optional operator note
122
+ * @returns {MissionStep}
123
+ */
124
+ export function completeStep(run, artifact, note) {
125
+ const active = run.steps.find((s) => s.status === "active");
126
+ if (!active) {
127
+ throw new Error("No active step to complete");
128
+ }
129
+
130
+ active.status = "completed";
131
+ active.artifact = artifact;
132
+ active.note = note || null;
133
+ active.completedAt = new Date().toISOString();
134
+
135
+ // Check if all steps are done
136
+ const allDone = run.steps.every((s) => s.status === "completed");
137
+ if (allDone) {
138
+ run.status = "completed";
139
+ run.completedAt = new Date().toISOString();
140
+ }
141
+
142
+ return active;
143
+ }
144
+
145
+ /**
146
+ * Mark the current active step as failed/partial.
147
+ * @param {MissionRun} run
148
+ * @param {"partial"|"failed"} status
149
+ * @param {string} reason
150
+ * @returns {MissionStep}
151
+ */
152
+ export function failStep(run, status, reason) {
153
+ if (status !== "partial" && status !== "failed") {
154
+ throw new Error(`Invalid fail status: "${status}". Use "partial" or "failed"`);
155
+ }
156
+
157
+ const active = run.steps.find((s) => s.status === "active");
158
+ if (!active) {
159
+ throw new Error("No active step to fail");
160
+ }
161
+
162
+ active.status = status;
163
+ active.note = reason;
164
+ active.completedAt = new Date().toISOString();
165
+
166
+ // Block all downstream pending steps (S2-F2)
167
+ let foundActive = false;
168
+ for (const step of run.steps) {
169
+ if (step === active) { foundActive = true; continue; }
170
+ if (foundActive && step.status === "pending") {
171
+ step.status = "blocked";
172
+ step.note = `Blocked: upstream ${active.role} ${status}`;
173
+ }
174
+ }
175
+
176
+ // Mission status follows the worst step
177
+ run.status = status;
178
+ run.completedAt = new Date().toISOString();
179
+
180
+ return active;
181
+ }
182
+
183
+ /**
184
+ * Record an escalation during a run.
185
+ * @param {MissionRun} run
186
+ * @param {string} from - Role escalating
187
+ * @param {string} to - Role receiving
188
+ * @param {string} trigger - What triggered it
189
+ * @param {string} action - What needs to happen
190
+ * @returns {{from: string, to: string, trigger: string, action: string, timestamp: string, reopened: boolean, warning: string|null}}
191
+ */
192
+ export function recordEscalation(run, from, to, trigger, action) {
193
+ const escalation = {
194
+ from,
195
+ to,
196
+ trigger,
197
+ action,
198
+ timestamp: new Date().toISOString(),
199
+ reopened: false,
200
+ warning: null,
201
+ };
202
+ run.escalations.push(escalation);
203
+
204
+ // S5-F1: Find the LAST matching step for the target role, not the first.
205
+ // Security-hardening has Security Reviewer twice — escalation should target
206
+ // the latest occurrence (re-audit), not the initial audit.
207
+ let targetStep = null;
208
+ for (let i = run.steps.length - 1; i >= 0; i--) {
209
+ if (run.steps[i].role === to && run.steps[i].status === "completed") {
210
+ targetStep = run.steps[i];
211
+ break;
212
+ }
213
+ }
214
+
215
+ if (targetStep) {
216
+ targetStep.status = "pending";
217
+ targetStep.artifact = null;
218
+ targetStep.note = `Re-opened by escalation: ${trigger}`;
219
+ targetStep.completedAt = null;
220
+ escalation.reopened = true;
221
+ } else {
222
+ // S4-F2: Target role not found in chain (or no completed step to re-open).
223
+ // Warn the operator instead of silently doing nothing.
224
+ const inChain = run.steps.some((s) => s.role === to);
225
+ if (!inChain) {
226
+ escalation.warning = `Role "${to}" is not in this mission's step chain. Escalation recorded but no step re-opened.`;
227
+ } else {
228
+ escalation.warning = `Role "${to}" has no completed step to re-open. Escalation recorded.`;
229
+ }
230
+ }
231
+
232
+ return escalation;
233
+ }
234
+
235
+ // ── Run introspection ───────────────────────────────────────────────────────
236
+
237
+ /**
238
+ * Get the current position in a run.
239
+ * @param {MissionRun} run
240
+ * @returns {{currentStep: MissionStep|null, completedCount: number, totalSteps: number, progress: string}}
241
+ */
242
+ export function getRunPosition(run) {
243
+ const active = run.steps.find((s) => s.status === "active");
244
+ const completedCount = run.steps.filter((s) => s.status === "completed").length;
245
+ const totalSteps = run.steps.length;
246
+
247
+ return {
248
+ currentStep: active || null,
249
+ completedCount,
250
+ totalSteps,
251
+ progress: `${completedCount}/${totalSteps}`,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Get the artifact chain produced so far.
257
+ * @param {MissionRun} run
258
+ * @returns {Array<{role: string, type: string, artifact: string}>}
259
+ */
260
+ export function getArtifactChain(run) {
261
+ return run.steps
262
+ .filter((s) => s.status === "completed" && s.artifact)
263
+ .map((s) => ({
264
+ role: s.role,
265
+ type: s.produces,
266
+ artifact: s.artifact,
267
+ }));
268
+ }
269
+
270
+ // ── Completion report ───────────────────────────────────────────────────────
271
+
272
+ /**
273
+ * Generate a completion report for a finished (or partially finished) run.
274
+ * This is the "honest partial" — it tells you exactly what happened,
275
+ * not what you hoped would happen.
276
+ *
277
+ * @param {MissionRun} run
278
+ * @returns {object} Completion report
279
+ */
280
+ export function generateCompletionReport(run) {
281
+ const mission = getMission(run.missionKey);
282
+ const position = getRunPosition(run);
283
+ const artifactChain = getArtifactChain(run);
284
+
285
+ const stepSummaries = run.steps.map((s) => ({
286
+ role: s.role,
287
+ produces: s.produces,
288
+ status: s.status,
289
+ hasArtifact: !!s.artifact,
290
+ note: s.note,
291
+ }));
292
+
293
+ const isComplete = run.status === "completed";
294
+ const isPartial = run.status === "partial";
295
+ const isFailed = run.status === "failed";
296
+
297
+ const report = {
298
+ missionKey: run.missionKey,
299
+ missionName: mission.name,
300
+ taskDescription: run.taskDescription,
301
+ outcome: run.status,
302
+ progress: position.progress,
303
+ startedAt: run.startedAt,
304
+ completedAt: run.completedAt,
305
+ durationMs: run.completedAt
306
+ ? new Date(run.completedAt) - new Date(run.startedAt)
307
+ : null,
308
+ steps: stepSummaries,
309
+ artifactsProduced: artifactChain.length,
310
+ artifactChain,
311
+ escalationCount: run.escalations.length,
312
+ escalations: run.escalations,
313
+ honestPartial: isPartial || isFailed ? mission.honestPartial : null,
314
+ verdict: isComplete
315
+ ? "Mission completed — all artifacts produced, all steps passed."
316
+ : isPartial
317
+ ? `Mission partially completed (${position.progress}). ${mission.honestPartial}`
318
+ : isFailed
319
+ ? `Mission failed at step ${position.completedCount + 1}/${position.totalSteps} (${run.steps.find((s) => s.status === "failed")?.role || "unknown"}).`
320
+ : `Mission still running (${position.progress}).`,
321
+ };
322
+
323
+ // Attach to run
324
+ run.completionReport = report;
325
+
326
+ return report;
327
+ }
328
+
329
+ /**
330
+ * Format a completion report as human-readable text.
331
+ * @param {object} report
332
+ * @returns {string}
333
+ */
334
+ export function formatCompletionReport(report) {
335
+ const lines = [];
336
+
337
+ lines.push(`# Mission Report: ${report.missionName}`);
338
+ lines.push("");
339
+ lines.push(`**Task:** ${report.taskDescription}`);
340
+ lines.push(`**Outcome:** ${report.outcome.toUpperCase()}`);
341
+ lines.push(`**Progress:** ${report.progress}`);
342
+
343
+ if (report.durationMs) {
344
+ const seconds = Math.round(report.durationMs / 1000);
345
+ lines.push(`**Duration:** ${seconds}s`);
346
+ }
347
+
348
+ lines.push("");
349
+ lines.push("## Steps");
350
+ for (const step of report.steps) {
351
+ const icon = step.status === "completed" ? "[x]" :
352
+ step.status === "active" ? "[>]" :
353
+ step.status === "partial" ? "[~]" :
354
+ step.status === "failed" ? "[!]" :
355
+ step.status === "blocked" ? "[-]" : "[ ]";
356
+ const artifact = step.hasArtifact ? ` → ${step.produces}` : "";
357
+ const note = step.note ? ` (${step.note})` : "";
358
+ lines.push(` ${icon} ${step.role}${artifact}${note}`);
359
+ }
360
+
361
+ if (report.escalationCount > 0) {
362
+ lines.push("");
363
+ lines.push("## Escalations");
364
+ for (const esc of report.escalations) {
365
+ lines.push(` - ${esc.from} → ${esc.to}: ${esc.trigger} → ${esc.action}`);
366
+ }
367
+ }
368
+
369
+ if (report.honestPartial) {
370
+ lines.push("");
371
+ lines.push("## Honest Partial");
372
+ lines.push(report.honestPartial);
373
+ }
374
+
375
+ lines.push("");
376
+ lines.push("## Verdict");
377
+ lines.push(report.verdict);
378
+
379
+ return lines.join("\n");
380
+ }
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Mission Library — Phase S (v1.8.0)
3
+ *
4
+ * Named, repeatable job types that make recurring work boringly reliable.
5
+ * Each mission declares:
6
+ * - Default pack and role chain
7
+ * - Expected artifact flow
8
+ * - Common escalation branches
9
+ * - What "honest partial" looks like
10
+ * - Entry conditions and stop conditions
11
+ *
12
+ * Missions are NOT new abstractions — they are proven patterns
13
+ * extracted from real execution (trials G1–G10, pack comparisons,
14
+ * completion proof arc).
15
+ */
16
+
17
+ import { TEAM_PACKS } from "./packs.mjs";
18
+ import { ROLE_ARTIFACT_CONTRACTS } from "./artifacts.mjs";
19
+ import { ROLE_CATALOG } from "./route.mjs";
20
+
21
+ // ── Mission definitions ─────────────────────────────────────────────────────
22
+
23
+ export const MISSIONS = {
24
+ // ── Feature Shipment ────────────────────────────────────────────────────
25
+ "feature-ship": {
26
+ name: "Feature Shipment",
27
+ description: "Scope, spec, implement, test, and review a new feature end-to-end.",
28
+ pack: "feature",
29
+ entryPath: "Product Strategist frames scope → Spec Writer writes spec → implementation → test → review",
30
+ roleChain: [
31
+ "Product Strategist",
32
+ "Spec Writer",
33
+ "Backend Engineer",
34
+ "Test Engineer",
35
+ "Critic Reviewer",
36
+ ],
37
+ artifactFlow: [
38
+ { role: "Product Strategist", produces: "strategy-brief", consumedBy: "Spec Writer" },
39
+ { role: "Spec Writer", produces: "implementation-spec", consumedBy: "Backend Engineer" },
40
+ { role: "Backend Engineer", produces: "change-plan", consumedBy: "Test Engineer" },
41
+ { role: "Test Engineer", produces: "test-report", consumedBy: "Critic Reviewer" },
42
+ { role: "Critic Reviewer", produces: "review-verdict", consumedBy: null },
43
+ ],
44
+ escalationBranches: [
45
+ { trigger: "scope ambiguity", from: "Spec Writer", to: "Product Strategist", action: "clarify scope before proceeding" },
46
+ { trigger: "untestable spec", from: "Test Engineer", to: "Spec Writer", action: "revise interface spec for testability" },
47
+ { trigger: "review rejection", from: "Critic Reviewer", to: "Backend Engineer", action: "address findings, re-test, re-review" },
48
+ ],
49
+ honestPartial: "Scope + spec complete but implementation blocked. Artifact chain intact up to the blocking point. Handoff includes what was tried and why it stalled.",
50
+ stopConditions: [
51
+ "Critic Reviewer accepts",
52
+ "Escalation exhausts retry budget (3 loops max)",
53
+ "Scope change invalidates the mission — replan required",
54
+ ],
55
+ dispatchDefaults: { model: "sonnet", maxTurns: 30, maxBudgetUsd: 5.0 },
56
+ trialEvidence: "G1, G2 — 6/6 gold passes. Pack comparison: wins vs free routing.",
57
+ },
58
+
59
+ // ── Bugfix / Diagnosis ──────────────────────────────────────────────────
60
+ "bugfix": {
61
+ name: "Bugfix & Diagnosis",
62
+ description: "Diagnose root cause, fix, test, and verify a bug or regression.",
63
+ pack: "bugfix",
64
+ entryPath: "Repo Researcher diagnoses root cause → Backend Engineer fixes → Test Engineer verifies → Critic reviews",
65
+ roleChain: [
66
+ "Repo Researcher",
67
+ "Backend Engineer",
68
+ "Test Engineer",
69
+ "Critic Reviewer",
70
+ ],
71
+ artifactFlow: [
72
+ { role: "Repo Researcher", produces: "diagnosis-report", consumedBy: "Backend Engineer" },
73
+ { role: "Backend Engineer", produces: "change-plan", consumedBy: "Test Engineer" },
74
+ { role: "Test Engineer", produces: "test-report", consumedBy: "Critic Reviewer" },
75
+ { role: "Critic Reviewer", produces: "review-verdict", consumedBy: null },
76
+ ],
77
+ escalationBranches: [
78
+ { trigger: "root cause unclear", from: "Repo Researcher", to: "Repo Researcher", action: "gather more evidence, add reproduction steps" },
79
+ { trigger: "fix introduces regression", from: "Test Engineer", to: "Backend Engineer", action: "revise fix approach" },
80
+ { trigger: "diagnosis was wrong", from: "Backend Engineer", to: "Repo Researcher", action: "re-diagnose with new evidence from attempted fix" },
81
+ ],
82
+ honestPartial: "Root cause identified and documented but fix is non-trivial. Diagnosis report artifact is complete. Handoff includes attempted approaches and why they failed.",
83
+ stopConditions: [
84
+ "Critic Reviewer accepts fix",
85
+ "Root cause confirmed but fix requires scope expansion — replan as feature-ship",
86
+ "Bug is not reproducible — document findings and close",
87
+ ],
88
+ dispatchDefaults: { model: "sonnet", maxTurns: 20, maxBudgetUsd: 3.0 },
89
+ trialEvidence: "G3, G4 — 4/4 gold passes. Honest partial validated in R1-2 (README anomaly).",
90
+ },
91
+
92
+ // ── Treatment (Repo Polish) ─────────────────────────────────────────────
93
+ "treatment": {
94
+ name: "Treatment (Repo Polish)",
95
+ description: "Full treatment: shipcheck gates, polish, docs, translations, version bump, publish.",
96
+ pack: "treatment",
97
+ entryPath: "Shipcheck gates first → Security Reviewer audits → Docs Architect updates → Deployment Verifier verifies CI → Critic reviews",
98
+ roleChain: [
99
+ "Security Reviewer",
100
+ "Docs Architect",
101
+ "Deployment Verifier",
102
+ "Critic Reviewer",
103
+ ],
104
+ artifactFlow: [
105
+ { role: "Security Reviewer", produces: "security-audit", consumedBy: "Docs Architect" },
106
+ { role: "Docs Architect", produces: "docs-update", consumedBy: "Deployment Verifier" },
107
+ { role: "Deployment Verifier", produces: "ci-verification", consumedBy: "Critic Reviewer" },
108
+ { role: "Critic Reviewer", produces: "review-verdict", consumedBy: null },
109
+ ],
110
+ escalationBranches: [
111
+ { trigger: "security gate fails", from: "Security Reviewer", to: "Backend Engineer", action: "fix security issue before treatment continues" },
112
+ { trigger: "CI fails after changes", from: "Deployment Verifier", to: "Backend Engineer", action: "fix CI, re-verify" },
113
+ { trigger: "shipcheck hard gate blocks", from: "Critic Reviewer", to: "Security Reviewer", action: "address blocking gate, restart treatment" },
114
+ ],
115
+ honestPartial: "Shipcheck passed but polish incomplete. Security and docs artifacts exist. Remaining items documented in handoff.",
116
+ stopConditions: [
117
+ "All shipcheck hard gates pass + Critic accepts",
118
+ "Security gate A blocks — must fix before continuing",
119
+ "Scope expands to feature work — decompose into treatment + feature-ship",
120
+ ],
121
+ dispatchDefaults: { model: "sonnet", maxTurns: 25, maxBudgetUsd: 4.0 },
122
+ trialEvidence: "G5, G6 — treatment pack proven. Completion proof arc validated honest partial.",
123
+ },
124
+
125
+ // ── Docs / Release ──────────────────────────────────────────────────────
126
+ "docs-release": {
127
+ name: "Docs & Release",
128
+ description: "Write or update documentation, prepare release notes, publish.",
129
+ pack: "docs",
130
+ entryPath: "Docs Architect synthesizes upstream → writes docs → Critic reviews completeness",
131
+ roleChain: [
132
+ "Docs Architect",
133
+ "Critic Reviewer",
134
+ ],
135
+ artifactFlow: [
136
+ { role: "Docs Architect", produces: "docs-update", consumedBy: "Critic Reviewer" },
137
+ { role: "Critic Reviewer", produces: "review-verdict", consumedBy: null },
138
+ ],
139
+ escalationBranches: [
140
+ { trigger: "upstream source missing", from: "Docs Architect", to: "Product Strategist", action: "clarify what changed and why" },
141
+ { trigger: "docs conflict with code", from: "Critic Reviewer", to: "Docs Architect", action: "reconcile docs with actual implementation" },
142
+ ],
143
+ honestPartial: "Core docs updated but supplementary pages (handbook, translations) pending. Main artifact complete.",
144
+ stopConditions: [
145
+ "Critic accepts docs as accurate and complete",
146
+ "Upstream changes still in flight — park docs until code stabilizes",
147
+ ],
148
+ dispatchDefaults: { model: "sonnet", maxTurns: 15, maxBudgetUsd: 2.0 },
149
+ trialEvidence: "G7, G8 — docs pack proven. Upstream-synthesis gate prevents stale docs.",
150
+ },
151
+
152
+ // ── Security Hardening ──────────────────────────────────────────────────
153
+ "security-hardening": {
154
+ name: "Security Hardening",
155
+ description: "Threat model, audit, fix vulnerabilities, verify gates.",
156
+ pack: "security",
157
+ entryPath: "Security Reviewer threat-models → identifies issues → Backend Engineer fixes → re-audit",
158
+ roleChain: [
159
+ "Security Reviewer",
160
+ "Backend Engineer",
161
+ "Test Engineer",
162
+ "Critic Reviewer",
163
+ ],
164
+ artifactFlow: [
165
+ { role: "Security Reviewer", produces: "security-audit", consumedBy: "Backend Engineer" },
166
+ { role: "Backend Engineer", produces: "change-plan", consumedBy: "Test Engineer" },
167
+ { role: "Test Engineer", produces: "test-report", consumedBy: "Security Reviewer" },
168
+ { role: "Security Reviewer", produces: "security-audit", consumedBy: "Critic Reviewer" },
169
+ { role: "Critic Reviewer", produces: "review-verdict", consumedBy: null },
170
+ ],
171
+ escalationBranches: [
172
+ { trigger: "critical vulnerability found", from: "Security Reviewer", to: "Backend Engineer", action: "immediate fix, skip non-critical work" },
173
+ { trigger: "fix breaks functionality", from: "Test Engineer", to: "Backend Engineer", action: "find fix that preserves both security and function" },
174
+ { trigger: "threat model scope expands", from: "Security Reviewer", to: "Product Strategist", action: "reframe scope — is this a feature-ship or security mission?" },
175
+ ],
176
+ honestPartial: "Threat model complete, critical issues fixed, non-critical items documented as backlog. Audit artifact reflects actual state.",
177
+ stopConditions: [
178
+ "All critical findings fixed + Critic accepts",
179
+ "No critical findings — document clean audit",
180
+ "Scope expands beyond security — decompose into security + feature-ship",
181
+ ],
182
+ dispatchDefaults: { model: "sonnet", maxTurns: 25, maxBudgetUsd: 4.0 },
183
+ trialEvidence: "G9, G10 — security pack proven. Treatment includes Security Reviewer by default.",
184
+ },
185
+
186
+ // ── Research → Framing → Launch ─────────────────────────────────────────
187
+ "research-launch": {
188
+ name: "Research → Framing → Launch",
189
+ description: "Investigate a question, frame findings into a decision, prepare launch artifacts.",
190
+ pack: "research",
191
+ entryPath: "Product Strategist frames question → Competitive Analyst investigates → findings → launch decision",
192
+ roleChain: [
193
+ "Product Strategist",
194
+ "Competitive Analyst",
195
+ "Docs Architect",
196
+ "Critic Reviewer",
197
+ ],
198
+ artifactFlow: [
199
+ { role: "Product Strategist", produces: "strategy-brief", consumedBy: "Competitive Analyst" },
200
+ { role: "Competitive Analyst", produces: "research-findings", consumedBy: "Product Strategist" },
201
+ { role: "Docs Architect", produces: "docs-update", consumedBy: "Critic Reviewer" },
202
+ { role: "Critic Reviewer", produces: "review-verdict", consumedBy: null },
203
+ ],
204
+ escalationBranches: [
205
+ { trigger: "research inconclusive", from: "Competitive Analyst", to: "Product Strategist", action: "narrow the question or accept uncertainty" },
206
+ { trigger: "findings contradict assumption", from: "Competitive Analyst", to: "Product Strategist", action: "reframe strategy based on evidence" },
207
+ { trigger: "launch blocked by findings", from: "Critic Reviewer", to: "Product Strategist", action: "decide: proceed with caveats or pivot" },
208
+ ],
209
+ honestPartial: "Research complete with findings documented. Launch decision pending stakeholder input. Research artifact is self-contained.",
210
+ stopConditions: [
211
+ "Critic accepts research findings and launch plan",
212
+ "Research reveals the question is wrong — reframe mission",
213
+ "Findings are clear but launch is premature — park with documented rationale",
214
+ ],
215
+ dispatchDefaults: { model: "sonnet", maxTurns: 20, maxBudgetUsd: 3.0 },
216
+ trialEvidence: "Research pack opens with Product Strategist (framing before research). Validated in pack comparison.",
217
+ },
218
+ };
219
+
220
+ // ── Mission catalog ─────────────────────────────────────────────────────────
221
+
222
+ /**
223
+ * List all missions with summary info.
224
+ * @returns {Array<{key: string, name: string, description: string, pack: string, roleCount: number}>}
225
+ */
226
+ export function listMissions() {
227
+ return Object.entries(MISSIONS).map(([key, m]) => ({
228
+ key,
229
+ name: m.name,
230
+ description: m.description,
231
+ pack: m.pack,
232
+ roleCount: m.roleChain.length,
233
+ }));
234
+ }
235
+
236
+ /**
237
+ * Get a mission by key.
238
+ * @param {string} key
239
+ * @returns {object|null}
240
+ */
241
+ export function getMission(key) {
242
+ return MISSIONS[key] || null;
243
+ }
244
+
245
+ /**
246
+ * Suggest a mission for a task description (signal matching).
247
+ * @param {string} taskDescription
248
+ * @returns {{mission: string, confidence: "high"|"medium"|"low", reason: string}|null}
249
+ */
250
+ export function suggestMission(taskDescription) {
251
+ const text = taskDescription.toLowerCase();
252
+
253
+ const MISSION_SIGNALS = {
254
+ "feature-ship": {
255
+ signals: ["add feature", "implement", "build", "create", "new command", "ship feature", "develop", "add command", "new feature", "add support"],
256
+ weight: 1,
257
+ },
258
+ "bugfix": {
259
+ signals: ["fix", "bug", "crash", "broken", "regression", "repair", "diagnose", "not working"],
260
+ weight: 1,
261
+ },
262
+ "treatment": {
263
+ signals: ["treatment", "polish", "shipcheck", "pre-release", "ship-ready", "cleanup", "housekeeping"],
264
+ weight: 1.2, // slightly prefer treatment when signals match (common task)
265
+ },
266
+ "docs-release": {
267
+ signals: ["documentation", "docs", "readme", "release notes", "changelog", "handbook", "write docs"],
268
+ weight: 1,
269
+ },
270
+ "security-hardening": {
271
+ signals: ["security", "vulnerability", "threat model", "audit", "CVE", "injection", "hardening"],
272
+ weight: 1.1,
273
+ },
274
+ "research-launch": {
275
+ signals: ["research", "investigate", "should we", "evaluate", "competitive", "launch plan", "strategy"],
276
+ weight: 1,
277
+ },
278
+ };
279
+
280
+ let bestKey = null;
281
+ let bestScore = 0;
282
+
283
+ for (const [key, def] of Object.entries(MISSION_SIGNALS)) {
284
+ let hits = 0;
285
+ for (const signal of def.signals) {
286
+ if (text.includes(signal)) hits++;
287
+ }
288
+ const score = hits * def.weight;
289
+ if (score > bestScore) {
290
+ bestScore = score;
291
+ bestKey = key;
292
+ }
293
+ }
294
+
295
+ if (!bestKey || bestScore === 0) return null;
296
+
297
+ const confidence = bestScore >= 3 ? "high" : bestScore >= 1.5 ? "medium" : "low";
298
+ const mission = MISSIONS[bestKey];
299
+
300
+ return {
301
+ mission: bestKey,
302
+ confidence,
303
+ reason: `Matched ${Math.floor(bestScore)} signal(s) for "${mission.name}"`,
304
+ };
305
+ }
306
+
307
+ // ── Mission validation ──────────────────────────────────────────────────────
308
+
309
+ /**
310
+ * Validate that a mission's pack and roles are properly wired.
311
+ * @param {string} key
312
+ * @returns {{valid: boolean, issues: string[]}}
313
+ */
314
+ export function validateMission(key) {
315
+ const mission = MISSIONS[key];
316
+ if (!mission) return { valid: false, issues: [`Mission "${key}" not found`] };
317
+
318
+ const issues = [];
319
+
320
+ // Check pack exists
321
+ if (!TEAM_PACKS[mission.pack]) {
322
+ issues.push(`Pack "${mission.pack}" not found in TEAM_PACKS`);
323
+ }
324
+
325
+ // S6-F1: Check all role names exist in ROLE_CATALOG
326
+ const catalogNames = new Set(ROLE_CATALOG.map((r) => r.name));
327
+ for (const roleName of mission.roleChain) {
328
+ if (!catalogNames.has(roleName)) {
329
+ issues.push(`Role "${roleName}" in roleChain not found in ROLE_CATALOG`);
330
+ }
331
+ }
332
+ for (const step of mission.artifactFlow) {
333
+ if (!catalogNames.has(step.role)) {
334
+ issues.push(`Role "${step.role}" in artifactFlow not found in ROLE_CATALOG`);
335
+ }
336
+ }
337
+
338
+ // Check artifact contracts exist for producing roles
339
+ for (const step of mission.artifactFlow) {
340
+ if (ROLE_ARTIFACT_CONTRACTS[step.role]) {
341
+ // Contract exists — good
342
+ }
343
+ // Not all roles need contracts (only chain-critical ones), so missing is a warning not error
344
+ }
345
+
346
+ // Check artifact flow continuity (each consumed artifact should be produced upstream)
347
+ const produced = new Set();
348
+ for (const step of mission.artifactFlow) {
349
+ produced.add(step.produces);
350
+ }
351
+
352
+ // Check escalation branches reference roles in the chain or known roles
353
+ for (const branch of mission.escalationBranches) {
354
+ if (!mission.roleChain.includes(branch.from) && branch.from !== branch.to) {
355
+ // from role should be in chain or self-referencing
356
+ issues.push(`Escalation "from" role "${branch.from}" not in role chain`);
357
+ }
358
+ }
359
+
360
+ // Check stop conditions exist
361
+ if (!mission.stopConditions || mission.stopConditions.length === 0) {
362
+ issues.push("Mission has no stop conditions");
363
+ }
364
+
365
+ // Check honest partial exists
366
+ if (!mission.honestPartial) {
367
+ issues.push("Mission has no honest-partial definition");
368
+ }
369
+
370
+ return { valid: issues.length === 0, issues };
371
+ }
372
+
373
+ /**
374
+ * Validate ALL missions.
375
+ * @returns {{allValid: boolean, results: Record<string, {valid: boolean, issues: string[]}>}}
376
+ */
377
+ export function validateAllMissions() {
378
+ const results = {};
379
+ let allValid = true;
380
+
381
+ for (const key of Object.keys(MISSIONS)) {
382
+ const result = validateMission(key);
383
+ results[key] = result;
384
+ if (!result.valid) allValid = false;
385
+ }
386
+
387
+ return { allValid, results };
388
+ }