jumpstart-mode 1.1.11 → 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 (188) hide show
  1. package/.github/agents/jumpstart-adversary.agent.md +2 -1
  2. package/.github/agents/jumpstart-architect.agent.md +6 -7
  3. package/.github/agents/jumpstart-challenger.agent.md +2 -1
  4. package/.github/agents/jumpstart-developer.agent.md +1 -1
  5. package/.github/agents/jumpstart-devops.agent.md +2 -2
  6. package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
  7. package/.github/agents/jumpstart-maintenance.agent.md +1 -0
  8. package/.github/agents/jumpstart-performance.agent.md +1 -0
  9. package/.github/agents/jumpstart-pm.agent.md +1 -1
  10. package/.github/agents/jumpstart-refactor.agent.md +1 -0
  11. package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
  12. package/.github/agents/jumpstart-researcher.agent.md +1 -0
  13. package/.github/agents/jumpstart-retrospective.agent.md +1 -0
  14. package/.github/agents/jumpstart-reviewer.agent.md +2 -0
  15. package/.github/agents/jumpstart-scout.agent.md +1 -1
  16. package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
  17. package/.github/agents/jumpstart-security.agent.md +2 -1
  18. package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
  19. package/.github/agents/jumpstart-uiux-designer.agent.md +66 -0
  20. package/.github/workflows/quality.yml +19 -2
  21. package/.jumpstart/agents/analyst.md +38 -0
  22. package/.jumpstart/agents/architect.md +39 -1
  23. package/.jumpstart/agents/challenger.md +38 -0
  24. package/.jumpstart/agents/developer.md +41 -0
  25. package/.jumpstart/agents/pm.md +38 -0
  26. package/.jumpstart/agents/scout.md +33 -0
  27. package/.jumpstart/agents/ux-designer.md +29 -9
  28. package/.jumpstart/commands/commands.md +6 -5
  29. package/.jumpstart/config.yaml +25 -1
  30. package/.jumpstart/roadmap.md +1 -1
  31. package/.jumpstart/schemas/timeline.schema.json +1 -0
  32. package/.jumpstart/skills/README.md +1 -0
  33. package/.jumpstart/skills/quality-gates/SKILL.md +126 -0
  34. package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
  35. package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
  36. package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
  37. package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
  38. package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
  39. package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  40. package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  41. package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
  42. package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
  43. package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  44. package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
  45. package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
  46. package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
  47. package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
  48. package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
  49. package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
  50. package/.jumpstart/skills/ui-ux-pro-max/SKILL.md +266 -0
  51. package/.jumpstart/skills/ui-ux-pro-max/data/charts.csv +26 -0
  52. package/.jumpstart/skills/ui-ux-pro-max/data/colors.csv +97 -0
  53. package/.jumpstart/skills/ui-ux-pro-max/data/icons.csv +101 -0
  54. package/.jumpstart/skills/ui-ux-pro-max/data/landing.csv +31 -0
  55. package/.jumpstart/skills/ui-ux-pro-max/data/products.csv +97 -0
  56. package/.jumpstart/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  57. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  58. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  59. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  60. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  61. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  62. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  63. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  64. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  65. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  66. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  67. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  68. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  69. package/.jumpstart/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  70. package/.jumpstart/skills/ui-ux-pro-max/data/styles.csv +68 -0
  71. package/.jumpstart/skills/ui-ux-pro-max/data/typography.csv +58 -0
  72. package/.jumpstart/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  73. package/.jumpstart/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  74. package/.jumpstart/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  75. package/.jumpstart/skills/ui-ux-pro-max/scripts/core.py +253 -0
  76. package/.jumpstart/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  77. package/.jumpstart/skills/ui-ux-pro-max/scripts/search.py +114 -0
  78. package/.jumpstart/state/timeline.json +659 -0
  79. package/.jumpstart/templates/model-map.md +1 -1
  80. package/.jumpstart/templates/ux-design.md +3 -3
  81. package/.jumpstart/usage-log.json +74 -3
  82. package/AGENTS.md +1 -1
  83. package/README.md +64 -3
  84. package/bin/cli.js +3217 -1
  85. package/bin/headless-runner.js +62 -2
  86. package/bin/lib/agent-checkpoint.js +168 -0
  87. package/bin/lib/ai-evaluation.js +104 -0
  88. package/bin/lib/ai-intake.js +152 -0
  89. package/bin/lib/ambiguity-heatmap.js +152 -0
  90. package/bin/lib/artifact-comparison.js +104 -0
  91. package/bin/lib/ast-edit-engine.js +157 -0
  92. package/bin/lib/backlog-sync.js +338 -0
  93. package/bin/lib/bcdr-planning.js +158 -0
  94. package/bin/lib/bidirectional-trace.js +199 -0
  95. package/bin/lib/branch-workflow.js +266 -0
  96. package/bin/lib/cab-output.js +119 -0
  97. package/bin/lib/chat-integration.js +122 -0
  98. package/bin/lib/ci-cd-integration.js +208 -0
  99. package/bin/lib/codebase-retrieval.js +125 -0
  100. package/bin/lib/collaboration.js +168 -0
  101. package/bin/lib/compliance-packs.js +213 -0
  102. package/bin/lib/context-chunker.js +128 -0
  103. package/bin/lib/context-onboarding.js +122 -0
  104. package/bin/lib/contract-first.js +124 -0
  105. package/bin/lib/cost-router.js +148 -0
  106. package/bin/lib/credential-boundary.js +155 -0
  107. package/bin/lib/data-classification.js +180 -0
  108. package/bin/lib/data-contracts.js +129 -0
  109. package/bin/lib/db-evolution.js +158 -0
  110. package/bin/lib/decision-conflicts.js +299 -0
  111. package/bin/lib/delivery-confidence.js +361 -0
  112. package/bin/lib/dependency-upgrade.js +153 -0
  113. package/bin/lib/design-system.js +133 -0
  114. package/bin/lib/deterministic-artifacts.js +151 -0
  115. package/bin/lib/diagram-studio.js +115 -0
  116. package/bin/lib/domain-ontology.js +140 -0
  117. package/bin/lib/ea-review-packet.js +151 -0
  118. package/bin/lib/enterprise-search.js +123 -0
  119. package/bin/lib/enterprise-templates.js +140 -0
  120. package/bin/lib/environment-promotion.js +220 -0
  121. package/bin/lib/estimation-studio.js +130 -0
  122. package/bin/lib/event-modeling.js +133 -0
  123. package/bin/lib/evidence-collector.js +179 -0
  124. package/bin/lib/finops-planner.js +182 -0
  125. package/bin/lib/fitness-functions.js +279 -0
  126. package/bin/lib/focus.js +448 -0
  127. package/bin/lib/governance-dashboard.js +165 -0
  128. package/bin/lib/guided-handoff.js +120 -0
  129. package/bin/lib/impact-analysis.js +190 -0
  130. package/bin/lib/incident-feedback.js +157 -0
  131. package/bin/lib/integrate.js +1 -1
  132. package/bin/lib/knowledge-graph.js +122 -0
  133. package/bin/lib/legacy-modernizer.js +160 -0
  134. package/bin/lib/migration-planner.js +144 -0
  135. package/bin/lib/model-governance.js +185 -0
  136. package/bin/lib/model-router.js +144 -0
  137. package/bin/lib/multi-repo.js +272 -0
  138. package/bin/lib/next-phase.js +53 -8
  139. package/bin/lib/ops-ownership.js +152 -0
  140. package/bin/lib/parallel-agents.js +257 -0
  141. package/bin/lib/pattern-library.js +115 -0
  142. package/bin/lib/persona-packs.js +99 -0
  143. package/bin/lib/plan-executor.js +366 -0
  144. package/bin/lib/platform-engineering.js +119 -0
  145. package/bin/lib/playback-summaries.js +126 -0
  146. package/bin/lib/policy-engine.js +240 -0
  147. package/bin/lib/portfolio-reporting.js +357 -0
  148. package/bin/lib/pr-package.js +197 -0
  149. package/bin/lib/project-memory.js +235 -0
  150. package/bin/lib/prompt-governance.js +130 -0
  151. package/bin/lib/promptless-mode.js +128 -0
  152. package/bin/lib/quality-graph.js +193 -0
  153. package/bin/lib/raci-matrix.js +188 -0
  154. package/bin/lib/refactor-planner.js +167 -0
  155. package/bin/lib/reference-architectures.js +304 -0
  156. package/bin/lib/release-readiness.js +171 -0
  157. package/bin/lib/repo-graph.js +262 -0
  158. package/bin/lib/requirements-baseline.js +358 -0
  159. package/bin/lib/risk-register.js +211 -0
  160. package/bin/lib/role-approval.js +249 -0
  161. package/bin/lib/role-views.js +142 -0
  162. package/bin/lib/root-cause-analysis.js +132 -0
  163. package/bin/lib/runtime-debugger.js +154 -0
  164. package/bin/lib/safe-rename.js +135 -0
  165. package/bin/lib/secret-scanner.js +313 -0
  166. package/bin/lib/semantic-diff.js +335 -0
  167. package/bin/lib/sla-slo.js +210 -0
  168. package/bin/lib/smoke-tester.js +344 -0
  169. package/bin/lib/spec-comments.js +147 -0
  170. package/bin/lib/spec-maturity.js +287 -0
  171. package/bin/lib/sre-integration.js +154 -0
  172. package/bin/lib/structured-elicitation.js +174 -0
  173. package/bin/lib/telemetry-feedback.js +118 -0
  174. package/bin/lib/test-generator.js +146 -0
  175. package/bin/lib/timeline.js +2 -1
  176. package/bin/lib/tool-bridge.js +159 -0
  177. package/bin/lib/tool-guardrails.js +139 -0
  178. package/bin/lib/tool-schemas.js +281 -3
  179. package/bin/lib/transcript-ingestion.js +150 -0
  180. package/bin/lib/type-checker.js +261 -0
  181. package/bin/lib/uat-coverage.js +411 -0
  182. package/bin/lib/vendor-risk.js +173 -0
  183. package/bin/lib/waiver-workflow.js +174 -0
  184. package/bin/lib/web-dashboard.js +126 -0
  185. package/bin/lib/workshop-mode.js +165 -0
  186. package/bin/lib/workstream-ownership.js +104 -0
  187. package/package.json +1 -1
  188. package/.github/agents/jumpstart-ux-designer.agent.md +0 -45
