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,104 @@
1
+ /**
2
+ * artifact-comparison.js — Artifact Comparison Across Versions (Item 78)
3
+ *
4
+ * Show what changed in business terms and why it matters.
5
+ *
6
+ * Usage:
7
+ * node bin/lib/artifact-comparison.js compare|history [options]
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const CHANGE_CATEGORIES = ['added', 'removed', 'modified', 'moved'];
16
+
17
+ function compareArtifacts(contentA, contentB, options = {}) {
18
+ if (!contentA || !contentB) return { success: false, error: 'Both contents are required' };
19
+
20
+ const linesA = contentA.split('\n');
21
+ const linesB = contentB.split('\n');
22
+
23
+ const sectionsA = extractSections(linesA);
24
+ const sectionsB = extractSections(linesB);
25
+
26
+ const changes = [];
27
+ const allKeys = new Set([...Object.keys(sectionsA), ...Object.keys(sectionsB)]);
28
+
29
+ for (const key of allKeys) {
30
+ if (!sectionsA[key]) {
31
+ changes.push({ section: key, type: 'added', summary: `New section: ${key}` });
32
+ } else if (!sectionsB[key]) {
33
+ changes.push({ section: key, type: 'removed', summary: `Removed section: ${key}` });
34
+ } else if (sectionsA[key] !== sectionsB[key]) {
35
+ changes.push({ section: key, type: 'modified', summary: `Modified section: ${key}` });
36
+ }
37
+ }
38
+
39
+ return {
40
+ success: true,
41
+ total_changes: changes.length,
42
+ changes,
43
+ lines_before: linesA.length,
44
+ lines_after: linesB.length,
45
+ line_diff: linesB.length - linesA.length
46
+ };
47
+ }
48
+
49
+ function extractSections(lines) {
50
+ const sections = {};
51
+ let current = '_header';
52
+ let content = [];
53
+
54
+ for (const line of lines) {
55
+ if (line.match(/^#+\s/)) {
56
+ if (content.length > 0) sections[current] = content.join('\n');
57
+ current = line.replace(/^#+\s+/, '').trim();
58
+ content = [];
59
+ } else {
60
+ content.push(line);
61
+ }
62
+ }
63
+ if (content.length > 0) sections[current] = content.join('\n');
64
+
65
+ return sections;
66
+ }
67
+
68
+ function compareFiles(fileA, fileB, options = {}) {
69
+ if (!fs.existsSync(fileA)) return { success: false, error: `File not found: ${fileA}` };
70
+ if (!fs.existsSync(fileB)) return { success: false, error: `File not found: ${fileB}` };
71
+
72
+ const contentA = fs.readFileSync(fileA, 'utf8');
73
+ const contentB = fs.readFileSync(fileB, 'utf8');
74
+
75
+ const result = compareArtifacts(contentA, contentB, options);
76
+ result.file_a = fileA;
77
+ result.file_b = fileB;
78
+ return result;
79
+ }
80
+
81
+ function getArtifactHistory(root, artifactName, options = {}) {
82
+ const archiveDir = path.join(root, '.jumpstart', 'archive');
83
+ const versions = [];
84
+
85
+ if (fs.existsSync(archiveDir)) {
86
+ for (const f of fs.readdirSync(archiveDir)) {
87
+ if (f.includes(artifactName)) {
88
+ versions.push({ file: f, path: path.join(archiveDir, f) });
89
+ }
90
+ }
91
+ }
92
+
93
+ const currentPath = path.join(root, 'specs', artifactName);
94
+ if (fs.existsSync(currentPath)) {
95
+ versions.push({ file: artifactName, path: currentPath, current: true });
96
+ }
97
+
98
+ return { success: true, artifact: artifactName, versions: versions.length, history: versions };
99
+ }
100
+
101
+ module.exports = {
102
+ compareArtifacts, compareFiles, getArtifactHistory, extractSections,
103
+ CHANGE_CATEGORIES
104
+ };
@@ -0,0 +1,157 @@
1
+ /**
2
+ * ast-edit-engine.js — AST-Aware Edit Engine (Item 42)
3
+ *
4
+ * Make structure-safe changes instead of relying heavily
5
+ * on plain text edits.
6
+ *
7
+ * Usage:
8
+ * node bin/lib/ast-edit-engine.js analyze|validate [options]
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const SUPPORTED_LANGUAGES = ['javascript', 'typescript', 'json', 'yaml', 'markdown'];
17
+
18
+ const STRUCTURE_PATTERNS = {
19
+ javascript: {
20
+ function_decl: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
21
+ class_decl: /^(?:export\s+)?class\s+(\w+)/gm,
22
+ const_export: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/gm,
23
+ module_exports: /module\.exports\s*=\s*\{([^}]+)\}/gm,
24
+ import_stmt: /^(?:import|const\s+\{[^}]+\}\s*=\s*require)/gm
25
+ },
26
+ typescript: {
27
+ interface_decl: /^(?:export\s+)?interface\s+(\w+)/gm,
28
+ type_decl: /^(?:export\s+)?type\s+(\w+)/gm,
29
+ function_decl: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/gm,
30
+ class_decl: /^(?:export\s+)?class\s+(\w+)/gm
31
+ }
32
+ };
33
+
34
+ /**
35
+ * Detect language from file extension.
36
+ *
37
+ * @param {string} filePath
38
+ * @returns {string|null}
39
+ */
40
+ function detectLanguage(filePath) {
41
+ const ext = path.extname(filePath).toLowerCase();
42
+ const map = {
43
+ '.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
44
+ '.ts': 'typescript', '.tsx': 'typescript',
45
+ '.json': 'json', '.yaml': 'yaml', '.yml': 'yaml',
46
+ '.md': 'markdown'
47
+ };
48
+ return map[ext] || null;
49
+ }
50
+
51
+ /**
52
+ * Analyze file structure.
53
+ *
54
+ * @param {string} filePath
55
+ * @param {object} [options]
56
+ * @returns {object}
57
+ */
58
+ function analyzeStructure(filePath, options = {}) {
59
+ if (!fs.existsSync(filePath)) {
60
+ return { success: false, error: `File not found: ${filePath}` };
61
+ }
62
+
63
+ const content = fs.readFileSync(filePath, 'utf8');
64
+ const language = options.language || detectLanguage(filePath);
65
+
66
+ if (!language) {
67
+ return { success: false, error: 'Unable to detect language' };
68
+ }
69
+
70
+ const symbols = [];
71
+ const patterns = STRUCTURE_PATTERNS[language] || {};
72
+
73
+ for (const [type, pattern] of Object.entries(patterns)) {
74
+ const regex = new RegExp(pattern.source, pattern.flags);
75
+ let match;
76
+ while ((match = regex.exec(content)) !== null) {
77
+ const line = content.substring(0, match.index).split('\n').length;
78
+ symbols.push({
79
+ type: type.replace(/_/g, ' '),
80
+ name: match[1] || match[0].trim(),
81
+ line
82
+ });
83
+ }
84
+ }
85
+
86
+ const lines = content.split('\n');
87
+
88
+ return {
89
+ success: true,
90
+ file: filePath,
91
+ language,
92
+ total_lines: lines.length,
93
+ symbols,
94
+ symbol_count: symbols.length,
95
+ has_exports: /(?:module\.exports|export\s)/m.test(content),
96
+ has_imports: /(?:require\(|import\s)/m.test(content)
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Validate that an edit is structure-safe.
102
+ *
103
+ * @param {string} filePath
104
+ * @param {string} oldStr
105
+ * @param {string} newStr
106
+ * @param {object} [options]
107
+ * @returns {object}
108
+ */
109
+ function validateEdit(filePath, oldStr, newStr, options = {}) {
110
+ if (!fs.existsSync(filePath)) {
111
+ return { success: false, error: `File not found: ${filePath}` };
112
+ }
113
+
114
+ const content = fs.readFileSync(filePath, 'utf8');
115
+ const occurrences = content.split(oldStr).length - 1;
116
+
117
+ if (occurrences === 0) {
118
+ return { success: false, safe: false, error: 'Old string not found in file' };
119
+ }
120
+ if (occurrences > 1) {
121
+ return { success: false, safe: false, error: `Old string found ${occurrences} times — ambiguous edit` };
122
+ }
123
+
124
+ // Check bracket balance
125
+ const beforeBrackets = countBrackets(content);
126
+ const after = content.replace(oldStr, newStr);
127
+ const afterBrackets = countBrackets(after);
128
+
129
+ const bracketsSafe = beforeBrackets.curly === afterBrackets.curly &&
130
+ beforeBrackets.square === afterBrackets.square &&
131
+ beforeBrackets.paren === afterBrackets.paren;
132
+
133
+ return {
134
+ success: true,
135
+ safe: bracketsSafe,
136
+ unique_match: true,
137
+ bracket_balance: bracketsSafe ? 'preserved' : 'changed',
138
+ warnings: bracketsSafe ? [] : ['Edit changes bracket balance — verify manually']
139
+ };
140
+ }
141
+
142
+ function countBrackets(content) {
143
+ return {
144
+ curly: (content.match(/\{/g) || []).length - (content.match(/\}/g) || []).length,
145
+ square: (content.match(/\[/g) || []).length - (content.match(/\]/g) || []).length,
146
+ paren: (content.match(/\(/g) || []).length - (content.match(/\)/g) || []).length
147
+ };
148
+ }
149
+
150
+ module.exports = {
151
+ detectLanguage,
152
+ analyzeStructure,
153
+ validateEdit,
154
+ countBrackets,
155
+ SUPPORTED_LANGUAGES,
156
+ STRUCTURE_PATTERNS
157
+ };
@@ -0,0 +1,338 @@
1
+ /**
2
+ * backlog-sync.js — Native Backlog Synchronization
3
+ *
4
+ * Push epics, features, user stories, and tasks to
5
+ * Azure DevOps, Jira, or GitHub Issues.
6
+ *
7
+ * Config: .jumpstart/backlog-sync.json
8
+ *
9
+ * Usage:
10
+ * node bin/lib/backlog-sync.js extract|preview|export|status [options]
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const DEFAULT_SYNC_FILE = path.join('.jumpstart', 'state', 'backlog-sync.json');
19
+ const SUPPORTED_TARGETS = ['github', 'jira', 'azure-devops'];
20
+
21
+ /**
22
+ * Default sync state.
23
+ * @returns {object}
24
+ */
25
+ function defaultSyncState() {
26
+ return {
27
+ version: '1.0.0',
28
+ created_at: new Date().toISOString(),
29
+ last_sync: null,
30
+ target: null,
31
+ synced_items: [],
32
+ export_history: []
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Load sync state from disk.
38
+ * @param {string} [syncFile]
39
+ * @returns {object}
40
+ */
41
+ function loadSyncState(syncFile) {
42
+ const filePath = syncFile || DEFAULT_SYNC_FILE;
43
+ if (!fs.existsSync(filePath)) {
44
+ return defaultSyncState();
45
+ }
46
+ try {
47
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
48
+ } catch {
49
+ return defaultSyncState();
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Save sync state to disk.
55
+ * @param {object} state
56
+ * @param {string} [syncFile]
57
+ */
58
+ function saveSyncState(state, syncFile) {
59
+ const filePath = syncFile || DEFAULT_SYNC_FILE;
60
+ const dir = path.dirname(filePath);
61
+ if (!fs.existsSync(dir)) {
62
+ fs.mkdirSync(dir, { recursive: true });
63
+ }
64
+ state.last_sync = new Date().toISOString();
65
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
66
+ }
67
+
68
+ /**
69
+ * Extract epics from PRD content.
70
+ * @param {string} content - PRD markdown content.
71
+ * @returns {object[]}
72
+ */
73
+ function extractEpics(content) {
74
+ const epics = [];
75
+ const lines = content.split('\n');
76
+ let currentEpic = null;
77
+
78
+ for (const line of lines) {
79
+ // Match epic headers like "### Epic 1: ...", "## E01: ...", "### E01 — ..."
80
+ const epicMatch = line.match(/^#{2,4}\s+(?:Epic\s+)?(\d+|E\d+)[:\s—–-]+\s*(.+)$/i);
81
+ if (epicMatch) {
82
+ if (currentEpic) epics.push(currentEpic);
83
+ currentEpic = {
84
+ id: epicMatch[1].startsWith('E') ? epicMatch[1] : `E${epicMatch[1].padStart(2, '0')}`,
85
+ title: epicMatch[2].trim(),
86
+ stories: [],
87
+ type: 'epic'
88
+ };
89
+ continue;
90
+ }
91
+
92
+ // Match stories like "- **E01-S01**: ...", "- E01-S01: ...", "#### Story: ..."
93
+ if (currentEpic) {
94
+ const storyMatch = line.match(/(?:^[-*]\s+\*{0,2})(E\d+-S\d+)(?:\*{0,2})[:\s—–-]+\s*(.+)/i);
95
+ if (storyMatch) {
96
+ currentEpic.stories.push({
97
+ id: storyMatch[1],
98
+ title: storyMatch[2].trim().replace(/\*{1,2}/g, ''),
99
+ type: 'story',
100
+ epic_id: currentEpic.id
101
+ });
102
+ }
103
+ }
104
+ }
105
+ if (currentEpic) epics.push(currentEpic);
106
+
107
+ return epics;
108
+ }
109
+
110
+ /**
111
+ * Extract tasks from implementation plan content.
112
+ * @param {string} content - Implementation plan markdown content.
113
+ * @returns {object[]}
114
+ */
115
+ function extractTasks(content) {
116
+ const tasks = [];
117
+ const lines = content.split('\n');
118
+
119
+ for (const line of lines) {
120
+ // Match task patterns like "- M01-T01: ...", "### M01-T01 — ...", "- **M01-T01**: ..."
121
+ const taskMatch = line.match(/(?:^#{2,4}\s+|^[-*]\s+\*{0,2})(M\d+-T\d+)(?:\*{0,2})[:\s—–-]+\s*(.+)/i);
122
+ if (taskMatch) {
123
+ const stories = [];
124
+ // Extract story references from the line
125
+ const storyRefs = line.match(/E\d+-S\d+/g) || [];
126
+ tasks.push({
127
+ id: taskMatch[1],
128
+ title: taskMatch[2].trim().replace(/\*{1,2}/g, ''),
129
+ type: 'task',
130
+ story_refs: [...new Set(storyRefs)]
131
+ });
132
+ }
133
+ }
134
+
135
+ return tasks;
136
+ }
137
+
138
+ /**
139
+ * Extract all backlog items from PRD and implementation plan.
140
+ *
141
+ * @param {string} root - Project root.
142
+ * @param {object} [options]
143
+ * @returns {object}
144
+ */
145
+ function extractBacklog(root, options = {}) {
146
+ const prdPath = options.prdPath || path.join(root, 'specs', 'prd.md');
147
+ const planPath = options.planPath || path.join(root, 'specs', 'implementation-plan.md');
148
+
149
+ const items = { epics: [], stories: [], tasks: [] };
150
+
151
+ if (fs.existsSync(prdPath)) {
152
+ const prdContent = fs.readFileSync(prdPath, 'utf8');
153
+ const epics = extractEpics(prdContent);
154
+ items.epics = epics;
155
+ for (const epic of epics) {
156
+ items.stories.push(...epic.stories);
157
+ }
158
+ }
159
+
160
+ if (fs.existsSync(planPath)) {
161
+ const planContent = fs.readFileSync(planPath, 'utf8');
162
+ items.tasks = extractTasks(planContent);
163
+ }
164
+
165
+ return {
166
+ success: true,
167
+ epics: items.epics.length,
168
+ stories: items.stories.length,
169
+ tasks: items.tasks.length,
170
+ items
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Format backlog items for a specific target platform.
176
+ *
177
+ * @param {object} backlog - Extracted backlog from extractBacklog.
178
+ * @param {string} target - Target platform (github, jira, azure-devops).
179
+ * @param {object} [options]
180
+ * @returns {object}
181
+ */
182
+ function formatForTarget(backlog, target, options = {}) {
183
+ if (!SUPPORTED_TARGETS.includes(target)) {
184
+ return { success: false, error: `Unsupported target: ${target}. Supported: ${SUPPORTED_TARGETS.join(', ')}` };
185
+ }
186
+
187
+ const formatted = [];
188
+
189
+ if (target === 'github') {
190
+ // GitHub Issues format
191
+ for (const epic of (backlog.items || backlog).epics || []) {
192
+ formatted.push({
193
+ type: 'issue',
194
+ title: `[Epic] ${epic.id}: ${epic.title}`,
195
+ labels: ['epic', 'jumpstart'],
196
+ body: `## Epic: ${epic.title}\n\nID: ${epic.id}\nStories: ${epic.stories.length}\n\n_Auto-generated by JumpStart_`
197
+ });
198
+ for (const story of epic.stories) {
199
+ formatted.push({
200
+ type: 'issue',
201
+ title: `[Story] ${story.id}: ${story.title}`,
202
+ labels: ['user-story', 'jumpstart', epic.id.toLowerCase()],
203
+ body: `## User Story: ${story.title}\n\nID: ${story.id}\nEpic: ${epic.id}\n\n_Auto-generated by JumpStart_`
204
+ });
205
+ }
206
+ }
207
+ for (const task of (backlog.items || backlog).tasks || []) {
208
+ formatted.push({
209
+ type: 'issue',
210
+ title: `[Task] ${task.id}: ${task.title}`,
211
+ labels: ['task', 'jumpstart'],
212
+ body: `## Task: ${task.title}\n\nID: ${task.id}\nStories: ${task.story_refs.join(', ') || 'none'}\n\n_Auto-generated by JumpStart_`
213
+ });
214
+ }
215
+ } else if (target === 'jira') {
216
+ // Jira format
217
+ for (const epic of (backlog.items || backlog).epics || []) {
218
+ formatted.push({
219
+ issueType: 'Epic',
220
+ summary: `${epic.id}: ${epic.title}`,
221
+ labels: ['jumpstart'],
222
+ customFields: { jumpstart_id: epic.id }
223
+ });
224
+ for (const story of epic.stories) {
225
+ formatted.push({
226
+ issueType: 'Story',
227
+ summary: `${story.id}: ${story.title}`,
228
+ labels: ['jumpstart'],
229
+ epicLink: epic.id,
230
+ customFields: { jumpstart_id: story.id }
231
+ });
232
+ }
233
+ }
234
+ for (const task of (backlog.items || backlog).tasks || []) {
235
+ formatted.push({
236
+ issueType: 'Task',
237
+ summary: `${task.id}: ${task.title}`,
238
+ labels: ['jumpstart'],
239
+ customFields: { jumpstart_id: task.id, story_refs: task.story_refs }
240
+ });
241
+ }
242
+ } else if (target === 'azure-devops') {
243
+ // Azure DevOps format
244
+ for (const epic of (backlog.items || backlog).epics || []) {
245
+ formatted.push({
246
+ workItemType: 'Epic',
247
+ title: `${epic.id}: ${epic.title}`,
248
+ tags: 'jumpstart',
249
+ fields: { 'Custom.JumpStartId': epic.id }
250
+ });
251
+ for (const story of epic.stories) {
252
+ formatted.push({
253
+ workItemType: 'User Story',
254
+ title: `${story.id}: ${story.title}`,
255
+ tags: 'jumpstart',
256
+ parentId: epic.id,
257
+ fields: { 'Custom.JumpStartId': story.id }
258
+ });
259
+ }
260
+ }
261
+ for (const task of (backlog.items || backlog).tasks || []) {
262
+ formatted.push({
263
+ workItemType: 'Task',
264
+ title: `${task.id}: ${task.title}`,
265
+ tags: 'jumpstart',
266
+ fields: { 'Custom.JumpStartId': task.id }
267
+ });
268
+ }
269
+ }
270
+
271
+ return {
272
+ success: true,
273
+ target,
274
+ total_items: formatted.length,
275
+ items: formatted
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Export backlog as a JSON file suitable for import.
281
+ *
282
+ * @param {string} root - Project root.
283
+ * @param {string} target - Target platform.
284
+ * @param {object} [options]
285
+ * @returns {object}
286
+ */
287
+ function exportBacklog(root, target, options = {}) {
288
+ const backlog = extractBacklog(root, options);
289
+ if (!backlog.success) return backlog;
290
+
291
+ const formatted = formatForTarget(backlog, target, options);
292
+ if (!formatted.success) return formatted;
293
+
294
+ const outputPath = options.output || path.join(root, '.jumpstart', 'exports', `backlog-${target}.json`);
295
+ const dir = path.dirname(outputPath);
296
+ if (!fs.existsSync(dir)) {
297
+ fs.mkdirSync(dir, { recursive: true });
298
+ }
299
+
300
+ const exportData = {
301
+ exported_at: new Date().toISOString(),
302
+ target,
303
+ source: 'jumpstart',
304
+ ...formatted
305
+ };
306
+
307
+ fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2) + '\n', 'utf8');
308
+
309
+ // Record in sync state
310
+ const syncFile = options.syncFile || path.join(root, DEFAULT_SYNC_FILE);
311
+ const state = loadSyncState(syncFile);
312
+ state.export_history.push({
313
+ exported_at: exportData.exported_at,
314
+ target,
315
+ items: formatted.total_items,
316
+ output: path.relative(root, outputPath)
317
+ });
318
+ saveSyncState(state, syncFile);
319
+
320
+ return {
321
+ success: true,
322
+ target,
323
+ items_exported: formatted.total_items,
324
+ output: outputPath
325
+ };
326
+ }
327
+
328
+ module.exports = {
329
+ defaultSyncState,
330
+ loadSyncState,
331
+ saveSyncState,
332
+ extractEpics,
333
+ extractTasks,
334
+ extractBacklog,
335
+ formatForTarget,
336
+ exportBacklog,
337
+ SUPPORTED_TARGETS
338
+ };