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.
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Structured evidence for verdicts.
3
+ *
4
+ * A verdict is not just an opinion. It is a decision bound to explicit evidence.
5
+ * This module defines evidence schemas, role-aware requirements, sufficiency
6
+ * checks, and verdict-to-recovery continuity.
7
+ */
8
+
9
+ // ── Evidence item kinds ───────────────────────────────────────────────────────
10
+
11
+ export const EVIDENCE_KINDS = [
12
+ "spec", // specification, requirements, scope doc
13
+ "code", // source code, implementation
14
+ "test", // test results, coverage data
15
+ "output", // build output, CLI output, logs
16
+ "packet", // another packet (upstream/downstream)
17
+ "diff", // code diff, before/after
18
+ "doc", // documentation, README, handbook
19
+ "reasoning", // architectural decision, tradeoff analysis
20
+ "dependency", // upstream dependency status
21
+ "user-intent", // user request, product requirement
22
+ "measurement", // metric, benchmark, performance data
23
+ "review", // prior review, verdict, feedback
24
+ ];
25
+
26
+ export const EVIDENCE_STATUSES = [
27
+ "supports", // evidence directly supports the verdict
28
+ "partial", // evidence partially supports — gaps remain
29
+ "contradicts", // evidence contradicts the verdict (must be explained)
30
+ "missing", // expected evidence was not provided
31
+ ];
32
+
33
+ export const VERDICT_TYPES = [
34
+ "accept", // work meets bar, move forward
35
+ "accept-with-notes", // work meets bar with documented caveats
36
+ "reject", // work does not meet bar, fundamental problems
37
+ "blocked", // cannot proceed — external dependency or ambiguity
38
+ ];
39
+
40
+ export const CONFIDENCE_LEVELS = ["high", "medium", "low"];
41
+
42
+ // ── Role-aware evidence requirements ──────────────────────────────────────────
43
+ // What evidence each reviewer role MUST provide. Derived from role contracts.
44
+
45
+ export const ROLE_EVIDENCE_REQUIREMENTS = {
46
+ "Critic Reviewer": {
47
+ required: ["spec", "reasoning"],
48
+ recommended: ["test", "code", "review"],
49
+ description: "Must cite quality bar items checked, contract compliance, and cross-contamination assessment",
50
+ },
51
+ "Test Engineer": {
52
+ required: ["test", "output"],
53
+ recommended: ["code", "measurement"],
54
+ description: "Must cite test results (pass/fail), coverage data, and edge cases verified",
55
+ },
56
+ "Backend Engineer": {
57
+ required: ["code", "test"],
58
+ recommended: ["diff", "output", "dependency"],
59
+ description: "Must cite changed files, test status, and API contract compliance",
60
+ },
61
+ "Frontend Developer": {
62
+ required: ["code", "test"],
63
+ recommended: ["diff", "output"],
64
+ description: "Must cite changed components, test status, and interaction verification",
65
+ },
66
+ "UI Designer": {
67
+ required: ["spec", "reasoning"],
68
+ recommended: ["doc"],
69
+ description: "Must cite design decisions, hierarchy rationale, and user flow justification",
70
+ },
71
+ "Product Strategist": {
72
+ required: ["user-intent", "reasoning"],
73
+ recommended: ["spec", "review"],
74
+ description: "Must cite requirement fit, scope alignment, and tradeoff decisions",
75
+ },
76
+ "Security Reviewer": {
77
+ required: ["code", "reasoning"],
78
+ recommended: ["test", "measurement"],
79
+ description: "Must cite patterns checked, threats assessed, and mitigations verified",
80
+ },
81
+ "Performance Engineer": {
82
+ required: ["measurement", "output"],
83
+ recommended: ["code", "diff"],
84
+ description: "Must cite profiling results, metrics before/after, and budget compliance",
85
+ },
86
+ "Coverage Auditor": {
87
+ required: ["test", "measurement"],
88
+ recommended: ["code", "reasoning"],
89
+ description: "Must cite coverage metrics, false confidence findings, and untested paths",
90
+ },
91
+ "Release Engineer": {
92
+ required: ["output", "doc"],
93
+ recommended: ["test", "diff"],
94
+ description: "Must cite version, changelog, build output, and publish status",
95
+ },
96
+ "Deployment Verifier": {
97
+ required: ["output", "doc"],
98
+ recommended: ["test"],
99
+ description: "Must cite live artifact status, badge checks, and spot-check results",
100
+ },
101
+ "Dependency Auditor": {
102
+ required: ["measurement", "reasoning"],
103
+ recommended: ["output"],
104
+ description: "Must cite audit results, vulnerability findings, and supply-chain assessment",
105
+ },
106
+ "Brand Guardian": {
107
+ required: ["reasoning", "doc"],
108
+ recommended: ["diff"],
109
+ description: "Must cite terminology audit, contamination findings, and identity assessment",
110
+ },
111
+ "Repo Researcher": {
112
+ required: ["doc", "reasoning"],
113
+ recommended: ["code"],
114
+ description: "Must cite repo map, entrypoints found, build/test commands verified",
115
+ },
116
+ "Spec Writer": {
117
+ required: ["spec", "reasoning"],
118
+ recommended: ["user-intent"],
119
+ description: "Must cite acceptance criteria, edge cases enumerated, and NFRs defined",
120
+ },
121
+ };
122
+
123
+ // Default for roles without explicit requirements
124
+ const DEFAULT_REQUIREMENTS = {
125
+ required: ["reasoning"],
126
+ recommended: [],
127
+ description: "Must provide reasoning for the verdict",
128
+ };
129
+
130
+ // ── Evidence sufficiency check ────────────────────────────────────────────────
131
+
132
+ /**
133
+ * @typedef {Object} EvidenceItem
134
+ * @property {string} kind - One of EVIDENCE_KINDS
135
+ * @property {string} reference - File, path, packet ID, section
136
+ * @property {string} claim - What this evidence supports
137
+ * @property {string} status - One of EVIDENCE_STATUSES
138
+ * @property {string} [notes] - Additional context
139
+ */
140
+
141
+ /**
142
+ * @typedef {Object} StructuredVerdict
143
+ * @property {string} verdict - One of VERDICT_TYPES
144
+ * @property {string} reviewerRole - Who is reviewing
145
+ * @property {string} summary - Brief verdict summary
146
+ * @property {EvidenceItem[]} evidence - Structured evidence items
147
+ * @property {string[]} gaps - What's missing or weak
148
+ * @property {string[]} risks - Identified risks
149
+ * @property {string} [requiredNextArtifact] - What the next role must produce (for non-approve)
150
+ * @property {string} confidence - One of CONFIDENCE_LEVELS
151
+ */
152
+
153
+ /**
154
+ * @typedef {Object} SufficiencyResult
155
+ * @property {boolean} sufficient - Whether evidence meets role requirements
156
+ * @property {string[]} missingRequired - Required evidence kinds not provided
157
+ * @property {string[]} missingRecommended - Recommended evidence kinds not provided
158
+ * @property {string[]} contradictions - Evidence items that contradict the verdict
159
+ * @property {string[]} warnings - Non-blocking issues
160
+ */
161
+
162
+ /**
163
+ * Check whether a structured verdict has sufficient evidence for the reviewer role.
164
+ *
165
+ * @param {StructuredVerdict} verdict
166
+ * @returns {SufficiencyResult}
167
+ */
168
+ export function checkSufficiency(verdict) {
169
+ const reqs = ROLE_EVIDENCE_REQUIREMENTS[verdict.reviewerRole] || DEFAULT_REQUIREMENTS;
170
+ const providedKinds = new Set(verdict.evidence.map(e => e.kind));
171
+ const warnings = [];
172
+
173
+ // Check required evidence kinds
174
+ const missingRequired = reqs.required.filter(kind => !providedKinds.has(kind));
175
+
176
+ // Check recommended evidence kinds
177
+ const missingRecommended = reqs.recommended.filter(kind => !providedKinds.has(kind));
178
+
179
+ // Check for contradictions
180
+ const contradictions = verdict.evidence
181
+ .filter(e => e.status === "contradicts")
182
+ .map(e => `${e.kind}: ${e.claim} (${e.reference})`);
183
+
184
+ if (contradictions.length > 0 && verdict.verdict === "accept") {
185
+ warnings.push("Verdict is 'approve' but evidence contains contradictions — review carefully");
186
+ }
187
+
188
+ // Check for missing evidence items on non-approve verdicts
189
+ const missingItems = verdict.evidence.filter(e => e.status === "missing");
190
+ if (missingItems.length > 0 && verdict.verdict === "accept") {
191
+ warnings.push("Verdict is 'approve' but some evidence items are marked 'missing'");
192
+ }
193
+
194
+ // Non-approve verdicts should have gaps or requiredNextArtifact
195
+ if (verdict.verdict !== "approve" && verdict.verdict !== "accept-with-notes") {
196
+ if (verdict.gaps.length === 0 && !verdict.requiredNextArtifact) {
197
+ warnings.push("Non-approve verdict should specify gaps or requiredNextArtifact for recovery");
198
+ }
199
+ }
200
+
201
+ // Low confidence + approve is suspicious
202
+ if (verdict.confidence === "low" && verdict.verdict === "accept") {
203
+ warnings.push("Low confidence approve — consider whether evidence is actually sufficient");
204
+ }
205
+
206
+ const sufficient = missingRequired.length === 0 && contradictions.length === 0;
207
+
208
+ return {
209
+ sufficient,
210
+ missingRequired,
211
+ missingRecommended,
212
+ contradictions,
213
+ warnings,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Get evidence requirements for a role.
219
+ *
220
+ * @param {string} roleName
221
+ * @returns {{required: string[], recommended: string[], description: string}}
222
+ */
223
+ export function getRequirements(roleName) {
224
+ return ROLE_EVIDENCE_REQUIREMENTS[roleName] || DEFAULT_REQUIREMENTS;
225
+ }
226
+
227
+ /**
228
+ * Validate an evidence item's structure.
229
+ *
230
+ * @param {EvidenceItem} item
231
+ * @returns {string[]} validation errors (empty = valid)
232
+ */
233
+ export function validateEvidenceItem(item) {
234
+ const errors = [];
235
+ if (!item.kind || !EVIDENCE_KINDS.includes(item.kind)) {
236
+ errors.push(`Invalid evidence kind: "${item.kind}". Valid: ${EVIDENCE_KINDS.join(", ")}`);
237
+ }
238
+ if (!item.reference || item.reference.trim().length === 0) {
239
+ errors.push("Evidence item must have a reference (file, path, section, or packet ID)");
240
+ }
241
+ if (!item.claim || item.claim.trim().length === 0) {
242
+ errors.push("Evidence item must have a claim (what it supports)");
243
+ }
244
+ if (!item.status || !EVIDENCE_STATUSES.includes(item.status)) {
245
+ errors.push(`Invalid evidence status: "${item.status}". Valid: ${EVIDENCE_STATUSES.join(", ")}`);
246
+ }
247
+ return errors;
248
+ }
249
+
250
+ /**
251
+ * Format a sufficiency result for operator display.
252
+ *
253
+ * @param {SufficiencyResult} result
254
+ * @param {string} roleName
255
+ * @returns {string}
256
+ */
257
+ export function formatSufficiency(result, roleName) {
258
+ const lines = [];
259
+ const reqs = ROLE_EVIDENCE_REQUIREMENTS[roleName] || DEFAULT_REQUIREMENTS;
260
+
261
+ if (result.sufficient) {
262
+ lines.push(` ✓ Evidence sufficient for ${roleName}`);
263
+ } else {
264
+ lines.push(` ✗ Evidence insufficient for ${roleName}`);
265
+ }
266
+
267
+ if (result.missingRequired.length > 0) {
268
+ lines.push(` missing (required): ${result.missingRequired.join(", ")}`);
269
+ lines.push(` ${roleName} requirement: ${reqs.description}`);
270
+ }
271
+
272
+ if (result.missingRecommended.length > 0) {
273
+ lines.push(` missing (recommended): ${result.missingRecommended.join(", ")}`);
274
+ }
275
+
276
+ if (result.contradictions.length > 0) {
277
+ lines.push(` contradictions:`);
278
+ for (const c of result.contradictions) {
279
+ lines.push(` - ${c}`);
280
+ }
281
+ }
282
+
283
+ for (const w of result.warnings) {
284
+ lines.push(` ! ${w}`);
285
+ }
286
+
287
+ return lines.join("\n");
288
+ }
@@ -0,0 +1,143 @@
1
+ import { listPacks, getPack, suggestPack, TEAM_PACKS } from "./packs.mjs";
2
+ import { readFileSafe } from "./fs-utils.mjs";
3
+ import { basename } from "node:path";
4
+
5
+ // ── List ──────────────────────────────────────────────────────────────────────
6
+
7
+ function runList() {
8
+ const packs = listPacks();
9
+ console.log(`\nroleos packs — ${packs.length} packs available\n`);
10
+ for (const p of packs) {
11
+ const key = p.key.padEnd(12);
12
+ const name = p.name.padEnd(30);
13
+ const count = `(${p.roleCount} roles)`.padEnd(12);
14
+ console.log(` ${key}${name}${count}${p.description}`);
15
+ }
16
+ console.log();
17
+ }
18
+
19
+ // ── Suggest ───────────────────────────────────────────────────────────────────
20
+
21
+ function runSuggest(packetFile) {
22
+ if (!packetFile) {
23
+ const err = new Error("Usage: roleos packs suggest <packet-file>");
24
+ err.exitCode = 1;
25
+ err.hint = "Provide the path to a packet file.";
26
+ throw err;
27
+ }
28
+
29
+ const content = readFileSafe(packetFile);
30
+ if (content === null) {
31
+ const err = new Error(`Packet not found: ${packetFile}`);
32
+ err.exitCode = 1;
33
+ err.hint = "Check the file path and try again.";
34
+ throw err;
35
+ }
36
+
37
+ const result = suggestPack(content);
38
+ const filename = basename(packetFile);
39
+
40
+ console.log(`\nroleos packs suggest — ${filename}\n`);
41
+
42
+ if (!result) {
43
+ console.log("No pack match found for this packet.");
44
+ console.log(" The packet has no recognizable keyword signals.");
45
+ console.log(" Add context or task description to improve routing.\n");
46
+ return;
47
+ }
48
+
49
+ console.log(`Suggested pack: ${result.pack}`);
50
+ console.log(`Confidence: ${result.confidence}`);
51
+
52
+ if (Object.keys(result.scores).length > 0) {
53
+ console.log("\nScores:");
54
+ const sorted = Object.entries(result.scores).sort((a, b) => b[1] - a[1]);
55
+ for (const [packKey, score] of sorted) {
56
+ console.log(` ${packKey.padEnd(14)}${score}`);
57
+ }
58
+ }
59
+
60
+ console.log(`\nNext: roleos route ${packetFile} --pack ${result.pack}\n`);
61
+ }
62
+
63
+ // ── Show ──────────────────────────────────────────────────────────────────────
64
+
65
+ function runShow(key) {
66
+ if (!key) {
67
+ const err = new Error("Usage: roleos packs show <pack-key>");
68
+ err.exitCode = 1;
69
+ err.hint = "Provide a pack key. Run 'roleos packs list' for options.";
70
+ throw err;
71
+ }
72
+
73
+ const pack = getPack(key);
74
+ if (!pack) {
75
+ const validKeys = Object.keys(TEAM_PACKS).join(", ");
76
+ const err = new Error(`Unknown pack: ${key}`);
77
+ err.exitCode = 1;
78
+ err.hint = `Valid pack keys: ${validKeys}`;
79
+ throw err;
80
+ }
81
+
82
+ console.log(`\nroleos packs show — ${key}\n`);
83
+ console.log(`${pack.name}`);
84
+ console.log(`${pack.description}\n`);
85
+
86
+ console.log(`Roles (${pack.roles.length}):`);
87
+ pack.roles.forEach((r, i) => console.log(` ${i + 1}. ${r}`));
88
+ console.log();
89
+
90
+ if (pack.optionalRoles.length > 0) {
91
+ console.log("Optional roles:");
92
+ for (const r of pack.optionalRoles) console.log(` - ${r}`);
93
+ } else {
94
+ console.log("Optional roles: None");
95
+ }
96
+ console.log();
97
+
98
+ console.log(`Chain order:\n ${pack.chainOrder}\n`);
99
+
100
+ console.log("Required artifacts:");
101
+ for (const a of pack.requiredArtifacts) console.log(` - ${a}`);
102
+ console.log();
103
+
104
+ console.log("Stop conditions:");
105
+ for (const s of pack.stopConditions) console.log(` - ${s}`);
106
+ console.log();
107
+
108
+ console.log(`Escalation owner: ${pack.escalationOwner}\n`);
109
+
110
+ const d = pack.dispatchDefaults;
111
+ console.log("Dispatch defaults:");
112
+ console.log(` model: ${d.model}`);
113
+ console.log(` maxTurns: ${d.maxTurns}`);
114
+ console.log(` maxBudgetUsd: $${d.maxBudgetUsd.toFixed(2)}`);
115
+ console.log();
116
+
117
+ console.log(`Trial evidence: ${pack.trialEvidence}\n`);
118
+ }
119
+
120
+ // ── Command entry point ───────────────────────────────────────────────────────
121
+
122
+ export async function packsCommand(args) {
123
+ const sub = args[0];
124
+
125
+ switch (sub) {
126
+ case undefined:
127
+ case "list":
128
+ runList();
129
+ break;
130
+ case "suggest":
131
+ runSuggest(args[1]);
132
+ break;
133
+ case "show":
134
+ runShow(args[1]);
135
+ break;
136
+ default: {
137
+ const err = new Error(`Unknown packs subcommand: ${sub}`);
138
+ err.exitCode = 1;
139
+ err.hint = "Run 'roleos packs list' for available subcommands.";
140
+ throw err;
141
+ }
142
+ }
143
+ }