role-os 1.7.0 → 1.9.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 +55 -0
- package/README.md +73 -27
- package/bin/roleos.mjs +13 -0
- package/package.json +1 -1
- package/src/entry-cmd.mjs +59 -0
- package/src/entry.mjs +357 -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,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
#### Unified Entry Path (Phase T)
|
|
8
|
+
- `roleos start <task>` — auto-decides mission vs pack vs free routing
|
|
9
|
+
- Three-level fallback ladder with confidence scores and alternatives
|
|
10
|
+
- Composite task detection warns when a task should be decomposed
|
|
11
|
+
- `--json` flag for machine-readable entry decisions
|
|
12
|
+
- 46 new tests: entry engine, comparison trials, CLI integration
|
|
13
|
+
|
|
14
|
+
#### Handbook Updates
|
|
15
|
+
- New Missions handbook page with full mission documentation
|
|
16
|
+
- Updated Getting Started to lead with `roleos start`
|
|
17
|
+
- Updated Reference with all CLI commands (start, mission, packs, artifacts, status, doctor)
|
|
18
|
+
- Updated handbook index with entry levels and 9 operating layers
|
|
19
|
+
|
|
20
|
+
#### README Overhaul
|
|
21
|
+
- "How it works" section leads with `roleos start` examples
|
|
22
|
+
- Quick Start updated with mission and start commands
|
|
23
|
+
- Added 6 Missions table
|
|
24
|
+
- Updated project structure with all 18 source modules
|
|
25
|
+
- Updated status history through v1.9.0
|
|
26
|
+
|
|
27
|
+
### Evidence
|
|
28
|
+
- 527 tests, zero failures (46 new)
|
|
29
|
+
- Entry path trials validated against 20+ real task descriptions
|
|
30
|
+
- Fallback ladder tested: mission, pack, free-routing, composite, empty input
|
|
31
|
+
|
|
32
|
+
## 1.8.0
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
#### Mission Library (Phase S — Mission Hardening)
|
|
37
|
+
- 6 named, repeatable mission types: feature-ship, bugfix, treatment, docs-release, security-hardening, research-launch
|
|
38
|
+
- Each mission declares: pack, role chain, artifact flow, escalation branches, honest-partial definition, stop conditions, dispatch defaults, trial evidence
|
|
39
|
+
- Mission runner: create → step through → complete/fail → generate completion report
|
|
40
|
+
- Completion proof reporter with honest-partial and formatted text output
|
|
41
|
+
- `roleos mission list` — list all missions
|
|
42
|
+
- `roleos mission show <key>` — full mission detail
|
|
43
|
+
- `roleos mission suggest <text>` — signal-based mission suggestion
|
|
44
|
+
- `roleos mission validate [key]` — validate mission wiring against packs/roles
|
|
45
|
+
|
|
46
|
+
#### Mission Runner Engine
|
|
47
|
+
- `createRun()` — instantiate a mission with tracked steps
|
|
48
|
+
- `startNextStep()` / `completeStep()` / `failStep()` — step lifecycle
|
|
49
|
+
- `recordEscalation()` — re-opens completed steps on escalation loops
|
|
50
|
+
- `getRunPosition()` / `getArtifactChain()` — run introspection
|
|
51
|
+
- `generateCompletionReport()` / `formatCompletionReport()` — honest outcome reporting
|
|
52
|
+
|
|
53
|
+
### Evidence
|
|
54
|
+
- 465 tests, zero failures (67 new)
|
|
55
|
+
- All 6 missions validate against live pack/role catalog
|
|
56
|
+
- Full lifecycle tests: end-to-end runs, escalation loops, partial completions, failure reporting
|
|
57
|
+
|
|
3
58
|
## 1.7.0
|
|
4
59
|
|
|
5
60
|
### Added
|
package/README.md
CHANGED
|
@@ -28,12 +28,35 @@ Role OS is the professional way to use multi-Claude. It prevents the specific fa
|
|
|
28
28
|
|
|
29
29
|
## How it works
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
Describe your task. Role OS decides the right level of orchestration automatically.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
roleos start "fix the crash in save handler"
|
|
35
|
+
# → MISSION: Bugfix & Diagnosis (70% confidence)
|
|
36
|
+
# Chain: Repo Researcher → Backend Engineer → Test Engineer → Critic Reviewer
|
|
37
|
+
|
|
38
|
+
roleos start "add a new export command"
|
|
39
|
+
# → PACK: Feature Build (50% confidence)
|
|
40
|
+
# Roles: Orchestrator, Product Strategist, Spec Writer, Backend Engineer, Test Engineer, Critic Reviewer
|
|
41
|
+
|
|
42
|
+
roleos start "something completely novel"
|
|
43
|
+
# → FREE-ROUTING (10% confidence)
|
|
44
|
+
# Hint: Create a packet and run `roleos route` for role-level routing
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**The fallback ladder:**
|
|
48
|
+
|
|
49
|
+
1. **Mission** — when the task matches a proven recurring workflow (bugfix, treatment, feature-ship, docs, security, research). Known role chain, artifact flow, escalation branches, and honest-partial definitions.
|
|
50
|
+
2. **Pack** — when the task is a known family but not a full mission shape. 7 calibrated team packs with auto-selection and mismatch guards.
|
|
51
|
+
3. **Free routing** — when the task is novel, mixed, or uncertain. Scores all 31 roles against packet content and assembles a dynamic chain.
|
|
52
|
+
|
|
53
|
+
The system never forces work through the wrong abstraction. It explains why it chose each level and offers alternatives.
|
|
54
|
+
|
|
55
|
+
**Once routed:**
|
|
56
|
+
|
|
57
|
+
1. **Each role produces a handoff** — structured output with evidence items that reduce ambiguity for the next role
|
|
58
|
+
2. **Critic reviews against contract** — accepts, rejects, or blocks based on structured evidence, not impression
|
|
59
|
+
3. **Recovery routes automatically** — blocked or rejected work gets routed to the right resolver with a reason, recovery type, and required artifact
|
|
37
60
|
|
|
38
61
|
## Org rollout state
|
|
39
62
|
|
|
@@ -73,11 +96,20 @@ Every role has a full contract: mission, use when, do not use when, expected inp
|
|
|
73
96
|
```bash
|
|
74
97
|
npx role-os init
|
|
75
98
|
|
|
76
|
-
#
|
|
99
|
+
# Describe what you need — Role OS picks the right level:
|
|
100
|
+
roleos start "fix the crash in save handler"
|
|
101
|
+
|
|
102
|
+
# Or go manual:
|
|
77
103
|
roleos packet new feature
|
|
78
104
|
roleos route .claude/packets/my-feature.md
|
|
79
105
|
roleos review .claude/packets/my-feature.md accept
|
|
80
106
|
roleos status
|
|
107
|
+
|
|
108
|
+
# Explore missions and packs:
|
|
109
|
+
roleos mission list
|
|
110
|
+
roleos mission show bugfix
|
|
111
|
+
roleos packs list
|
|
112
|
+
roleos packs show feature
|
|
81
113
|
```
|
|
82
114
|
|
|
83
115
|
## When not to use Role OS
|
|
@@ -132,30 +164,26 @@ These are non-negotiable. If a change weakens any of them, reject it.
|
|
|
132
164
|
role-os/
|
|
133
165
|
bin/roleos.mjs ← CLI entrypoint
|
|
134
166
|
src/
|
|
167
|
+
entry.mjs ← Unified entry: mission → pack → free routing
|
|
168
|
+
entry-cmd.mjs ← `roleos start` CLI command
|
|
169
|
+
mission.mjs ← 6 named mission types (feature, bugfix, treatment, docs, security, research)
|
|
170
|
+
mission-run.mjs ← Mission runner: create → step → complete → report
|
|
171
|
+
mission-cmd.mjs ← `roleos mission` CLI commands
|
|
135
172
|
route.mjs ← 31-role routing + dynamic chain builder
|
|
173
|
+
packs.mjs ← 7 calibrated team packs + auto-selection
|
|
136
174
|
conflicts.mjs ← 4-pass conflict detection
|
|
137
175
|
escalation.mjs ← Auto-routing for blocked/rejected/split
|
|
138
176
|
evidence.mjs ← Structured evidence + role-aware requirements
|
|
139
177
|
dispatch.mjs ← Runtime dispatch manifests for multi-claude
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
dispatch.test.mjs ← 21 tests (manifests + state + escalation packets)
|
|
150
|
-
trial.test.mjs ← 12 tests (trial framework)
|
|
151
|
-
cli.test.mjs ← 22 tests (CLI integration)
|
|
152
|
-
.claude/
|
|
153
|
-
agents/ ← 31 role contracts across 8 packs
|
|
154
|
-
schemas/ ← Packet, handoff, verdict formats
|
|
155
|
-
policy/ ← Routing rules, permissions, escalation, done
|
|
156
|
-
workflows/ ← Ship feature, fix bug, launch update, full treatment
|
|
157
|
-
context/ ← Fill these for your repo
|
|
158
|
-
trials/ ← Execution trial packets + results
|
|
178
|
+
artifacts.mjs ← 20 per-role artifact contracts + 7 pack handoffs
|
|
179
|
+
decompose.mjs ← Composite task detection + splitting
|
|
180
|
+
composite.mjs ← Dependency-ordered execution + recovery
|
|
181
|
+
replan.mjs ← Mid-run adaptive replanning
|
|
182
|
+
calibration.mjs ← Outcome recording + weight tuning
|
|
183
|
+
hooks.mjs ← 5 lifecycle hooks for runtime enforcement
|
|
184
|
+
session.mjs ← Session scaffolding + doctor
|
|
185
|
+
test/ ← 527 tests across 20 test files
|
|
186
|
+
starter-pack/ ← Drop-in role contracts, policies, schemas, workflows
|
|
159
187
|
```
|
|
160
188
|
|
|
161
189
|
## Security
|
|
@@ -181,6 +209,22 @@ Role OS operates **locally only**. It copies markdown templates and writes packe
|
|
|
181
209
|
| **Session spine** | `roleos init claude` scaffolds CLAUDE.md, /roleos-route, /roleos-review, /roleos-status. `roleos doctor` verifies wiring. Route cards prove engagement. | ✓ Shipped |
|
|
182
210
|
| **Hook spine** | 5 lifecycle hooks (SessionStart, PromptSubmit, PreToolUse, SubagentStart, Stop). Advisory enforcement: route card reminders, write-tool gating, subagent role injection, completion audit. | ✓ Shipped |
|
|
183
211
|
| **Artifact spine** | 20 per-role artifact contracts. 7 pack handoff contracts. Structural validation. Chain completeness checks. Downstream roles never guess what they received. | ✓ Shipped |
|
|
212
|
+
| **Mission library** | 6 named missions (feature-ship, bugfix, treatment, docs-release, security-hardening, research-launch). Each declares pack, role chain, artifact flow, escalation branches, honest-partial definition. All 6 trial-run and hardened. | ✓ Shipped |
|
|
213
|
+
| **Mission runner** | Create runs, step through with tracked state, complete/fail with honest reporting. Blocked-step propagation, out-of-chain escalation warnings, last-step re-opening. | ✓ Shipped |
|
|
214
|
+
| **Unified entry** | `roleos start` decides mission vs pack vs free routing automatically. Fallback ladder with confidence scores, alternatives, and composite detection. | ✓ Shipped |
|
|
215
|
+
|
|
216
|
+
## 6 missions
|
|
217
|
+
|
|
218
|
+
| Mission | Pack | Roles | When to use |
|
|
219
|
+
|---------|------|-------|-------------|
|
|
220
|
+
| `feature-ship` | feature | 5 | Full feature delivery: scope → spec → implement → test → review |
|
|
221
|
+
| `bugfix` | bugfix | 4 | Diagnose root cause, fix, test, verify |
|
|
222
|
+
| `treatment` | treatment | 4 | Shipcheck + polish + docs + CI verify + review |
|
|
223
|
+
| `docs-release` | docs | 2 | Write/update documentation, release notes |
|
|
224
|
+
| `security-hardening` | security | 4 | Threat model, audit, fix vulnerabilities, re-audit, verify |
|
|
225
|
+
| `research-launch` | research | 4 | Frame question, research, document findings, decide |
|
|
226
|
+
|
|
227
|
+
Each mission includes honest-partial definitions — when work stalls, the system documents what was completed and what remains instead of bluffing completion.
|
|
184
228
|
|
|
185
229
|
## Status
|
|
186
230
|
|
|
@@ -193,7 +237,9 @@ Role OS operates **locally only**. It copies markdown templates and writes packe
|
|
|
193
237
|
- v1.4.0: Session spine — `roleos init claude`, `roleos doctor`, route cards, /roleos-route + /roleos-review + /roleos-status commands. 335 tests.
|
|
194
238
|
- v1.5.0: Hook spine — 5 lifecycle hooks for runtime enforcement. 358 tests.
|
|
195
239
|
- v1.6.0: Artifact spine — 20 per-role artifact contracts, 7 pack handoff contracts, structural validation. 385 tests.
|
|
196
|
-
-
|
|
240
|
+
- v1.7.0: Completion proof — real tasks run through the full stack. `roleos artifacts` CLI. Honest escalation on structural fixes. 398 tests.
|
|
241
|
+
- v1.8.0: Mission library (Phase S) — 6 named missions, runner engine, completion reports. Hardened from 6 real trial runs. 481 tests.
|
|
242
|
+
- **v1.9.0**: Unified entry path (Phase T) — `roleos start` auto-decides mission vs pack vs free routing. Fallback ladder, composite detection, entry-path comparison trials. 527 tests.
|
|
197
243
|
|
|
198
244
|
## License
|
|
199
245
|
|
package/bin/roleos.mjs
CHANGED
|
@@ -11,6 +11,8 @@ 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";
|
|
15
|
+
import { startCommand } from "../src/entry-cmd.mjs";
|
|
14
16
|
|
|
15
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
18
|
const VERSION = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
@@ -20,6 +22,7 @@ function printHelp() {
|
|
|
20
22
|
roleos v${VERSION} — Role OS bootstrap CLI
|
|
21
23
|
|
|
22
24
|
Usage:
|
|
25
|
+
roleos start <task> Decide entry path: mission, pack, or free routing
|
|
23
26
|
roleos init Scaffold Role OS into .claude/
|
|
24
27
|
roleos init --force Update canonical files (protects context/)
|
|
25
28
|
roleos packet new <type> Create a new packet (feature|integration|identity)
|
|
@@ -35,6 +38,10 @@ Usage:
|
|
|
35
38
|
roleos artifacts show <role> Show artifact contract for a role
|
|
36
39
|
roleos artifacts validate <role> <file> Validate a file against a contract
|
|
37
40
|
roleos artifacts chain <pack> Show pack handoff flow
|
|
41
|
+
roleos mission list List all missions
|
|
42
|
+
roleos mission show <key> Show full mission detail
|
|
43
|
+
roleos mission suggest <text> Suggest a mission for a task
|
|
44
|
+
roleos mission validate [key] Validate mission wiring
|
|
38
45
|
roleos doctor Verify repo is wired for Role OS sessions
|
|
39
46
|
roleos help Show this help
|
|
40
47
|
|
|
@@ -70,6 +77,9 @@ const args = process.argv.slice(3);
|
|
|
70
77
|
|
|
71
78
|
try {
|
|
72
79
|
switch (command) {
|
|
80
|
+
case "start":
|
|
81
|
+
await startCommand(args);
|
|
82
|
+
break;
|
|
73
83
|
case "init":
|
|
74
84
|
if (args[0] === "claude") {
|
|
75
85
|
const force = args.includes("--force");
|
|
@@ -111,6 +121,9 @@ try {
|
|
|
111
121
|
case "artifacts":
|
|
112
122
|
await artifactsCommand(args);
|
|
113
123
|
break;
|
|
124
|
+
case "mission":
|
|
125
|
+
await missionCommand(args);
|
|
126
|
+
break;
|
|
114
127
|
case "help":
|
|
115
128
|
case "--help":
|
|
116
129
|
case "-h":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "role-os",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry CLI — Phase T (v1.9.0)
|
|
3
|
+
*
|
|
4
|
+
* roleos start <task description> Decide best entry path automatically
|
|
5
|
+
* roleos start --json <text> Output as JSON
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { decideEntry, formatEntryDecision } from "./entry.mjs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string[]} args
|
|
12
|
+
*/
|
|
13
|
+
export async function startCommand(args) {
|
|
14
|
+
const isJson = args.includes("--json");
|
|
15
|
+
const textArgs = args.filter(a => a !== "--json");
|
|
16
|
+
const text = textArgs.join(" ").trim();
|
|
17
|
+
|
|
18
|
+
if (!text) {
|
|
19
|
+
const err = new Error(
|
|
20
|
+
"Usage: roleos start <task description>\n" +
|
|
21
|
+
" Decides whether to route through a mission, pack, or free routing.\n\n" +
|
|
22
|
+
"Examples:\n" +
|
|
23
|
+
' roleos start "fix the crash in save handler"\n' +
|
|
24
|
+
' roleos start "add a new export command"\n' +
|
|
25
|
+
' roleos start "run the full treatment before publishing"'
|
|
26
|
+
);
|
|
27
|
+
err.exitCode = 1;
|
|
28
|
+
err.hint = "Describe the task you want to accomplish.";
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const decision = decideEntry(text);
|
|
33
|
+
|
|
34
|
+
if (isJson) {
|
|
35
|
+
console.log(JSON.stringify(decision, null, 2));
|
|
36
|
+
} else {
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(formatEntryDecision(decision));
|
|
39
|
+
console.log("");
|
|
40
|
+
|
|
41
|
+
// Action guidance
|
|
42
|
+
if (decision.level === "mission") {
|
|
43
|
+
console.log("Next steps:");
|
|
44
|
+
console.log(` 1. roleos mission show ${decision.mission.key} # review the mission`);
|
|
45
|
+
console.log(` 2. Create a packet and start working through the mission steps`);
|
|
46
|
+
console.log(` 3. Use the mission runner to track progress and artifacts`);
|
|
47
|
+
} else if (decision.level === "pack") {
|
|
48
|
+
console.log("Next steps:");
|
|
49
|
+
console.log(` 1. roleos packs show ${decision.pack.key} # review the pack`);
|
|
50
|
+
console.log(` 2. roleos packet new feature # create a packet`);
|
|
51
|
+
console.log(` 3. roleos route <packet> --pack=${decision.pack.key} # route with pack`);
|
|
52
|
+
} else {
|
|
53
|
+
console.log("Next steps:");
|
|
54
|
+
console.log(" 1. roleos packet new feature # create a packet");
|
|
55
|
+
console.log(" 2. roleos route <packet> # free routing");
|
|
56
|
+
}
|
|
57
|
+
console.log("");
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/entry.mjs
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Entry Path — Phase T (v1.9.0)
|
|
3
|
+
*
|
|
4
|
+
* When a user brings Role-OS a task, the system decides the best
|
|
5
|
+
* abstraction level automatically:
|
|
6
|
+
*
|
|
7
|
+
* 1. MISSION — when the task matches a proven recurring workflow
|
|
8
|
+
* 2. PACK — when the task is a known family but not a full mission shape
|
|
9
|
+
* 3. FREE ROUTING — when the task is novel, mixed, or uncertain
|
|
10
|
+
*
|
|
11
|
+
* The entry path is honest: it explains WHY it chose each level,
|
|
12
|
+
* and never forces work through the wrong abstraction.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { suggestMission, getMission, MISSIONS } from "./mission.mjs";
|
|
16
|
+
import { suggestPack, getPack, checkPackMismatch, TEAM_PACKS } from "./packs.mjs";
|
|
17
|
+
import { detectComposite } from "./decompose.mjs";
|
|
18
|
+
import { ROLE_CATALOG } from "./route.mjs";
|
|
19
|
+
|
|
20
|
+
// ── Entry levels ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {"mission"|"pack"|"free-routing"} EntryLevel
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} EntryDecision
|
|
28
|
+
* @property {EntryLevel} level - Which abstraction level was chosen
|
|
29
|
+
* @property {string} reason - Human-readable explanation
|
|
30
|
+
* @property {number} confidence - 0-1 confidence in this choice
|
|
31
|
+
* @property {object|null} mission - Mission details (when level=mission)
|
|
32
|
+
* @property {object|null} pack - Pack details (when level=pack)
|
|
33
|
+
* @property {object|null} freeRouting - Routing hints (when level=free-routing)
|
|
34
|
+
* @property {object|null} alternative - Next-best option if operator disagrees
|
|
35
|
+
* @property {boolean} isComposite - Whether the task looks like multiple jobs
|
|
36
|
+
* @property {string[]} warnings - Any concerns about the choice
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ── Confidence thresholds ───────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const MISSION_HIGH_THRESHOLD = 0.7; // strong mission match → use mission
|
|
42
|
+
const MISSION_MEDIUM_THRESHOLD = 0.4; // decent match → mission if pack also agrees
|
|
43
|
+
const PACK_HIGH_THRESHOLD = 0.6; // strong pack match → use pack
|
|
44
|
+
const PACK_MEDIUM_THRESHOLD = 0.3; // decent match → use pack as fallback
|
|
45
|
+
|
|
46
|
+
// ── Core entry function ─────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Decide the best entry path for a task description.
|
|
50
|
+
*
|
|
51
|
+
* The fallback ladder:
|
|
52
|
+
* mission (when fit is strong)
|
|
53
|
+
* → pack (when mission fit is weak but task family is clear)
|
|
54
|
+
* → free routing (when the task is novel or mixed)
|
|
55
|
+
*
|
|
56
|
+
* @param {string} taskDescription
|
|
57
|
+
* @returns {EntryDecision}
|
|
58
|
+
*/
|
|
59
|
+
export function decideEntry(taskDescription) {
|
|
60
|
+
if (!taskDescription || taskDescription.trim().length === 0) {
|
|
61
|
+
return {
|
|
62
|
+
level: "free-routing",
|
|
63
|
+
reason: "No task description provided — defaulting to free routing.",
|
|
64
|
+
confidence: 0,
|
|
65
|
+
mission: null,
|
|
66
|
+
pack: null,
|
|
67
|
+
freeRouting: { hint: "Provide a task description for better routing." },
|
|
68
|
+
alternative: null,
|
|
69
|
+
isComposite: false,
|
|
70
|
+
warnings: ["Empty task description"],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const text = taskDescription.trim();
|
|
75
|
+
const warnings = [];
|
|
76
|
+
|
|
77
|
+
// Step 1: Check for composite tasks (multi-job detection)
|
|
78
|
+
const composite = detectComposite(text);
|
|
79
|
+
const isComposite = composite.isComposite;
|
|
80
|
+
if (isComposite) {
|
|
81
|
+
warnings.push(
|
|
82
|
+
`Task looks composite (${composite.detectedCategories.map(c => c.category).join(" + ")}). ` +
|
|
83
|
+
`Consider decomposing before routing.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Step 2: Try mission suggestion
|
|
88
|
+
const missionSuggestion = suggestMission(text);
|
|
89
|
+
const missionScore = scoreMissionFit(missionSuggestion);
|
|
90
|
+
|
|
91
|
+
// Step 3: Try pack suggestion
|
|
92
|
+
const packSuggestion = suggestPack(text);
|
|
93
|
+
const packScore = scorePackFit(packSuggestion);
|
|
94
|
+
|
|
95
|
+
// Step 4: Check agreement (mission and pack point to the same family)
|
|
96
|
+
const agreement = checkAgreement(missionSuggestion, packSuggestion);
|
|
97
|
+
|
|
98
|
+
// Step 5: Apply the fallback ladder
|
|
99
|
+
return applyLadder(text, missionSuggestion, missionScore, packSuggestion, packScore, agreement, isComposite, composite, warnings);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Scoring helpers ─────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Normalize mission suggestion into a 0-1 score.
|
|
106
|
+
*/
|
|
107
|
+
function scoreMissionFit(suggestion) {
|
|
108
|
+
if (!suggestion) return 0;
|
|
109
|
+
switch (suggestion.confidence) {
|
|
110
|
+
case "high": return 0.9;
|
|
111
|
+
case "medium": return 0.55;
|
|
112
|
+
case "low": return 0.25;
|
|
113
|
+
default: return 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Normalize pack suggestion into a 0-1 score.
|
|
119
|
+
*/
|
|
120
|
+
function scorePackFit(suggestion) {
|
|
121
|
+
if (!suggestion) return 0;
|
|
122
|
+
switch (suggestion.confidence) {
|
|
123
|
+
case "high": return 0.85;
|
|
124
|
+
case "medium": return 0.5;
|
|
125
|
+
case "low": return 0.2;
|
|
126
|
+
default: return 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if mission and pack suggestions agree (point to same family).
|
|
132
|
+
*/
|
|
133
|
+
function checkAgreement(missionSuggestion, packSuggestion) {
|
|
134
|
+
if (!missionSuggestion || !packSuggestion) return false;
|
|
135
|
+
const mission = getMission(missionSuggestion.mission);
|
|
136
|
+
if (!mission) return false;
|
|
137
|
+
return mission.pack === packSuggestion.pack;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Fallback ladder ─────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
function applyLadder(text, missionSug, missionScore, packSug, packScore, agreement, isComposite, composite, warnings) {
|
|
143
|
+
// ── MISSION: strong match, or medium match with pack agreement ──────────
|
|
144
|
+
if (missionScore >= MISSION_HIGH_THRESHOLD) {
|
|
145
|
+
const mission = getMission(missionSug.mission);
|
|
146
|
+
return {
|
|
147
|
+
level: "mission",
|
|
148
|
+
reason: `Strong mission match: "${mission.name}" (${missionSug.confidence} confidence, ${missionSug.reason}). ` +
|
|
149
|
+
`This is a proven recurring workflow with known role chain, artifact flow, and escalation branches.`,
|
|
150
|
+
confidence: missionScore,
|
|
151
|
+
mission: {
|
|
152
|
+
key: missionSug.mission,
|
|
153
|
+
name: mission.name,
|
|
154
|
+
pack: mission.pack,
|
|
155
|
+
roleChain: mission.roleChain,
|
|
156
|
+
entryPath: mission.entryPath,
|
|
157
|
+
stepCount: mission.artifactFlow.length,
|
|
158
|
+
},
|
|
159
|
+
pack: null,
|
|
160
|
+
freeRouting: null,
|
|
161
|
+
alternative: packSug ? {
|
|
162
|
+
level: "pack",
|
|
163
|
+
key: packSug.pack,
|
|
164
|
+
name: TEAM_PACKS[packSug.pack]?.name,
|
|
165
|
+
confidence: packScore,
|
|
166
|
+
} : null,
|
|
167
|
+
isComposite,
|
|
168
|
+
warnings,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (missionScore >= MISSION_MEDIUM_THRESHOLD && agreement) {
|
|
173
|
+
const mission = getMission(missionSug.mission);
|
|
174
|
+
return {
|
|
175
|
+
level: "mission",
|
|
176
|
+
reason: `Mission match: "${mission.name}" (${missionSug.confidence} confidence). ` +
|
|
177
|
+
`Pack suggestion agrees (${packSug.pack}), reinforcing the mission choice.`,
|
|
178
|
+
confidence: Math.min(missionScore + 0.15, 0.85), // boost from agreement
|
|
179
|
+
mission: {
|
|
180
|
+
key: missionSug.mission,
|
|
181
|
+
name: mission.name,
|
|
182
|
+
pack: mission.pack,
|
|
183
|
+
roleChain: mission.roleChain,
|
|
184
|
+
entryPath: mission.entryPath,
|
|
185
|
+
stepCount: mission.artifactFlow.length,
|
|
186
|
+
},
|
|
187
|
+
pack: null,
|
|
188
|
+
freeRouting: null,
|
|
189
|
+
alternative: {
|
|
190
|
+
level: "pack",
|
|
191
|
+
key: packSug.pack,
|
|
192
|
+
name: TEAM_PACKS[packSug.pack]?.name,
|
|
193
|
+
confidence: packScore,
|
|
194
|
+
},
|
|
195
|
+
isComposite,
|
|
196
|
+
warnings,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── PACK: strong pack match but mission doesn't fit well ─────────────────
|
|
201
|
+
if (packScore >= PACK_HIGH_THRESHOLD) {
|
|
202
|
+
const pack = TEAM_PACKS[packSug.pack];
|
|
203
|
+
return {
|
|
204
|
+
level: "pack",
|
|
205
|
+
reason: `Task family match: "${pack.name}" pack (${packSug.confidence} confidence). ` +
|
|
206
|
+
(missionScore > 0
|
|
207
|
+
? `Mission "${getMission(missionSug.mission)?.name}" was considered but fit was weak (${missionSug.confidence}).`
|
|
208
|
+
: `No mission matched — task family is clear but not a full recurring workflow.`),
|
|
209
|
+
confidence: packScore,
|
|
210
|
+
mission: null,
|
|
211
|
+
pack: {
|
|
212
|
+
key: packSug.pack,
|
|
213
|
+
name: pack.name,
|
|
214
|
+
roles: pack.roles,
|
|
215
|
+
chainOrder: pack.chainOrder,
|
|
216
|
+
description: pack.description,
|
|
217
|
+
},
|
|
218
|
+
freeRouting: null,
|
|
219
|
+
alternative: missionSug ? {
|
|
220
|
+
level: "mission",
|
|
221
|
+
key: missionSug.mission,
|
|
222
|
+
name: getMission(missionSug.mission)?.name,
|
|
223
|
+
confidence: missionScore,
|
|
224
|
+
} : null,
|
|
225
|
+
isComposite,
|
|
226
|
+
warnings,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (packScore >= PACK_MEDIUM_THRESHOLD) {
|
|
231
|
+
const pack = TEAM_PACKS[packSug.pack];
|
|
232
|
+
return {
|
|
233
|
+
level: "pack",
|
|
234
|
+
reason: `Weak task family match: "${pack.name}" pack (${packSug.confidence} confidence). ` +
|
|
235
|
+
`Using pack as starting point — operator may want to adjust.`,
|
|
236
|
+
confidence: packScore,
|
|
237
|
+
mission: null,
|
|
238
|
+
pack: {
|
|
239
|
+
key: packSug.pack,
|
|
240
|
+
name: pack.name,
|
|
241
|
+
roles: pack.roles,
|
|
242
|
+
chainOrder: pack.chainOrder,
|
|
243
|
+
description: pack.description,
|
|
244
|
+
},
|
|
245
|
+
freeRouting: null,
|
|
246
|
+
alternative: {
|
|
247
|
+
level: "free-routing",
|
|
248
|
+
confidence: 0,
|
|
249
|
+
},
|
|
250
|
+
isComposite,
|
|
251
|
+
warnings: [...warnings, "Low pack confidence — consider free routing if this doesn't fit"],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── FREE ROUTING: novel, mixed, or uncertain ─────────────────────────────
|
|
256
|
+
const freeHints = buildFreeRoutingHints(text, missionSug, packSug, isComposite, composite);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
level: "free-routing",
|
|
260
|
+
reason: freeHints.reason,
|
|
261
|
+
confidence: 0.1,
|
|
262
|
+
mission: null,
|
|
263
|
+
pack: null,
|
|
264
|
+
freeRouting: freeHints,
|
|
265
|
+
alternative: missionSug ? {
|
|
266
|
+
level: "mission",
|
|
267
|
+
key: missionSug.mission,
|
|
268
|
+
name: getMission(missionSug.mission)?.name,
|
|
269
|
+
confidence: missionScore,
|
|
270
|
+
} : packSug ? {
|
|
271
|
+
level: "pack",
|
|
272
|
+
key: packSug.pack,
|
|
273
|
+
name: TEAM_PACKS[packSug.pack]?.name,
|
|
274
|
+
confidence: packScore,
|
|
275
|
+
} : null,
|
|
276
|
+
isComposite,
|
|
277
|
+
warnings: [...warnings, "Free routing selected — task will be scored against all 31 roles"],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function buildFreeRoutingHints(text, missionSug, packSug, isComposite, composite) {
|
|
282
|
+
let reason;
|
|
283
|
+
if (isComposite) {
|
|
284
|
+
reason = `Task looks composite (${composite.detectedCategories.map(c => c.category).join(" + ")}). ` +
|
|
285
|
+
`No single mission or pack covers all parts — use free routing with decomposition.`;
|
|
286
|
+
} else if (!missionSug && !packSug) {
|
|
287
|
+
reason = "No mission or pack matched. Task is novel — free routing will score all 31 roles.";
|
|
288
|
+
} else {
|
|
289
|
+
reason = "Mission and pack matches were too weak to commit. Free routing will let role scoring decide.";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
reason,
|
|
294
|
+
hint: isComposite
|
|
295
|
+
? "Consider running `roleos route` on a packet for each sub-task."
|
|
296
|
+
: "Create a packet with `roleos packet new` and run `roleos route` for role-level routing.",
|
|
297
|
+
suggestedRoleCount: isComposite ? composite.detectedCategories.length * 3 : null,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── Format for display ──────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Format an entry decision as human-readable text.
|
|
305
|
+
* @param {EntryDecision} decision
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
export function formatEntryDecision(decision) {
|
|
309
|
+
const lines = [];
|
|
310
|
+
const conf = Math.round(decision.confidence * 100);
|
|
311
|
+
|
|
312
|
+
lines.push(`Entry Decision: ${decision.level.toUpperCase()} (${conf}% confidence)`);
|
|
313
|
+
lines.push("");
|
|
314
|
+
lines.push(`Reason: ${decision.reason}`);
|
|
315
|
+
|
|
316
|
+
if (decision.level === "mission" && decision.mission) {
|
|
317
|
+
lines.push("");
|
|
318
|
+
lines.push(`Mission: ${decision.mission.name} (${decision.mission.key})`);
|
|
319
|
+
lines.push(`Pack: ${decision.mission.pack}`);
|
|
320
|
+
lines.push(`Entry: ${decision.mission.entryPath}`);
|
|
321
|
+
lines.push(`Chain: ${decision.mission.roleChain.join(" → ")}`);
|
|
322
|
+
lines.push(`Steps: ${decision.mission.stepCount}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (decision.level === "pack" && decision.pack) {
|
|
326
|
+
lines.push("");
|
|
327
|
+
lines.push(`Pack: ${decision.pack.name} (${decision.pack.key})`);
|
|
328
|
+
lines.push(`Roles: ${decision.pack.roles.join(", ")}`);
|
|
329
|
+
lines.push(`Chain: ${decision.pack.chainOrder}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (decision.level === "free-routing" && decision.freeRouting) {
|
|
333
|
+
lines.push("");
|
|
334
|
+
lines.push(`Hint: ${decision.freeRouting.hint}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (decision.alternative) {
|
|
338
|
+
lines.push("");
|
|
339
|
+
const altConf = Math.round((decision.alternative.confidence || 0) * 100);
|
|
340
|
+
lines.push(`Alternative: ${decision.alternative.level}${decision.alternative.name ? ` (${decision.alternative.name})` : ""} at ${altConf}% confidence`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (decision.isComposite) {
|
|
344
|
+
lines.push("");
|
|
345
|
+
lines.push(`Note: This task looks composite — consider decomposing before proceeding.`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (decision.warnings.length > 0) {
|
|
349
|
+
lines.push("");
|
|
350
|
+
lines.push("Warnings:");
|
|
351
|
+
for (const w of decision.warnings) {
|
|
352
|
+
lines.push(` - ${w}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|