jumpstart-mode 1.1.12 → 1.1.13

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.
Files changed (146) hide show
  1. package/.github/agents/jumpstart-adversary.agent.md +2 -1
  2. package/.github/agents/jumpstart-architect.agent.md +5 -6
  3. package/.github/agents/jumpstart-challenger.agent.md +2 -1
  4. package/.github/agents/jumpstart-devops.agent.md +2 -2
  5. package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
  6. package/.github/agents/jumpstart-maintenance.agent.md +1 -0
  7. package/.github/agents/jumpstart-performance.agent.md +1 -0
  8. package/.github/agents/jumpstart-pm.agent.md +1 -1
  9. package/.github/agents/jumpstart-refactor.agent.md +1 -0
  10. package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
  11. package/.github/agents/jumpstart-researcher.agent.md +1 -0
  12. package/.github/agents/jumpstart-retrospective.agent.md +1 -0
  13. package/.github/agents/jumpstart-reviewer.agent.md +2 -0
  14. package/.github/agents/jumpstart-scout.agent.md +1 -1
  15. package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
  16. package/.github/agents/jumpstart-security.agent.md +2 -1
  17. package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
  18. package/.github/workflows/quality.yml +19 -2
  19. package/.jumpstart/agents/analyst.md +38 -0
  20. package/.jumpstart/agents/architect.md +38 -0
  21. package/.jumpstart/agents/challenger.md +38 -0
  22. package/.jumpstart/agents/developer.md +41 -0
  23. package/.jumpstart/agents/pm.md +38 -0
  24. package/.jumpstart/agents/scout.md +33 -0
  25. package/.jumpstart/agents/ux-designer.md +4 -0
  26. package/.jumpstart/config.yaml +24 -0
  27. package/.jumpstart/schemas/timeline.schema.json +1 -0
  28. package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
  29. package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
  30. package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
  31. package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
  32. package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
  33. package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  34. package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  35. package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
  36. package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
  37. package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  38. package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
  39. package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
  40. package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
  41. package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
  42. package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
  43. package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
  44. package/.jumpstart/state/timeline.json +659 -0
  45. package/.jumpstart/usage-log.json +74 -3
  46. package/README.md +62 -1
  47. package/bin/cli.js +3217 -1
  48. package/bin/headless-runner.js +62 -2
  49. package/bin/lib/agent-checkpoint.js +168 -0
  50. package/bin/lib/ai-evaluation.js +104 -0
  51. package/bin/lib/ai-intake.js +152 -0
  52. package/bin/lib/ambiguity-heatmap.js +152 -0
  53. package/bin/lib/artifact-comparison.js +104 -0
  54. package/bin/lib/ast-edit-engine.js +157 -0
  55. package/bin/lib/backlog-sync.js +338 -0
  56. package/bin/lib/bcdr-planning.js +158 -0
  57. package/bin/lib/bidirectional-trace.js +199 -0
  58. package/bin/lib/branch-workflow.js +266 -0
  59. package/bin/lib/cab-output.js +119 -0
  60. package/bin/lib/chat-integration.js +122 -0
  61. package/bin/lib/ci-cd-integration.js +208 -0
  62. package/bin/lib/codebase-retrieval.js +125 -0
  63. package/bin/lib/collaboration.js +168 -0
  64. package/bin/lib/compliance-packs.js +213 -0
  65. package/bin/lib/context-chunker.js +128 -0
  66. package/bin/lib/context-onboarding.js +122 -0
  67. package/bin/lib/contract-first.js +124 -0
  68. package/bin/lib/cost-router.js +148 -0
  69. package/bin/lib/credential-boundary.js +155 -0
  70. package/bin/lib/data-classification.js +180 -0
  71. package/bin/lib/data-contracts.js +129 -0
  72. package/bin/lib/db-evolution.js +158 -0
  73. package/bin/lib/decision-conflicts.js +299 -0
  74. package/bin/lib/delivery-confidence.js +361 -0
  75. package/bin/lib/dependency-upgrade.js +153 -0
  76. package/bin/lib/design-system.js +133 -0
  77. package/bin/lib/deterministic-artifacts.js +151 -0
  78. package/bin/lib/diagram-studio.js +115 -0
  79. package/bin/lib/domain-ontology.js +140 -0
  80. package/bin/lib/ea-review-packet.js +151 -0
  81. package/bin/lib/enterprise-search.js +123 -0
  82. package/bin/lib/enterprise-templates.js +140 -0
  83. package/bin/lib/environment-promotion.js +220 -0
  84. package/bin/lib/estimation-studio.js +130 -0
  85. package/bin/lib/event-modeling.js +133 -0
  86. package/bin/lib/evidence-collector.js +179 -0
  87. package/bin/lib/finops-planner.js +182 -0
  88. package/bin/lib/fitness-functions.js +279 -0
  89. package/bin/lib/focus.js +448 -0
  90. package/bin/lib/governance-dashboard.js +165 -0
  91. package/bin/lib/guided-handoff.js +120 -0
  92. package/bin/lib/impact-analysis.js +190 -0
  93. package/bin/lib/incident-feedback.js +157 -0
  94. package/bin/lib/integrate.js +1 -1
  95. package/bin/lib/knowledge-graph.js +122 -0
  96. package/bin/lib/legacy-modernizer.js +160 -0
  97. package/bin/lib/migration-planner.js +144 -0
  98. package/bin/lib/model-governance.js +185 -0
  99. package/bin/lib/model-router.js +144 -0
  100. package/bin/lib/multi-repo.js +272 -0
  101. package/bin/lib/next-phase.js +53 -8
  102. package/bin/lib/ops-ownership.js +152 -0
  103. package/bin/lib/parallel-agents.js +257 -0
  104. package/bin/lib/pattern-library.js +115 -0
  105. package/bin/lib/persona-packs.js +99 -0
  106. package/bin/lib/plan-executor.js +366 -0
  107. package/bin/lib/platform-engineering.js +119 -0
  108. package/bin/lib/playback-summaries.js +126 -0
  109. package/bin/lib/policy-engine.js +240 -0
  110. package/bin/lib/portfolio-reporting.js +357 -0
  111. package/bin/lib/pr-package.js +197 -0
  112. package/bin/lib/project-memory.js +235 -0
  113. package/bin/lib/prompt-governance.js +130 -0
  114. package/bin/lib/promptless-mode.js +128 -0
  115. package/bin/lib/quality-graph.js +193 -0
  116. package/bin/lib/raci-matrix.js +188 -0
  117. package/bin/lib/refactor-planner.js +167 -0
  118. package/bin/lib/reference-architectures.js +304 -0
  119. package/bin/lib/release-readiness.js +171 -0
  120. package/bin/lib/repo-graph.js +262 -0
  121. package/bin/lib/requirements-baseline.js +358 -0
  122. package/bin/lib/risk-register.js +211 -0
  123. package/bin/lib/role-approval.js +249 -0
  124. package/bin/lib/role-views.js +142 -0
  125. package/bin/lib/root-cause-analysis.js +132 -0
  126. package/bin/lib/runtime-debugger.js +154 -0
  127. package/bin/lib/safe-rename.js +135 -0
  128. package/bin/lib/semantic-diff.js +335 -0
  129. package/bin/lib/sla-slo.js +210 -0
  130. package/bin/lib/spec-comments.js +147 -0
  131. package/bin/lib/spec-maturity.js +287 -0
  132. package/bin/lib/sre-integration.js +154 -0
  133. package/bin/lib/structured-elicitation.js +174 -0
  134. package/bin/lib/telemetry-feedback.js +118 -0
  135. package/bin/lib/test-generator.js +146 -0
  136. package/bin/lib/timeline.js +2 -1
  137. package/bin/lib/tool-bridge.js +107 -0
  138. package/bin/lib/tool-guardrails.js +139 -0
  139. package/bin/lib/tool-schemas.js +172 -3
  140. package/bin/lib/transcript-ingestion.js +150 -0
  141. package/bin/lib/vendor-risk.js +173 -0
  142. package/bin/lib/waiver-workflow.js +174 -0
  143. package/bin/lib/web-dashboard.js +126 -0
  144. package/bin/lib/workshop-mode.js +165 -0
  145. package/bin/lib/workstream-ownership.js +104 -0
  146. package/package.json +1 -1
