specweave 0.21.2 → 0.21.3

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 (553) hide show
  1. package/CLAUDE.md +232 -5
  2. package/dist/plugins/specweave-github/lib/IssueStateManager.d.ts +98 -0
  3. package/dist/plugins/specweave-github/lib/IssueStateManager.d.ts.map +1 -0
  4. package/dist/plugins/specweave-github/lib/IssueStateManager.js +146 -0
  5. package/dist/plugins/specweave-github/lib/IssueStateManager.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -1
  7. package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +6 -0
  8. package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -1
  9. package/dist/src/cli/commands/check-hooks.d.ts +11 -0
  10. package/dist/src/cli/commands/check-hooks.d.ts.map +1 -0
  11. package/dist/src/cli/commands/check-hooks.js +144 -0
  12. package/dist/src/cli/commands/check-hooks.js.map +1 -0
  13. package/dist/src/cli/commands/cicd-monitor.js +3 -3
  14. package/dist/src/cli/commands/cicd-monitor.js.map +1 -1
  15. package/dist/src/cli/commands/import-docs.js +2 -2
  16. package/dist/src/cli/commands/import-docs.js.map +1 -1
  17. package/dist/src/cli/commands/init-multiproject.js +4 -4
  18. package/dist/src/cli/commands/init-multiproject.js.map +1 -1
  19. package/dist/src/cli/commands/migrate-to-multiproject.js +3 -3
  20. package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
  21. package/dist/src/cli/commands/plan/agent-invoker.d.ts +79 -0
  22. package/dist/src/cli/commands/plan/agent-invoker.d.ts.map +1 -0
  23. package/dist/src/cli/commands/plan/agent-invoker.js +383 -0
  24. package/dist/src/cli/commands/plan/agent-invoker.js.map +1 -0
  25. package/dist/src/cli/commands/plan/increment-detector.d.ts +27 -0
  26. package/dist/src/cli/commands/plan/increment-detector.d.ts.map +1 -0
  27. package/dist/src/cli/commands/plan/increment-detector.js +159 -0
  28. package/dist/src/cli/commands/plan/increment-detector.js.map +1 -0
  29. package/dist/src/cli/commands/plan/plan-orchestrator.d.ts +45 -0
  30. package/dist/src/cli/commands/plan/plan-orchestrator.d.ts.map +1 -0
  31. package/dist/src/cli/commands/plan/plan-orchestrator.js +229 -0
  32. package/dist/src/cli/commands/plan/plan-orchestrator.js.map +1 -0
  33. package/dist/src/cli/commands/plan/plan-validator.d.ts +36 -0
  34. package/dist/src/cli/commands/plan/plan-validator.d.ts.map +1 -0
  35. package/dist/src/cli/commands/plan/plan-validator.js +174 -0
  36. package/dist/src/cli/commands/plan/plan-validator.js.map +1 -0
  37. package/dist/src/cli/commands/plan/types.d.ts +170 -0
  38. package/dist/src/cli/commands/plan/types.d.ts.map +1 -0
  39. package/dist/src/cli/commands/plan/types.js +42 -0
  40. package/dist/src/cli/commands/plan/types.js.map +1 -0
  41. package/dist/src/cli/commands/plan-command.d.ts +16 -0
  42. package/dist/src/cli/commands/plan-command.d.ts.map +1 -0
  43. package/dist/src/cli/commands/plan-command.js +127 -0
  44. package/dist/src/cli/commands/plan-command.js.map +1 -0
  45. package/dist/src/cli/commands/switch-project.js +3 -3
  46. package/dist/src/cli/commands/switch-project.js.map +1 -1
  47. package/dist/src/cli/commands/validate-parent-repo.js +1 -1
  48. package/dist/src/cli/commands/validate-parent-repo.js.map +1 -1
  49. package/dist/src/config/ConfigManager.d.ts +69 -0
  50. package/dist/src/config/ConfigManager.d.ts.map +1 -0
  51. package/dist/src/config/ConfigManager.js +130 -0
  52. package/dist/src/config/ConfigManager.js.map +1 -0
  53. package/dist/src/config/types.d.ts +1357 -0
  54. package/dist/src/config/types.d.ts.map +1 -0
  55. package/dist/src/config/types.js +67 -0
  56. package/dist/src/config/types.js.map +1 -0
  57. package/dist/src/core/brownfield/importer.d.ts +1 -1
  58. package/dist/src/core/brownfield/importer.d.ts.map +1 -1
  59. package/dist/src/core/brownfield/importer.js +3 -3
  60. package/dist/src/core/brownfield/importer.js.map +1 -1
  61. package/dist/src/core/cicd/config-loader.d.ts +1 -1
  62. package/dist/src/core/cicd/config-loader.d.ts.map +1 -1
  63. package/dist/src/core/cicd/index.d.ts +6 -6
  64. package/dist/src/core/cicd/index.d.ts.map +1 -1
  65. package/dist/src/core/cicd/index.js +6 -6
  66. package/dist/src/core/cicd/index.js.map +1 -1
  67. package/dist/src/core/cicd/monitor-service.d.ts +3 -3
  68. package/dist/src/core/cicd/monitor-service.d.ts.map +1 -1
  69. package/dist/src/core/cicd/monitor-service.js +3 -3
  70. package/dist/src/core/cicd/monitor-service.js.map +1 -1
  71. package/dist/src/core/cicd/notifier.d.ts +1 -1
  72. package/dist/src/core/cicd/notifier.d.ts.map +1 -1
  73. package/dist/src/core/cicd/state-manager.d.ts +1 -1
  74. package/dist/src/core/cicd/state-manager.d.ts.map +1 -1
  75. package/dist/src/core/cicd/state-manager.js +1 -1
  76. package/dist/src/core/cicd/state-manager.js.map +1 -1
  77. package/dist/src/core/cicd/workflow-monitor.d.ts +1 -1
  78. package/dist/src/core/cicd/workflow-monitor.d.ts.map +1 -1
  79. package/dist/src/core/cicd/workflow-monitor.js +1 -1
  80. package/dist/src/core/cicd/workflow-monitor.js.map +1 -1
  81. package/dist/src/core/cost-tracker.d.ts +2 -2
  82. package/dist/src/core/cost-tracker.d.ts.map +1 -1
  83. package/dist/src/core/cost-tracker.js +1 -1
  84. package/dist/src/core/cost-tracker.js.map +1 -1
  85. package/dist/src/core/hooks/HealthReporter.d.ts +55 -0
  86. package/dist/src/core/hooks/HealthReporter.d.ts.map +1 -0
  87. package/dist/src/core/hooks/HealthReporter.js +268 -0
  88. package/dist/src/core/hooks/HealthReporter.js.map +1 -0
  89. package/dist/src/core/hooks/HookAutoFixer.d.ts +41 -0
  90. package/dist/src/core/hooks/HookAutoFixer.d.ts.map +1 -0
  91. package/dist/src/core/hooks/HookAutoFixer.js +222 -0
  92. package/dist/src/core/hooks/HookAutoFixer.js.map +1 -0
  93. package/dist/src/core/hooks/HookExecutor.d.ts +57 -0
  94. package/dist/src/core/hooks/HookExecutor.d.ts.map +1 -0
  95. package/dist/src/core/hooks/HookExecutor.js +287 -0
  96. package/dist/src/core/hooks/HookExecutor.js.map +1 -0
  97. package/dist/src/core/hooks/HookHealthChecker.d.ts +51 -0
  98. package/dist/src/core/hooks/HookHealthChecker.d.ts.map +1 -0
  99. package/dist/src/core/hooks/HookHealthChecker.js +212 -0
  100. package/dist/src/core/hooks/HookHealthChecker.js.map +1 -0
  101. package/dist/src/core/hooks/HookScanner.d.ts +65 -0
  102. package/dist/src/core/hooks/HookScanner.d.ts.map +1 -0
  103. package/dist/src/core/hooks/HookScanner.js +214 -0
  104. package/dist/src/core/hooks/HookScanner.js.map +1 -0
  105. package/dist/src/core/hooks/types.d.ts +281 -0
  106. package/dist/src/core/hooks/types.d.ts.map +1 -0
  107. package/dist/src/core/hooks/types.js +10 -0
  108. package/dist/src/core/hooks/types.js.map +1 -0
  109. package/dist/src/core/iac/index.d.ts +10 -0
  110. package/dist/src/core/iac/index.d.ts.map +1 -0
  111. package/dist/src/core/iac/index.js +11 -0
  112. package/dist/src/core/iac/index.js.map +1 -0
  113. package/dist/src/core/iac/template-engine.d.ts +77 -0
  114. package/dist/src/core/iac/template-engine.d.ts.map +1 -0
  115. package/dist/src/core/iac/template-engine.js +182 -0
  116. package/dist/src/core/iac/template-engine.js.map +1 -0
  117. package/dist/src/core/increment/ac-status-manager.d.ts +115 -0
  118. package/dist/src/core/increment/ac-status-manager.d.ts.map +1 -0
  119. package/dist/src/core/increment/ac-status-manager.js +343 -0
  120. package/dist/src/core/increment/ac-status-manager.js.map +1 -0
  121. package/dist/src/core/increment/auto-transition-manager.d.ts +60 -0
  122. package/dist/src/core/increment/auto-transition-manager.d.ts.map +1 -0
  123. package/dist/src/core/increment/auto-transition-manager.js +192 -0
  124. package/dist/src/core/increment/auto-transition-manager.js.map +1 -0
  125. package/dist/src/core/increment/limits.d.ts +1 -1
  126. package/dist/src/core/increment/limits.d.ts.map +1 -1
  127. package/dist/src/core/increment/limits.js +2 -2
  128. package/dist/src/core/increment/limits.js.map +1 -1
  129. package/dist/src/core/increment/spec-sync-manager.d.ts +177 -0
  130. package/dist/src/core/increment/spec-sync-manager.d.ts.map +1 -0
  131. package/dist/src/core/increment/spec-sync-manager.js +496 -0
  132. package/dist/src/core/increment/spec-sync-manager.js.map +1 -0
  133. package/dist/src/core/increment/status-auto-transition.d.ts +46 -0
  134. package/dist/src/core/increment/status-auto-transition.d.ts.map +1 -0
  135. package/dist/src/core/increment/status-auto-transition.js +178 -0
  136. package/dist/src/core/increment/status-auto-transition.js.map +1 -0
  137. package/dist/src/core/increment/task-state-manager.d.ts +75 -0
  138. package/dist/src/core/increment/task-state-manager.d.ts.map +1 -0
  139. package/dist/src/core/increment/task-state-manager.js +117 -0
  140. package/dist/src/core/increment/task-state-manager.js.map +1 -0
  141. package/dist/src/core/living-docs/CodeValidator.d.ts +68 -0
  142. package/dist/src/core/living-docs/CodeValidator.d.ts.map +1 -0
  143. package/dist/src/core/living-docs/CodeValidator.js +160 -0
  144. package/dist/src/core/living-docs/CodeValidator.js.map +1 -0
  145. package/dist/src/core/living-docs/CompletionPropagator.d.ts +84 -0
  146. package/dist/src/core/living-docs/CompletionPropagator.d.ts.map +1 -0
  147. package/dist/src/core/living-docs/CompletionPropagator.js +205 -0
  148. package/dist/src/core/living-docs/CompletionPropagator.js.map +1 -0
  149. package/dist/src/core/living-docs/ProjectDetector.d.ts +29 -0
  150. package/dist/src/core/living-docs/ProjectDetector.d.ts.map +1 -0
  151. package/dist/src/core/living-docs/ProjectDetector.js +94 -0
  152. package/dist/src/core/living-docs/ProjectDetector.js.map +1 -0
  153. package/dist/src/core/living-docs/SpecDistributor.d.ts +55 -0
  154. package/dist/src/core/living-docs/SpecDistributor.d.ts.map +1 -0
  155. package/dist/src/core/living-docs/SpecDistributor.js +216 -0
  156. package/dist/src/core/living-docs/SpecDistributor.js.map +1 -0
  157. package/dist/src/core/living-docs/ThreeLayerSyncManager.d.ts +116 -0
  158. package/dist/src/core/living-docs/ThreeLayerSyncManager.d.ts.map +1 -0
  159. package/dist/src/core/living-docs/ThreeLayerSyncManager.js +356 -0
  160. package/dist/src/core/living-docs/ThreeLayerSyncManager.js.map +1 -0
  161. package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -1
  162. package/dist/src/core/living-docs/hierarchy-mapper.js +2 -1
  163. package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
  164. package/dist/src/core/living-docs/index.d.ts +1 -1
  165. package/dist/src/core/living-docs/index.d.ts.map +1 -1
  166. package/dist/src/core/living-docs/index.js +5 -1
  167. package/dist/src/core/living-docs/index.js.map +1 -1
  168. package/dist/src/core/living-docs/types.d.ts +70 -300
  169. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  170. package/dist/src/core/living-docs/types.js +2 -10
  171. package/dist/src/core/living-docs/types.js.map +1 -1
  172. package/dist/src/core/project-manager.d.ts +1 -1
  173. package/dist/src/core/project-manager.d.ts.map +1 -1
  174. package/dist/src/core/project-manager.js +2 -2
  175. package/dist/src/core/project-manager.js.map +1 -1
  176. package/dist/src/core/qa/quality-gate-decider.d.ts +1 -1
  177. package/dist/src/core/qa/quality-gate-decider.d.ts.map +1 -1
  178. package/dist/src/core/qa/risk-calculator.d.ts +1 -1
  179. package/dist/src/core/qa/risk-calculator.d.ts.map +1 -1
  180. package/dist/src/core/repo-structure/setup-summary.d.ts +1 -1
  181. package/dist/src/core/repo-structure/setup-summary.d.ts.map +1 -1
  182. package/dist/src/core/rfc-generator-v2.d.ts +1 -1
  183. package/dist/src/core/rfc-generator-v2.d.ts.map +1 -1
  184. package/dist/src/core/rfc-generator-v2.js +1 -1
  185. package/dist/src/core/rfc-generator-v2.js.map +1 -1
  186. package/dist/src/core/serverless/context-detector.d.ts +12 -0
  187. package/dist/src/core/serverless/context-detector.d.ts.map +1 -0
  188. package/dist/src/core/serverless/context-detector.js +213 -0
  189. package/dist/src/core/serverless/context-detector.js.map +1 -0
  190. package/dist/src/core/serverless/cost-comparison.d.ts +73 -0
  191. package/dist/src/core/serverless/cost-comparison.d.ts.map +1 -0
  192. package/dist/src/core/serverless/cost-comparison.js +198 -0
  193. package/dist/src/core/serverless/cost-comparison.js.map +1 -0
  194. package/dist/src/core/serverless/cost-estimator.d.ts +69 -0
  195. package/dist/src/core/serverless/cost-estimator.d.ts.map +1 -0
  196. package/dist/src/core/serverless/cost-estimator.js +127 -0
  197. package/dist/src/core/serverless/cost-estimator.js.map +1 -0
  198. package/dist/src/core/serverless/cost-optimizer.d.ts +57 -0
  199. package/dist/src/core/serverless/cost-optimizer.d.ts.map +1 -0
  200. package/dist/src/core/serverless/cost-optimizer.js +221 -0
  201. package/dist/src/core/serverless/cost-optimizer.js.map +1 -0
  202. package/dist/src/core/serverless/index.d.ts +20 -0
  203. package/dist/src/core/serverless/index.d.ts.map +1 -0
  204. package/dist/src/core/serverless/index.js +26 -0
  205. package/dist/src/core/serverless/index.js.map +1 -0
  206. package/dist/src/core/serverless/learning-path-recommender.d.ts +152 -0
  207. package/dist/src/core/serverless/learning-path-recommender.d.ts.map +1 -0
  208. package/dist/src/core/serverless/learning-path-recommender.js +389 -0
  209. package/dist/src/core/serverless/learning-path-recommender.js.map +1 -0
  210. package/dist/src/core/serverless/platform-data-loader.d.ts +22 -0
  211. package/dist/src/core/serverless/platform-data-loader.d.ts.map +1 -0
  212. package/dist/src/core/serverless/platform-data-loader.js +49 -0
  213. package/dist/src/core/serverless/platform-data-loader.js.map +1 -0
  214. package/dist/src/core/serverless/platform-selector.d.ts +20 -0
  215. package/dist/src/core/serverless/platform-selector.d.ts.map +1 -0
  216. package/dist/src/core/serverless/platform-selector.js +279 -0
  217. package/dist/src/core/serverless/platform-selector.js.map +1 -0
  218. package/dist/src/core/serverless/recommendation-formatter.d.ts +24 -0
  219. package/dist/src/core/serverless/recommendation-formatter.d.ts.map +1 -0
  220. package/dist/src/core/serverless/recommendation-formatter.js +70 -0
  221. package/dist/src/core/serverless/recommendation-formatter.js.map +1 -0
  222. package/dist/src/core/serverless/suitability-analyzer.d.ts +22 -0
  223. package/dist/src/core/serverless/suitability-analyzer.d.ts.map +1 -0
  224. package/dist/src/core/serverless/suitability-analyzer.js +262 -0
  225. package/dist/src/core/serverless/suitability-analyzer.js.map +1 -0
  226. package/dist/src/core/serverless/types.d.ts +96 -0
  227. package/dist/src/core/serverless/types.d.ts.map +1 -0
  228. package/dist/src/core/serverless/types.js +5 -0
  229. package/dist/src/core/serverless/types.js.map +1 -0
  230. package/dist/src/core/sync/bidirectional-engine.d.ts +1 -1
  231. package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
  232. package/dist/src/core/sync/enhanced-content-builder.d.ts +1 -1
  233. package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -1
  234. package/dist/src/core/sync/profile-manager.d.ts +1 -1
  235. package/dist/src/core/sync/profile-manager.d.ts.map +1 -1
  236. package/dist/src/core/sync/profile-selector.d.ts +1 -1
  237. package/dist/src/core/sync/profile-selector.d.ts.map +1 -1
  238. package/dist/src/core/sync/profile-selector.js +1 -1
  239. package/dist/src/core/sync/profile-selector.js.map +1 -1
  240. package/dist/src/core/sync/project-context.d.ts +1 -1
  241. package/dist/src/core/sync/project-context.d.ts.map +1 -1
  242. package/dist/src/core/sync/rate-limiter.d.ts +1 -1
  243. package/dist/src/core/sync/rate-limiter.d.ts.map +1 -1
  244. package/dist/src/core/sync/status-sync-engine.d.ts +2 -2
  245. package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -1
  246. package/dist/src/core/sync/status-sync-engine.js +2 -2
  247. package/dist/src/core/sync/status-sync-engine.js.map +1 -1
  248. package/dist/src/core/sync/time-range-selector.d.ts +1 -1
  249. package/dist/src/core/sync/time-range-selector.d.ts.map +1 -1
  250. package/dist/src/core/sync/time-range-selector.js +1 -1
  251. package/dist/src/core/sync/time-range-selector.js.map +1 -1
  252. package/dist/src/core/types/increment-metadata.d.ts +27 -0
  253. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  254. package/dist/src/core/types/increment-metadata.js +45 -1
  255. package/dist/src/core/types/increment-metadata.js.map +1 -1
  256. package/dist/src/core/types/sync-profile.d.ts +2 -0
  257. package/dist/src/core/types/sync-profile.d.ts.map +1 -1
  258. package/dist/src/core/types/sync-profile.js.map +1 -1
  259. package/dist/src/core/validation/increment-structure-validator.d.ts +47 -0
  260. package/dist/src/core/validation/increment-structure-validator.d.ts.map +1 -0
  261. package/dist/src/core/validation/increment-structure-validator.js +187 -0
  262. package/dist/src/core/validation/increment-structure-validator.js.map +1 -0
  263. package/dist/src/core/validation/three-file-validator.d.ts +82 -0
  264. package/dist/src/core/validation/three-file-validator.d.ts.map +1 -0
  265. package/dist/src/core/validation/three-file-validator.js +320 -0
  266. package/dist/src/core/validation/three-file-validator.js.map +1 -0
  267. package/dist/src/core/workflow/phase-detector.d.ts +103 -0
  268. package/dist/src/core/workflow/phase-detector.d.ts.map +1 -0
  269. package/dist/src/core/workflow/phase-detector.js +704 -0
  270. package/dist/src/core/workflow/phase-detector.js.map +1 -0
  271. package/dist/src/core/workflow/types.d.ts +153 -0
  272. package/dist/src/core/workflow/types.d.ts.map +1 -0
  273. package/dist/src/core/workflow/types.js +47 -0
  274. package/dist/src/core/workflow/types.js.map +1 -0
  275. package/dist/src/init/InitFlow.d.ts +37 -0
  276. package/dist/src/init/InitFlow.d.ts.map +1 -0
  277. package/dist/src/init/InitFlow.js +209 -0
  278. package/dist/src/init/InitFlow.js.map +1 -0
  279. package/dist/src/init/architecture/ArchitectureDecisionEngine.d.ts +107 -0
  280. package/dist/src/init/architecture/ArchitectureDecisionEngine.d.ts.map +1 -0
  281. package/dist/src/init/architecture/ArchitectureDecisionEngine.js +405 -0
  282. package/dist/src/init/architecture/ArchitectureDecisionEngine.js.map +1 -0
  283. package/dist/src/init/architecture/CloudCreditsDatabase.d.ts +11 -0
  284. package/dist/src/init/architecture/CloudCreditsDatabase.d.ts.map +1 -0
  285. package/dist/src/init/architecture/CloudCreditsDatabase.js +92 -0
  286. package/dist/src/init/architecture/CloudCreditsDatabase.js.map +1 -0
  287. package/dist/src/init/architecture/types.d.ts +251 -0
  288. package/dist/src/init/architecture/types.d.ts.map +1 -0
  289. package/dist/src/init/architecture/types.js +54 -0
  290. package/dist/src/init/architecture/types.js.map +1 -0
  291. package/dist/src/init/compliance/ComplianceDetector.d.ts +17 -0
  292. package/dist/src/init/compliance/ComplianceDetector.d.ts.map +1 -0
  293. package/dist/src/init/compliance/ComplianceDetector.js +64 -0
  294. package/dist/src/init/compliance/ComplianceDetector.js.map +1 -0
  295. package/dist/src/init/compliance/standards-database.d.ts +48 -0
  296. package/dist/src/init/compliance/standards-database.d.ts.map +1 -0
  297. package/dist/src/init/compliance/standards-database.js +506 -0
  298. package/dist/src/init/compliance/standards-database.js.map +1 -0
  299. package/dist/src/init/compliance/types.d.ts +91 -0
  300. package/dist/src/init/compliance/types.d.ts.map +1 -0
  301. package/dist/src/init/compliance/types.js +43 -0
  302. package/dist/src/init/compliance/types.js.map +1 -0
  303. package/dist/src/init/repo/GitHubAPIClient.d.ts +51 -0
  304. package/dist/src/init/repo/GitHubAPIClient.d.ts.map +1 -0
  305. package/dist/src/init/repo/GitHubAPIClient.js +144 -0
  306. package/dist/src/init/repo/GitHubAPIClient.js.map +1 -0
  307. package/dist/src/init/repo/RepositorySelector.d.ts +45 -0
  308. package/dist/src/init/repo/RepositorySelector.d.ts.map +1 -0
  309. package/dist/src/init/repo/RepositorySelector.js +106 -0
  310. package/dist/src/init/repo/RepositorySelector.js.map +1 -0
  311. package/dist/src/init/repo/types.d.ts +95 -0
  312. package/dist/src/init/repo/types.d.ts.map +1 -0
  313. package/dist/src/init/repo/types.js +25 -0
  314. package/dist/src/init/repo/types.js.map +1 -0
  315. package/dist/src/init/research/CompetitorAnalyzer.d.ts +79 -0
  316. package/dist/src/init/research/CompetitorAnalyzer.d.ts.map +1 -0
  317. package/dist/src/init/research/CompetitorAnalyzer.js +265 -0
  318. package/dist/src/init/research/CompetitorAnalyzer.js.map +1 -0
  319. package/dist/src/init/research/MarketDetector.d.ts +62 -0
  320. package/dist/src/init/research/MarketDetector.d.ts.map +1 -0
  321. package/dist/src/init/research/MarketDetector.js +247 -0
  322. package/dist/src/init/research/MarketDetector.js.map +1 -0
  323. package/dist/src/init/research/OpportunityScorer.d.ts +58 -0
  324. package/dist/src/init/research/OpportunityScorer.d.ts.map +1 -0
  325. package/dist/src/init/research/OpportunityScorer.js +194 -0
  326. package/dist/src/init/research/OpportunityScorer.js.map +1 -0
  327. package/dist/src/init/research/QuestionGenerator.d.ts +68 -0
  328. package/dist/src/init/research/QuestionGenerator.d.ts.map +1 -0
  329. package/dist/src/init/research/QuestionGenerator.js +244 -0
  330. package/dist/src/init/research/QuestionGenerator.js.map +1 -0
  331. package/dist/src/init/research/ReportGenerator.d.ts +36 -0
  332. package/dist/src/init/research/ReportGenerator.d.ts.map +1 -0
  333. package/dist/src/init/research/ReportGenerator.js +125 -0
  334. package/dist/src/init/research/ReportGenerator.js.map +1 -0
  335. package/dist/src/init/research/VisionAnalyzer.d.ts +129 -0
  336. package/dist/src/init/research/VisionAnalyzer.d.ts.map +1 -0
  337. package/dist/src/init/research/VisionAnalyzer.js +212 -0
  338. package/dist/src/init/research/VisionAnalyzer.js.map +1 -0
  339. package/dist/src/init/research/keyword-extractor.d.ts +78 -0
  340. package/dist/src/init/research/keyword-extractor.d.ts.map +1 -0
  341. package/dist/src/init/research/keyword-extractor.js +230 -0
  342. package/dist/src/init/research/keyword-extractor.js.map +1 -0
  343. package/dist/src/init/research/src/config/ConfigManager.d.ts +14 -0
  344. package/dist/src/init/research/src/config/ConfigManager.d.ts.map +1 -0
  345. package/dist/src/init/research/src/config/ConfigManager.js +45 -0
  346. package/dist/src/init/research/src/config/ConfigManager.js.map +1 -0
  347. package/dist/src/init/research/src/config/types.d.ts +102 -0
  348. package/dist/src/init/research/src/config/types.d.ts.map +1 -0
  349. package/dist/src/init/research/src/config/types.js +24 -0
  350. package/dist/src/init/research/src/config/types.js.map +1 -0
  351. package/dist/src/init/research/types.d.ts +183 -0
  352. package/dist/src/init/research/types.d.ts.map +1 -0
  353. package/dist/src/init/research/types.js +65 -0
  354. package/dist/src/init/research/types.js.map +1 -0
  355. package/dist/src/init/team/ServerlessSavingsCalculator.d.ts +136 -0
  356. package/dist/src/init/team/ServerlessSavingsCalculator.d.ts.map +1 -0
  357. package/dist/src/init/team/ServerlessSavingsCalculator.js +360 -0
  358. package/dist/src/init/team/ServerlessSavingsCalculator.js.map +1 -0
  359. package/dist/src/init/team/TeamRecommender.d.ts +122 -0
  360. package/dist/src/init/team/TeamRecommender.d.ts.map +1 -0
  361. package/dist/src/init/team/TeamRecommender.js +405 -0
  362. package/dist/src/init/team/TeamRecommender.js.map +1 -0
  363. package/dist/src/init/team/types.d.ts +95 -0
  364. package/dist/src/init/team/types.d.ts.map +1 -0
  365. package/dist/src/init/team/types.js +23 -0
  366. package/dist/src/init/team/types.js.map +1 -0
  367. package/dist/src/integrations/jira/jira-mapper.d.ts +1 -1
  368. package/dist/src/integrations/jira/jira-mapper.d.ts.map +1 -1
  369. package/dist/src/types/cost-tracking.d.ts +1 -1
  370. package/dist/src/types/cost-tracking.d.ts.map +1 -1
  371. package/dist/src/utils/cost-reporter.d.ts +2 -2
  372. package/dist/src/utils/cost-reporter.d.ts.map +1 -1
  373. package/dist/src/utils/docs-preview/config-generator.d.ts +1 -1
  374. package/dist/src/utils/docs-preview/config-generator.d.ts.map +1 -1
  375. package/dist/src/utils/docs-preview/config-generator.js +1 -1
  376. package/dist/src/utils/docs-preview/docusaurus-setup.d.ts +1 -1
  377. package/dist/src/utils/docs-preview/docusaurus-setup.d.ts.map +1 -1
  378. package/dist/src/utils/docs-preview/docusaurus-setup.js +4 -4
  379. package/dist/src/utils/docs-preview/docusaurus-setup.js.map +1 -1
  380. package/dist/src/utils/docs-preview/index.d.ts +6 -6
  381. package/dist/src/utils/docs-preview/index.d.ts.map +1 -1
  382. package/dist/src/utils/docs-preview/index.js +6 -6
  383. package/dist/src/utils/docs-preview/index.js.map +1 -1
  384. package/dist/src/utils/docs-preview/package-installer.d.ts +1 -1
  385. package/dist/src/utils/docs-preview/package-installer.d.ts.map +1 -1
  386. package/dist/src/utils/docs-preview/package-installer.js +1 -1
  387. package/dist/src/utils/docs-preview/package-installer.js.map +1 -1
  388. package/dist/src/utils/docs-preview/server-manager.d.ts +1 -1
  389. package/dist/src/utils/docs-preview/server-manager.d.ts.map +1 -1
  390. package/dist/src/utils/docs-preview/server-manager.js +1 -1
  391. package/dist/src/utils/docs-preview/server-manager.js.map +1 -1
  392. package/dist/src/utils/docs-preview/sidebar-builder.d.ts +1 -1
  393. package/dist/src/utils/docs-preview/sidebar-builder.d.ts.map +1 -1
  394. package/dist/src/utils/generate-skills-index.d.ts +1 -1
  395. package/dist/src/utils/generate-skills-index.js +1 -1
  396. package/dist/src/utils/project-detection.js +1 -1
  397. package/dist/src/utils/project-detection.js.map +1 -1
  398. package/package.json +9 -3
  399. package/plugins/specweave/agents/architect/AGENT.md +605 -0
  400. package/plugins/specweave/agents/infrastructure/AGENT.md +760 -0
  401. package/plugins/specweave/agents/pm/AGENT.md +14 -13
  402. package/plugins/specweave/commands/specweave-check-hooks.md +186 -0
  403. package/plugins/specweave/commands/specweave-plan.md +151 -0
  404. package/plugins/specweave/commands/specweave-sync-acs.md +342 -0
  405. package/plugins/specweave/commands/specweave-validate.md +60 -11
  406. package/plugins/specweave/hooks/lib/update-status-line.sh +8 -4
  407. package/plugins/specweave/hooks/post-increment-change.sh +4 -0
  408. package/plugins/specweave/hooks/post-increment-completion.sh +7 -1
  409. package/plugins/specweave/hooks/post-increment-planning.sh +4 -0
  410. package/plugins/specweave/hooks/post-increment-status-change.sh +4 -0
  411. package/plugins/specweave/hooks/user-prompt-submit.sh +78 -0
  412. package/plugins/specweave/iac-templates/aws-lambda/README.md.hbs +280 -0
  413. package/plugins/specweave/iac-templates/aws-lambda/defaults.json +118 -0
  414. package/plugins/specweave/iac-templates/aws-lambda/environments/dev.defaults.json +46 -0
  415. package/plugins/specweave/iac-templates/aws-lambda/environments/prod.defaults.json +67 -0
  416. package/plugins/specweave/iac-templates/aws-lambda/environments/staging.defaults.json +47 -0
  417. package/plugins/specweave/iac-templates/aws-lambda/main.tf.hbs +241 -0
  418. package/plugins/specweave/iac-templates/aws-lambda/outputs.tf.hbs +61 -0
  419. package/plugins/specweave/iac-templates/aws-lambda/provider.tf.hbs +15 -0
  420. package/plugins/specweave/iac-templates/aws-lambda/variables.tf.hbs +88 -0
  421. package/plugins/specweave/iac-templates/azure-functions/README.md.hbs +315 -0
  422. package/plugins/specweave/iac-templates/azure-functions/defaults.json +65 -0
  423. package/plugins/specweave/iac-templates/azure-functions/environments/dev.defaults.json +30 -0
  424. package/plugins/specweave/iac-templates/azure-functions/environments/prod.defaults.json +34 -0
  425. package/plugins/specweave/iac-templates/azure-functions/environments/staging.defaults.json +31 -0
  426. package/plugins/specweave/iac-templates/azure-functions/iam.tf.hbs +34 -0
  427. package/plugins/specweave/iac-templates/azure-functions/main.tf.hbs +247 -0
  428. package/plugins/specweave/iac-templates/azure-functions/outputs.tf.hbs +72 -0
  429. package/plugins/specweave/iac-templates/azure-functions/provider.tf.hbs +14 -0
  430. package/plugins/specweave/iac-templates/azure-functions/variables.tf.hbs +64 -0
  431. package/plugins/specweave/iac-templates/firebase/README.md.hbs +487 -0
  432. package/plugins/specweave/iac-templates/firebase/defaults.json +55 -0
  433. package/plugins/specweave/iac-templates/firebase/environments/dev.defaults.json +44 -0
  434. package/plugins/specweave/iac-templates/firebase/environments/prod.defaults.json +52 -0
  435. package/plugins/specweave/iac-templates/firebase/environments/staging.defaults.json +43 -0
  436. package/plugins/specweave/iac-templates/firebase/iam.tf.hbs +75 -0
  437. package/plugins/specweave/iac-templates/firebase/main.tf.hbs +297 -0
  438. package/plugins/specweave/iac-templates/firebase/outputs.tf.hbs +67 -0
  439. package/plugins/specweave/iac-templates/firebase/provider.tf.hbs +26 -0
  440. package/plugins/specweave/iac-templates/firebase/variables.tf.hbs +68 -0
  441. package/plugins/specweave/iac-templates/gcp-cloud-functions/README.md.hbs +330 -0
  442. package/plugins/specweave/iac-templates/gcp-cloud-functions/defaults.json +69 -0
  443. package/plugins/specweave/iac-templates/gcp-cloud-functions/environments/dev.defaults.json +33 -0
  444. package/plugins/specweave/iac-templates/gcp-cloud-functions/environments/prod.defaults.json +40 -0
  445. package/plugins/specweave/iac-templates/gcp-cloud-functions/environments/staging.defaults.json +33 -0
  446. package/plugins/specweave/iac-templates/gcp-cloud-functions/iam.tf.hbs +54 -0
  447. package/plugins/specweave/iac-templates/gcp-cloud-functions/main.tf.hbs +211 -0
  448. package/plugins/specweave/iac-templates/gcp-cloud-functions/outputs.tf.hbs +44 -0
  449. package/plugins/specweave/iac-templates/gcp-cloud-functions/provider.tf.hbs +14 -0
  450. package/plugins/specweave/iac-templates/gcp-cloud-functions/variables.tf.hbs +82 -0
  451. package/plugins/specweave/iac-templates/supabase/README.md.hbs +534 -0
  452. package/plugins/specweave/iac-templates/supabase/defaults.json +69 -0
  453. package/plugins/specweave/iac-templates/supabase/environments/dev.defaults.json +55 -0
  454. package/plugins/specweave/iac-templates/supabase/environments/prod.defaults.json +75 -0
  455. package/plugins/specweave/iac-templates/supabase/environments/staging.defaults.json +54 -0
  456. package/plugins/specweave/iac-templates/supabase/iam.tf.hbs +146 -0
  457. package/plugins/specweave/iac-templates/supabase/main.tf.hbs +310 -0
  458. package/plugins/specweave/iac-templates/supabase/outputs.tf.hbs +74 -0
  459. package/plugins/specweave/iac-templates/supabase/provider.tf.hbs +19 -0
  460. package/plugins/specweave/iac-templates/supabase/variables.tf.hbs +78 -0
  461. package/plugins/specweave/knowledge-base/serverless/FRESHNESS.md +69 -0
  462. package/plugins/specweave/knowledge-base/serverless/learning-paths.json +865 -0
  463. package/plugins/specweave/knowledge-base/serverless/platforms/aws-lambda.json +41 -0
  464. package/plugins/specweave/knowledge-base/serverless/platforms/azure-functions.json +41 -0
  465. package/plugins/specweave/knowledge-base/serverless/platforms/firebase.json +46 -0
  466. package/plugins/specweave/knowledge-base/serverless/platforms/gcp-cloud-functions.json +41 -0
  467. package/plugins/specweave/knowledge-base/serverless/platforms/supabase.json +41 -0
  468. package/plugins/specweave/knowledge-base/serverless/schema.json +155 -0
  469. package/plugins/specweave/lib/hooks/auto-transition.js +50 -0
  470. package/plugins/specweave/lib/hooks/auto-transition.ts +84 -0
  471. package/plugins/specweave/lib/hooks/invoke-translator-skill.js +1 -1
  472. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +1 -1
  473. package/plugins/specweave/lib/hooks/sync-living-docs.js +4 -31
  474. package/plugins/specweave/lib/hooks/{sync-living-docs.ts → sync-living-docs.ts.DISABLED} +9 -48
  475. package/plugins/specweave/lib/hooks/translate-file.js +1 -1
  476. package/plugins/specweave/lib/hooks/translate-file.ts +1 -1
  477. package/plugins/specweave/lib/hooks/update-ac-status.js +24 -75
  478. package/plugins/specweave/lib/hooks/update-ac-status.ts +46 -135
  479. package/plugins/specweave/lib/hooks/update-tasks-md.js +115 -3
  480. package/plugins/specweave/lib/hooks/update-tasks-md.ts +182 -10
  481. package/plugins/specweave/lib/utils/validate-dev-setup.sh +133 -0
  482. package/plugins/specweave/skills/increment-planner/SKILL.md +25 -15
  483. package/plugins/specweave/skills/serverless-recommender/SKILL.md +368 -0
  484. package/plugins/specweave/templates/iac/aws-lambda/templates/iam.tf.hbs +137 -0
  485. package/plugins/specweave/templates/iac/aws-lambda/templates/main.tf.hbs +216 -0
  486. package/plugins/specweave-github/lib/IssueStateManager.js +117 -0
  487. package/plugins/specweave-github/lib/IssueStateManager.ts +231 -0
  488. package/plugins/specweave-github/lib/user-story-issue-builder.js +7 -0
  489. package/plugins/specweave-github/lib/user-story-issue-builder.ts +11 -0
  490. package/plugins/specweave-ui/.mcp.json +0 -10
  491. package/plugins/specweave-ui/README.md +26 -26
  492. package/plugins/specweave-ui/skills/browser-automation/SKILL.md +31 -18
  493. package/src/templates/tasks.md.template +51 -33
  494. package/dist/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts +0 -89
  495. package/dist/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts.map +0 -1
  496. package/dist/plugins/specweave/lib/hooks/git-diff-analyzer.js +0 -226
  497. package/dist/plugins/specweave/lib/hooks/git-diff-analyzer.js.map +0 -1
  498. package/dist/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts +0 -60
  499. package/dist/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts.map +0 -1
  500. package/dist/plugins/specweave/lib/hooks/invoke-translator-skill.js +0 -201
  501. package/dist/plugins/specweave/lib/hooks/invoke-translator-skill.js.map +0 -1
  502. package/dist/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts +0 -42
  503. package/dist/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts.map +0 -1
  504. package/dist/plugins/specweave/lib/hooks/prepare-reflection-context.js +0 -123
  505. package/dist/plugins/specweave/lib/hooks/prepare-reflection-context.js.map +0 -1
  506. package/dist/plugins/specweave/lib/hooks/reflection-config-loader.d.ts +0 -45
  507. package/dist/plugins/specweave/lib/hooks/reflection-config-loader.d.ts.map +0 -1
  508. package/dist/plugins/specweave/lib/hooks/reflection-config-loader.js +0 -132
  509. package/dist/plugins/specweave/lib/hooks/reflection-config-loader.js.map +0 -1
  510. package/dist/plugins/specweave/lib/hooks/reflection-parser.d.ts +0 -33
  511. package/dist/plugins/specweave/lib/hooks/reflection-parser.d.ts.map +0 -1
  512. package/dist/plugins/specweave/lib/hooks/reflection-parser.js +0 -419
  513. package/dist/plugins/specweave/lib/hooks/reflection-parser.js.map +0 -1
  514. package/dist/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts +0 -56
  515. package/dist/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts.map +0 -1
  516. package/dist/plugins/specweave/lib/hooks/reflection-prompt-builder.js +0 -239
  517. package/dist/plugins/specweave/lib/hooks/reflection-prompt-builder.js.map +0 -1
  518. package/dist/plugins/specweave/lib/hooks/reflection-storage.d.ts +0 -64
  519. package/dist/plugins/specweave/lib/hooks/reflection-storage.d.ts.map +0 -1
  520. package/dist/plugins/specweave/lib/hooks/reflection-storage.js +0 -305
  521. package/dist/plugins/specweave/lib/hooks/reflection-storage.js.map +0 -1
  522. package/dist/plugins/specweave/lib/hooks/run-self-reflection.d.ts +0 -43
  523. package/dist/plugins/specweave/lib/hooks/run-self-reflection.d.ts.map +0 -1
  524. package/dist/plugins/specweave/lib/hooks/run-self-reflection.js +0 -203
  525. package/dist/plugins/specweave/lib/hooks/run-self-reflection.js.map +0 -1
  526. package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts +0 -32
  527. package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +0 -1
  528. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +0 -405
  529. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +0 -1
  530. package/dist/plugins/specweave/lib/hooks/translate-file.d.ts +0 -59
  531. package/dist/plugins/specweave/lib/hooks/translate-file.d.ts.map +0 -1
  532. package/dist/plugins/specweave/lib/hooks/translate-file.js +0 -350
  533. package/dist/plugins/specweave/lib/hooks/translate-file.js.map +0 -1
  534. package/dist/plugins/specweave/lib/hooks/translate-living-docs.d.ts +0 -13
  535. package/dist/plugins/specweave/lib/hooks/translate-living-docs.d.ts.map +0 -1
  536. package/dist/plugins/specweave/lib/hooks/translate-living-docs.js +0 -175
  537. package/dist/plugins/specweave/lib/hooks/translate-living-docs.js.map +0 -1
  538. package/dist/plugins/specweave/lib/hooks/types/reflection-types.d.ts +0 -164
  539. package/dist/plugins/specweave/lib/hooks/types/reflection-types.d.ts.map +0 -1
  540. package/dist/plugins/specweave/lib/hooks/types/reflection-types.js +0 -73
  541. package/dist/plugins/specweave/lib/hooks/types/reflection-types.js.map +0 -1
  542. package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts +0 -21
  543. package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts.map +0 -1
  544. package/dist/plugins/specweave/lib/hooks/update-ac-status.js +0 -162
  545. package/dist/plugins/specweave/lib/hooks/update-ac-status.js.map +0 -1
  546. package/dist/plugins/specweave/lib/hooks/update-tasks-md.d.ts +0 -29
  547. package/dist/plugins/specweave/lib/hooks/update-tasks-md.d.ts.map +0 -1
  548. package/dist/plugins/specweave/lib/hooks/update-tasks-md.js +0 -203
  549. package/dist/plugins/specweave/lib/hooks/update-tasks-md.js.map +0 -1
  550. package/dist/src/core/living-docs/spec-distributor.d.ts +0 -180
  551. package/dist/src/core/living-docs/spec-distributor.d.ts.map +0 -1
  552. package/dist/src/core/living-docs/spec-distributor.js +0 -1840
  553. package/dist/src/core/living-docs/spec-distributor.js.map +0 -1
