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,158 @@
1
+ /**
2
+ * bcdr-planning.js — Business Continuity & DR Planning (Item 38)
3
+ *
4
+ * Add RTO/RPO, failover design, backup validation, and continuity
5
+ * considerations into specs.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/bcdr-planning.js define|check|report [options]
9
+ *
10
+ * State file: .jumpstart/state/bcdr.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', 'bcdr.json');
19
+
20
+ const SERVICE_TIERS = {
21
+ platinum: { rto_hours: 0.25, rpo_hours: 0, failover: 'automatic', backup_frequency: 'continuous' },
22
+ gold: { rto_hours: 1, rpo_hours: 1, failover: 'automatic', backup_frequency: 'hourly' },
23
+ silver: { rto_hours: 4, rpo_hours: 4, failover: 'manual', backup_frequency: 'daily' },
24
+ bronze: { rto_hours: 24, rpo_hours: 24, failover: 'manual', backup_frequency: 'weekly' }
25
+ };
26
+
27
+ const BCDR_COMPONENTS = ['rto-rpo', 'failover-design', 'backup-validation', 'communication-plan', 'recovery-procedures', 'testing-schedule'];
28
+
29
+ function defaultState() {
30
+ return {
31
+ version: '1.0.0',
32
+ created_at: new Date().toISOString(),
33
+ last_updated: null,
34
+ services: [],
35
+ dr_tests: []
36
+ };
37
+ }
38
+
39
+ function loadState(stateFile) {
40
+ const filePath = stateFile || DEFAULT_STATE_FILE;
41
+ if (!fs.existsSync(filePath)) return defaultState();
42
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
43
+ catch { return defaultState(); }
44
+ }
45
+
46
+ function saveState(state, stateFile) {
47
+ const filePath = stateFile || DEFAULT_STATE_FILE;
48
+ const dir = path.dirname(filePath);
49
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
50
+ state.last_updated = new Date().toISOString();
51
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
52
+ }
53
+
54
+ /**
55
+ * Define BC/DR requirements for a service.
56
+ *
57
+ * @param {object} service - { name, tier, rto_hours?, rpo_hours?, dependencies[]? }
58
+ * @param {object} [options]
59
+ * @returns {object}
60
+ */
61
+ function defineService(service, options = {}) {
62
+ if (!service || !service.name) return { success: false, error: 'service.name is required' };
63
+
64
+ const tier = (service.tier || 'silver').toLowerCase();
65
+ const template = SERVICE_TIERS[tier];
66
+ if (!template) {
67
+ return { success: false, error: `Invalid tier. Must be one of: ${Object.keys(SERVICE_TIERS).join(', ')}` };
68
+ }
69
+
70
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
71
+ const state = loadState(stateFile);
72
+
73
+ const svc = {
74
+ id: `SVC-${(state.services.length + 1).toString().padStart(3, '0')}`,
75
+ name: service.name,
76
+ tier,
77
+ rto_hours: service.rto_hours || template.rto_hours,
78
+ rpo_hours: service.rpo_hours || template.rpo_hours,
79
+ failover: service.failover || template.failover,
80
+ backup_frequency: service.backup_frequency || template.backup_frequency,
81
+ dependencies: service.dependencies || [],
82
+ recovery_procedures: service.recovery_procedures || [],
83
+ defined_at: new Date().toISOString()
84
+ };
85
+
86
+ state.services.push(svc);
87
+ saveState(state, stateFile);
88
+
89
+ return { success: true, service: svc };
90
+ }
91
+
92
+ /**
93
+ * Check BC/DR coverage in specs.
94
+ *
95
+ * @param {string} root
96
+ * @param {object} [options]
97
+ * @returns {object}
98
+ */
99
+ function checkCoverage(root, options = {}) {
100
+ const archFile = path.join(root, 'specs', 'architecture.md');
101
+ const findings = {};
102
+
103
+ if (fs.existsSync(archFile)) {
104
+ try {
105
+ const content = fs.readFileSync(archFile, 'utf8');
106
+ findings['rto-rpo'] = /\bRTO\b|\bRPO\b|recovery.time|recovery.point/i.test(content);
107
+ findings['failover-design'] = /\bfailover\b|high.availability|redundan/i.test(content);
108
+ findings['backup-validation'] = /\bbackup\b|snapshot|restore/i.test(content);
109
+ findings['recovery-procedures'] = /\brecovery\b|disaster|DR\b/i.test(content);
110
+ } catch { /* ignore */ }
111
+ }
112
+
113
+ findings['communication-plan'] = false;
114
+ findings['testing-schedule'] = false;
115
+
116
+ const covered = Object.values(findings).filter(Boolean).length;
117
+ const total = BCDR_COMPONENTS.length;
118
+
119
+ return {
120
+ success: true,
121
+ coverage: Math.round((covered / total) * 100),
122
+ components: findings,
123
+ gaps: BCDR_COMPONENTS.filter(c => !findings[c]),
124
+ recommendations: BCDR_COMPONENTS.filter(c => !findings[c]).map(c => `Add ${c} section to architecture spec`)
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Generate BCDR report.
130
+ *
131
+ * @param {object} [options]
132
+ * @returns {object}
133
+ */
134
+ function generateReport(options = {}) {
135
+ const stateFile = options.stateFile || DEFAULT_STATE_FILE;
136
+ const state = loadState(stateFile);
137
+
138
+ return {
139
+ success: true,
140
+ total_services: state.services.length,
141
+ by_tier: state.services.reduce((acc, s) => { acc[s.tier] = (acc[s.tier] || 0) + 1; return acc; }, {}),
142
+ services: state.services,
143
+ dr_tests: state.dr_tests,
144
+ lowest_rto: state.services.length > 0 ? Math.min(...state.services.map(s => s.rto_hours)) : null,
145
+ lowest_rpo: state.services.length > 0 ? Math.min(...state.services.map(s => s.rpo_hours)) : null
146
+ };
147
+ }
148
+
149
+ module.exports = {
150
+ defaultState,
151
+ loadState,
152
+ saveState,
153
+ defineService,
154
+ checkCoverage,
155
+ generateReport,
156
+ SERVICE_TIERS,
157
+ BCDR_COMPONENTS
158
+ };
@@ -0,0 +1,199 @@
1
+ /**
2
+ * bidirectional-trace.js — True Bidirectional Code-to-Spec Traceability
3
+ *
4
+ * Auto-links requirements to files, functions, tests, PRs, commits,
5
+ * and deployments. Provides forward (spec→code) and reverse (code→spec)
6
+ * trace lookups.
7
+ *
8
+ * Usage:
9
+ * node bin/lib/bidirectional-trace.js scan|report|link [options]
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ /**
18
+ * Scan a source directory and build a bidirectional trace map.
19
+ *
20
+ * @param {string} root - Project root.
21
+ * @param {object} [options]
22
+ * @returns {object} Trace map with forward and reverse indexes.
23
+ */
24
+ function scanTraceLinks(root, options = {}) {
25
+ const specsDir = path.join(root, 'specs');
26
+ const srcDir = path.join(root, options.srcDir || 'src');
27
+ const testsDir = path.join(root, options.testsDir || 'tests');
28
+
29
+ const forwardMap = {}; // specId → [{ file, line, type }]
30
+ const reverseMap = {}; // file → [{ specId, line, type }]
31
+
32
+ // Patterns for spec references in code
33
+ const SPEC_ID_PATTERN = /(?:E\d+-S\d+|M\d+-T\d+|NFR-[A-Z]+\d+|VC-\d+)/g;
34
+
35
+ function recordLink(specId, file, line, type) {
36
+ if (!forwardMap[specId]) forwardMap[specId] = [];
37
+ forwardMap[specId].push({ file, line, type });
38
+
39
+ if (!reverseMap[file]) reverseMap[file] = [];
40
+ reverseMap[file].push({ specId, line, type });
41
+ }
42
+
43
+ function scanDir(dir, fileType) {
44
+ if (!fs.existsSync(dir)) return;
45
+ const walk = (d) => {
46
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
47
+ const full = path.join(d, entry.name);
48
+ if (entry.isDirectory()) {
49
+ walk(full);
50
+ } else if (entry.isFile()) {
51
+ const rel = path.relative(root, full).replace(/\\/g, '/');
52
+ try {
53
+ const content = fs.readFileSync(full, 'utf8');
54
+ const lines = content.split('\n');
55
+ lines.forEach((line, idx) => {
56
+ let match;
57
+ const pattern = new RegExp(SPEC_ID_PATTERN.source, 'g');
58
+ while ((match = pattern.exec(line)) !== null) {
59
+ recordLink(match[0], rel, idx + 1, fileType);
60
+ }
61
+ });
62
+ } catch {
63
+ // skip unreadable files
64
+ }
65
+ }
66
+ }
67
+ };
68
+ walk(dir);
69
+ }
70
+
71
+ scanDir(srcDir, 'source');
72
+ scanDir(testsDir, 'test');
73
+
74
+ // Also scan spec files for cross-references
75
+ if (fs.existsSync(specsDir)) {
76
+ scanDir(specsDir, 'spec');
77
+ }
78
+
79
+ const stats = {
80
+ total_spec_ids: Object.keys(forwardMap).length,
81
+ total_files_with_links: Object.keys(reverseMap).length,
82
+ total_links: Object.values(forwardMap).reduce((s, a) => s + a.length, 0)
83
+ };
84
+
85
+ return { forward_map: forwardMap, reverse_map: reverseMap, stats };
86
+ }
87
+
88
+ /**
89
+ * Trace forward: given a spec ID, find all code that implements it.
90
+ *
91
+ * @param {string} specId - Spec ID (e.g., E1-S1, M1-T01).
92
+ * @param {object} traceMap - Result from scanTraceLinks().
93
+ * @returns {object[]} Array of { file, line, type }.
94
+ */
95
+ function traceForward(specId, traceMap) {
96
+ return (traceMap.forward_map || {})[specId] || [];
97
+ }
98
+
99
+ /**
100
+ * Trace reverse: given a file, find all spec IDs it relates to.
101
+ *
102
+ * @param {string} filePath - Relative file path.
103
+ * @param {object} traceMap - Result from scanTraceLinks().
104
+ * @returns {object[]} Array of { specId, line, type }.
105
+ */
106
+ function traceReverse(filePath, traceMap) {
107
+ return (traceMap.reverse_map || {})[filePath] || [];
108
+ }
109
+
110
+ /**
111
+ * Build a coverage summary — which spec IDs have no code links (gaps).
112
+ *
113
+ * @param {string} root - Project root.
114
+ * @param {object} traceMap - Result from scanTraceLinks().
115
+ * @returns {object} Coverage report.
116
+ */
117
+ function buildCoverageReport(root, traceMap) {
118
+ const specsDir = path.join(root, 'specs');
119
+ const prdPath = path.join(specsDir, 'prd.md');
120
+ const implPath = path.join(specsDir, 'implementation-plan.md');
121
+
122
+ const allSpecIds = new Set();
123
+ const SPEC_ID_PATTERN = /(?:E\d+-S\d+|M\d+-T\d+|NFR-[A-Z]+\d+|VC-\d+)/g;
124
+
125
+ function extractFromFile(filePath) {
126
+ if (!fs.existsSync(filePath)) return;
127
+ const content = fs.readFileSync(filePath, 'utf8');
128
+ let match;
129
+ while ((match = SPEC_ID_PATTERN.exec(content)) !== null) {
130
+ allSpecIds.add(match[0]);
131
+ }
132
+ }
133
+
134
+ extractFromFile(prdPath);
135
+ extractFromFile(implPath);
136
+
137
+ // Only count links from source and test files, not from spec files themselves.
138
+ const codeLinked = new Set();
139
+ for (const [specId, links] of Object.entries(traceMap.forward_map || {})) {
140
+ if (links.some(l => l.type === 'source' || l.type === 'test')) {
141
+ codeLinked.add(specId);
142
+ }
143
+ }
144
+ const gaps = [...allSpecIds].filter(id => !codeLinked.has(id));
145
+ const covered = [...allSpecIds].filter(id => codeLinked.has(id));
146
+
147
+ const coverage_pct = allSpecIds.size > 0
148
+ ? Math.round((covered.length / allSpecIds.size) * 100)
149
+ : 0;
150
+
151
+ return {
152
+ total_spec_ids: allSpecIds.size,
153
+ covered: covered.length,
154
+ gaps: gaps.length,
155
+ coverage_pct,
156
+ gap_list: gaps,
157
+ covered_list: covered
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Persist a trace map to disk.
163
+ *
164
+ * @param {object} traceMap
165
+ * @param {string} outputPath
166
+ */
167
+ function saveTraceMap(traceMap, outputPath) {
168
+ const dir = path.dirname(outputPath);
169
+ if (!fs.existsSync(dir)) {
170
+ fs.mkdirSync(dir, { recursive: true });
171
+ }
172
+ fs.writeFileSync(outputPath, JSON.stringify(traceMap, null, 2) + '\n', 'utf8');
173
+ }
174
+
175
+ /**
176
+ * Load a persisted trace map from disk.
177
+ *
178
+ * @param {string} inputPath
179
+ * @returns {object}
180
+ */
181
+ function loadTraceMap(inputPath) {
182
+ if (!fs.existsSync(inputPath)) {
183
+ return { forward_map: {}, reverse_map: {}, stats: {} };
184
+ }
185
+ try {
186
+ return JSON.parse(fs.readFileSync(inputPath, 'utf8'));
187
+ } catch {
188
+ return { forward_map: {}, reverse_map: {}, stats: {} };
189
+ }
190
+ }
191
+
192
+ module.exports = {
193
+ scanTraceLinks,
194
+ traceForward,
195
+ traceReverse,
196
+ buildCoverageReport,
197
+ saveTraceMap,
198
+ loadTraceMap
199
+ };
@@ -0,0 +1,266 @@
1
+ /**
2
+ * branch-workflow.js — Branch-Aware Workflow Engine
3
+ *
4
+ * Specs, approvals, and implementation plans track by git branch and PR,
5
+ * not just by repo folder state. Each branch gets its own workflow snapshot.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/branch-workflow.js track|status|sync [options]
9
+ *
10
+ * State file: .jumpstart/state/branch-workflows.json
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { execSync } = require('child_process');
18
+
19
+ const DEFAULT_BRANCH_STATE_FILE = path.join('.jumpstart', 'state', 'branch-workflows.json');
20
+
21
+ /**
22
+ * Safely get the current git branch name.
23
+ * @param {string} [cwd]
24
+ * @returns {string} Branch name or 'unknown'.
25
+ */
26
+ function getCurrentBranch(cwd) {
27
+ try {
28
+ return execSync('git rev-parse --abbrev-ref HEAD', {
29
+ cwd: cwd || process.cwd(),
30
+ stdio: ['ignore', 'pipe', 'ignore']
31
+ }).toString().trim();
32
+ } catch {
33
+ return 'unknown';
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Safely get the current git commit SHA.
39
+ * @param {string} [cwd]
40
+ * @returns {string} Commit SHA or 'unknown'.
41
+ */
42
+ function getCurrentCommit(cwd) {
43
+ try {
44
+ return execSync('git rev-parse HEAD', {
45
+ cwd: cwd || process.cwd(),
46
+ stdio: ['ignore', 'pipe', 'ignore']
47
+ }).toString().trim();
48
+ } catch {
49
+ return 'unknown';
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Default branch workflow store.
55
+ * @returns {object}
56
+ */
57
+ function defaultBranchStore() {
58
+ return {
59
+ version: '1.0.0',
60
+ created_at: new Date().toISOString(),
61
+ last_updated: null,
62
+ branches: {}
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Load the branch workflow store from disk.
68
+ * @param {string} [stateFile]
69
+ * @returns {object}
70
+ */
71
+ function loadBranchStore(stateFile) {
72
+ const filePath = stateFile || DEFAULT_BRANCH_STATE_FILE;
73
+ if (!fs.existsSync(filePath)) {
74
+ return defaultBranchStore();
75
+ }
76
+ try {
77
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
78
+ } catch {
79
+ return defaultBranchStore();
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Save the branch workflow store to disk.
85
+ * @param {object} store
86
+ * @param {string} [stateFile]
87
+ */
88
+ function saveBranchStore(store, stateFile) {
89
+ const filePath = stateFile || DEFAULT_BRANCH_STATE_FILE;
90
+ const dir = path.dirname(filePath);
91
+ if (!fs.existsSync(dir)) {
92
+ fs.mkdirSync(dir, { recursive: true });
93
+ }
94
+ store.last_updated = new Date().toISOString();
95
+ fs.writeFileSync(filePath, JSON.stringify(store, null, 2) + '\n', 'utf8');
96
+ }
97
+
98
+ /**
99
+ * Start tracking a branch. Creates or updates the branch workflow entry.
100
+ *
101
+ * @param {string} root - Project root.
102
+ * @param {object} [options] - { branch?, pr_number?, pr_url?, stateFile? }
103
+ * @returns {object}
104
+ */
105
+ function trackBranch(root, options = {}) {
106
+ const branch = options.branch || getCurrentBranch(root);
107
+ const commit = getCurrentCommit(root);
108
+ const stateFile = options.stateFile || path.join(root, DEFAULT_BRANCH_STATE_FILE);
109
+ const store = loadBranchStore(stateFile);
110
+
111
+ const existing = store.branches[branch] || {
112
+ branch,
113
+ created_at: new Date().toISOString(),
114
+ phase_snapshots: [],
115
+ approved_artifacts: [],
116
+ pr_number: null,
117
+ pr_url: null
118
+ };
119
+
120
+ existing.branch = branch;
121
+ existing.last_commit = commit;
122
+ existing.last_seen = new Date().toISOString();
123
+
124
+ if (options.pr_number !== undefined) existing.pr_number = options.pr_number;
125
+ if (options.pr_url !== undefined) existing.pr_url = options.pr_url;
126
+
127
+ store.branches[branch] = existing;
128
+ saveBranchStore(store, stateFile);
129
+
130
+ return { success: true, branch: existing };
131
+ }
132
+
133
+ /**
134
+ * Record a phase snapshot for a specific branch.
135
+ *
136
+ * @param {string} root - Project root.
137
+ * @param {number} phase - Phase number.
138
+ * @param {object} snapshot - Additional snapshot data.
139
+ * @param {object} [options]
140
+ * @returns {object}
141
+ */
142
+ function recordPhaseSnapshot(root, phase, snapshot = {}, options = {}) {
143
+ const branch = options.branch || getCurrentBranch(root);
144
+ const stateFile = options.stateFile || path.join(root, DEFAULT_BRANCH_STATE_FILE);
145
+ const store = loadBranchStore(stateFile);
146
+
147
+ if (!store.branches[branch]) {
148
+ store.branches[branch] = {
149
+ branch,
150
+ created_at: new Date().toISOString(),
151
+ phase_snapshots: [],
152
+ approved_artifacts: [],
153
+ pr_number: null,
154
+ pr_url: null
155
+ };
156
+ }
157
+
158
+ const entry = {
159
+ phase,
160
+ recorded_at: new Date().toISOString(),
161
+ commit: getCurrentCommit(root),
162
+ ...snapshot
163
+ };
164
+
165
+ store.branches[branch].phase_snapshots.push(entry);
166
+ saveBranchStore(store, stateFile);
167
+
168
+ return { success: true, branch, snapshot: entry };
169
+ }
170
+
171
+ /**
172
+ * Record an artifact approval for a specific branch.
173
+ *
174
+ * @param {string} root
175
+ * @param {string} artifactPath
176
+ * @param {string} approver
177
+ * @param {object} [options]
178
+ * @returns {object}
179
+ */
180
+ function recordBranchApproval(root, artifactPath, approver, options = {}) {
181
+ const branch = options.branch || getCurrentBranch(root);
182
+ const stateFile = options.stateFile || path.join(root, DEFAULT_BRANCH_STATE_FILE);
183
+ const store = loadBranchStore(stateFile);
184
+
185
+ if (!store.branches[branch]) {
186
+ store.branches[branch] = {
187
+ branch,
188
+ created_at: new Date().toISOString(),
189
+ phase_snapshots: [],
190
+ approved_artifacts: [],
191
+ pr_number: null,
192
+ pr_url: null
193
+ };
194
+ }
195
+
196
+ const entry = {
197
+ artifact: artifactPath,
198
+ approver,
199
+ approved_at: new Date().toISOString(),
200
+ commit: getCurrentCommit(root)
201
+ };
202
+
203
+ store.branches[branch].approved_artifacts.push(entry);
204
+ saveBranchStore(store, stateFile);
205
+
206
+ return { success: true, branch, approval: entry };
207
+ }
208
+
209
+ /**
210
+ * Get the workflow status of a branch.
211
+ *
212
+ * @param {string} root
213
+ * @param {object} [options]
214
+ * @returns {object}
215
+ */
216
+ function getBranchStatus(root, options = {}) {
217
+ const branch = options.branch || getCurrentBranch(root);
218
+ const stateFile = options.stateFile || path.join(root, DEFAULT_BRANCH_STATE_FILE);
219
+ const store = loadBranchStore(stateFile);
220
+
221
+ const branchData = store.branches[branch];
222
+ if (!branchData) {
223
+ return {
224
+ success: true,
225
+ branch,
226
+ tracked: false,
227
+ message: `Branch "${branch}" is not yet tracked. Run: jumpstart-mode branch-workflow track`
228
+ };
229
+ }
230
+
231
+ return {
232
+ success: true,
233
+ branch,
234
+ tracked: true,
235
+ data: branchData,
236
+ phase_count: (branchData.phase_snapshots || []).length,
237
+ approved_count: (branchData.approved_artifacts || []).length
238
+ };
239
+ }
240
+
241
+ /**
242
+ * List all tracked branches.
243
+ *
244
+ * @param {object} [options]
245
+ * @returns {object}
246
+ */
247
+ function listTrackedBranches(options = {}) {
248
+ const stateFile = options.stateFile || DEFAULT_BRANCH_STATE_FILE;
249
+ const store = loadBranchStore(stateFile);
250
+
251
+ const branches = Object.values(store.branches);
252
+ return { success: true, branches, total: branches.length };
253
+ }
254
+
255
+ module.exports = {
256
+ getCurrentBranch,
257
+ getCurrentCommit,
258
+ loadBranchStore,
259
+ saveBranchStore,
260
+ defaultBranchStore,
261
+ trackBranch,
262
+ recordPhaseSnapshot,
263
+ recordBranchApproval,
264
+ getBranchStatus,
265
+ listTrackedBranches
266
+ };