@@ -0,0 +1,448 @@
1
+ /**
2
+ * focus.js — Phase Focus Mode
3
+ *
4
+ * Allows users to focus on particular phases of the Jump Start workflow
5
+ * instead of running the full sequential pipeline. Useful for role-based
6
+ * workflows (e.g., Business Analysts focusing on Analyst + PM phases)
7
+ * or converting existing artifacts (e.g., importing an existing PRD).
8
+ *
9
+ * Usage:
10
+ * echo '{"action":"list"}' | node bin/lib/focus.js
11
+ * echo '{"action":"set","preset":"business-analyst"}' | node bin/lib/focus.js
12
+ * echo '{"action":"set","start_phase":1,"end_phase":2}' | node bin/lib/focus.js
13
+ * echo '{"action":"clear"}' | node bin/lib/focus.js
14
+ * echo '{"action":"status"}' | node bin/lib/focus.js
15
+ *
16
+ * Output (stdout JSON):
17
+ * { "ok": true, ... }
18
+ */
19
+
20
+ import { createRequire } from 'module';
21
+ const require = createRequire(import.meta.url);
22
+ const { readFileSync, writeFileSync, existsSync } = require('fs');
23
+ const { join } = require('path');
24
+
25
+ /**
26
+ * Phase names for display.
27
+ */
28
+ const PHASE_NAMES = {
29
+ '-1': 'Scout',
30
+ '0': 'Challenger',
31
+ '1': 'Analyst',
32
+ '2': 'PM',
33
+ '3': 'Architect',
34
+ '4': 'Developer'
35
+ };
36
+
37
+ /**
38
+ * Phase-to-slash-command map.
39
+ */
40
+ const AGENT_COMMANDS = {
41
+ '-1': '/jumpstart.scout',
42
+ '0': '/jumpstart.challenge',
43
+ '1': '/jumpstart.analyze',
44
+ '2': '/jumpstart.plan',
45
+ '3': '/jumpstart.architect',
46
+ '4': '/jumpstart.build'
47
+ };
48
+
49
+ /**
50
+ * Predefined focus presets for common role-based workflows.
51
+ *
52
+ * Each preset defines a start phase, end phase, description, and
53
+ * the typical role that would use it.
54
+ */
55
+ const PRESETS = {
56
+ full: {
57
+ description: 'Full workflow — all phases from Challenger through Developer. This is the default.',
58
+ start_phase: 0,
59
+ end_phase: 4,
60
+ role: 'Full Team',
61
+ phases: ['Challenger', 'Analyst', 'PM', 'Architect', 'Developer']
62
+ },
63
+ 'business-analyst': {
64
+ description: 'Business Analyst focus — challenge assumptions, define personas and user journeys, then write the PRD with user stories and acceptance criteria.',
65
+ start_phase: 0,
66
+ end_phase: 2,
67
+ role: 'Business Analyst',
68
+ phases: ['Challenger', 'Analyst', 'PM']
69
+ },
70
+ 'prd-ready': {
71
+ description: 'PRD conversion — focus only on the PM phase to convert an existing PRD into a JumpStart-ready format with structured user stories, acceptance criteria, and NFRs.',
72
+ start_phase: 2,
73
+ end_phase: 2,
74
+ role: 'Product Manager',
75
+ phases: ['PM']
76
+ },
77
+ discovery: {
78
+ description: 'Discovery focus — challenge assumptions and analyze the problem space without committing to a PRD or architecture.',
79
+ start_phase: 0,
80
+ end_phase: 1,
81
+ role: 'Product / Strategy',
82
+ phases: ['Challenger', 'Analyst']
83
+ },
84
+ 'technical-lead': {
85
+ description: 'Technical Lead focus — design architecture and plan implementation tasks from an existing PRD. Assumes PRD is already available.',
86
+ start_phase: 3,
87
+ end_phase: 3,
88
+ role: 'Technical Lead / Architect',
89
+ phases: ['Architect']
90
+ },
91
+ 'developer-only': {
92
+ description: 'Developer focus — build from existing specs. Assumes architecture and implementation plan are already available.',
93
+ start_phase: 4,
94
+ end_phase: 4,
95
+ role: 'Developer',
96
+ phases: ['Developer']
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Valid preset names.
102
+ */
103
+ export const VALID_PRESETS = Object.keys(PRESETS);
104
+
105
+ /**
106
+ * Valid phase numbers.
107
+ */
108
+ const VALID_PHASES = [-1, 0, 1, 2, 3, 4];
109
+
110
+ /**
111
+ * Get details for a specific preset.
112
+ *
113
+ * @param {string} presetName - Name of the preset.
114
+ * @returns {object} Preset definition.
115
+ * @throws {Error} If preset is invalid.
116
+ */
117
+ export function getPreset(presetName) {
118
+ const preset = PRESETS[presetName];
119
+ if (!preset) {
120
+ throw new Error(
121
+ `Unknown focus preset: "${presetName}". Valid presets: ${VALID_PRESETS.join(', ')}`
122
+ );
123
+ }
124
+ return { name: presetName, ...preset };
125
+ }
126
+
127
+ /**
128
+ * List all available presets with descriptions.
129
+ *
130
+ * @returns {object[]} Array of preset summaries.
131
+ */
132
+ export function listPresets() {
133
+ return VALID_PRESETS.map(name => ({
134
+ name,
135
+ description: PRESETS[name].description,
136
+ start_phase: PRESETS[name].start_phase,
137
+ end_phase: PRESETS[name].end_phase,
138
+ role: PRESETS[name].role,
139
+ phases: PRESETS[name].phases
140
+ }));
141
+ }
142
+
143
+ /**
144
+ * Validate a phase range.
145
+ *
146
+ * @param {number} startPhase - Start phase number.
147
+ * @param {number} endPhase - End phase number.
148
+ * @returns {{ valid: boolean, error?: string }}
149
+ */
150
+ export function validatePhaseRange(startPhase, endPhase) {
151
+ if (!VALID_PHASES.includes(startPhase)) {
152
+ return { valid: false, error: `Invalid start phase: ${startPhase}. Valid phases: ${VALID_PHASES.join(', ')}` };
153
+ }
154
+ if (!VALID_PHASES.includes(endPhase)) {
155
+ return { valid: false, error: `Invalid end phase: ${endPhase}. Valid phases: ${VALID_PHASES.join(', ')}` };
156
+ }
157
+ if (startPhase > endPhase) {
158
+ return { valid: false, error: `Start phase (${startPhase}) cannot be after end phase (${endPhase})` };
159
+ }
160
+ return { valid: true };
161
+ }
162
+
163
+ /**
164
+ * Check whether a given phase is within the active focus range.
165
+ *
166
+ * @param {number} phase - Phase number to check.
167
+ * @param {object} focusConfig - Focus configuration with start_phase and end_phase.
168
+ * @returns {boolean} True if the phase is within focus range.
169
+ */
170
+ export function isPhaseInFocus(phase, focusConfig) {
171
+ if (!focusConfig || !focusConfig.enabled) {
172
+ return true; // No focus restriction — all phases are in range
173
+ }
174
+ const start = focusConfig.start_phase;
175
+ const end = focusConfig.end_phase;
176
+ if (start === undefined || start === null || end === undefined || end === null) {
177
+ return true;
178
+ }
179
+ return phase >= start && phase <= end;
180
+ }
181
+
182
+ /**
183
+ * Get the phases included in a focus range.
184
+ *
185
+ * @param {number} startPhase - Start phase.
186
+ * @param {number} endPhase - End phase.
187
+ * @returns {object[]} Array of { phase, name, command }.
188
+ */
189
+ export function getPhasesInRange(startPhase, endPhase) {
190
+ const phases = [];
191
+ for (const p of VALID_PHASES) {
192
+ if (p >= startPhase && p <= endPhase) {
193
+ phases.push({
194
+ phase: p,
195
+ name: PHASE_NAMES[String(p)],
196
+ command: AGENT_COMMANDS[String(p)]
197
+ });
198
+ }
199
+ }
200
+ return phases;
201
+ }
202
+
203
+ /**
204
+ * Build the focus configuration object from a preset or custom range.
205
+ *
206
+ * @param {object} options - Either { preset } or { start_phase, end_phase }.
207
+ * @returns {object} Focus configuration.
208
+ * @throws {Error} If preset is invalid or range is invalid.
209
+ */
210
+ export function buildFocusConfig(options) {
211
+ if (options.preset) {
212
+ const preset = getPreset(options.preset);
213
+ return {
214
+ enabled: options.preset !== 'full',
215
+ preset: options.preset,
216
+ start_phase: preset.start_phase,
217
+ end_phase: preset.end_phase,
218
+ description: preset.description,
219
+ role: preset.role,
220
+ phases: getPhasesInRange(preset.start_phase, preset.end_phase)
221
+ };
222
+ }
223
+
224
+ const start = options.start_phase;
225
+ const end = options.end_phase;
226
+ const validation = validatePhaseRange(start, end);
227
+ if (!validation.valid) {
228
+ throw new Error(validation.error);
229
+ }
230
+
231
+ const phases = getPhasesInRange(start, end);
232
+ return {
233
+ enabled: !(start === 0 && end === 4),
234
+ preset: null,
235
+ start_phase: start,
236
+ end_phase: end,
237
+ description: `Custom focus: Phase ${start} (${PHASE_NAMES[String(start)]}) through Phase ${end} (${PHASE_NAMES[String(end)]})`,
238
+ role: 'Custom',
239
+ phases
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Read focus configuration from config.yaml.
245
+ *
246
+ * @param {string} configPath - Path to config.yaml.
247
+ * @returns {object|null} Focus config or null if not set.
248
+ */
249
+ export function readFocusFromConfig(configPath) {
250
+ if (!existsSync(configPath)) return null;
251
+ try {
252
+ const content = readFileSync(configPath, 'utf8');
253
+ const lines = content.split('\n');
254
+
255
+ // Find the focus: section and extract its indented children
256
+ let inFocus = false;
257
+ const focusLines = [];
258
+ for (const line of lines) {
259
+ if (/^focus:\s*$/.test(line)) {
260
+ inFocus = true;
261
+ continue;
262
+ }
263
+ if (inFocus) {
264
+ // Stop at next top-level key or end of file
265
+ if (/^\S/.test(line) && line.trim() !== '') {
266
+ break;
267
+ }
268
+ focusLines.push(line);
269
+ }
270
+ }
271
+
272
+ if (focusLines.length === 0) return null;
273
+
274
+ const section = focusLines.join('\n');
275
+ const enabled = /^\s+enabled:\s*true/m.test(section);
276
+ if (!enabled) return null;
277
+
278
+ const presetMatch = section.match(/^\s+preset:\s*(\S+)/m);
279
+ const startMatch = section.match(/^\s+start_phase:\s*(-?\d+)/m);
280
+ const endMatch = section.match(/^\s+end_phase:\s*(-?\d+)/m);
281
+
282
+ const preset = presetMatch ? presetMatch[1] : null;
283
+ const startPhase = startMatch ? parseInt(startMatch[1], 10) : null;
284
+ const endPhase = endMatch ? parseInt(endMatch[1], 10) : null;
285
+
286
+ if (preset && preset !== 'null') {
287
+ try {
288
+ return buildFocusConfig({ preset });
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+
294
+ if (startPhase !== null && endPhase !== null) {
295
+ try {
296
+ return buildFocusConfig({ start_phase: startPhase, end_phase: endPhase });
297
+ } catch {
298
+ return null;
299
+ }
300
+ }
301
+
302
+ return null;
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Write focus configuration to config.yaml.
310
+ *
311
+ * Inserts or updates the focus: section in the config file.
312
+ *
313
+ * @param {string} configPath - Path to config.yaml.
314
+ * @param {object} focusConfig - Focus config from buildFocusConfig().
315
+ * @returns {{ success: boolean, error?: string }}
316
+ */
317
+ export function writeFocusToConfig(configPath, focusConfig) {
318
+ if (!existsSync(configPath)) {
319
+ return { success: false, error: 'Config file not found. Run jumpstart-mode init first.' };
320
+ }
321
+
322
+ let content = readFileSync(configPath, 'utf8');
323
+
324
+ const focusYaml = [
325
+ 'focus:',
326
+ ` enabled: ${focusConfig.enabled}`,
327
+ ` preset: ${focusConfig.preset || 'null'}`,
328
+ ` start_phase: ${focusConfig.start_phase}`,
329
+ ` end_phase: ${focusConfig.end_phase}`
330
+ ].join('\n');
331
+
332
+ // Check if focus section already exists
333
+ const focusPattern = /^focus:\s*\n(?:(?:[ \t]+\S.*|[ \t]*)?\n?)*/m;
334
+ if (focusPattern.test(content)) {
335
+ content = content.replace(focusPattern, focusYaml + '\n\n');
336
+ } else {
337
+ // Insert before the workflow: section (or at end)
338
+ const workflowIndex = content.indexOf('\nworkflow:');
339
+ if (workflowIndex !== -1) {
340
+ const insertComment = '\n# ---------------------------------------------------------------------------\n# Focus Mode — Restrict workflow to specific phases\n# ---------------------------------------------------------------------------\n';
341
+ content = content.slice(0, workflowIndex) + insertComment + focusYaml + '\n' + content.slice(workflowIndex);
342
+ } else {
343
+ content += '\n' + focusYaml + '\n';
344
+ }
345
+ }
346
+
347
+ writeFileSync(configPath, content, 'utf8');
348
+ return { success: true };
349
+ }
350
+
351
+ /**
352
+ * Clear focus configuration (reset to full workflow).
353
+ *
354
+ * @param {string} configPath - Path to config.yaml.
355
+ * @returns {{ success: boolean, error?: string }}
356
+ */
357
+ export function clearFocusFromConfig(configPath) {
358
+ return writeFocusToConfig(configPath, {
359
+ enabled: false,
360
+ preset: 'full',
361
+ start_phase: 0,
362
+ end_phase: 4
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Get current focus status for a project.
368
+ *
369
+ * @param {object} options - { root }
370
+ * @returns {object} Status object.
371
+ */
372
+ export function getFocusStatus(options = {}) {
373
+ const root = options.root || '.';
374
+ const configPath = join(root, '.jumpstart', 'config.yaml');
375
+
376
+ if (!existsSync(configPath)) {
377
+ return { active: false, message: 'Project not initialized.' };
378
+ }
379
+
380
+ const focusConfig = readFocusFromConfig(configPath);
381
+ if (!focusConfig || !focusConfig.enabled) {
382
+ return {
383
+ active: false,
384
+ message: 'No focus restriction — full workflow is active.',
385
+ preset: 'full',
386
+ phases: getPhasesInRange(0, 4)
387
+ };
388
+ }
389
+
390
+ return {
391
+ active: true,
392
+ preset: focusConfig.preset || 'custom',
393
+ start_phase: focusConfig.start_phase,
394
+ end_phase: focusConfig.end_phase,
395
+ description: focusConfig.description,
396
+ role: focusConfig.role,
397
+ phases: focusConfig.phases,
398
+ message: `Focus mode active: ${focusConfig.description}`
399
+ };
400
+ }
401
+
402
+ // ─── CLI Entry Point ──────────────────────────────────────────────────────────
403
+
404
+ if (process.argv[1] && (
405
+ process.argv[1].endsWith('focus.js') ||
406
+ process.argv[1].endsWith('focus')
407
+ )) {
408
+ let input = '';
409
+ process.stdin.setEncoding('utf8');
410
+ process.stdin.on('data', chunk => { input += chunk; });
411
+ process.stdin.on('end', () => {
412
+ try {
413
+ const parsed = input.trim() ? JSON.parse(input) : {};
414
+ const action = parsed.action || 'list';
415
+ let result;
416
+
417
+ if (action === 'list') {
418
+ result = { presets: listPresets() };
419
+ } else if (action === 'set') {
420
+ if (parsed.preset) {
421
+ result = buildFocusConfig({ preset: parsed.preset });
422
+ } else {
423
+ result = buildFocusConfig({ start_phase: parsed.start_phase, end_phase: parsed.end_phase });
424
+ }
425
+ } else if (action === 'clear') {
426
+ const root = parsed.root || '.';
427
+ const configPath = join(root, '.jumpstart', 'config.yaml');
428
+ result = clearFocusFromConfig(configPath);
429
+ } else if (action === 'status') {
430
+ result = getFocusStatus({ root: parsed.root || '.' });
431
+ } else {
432
+ process.stderr.write(JSON.stringify({ ok: false, error: `Unknown action: ${action}` }) + '\n');
433
+ process.exit(2);
434
+ return;
435
+ }
436
+
437
+ process.stdout.write(JSON.stringify({ ok: true, timestamp: new Date().toISOString(), ...result }, null, 2) + '\n');
438
+ } catch (err) {
439
+ process.stderr.write(JSON.stringify({ ok: false, error: err.message }) + '\n');
440
+ process.exit(2);
441
+ }
442
+ });
443
+
444
+ if (process.stdin.isTTY) {
445
+ const presets = listPresets();
446
+ process.stdout.write(JSON.stringify({ ok: true, timestamp: new Date().toISOString(), presets }, null, 2) + '\n');
447
+ }
448
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * governance-dashboard.js — Governance Dashboards for Leadership (Item 40)
3
+ *
4
+ * Show policy violations, open waivers, security findings,
5
+ * readiness trends, and delivery risk.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/governance-dashboard.js [options]
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ /**
17
+ * Gather governance dashboard data from project state files.
18
+ *
19
+ * @param {string} root - Project root.
20
+ * @param {object} [options]
21
+ * @returns {object}
22
+ */
23
+ function gatherGovernanceData(root, options = {}) {
24
+ const data = {
25
+ generated_at: new Date().toISOString(),
26
+ project_root: root,
27
+ sections: {}
28
+ };
29
+
30
+ // Policy violations
31
+ const policyFile = path.join(root, '.jumpstart', 'policies.json');
32
+ if (fs.existsSync(policyFile)) {
33
+ try {
34
+ const policies = JSON.parse(fs.readFileSync(policyFile, 'utf8'));
35
+ data.sections.policies = {
36
+ total: policies.policies ? policies.policies.length : 0,
37
+ enabled: (policies.policies || []).filter(p => p.enabled !== false).length
38
+ };
39
+ } catch { data.sections.policies = { total: 0, enabled: 0 }; }
40
+ } else {
41
+ data.sections.policies = { total: 0, enabled: 0 };
42
+ }
43
+
44
+ // Open waivers
45
+ const waiverFile = path.join(root, '.jumpstart', 'state', 'waivers.json');
46
+ if (fs.existsSync(waiverFile)) {
47
+ try {
48
+ const waivers = JSON.parse(fs.readFileSync(waiverFile, 'utf8'));
49
+ const all = waivers.waivers || [];
50
+ data.sections.waivers = {
51
+ total: all.length,
52
+ pending: all.filter(w => w.status === 'pending').length,
53
+ approved: all.filter(w => w.status === 'approved').length,
54
+ expired: all.filter(w => w.status === 'expired').length
55
+ };
56
+ } catch { data.sections.waivers = { total: 0, pending: 0, approved: 0, expired: 0 }; }
57
+ } else {
58
+ data.sections.waivers = { total: 0, pending: 0, approved: 0, expired: 0 };
59
+ }
60
+
61
+ // Security findings
62
+ data.sections.security = { findings: 0, critical: 0, high: 0 };
63
+
64
+ // Risk register
65
+ const riskFile = path.join(root, '.jumpstart', 'state', 'risk-register.json');
66
+ if (fs.existsSync(riskFile)) {
67
+ try {
68
+ const risks = JSON.parse(fs.readFileSync(riskFile, 'utf8'));
69
+ const all = risks.risks || [];
70
+ data.sections.risks = {
71
+ total: all.length,
72
+ high: all.filter(r => r.score >= 15).length,
73
+ unmitigated: all.filter(r => !r.mitigation && r.status === 'identified').length
74
+ };
75
+ } catch { data.sections.risks = { total: 0, high: 0, unmitigated: 0 }; }
76
+ } else {
77
+ data.sections.risks = { total: 0, high: 0, unmitigated: 0 };
78
+ }
79
+
80
+ // Compliance
81
+ const complianceFile = path.join(root, '.jumpstart', 'state', 'compliance.json');
82
+ if (fs.existsSync(complianceFile)) {
83
+ try {
84
+ const compliance = JSON.parse(fs.readFileSync(complianceFile, 'utf8'));
85
+ data.sections.compliance = {
86
+ frameworks: (compliance.applied_frameworks || []).length,
87
+ frameworks_list: compliance.applied_frameworks || []
88
+ };
89
+ } catch { data.sections.compliance = { frameworks: 0, frameworks_list: [] }; }
90
+ } else {
91
+ data.sections.compliance = { frameworks: 0, frameworks_list: [] };
92
+ }
93
+
94
+ // Release readiness
95
+ const readinessFile = path.join(root, '.jumpstart', 'state', 'release-readiness.json');
96
+ if (fs.existsSync(readinessFile)) {
97
+ try {
98
+ const readiness = JSON.parse(fs.readFileSync(readinessFile, 'utf8'));
99
+ if (readiness.current_readiness) {
100
+ data.sections.readiness = {
101
+ score: readiness.current_readiness.total_score,
102
+ level: readiness.current_readiness.level,
103
+ recommendation: readiness.current_readiness.recommendation
104
+ };
105
+ } else {
106
+ data.sections.readiness = { score: null, level: 'Not assessed' };
107
+ }
108
+ } catch { data.sections.readiness = { score: null, level: 'Error' }; }
109
+ } else {
110
+ data.sections.readiness = { score: null, level: 'Not assessed' };
111
+ }
112
+
113
+ // Environment promotion
114
+ const envFile = path.join(root, '.jumpstart', 'state', 'environment-promotion.json');
115
+ if (fs.existsSync(envFile)) {
116
+ try {
117
+ const env = JSON.parse(fs.readFileSync(envFile, 'utf8'));
118
+ data.sections.environment = {
119
+ current: env.current_environment,
120
+ promotions: (env.promotion_history || []).length
121
+ };
122
+ } catch { data.sections.environment = { current: 'unknown' }; }
123
+ } else {
124
+ data.sections.environment = { current: 'unknown' };
125
+ }
126
+
127
+ // Calculate overall governance score
128
+ let scoreItems = 0;
129
+ let scoreTotal = 0;
130
+
131
+ if (data.sections.policies.total > 0) { scoreItems++; scoreTotal += 80; }
132
+ if (data.sections.compliance.frameworks > 0) { scoreItems++; scoreTotal += 80; }
133
+ if (data.sections.risks.total > 0) { scoreItems++; scoreTotal += data.sections.risks.unmitigated === 0 ? 90 : 50; }
134
+ if (data.sections.readiness.score !== null) { scoreItems++; scoreTotal += data.sections.readiness.score; }
135
+
136
+ data.governance_score = scoreItems > 0 ? Math.round(scoreTotal / scoreItems) : 0;
137
+
138
+ return { success: true, ...data };
139
+ }
140
+
141
+ /**
142
+ * Render governance dashboard as text.
143
+ *
144
+ * @param {object} data
145
+ * @returns {string}
146
+ */
147
+ function renderDashboardText(data) {
148
+ const lines = [];
149
+ lines.push(`\nšŸ›ļø Governance Dashboard (${data.generated_at})`);
150
+ lines.push(`${'─'.repeat(50)}`);
151
+ lines.push(` Governance Score: ${data.governance_score}%`);
152
+ lines.push(` Policies: ${data.sections.policies.total} (${data.sections.policies.enabled} enabled)`);
153
+ lines.push(` Waivers: ${data.sections.waivers.total} (${data.sections.waivers.pending} pending, ${data.sections.waivers.approved} approved)`);
154
+ lines.push(` Risks: ${data.sections.risks.total} (${data.sections.risks.high} high, ${data.sections.risks.unmitigated} unmitigated)`);
155
+ lines.push(` Compliance: ${data.sections.compliance.frameworks} framework(s)`);
156
+ lines.push(` Readiness: ${data.sections.readiness.level} (${data.sections.readiness.score || 'N/A'}%)`);
157
+ lines.push(` Environment: ${data.sections.environment.current}`);
158
+ lines.push('');
159
+ return lines.join('\n');
160
+ }
161
+
162
+ module.exports = {
163
+ gatherGovernanceData,
164
+ renderDashboardText
165
+ };