role-os 1.0.2 → 1.2.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/src/packs.mjs ADDED
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Proven Team Packs — Calibrated.
3
+ *
4
+ * Battle-tested role combinations for common task families.
5
+ * Each pack was proven through execution trials (G1–G10) and
6
+ * calibrated by pack comparison trials (PACK-COMPARISON.md).
7
+ *
8
+ * Calibration findings applied:
9
+ * - Orchestrator is conditional (only when task is multi-role + ambiguous)
10
+ * - Every pack has mismatch detection + alternative suggestion
11
+ * - Treatment includes Security Reviewer by default
12
+ * - Research opens with Product Strategist (framing before research)
13
+ * - Docs has upstream-synthesis gate
14
+ *
15
+ * Usage: `roleos route --pack feature` or auto-detected from packet content.
16
+ */
17
+
18
+ // ── Mismatch detection ────────────────────────────────────────────────────────
19
+ // Each pack declares what it is NOT for, and which pack IS right.
20
+
21
+ /**
22
+ * @typedef {Object} MismatchGuard
23
+ * @property {string[]} notForSignals - Content patterns that indicate this pack is wrong
24
+ * @property {string} suggestInstead - Which pack to suggest instead
25
+ * @property {string} reason - Why this pack is wrong for that signal
26
+ */
27
+
28
+ // ── Pack definitions ──────────────────────────────────────────────────────────
29
+
30
+ export const TEAM_PACKS = {
31
+ // ── Feature Build ─────────────────────────────────────────────────────────
32
+ feature: {
33
+ name: "Feature Build",
34
+ description: "Full feature delivery: scope → spec → implement → test → review",
35
+ roles: [
36
+ "Orchestrator",
37
+ "Product Strategist",
38
+ "Spec Writer",
39
+ "Backend Engineer",
40
+ "Test Engineer",
41
+ "Critic Reviewer",
42
+ ],
43
+ orchestratorRequired: true, // multi-role, cross-functional — Orchestrator adds value
44
+ optionalRoles: ["UI Designer", "Frontend Developer", "Security Reviewer"],
45
+ chainOrder: "Product Strategist → Spec Writer → Backend Engineer → Test Engineer",
46
+ requiredArtifacts: ["scope doc", "spec", "implementation", "test results", "verdict"],
47
+ stopConditions: [
48
+ "Spec Writer finds scope ambiguity → escalate to Product Strategist",
49
+ "Test Engineer finds untestable spec → escalate to Spec Writer",
50
+ "Critic rejects → loop back to responsible role",
51
+ ],
52
+ escalationOwner: "Orchestrator",
53
+ dispatchDefaults: { model: "sonnet", maxTurns: 30, maxBudgetUsd: 5.0 },
54
+ trialEvidence: "G1 (Product), G2 (Engineering) — 6/6 gold-task passes. Pack comparison: wins vs free routing.",
55
+ mismatchGuards: [
56
+ { notForSignals: ["security review", "threat model", "vulnerability", "injection"], suggestInstead: "security", reason: "This is a security review, not a feature build" },
57
+ { notForSignals: ["launch", "announce", "release notes", "messaging"], suggestInstead: "launch", reason: "This is launch/messaging work, not feature implementation" },
58
+ ],
59
+ },
60
+
61
+ // ── Bugfix / Repair ───────────────────────────────────────────────────────
62
+ bugfix: {
63
+ name: "Bugfix / Repair",
64
+ description: "Diagnose → fix → verify → review. Minimal chain, fast turnaround.",
65
+ roles: [
66
+ "Repo Researcher",
67
+ "Backend Engineer",
68
+ "Test Engineer",
69
+ "Critic Reviewer",
70
+ ],
71
+ orchestratorRequired: false, // clear scope, single-domain — Orchestrator is overhead
72
+ optionalRoles: ["Frontend Developer", "Performance Engineer"],
73
+ chainOrder: "Repo Researcher → Backend Engineer → Test Engineer",
74
+ requiredArtifacts: ["repo map / diagnosis", "fix implementation", "regression tests", "verdict"],
75
+ stopConditions: [
76
+ "Repo Researcher cannot reproduce → escalate to user",
77
+ "Fix introduces new failures → loop back to Backend Engineer",
78
+ ],
79
+ escalationOwner: "Critic Reviewer",
80
+ dispatchDefaults: { model: "sonnet", maxTurns: 20, maxBudgetUsd: 3.0 },
81
+ trialEvidence: "G2 (Engineering), G7 (Repo Researcher), I-2 (shipped real fix). Pack comparison: free routing wins (Orchestrator overhead).",
82
+ mismatchGuards: [
83
+ { notForSignals: ["launch", "announce", "release notes"], suggestInstead: "launch", reason: "This is launch work, not a bugfix" },
84
+ { notForSignals: ["research", "should we", "tradeoff", "strategy"], suggestInstead: "research", reason: "This is a research/strategy question, not a bug to fix" },
85
+ ],
86
+ },
87
+
88
+ // ── Security Review ───────────────────────────────────────────────────────
89
+ security: {
90
+ name: "Security Review",
91
+ description: "Threat model → code review → dependency audit → verdict",
92
+ roles: [
93
+ "Security Reviewer",
94
+ "Dependency Auditor",
95
+ "Critic Reviewer",
96
+ ],
97
+ orchestratorRequired: false, // single-domain, clear scope
98
+ optionalRoles: ["Backend Engineer", "Test Engineer"],
99
+ chainOrder: "Security Reviewer → Dependency Auditor",
100
+ requiredArtifacts: ["threat model", "code review findings", "dependency audit", "verdict"],
101
+ stopConditions: [
102
+ "Critical vulnerability found → immediate escalation to user",
103
+ "Dependency with known CVE → flag for Engineering",
104
+ ],
105
+ escalationOwner: "Security Reviewer",
106
+ dispatchDefaults: { model: "sonnet", maxTurns: 25, maxBudgetUsd: 4.0 },
107
+ trialEvidence: "G6 (Security Reviewer, Dependency Auditor) — 2/2 gold-task passes. I-3 Critic found 3 gaps prior roles missed.",
108
+ mismatchGuards: [
109
+ { notForSignals: ["documentation", "handbook", "restructure", "navigation"], suggestInstead: "docs", reason: "This is docs/structure work, not a security review" },
110
+ { notForSignals: ["feature", "implement", "build", "add command"], suggestInstead: "feature", reason: "This is feature work, not a security review" },
111
+ ],
112
+ },
113
+
114
+ // ── Docs / Handbook / Release ─────────────────────────────────────────────
115
+ docs: {
116
+ name: "Docs / Handbook",
117
+ description: "Triage → synthesize → structure → write → metadata → review",
118
+ roles: [
119
+ "Support Triage Lead", // interpret raw input (triage reports, issue lists, feedback)
120
+ "Feedback Synthesizer", // cluster and theme the interpreted input
121
+ "Docs Architect", // structure and write the docs
122
+ "Metadata Curator", // verify metadata alignment
123
+ "Critic Reviewer",
124
+ ],
125
+ orchestratorRequired: false,
126
+ optionalRoles: ["Repo Translator", "Brand Guardian", "Release Engineer", "Deployment Verifier"],
127
+ chainOrder: "Support Triage Lead → Feedback Synthesizer → Docs Architect → Metadata Curator",
128
+ requiredArtifacts: ["classified input", "synthesized themes", "docs structure", "metadata audit", "verdict"],
129
+ stopConditions: [
130
+ "Support Triage Lead finds input data ambiguous → request clarification",
131
+ "Feedback Synthesizer finds insufficient signal → escalate to user",
132
+ "Docs Architect finds product direction unclear → escalate to Product Strategist",
133
+ ],
134
+ escalationOwner: "Docs Architect",
135
+ dispatchDefaults: { model: "sonnet", maxTurns: 25, maxBudgetUsd: 4.0 },
136
+ trialEvidence: "G4 (Docs Architect), G7 (Treatment), I-4 (shipped page). Calibrated: Support Triage Lead + Feedback Synthesizer upstream. Release/Deploy moved to optional (overhead for docs-only tasks).",
137
+ mismatchGuards: [
138
+ { notForSignals: ["research", "should we", "competitive", "strategy"], suggestInstead: "research", reason: "This is a research/strategy question — decide before documenting" },
139
+ { notForSignals: ["security", "threat", "vulnerability"], suggestInstead: "security", reason: "This is a security review, not docs work" },
140
+ ],
141
+ },
142
+
143
+ // ── Launch / Messaging ────────────────────────────────────────────────────
144
+ launch: {
145
+ name: "Launch / Messaging",
146
+ description: "Plan launch → write copy. Hard pipeline: Strategist → Copywriter.",
147
+ roles: [
148
+ "Launch Strategist",
149
+ "Launch Copywriter",
150
+ "Critic Reviewer",
151
+ ],
152
+ orchestratorRequired: false, // smallest pack, hard pipeline, no decomposition needed
153
+ optionalRoles: ["Content Strategist", "Community Manager"],
154
+ chainOrder: "Launch Strategist → Launch Copywriter",
155
+ requiredArtifacts: ["launch plan", "release copy", "verdict"],
156
+ stopConditions: [
157
+ "Launch Strategist finds no proof assets → delay launch",
158
+ "Launch Copywriter finds product claims unverifiable → escalate to Product Strategist",
159
+ ],
160
+ escalationOwner: "Launch Strategist",
161
+ dispatchDefaults: { model: "sonnet", maxTurns: 20, maxBudgetUsd: 3.0 },
162
+ trialEvidence: "G3 (pipeline proven), I-5 (v1.1.0 launch, Accept). Pack comparison: tie/marginal win. TRUE DEFAULT.",
163
+ mismatchGuards: [
164
+ { notForSignals: ["bug", "fix", "crash", "broken", "error"], suggestInstead: "bugfix", reason: "This is a bug to fix, not a launch to plan" },
165
+ { notForSignals: ["implement", "build", "add command", "new feature"], suggestInstead: "feature", reason: "This is feature work — build first, launch second" },
166
+ ],
167
+ },
168
+
169
+ // ── Research / Strategy ───────────────────────────────────────────────────
170
+ research: {
171
+ name: "Research / Strategy",
172
+ description: "Frame decision → gather evidence → synthesize → recommend",
173
+ roles: [
174
+ "Product Strategist", // REORDERED: framing first, then research
175
+ "UX Researcher",
176
+ "Competitive Analyst",
177
+ "Feedback Synthesizer",
178
+ "Critic Reviewer",
179
+ ],
180
+ orchestratorRequired: false, // clear pipeline, Product Strategist frames
181
+ optionalRoles: ["Trend Researcher", "User Interview Synthesizer"],
182
+ chainOrder: "Product Strategist → UX Researcher → Competitive Analyst → Feedback Synthesizer",
183
+ requiredArtifacts: ["decision frame", "friction inventory", "competitive landscape", "signal synthesis", "verdict"],
184
+ stopConditions: [
185
+ "Product Strategist finds the question too vague → request clarification",
186
+ "UX Researcher finds insufficient user data → escalate to Product Strategist",
187
+ "Competitive Analyst finds no comparable products → narrow scope",
188
+ ],
189
+ escalationOwner: "Product Strategist",
190
+ dispatchDefaults: { model: "sonnet", maxTurns: 25, maxBudgetUsd: 4.0 },
191
+ trialEvidence: "G8 (Research cluster), G9 (Growth/Product), I-6 (game dev decision). Calibrated: Product Strategist now opens (framing before research).",
192
+ mismatchGuards: [
193
+ { notForSignals: ["implement", "build", "add command", "write code"], suggestInstead: "feature", reason: "This is implementation work, not research" },
194
+ { notForSignals: ["bug", "fix", "crash", "broken"], suggestInstead: "bugfix", reason: "This is a bugfix, not a research question" },
195
+ ],
196
+ },
197
+
198
+ // ── Treatment (repo polish) ───────────────────────────────────────────────
199
+ treatment: {
200
+ name: "Treatment (Repo Polish)",
201
+ description: "Full repo treatment: research → security → audit → docs → metadata → release → deploy → verify",
202
+ roles: [
203
+ "Repo Researcher",
204
+ "Security Reviewer", // ADDED: was optional, now default (pack comparison finding)
205
+ "Coverage Auditor",
206
+ "Docs Architect",
207
+ "Metadata Curator",
208
+ "Release Engineer",
209
+ "Deployment Verifier",
210
+ "Critic Reviewer",
211
+ ],
212
+ orchestratorRequired: false, // long but sequential — each role has a clear handoff
213
+ optionalRoles: ["Brand Guardian", "Repo Translator", "Dependency Auditor"],
214
+ chainOrder: "Repo Researcher → Security Reviewer → Coverage Auditor → Docs Architect → Metadata Curator → Release Engineer → Deployment Verifier",
215
+ requiredArtifacts: ["repo map", "security findings", "coverage audit", "docs", "metadata audit", "release", "deployment verification", "verdict"],
216
+ stopConditions: [
217
+ "Security Reviewer finds critical vulnerability → block release until resolved",
218
+ "Coverage Auditor finds false confidence → flag for Test Engineer",
219
+ "Deployment Verifier finds broken live artifacts → loop back to Release Engineer",
220
+ ],
221
+ escalationOwner: "Repo Researcher",
222
+ dispatchDefaults: { model: "sonnet", maxTurns: 30, maxBudgetUsd: 5.0 },
223
+ trialEvidence: "G6-G7 (roles proven), I-7 (full chain, Accept-with-notes). Calibrated: Security Reviewer now default (was optional — pack comparison loss).",
224
+ mismatchGuards: [
225
+ { notForSignals: ["launch", "announce", "release notes", "social", "messaging"], suggestInstead: "launch", reason: "This is launch/messaging work — Treatment audits repos, it doesn't write announcements" },
226
+ { notForSignals: ["research", "should we", "competitive", "strategy"], suggestInstead: "research", reason: "This is a research/strategy question, not a repo treatment" },
227
+ ],
228
+ },
229
+ };
230
+
231
+ // ── Pack selection ────────────────────────────────────────────────────────────
232
+
233
+ const PACK_KEYWORDS = {
234
+ feature: ["feature", "build", "implement", "new", "add", "create"],
235
+ bugfix: ["bug", "fix", "repair", "broken", "crash", "error", "regression"],
236
+ security: ["security", "threat", "vulnerability", "audit", "owasp", "cve"],
237
+ docs: ["docs", "documentation", "handbook", "release", "publish", "changelog"],
238
+ launch: ["launch", "announce", "release notes", "messaging", "go-to-market"],
239
+ research: ["research", "competitive", "ux", "friction", "user", "strategy", "trend"],
240
+ treatment: ["treatment", "polish", "cleanup", "repo audit", "shipcheck", "full treatment"],
241
+ };
242
+
243
+ /**
244
+ * Suggest the best pack for a packet based on content analysis.
245
+ */
246
+ export function suggestPack(content) {
247
+ const lower = content.toLowerCase();
248
+ const scores = {};
249
+
250
+ for (const [packName, keywords] of Object.entries(PACK_KEYWORDS)) {
251
+ let score = 0;
252
+ for (const kw of keywords) {
253
+ if (lower.includes(kw)) score++;
254
+ }
255
+ if (score > 0) scores[packName] = score;
256
+ }
257
+
258
+ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
259
+ if (sorted.length === 0) return null;
260
+
261
+ const [topPack, topScore] = sorted[0];
262
+ const confidence = topScore >= 3 ? "high" : topScore >= 2 ? "medium" : "low";
263
+
264
+ return { pack: topPack, confidence, scores };
265
+ }
266
+
267
+ /**
268
+ * Check if a pack is a mismatch for the given content.
269
+ * Returns null if no mismatch, or the suggested alternative if mismatch detected.
270
+ *
271
+ * @param {string} packName
272
+ * @param {string} content - Packet content
273
+ * @returns {{ suggestInstead: string, reason: string } | null}
274
+ */
275
+ export function checkPackMismatch(packName, content) {
276
+ const pack = TEAM_PACKS[packName];
277
+ if (!pack || !pack.mismatchGuards) return null;
278
+
279
+ const lower = content.toLowerCase();
280
+ for (const guard of pack.mismatchGuards) {
281
+ const triggered = guard.notForSignals.some(signal => lower.includes(signal));
282
+ if (triggered) {
283
+ return { suggestInstead: guard.suggestInstead, reason: guard.reason };
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+
289
+ /**
290
+ * Get a pack's effective roles (with conditional Orchestrator).
291
+ *
292
+ * @param {string} packName
293
+ * @param {boolean} [forceOrchestrator=false]
294
+ * @returns {string[] | null}
295
+ */
296
+ export function getPackRoles(packName, forceOrchestrator = false) {
297
+ const pack = TEAM_PACKS[packName];
298
+ if (!pack) return null;
299
+
300
+ const roles = [...pack.roles];
301
+ // Add Orchestrator only if the pack requires it or forced
302
+ if ((pack.orchestratorRequired || forceOrchestrator) && !roles.includes("Orchestrator")) {
303
+ roles.unshift("Orchestrator");
304
+ }
305
+ // Remove Orchestrator if pack doesn't require it and not forced
306
+ if (!pack.orchestratorRequired && !forceOrchestrator && roles[0] === "Orchestrator") {
307
+ roles.shift();
308
+ }
309
+ return roles;
310
+ }
311
+
312
+ /**
313
+ * Get a pack by name.
314
+ */
315
+ export function getPack(name) {
316
+ return TEAM_PACKS[name] || null;
317
+ }
318
+
319
+ /**
320
+ * List all available packs.
321
+ */
322
+ export function listPacks() {
323
+ return Object.entries(TEAM_PACKS).map(([key, pack]) => ({
324
+ key,
325
+ name: pack.name,
326
+ description: pack.description,
327
+ roleCount: pack.roles.length,
328
+ optionalCount: pack.optionalRoles.length,
329
+ orchestratorRequired: pack.orchestratorRequired,
330
+ }));
331
+ }
package/src/review.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { resolve, dirname, basename } from "node:path";
2
2
  import { readFileSafe, writeFileSafe } from "./fs-utils.mjs";
3
3
  import { askRequired, askWithDefault, closePrompts } from "./prompts.mjs";
4
+ import { resolveBlocked, resolveRejected, formatEscalation } from "./escalation.mjs";
4
5
 
5
6
  const VERDICTS = ["accept", "accept-with-notes", "reject", "blocked"];
6
7
 
@@ -91,4 +92,15 @@ ${nextOwner}
91
92
  if (wrote) {
92
93
  console.log(`\nVerdict recorded: ${verdictPath}`);
93
94
  }
95
+
96
+ // ── Escalation auto-routing for blocked/rejected verdicts ──
97
+ if (verdict === "blocked") {
98
+ const escalation = resolveBlocked(reason);
99
+ console.log(`\nEscalation (auto-routed):`);
100
+ console.log(formatEscalation(escalation));
101
+ } else if (verdict === "reject") {
102
+ const escalation = resolveRejected(reason, reviewer);
103
+ console.log(`\nEscalation (auto-routed):`);
104
+ console.log(formatEscalation(escalation));
105
+ }
94
106
  }