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.
- package/.github/agents/jumpstart-adversary.agent.md +2 -1
- package/.github/agents/jumpstart-architect.agent.md +6 -7
- package/.github/agents/jumpstart-challenger.agent.md +2 -1
- package/.github/agents/jumpstart-developer.agent.md +1 -1
- package/.github/agents/jumpstart-devops.agent.md +2 -2
- package/.github/agents/jumpstart-diagram-verifier.agent.md +2 -1
- package/.github/agents/jumpstart-maintenance.agent.md +1 -0
- package/.github/agents/jumpstart-performance.agent.md +1 -0
- package/.github/agents/jumpstart-pm.agent.md +1 -1
- package/.github/agents/jumpstart-refactor.agent.md +1 -0
- package/.github/agents/jumpstart-requirements-extractor.agent.md +1 -0
- package/.github/agents/jumpstart-researcher.agent.md +1 -0
- package/.github/agents/jumpstart-retrospective.agent.md +1 -0
- package/.github/agents/jumpstart-reviewer.agent.md +2 -0
- package/.github/agents/jumpstart-scout.agent.md +1 -1
- package/.github/agents/jumpstart-scrum-master.agent.md +1 -0
- package/.github/agents/jumpstart-security.agent.md +2 -1
- package/.github/agents/jumpstart-tech-writer.agent.md +1 -0
- package/.github/agents/jumpstart-uiux-designer.agent.md +66 -0
- package/.github/workflows/quality.yml +19 -2
- package/.jumpstart/agents/analyst.md +38 -0
- package/.jumpstart/agents/architect.md +39 -1
- package/.jumpstart/agents/challenger.md +38 -0
- package/.jumpstart/agents/developer.md +41 -0
- package/.jumpstart/agents/pm.md +38 -0
- package/.jumpstart/agents/scout.md +33 -0
- package/.jumpstart/agents/ux-designer.md +29 -9
- package/.jumpstart/commands/commands.md +6 -5
- package/.jumpstart/config.yaml +25 -1
- package/.jumpstart/roadmap.md +1 -1
- package/.jumpstart/schemas/timeline.schema.json +1 -0
- package/.jumpstart/skills/README.md +1 -0
- package/.jumpstart/skills/quality-gates/SKILL.md +126 -0
- package/.jumpstart/skills/skill-creator/SKILL.md +485 -357
- package/.jumpstart/skills/skill-creator/agents/analyzer.md +274 -0
- package/.jumpstart/skills/skill-creator/agents/comparator.md +202 -0
- package/.jumpstart/skills/skill-creator/agents/grader.md +223 -0
- package/.jumpstart/skills/skill-creator/assets/eval_review.html +146 -0
- package/.jumpstart/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.jumpstart/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.jumpstart/skills/skill-creator/references/schemas.md +430 -0
- package/.jumpstart/skills/skill-creator/scripts/__init__.py +0 -0
- package/.jumpstart/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.jumpstart/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.jumpstart/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.jumpstart/skills/skill-creator/scripts/package_skill.py +136 -110
- package/.jumpstart/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.jumpstart/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.jumpstart/skills/skill-creator/scripts/utils.py +47 -0
- package/.jumpstart/skills/ui-ux-pro-max/SKILL.md +266 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.jumpstart/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.jumpstart/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.jumpstart/state/timeline.json +659 -0
- package/.jumpstart/templates/model-map.md +1 -1
- package/.jumpstart/templates/ux-design.md +3 -3
- package/.jumpstart/usage-log.json +74 -3
- package/AGENTS.md +1 -1
- package/README.md +64 -3
- package/bin/cli.js +3217 -1
- package/bin/headless-runner.js +62 -2
- package/bin/lib/agent-checkpoint.js +168 -0
- package/bin/lib/ai-evaluation.js +104 -0
- package/bin/lib/ai-intake.js +152 -0
- package/bin/lib/ambiguity-heatmap.js +152 -0
- package/bin/lib/artifact-comparison.js +104 -0
- package/bin/lib/ast-edit-engine.js +157 -0
- package/bin/lib/backlog-sync.js +338 -0
- package/bin/lib/bcdr-planning.js +158 -0
- package/bin/lib/bidirectional-trace.js +199 -0
- package/bin/lib/branch-workflow.js +266 -0
- package/bin/lib/cab-output.js +119 -0
- package/bin/lib/chat-integration.js +122 -0
- package/bin/lib/ci-cd-integration.js +208 -0
- package/bin/lib/codebase-retrieval.js +125 -0
- package/bin/lib/collaboration.js +168 -0
- package/bin/lib/compliance-packs.js +213 -0
- package/bin/lib/context-chunker.js +128 -0
- package/bin/lib/context-onboarding.js +122 -0
- package/bin/lib/contract-first.js +124 -0
- package/bin/lib/cost-router.js +148 -0
- package/bin/lib/credential-boundary.js +155 -0
- package/bin/lib/data-classification.js +180 -0
- package/bin/lib/data-contracts.js +129 -0
- package/bin/lib/db-evolution.js +158 -0
- package/bin/lib/decision-conflicts.js +299 -0
- package/bin/lib/delivery-confidence.js +361 -0
- package/bin/lib/dependency-upgrade.js +153 -0
- package/bin/lib/design-system.js +133 -0
- package/bin/lib/deterministic-artifacts.js +151 -0
- package/bin/lib/diagram-studio.js +115 -0
- package/bin/lib/domain-ontology.js +140 -0
- package/bin/lib/ea-review-packet.js +151 -0
- package/bin/lib/enterprise-search.js +123 -0
- package/bin/lib/enterprise-templates.js +140 -0
- package/bin/lib/environment-promotion.js +220 -0
- package/bin/lib/estimation-studio.js +130 -0
- package/bin/lib/event-modeling.js +133 -0
- package/bin/lib/evidence-collector.js +179 -0
- package/bin/lib/finops-planner.js +182 -0
- package/bin/lib/fitness-functions.js +279 -0
- package/bin/lib/focus.js +448 -0
- package/bin/lib/governance-dashboard.js +165 -0
- package/bin/lib/guided-handoff.js +120 -0
- package/bin/lib/impact-analysis.js +190 -0
- package/bin/lib/incident-feedback.js +157 -0
- package/bin/lib/integrate.js +1 -1
- package/bin/lib/knowledge-graph.js +122 -0
- package/bin/lib/legacy-modernizer.js +160 -0
- package/bin/lib/migration-planner.js +144 -0
- package/bin/lib/model-governance.js +185 -0
- package/bin/lib/model-router.js +144 -0
- package/bin/lib/multi-repo.js +272 -0
- package/bin/lib/next-phase.js +53 -8
- package/bin/lib/ops-ownership.js +152 -0
- package/bin/lib/parallel-agents.js +257 -0
- package/bin/lib/pattern-library.js +115 -0
- package/bin/lib/persona-packs.js +99 -0
- package/bin/lib/plan-executor.js +366 -0
- package/bin/lib/platform-engineering.js +119 -0
- package/bin/lib/playback-summaries.js +126 -0
- package/bin/lib/policy-engine.js +240 -0
- package/bin/lib/portfolio-reporting.js +357 -0
- package/bin/lib/pr-package.js +197 -0
- package/bin/lib/project-memory.js +235 -0
- package/bin/lib/prompt-governance.js +130 -0
- package/bin/lib/promptless-mode.js +128 -0
- package/bin/lib/quality-graph.js +193 -0
- package/bin/lib/raci-matrix.js +188 -0
- package/bin/lib/refactor-planner.js +167 -0
- package/bin/lib/reference-architectures.js +304 -0
- package/bin/lib/release-readiness.js +171 -0
- package/bin/lib/repo-graph.js +262 -0
- package/bin/lib/requirements-baseline.js +358 -0
- package/bin/lib/risk-register.js +211 -0
- package/bin/lib/role-approval.js +249 -0
- package/bin/lib/role-views.js +142 -0
- package/bin/lib/root-cause-analysis.js +132 -0
- package/bin/lib/runtime-debugger.js +154 -0
- package/bin/lib/safe-rename.js +135 -0
- package/bin/lib/secret-scanner.js +313 -0
- package/bin/lib/semantic-diff.js +335 -0
- package/bin/lib/sla-slo.js +210 -0
- package/bin/lib/smoke-tester.js +344 -0
- package/bin/lib/spec-comments.js +147 -0
- package/bin/lib/spec-maturity.js +287 -0
- package/bin/lib/sre-integration.js +154 -0
- package/bin/lib/structured-elicitation.js +174 -0
- package/bin/lib/telemetry-feedback.js +118 -0
- package/bin/lib/test-generator.js +146 -0
- package/bin/lib/timeline.js +2 -1
- package/bin/lib/tool-bridge.js +159 -0
- package/bin/lib/tool-guardrails.js +139 -0
- package/bin/lib/tool-schemas.js +281 -3
- package/bin/lib/transcript-ingestion.js +150 -0
- package/bin/lib/type-checker.js +261 -0
- package/bin/lib/uat-coverage.js +411 -0
- package/bin/lib/vendor-risk.js +173 -0
- package/bin/lib/waiver-workflow.js +174 -0
- package/bin/lib/web-dashboard.js +126 -0
- package/bin/lib/workshop-mode.js +165 -0
- package/bin/lib/workstream-ownership.js +104 -0
- package/package.json +1 -1
- package/.github/agents/jumpstart-ux-designer.agent.md +0 -45
package/bin/lib/focus.js
ADDED
|
@@ -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
|
+
};
|