@@ -1,1840 +0,0 @@
1
- /**
2
- * SpecWeave Spec Distributor
3
- *
4
- * Distributes increment specs into hierarchical living docs structure:
5
- * - Epic (SPEC-###.md) - High-level summary
6
- * - User Stories (us-###.md) - Detailed requirements
7
- * - Tasks (tasks.md) - Implementation details (already exists)
8
- *
9
- * @author SpecWeave Team
10
- * @version 2.0.0
11
- */
12
- import fs from 'fs-extra';
13
- import path from 'path';
14
- import { HierarchyMapper } from './hierarchy-mapper.js';
15
- import { detectPrimaryGitHubRemote } from '../../utils/git-detector.js';
16
- import { ACProjectSpecificGenerator } from './ac-project-specific-generator.js';
17
- import { TaskProjectSpecificGenerator } from './task-project-specific-generator.js';
18
- /**
19
- * SpecDistributor - Distributes increment specs into hierarchical living docs
20
- */
21
- export class SpecDistributor {
22
- constructor(projectRoot, config) {
23
- this.githubRemote = null;
24
- this.projectRoot = projectRoot;
25
- // Detect project ID from config or use default
26
- const projectId = config?.specsDir?.includes('/specs/')
27
- ? config.specsDir.split('/specs/')[1]?.split('/')[0] || 'default'
28
- : 'default';
29
- this.config = {
30
- specsDir: path.join(projectRoot, '.specweave', 'docs', 'internal', 'specs', projectId),
31
- userStoriesSubdir: 'user-stories',
32
- epicFilePattern: 'SPEC-{id}-{name}.md',
33
- userStoryFilePattern: 'us-{id}-{name}.md',
34
- generateFrontmatter: true,
35
- generateCrossLinks: true,
36
- preserveOriginal: true,
37
- overwriteExisting: false,
38
- createBackups: true,
39
- ...config,
40
- };
41
- // Initialize HierarchyMapper
42
- this.hierarchyMapper = new HierarchyMapper(projectRoot);
43
- // Initialize AC Generator
44
- this.acGenerator = new ACProjectSpecificGenerator();
45
- // ✅ NEW: Initialize Task Generator
46
- this.taskGenerator = new TaskProjectSpecificGenerator(projectRoot);
47
- }
48
- /**
49
- * Distribute increment spec into universal hierarchy (epic + feature + user stories)
50
- */
51
- async distribute(incrementId) {
52
- const errors = [];
53
- const warnings = [];
54
- try {
55
- // Detect GitHub remote for generating GitHub URLs (if not already detected)
56
- if (!this.githubRemote) {
57
- this.githubRemote = await detectPrimaryGitHubRemote(this.projectRoot);
58
- }
59
- // Step 1: Parse increment spec (with epic and project detection)
60
- const parsed = await this.parseIncrementSpec(incrementId);
61
- // Step 1.5: Filter abandoned/archived increments (CRITICAL: prevent pollution)
62
- if (parsed.status === 'abandoned' || parsed.status === 'archived') {
63
- console.log(` ⚠️ Skipping distribution for ${parsed.status} increment: ${incrementId}`);
64
- console.log(` 💡 Living docs are NOT updated for ${parsed.status} increments`);
65
- return {
66
- epic: {},
67
- userStories: [],
68
- incrementId,
69
- specId: `${parsed.status.toUpperCase()}-${incrementId}`,
70
- totalStories: 0,
71
- totalFiles: 0,
72
- epicPath: '',
73
- userStoryPaths: [],
74
- success: false,
75
- errors: [`Increment ${incrementId} is ${parsed.status} - skipping living docs distribution`],
76
- warnings: [`To preserve history, archived content remains in the increment folder`],
77
- };
78
- }
79
- // Step 2: Check if we should create epics (skip for GitHub)
80
- const config = await this.hierarchyMapper.getSpecweaveConfig();
81
- const syncProvider = config.sync?.provider || config.sync?.activeProfile ?
82
- config.sync.profiles?.[config.sync.activeProfile]?.provider : undefined;
83
- const shouldCreateEpic = syncProvider !== 'github'; // GitHub doesn't have epics concept
84
- // Step 3: Detect epic mapping (OPTIONAL - skip for GitHub)
85
- let epicMapping = null;
86
- if (shouldCreateEpic) {
87
- epicMapping = await this.hierarchyMapper.detectEpicMapping(incrementId);
88
- if (epicMapping) {
89
- console.log(` 🎯 Mapped to epic ${epicMapping.epicId} (confidence: ${epicMapping.confidence}%)`);
90
- }
91
- }
92
- else {
93
- console.log(` ⚡ Skipping epic creation (GitHub integration)`);
94
- }
95
- // Step 4: Detect feature mapping (REQUIRED)
96
- console.log(` 🔍 Detecting feature folder for ${incrementId}...`);
97
- const featureMapping = await this.hierarchyMapper.detectFeatureMapping(incrementId);
98
- console.log(` 📁 Mapped to feature ${featureMapping.featureId} (confidence: ${featureMapping.confidence}%, method: ${featureMapping.detectionMethod})`);
99
- console.log(` 📦 Projects: ${featureMapping.projects.join(', ')}`);
100
- // Step 5: Classify content by project (NEW)
101
- const storiesByProject = await this.classifyContentByProject(parsed, featureMapping);
102
- console.log(` 📊 Classified ${parsed.userStories.length} user stories across ${storiesByProject.size} project(s)`);
103
- // Step 6: Generate epic file (OPTIONAL - only if not GitHub)
104
- const epicFile = epicMapping && shouldCreateEpic ? await this.generateEpicFile(parsed, epicMapping, featureMapping) : null;
105
- // Step 7: Generate feature file (REQUIRED)
106
- const featureFile = await this.generateFeatureFile(parsed, featureMapping, storiesByProject, incrementId);
107
- // Step 8: Generate project context files (REQUIRED)
108
- const projectContextFiles = await this.generateProjectContextFiles(featureMapping, parsed);
109
- // Step 9: Generate user story files by project (REQUIRED)
110
- const userStoryFilesByProject = await this.generateUserStoryFilesByProject(storiesByProject, featureMapping, incrementId);
111
- // Step 10: Write epic file (if exists and not GitHub)
112
- const epicPath = epicFile && epicMapping && shouldCreateEpic ? await this.writeEpicFile(epicFile, epicMapping) : null;
113
- // Step 11: Write feature file
114
- const featurePath = await this.writeFeatureFile(featureFile, featureMapping);
115
- // Step 12: Write project context files
116
- const contextPaths = await this.writeProjectContextFiles(projectContextFiles, featureMapping);
117
- // Step 13: Write user story files by project
118
- const storyPathsByProject = await this.writeUserStoryFilesByProject(userStoryFilesByProject, featureMapping, incrementId);
119
- // Step 14: Update tasks.md with bidirectional links (project-aware)
120
- await this.updateTasksWithUserStoryLinks(incrementId, userStoryFilesByProject, featureMapping);
121
- // Prepare legacy result (for backward compatibility)
122
- const allUserStories = Array.from(userStoryFilesByProject.values()).flat();
123
- const allStoryPaths = Array.from(storyPathsByProject.values()).flat();
124
- return {
125
- epic: featureFile, // Type compatibility hack
126
- userStories: allUserStories,
127
- incrementId,
128
- specId: featureFile.id,
129
- totalStories: allUserStories.length,
130
- totalFiles: 1 + (epicFile ? 1 : 0) + contextPaths.length + allStoryPaths.length,
131
- epicPath: featurePath, // Feature is the new "epic"
132
- userStoryPaths: allStoryPaths,
133
- success: true,
134
- errors,
135
- warnings,
136
- };
137
- }
138
- catch (error) {
139
- errors.push(`Distribution failed: ${error}`);
140
- throw new Error(`Failed to distribute increment ${incrementId}: ${error}`);
141
- }
142
- }
143
- /**
144
- * Parse increment spec into structured data
145
- */
146
- async parseIncrementSpec(incrementId) {
147
- const specPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'spec.md');
148
- if (!fs.existsSync(specPath)) {
149
- throw new Error(`Increment spec not found: ${specPath}`);
150
- }
151
- const content = await fs.readFile(specPath, 'utf-8');
152
- // Load external links from metadata.json (NEW: source of truth for external integrations)
153
- const externalLinks = await this.loadExternalLinks(incrementId);
154
- // Extract YAML frontmatter if present
155
- let frontmatter = {};
156
- let bodyContent = content;
157
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
158
- if (frontmatterMatch) {
159
- try {
160
- const yaml = await import('yaml');
161
- frontmatter = yaml.parse(frontmatterMatch[1]);
162
- bodyContent = content.slice(frontmatterMatch[0].length).trim();
163
- }
164
- catch (error) {
165
- console.warn(` ⚠️ Failed to parse frontmatter for ${incrementId}`);
166
- }
167
- }
168
- // Extract title (try multiple patterns)
169
- let title = frontmatter.title || '';
170
- if (!title) {
171
- // Pattern 1: # SPEC-####: Title
172
- const specTitleMatch = bodyContent.match(/^#\s+SPEC-\d+:\s+(.+)$/m);
173
- if (specTitleMatch)
174
- title = specTitleMatch[1].trim();
175
- }
176
- if (!title) {
177
- // Pattern 2: # Increment ####: Title
178
- const incTitleMatch = bodyContent.match(/^#\s+Increment\s+\d+:\s+(.+)$/m);
179
- if (incTitleMatch)
180
- title = incTitleMatch[1].trim();
181
- }
182
- if (!title) {
183
- // Pattern 3: First # heading
184
- const headingMatch = bodyContent.match(/^#\s+(.+)$/m);
185
- if (headingMatch) {
186
- title = headingMatch[1]
187
- .replace(/^SPEC-\d+:\s*/, '')
188
- .replace(/^Increment\s+\d+:\s*/, '')
189
- .trim();
190
- }
191
- }
192
- if (!title) {
193
- // Fallback: Use increment ID
194
- title = incrementId
195
- .replace(/^\d+-/, '')
196
- .split('-')
197
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
198
- .join(' ');
199
- }
200
- // Extract overview (try multiple sections)
201
- let overview = '';
202
- // Try "Quick Overview" or "Executive Summary"
203
- let overviewMatch = bodyContent.match(/##\s+(?:Quick\s+)?(?:Overview|Executive\s+Summary)\s*\n+([\s\S]*?)(?=\n\*\*Business Value\*\*:|\n##|\n---|$)/im);
204
- if (overviewMatch) {
205
- overview = overviewMatch[1].trim();
206
- // Remove any trailing fragments or incomplete sentences
207
- if (overview && !overview.endsWith('.') && !overview.endsWith('!') && !overview.endsWith('?')) {
208
- // Try to complete the sentence by looking ahead
209
- const extendedMatch = bodyContent.match(/##\s+(?:Quick\s+)?(?:Overview|Executive\s+Summary)\s*\n+([\s\S]{0,1000}?)(?:\n\n|\n\*\*|\n##|\n---|$)/im);
210
- if (extendedMatch) {
211
- const extended = extendedMatch[1].trim();
212
- // Find the last complete sentence
213
- const sentences = extended.match(/[^.!?]+[.!?]/g);
214
- if (sentences) {
215
- overview = sentences.join('').trim();
216
- }
217
- }
218
- }
219
- }
220
- if (!overview) {
221
- // Try "Overview" section
222
- overviewMatch = bodyContent.match(/##\s+Overview\s*\n+([\s\S]*?)(?=\n##|\n---|$)/im);
223
- if (overviewMatch) {
224
- const parts = overviewMatch[1].trim().split(/\n\n|\n---/);
225
- overview = parts[0].trim();
226
- }
227
- }
228
- if (!overview) {
229
- // Try "Problem Statement" section
230
- const problemMatch = bodyContent.match(/##\s+Problem\s+Statement\s*\n+([\s\S]*?)(?=\n##|\n---|$)/im);
231
- if (problemMatch) {
232
- // Take first paragraph only
233
- const firstPara = problemMatch[1].trim().split('\n\n')[0];
234
- overview = firstPara;
235
- }
236
- }
237
- if (!overview) {
238
- // Fallback: First paragraph after title
239
- const firstParaMatch = bodyContent.match(/^#[^\n]+\n+([^\n]+)/);
240
- if (firstParaMatch)
241
- overview = firstParaMatch[1].trim();
242
- }
243
- // Extract business value
244
- const businessValue = [];
245
- const businessValueMatch = content.match(/\*\*Business Value\*\*:\s*\n([\s\S]*?)(?=\n---|\n##|\Z)/i);
246
- if (businessValueMatch) {
247
- const lines = businessValueMatch[1].split('\n');
248
- for (const line of lines) {
249
- const bulletMatch = line.match(/^[-*]\s+\*\*(.+?)\*\*:\s+(.+)$/);
250
- if (bulletMatch) {
251
- businessValue.push(`${bulletMatch[1]}: ${bulletMatch[2]}`);
252
- }
253
- }
254
- }
255
- // Extract user stories
256
- const userStories = await this.extractUserStories(content, incrementId);
257
- // Extract priority, status, and created date from frontmatter
258
- const priority = frontmatter.priority || 'P1';
259
- const status = frontmatter.status || 'planning';
260
- const created = frontmatter.created || new Date().toISOString();
261
- return {
262
- incrementId,
263
- title,
264
- overview,
265
- businessValue,
266
- epic: frontmatter.epic, // Epic ID from frontmatter (optional)
267
- project: frontmatter.project, // Project ID from frontmatter (if present)
268
- projects: frontmatter.projects || [], // Multiple projects (for cross-project features)
269
- priority,
270
- status,
271
- created,
272
- userStories,
273
- externalLinks, // External links from metadata.json
274
- };
275
- }
276
- /**
277
- * Load external links from metadata.json (source of truth)
278
- */
279
- async loadExternalLinks(incrementId) {
280
- const metadataPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'metadata.json');
281
- const links = {};
282
- if (!fs.existsSync(metadataPath)) {
283
- return links;
284
- }
285
- try {
286
- const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
287
- // Extract GitHub link
288
- if (metadata.github?.url) {
289
- links.github = metadata.github.url;
290
- }
291
- // Extract JIRA link
292
- if (metadata.jira?.epicKey) {
293
- links.jira = metadata.jira.epicKey; // Store just the key, can be converted to URL in template
294
- }
295
- // Extract Azure DevOps link
296
- if (metadata.ado?.workItemUrl) {
297
- links.ado = metadata.ado.workItemUrl;
298
- }
299
- }
300
- catch (error) {
301
- console.warn(` ⚠️ Failed to parse metadata.json for ${incrementId}: ${error}`);
302
- }
303
- return links;
304
- }
305
- /**
306
- * Detect external tool mapping from metadata.json
307
- *
308
- * Maps SpecWeave hierarchy to external tool hierarchy with clear indicators.
309
- * Examples:
310
- * - SpecWeave Feature (FS-031) → JIRA Epic (AUTH-100)
311
- * - SpecWeave Feature (FS-031) → GitHub Issue (#45)
312
- * - SpecWeave Feature (FS-031) → ADO Feature (12345)
313
- */
314
- async detectExternalToolMapping(incrementId) {
315
- const metadataPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'metadata.json');
316
- if (!fs.existsSync(metadataPath)) {
317
- return undefined;
318
- }
319
- try {
320
- const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
321
- // Priority order: JIRA > ADO > GitHub
322
- // (JIRA has most divergence, GitHub has least)
323
- // JIRA mapping (Epic → Feature divergence)
324
- if (metadata.jira?.epicKey) {
325
- return {
326
- provider: 'jira',
327
- externalType: 'epic',
328
- externalId: metadata.jira.epicKey,
329
- externalUrl: metadata.jira.url || `https://jira.atlassian.com/browse/${metadata.jira.epicKey}`,
330
- hierarchyLevel: 'feature',
331
- mappingNote: 'JIRA Epic maps to SpecWeave Feature',
332
- };
333
- }
334
- // Azure DevOps mapping (Feature → Feature same level)
335
- if (metadata.ado?.workItemId) {
336
- return {
337
- provider: 'ado',
338
- externalType: 'feature',
339
- externalId: String(metadata.ado.workItemId),
340
- externalUrl: metadata.ado.workItemUrl,
341
- hierarchyLevel: 'feature',
342
- mappingNote: 'ADO Feature maps to SpecWeave Feature',
343
- };
344
- }
345
- // GitHub mapping - Features should NOT map to issues
346
- // According to Universal Hierarchy:
347
- // - SpecWeave Feature (FS-*) → GitHub Milestone
348
- // - SpecWeave User Story (US-*) → GitHub Issue
349
- // We should NOT show GitHub issue mapping at feature level
350
- // This will be handled at user story level instead
351
- }
352
- catch (error) {
353
- console.warn(` ⚠️ Failed to parse metadata.json for external mapping: ${error}`);
354
- }
355
- return undefined;
356
- }
357
- /**
358
- * Extract user stories from increment spec
359
- */
360
- async extractUserStories(content, incrementId) {
361
- const userStories = [];
362
- // Split content into individual user stories
363
- // Each story runs from "### US-XXX:" or "#### US-XXX:" until the next user story or end of content
364
- const storyParts = content.split(/(?=^#{3,4}\s+US-\d+:)/m);
365
- for (const part of storyParts) {
366
- // Skip empty parts or parts that don't start with US-
367
- if (!part.trim() || !part.match(/^#{3,4}\s+US-\d+:/))
368
- continue;
369
- // Extract the user story ID and title from the first line (supports both ### and ####)
370
- const headerMatch = part.match(/^#{3,4}\s+(US-\d+):\s+(.+?)$/m);
371
- if (!headerMatch)
372
- continue;
373
- const id = headerMatch[1];
374
- const title = headerMatch[2];
375
- // Get everything after the header line, removing trailing ---
376
- const storyContent = part
377
- .substring(part.indexOf('\n') + 1)
378
- .replace(/\n---\s*$/, '')
379
- .trim();
380
- // Extract description (As a... I want... So that...) - supports both inline and separate line formats
381
- const descMatch = storyContent.match(/\*\*As a\*\*\s+(.*?)\n\*\*I want\*\*\s+(.*?)\n\*\*So that\*\*\s+(.*?)(?:\n|$)/is);
382
- const description = descMatch
383
- ? `**As a** ${descMatch[1].trim()}\n**I want** ${descMatch[2].trim()}\n**So that** ${descMatch[3].trim()}`
384
- : '';
385
- // Extract acceptance criteria from the Acceptance Criteria section
386
- // Try full content first (works for #### headings), then story content (works for ### headings)
387
- let acceptanceCriteria = this.extractAcceptanceCriteriaFromSection(content, id);
388
- if (!acceptanceCriteria || acceptanceCriteria.length === 0) {
389
- acceptanceCriteria = this.extractAcceptanceCriteria(storyContent);
390
- }
391
- // Extract business rationale
392
- const rationaleMatch = storyContent.match(/\*\*Business Rationale\*\*:\s+(.*?)(?=\n\n---|\n\n##|$)/is);
393
- const businessRationale = rationaleMatch ? rationaleMatch[1].trim() : undefined;
394
- // Extract phase (look for phase header before this story in the original content)
395
- const storyIndex = content.indexOf(part);
396
- const phaseMatch = content.substring(0, storyIndex).match(/###\s+(Phase\s+\d+:.*?)$/im);
397
- const phase = phaseMatch ? phaseMatch[1] : undefined;
398
- // Determine status (assume complete if in completed increment)
399
- const status = 'complete'; // Can be enhanced later
400
- userStories.push({
401
- id,
402
- title,
403
- description,
404
- acceptanceCriteria,
405
- tasks: [], // Will be populated later
406
- businessRationale,
407
- status,
408
- phase,
409
- });
410
- }
411
- return userStories;
412
- }
413
- /**
414
- * Extract acceptance criteria from user story content
415
- */
416
- extractAcceptanceCriteria(content) {
417
- const criteria = [];
418
- // Pattern 1: - [x] **AC-US1-01**: Description (P1, testable)
419
- // Pattern 2: - [ ] **AC-001**: Description (P0, testable)
420
- // Pattern 3: - AC-001: Description (P0, testable) [without checkbox]
421
- // Also handles both ** bold ** and plain text formats
422
- const acPatterns = [
423
- // With checkbox and bold
424
- /^[-*]\s+\[([ x])\]\s+\*\*(AC-[^:]+)\*\*:\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
425
- // Without checkbox but with bold
426
- /^[-*]\s+\*\*(AC-[^:]+)\*\*:\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
427
- // Without checkbox and without bold (common in specs)
428
- /^[-*]\s+(AC-\d+):\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
429
- ];
430
- // Try each pattern
431
- for (const pattern of acPatterns) {
432
- const contentCopy = content; // Work with a copy for each pattern
433
- pattern.lastIndex = 0; // Reset regex state
434
- let match;
435
- while ((match = pattern.exec(contentCopy)) !== null) {
436
- let completed = false;
437
- let id = '';
438
- let description = '';
439
- let metaString = '';
440
- // Handle different match groups based on pattern
441
- if (match.length === 5) {
442
- // Pattern with checkbox: [1]=checkbox, [2]=id, [3]=desc, [4]=meta
443
- completed = match[1] === 'x';
444
- id = match[2];
445
- description = match[3];
446
- metaString = match[4] || '';
447
- }
448
- else if (match.length === 4) {
449
- // Pattern without checkbox: [1]=id, [2]=desc, [3]=meta
450
- completed = false; // Default to not completed
451
- id = match[1];
452
- description = match[2];
453
- metaString = match[3] || '';
454
- }
455
- // Skip if already added (avoid duplicates from multiple patterns)
456
- if (criteria.some(c => c.id === id)) {
457
- continue;
458
- }
459
- const priority = metaString.match(/P\d/)?.[0];
460
- const testable = metaString.includes('testable');
461
- criteria.push({
462
- id,
463
- description,
464
- priority,
465
- testable,
466
- completed,
467
- });
468
- }
469
- }
470
- return criteria;
471
- }
472
- /**
473
- * Extract acceptance criteria from the full spec content for a specific user story
474
- * This handles cases where AC is in a separate section after the user story
475
- */
476
- extractAcceptanceCriteriaFromSection(content, userStoryId) {
477
- const criteria = [];
478
- // Find the user story section and its acceptance criteria
479
- const usNumber = userStoryId.replace('US-', '');
480
- // Try multiple patterns to find AC section (supports both ### and #### headings)
481
- const patterns = [
482
- // Pattern 1: AC in "**Acceptance Criteria**:" section (most common - matches spec format)
483
- // This matches: **Acceptance Criteria**:\n- AC-XXX: description
484
- new RegExp(`####+?\\s+${userStoryId}:.*?\\n[\\s\\S]*?\\*\\*Acceptance Criteria\\*\\*:\\s*\\n+((?:[-*]\\s+AC-\\d+:[^\\n]+\\n?)+)`, 'im'),
485
- // Pattern 2: AC with checkboxes format
486
- new RegExp(`####+?\\s+${userStoryId}:.*?\\n[\\s\\S]*?\\*\\*Acceptance Criteria\\*\\*:\\s*\\n+((?:[-*]\\s+\\[[ x]\\]\\s+\\*\\*AC-[^\\n]+\\n?)+)`, 'im'),
487
- // Pattern 3: AC directly after user story (no separate section)
488
- new RegExp(`####+?\\s+${userStoryId}:.*?\\n[\\s\\S]*?(?:\\n\\n|\\n)+([-*]\\s+\\[[ x]\\]\\s+\\*\\*AC-US${usNumber}-\\d+[\\s\\S]*?)(?=\\n\\s*\\*\\*Business|\\n###|\\n---|$)`, 'im'),
489
- // Pattern 4: AC in a dedicated "### Acceptance Criteria" subsection
490
- new RegExp(`####+?\\s+${userStoryId}:.*?\\n[\\s\\S]*?####+?\\s+Acceptance Criteria\\s*\\n+([\\s\\S]*?)(?=\\n\\s*\\*\\*Business|\\n###|\\n---|$)`, 'im'),
491
- ];
492
- let acContent = '';
493
- for (const pattern of patterns) {
494
- const match = content.match(pattern);
495
- if (match && match[1]) {
496
- acContent = match[1];
497
- break;
498
- }
499
- }
500
- if (!acContent) {
501
- return criteria;
502
- }
503
- // Multiple patterns to handle different AC formats
504
- // Pattern 1: - [ ] **AC-USX-XX**: Description (P1, testable)
505
- // Pattern 2: - [x] **AC-001**: Description (P0, testable)
506
- // Pattern 3: - AC-001: Description (P0, testable) [without checkbox]
507
- const acPatterns = [
508
- // With checkbox and bold (specific user story format)
509
- /^[-*]\s+\[([ x])\]\s+\*\*(AC-US\d+-\d+)\*\*:\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
510
- // With checkbox and bold (general format)
511
- /^[-*]\s+\[([ x])\]\s+\*\*(AC-\d+)\*\*:\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
512
- // Without checkbox but with bold
513
- /^[-*]\s+\*\*(AC-\d+)\*\*:\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
514
- // Without checkbox and without bold (common in specs)
515
- /^[-*]\s+(AC-\d+):\s+(.+?)(?:\s+\(([^)]+)\))?$/gm,
516
- ];
517
- // Try each pattern
518
- for (const pattern of acPatterns) {
519
- pattern.lastIndex = 0; // Reset regex state
520
- let match;
521
- while ((match = pattern.exec(acContent)) !== null) {
522
- let completed = false;
523
- let id = '';
524
- let description = '';
525
- let metaString = '';
526
- // Handle different match groups based on pattern
527
- if (match.length === 5) {
528
- // Pattern with checkbox: [1]=checkbox, [2]=id, [3]=desc, [4]=meta
529
- completed = match[1] === 'x';
530
- id = match[2];
531
- description = match[3];
532
- metaString = match[4] || '';
533
- }
534
- else if (match.length === 4) {
535
- // Pattern without checkbox: [1]=id, [2]=desc, [3]=meta
536
- completed = false; // Default to not completed
537
- id = match[1];
538
- description = match[2];
539
- metaString = match[3] || '';
540
- }
541
- // Skip if already added (avoid duplicates from multiple patterns)
542
- if (criteria.some(c => c.id === id)) {
543
- continue;
544
- }
545
- const priority = metaString.match(/P\d/)?.[0];
546
- const testable = metaString.includes('testable');
547
- criteria.push({
548
- id,
549
- description,
550
- priority,
551
- testable,
552
- completed,
553
- });
554
- }
555
- }
556
- return criteria;
557
- }
558
- /**
559
- * Generate user story files
560
- */
561
- async generateUserStoryFiles(classified, incrementId) {
562
- const userStoryFiles = [];
563
- // Load tasks from tasks.md to extract task references
564
- const taskMap = await this.loadTaskReferences(incrementId);
565
- for (const userStory of classified.userStories) {
566
- // Find tasks that implement this user story
567
- const tasks = this.findTasksForUserStory(userStory.id, taskMap);
568
- // Find related user stories (same phase)
569
- const relatedStories = classified.userStories
570
- .filter((us) => us.id !== userStory.id && us.phase === userStory.phase)
571
- .map((us) => ({
572
- id: us.id,
573
- title: us.title,
574
- status: us.status,
575
- phase: us.phase,
576
- filePath: this.generateUserStoryFilename(us.id, us.title),
577
- }));
578
- userStoryFiles.push({
579
- id: userStory.id,
580
- epic: classified.epic.id,
581
- title: userStory.title,
582
- status: userStory.status,
583
- priority: userStory.priority,
584
- created: new Date().toISOString().split('T')[0],
585
- completed: userStory.status === 'complete' ? new Date().toISOString().split('T')[0] : undefined,
586
- description: userStory.description,
587
- acceptanceCriteria: userStory.acceptanceCriteria,
588
- implementation: {
589
- increment: incrementId,
590
- tasks,
591
- },
592
- businessRationale: userStory.businessRationale,
593
- relatedStories,
594
- phase: userStory.phase,
595
- });
596
- }
597
- return userStoryFiles;
598
- }
599
- /**
600
- * Load task references from tasks.md (with AC-ID extraction)
601
- */
602
- async loadTaskReferences(incrementId) {
603
- const tasksPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'tasks.md');
604
- const taskMap = new Map();
605
- if (!fs.existsSync(tasksPath)) {
606
- return taskMap;
607
- }
608
- const content = await fs.readFile(tasksPath, 'utf-8');
609
- // Pattern: ### T-001: Task Title followed by **AC**: field
610
- // Supports both ## and ### headings
611
- const taskPattern = /^##+ (T-\d+):\s+(.+?)$[\s\S]*?\*\*AC\*\*:\s*([^\n]+)?/gm;
612
- let match;
613
- while ((match = taskPattern.exec(content)) !== null) {
614
- const taskId = match[1]; // T-001
615
- const taskTitle = match[2];
616
- const acList = match[3] || ''; // AC-US1-01, AC-US1-02
617
- const anchor = this.generateTaskAnchor(taskId, taskTitle);
618
- // Extract AC-IDs from the list
619
- const acIds = [];
620
- const acPattern = /AC-US\d+-\d+/g;
621
- let acMatch;
622
- while ((acMatch = acPattern.exec(acList)) !== null) {
623
- acIds.push(acMatch[0]); // AC-US1-01
624
- }
625
- taskMap.set(taskId, {
626
- id: taskId,
627
- title: taskTitle,
628
- anchor,
629
- path: `../../../../../increments/${incrementId}/tasks.md${anchor}`,
630
- acIds,
631
- });
632
- }
633
- return taskMap;
634
- }
635
- /**
636
- * Find tasks that implement a user story (using AC-ID based filtering)
637
- */
638
- findTasksForUserStory(userStoryId, taskMap) {
639
- const tasks = [];
640
- // Extract US number from userStoryId (US-001 → "1")
641
- const usMatch = userStoryId.match(/US-(\d+)/);
642
- if (!usMatch) {
643
- return tasks;
644
- }
645
- const usNumber = parseInt(usMatch[1], 10); // 1
646
- // Find tasks that reference this user story's AC-IDs
647
- for (const task of taskMap.values()) {
648
- // Check if task has AC-IDs for this user story (AC-US1-01, AC-US1-02, etc.)
649
- const hasMatchingAC = task.acIds.some((acId) => {
650
- const acMatch = acId.match(/AC-US(\d+)-\d+/);
651
- return acMatch && parseInt(acMatch[1], 10) === usNumber;
652
- });
653
- if (hasMatchingAC) {
654
- tasks.push(task);
655
- }
656
- }
657
- return tasks;
658
- }
659
- /**
660
- * Generate task anchor
661
- */
662
- generateTaskAnchor(taskId, taskTitle) {
663
- const slug = taskTitle
664
- .toLowerCase()
665
- .replace(/[^a-z0-9]+/g, '-')
666
- .replace(/^-|-$/g, '');
667
- return `#${taskId.toLowerCase()}-${slug}`;
668
- }
669
- /**
670
- * Generate user story filename
671
- */
672
- generateUserStoryFilename(id, title) {
673
- const slug = title
674
- .toLowerCase()
675
- .replace(/[^a-z0-9]+/g, '-')
676
- .replace(/^-|-$/g, '');
677
- return `${id.toLowerCase()}-${slug}.md`;
678
- }
679
- /**
680
- * Format epic file as markdown
681
- */
682
- formatEpicFile(epic) {
683
- const lines = [];
684
- // Frontmatter
685
- lines.push('---');
686
- lines.push(`id: ${epic.id}`);
687
- lines.push(`title: "${epic.title}"`);
688
- lines.push(`type: epic`);
689
- lines.push(`status: ${epic.status}`);
690
- if (epic.priority)
691
- lines.push(`priority: ${epic.priority}`);
692
- lines.push(`created: ${epic.created}`);
693
- lines.push(`last_updated: ${epic.lastUpdated}`);
694
- lines.push('---');
695
- lines.push('');
696
- // Title
697
- lines.push(`# ${epic.id}: ${epic.title}`);
698
- lines.push('');
699
- lines.push(epic.overview);
700
- lines.push('');
701
- // Business Value
702
- if (epic.businessValue.length > 0) {
703
- lines.push('**Business Value**:');
704
- lines.push('');
705
- for (const value of epic.businessValue) {
706
- lines.push(`- **${value.split(':')[0]}**: ${value.split(':').slice(1).join(':').trim()}`);
707
- }
708
- lines.push('');
709
- }
710
- lines.push('---');
711
- lines.push('');
712
- // Implementation History
713
- if (epic.implementationHistory.length > 0) {
714
- lines.push('## Implementation History');
715
- lines.push('');
716
- lines.push('| Increment | User Stories | Status | Completion Date |');
717
- lines.push('|-----------|--------------|--------|----------------|');
718
- for (const entry of epic.implementationHistory) {
719
- const statusEmoji = entry.status === 'complete' ? '✅' : entry.status === 'in-progress' ? '⏳' : '📋';
720
- // Generate increment link (prefer GitHub URL for deployed version, fallback to relative path)
721
- let incrementLink;
722
- if (this.githubRemote && this.githubRemote.owner && this.githubRemote.repo) {
723
- // GitHub URL (works on deployed version)
724
- incrementLink = `[${entry.increment}](https://github.com/${this.githubRemote.owner}/${this.githubRemote.repo}/tree/develop/.specweave/increments/${entry.increment})`;
725
- }
726
- else {
727
- // Fallback to relative path (5 levels up, not 4!)
728
- incrementLink = `[${entry.increment}](../../../../../increments/${entry.increment}/tasks.md)`;
729
- }
730
- // Handle empty stories array (no user stories in spec)
731
- let storiesText;
732
- if (entry.stories.length === 0) {
733
- storiesText = 'Implementation only (no user stories)';
734
- }
735
- else if (entry.stories.length === 1) {
736
- storiesText = entry.stories[0];
737
- }
738
- else {
739
- const firstStory = entry.stories[0];
740
- const lastStory = entry.stories[entry.stories.length - 1];
741
- storiesText = `${firstStory} through ${lastStory} (all)`;
742
- }
743
- lines.push(`| ${incrementLink} | ${storiesText} | ${statusEmoji} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)} | ${entry.date || '-'} |`);
744
- }
745
- lines.push('');
746
- // Handle division by zero (no user stories)
747
- const progressText = epic.totalStories === 0
748
- ? 'No user stories (implementation only)'
749
- : `${epic.completedStories}/${epic.totalStories} user stories complete (${epic.overallProgress}%)`;
750
- lines.push(`**Overall Progress**: ${progressText}`);
751
- lines.push('');
752
- lines.push('---');
753
- lines.push('');
754
- }
755
- // User Stories
756
- lines.push('## User Stories');
757
- lines.push('');
758
- // Group by phase
759
- const phases = new Map();
760
- for (const story of epic.userStories) {
761
- const phase = story.phase || 'General';
762
- if (!phases.has(phase)) {
763
- phases.set(phase, []);
764
- }
765
- phases.get(phase).push(story);
766
- }
767
- for (const [phase, stories] of phases) {
768
- if (phase !== 'General') {
769
- lines.push(`### ${phase}`);
770
- lines.push('');
771
- }
772
- for (const story of stories) {
773
- const statusEmoji = story.status === 'complete' ? '✅' : story.status === 'in-progress' ? '⏳' : '📋';
774
- lines.push(`- [${story.id}: ${story.title}](${story.filePath}) - ${statusEmoji} ${story.status.charAt(0).toUpperCase() + story.status.slice(1)}`);
775
- }
776
- lines.push('');
777
- }
778
- lines.push('---');
779
- lines.push('');
780
- // External Tool Integration (only if there are actual links)
781
- const hasExternalLinks = epic.externalLinks.github || epic.externalLinks.jira || epic.externalLinks.ado;
782
- if (hasExternalLinks) {
783
- lines.push('## External Tool Integration');
784
- lines.push('');
785
- // Only show tools that have actual links
786
- if (epic.externalLinks.github) {
787
- lines.push(`**GitHub Project**: [${epic.externalLinks.github}](${epic.externalLinks.github})`);
788
- }
789
- if (epic.externalLinks.jira) {
790
- // Convert JIRA key to URL (if it's just a key like SCRUM-23)
791
- const jiraUrl = epic.externalLinks.jira.startsWith('http')
792
- ? epic.externalLinks.jira
793
- : `https://jira.atlassian.com/browse/${epic.externalLinks.jira}`;
794
- lines.push(`**JIRA Epic**: [${epic.externalLinks.jira}](${jiraUrl})`);
795
- }
796
- if (epic.externalLinks.ado) {
797
- lines.push(`**Azure DevOps**: [${epic.externalLinks.ado}](${epic.externalLinks.ado})`);
798
- }
799
- lines.push('');
800
- }
801
- return lines.join('\n');
802
- }
803
- /**
804
- * Format user story file as markdown
805
- */
806
- formatUserStoryFile(userStory) {
807
- const lines = [];
808
- // Frontmatter
809
- lines.push('---');
810
- lines.push(`id: ${userStory.id}`);
811
- lines.push(`feature: ${userStory.epic}`); // ✅ FIX: Use 'feature:' not 'epic:' (Universal Hierarchy)
812
- lines.push(`title: "${userStory.title}"`);
813
- lines.push(`status: ${userStory.status}`);
814
- if (userStory.priority)
815
- lines.push(`priority: ${userStory.priority}`);
816
- lines.push(`created: ${userStory.created}`);
817
- if (userStory.completed)
818
- lines.push(`completed: ${userStory.completed}`);
819
- // Add external tool mapping to frontmatter
820
- if (userStory.externalToolMapping) {
821
- lines.push('externalTool:');
822
- lines.push(` provider: ${userStory.externalToolMapping.provider}`);
823
- lines.push(` type: ${userStory.externalToolMapping.externalType}`);
824
- lines.push(` id: "${userStory.externalToolMapping.externalId}"`);
825
- lines.push(` url: "${userStory.externalToolMapping.externalUrl}"`);
826
- }
827
- lines.push('---');
828
- lines.push('');
829
- // Title with external tool indicator
830
- let titleLine = `# ${userStory.id}: ${userStory.title}`;
831
- if (userStory.externalToolMapping) {
832
- const provider = userStory.externalToolMapping.provider.toUpperCase();
833
- const type = userStory.externalToolMapping.externalType.charAt(0).toUpperCase() +
834
- userStory.externalToolMapping.externalType.slice(1);
835
- titleLine += ` (${provider} ${type}: ${userStory.externalToolMapping.externalId})`;
836
- }
837
- lines.push(titleLine);
838
- lines.push('');
839
- // Feature link (to _features folder)
840
- // User story is at: .specweave/docs/internal/specs/{project}/{FS-XXX}/us-*.md
841
- // Feature is at: .specweave/docs/internal/specs/_features/{FS-XXX}/FEATURE.md
842
- // From {project}/{FS-XXX}/ we need to go up 2 levels to specs/, then into _features/
843
- // Relative path: ../../_features/{FS-XXX}/FEATURE.md
844
- const featureRelativePath = `../../_features/${userStory.epic}/FEATURE.md`;
845
- lines.push(`**Feature**: [${userStory.epic}](${featureRelativePath})`);
846
- lines.push('');
847
- // Description
848
- if (userStory.description) {
849
- lines.push(userStory.description);
850
- lines.push('');
851
- }
852
- lines.push('---');
853
- lines.push('');
854
- // Acceptance Criteria
855
- lines.push('## Acceptance Criteria');
856
- lines.push('');
857
- if (userStory.acceptanceCriteria && userStory.acceptanceCriteria.length > 0) {
858
- for (const ac of userStory.acceptanceCriteria) {
859
- const checkbox = ac.completed ? '[x]' : '[ ]';
860
- const priorityText = ac.priority ? ` (${ac.priority}, testable)` : '';
861
- lines.push(`- ${checkbox} **${ac.id}**: ${ac.description}${priorityText}`);
862
- }
863
- }
864
- else {
865
- lines.push('*Acceptance criteria to be extracted from increment specification*');
866
- }
867
- lines.push('');
868
- lines.push('---');
869
- lines.push('');
870
- // ✅ NEW: Tasks section with checkboxes (project-specific)
871
- if (userStory.tasks && userStory.tasks.length > 0) {
872
- lines.push('## Tasks');
873
- lines.push('');
874
- for (const task of userStory.tasks) {
875
- const checkbox = task.completed ? '[x]' : '[ ]';
876
- lines.push(`- ${checkbox} **${task.id}**: ${task.title}`);
877
- }
878
- lines.push('');
879
- lines.push('> **Note**: Tasks are project-specific. For the full increment task list, see [increment tasks.md](../../../../../increments/${userStory.implementation.increment}/tasks.md)');
880
- lines.push('');
881
- lines.push('---');
882
- lines.push('');
883
- }
884
- // Implementation (source reference)
885
- lines.push('## Implementation');
886
- lines.push('');
887
- const incrementLink = userStory.implementation.tasks[0]?.path.replace(/#.*$/, '') || `../../../../../increments/${userStory.implementation.increment}/tasks.md`;
888
- lines.push(`**Increment**: [${userStory.implementation.increment}](${incrementLink})`);
889
- lines.push('');
890
- if (userStory.implementation.tasks.length > 0) {
891
- lines.push('**Source Tasks**: See increment tasks.md for complete task breakdown');
892
- lines.push('');
893
- }
894
- // Business Rationale
895
- if (userStory.businessRationale) {
896
- lines.push('---');
897
- lines.push('');
898
- lines.push('## Business Rationale');
899
- lines.push('');
900
- lines.push(userStory.businessRationale);
901
- lines.push('');
902
- }
903
- // Related User Stories
904
- if (userStory.relatedStories.length > 0) {
905
- lines.push('---');
906
- lines.push('');
907
- lines.push('## Related User Stories');
908
- lines.push('');
909
- for (const related of userStory.relatedStories) {
910
- lines.push(`- [${related.id}: ${related.title}](${related.filePath})`);
911
- }
912
- lines.push('');
913
- }
914
- lines.push('---');
915
- lines.push('');
916
- lines.push(`**Status**: ${userStory.status === 'complete' ? '✅' : '⏳'} ${userStory.status.charAt(0).toUpperCase() + userStory.status.slice(1)}`);
917
- if (userStory.completed) {
918
- lines.push(`**Completed**: ${userStory.completed}`);
919
- }
920
- lines.push('');
921
- return lines.join('\n');
922
- }
923
- /**
924
- * Update tasks.md with bidirectional links to user stories (CRITICAL!)
925
- *
926
- * This creates bidirectional traceability:
927
-
928
- /**
929
- * Classify content by project (NEW for universal hierarchy)
930
- * Split user stories across projects based on keywords and frontmatter
931
- */
932
- async classifyContentByProject(parsed, featureMapping) {
933
- const storiesByProject = new Map();
934
- for (const story of parsed.userStories) {
935
- // Detect which project(s) this story belongs to
936
- const storyProjects = await this.detectUserStoryProjects(story, featureMapping.projects);
937
- for (const project of storyProjects) {
938
- if (!storiesByProject.has(project)) {
939
- storiesByProject.set(project, []);
940
- }
941
- storiesByProject.get(project).push(story);
942
- }
943
- }
944
- // If no stories were classified to any project, add all to default/first project
945
- if (storiesByProject.size === 0 && parsed.userStories.length > 0) {
946
- const defaultProject = featureMapping.projects[0] || 'default';
947
- storiesByProject.set(defaultProject, parsed.userStories);
948
- }
949
- return storiesByProject;
950
- }
951
- /**
952
- * Detect which projects a user story belongs to
953
- */
954
- async detectUserStoryProjects(story, availableProjects) {
955
- // If only one project available, return it
956
- if (availableProjects.length === 1) {
957
- return availableProjects;
958
- }
959
- const projects = [];
960
- const config = await this.hierarchyMapper.getSpecweaveConfig();
961
- // Method 1: Check if story has explicit project field
962
- if (story.project) {
963
- if (availableProjects.includes(story.project)) {
964
- return [story.project];
965
- }
966
- }
967
- // Method 2: Keyword detection in title and description
968
- const text = `${story.title} ${story.description}`.toLowerCase();
969
- for (const projectId of availableProjects) {
970
- if (projectId === 'default') {
971
- continue; // Skip default for keyword matching
972
- }
973
- // Get project config for keywords
974
- const projectConfig = config.multiProject?.projects?.[projectId];
975
- if (projectConfig?.keywords) {
976
- const hasKeyword = projectConfig.keywords.some(keyword => text.includes(keyword.toLowerCase()));
977
- if (hasKeyword) {
978
- projects.push(projectId);
979
- }
980
- }
981
- // Also check if project name is mentioned
982
- if (text.includes(projectId.toLowerCase())) {
983
- if (!projects.includes(projectId)) {
984
- projects.push(projectId);
985
- }
986
- }
987
- }
988
- // Method 3: Fallback - if no projects detected, assign to all
989
- if (projects.length === 0) {
990
- return availableProjects;
991
- }
992
- return projects;
993
- }
994
- /**
995
- * Generate epic file (OPTIONAL - for strategic themes)
996
- */
997
- async generateEpicFile(parsed, epicMapping, featureMapping) {
998
- if (!epicMapping)
999
- return null;
1000
- return {
1001
- id: epicMapping.epicId,
1002
- title: parsed.title,
1003
- type: 'epic',
1004
- status: parsed.status || 'in-progress',
1005
- created: parsed.created || new Date().toISOString(),
1006
- lastUpdated: new Date().toISOString(),
1007
- strategicOverview: parsed.overview,
1008
- features: [featureMapping.featureId],
1009
- successMetrics: [],
1010
- timeline: {
1011
- start: parsed.created || new Date().toISOString(),
1012
- targetCompletion: 'TBD',
1013
- currentPhase: 'Phase 1',
1014
- },
1015
- stakeholders: {},
1016
- externalLinks: parsed.externalLinks || {},
1017
- };
1018
- }
1019
- /**
1020
- * Generate feature file (REQUIRED - cross-project overview)
1021
- */
1022
- async generateFeatureFile(parsed, featureMapping, storiesByProject, incrementId) {
1023
- // Convert stories to summaries grouped by project
1024
- const userStoriesByProject = new Map();
1025
- for (const [project, stories] of storiesByProject.entries()) {
1026
- const summaries = stories.map(s => ({
1027
- id: s.id,
1028
- title: s.title,
1029
- status: s.status,
1030
- phase: s.phase,
1031
- filePath: `../../${project}/${featureMapping.featureFolder}/${this.generateUserStoryFilename(s.id, s.title)}`,
1032
- }));
1033
- userStoriesByProject.set(project, summaries);
1034
- }
1035
- // Count completed stories
1036
- let completedStories = 0;
1037
- for (const stories of storiesByProject.values()) {
1038
- completedStories += stories.filter(s => s.status === 'complete').length;
1039
- }
1040
- // Detect external tool mapping
1041
- const externalToolMapping = incrementId ? await this.detectExternalToolMapping(incrementId) : undefined;
1042
- return {
1043
- id: featureMapping.featureId,
1044
- title: parsed.title,
1045
- type: 'feature',
1046
- status: parsed.status || 'in-progress',
1047
- priority: parsed.priority || 'P1',
1048
- created: parsed.created || new Date().toISOString(),
1049
- lastUpdated: new Date().toISOString(),
1050
- epic: featureMapping.epic,
1051
- projects: featureMapping.projects,
1052
- overview: parsed.overview,
1053
- businessValue: parsed.businessValue || [],
1054
- implementationHistory: [],
1055
- userStoriesByProject,
1056
- externalLinks: parsed.externalLinks || {},
1057
- totalStories: parsed.userStories.length,
1058
- completedStories,
1059
- overallProgress: parsed.userStories.length > 0
1060
- ? Math.round((completedStories / parsed.userStories.length) * 100)
1061
- : 0,
1062
- sourceIncrement: incrementId, // Track the source increment
1063
- externalToolMapping, // Include external tool mapping
1064
- };
1065
- }
1066
- /**
1067
- * Generate project context files (README.md for each project)
1068
- */
1069
- async generateProjectContextFiles(featureMapping, parsed) {
1070
- const contextFiles = new Map();
1071
- for (const project of featureMapping.projects) {
1072
- const projectContext = await this.hierarchyMapper.getProjectContext(project);
1073
- if (!projectContext)
1074
- continue;
1075
- const content = this.formatProjectContextFile(featureMapping, projectContext, parsed);
1076
- contextFiles.set(project, content);
1077
- }
1078
- return contextFiles;
1079
- }
1080
- /**
1081
- * Format project context file (README.md)
1082
- * IMPORTANT: This README is created for ALL increments, even without user stories!
1083
- */
1084
- formatProjectContextFile(featureMapping, projectContext, parsed) {
1085
- const featurePathUp = featureMapping.projects.length > 1 ? '../../../' : '../../';
1086
- // Determine if this increment has user stories
1087
- const hasUserStories = parsed.userStories && parsed.userStories.length > 0;
1088
- const statusNote = parsed.status === 'abandoned' ? 'abandoned' :
1089
- parsed.status === 'complete' ? 'complete' : 'in-progress';
1090
- return `---
1091
- id: ${featureMapping.featureId}-${projectContext.projectId}
1092
- title: "${parsed.title} - ${projectContext.projectName} Implementation"
1093
- feature: ${featureMapping.featureId}
1094
- project: ${projectContext.projectId}
1095
- type: feature-context
1096
- status: ${statusNote}
1097
- sourceIncrement: ${parsed.incrementId}
1098
- ---
1099
-
1100
- # ${projectContext.projectName} Implementation: ${parsed.title}
1101
-
1102
- **Feature**: [${featureMapping.featureId}](${featurePathUp}_features/${featureMapping.featureFolder}/FEATURE.md)
1103
-
1104
- ## Overview
1105
-
1106
- ${parsed.overview}
1107
-
1108
- ## ${projectContext.projectName}-Specific Context
1109
-
1110
- This document contains the ${projectContext.projectName} implementation details for the ${parsed.title} feature.
1111
-
1112
- ## Tech Stack
1113
-
1114
- ${projectContext.techStack.map(t => `- ${t}`).join('\n')}
1115
-
1116
- ## User Stories (${projectContext.projectName})
1117
-
1118
- ${hasUserStories ? 'User stories for this project are listed below.' : `_This increment has no user stories. See [FEATURE.md](${featurePathUp}_features/${featureMapping.featureFolder}/FEATURE.md) for overview and implementation details._`}
1119
-
1120
- ## Dependencies
1121
-
1122
- [Project-specific dependencies will be documented here]
1123
-
1124
- ## Architecture Considerations
1125
-
1126
- [${projectContext.projectName}-specific architecture notes]
1127
-
1128
- ---
1129
-
1130
- **Source**: [Increment ${parsed.incrementId}](../../../../../increments/${parsed.incrementId})
1131
- `;
1132
- }
1133
- /**
1134
- * Generate user story files by project
1135
- */
1136
- async generateUserStoryFilesByProject(storiesByProject, featureMapping, incrementId) {
1137
- const filesByProject = new Map();
1138
- // Load tasks for linking (LEGACY - for backward compatibility)
1139
- const taskMap = await this.loadTaskReferences(incrementId);
1140
- for (const [project, stories] of storiesByProject.entries()) {
1141
- const userStoryFiles = [];
1142
- // Get project context for AC and task transformation
1143
- const rawProjectContext = await this.hierarchyMapper.getProjectContext(project);
1144
- for (const userStory of stories) {
1145
- // LEGACY: Find tasks that implement this user story (for backward compatibility)
1146
- const taskReferences = this.findTasksForUserStory(userStory.id, taskMap);
1147
- // ✅ NEW: Generate project-specific tasks with completion status
1148
- const projectSpecificTasks = await this.taskGenerator.generateProjectSpecificTasks(incrementId, userStory.id, rawProjectContext ? {
1149
- id: rawProjectContext.projectId,
1150
- name: rawProjectContext.projectName,
1151
- type: this.detectProjectType(rawProjectContext),
1152
- techStack: rawProjectContext.techStack,
1153
- keywords: rawProjectContext.keywords,
1154
- } : undefined);
1155
- // Find related user stories (same project, same phase)
1156
- const relatedStories = stories
1157
- .filter((us) => us.id !== userStory.id && us.phase === userStory.phase)
1158
- .map((us) => ({
1159
- id: us.id,
1160
- title: us.title,
1161
- status: us.status,
1162
- phase: us.phase,
1163
- filePath: this.generateUserStoryFilename(us.id, us.title),
1164
- }));
1165
- // ✨ Apply project-specific AC transformation
1166
- let projectSpecificACs = userStory.acceptanceCriteria;
1167
- if (rawProjectContext) {
1168
- // Map ProjectContext to AC generator's expected format
1169
- const acGeneratorContext = {
1170
- id: rawProjectContext.projectId,
1171
- name: rawProjectContext.projectName,
1172
- type: this.detectProjectType(rawProjectContext),
1173
- techStack: rawProjectContext.techStack,
1174
- keywords: rawProjectContext.keywords,
1175
- };
1176
- projectSpecificACs = this.acGenerator.makeProjectSpecific(userStory.acceptanceCriteria, userStory.id, acGeneratorContext);
1177
- }
1178
- userStoryFiles.push({
1179
- id: userStory.id,
1180
- epic: featureMapping.featureId, // Feature is the parent
1181
- title: userStory.title,
1182
- status: userStory.status,
1183
- priority: userStory.priority,
1184
- created: new Date().toISOString().split('T')[0],
1185
- completed: userStory.status === 'complete' ? new Date().toISOString().split('T')[0] : undefined,
1186
- description: userStory.description,
1187
- acceptanceCriteria: projectSpecificACs, // ✅ Use project-specific AC
1188
- tasks: projectSpecificTasks, // ✅ NEW: Project-specific tasks with completion status
1189
- implementation: {
1190
- increment: incrementId,
1191
- tasks: taskReferences, // LEGACY: Keep for backward compatibility
1192
- },
1193
- businessRationale: userStory.businessRationale,
1194
- relatedStories,
1195
- phase: userStory.phase,
1196
- project, // Add project field
1197
- });
1198
- }
1199
- filesByProject.set(project, userStoryFiles);
1200
- }
1201
- return filesByProject;
1202
- }
1203
- /**
1204
- * Write epic file to _epics/ folder
1205
- */
1206
- async writeEpicFile(epic, epicMapping) {
1207
- if (!epic || !epicMapping)
1208
- return null;
1209
- const epicPath = path.join(epicMapping.epicPath, 'EPIC.md');
1210
- const content = this.formatEpicThemeFile(epic);
1211
- await fs.ensureDir(path.dirname(epicPath));
1212
- await fs.writeFile(epicPath, content, 'utf-8');
1213
- console.log(` ✅ Written epic overview to _epics/${epicMapping.epicFolder}/EPIC.md`);
1214
- return epicPath;
1215
- }
1216
- /**
1217
- * Format epic theme file content
1218
- */
1219
- formatEpicThemeFile(epic) {
1220
- const yaml = `---
1221
- id: ${epic.id}
1222
- title: "${epic.title}"
1223
- type: ${epic.type}
1224
- status: ${epic.status}
1225
- ---`;
1226
- return `${yaml}
1227
-
1228
- # ${epic.title}
1229
-
1230
- ## Strategic Overview
1231
-
1232
- ${epic.strategicOverview}
1233
-
1234
- ## Features
1235
-
1236
- ${epic.features.map(f => `- ${f}`).join('\n')}
1237
-
1238
- ## Timeline
1239
-
1240
- - **Start**: ${epic.timeline.start}
1241
- - **Target Completion**: ${epic.timeline.targetCompletion}
1242
- - **Current Phase**: ${epic.timeline.currentPhase}
1243
- `;
1244
- }
1245
- /**
1246
- * Write feature file to _features/ folder
1247
- */
1248
- async writeFeatureFile(feature, featureMapping) {
1249
- const featurePath = path.join(featureMapping.featurePath, 'FEATURE.md');
1250
- const content = this.formatFeatureFile(feature);
1251
- await fs.ensureDir(path.dirname(featurePath));
1252
- await fs.writeFile(featurePath, content, 'utf-8');
1253
- console.log(` ✅ Written feature overview to _features/${featureMapping.featureFolder}/FEATURE.md`);
1254
- return featurePath;
1255
- }
1256
- /**
1257
- * Format feature file content
1258
- */
1259
- formatFeatureFile(feature) {
1260
- // Build YAML frontmatter with external tool mapping
1261
- let yaml = `---
1262
- id: ${feature.id}
1263
- title: "${feature.title}"
1264
- type: ${feature.type}
1265
- status: ${feature.status}
1266
- priority: ${feature.priority}
1267
- created: ${feature.created}
1268
- lastUpdated: ${feature.lastUpdated}
1269
- projects: [${feature.projects.map(p => `"${p}"`).join(', ')}]
1270
- ${feature.epic ? `epic: ${feature.epic}` : ''}
1271
- ${feature.sourceIncrement ? `sourceIncrement: ${feature.sourceIncrement}` : ''}`;
1272
- // Add external tool mapping to frontmatter
1273
- if (feature.externalToolMapping) {
1274
- yaml += `
1275
- externalTool:
1276
- provider: ${feature.externalToolMapping.provider}
1277
- type: ${feature.externalToolMapping.externalType}
1278
- id: "${feature.externalToolMapping.externalId}"
1279
- url: "${feature.externalToolMapping.externalUrl}"`;
1280
- }
1281
- yaml += '\n---';
1282
- const storiesByProjectSection = Array.from(feature.userStoriesByProject.entries())
1283
- .map(([project, stories]) => `
1284
- ### ${project}
1285
-
1286
- ${stories.map(s => `- [${s.id}: ${s.title}](${s.filePath}) - ${s.status}`).join('\n')}`)
1287
- .join('\n');
1288
- // Build title with external tool indicator
1289
- let titleLine = `# ${feature.title}`;
1290
- if (feature.externalToolMapping) {
1291
- const provider = feature.externalToolMapping.provider.toUpperCase();
1292
- const type = feature.externalToolMapping.externalType.charAt(0).toUpperCase() +
1293
- feature.externalToolMapping.externalType.slice(1);
1294
- titleLine += ` (${provider} ${type}: ${feature.externalToolMapping.externalId})`;
1295
- }
1296
- // Add external tool mapping section
1297
- let externalMappingSection = '';
1298
- if (feature.externalToolMapping) {
1299
- externalMappingSection = `
1300
- ## External Tool Mapping
1301
-
1302
- **Mapped from**: ${feature.externalToolMapping.provider.toUpperCase()} ${feature.externalToolMapping.externalType.charAt(0).toUpperCase() + feature.externalToolMapping.externalType.slice(1)} [${feature.externalToolMapping.externalId}](${feature.externalToolMapping.externalUrl})
1303
-
1304
- > **Note**: ${feature.externalToolMapping.mappingNote}
1305
-
1306
- This SpecWeave Feature corresponds to a ${feature.externalToolMapping.externalType} in ${feature.externalToolMapping.provider.toUpperCase()}. The hierarchy mapping is:
1307
- - **SpecWeave**: Feature (${feature.id})
1308
- - **${feature.externalToolMapping.provider.toUpperCase()}**: ${feature.externalToolMapping.externalType.charAt(0).toUpperCase() + feature.externalToolMapping.externalType.slice(1)} (${feature.externalToolMapping.externalId})
1309
-
1310
- `;
1311
- }
1312
- // Add source section based on where the feature came from
1313
- let sourceSection = '';
1314
- if (feature.sourceIncrement) {
1315
- sourceSection = `
1316
- ## Source
1317
-
1318
- This feature was created from increment: [\`${feature.sourceIncrement}\`](../../../../../increments/${feature.sourceIncrement})
1319
- `;
1320
- }
1321
- else if (feature.externalLinks?.github || feature.externalLinks?.jira || feature.externalLinks?.ado) {
1322
- sourceSection = `
1323
- ## Source
1324
-
1325
- This feature was imported from external tool:
1326
- ${feature.externalLinks?.github ? `- GitHub: ${feature.externalLinks.github}` : ''}
1327
- ${feature.externalLinks?.jira ? `- JIRA: ${feature.externalLinks.jira}` : ''}
1328
- ${feature.externalLinks?.ado ? `- Azure DevOps: ${feature.externalLinks.ado}` : ''}
1329
-
1330
- Note: No explicit feature was specified during import.
1331
- `;
1332
- }
1333
- return `${yaml}
1334
-
1335
- ${titleLine}
1336
- ${externalMappingSection}
1337
- ## Overview
1338
-
1339
- ${feature.overview}
1340
- ${sourceSection}
1341
- ## Business Value
1342
-
1343
- ${feature.businessValue.map(v => `- ${v}`).join('\n') || 'See overview above'}
1344
-
1345
- ## Projects
1346
-
1347
- This feature spans the following projects:
1348
- ${feature.projects.map(p => `- ${p}`).join('\n')}
1349
-
1350
- ## User Stories by Project
1351
- ${storiesByProjectSection}
1352
-
1353
- ## Progress
1354
-
1355
- - **Total Stories**: ${feature.totalStories}
1356
- - **Completed**: ${feature.completedStories}
1357
- - **Progress**: ${feature.overallProgress}%
1358
- `;
1359
- }
1360
- /**
1361
- * Write project context files (README.md for each project)
1362
- * IMPORTANT: Always create README.md even if increment has no user stories!
1363
- * This ensures every increment is represented in living docs, preventing gaps.
1364
- */
1365
- async writeProjectContextFiles(contextFiles, featureMapping) {
1366
- const writtenPaths = [];
1367
- // Write README.md for EVERY project, even if no user stories exist
1368
- for (const [project, content] of contextFiles.entries()) {
1369
- const projectPath = featureMapping.projectPaths.get(project);
1370
- if (!projectPath) {
1371
- console.warn(` ⚠️ No project path found for ${project}, skipping README`);
1372
- continue;
1373
- }
1374
- // Ensure directory exists
1375
- await fs.ensureDir(projectPath);
1376
- // Write README.md
1377
- const readmePath = path.join(projectPath, 'README.md');
1378
- await fs.writeFile(readmePath, content, 'utf-8');
1379
- writtenPaths.push(readmePath);
1380
- }
1381
- if (writtenPaths.length > 0) {
1382
- console.log(` ✅ Written README.md to ${writtenPaths.length} project folder(s)`);
1383
- }
1384
- return writtenPaths;
1385
- }
1386
- /**
1387
- * Write user story files by project
1388
- * IMPORTANT: Ensures project folders exist even if no user stories!
1389
- */
1390
- async writeUserStoryFilesByProject(userStoryFilesByProject, featureMapping, incrementId) {
1391
- const pathsByProject = new Map();
1392
- // CRITICAL FIX: Ensure ALL project folders exist, even without user stories
1393
- for (const project of featureMapping.projects) {
1394
- const projectPath = featureMapping.projectPaths.get(project);
1395
- if (!projectPath) {
1396
- console.warn(` ⚠️ No project path found for ${project}, skipping`);
1397
- continue;
1398
- }
1399
- // Ensure directory exists (even if no stories to write)
1400
- await fs.ensureDir(projectPath);
1401
- const stories = userStoryFilesByProject.get(project) || [];
1402
- const paths = [];
1403
- // Write user story files (if any exist)
1404
- for (const story of stories) {
1405
- const filename = this.generateUserStoryFilename(story.id, story.title);
1406
- const filePath = path.join(projectPath, filename);
1407
- const content = this.formatUserStoryFile(story);
1408
- await fs.writeFile(filePath, content, 'utf-8');
1409
- paths.push(filePath);
1410
- }
1411
- pathsByProject.set(project, paths);
1412
- }
1413
- const totalStories = Array.from(userStoryFilesByProject.values())
1414
- .reduce((sum, stories) => sum + stories.length, 0);
1415
- if (totalStories > 0) {
1416
- console.log(` ✅ Written ${totalStories} user stories to ${featureMapping.projects.length} project(s)`);
1417
- }
1418
- else {
1419
- console.log(` ℹ️ No user stories to write, but created ${featureMapping.projects.length} project folder(s)`);
1420
- }
1421
- return pathsByProject;
1422
- }
1423
- /**
1424
- * Update tasks.md with bidirectional links (project-aware)
1425
- */
1426
- async updateTasksWithUserStoryLinks(incrementId, userStoryFilesByProject, featureMapping) {
1427
- const tasksPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'tasks.md');
1428
- if (!fs.existsSync(tasksPath)) {
1429
- console.warn(` ⚠️ tasks.md not found for ${incrementId}, skipping bidirectional linking`);
1430
- return;
1431
- }
1432
- let content = await fs.readFile(tasksPath, 'utf-8');
1433
- // Build a map of all user stories across all projects
1434
- const allUserStories = new Map();
1435
- for (const [project, stories] of userStoryFilesByProject.entries()) {
1436
- for (const story of stories) {
1437
- allUserStories.set(story.id, { story, project });
1438
- }
1439
- }
1440
- // Pattern to find task headings and their AC fields
1441
- const taskSections = content.split(/(?=^##+ T-\d+:)/m);
1442
- const updatedSections = [];
1443
- for (const section of taskSections) {
1444
- if (!section.trim()) {
1445
- updatedSections.push(section);
1446
- continue;
1447
- }
1448
- // Extract task ID and AC list
1449
- const taskMatch = section.match(/^(##+ T-\d+:.+?)$/m);
1450
- const acMatch = section.match(/\*\*AC\*\*:\s*([^\n]+)/);
1451
- if (!taskMatch || !acMatch) {
1452
- updatedSections.push(section);
1453
- continue;
1454
- }
1455
- const taskHeading = taskMatch[1];
1456
- const acList = acMatch[1];
1457
- // Find which user story this task belongs to based on AC-IDs
1458
- let linkedStory = null;
1459
- const acPattern = /AC-US(\d+)-\d+/g;
1460
- let acIdMatch;
1461
- while ((acIdMatch = acPattern.exec(acList)) !== null) {
1462
- const usNumber = acIdMatch[1];
1463
- const usId = `US-${usNumber.padStart(3, '0')}`;
1464
- if (allUserStories.has(usId)) {
1465
- linkedStory = allUserStories.get(usId);
1466
- break;
1467
- }
1468
- }
1469
- if (linkedStory) {
1470
- const { story, project } = linkedStory;
1471
- // Use the FS-XXX feature ID from featureMapping, not featureFolder
1472
- const newRelativePath = `../../docs/internal/specs/${project}/${featureMapping.featureId}/${this.generateUserStoryFilename(story.id, story.title)}`;
1473
- const newLinkLine = `**User Story**: [${story.id}: ${story.title}](${newRelativePath})`;
1474
- // Check for existing user story links (old or new format)
1475
- const userStoryLinkPattern = /\*\*User Story\*\*:.*/g;
1476
- const existingLinks = section.match(userStoryLinkPattern);
1477
- if (!existingLinks || existingLinks.length === 0) {
1478
- // No link exists - add new one
1479
- const lines = section.split('\n');
1480
- const headingIndex = lines.findIndex(line => line.match(/^##+ T-\d+:/));
1481
- if (headingIndex >= 0) {
1482
- lines.splice(headingIndex + 1, 0, '', newLinkLine);
1483
- updatedSections.push(lines.join('\n'));
1484
- }
1485
- else {
1486
- updatedSections.push(section);
1487
- }
1488
- }
1489
- else {
1490
- // Link(s) exist - update them and remove duplicates/blank lines
1491
- const lines = section.split('\n');
1492
- let foundFirst = false;
1493
- const cleanedLines = [];
1494
- let consecutiveBlankLines = 0;
1495
- for (let i = 0; i < lines.length; i++) {
1496
- const line = lines[i];
1497
- // Handle user story links
1498
- if (line.includes('**User Story**:')) {
1499
- if (!foundFirst) {
1500
- foundFirst = true;
1501
- cleanedLines.push(newLinkLine); // Replace with updated link
1502
- consecutiveBlankLines = 0;
1503
- }
1504
- // Skip duplicate links
1505
- continue;
1506
- }
1507
- // Handle blank lines (keep max 1 consecutive)
1508
- if (line.trim() === '') {
1509
- consecutiveBlankLines++;
1510
- if (consecutiveBlankLines <= 1) {
1511
- cleanedLines.push(line);
1512
- }
1513
- }
1514
- else {
1515
- consecutiveBlankLines = 0;
1516
- cleanedLines.push(line);
1517
- }
1518
- }
1519
- updatedSections.push(cleanedLines.join('\n'));
1520
- }
1521
- }
1522
- else {
1523
- updatedSections.push(section);
1524
- }
1525
- }
1526
- const updatedContent = updatedSections.join('');
1527
- if (content !== updatedContent) {
1528
- await fs.writeFile(tasksPath, updatedContent, 'utf-8');
1529
- console.log(` ✅ Updated tasks.md with bidirectional user story links`);
1530
- }
1531
- }
1532
- /**
1533
- * Update acceptance criteria status in user stories based on completed tasks
1534
- * This method synchronizes AC checkboxes with task completion status
1535
- */
1536
- async updateAcceptanceCriteriaStatus(incrementId) {
1537
- console.log(`\n📊 Updating acceptance criteria status for increment: ${incrementId}`);
1538
- const incrementPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId);
1539
- const tasksPath = path.join(incrementPath, 'tasks.md');
1540
- const specPath = path.join(incrementPath, 'spec.md');
1541
- if (!await fs.pathExists(tasksPath)) {
1542
- console.log(` ⚠️ No tasks.md found, skipping AC status update`);
1543
- return;
1544
- }
1545
- const tasksContent = await fs.readFile(tasksPath, 'utf-8');
1546
- // Parse completed tasks and their AC references
1547
- const completedACs = this.extractCompletedAcceptanceCriteria(tasksContent);
1548
- if (completedACs.size === 0) {
1549
- console.log(` ℹ️ No completed acceptance criteria found`);
1550
- return;
1551
- }
1552
- console.log(` 📝 Found ${completedACs.size} user stories with completed acceptance criteria`);
1553
- // Try to detect the feature folder this increment maps to
1554
- let featureId = null;
1555
- if (await fs.pathExists(specPath)) {
1556
- const specContent = await fs.readFile(specPath, 'utf-8');
1557
- // Look for feature mapping in frontmatter or content
1558
- const featureMatch = specContent.match(/feature:\s*(FS-[^\s\n]+)/i);
1559
- if (featureMatch) {
1560
- const declaredFeature = featureMatch[1];
1561
- // Try to find the actual feature folder that exists
1562
- featureId = await this.findActualFeatureFolder(incrementId, declaredFeature);
1563
- console.log(` 📁 Feature mapping: ${declaredFeature} → ${featureId || 'not found'}`);
1564
- }
1565
- }
1566
- // Update user story files with completed ACs
1567
- await this.updateUserStoryACStatus(completedACs, incrementId, featureId);
1568
- }
1569
- /**
1570
- * Extract completed acceptance criteria from tasks
1571
- */
1572
- extractCompletedAcceptanceCriteria(tasksContent) {
1573
- const completedACs = new Map(); // US-ID -> Set of AC-IDs
1574
- // Split into task sections
1575
- const taskSections = tasksContent.split(/^#{2,3}\s+T-\d+:/m);
1576
- for (const section of taskSections) {
1577
- if (!section.trim())
1578
- continue;
1579
- // Check if task is completed
1580
- const statusMatch = section.match(/\*\*Status\*\*:\s*\[x\]/i);
1581
- if (!statusMatch)
1582
- continue; // Skip incomplete tasks
1583
- // Extract AC field
1584
- const acMatch = section.match(/\*\*AC\*\*:\s*([^\n]+)/);
1585
- if (!acMatch)
1586
- continue;
1587
- // Parse AC-IDs
1588
- const acIds = acMatch[1].split(/[,\s]+/).filter(id => id.match(/^AC-/));
1589
- for (const acId of acIds) {
1590
- // Determine user story from AC-ID
1591
- // Patterns: AC-US1-01 -> US-001, AC-001 -> US-001 (assume single story)
1592
- let userStoryId = '';
1593
- const usMatch = acId.match(/AC-US(\d+)-/);
1594
- if (usMatch) {
1595
- userStoryId = `US-${usMatch[1].padStart(3, '0')}`;
1596
- }
1597
- else {
1598
- // For generic AC-XXX format, try to infer from task's user story link
1599
- const usLinkMatch = section.match(/\[US-(\d+):/);
1600
- if (usLinkMatch) {
1601
- userStoryId = `US-${usLinkMatch[1].padStart(3, '0')}`;
1602
- }
1603
- }
1604
- if (userStoryId) {
1605
- if (!completedACs.has(userStoryId)) {
1606
- completedACs.set(userStoryId, new Set());
1607
- }
1608
- completedACs.get(userStoryId).add(acId);
1609
- }
1610
- }
1611
- }
1612
- return completedACs;
1613
- }
1614
- /**
1615
- * Update user story files with completed AC status
1616
- */
1617
- async updateUserStoryACStatus(completedACs, incrementId, featureId = null) {
1618
- const specsPath = path.join(this.projectRoot, '.specweave', 'docs', 'internal', 'specs');
1619
- // Find all user story files
1620
- const projects = await this.getProjectFolders();
1621
- let updatedCount = 0;
1622
- for (const project of projects) {
1623
- const projectSpecsPath = path.join(specsPath, project);
1624
- if (!await fs.pathExists(projectSpecsPath))
1625
- continue;
1626
- // Find all feature folders
1627
- const entries = await fs.readdir(projectSpecsPath, { withFileTypes: true });
1628
- let featureFolders = entries.filter(e => e.isDirectory() && e.name.startsWith('FS-'));
1629
- // If we have a specific feature ID, only look in that folder
1630
- if (featureId) {
1631
- featureFolders = featureFolders.filter(e => e.name === featureId || e.name.includes(featureId));
1632
- if (featureFolders.length === 0) {
1633
- console.log(` ⚠️ Feature folder ${featureId} not found in ${project}`);
1634
- }
1635
- }
1636
- for (const folder of featureFolders) {
1637
- const featurePath = path.join(projectSpecsPath, folder.name);
1638
- // Find all user story files
1639
- const files = await fs.readdir(featurePath);
1640
- const userStoryFiles = files.filter(f => f.match(/^us-\d+-.+\.md$/));
1641
- for (const file of userStoryFiles) {
1642
- const filePath = path.join(featurePath, file);
1643
- const content = await fs.readFile(filePath, 'utf-8');
1644
- // Extract user story ID from content or filename
1645
- const idMatch = content.match(/^id:\s*(US-\d+)/m) || file.match(/us-(\d+)/);
1646
- if (!idMatch)
1647
- continue;
1648
- const userStoryId = idMatch[1].startsWith('US') ? idMatch[1] : `US-${idMatch[1].padStart(3, '0')}`;
1649
- const acsToComplete = completedACs.get(userStoryId);
1650
- if (!acsToComplete || acsToComplete.size === 0)
1651
- continue;
1652
- // Update AC checkboxes
1653
- let updatedContent = content;
1654
- let hasUpdates = false;
1655
- for (const acId of acsToComplete) {
1656
- // Find and update the AC checkbox
1657
- // Pattern: - [ ] **AC-XXX**: Description
1658
- const acPattern = new RegExp(`^(\\s*-\\s*)\\[\\s\\](\\s*\\*\\*${acId}\\*\\*:.*)$`, 'gm');
1659
- // Check if pattern exists before replacing
1660
- const originalContent = updatedContent;
1661
- updatedContent = updatedContent.replace(acPattern, '$1[x]$2');
1662
- // Check if replacement actually happened
1663
- if (originalContent !== updatedContent) {
1664
- hasUpdates = true;
1665
- console.log(` ✅ Updated ${acId} in ${userStoryId} (${project}/${folder.name})`);
1666
- }
1667
- else {
1668
- // Debug: pattern didn't match
1669
- console.log(` ⚠️ Could not find ${acId} in ${userStoryId} (${project}/${folder.name})`);
1670
- // Try to find what format the AC actually has
1671
- const lineWithAc = updatedContent.split('\n').find(line => line.includes(acId));
1672
- if (lineWithAc) {
1673
- console.log(` Found line: "${lineWithAc}"`);
1674
- }
1675
- }
1676
- }
1677
- if (hasUpdates) {
1678
- await fs.writeFile(filePath, updatedContent, 'utf-8');
1679
- updatedCount++;
1680
- }
1681
- }
1682
- }
1683
- }
1684
- if (updatedCount > 0) {
1685
- console.log(` ✅ Updated ${updatedCount} user story file(s) with completed acceptance criteria`);
1686
- }
1687
- }
1688
- /**
1689
- * Get all project folders from specs
1690
- */
1691
- async getProjectFolders() {
1692
- const specsPath = path.join(this.projectRoot, '.specweave', 'docs', 'internal', 'specs');
1693
- if (!await fs.pathExists(specsPath)) {
1694
- return ['default'];
1695
- }
1696
- const entries = await fs.readdir(specsPath, { withFileTypes: true });
1697
- const projectFolders = entries
1698
- .filter(e => e.isDirectory() && !e.name.startsWith('_'))
1699
- .map(e => e.name);
1700
- return projectFolders.length > 0 ? projectFolders : ['default'];
1701
- }
1702
- /**
1703
- * Detect project type for AC generation (backend, frontend, mobile, infrastructure, generic)
1704
- */
1705
- detectProjectType(context) {
1706
- const projectId = context.projectId.toLowerCase();
1707
- const keywords = context.keywords.map(k => k.toLowerCase());
1708
- const techStack = context.techStack.map(t => t.toLowerCase());
1709
- // Direct match on project ID
1710
- if (projectId.includes('backend') || projectId.includes('api') || projectId.includes('service')) {
1711
- return 'backend';
1712
- }
1713
- if (projectId.includes('frontend') || projectId.includes('web') || projectId.includes('ui')) {
1714
- return 'frontend';
1715
- }
1716
- if (projectId.includes('mobile') || projectId.includes('ios') || projectId.includes('android')) {
1717
- return 'mobile';
1718
- }
1719
- if (projectId.includes('infra') || projectId.includes('devops') || projectId.includes('deployment')) {
1720
- return 'infrastructure';
1721
- }
1722
- // Keyword-based detection
1723
- const backendKeywords = ['api', 'backend', 'service', 'server', 'database'];
1724
- const frontendKeywords = ['frontend', 'ui', 'component', 'react', 'web'];
1725
- const mobileKeywords = ['mobile', 'ios', 'android', 'react-native'];
1726
- const infraKeywords = ['infrastructure', 'devops', 'deployment', 'ci/cd', 'kubernetes'];
1727
- if (keywords.some(k => backendKeywords.includes(k)))
1728
- return 'backend';
1729
- if (keywords.some(k => frontendKeywords.includes(k)))
1730
- return 'frontend';
1731
- if (keywords.some(k => mobileKeywords.includes(k)))
1732
- return 'mobile';
1733
- if (keywords.some(k => infraKeywords.includes(k)))
1734
- return 'infrastructure';
1735
- // Tech stack-based detection
1736
- const backendTech = ['node.js', 'express', 'postgresql', 'mongodb', 'redis'];
1737
- const frontendTech = ['react', 'next.js', 'vue', 'angular', 'typescript'];
1738
- const mobileTech = ['react native', 'swift', 'kotlin', 'flutter'];
1739
- const infraTech = ['docker', 'kubernetes', 'terraform', 'ansible'];
1740
- if (techStack.some(t => backendTech.some(bt => t.includes(bt))))
1741
- return 'backend';
1742
- if (techStack.some(t => frontendTech.some(ft => t.includes(ft))))
1743
- return 'frontend';
1744
- if (techStack.some(t => mobileTech.some(mt => t.includes(mt))))
1745
- return 'mobile';
1746
- if (techStack.some(t => infraTech.some(it => t.includes(it))))
1747
- return 'infrastructure';
1748
- // Default to generic if no match
1749
- return 'generic';
1750
- }
1751
- /**
1752
- * Find the actual feature folder that matches an increment
1753
- * Handles mapping between different naming conventions
1754
- */
1755
- async findActualFeatureFolder(incrementId, declaredFeature) {
1756
- const specsPath = path.join(this.projectRoot, '.specweave', 'docs', 'internal', 'specs');
1757
- const projects = await this.getProjectFolders();
1758
- // Extract increment number
1759
- const incrementNum = incrementId.match(/^(\d+)/)?.[1];
1760
- for (const project of projects) {
1761
- const projectPath = path.join(specsPath, project);
1762
- if (!await fs.pathExists(projectPath))
1763
- continue;
1764
- const entries = await fs.readdir(projectPath);
1765
- const featureFolders = entries.filter(e => e.startsWith('FS-'));
1766
- // Strategy 1: Direct match
1767
- if (featureFolders.includes(declaredFeature)) {
1768
- return declaredFeature;
1769
- }
1770
- // Strategy 2: Match by increment number (FS-031 for increment 0031)
1771
- if (incrementNum) {
1772
- const paddedNum = incrementNum.padStart(3, '0');
1773
- const numericFeature = `FS-${paddedNum}`;
1774
- if (featureFolders.includes(numericFeature)) {
1775
- return numericFeature;
1776
- }
1777
- }
1778
- // Strategy 3: Match by suffix (e.g., FS-XXX-external-tool-sync matches external-tool-sync)
1779
- const incrementSuffix = incrementId.replace(/^\d+-/, '');
1780
- for (const folder of featureFolders) {
1781
- if (folder.includes(incrementSuffix)) {
1782
- return folder;
1783
- }
1784
- }
1785
- // Strategy 4: Match by date pattern
1786
- if (declaredFeature.match(/^FS-\d{2}-\d{2}-\d{2}-/)) {
1787
- // Find folder with same date prefix
1788
- const datePrefix = declaredFeature.match(/^FS-\d{2}-\d{2}-\d{2}/)?.[0];
1789
- for (const folder of featureFolders) {
1790
- if (folder.startsWith(datePrefix)) {
1791
- return folder;
1792
- }
1793
- }
1794
- }
1795
- // Strategy 5: Smart content-based matching
1796
- // Look for feature folders containing user stories that reference this increment
1797
- for (const folder of featureFolders) {
1798
- const featurePath = path.join(projectPath, folder);
1799
- const files = await fs.readdir(featurePath);
1800
- const userStoryFiles = files.filter(f => f.match(/^us-\d+-.+\.md$/));
1801
- for (const file of userStoryFiles) {
1802
- const filePath = path.join(featurePath, file);
1803
- const content = await fs.readFile(filePath, 'utf-8');
1804
- // Check if this user story references our increment
1805
- if (content.includes(`increments/${incrementId}/`) ||
1806
- content.includes(`Increment**: [${incrementId}]`)) {
1807
- console.log(` 🎯 Found feature folder ${folder} by content match in ${file}`);
1808
- return folder;
1809
- }
1810
- }
1811
- }
1812
- // Strategy 6: Fuzzy match on increment name parts
1813
- // Split increment name into words and look for feature folders with matching words
1814
- const incrementWords = incrementId.toLowerCase().split(/[-_]/);
1815
- let bestMatch = null;
1816
- for (const folder of featureFolders) {
1817
- const folderWords = folder.toLowerCase().split(/[-_]/);
1818
- let matchScore = 0;
1819
- // Count matching words (ignoring numbers and 'FS' prefix)
1820
- for (const word of incrementWords) {
1821
- if (word.match(/^\d+$/) || word === 'fs')
1822
- continue;
1823
- if (folderWords.includes(word)) {
1824
- matchScore++;
1825
- }
1826
- }
1827
- // Update best match if this score is better
1828
- if (matchScore > 0 && (!bestMatch || matchScore > bestMatch.score)) {
1829
- bestMatch = { folder, score: matchScore };
1830
- }
1831
- }
1832
- if (bestMatch && bestMatch.score >= 2) { // Require at least 2 word matches
1833
- console.log(` 🎯 Found feature folder ${bestMatch.folder} by fuzzy match (score: ${bestMatch.score})`);
1834
- return bestMatch.folder;
1835
- }
1836
- }
1837
- return null;
1838
- }
1839
- }
1840
- //# sourceMappingURL=spec-distributor.js.map