role-os 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +332 -268
- package/README.es.md +250 -160
- package/README.fr.md +250 -160
- package/README.hi.md +250 -160
- package/README.it.md +250 -160
- package/README.ja.md +250 -160
- package/README.md +287 -204
- package/README.pt-BR.md +250 -160
- package/README.zh.md +250 -160
- package/bin/roleos.mjs +205 -140
- package/package.json +51 -51
- package/src/entry-cmd.mjs +59 -0
- package/src/entry.mjs +357 -0
- package/src/run-cmd.mjs +405 -0
- package/src/run.mjs +949 -0
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
|
+
}
|