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,262 @@
1
+ /**
2
+ * repo-graph.js — Automated Repo Understanding Graph
3
+ *
4
+ * Builds and persists a semantic graph of domains, modules, APIs,
5
+ * ownership, decisions, and dependencies for a project repository.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/repo-graph.js build|query|export [options]
9
+ *
10
+ * Output file: .jumpstart/state/repo-graph.json
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_GRAPH_FILE = path.join('.jumpstart', 'state', 'repo-graph.json');
19
+
20
+ /**
21
+ * Default empty graph structure.
22
+ * @returns {object}
23
+ */
24
+ function defaultRepoGraph() {
25
+ return {
26
+ version: '1.0.0',
27
+ generated_at: new Date().toISOString(),
28
+ last_updated: null,
29
+ nodes: {}, // id → { id, type, name, metadata }
30
+ edges: [] // { from, to, relationship }
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Load a repo graph from disk.
36
+ * @param {string} [graphFile]
37
+ * @returns {object}
38
+ */
39
+ function loadRepoGraph(graphFile) {
40
+ const filePath = graphFile || DEFAULT_GRAPH_FILE;
41
+ if (!fs.existsSync(filePath)) {
42
+ return defaultRepoGraph();
43
+ }
44
+ try {
45
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
46
+ } catch {
47
+ return defaultRepoGraph();
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Save a repo graph to disk.
53
+ * @param {object} graph
54
+ * @param {string} [graphFile]
55
+ */
56
+ function saveRepoGraph(graph, graphFile) {
57
+ const filePath = graphFile || DEFAULT_GRAPH_FILE;
58
+ const dir = path.dirname(filePath);
59
+ if (!fs.existsSync(dir)) {
60
+ fs.mkdirSync(dir, { recursive: true });
61
+ }
62
+ graph.last_updated = new Date().toISOString();
63
+ fs.writeFileSync(filePath, JSON.stringify(graph, null, 2) + '\n', 'utf8');
64
+ }
65
+
66
+ /**
67
+ * Add or update a node in the graph.
68
+ * @param {object} graph
69
+ * @param {string} id
70
+ * @param {string} type - domain|module|api|file|decision|owner|dependency
71
+ * @param {object} [metadata]
72
+ */
73
+ function upsertNode(graph, id, type, metadata = {}) {
74
+ graph.nodes[id] = {
75
+ ...(graph.nodes[id] || {}),
76
+ id,
77
+ type,
78
+ ...metadata,
79
+ updated_at: new Date().toISOString()
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Add an edge between two nodes (avoids duplicates).
85
+ * @param {object} graph
86
+ * @param {string} from
87
+ * @param {string} to
88
+ * @param {string} relationship
89
+ */
90
+ function addEdge(graph, from, to, relationship) {
91
+ const exists = graph.edges.some(
92
+ e => e.from === from && e.to === to && e.relationship === relationship
93
+ );
94
+ if (!exists) {
95
+ graph.edges.push({ from, to, relationship });
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Build the semantic graph from the project file system.
101
+ * Scans src/, specs/, and config files to auto-discover structure.
102
+ *
103
+ * @param {string} root - Project root.
104
+ * @param {object} [options]
105
+ * @returns {object} Populated graph.
106
+ */
107
+ function buildRepoGraph(root, options = {}) {
108
+ const graph = defaultRepoGraph();
109
+ const srcDir = path.join(root, options.srcDir || 'src');
110
+ const specsDir = path.join(root, 'specs');
111
+ const graphFile = options.graphFile || path.join(root, DEFAULT_GRAPH_FILE);
112
+
113
+ // 1. Scan src directory for modules and files
114
+ if (fs.existsSync(srcDir)) {
115
+ upsertNode(graph, 'src', 'module', { name: 'Source Root', path: 'src' });
116
+
117
+ const walk = (dir, parentId) => {
118
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
119
+ const full = path.join(dir, entry.name);
120
+ const rel = path.relative(root, full).replace(/\\/g, '/');
121
+ const nodeId = `file:${rel}`;
122
+
123
+ if (entry.isDirectory()) {
124
+ upsertNode(graph, nodeId, 'module', { name: entry.name, path: rel });
125
+ addEdge(graph, parentId, nodeId, 'contains');
126
+ walk(full, nodeId);
127
+ } else if (entry.isFile()) {
128
+ // Classify file type
129
+ let fileType = 'file';
130
+ const lower = entry.name.toLowerCase();
131
+ if (lower.includes('api') || lower.includes('route') || lower.includes('endpoint')) {
132
+ fileType = 'api';
133
+ } else if (lower.includes('model') || lower.includes('schema') || lower.includes('entity')) {
134
+ fileType = 'model';
135
+ } else if (lower.includes('service') || lower.includes('controller')) {
136
+ fileType = 'service';
137
+ }
138
+
139
+ upsertNode(graph, nodeId, fileType, { name: entry.name, path: rel });
140
+ addEdge(graph, parentId, nodeId, 'contains');
141
+
142
+ // Extract any ownership annotations (e.g., @owner comments)
143
+ try {
144
+ const content = fs.readFileSync(full, 'utf8');
145
+ const ownerMatch = content.match(/@owner[:\s]+(\S+)/i);
146
+ if (ownerMatch) {
147
+ const ownerId = `owner:${ownerMatch[1]}`;
148
+ upsertNode(graph, ownerId, 'owner', { name: ownerMatch[1] });
149
+ addEdge(graph, ownerId, nodeId, 'owns');
150
+ }
151
+ } catch {
152
+ // skip unreadable files
153
+ }
154
+ }
155
+ }
156
+ };
157
+
158
+ walk(srcDir, 'src');
159
+ }
160
+
161
+ // 2. Scan specs for decisions
162
+ const decisionsDir = path.join(specsDir, 'decisions');
163
+ if (fs.existsSync(decisionsDir)) {
164
+ upsertNode(graph, 'decisions', 'module', { name: 'Architecture Decisions', path: 'specs/decisions' });
165
+ for (const file of fs.readdirSync(decisionsDir)) {
166
+ if (file.endsWith('.md')) {
167
+ const nodeId = `decision:${file}`;
168
+ const adrPath = path.join(decisionsDir, file);
169
+ let title = file;
170
+ try {
171
+ const content = fs.readFileSync(adrPath, 'utf8');
172
+ const titleMatch = content.match(/^# (.+)/m);
173
+ if (titleMatch) title = titleMatch[1];
174
+ } catch {
175
+ // use filename as title
176
+ }
177
+ upsertNode(graph, nodeId, 'decision', { name: title, path: `specs/decisions/${file}` });
178
+ addEdge(graph, 'decisions', nodeId, 'contains');
179
+ }
180
+ }
181
+ }
182
+
183
+ // 3. Add spec files as nodes
184
+ const specFiles = ['challenger-brief.md', 'product-brief.md', 'prd.md', 'architecture.md', 'implementation-plan.md'];
185
+ upsertNode(graph, 'specs', 'module', { name: 'Specifications', path: 'specs' });
186
+ for (const sf of specFiles) {
187
+ const specPath = path.join(specsDir, sf);
188
+ if (fs.existsSync(specPath)) {
189
+ const nodeId = `spec:${sf}`;
190
+ upsertNode(graph, nodeId, 'spec', { name: sf, path: `specs/${sf}` });
191
+ addEdge(graph, 'specs', nodeId, 'contains');
192
+ }
193
+ }
194
+
195
+ const nodeCount = Object.keys(graph.nodes).length;
196
+ const edgeCount = graph.edges.length;
197
+
198
+ saveRepoGraph(graph, graphFile);
199
+
200
+ return {
201
+ success: true,
202
+ node_count: nodeCount,
203
+ edge_count: edgeCount,
204
+ graph_file: graphFile
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Query the graph for nodes matching a type or name pattern.
210
+ *
211
+ * @param {object} graph
212
+ * @param {object} query - { type?, nameContains? }
213
+ * @returns {object[]} Matching nodes.
214
+ */
215
+ function queryGraph(graph, query = {}) {
216
+ let nodes = Object.values(graph.nodes);
217
+
218
+ if (query.type) {
219
+ nodes = nodes.filter(n => n.type === query.type);
220
+ }
221
+
222
+ if (query.nameContains) {
223
+ const lower = query.nameContains.toLowerCase();
224
+ nodes = nodes.filter(n => n.name && n.name.toLowerCase().includes(lower));
225
+ }
226
+
227
+ if (query.id) {
228
+ nodes = nodes.filter(n => n.id === query.id);
229
+ }
230
+
231
+ return nodes;
232
+ }
233
+
234
+ /**
235
+ * Find all neighbours of a node (one-hop).
236
+ *
237
+ * @param {object} graph
238
+ * @param {string} nodeId
239
+ * @returns {{ incoming: object[], outgoing: object[] }}
240
+ */
241
+ function getNeighbours(graph, nodeId) {
242
+ const outgoing = graph.edges
243
+ .filter(e => e.from === nodeId)
244
+ .map(e => ({ node: graph.nodes[e.to] || { id: e.to }, relationship: e.relationship }));
245
+
246
+ const incoming = graph.edges
247
+ .filter(e => e.to === nodeId)
248
+ .map(e => ({ node: graph.nodes[e.from] || { id: e.from }, relationship: e.relationship }));
249
+
250
+ return { incoming, outgoing };
251
+ }
252
+
253
+ module.exports = {
254
+ defaultRepoGraph,
255
+ loadRepoGraph,
256
+ saveRepoGraph,
257
+ upsertNode,
258
+ addEdge,
259
+ buildRepoGraph,
260
+ queryGraph,
261
+ getNeighbours
262
+ };
@@ -0,0 +1,358 @@
1
+ /**
2
+ * requirements-baseline.js — Requirements Baseline & Change Control
3
+ *
4
+ * Freezes approved requirements and forces impact assessment when
5
+ * downstream changes are proposed.
6
+ *
7
+ * Baseline file: .jumpstart/state/requirements-baseline.json
8
+ *
9
+ * Usage:
10
+ * node bin/lib/requirements-baseline.js freeze|check|impact|status [options]
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const crypto = require('crypto');
18
+
19
+ const DEFAULT_BASELINE_FILE = path.join('.jumpstart', 'state', 'requirements-baseline.json');
20
+
21
+ const ARTIFACT_TYPES = ['challenger-brief', 'product-brief', 'prd', 'architecture', 'implementation-plan'];
22
+
23
+ /**
24
+ * Default empty baseline registry.
25
+ * @returns {object}
26
+ */
27
+ function defaultBaseline() {
28
+ return {
29
+ version: '1.0.0',
30
+ created_at: new Date().toISOString(),
31
+ last_updated: null,
32
+ frozen: false,
33
+ baselines: [],
34
+ change_requests: []
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Load baseline from disk.
40
+ * @param {string} [baselineFile]
41
+ * @returns {object}
42
+ */
43
+ function loadBaseline(baselineFile) {
44
+ const filePath = baselineFile || DEFAULT_BASELINE_FILE;
45
+ if (!fs.existsSync(filePath)) {
46
+ return defaultBaseline();
47
+ }
48
+ try {
49
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
50
+ } catch {
51
+ return defaultBaseline();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Save baseline to disk.
57
+ * @param {object} baseline
58
+ * @param {string} [baselineFile]
59
+ */
60
+ function saveBaseline(baseline, baselineFile) {
61
+ const filePath = baselineFile || DEFAULT_BASELINE_FILE;
62
+ const dir = path.dirname(filePath);
63
+ if (!fs.existsSync(dir)) {
64
+ fs.mkdirSync(dir, { recursive: true });
65
+ }
66
+ baseline.last_updated = new Date().toISOString();
67
+ fs.writeFileSync(filePath, JSON.stringify(baseline, null, 2) + '\n', 'utf8');
68
+ }
69
+
70
+ /**
71
+ * Compute SHA-256 hash of content.
72
+ * @param {string} content
73
+ * @returns {string}
74
+ */
75
+ function hashContent(content) {
76
+ return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
77
+ }
78
+
79
+ /**
80
+ * Extract requirement IDs from content.
81
+ * Matches patterns like REQ-001, E01-S01, NFR-001, UC-001, etc.
82
+ * @param {string} content
83
+ * @returns {string[]}
84
+ */
85
+ function extractRequirementIds(content) {
86
+ const patterns = [
87
+ /\b(REQ-\d+)\b/g,
88
+ /\b(E\d+-S\d+)\b/g,
89
+ /\b(NFR-\d+)\b/g,
90
+ /\b(UC-\d+)\b/g,
91
+ /\b(FR-\d+)\b/g,
92
+ /\b(AC-\d+)\b/g
93
+ ];
94
+ const ids = new Set();
95
+ for (const pattern of patterns) {
96
+ let match;
97
+ while ((match = pattern.exec(content)) !== null) {
98
+ ids.add(match[1]);
99
+ }
100
+ }
101
+ return [...ids].sort();
102
+ }
103
+
104
+ /**
105
+ * Freeze current requirements as baseline.
106
+ *
107
+ * @param {string} root - Project root.
108
+ * @param {object} [options]
109
+ * @returns {object}
110
+ */
111
+ function freezeBaseline(root, options = {}) {
112
+ const baselineFile = options.baselineFile || path.join(root, DEFAULT_BASELINE_FILE);
113
+ const baseline = loadBaseline(baselineFile);
114
+
115
+ const specsDir = path.join(root, 'specs');
116
+ if (!fs.existsSync(specsDir)) {
117
+ return { success: false, error: 'specs/ directory not found' };
118
+ }
119
+
120
+ const snapshots = [];
121
+ const artifactMap = {
122
+ 'challenger-brief': 'specs/challenger-brief.md',
123
+ 'product-brief': 'specs/product-brief.md',
124
+ 'prd': 'specs/prd.md',
125
+ 'architecture': 'specs/architecture.md',
126
+ 'implementation-plan': 'specs/implementation-plan.md'
127
+ };
128
+
129
+ for (const [type, relPath] of Object.entries(artifactMap)) {
130
+ const fullPath = path.join(root, relPath);
131
+ if (fs.existsSync(fullPath)) {
132
+ const content = fs.readFileSync(fullPath, 'utf8');
133
+ snapshots.push({
134
+ type,
135
+ path: relPath,
136
+ hash: hashContent(content),
137
+ requirement_ids: extractRequirementIds(content),
138
+ frozen_at: new Date().toISOString()
139
+ });
140
+ }
141
+ }
142
+
143
+ if (snapshots.length === 0) {
144
+ return { success: false, error: 'No spec artifacts found to freeze' };
145
+ }
146
+
147
+ const baselineEntry = {
148
+ id: `baseline-${Date.now()}`,
149
+ frozen_at: new Date().toISOString(),
150
+ frozen_by: options.approver || 'system',
151
+ snapshots,
152
+ total_requirements: snapshots.reduce((sum, s) => sum + s.requirement_ids.length, 0)
153
+ };
154
+
155
+ baseline.baselines.push(baselineEntry);
156
+ baseline.frozen = true;
157
+ saveBaseline(baseline, baselineFile);
158
+
159
+ return {
160
+ success: true,
161
+ baseline_id: baselineEntry.id,
162
+ artifacts_frozen: snapshots.length,
163
+ total_requirements: baselineEntry.total_requirements,
164
+ snapshots: snapshots.map(s => ({ type: s.type, path: s.path, requirements: s.requirement_ids.length }))
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Check if any frozen artifacts have changed since the baseline.
170
+ *
171
+ * @param {string} root - Project root.
172
+ * @param {object} [options]
173
+ * @returns {object}
174
+ */
175
+ function checkBaseline(root, options = {}) {
176
+ const baselineFile = options.baselineFile || path.join(root, DEFAULT_BASELINE_FILE);
177
+ const baseline = loadBaseline(baselineFile);
178
+
179
+ if (!baseline.frozen || baseline.baselines.length === 0) {
180
+ return { success: true, frozen: false, message: 'No frozen baseline found' };
181
+ }
182
+
183
+ const latestBaseline = baseline.baselines[baseline.baselines.length - 1];
184
+ const changes = [];
185
+ const unchanged = [];
186
+
187
+ for (const snapshot of latestBaseline.snapshots) {
188
+ const fullPath = path.join(root, snapshot.path);
189
+ if (!fs.existsSync(fullPath)) {
190
+ changes.push({
191
+ type: snapshot.type,
192
+ path: snapshot.path,
193
+ change: 'deleted',
194
+ severity: 'critical'
195
+ });
196
+ continue;
197
+ }
198
+
199
+ const content = fs.readFileSync(fullPath, 'utf8');
200
+ const currentHash = hashContent(content);
201
+
202
+ if (currentHash !== snapshot.hash) {
203
+ const currentIds = extractRequirementIds(content);
204
+ const addedIds = currentIds.filter(id => !snapshot.requirement_ids.includes(id));
205
+ const removedIds = snapshot.requirement_ids.filter(id => !currentIds.includes(id));
206
+
207
+ changes.push({
208
+ type: snapshot.type,
209
+ path: snapshot.path,
210
+ change: 'modified',
211
+ severity: removedIds.length > 0 ? 'critical' : addedIds.length > 0 ? 'warning' : 'info',
212
+ added_requirements: addedIds,
213
+ removed_requirements: removedIds
214
+ });
215
+ } else {
216
+ unchanged.push({ type: snapshot.type, path: snapshot.path });
217
+ }
218
+ }
219
+
220
+ const drifted = changes.length > 0;
221
+ return {
222
+ success: true,
223
+ frozen: true,
224
+ baseline_id: latestBaseline.id,
225
+ frozen_at: latestBaseline.frozen_at,
226
+ drifted,
227
+ changes,
228
+ unchanged,
229
+ summary: {
230
+ total_artifacts: latestBaseline.snapshots.length,
231
+ changed: changes.length,
232
+ unchanged: unchanged.length,
233
+ critical: changes.filter(c => c.severity === 'critical').length
234
+ }
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Perform impact assessment for a proposed change.
240
+ *
241
+ * @param {string} artifactPath - Path to the changed artifact.
242
+ * @param {string} root - Project root.
243
+ * @param {object} [options]
244
+ * @returns {object}
245
+ */
246
+ function assessImpact(artifactPath, root, options = {}) {
247
+ const baselineFile = options.baselineFile || path.join(root, DEFAULT_BASELINE_FILE);
248
+ const baseline = loadBaseline(baselineFile);
249
+
250
+ if (!baseline.frozen || baseline.baselines.length === 0) {
251
+ return { success: true, impact: 'none', message: 'No frozen baseline — changes are unconstrained' };
252
+ }
253
+
254
+ const relPath = path.relative(root, path.resolve(root, artifactPath)).replace(/\\/g, '/');
255
+ const latestBaseline = baseline.baselines[baseline.baselines.length - 1];
256
+ const snapshot = latestBaseline.snapshots.find(s => s.path === relPath);
257
+
258
+ if (!snapshot) {
259
+ return { success: true, impact: 'none', message: `${relPath} is not part of the frozen baseline` };
260
+ }
261
+
262
+ const fullPath = path.join(root, relPath);
263
+ if (!fs.existsSync(fullPath)) {
264
+ return {
265
+ success: true,
266
+ impact: 'critical',
267
+ artifact: relPath,
268
+ assessment: {
269
+ change_type: 'deletion',
270
+ affected_requirements: snapshot.requirement_ids,
271
+ downstream_artifacts: ARTIFACT_TYPES.filter(t => t !== snapshot.type)
272
+ }
273
+ };
274
+ }
275
+
276
+ const content = fs.readFileSync(fullPath, 'utf8');
277
+ const currentHash = hashContent(content);
278
+
279
+ if (currentHash === snapshot.hash) {
280
+ return { success: true, impact: 'none', message: 'Artifact matches frozen baseline' };
281
+ }
282
+
283
+ const currentIds = extractRequirementIds(content);
284
+ const addedIds = currentIds.filter(id => !snapshot.requirement_ids.includes(id));
285
+ const removedIds = snapshot.requirement_ids.filter(id => !currentIds.includes(id));
286
+ const unchangedIds = currentIds.filter(id => snapshot.requirement_ids.includes(id));
287
+
288
+ const typeIndex = ARTIFACT_TYPES.indexOf(snapshot.type);
289
+ const downstreamTypes = ARTIFACT_TYPES.slice(typeIndex + 1);
290
+
291
+ const impactLevel = removedIds.length > 0 ? 'critical'
292
+ : addedIds.length > 3 ? 'high'
293
+ : addedIds.length > 0 ? 'medium'
294
+ : 'low';
295
+
296
+ const changeRequest = {
297
+ id: `cr-${Date.now()}`,
298
+ artifact: relPath,
299
+ artifact_type: snapshot.type,
300
+ requested_at: new Date().toISOString(),
301
+ impact_level: impactLevel,
302
+ added_requirements: addedIds,
303
+ removed_requirements: removedIds,
304
+ downstream_artifacts: downstreamTypes,
305
+ status: 'pending_review'
306
+ };
307
+
308
+ baseline.change_requests.push(changeRequest);
309
+ saveBaseline(baseline, baselineFile);
310
+
311
+ return {
312
+ success: true,
313
+ impact: impactLevel,
314
+ change_request_id: changeRequest.id,
315
+ artifact: relPath,
316
+ assessment: {
317
+ change_type: removedIds.length > 0 ? 'breaking' : 'additive',
318
+ added_requirements: addedIds,
319
+ removed_requirements: removedIds,
320
+ unchanged_requirements: unchangedIds.length,
321
+ downstream_artifacts: downstreamTypes,
322
+ requires_re_approval: impactLevel === 'critical' || impactLevel === 'high'
323
+ }
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Get baseline status.
329
+ *
330
+ * @param {object} [options]
331
+ * @returns {object}
332
+ */
333
+ function getBaselineStatus(options = {}) {
334
+ const baselineFile = options.baselineFile || DEFAULT_BASELINE_FILE;
335
+ const baseline = loadBaseline(baselineFile);
336
+
337
+ return {
338
+ success: true,
339
+ frozen: baseline.frozen,
340
+ total_baselines: baseline.baselines.length,
341
+ total_change_requests: baseline.change_requests.length,
342
+ pending_change_requests: baseline.change_requests.filter(cr => cr.status === 'pending_review').length,
343
+ latest_baseline: baseline.baselines.length > 0 ? baseline.baselines[baseline.baselines.length - 1] : null
344
+ };
345
+ }
346
+
347
+ module.exports = {
348
+ defaultBaseline,
349
+ loadBaseline,
350
+ saveBaseline,
351
+ hashContent,
352
+ extractRequirementIds,
353
+ freezeBaseline,
354
+ checkBaseline,
355
+ assessImpact,
356
+ getBaselineStatus,
357
+ ARTIFACT_TYPES
358
+ };