@@ -0,0 +1,211 @@
1
+ /**
2
+ * risk-register.js — Risk Register Generation & Tracking (Item 29)
3
+ *
4
+ * Create, update, and monitor business, delivery, security,
5
+ * and operational risks.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/risk-register.js add|update|list|report [options]
9
+ *
10
+ * State file: .jumpstart/state/risk-register.json
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'risk-register.json');
19
+
20
+ const RISK_CATEGORIES = ['business', 'delivery', 'security', 'operational', 'compliance', 'technical'];
21
+ const RISK_LIKELIHOODS = ['rare', 'unlikely', 'possible', 'likely', 'almost-certain'];
22
+ const RISK_IMPACTS = ['negligible', 'minor', 'moderate', 'major', 'critical'];
23
+ const RISK_STATUSES = ['identified', 'mitigating', 'accepted', 'resolved', 'closed'];
24
+
25
+ const RISK_SCORE_MATRIX = {
26
+ 'rare': { negligible: 1, minor: 2, moderate: 3, major: 4, critical: 5 },
27
+ 'unlikely': { negligible: 2, minor: 4, moderate: 6, major: 8, critical: 10 },
28
+ 'possible': { negligible: 3, minor: 6, moderate: 9, major: 12, critical: 15 },
29
+ 'likely': { negligible: 4, minor: 8, moderate: 12, major: 16, critical: 20 },
30
+ 'almost-certain': { negligible: 5, minor: 10, moderate: 15, major: 20, critical: 25 }
31
+ };
32
+
33
+ function defaultState() {
34
+ return {
35
+ version: '1.0.0',
36
+ created_at: new Date().toISOString(),
37
+ last_updated: null,
38
+ risks: [],
39
+ mitigations: []
40
+ };
41
+ }
42
+
43
+ function loadState(stateFile) {
44
+ const filePath = stateFile || DEFAULT_STATE_FILE;
45
+ if (!fs.existsSync(filePath)) return defaultState();
46
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
47
+ catch { return defaultState(); }
48
+ }
49
+
50
+ function saveState(state, stateFile) {
51
+ const filePath = stateFile || DEFAULT_STATE_FILE;
52
+ const dir = path.dirname(filePath);
53
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
54
+ state.last_updated = new Date().toISOString();
55
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
56
+ }
57
+
58
+ /**
59
+ * Add a new risk.
60
+ *
61
+ * @param {object} risk - { title, category, description, likelihood, impact, owner?, mitigation? }
62
+ * @param {object} [options]
63
+ * @returns {object}
64
+ */
65
+ function addRisk(risk, options = {}) {
66
+ if (!risk || !risk.title || !risk.description) {
67
+ return { success: false, error: 'title and description are required' };
68
+ }
69
+
70
+ const category = (risk.category || 'technical').toLowerCase();
71
+ if (!RISK_CATEGORIES.includes(category)) {
72
+ return { success: false, error: `Invalid category. Must be one of: ${RISK_CATEGORIES.join(', ')}` };
73
+ }
74
+
75
+ const likelihood = (risk.likelihood || 'possible').toLowerCase();
76
+ const impact = (risk.impact || 'moderate').toLowerCase();
77
+
78
+ if (!RISK_LIKELIHOODS.includes(likelihood)) {
79
+ return { success: false, error: `Invalid likelihood. Must be one of: ${RISK_LIKELIHOODS.join(', ')}` };
80
+ }
81
+ if (!RISK_IMPACTS.includes(impact)) {
82
+ return { success: false, error: `Invalid impact. Must be one of: ${RISK_IMPACTS.join(', ')}` };
83
+ }
84
+
85
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
86
+ const state = loadState(stateFile);
87
+
88
+ const score = RISK_SCORE_MATRIX[likelihood][impact];
89
+
90
+ const newRisk = {
91
+ id: `RISK-${(state.risks.length + 1).toString().padStart(3, '0')}`,
92
+ title: risk.title,
93
+ description: risk.description,
94
+ category,
95
+ likelihood,
96
+ impact,
97
+ score,
98
+ status: 'identified',
99
+ owner: risk.owner || null,
100
+ mitigation: risk.mitigation || null,
101
+ created_at: new Date().toISOString(),
102
+ updated_at: new Date().toISOString()
103
+ };
104
+
105
+ state.risks.push(newRisk);
106
+ saveState(state, stateFile);
107
+
108
+ return { success: true, risk: newRisk };
109
+ }
110
+
111
+ /**
112
+ * Update a risk's status or details.
113
+ *
114
+ * @param {string} riskId
115
+ * @param {object} updates
116
+ * @param {object} [options]
117
+ * @returns {object}
118
+ */
119
+ function updateRisk(riskId, updates, options = {}) {
120
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
121
+ const state = loadState(stateFile);
122
+
123
+ const risk = state.risks.find(r => r.id === riskId);
124
+ if (!risk) return { success: false, error: `Risk not found: ${riskId}` };
125
+
126
+ if (updates.status && RISK_STATUSES.includes(updates.status)) risk.status = updates.status;
127
+ if (updates.mitigation) risk.mitigation = updates.mitigation;
128
+ if (updates.owner) risk.owner = updates.owner;
129
+ if (updates.likelihood && RISK_LIKELIHOODS.includes(updates.likelihood)) {
130
+ risk.likelihood = updates.likelihood;
131
+ risk.score = RISK_SCORE_MATRIX[risk.likelihood][risk.impact];
132
+ }
133
+ if (updates.impact && RISK_IMPACTS.includes(updates.impact)) {
134
+ risk.impact = updates.impact;
135
+ risk.score = RISK_SCORE_MATRIX[risk.likelihood][risk.impact];
136
+ }
137
+
138
+ risk.updated_at = new Date().toISOString();
139
+ saveState(state, stateFile);
140
+
141
+ return { success: true, risk };
142
+ }
143
+
144
+ /**
145
+ * List risks with optional filter.
146
+ *
147
+ * @param {object} [filter]
148
+ * @param {object} [options]
149
+ * @returns {object}
150
+ */
151
+ function listRisks(filter = {}, options = {}) {
152
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
153
+ const state = loadState(stateFile);
154
+ let risks = state.risks;
155
+
156
+ if (filter.category) risks = risks.filter(r => r.category === filter.category);
157
+ if (filter.status) risks = risks.filter(r => r.status === filter.status);
158
+ if (filter.minScore) risks = risks.filter(r => r.score >= filter.minScore);
159
+
160
+ return { success: true, risks, total: risks.length };
161
+ }
162
+
163
+ /**
164
+ * Generate risk report.
165
+ *
166
+ * @param {object} [options]
167
+ * @returns {object}
168
+ */
169
+ function generateReport(options = {}) {
170
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
171
+ const state = loadState(stateFile);
172
+
173
+ const byCategory = {};
174
+ const byStatus = {};
175
+ let totalScore = 0;
176
+
177
+ for (const risk of state.risks) {
178
+ byCategory[risk.category] = (byCategory[risk.category] || 0) + 1;
179
+ byStatus[risk.status] = (byStatus[risk.status] || 0) + 1;
180
+ totalScore += risk.score;
181
+ }
182
+
183
+ const highRisks = state.risks.filter(r => r.score >= 15);
184
+ const unmitigated = state.risks.filter(r => !r.mitigation && r.status === 'identified');
185
+
186
+ return {
187
+ success: true,
188
+ total_risks: state.risks.length,
189
+ by_category: byCategory,
190
+ by_status: byStatus,
191
+ average_score: state.risks.length > 0 ? Math.round(totalScore / state.risks.length) : 0,
192
+ high_risks: highRisks.length,
193
+ unmitigated: unmitigated.length,
194
+ top_risks: state.risks.sort((a, b) => b.score - a.score).slice(0, 5)
195
+ };
196
+ }
197
+
198
+ module.exports = {
199
+ defaultState,
200
+ loadState,
201
+ saveState,
202
+ addRisk,
203
+ updateRisk,
204
+ listRisks,
205
+ generateReport,
206
+ RISK_CATEGORIES,
207
+ RISK_LIKELIHOODS,
208
+ RISK_IMPACTS,
209
+ RISK_STATUSES,
210
+ RISK_SCORE_MATRIX
211
+ };
@@ -0,0 +1,249 @@
1
+ /**
2
+ * role-approval.js — Human Approval Workflows with Roles
3
+ *
4
+ * Supports named approvers by role: product, architect, security, legal,
5
+ * platform owner. Tracks multi-role approval chains for artifacts.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/role-approval.js assign|approve|status [options]
9
+ *
10
+ * State file: .jumpstart/state/role-approvals.json
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_STATE_FILE = path.join('.jumpstart', 'state', 'role-approvals.json');
19
+
20
+ const APPROVER_ROLES = ['product', 'architect', 'security', 'legal', 'platform', 'qa', 'custom'];
21
+
22
+ /**
23
+ * Default role approval store.
24
+ * @returns {object}
25
+ */
26
+ function defaultRoleApprovalStore() {
27
+ return {
28
+ version: '1.0.0',
29
+ created_at: new Date().toISOString(),
30
+ last_updated: null,
31
+ workflows: {} // artifact_path → workflow
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Load the role approval store from disk.
37
+ * @param {string} [stateFile]
38
+ * @returns {object}
39
+ */
40
+ function loadRoleApprovalStore(stateFile) {
41
+ const filePath = stateFile || DEFAULT_STATE_FILE;
42
+ if (!fs.existsSync(filePath)) {
43
+ return defaultRoleApprovalStore();
44
+ }
45
+ try {
46
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
47
+ } catch {
48
+ return defaultRoleApprovalStore();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Save the role approval store to disk.
54
+ * @param {object} store
55
+ * @param {string} [stateFile]
56
+ */
57
+ function saveRoleApprovalStore(store, stateFile) {
58
+ const filePath = stateFile || DEFAULT_STATE_FILE;
59
+ const dir = path.dirname(filePath);
60
+ if (!fs.existsSync(dir)) {
61
+ fs.mkdirSync(dir, { recursive: true });
62
+ }
63
+ store.last_updated = new Date().toISOString();
64
+ fs.writeFileSync(filePath, JSON.stringify(store, null, 2) + '\n', 'utf8');
65
+ }
66
+
67
+ /**
68
+ * Assign approvers to an artifact.
69
+ *
70
+ * @param {string} artifactPath - Relative path to the artifact.
71
+ * @param {object[]} approvers - Array of { role, name, required? }.
72
+ * @param {object} [options]
73
+ * @returns {object}
74
+ */
75
+ function assignApprovers(artifactPath, approvers, options = {}) {
76
+ if (!artifactPath) {
77
+ return { success: false, error: 'artifactPath is required' };
78
+ }
79
+ if (!Array.isArray(approvers) || approvers.length === 0) {
80
+ return { success: false, error: 'approvers array is required and must not be empty' };
81
+ }
82
+
83
+ for (const a of approvers) {
84
+ const role = (a.role || '').toLowerCase();
85
+ if (!APPROVER_ROLES.includes(role)) {
86
+ return { success: false, error: `Invalid role "${a.role}". Must be one of: ${APPROVER_ROLES.join(', ')}` };
87
+ }
88
+ }
89
+
90
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
91
+ const store = loadRoleApprovalStore(stateFile);
92
+
93
+ const workflow = {
94
+ artifact: artifactPath,
95
+ created_at: new Date().toISOString(),
96
+ last_updated: new Date().toISOString(),
97
+ status: 'pending',
98
+ approvers: approvers.map(a => ({
99
+ role: a.role.toLowerCase(),
100
+ name: a.name || null,
101
+ required: a.required !== false,
102
+ status: 'pending', // pending | approved | rejected | skipped
103
+ approved_at: null,
104
+ comment: null
105
+ }))
106
+ };
107
+
108
+ store.workflows[artifactPath] = workflow;
109
+ saveRoleApprovalStore(store, stateFile);
110
+
111
+ return {
112
+ success: true,
113
+ artifact: artifactPath,
114
+ approvers: workflow.approvers,
115
+ total_required: workflow.approvers.filter(a => a.required).length
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Record an approval (or rejection) by a specific role.
121
+ *
122
+ * @param {string} artifactPath
123
+ * @param {string} role
124
+ * @param {string} action - 'approve' | 'reject'
125
+ * @param {object} [options] - { approverName?, comment?, stateFile? }
126
+ * @returns {object}
127
+ */
128
+ function recordRoleAction(artifactPath, role, action, options = {}) {
129
+ if (!artifactPath || !role || !action) {
130
+ return { success: false, error: 'artifactPath, role, and action are required' };
131
+ }
132
+
133
+ if (!['approve', 'reject'].includes(action)) {
134
+ return { success: false, error: 'action must be "approve" or "reject"' };
135
+ }
136
+
137
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
138
+ const store = loadRoleApprovalStore(stateFile);
139
+
140
+ const workflow = store.workflows[artifactPath];
141
+ if (!workflow) {
142
+ return { success: false, error: `No approval workflow found for: ${artifactPath}` };
143
+ }
144
+
145
+ const normalizedRole = role.toLowerCase();
146
+ const approver = workflow.approvers.find(a => a.role === normalizedRole);
147
+ if (!approver) {
148
+ return { success: false, error: `Role "${role}" not assigned to this artifact` };
149
+ }
150
+
151
+ approver.status = action === 'approve' ? 'approved' : 'rejected';
152
+ approver.approved_at = new Date().toISOString();
153
+ if (options.approverName) approver.name = options.approverName;
154
+ if (options.comment) approver.comment = options.comment;
155
+ workflow.last_updated = new Date().toISOString();
156
+
157
+ // Update overall workflow status
158
+ const required = workflow.approvers.filter(a => a.required);
159
+ const allApproved = required.every(a => a.status === 'approved');
160
+ const anyRejected = required.some(a => a.status === 'rejected');
161
+
162
+ if (anyRejected) {
163
+ workflow.status = 'rejected';
164
+ } else if (allApproved) {
165
+ workflow.status = 'approved';
166
+ } else {
167
+ workflow.status = 'pending';
168
+ }
169
+
170
+ saveRoleApprovalStore(store, stateFile);
171
+
172
+ return {
173
+ success: true,
174
+ artifact: artifactPath,
175
+ role: normalizedRole,
176
+ action,
177
+ workflow_status: workflow.status,
178
+ pending_roles: workflow.approvers.filter(a => a.required && a.status === 'pending').map(a => a.role)
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Get the approval status of an artifact.
184
+ *
185
+ * @param {string} artifactPath
186
+ * @param {object} [options]
187
+ * @returns {object}
188
+ */
189
+ function getApprovalStatus(artifactPath, options = {}) {
190
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
191
+ const store = loadRoleApprovalStore(stateFile);
192
+
193
+ const workflow = store.workflows[artifactPath];
194
+ if (!workflow) {
195
+ return {
196
+ success: true,
197
+ artifact: artifactPath,
198
+ has_workflow: false,
199
+ message: 'No approval workflow assigned'
200
+ };
201
+ }
202
+
203
+ const pending = workflow.approvers.filter(a => a.required && a.status === 'pending').map(a => a.role);
204
+ const approved = workflow.approvers.filter(a => a.status === 'approved').map(a => a.role);
205
+ const rejected = workflow.approvers.filter(a => a.status === 'rejected').map(a => a.role);
206
+
207
+ return {
208
+ success: true,
209
+ artifact: artifactPath,
210
+ has_workflow: true,
211
+ status: workflow.status,
212
+ pending_roles: pending,
213
+ approved_roles: approved,
214
+ rejected_roles: rejected,
215
+ approvers: workflow.approvers,
216
+ fully_approved: workflow.status === 'approved'
217
+ };
218
+ }
219
+
220
+ /**
221
+ * List all approval workflows.
222
+ *
223
+ * @param {object} [filter] - { status? }
224
+ * @param {object} [options]
225
+ * @returns {object}
226
+ */
227
+ function listApprovalWorkflows(filter = {}, options = {}) {
228
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
229
+ const store = loadRoleApprovalStore(stateFile);
230
+
231
+ let workflows = Object.values(store.workflows);
232
+
233
+ if (filter.status) {
234
+ workflows = workflows.filter(w => w.status === filter.status);
235
+ }
236
+
237
+ return { success: true, workflows, total: workflows.length };
238
+ }
239
+
240
+ module.exports = {
241
+ APPROVER_ROLES,
242
+ loadRoleApprovalStore,
243
+ saveRoleApprovalStore,
244
+ defaultRoleApprovalStore,
245
+ assignApprovers,
246
+ recordRoleAction,
247
+ getApprovalStatus,
248
+ listApprovalWorkflows
249
+ };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * role-views.js — Role-Based Project Views (Item 62)
3
+ *
4
+ * Same project data, different role-based lenses for
5
+ * executive, architect, product, and engineer audiences.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/role-views.js generate|list [options]
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const ROLES = ['executive', 'architect', 'product', 'engineer'];
17
+
18
+ const ROLE_FOCUS = {
19
+ executive: {
20
+ label: 'Executive View',
21
+ focus: ['timeline', 'risks', 'budget', 'milestones', 'decisions'],
22
+ exclude: ['code_details', 'test_coverage', 'api_contracts']
23
+ },
24
+ architect: {
25
+ label: 'Architect View',
26
+ focus: ['components', 'data_model', 'api_contracts', 'decisions', 'tech_stack', 'nfrs'],
27
+ exclude: ['budget', 'stakeholder_comms']
28
+ },
29
+ product: {
30
+ label: 'Product View',
31
+ focus: ['stories', 'acceptance_criteria', 'personas', 'journeys', 'scope', 'priorities'],
32
+ exclude: ['api_contracts', 'data_model', 'test_coverage']
33
+ },
34
+ engineer: {
35
+ label: 'Engineer View',
36
+ focus: ['tasks', 'api_contracts', 'data_model', 'test_coverage', 'tech_stack', 'code_details'],
37
+ exclude: ['budget', 'stakeholder_comms', 'personas']
38
+ }
39
+ };
40
+
41
+ /**
42
+ * Generate a role-specific view of the project.
43
+ */
44
+ function generateView(root, role, options = {}) {
45
+ if (!ROLES.includes(role)) {
46
+ return { success: false, error: `Unknown role: ${role}. Valid roles: ${ROLES.join(', ')}` };
47
+ }
48
+
49
+ const config = ROLE_FOCUS[role];
50
+ const view = {
51
+ role,
52
+ label: config.label,
53
+ generated_at: new Date().toISOString(),
54
+ focus_areas: config.focus,
55
+ excluded_areas: config.exclude,
56
+ sections: {}
57
+ };
58
+
59
+ // Gather available specs
60
+ const specsDir = path.join(root, 'specs');
61
+ const availableSpecs = [];
62
+ if (fs.existsSync(specsDir)) {
63
+ for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.md'))) {
64
+ availableSpecs.push(f);
65
+ }
66
+ }
67
+ view.sections.available_specs = availableSpecs;
68
+
69
+ // Phase status
70
+ const stateFile = path.join(root, '.jumpstart', 'state', 'state.json');
71
+ if (fs.existsSync(stateFile)) {
72
+ try {
73
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
74
+ view.sections.phase_status = {
75
+ current_phase: state.current_phase || 0,
76
+ current_agent: state.current_agent || null
77
+ };
78
+ } catch { view.sections.phase_status = { current_phase: 0 }; }
79
+ } else {
80
+ view.sections.phase_status = { current_phase: 0 };
81
+ }
82
+
83
+ // Risk summary (if relevant to role)
84
+ if (config.focus.includes('risks')) {
85
+ const riskFile = path.join(root, '.jumpstart', 'state', 'risk-register.json');
86
+ if (fs.existsSync(riskFile)) {
87
+ try {
88
+ const risks = JSON.parse(fs.readFileSync(riskFile, 'utf8'));
89
+ view.sections.risks = {
90
+ total: (risks.risks || []).length,
91
+ high: (risks.risks || []).filter(r => r.score >= 15).length
92
+ };
93
+ } catch { view.sections.risks = { total: 0, high: 0 }; }
94
+ } else {
95
+ view.sections.risks = { total: 0, high: 0 };
96
+ }
97
+ }
98
+
99
+ return { success: true, view };
100
+ }
101
+
102
+ /**
103
+ * List all available roles and their focus areas.
104
+ */
105
+ function listRoles() {
106
+ return {
107
+ success: true,
108
+ roles: ROLES.map(r => ({
109
+ id: r,
110
+ label: ROLE_FOCUS[r].label,
111
+ focus: ROLE_FOCUS[r].focus,
112
+ exclude: ROLE_FOCUS[r].exclude
113
+ }))
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Generate summary for a specific role.
119
+ */
120
+ function generateRoleSummary(root, role) {
121
+ const viewResult = generateView(root, role);
122
+ if (!viewResult.success) return viewResult;
123
+
124
+ const view = viewResult.view;
125
+ const summary = {
126
+ role,
127
+ label: view.label,
128
+ current_phase: view.sections.phase_status ? view.sections.phase_status.current_phase : 0,
129
+ specs_count: view.sections.available_specs ? view.sections.available_specs.length : 0,
130
+ generated_at: view.generated_at
131
+ };
132
+
133
+ return { success: true, summary };
134
+ }
135
+
136
+ module.exports = {
137
+ generateView,
138
+ listRoles,
139
+ generateRoleSummary,
140
+ ROLES,
141
+ ROLE_FOCUS
142
+ };