role-os 1.6.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 +50 -0
- package/README.md +2 -1
- package/bin/roleos.mjs +16 -0
- package/package.json +1 -1
- package/src/artifacts-cmd.mjs +180 -0
- package/src/mission-cmd.mjs +179 -0
- package/src/mission-run.mjs +380 -0
- package/src/mission.mjs +388 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
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
|
+
|
|
29
|
+
## 1.7.0
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
#### Completion Proof (Phase R)
|
|
34
|
+
- `roleos artifacts` CLI command: list, show, validate, chain subcommands
|
|
35
|
+
- 13 new CLI integration tests for artifact inspection
|
|
36
|
+
- Real task completion missions through the full stack
|
|
37
|
+
|
|
38
|
+
#### Completion Proof Evidence
|
|
39
|
+
- R1-1 Feature mission: `roleos artifacts` command shipped through feature pack
|
|
40
|
+
- Pack: feature (high confidence, correct)
|
|
41
|
+
- Chain: 5 roles, 0 escalations, 1 minor correction
|
|
42
|
+
- Artifact contracts: all 4 used and valid
|
|
43
|
+
- R1-2 Bugfix mission: README.zh.md npm anomaly
|
|
44
|
+
- Diagnosed correctly: npm auto-includes README* regardless of files field
|
|
45
|
+
- Escalated honestly: fix requires structural decision (translation file organization)
|
|
46
|
+
- Not force-closed: deferred to treatment pass
|
|
47
|
+
|
|
48
|
+
### Evidence
|
|
49
|
+
- 398 tests, zero failures
|
|
50
|
+
- 3 missions run through the full stack
|
|
51
|
+
- Completion metrics recorded per mission
|
|
52
|
+
|
|
3
53
|
## 1.6.0
|
|
4
54
|
|
|
5
55
|
### Added
|
package/README.md
CHANGED
|
@@ -192,7 +192,8 @@ Role OS operates **locally only**. It copies markdown templates and writes packe
|
|
|
192
192
|
- v1.3.0: Outcome calibration, mixed-task decomposition, composite execution, adaptive replanning. 317 tests.
|
|
193
193
|
- v1.4.0: Session spine — `roleos init claude`, `roleos doctor`, route cards, /roleos-route + /roleos-review + /roleos-status commands. 335 tests.
|
|
194
194
|
- v1.5.0: Hook spine — 5 lifecycle hooks for runtime enforcement. 358 tests.
|
|
195
|
-
-
|
|
195
|
+
- v1.6.0: Artifact spine — 20 per-role artifact contracts, 7 pack handoff contracts, structural validation. 385 tests.
|
|
196
|
+
- **v1.7.0**: Completion proof — real tasks run through the full stack. `roleos artifacts` CLI. Honest escalation on structural fixes. 398 tests.
|
|
196
197
|
|
|
197
198
|
## License
|
|
198
199
|
|
package/bin/roleos.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import { reviewCommand } from "../src/review.mjs";
|
|
|
10
10
|
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
|
+
import { artifactsCommand } from "../src/artifacts-cmd.mjs";
|
|
14
|
+
import { missionCommand } from "../src/mission-cmd.mjs";
|
|
13
15
|
|
|
14
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
17
|
const VERSION = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
@@ -30,6 +32,14 @@ Usage:
|
|
|
30
32
|
roleos packs list List all available team packs
|
|
31
33
|
roleos packs suggest <packet-file> Suggest a pack for a packet
|
|
32
34
|
roleos packs show <pack-key> Show full detail for a named pack
|
|
35
|
+
roleos artifacts List all role artifact contracts
|
|
36
|
+
roleos artifacts show <role> Show artifact contract for a role
|
|
37
|
+
roleos artifacts validate <role> <file> Validate a file against a contract
|
|
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
|
|
33
43
|
roleos doctor Verify repo is wired for Role OS sessions
|
|
34
44
|
roleos help Show this help
|
|
35
45
|
|
|
@@ -103,6 +113,12 @@ try {
|
|
|
103
113
|
case "packs":
|
|
104
114
|
await packsCommand(args);
|
|
105
115
|
break;
|
|
116
|
+
case "artifacts":
|
|
117
|
+
await artifactsCommand(args);
|
|
118
|
+
break;
|
|
119
|
+
case "mission":
|
|
120
|
+
await missionCommand(args);
|
|
121
|
+
break;
|
|
106
122
|
case "help":
|
|
107
123
|
case "--help":
|
|
108
124
|
case "-h":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "role-os",
|
|
3
|
-
"version": "1.
|
|
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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI surface for artifact contracts — roleos artifacts
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* - artifacts (bare) — list all roles with contracts
|
|
6
|
+
* - artifacts show <role> — display artifact contract
|
|
7
|
+
* - artifacts validate <role> <file> — validate file against contract
|
|
8
|
+
* - artifacts chain <pack> — show pack handoff flow
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
12
|
+
import {
|
|
13
|
+
ROLE_ARTIFACT_CONTRACTS,
|
|
14
|
+
PACK_HANDOFF_CONTRACTS,
|
|
15
|
+
validateArtifact,
|
|
16
|
+
getArtifactContract,
|
|
17
|
+
getHandoffContract,
|
|
18
|
+
formatArtifactValidation,
|
|
19
|
+
formatPackChain,
|
|
20
|
+
validatePackChain,
|
|
21
|
+
} from "./artifacts.mjs";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Main command handler.
|
|
25
|
+
* @param {string[]} args
|
|
26
|
+
*/
|
|
27
|
+
export async function artifactsCommand(args) {
|
|
28
|
+
const sub = args[0];
|
|
29
|
+
|
|
30
|
+
if (!sub || sub === "list") {
|
|
31
|
+
runList();
|
|
32
|
+
} else if (sub === "show") {
|
|
33
|
+
runShow(args.slice(1));
|
|
34
|
+
} else if (sub === "validate") {
|
|
35
|
+
runValidate(args.slice(1));
|
|
36
|
+
} else if (sub === "chain") {
|
|
37
|
+
runChain(args.slice(1));
|
|
38
|
+
} else {
|
|
39
|
+
const err = new Error(`Unknown artifacts subcommand: "${sub}"`);
|
|
40
|
+
err.exitCode = 1;
|
|
41
|
+
err.hint = "Valid subcommands: list, show <role>, validate <role> <file>, chain <pack>";
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runList() {
|
|
47
|
+
const roles = Object.entries(ROLE_ARTIFACT_CONTRACTS);
|
|
48
|
+
console.log(`\nRole artifact contracts (${roles.length} roles)\n`);
|
|
49
|
+
console.log(`${"Role".padEnd(25)} ${"Artifact Type".padEnd(22)} ${"Required".padEnd(8)} Consumed By`);
|
|
50
|
+
console.log(`${"─".repeat(25)} ${"─".repeat(22)} ${"─".repeat(8)} ${"─".repeat(30)}`);
|
|
51
|
+
|
|
52
|
+
for (const [role, contract] of roles) {
|
|
53
|
+
const consumers = contract.consumedBy.length > 0 ? contract.consumedBy.join(", ") : "terminal";
|
|
54
|
+
console.log(
|
|
55
|
+
`${role.padEnd(25)} ${contract.artifactType.padEnd(22)} ${String(contract.requiredSections.length).padEnd(8)} ${consumers}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function runShow(args) {
|
|
61
|
+
if (args.length === 0) {
|
|
62
|
+
const err = new Error("Missing role name.");
|
|
63
|
+
err.exitCode = 1;
|
|
64
|
+
err.hint = "Usage: roleos artifacts show <role-name>";
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const input = args.join(" ");
|
|
69
|
+
const roleName = resolveRoleName(input);
|
|
70
|
+
if (!roleName) {
|
|
71
|
+
const err = new Error(`No artifact contract for role "${input}".`);
|
|
72
|
+
err.exitCode = 1;
|
|
73
|
+
err.hint = `Available roles: ${Object.keys(ROLE_ARTIFACT_CONTRACTS).join(", ")}`;
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const contract = getArtifactContract(roleName);
|
|
78
|
+
|
|
79
|
+
console.log(`\nArtifact Contract — ${roleName}\n`);
|
|
80
|
+
console.log(`Type: ${contract.artifactType}`);
|
|
81
|
+
console.log(`Required sections: ${contract.requiredSections.join(", ")}`);
|
|
82
|
+
if (contract.optionalSections.length > 0) {
|
|
83
|
+
console.log(`Optional sections: ${contract.optionalSections.join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
if (contract.requiredEvidence.length > 0) {
|
|
86
|
+
console.log(`Required evidence: ${contract.requiredEvidence.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
console.log(`Consumed by: ${contract.consumedBy.length > 0 ? contract.consumedBy.join(", ") : "terminal (no downstream consumer)"}`);
|
|
89
|
+
console.log(`Completion rule: ${contract.completionRule}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function runValidate(args) {
|
|
93
|
+
if (args.length < 2) {
|
|
94
|
+
const err = new Error("Missing arguments.");
|
|
95
|
+
err.exitCode = 1;
|
|
96
|
+
err.hint = "Usage: roleos artifacts validate <role-name> <file-path>";
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Last arg is the file, everything before is the role name
|
|
101
|
+
const filePath = args[args.length - 1];
|
|
102
|
+
const roleArgs = args.slice(0, -1);
|
|
103
|
+
const roleName = resolveRoleName(roleArgs.join(" "));
|
|
104
|
+
|
|
105
|
+
if (!roleName) {
|
|
106
|
+
const err = new Error(`Unknown role: "${roleArgs.join(" ")}".`);
|
|
107
|
+
err.exitCode = 1;
|
|
108
|
+
err.hint = `Available roles: ${Object.keys(ROLE_ARTIFACT_CONTRACTS).join(", ")}`;
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!existsSync(filePath)) {
|
|
113
|
+
const err = new Error(`File not found: "${filePath}".`);
|
|
114
|
+
err.exitCode = 1;
|
|
115
|
+
err.hint = "Provide a path to the artifact file to validate.";
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const content = readFileSync(filePath, "utf-8");
|
|
120
|
+
const validation = validateArtifact(roleName, content);
|
|
121
|
+
console.log(formatArtifactValidation(roleName, validation));
|
|
122
|
+
|
|
123
|
+
if (!validation.valid) {
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function runChain(args) {
|
|
129
|
+
const packName = args[0];
|
|
130
|
+
if (!packName) {
|
|
131
|
+
const err = new Error("Missing pack name.");
|
|
132
|
+
err.exitCode = 1;
|
|
133
|
+
err.hint = `Usage: roleos artifacts chain <pack-name>. Available: ${Object.keys(PACK_HANDOFF_CONTRACTS).join(", ")}`;
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const contract = getHandoffContract(packName);
|
|
138
|
+
if (!contract) {
|
|
139
|
+
const err = new Error(`No handoff contract for pack "${packName}".`);
|
|
140
|
+
err.exitCode = 1;
|
|
141
|
+
err.hint = `Available packs: ${Object.keys(PACK_HANDOFF_CONTRACTS).join(", ")}`;
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`\nHandoff Contract — ${packName} pack\n`);
|
|
146
|
+
console.log(`${"Step".padEnd(5)} ${"Role".padEnd(25)} ${"Produces".padEnd(22)} Consumed By`);
|
|
147
|
+
console.log(`${"─".repeat(5)} ${"─".repeat(25)} ${"─".repeat(22)} ${"─".repeat(25)}`);
|
|
148
|
+
|
|
149
|
+
contract.flow.forEach((step, i) => {
|
|
150
|
+
const consumer = step.consumedBy || "terminal";
|
|
151
|
+
console.log(
|
|
152
|
+
`${String(i + 1).padEnd(5)} ${step.role.padEnd(25)} ${step.produces.padEnd(22)} ${consumer}`
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolve a role name from user input.
|
|
159
|
+
* Handles both exact names ("Product Strategist") and slug forms ("product-strategist").
|
|
160
|
+
*/
|
|
161
|
+
function resolveRoleName(input) {
|
|
162
|
+
if (!input || !input.trim()) return null;
|
|
163
|
+
const trimmed = input.trim();
|
|
164
|
+
|
|
165
|
+
// Exact match
|
|
166
|
+
if (ROLE_ARTIFACT_CONTRACTS[trimmed]) return trimmed;
|
|
167
|
+
|
|
168
|
+
// Case-insensitive match
|
|
169
|
+
for (const role of Object.keys(ROLE_ARTIFACT_CONTRACTS)) {
|
|
170
|
+
if (role.toLowerCase() === trimmed.toLowerCase()) return role;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Slug match (product-strategist → Product Strategist)
|
|
174
|
+
const slugToName = trimmed.replace(/-/g, " ");
|
|
175
|
+
for (const role of Object.keys(ROLE_ARTIFACT_CONTRACTS)) {
|
|
176
|
+
if (role.toLowerCase() === slugToName.toLowerCase()) return role;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
@@ -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
|
+
}
|
package/src/mission.mjs
ADDED
|
@@ -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
|
+
}
|