specweave 0.21.3 → 0.22.2

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 (235) hide show
  1. package/CLAUDE.md +409 -6
  2. package/README.md +38 -8
  3. package/bin/specweave.js +5 -8
  4. package/dist/plugins/specweave-github/lib/CodeValidator.d.ts +101 -0
  5. package/dist/plugins/specweave-github/lib/CodeValidator.d.ts.map +1 -0
  6. package/dist/plugins/specweave-github/lib/CodeValidator.js +219 -0
  7. package/dist/plugins/specweave-github/lib/CodeValidator.js.map +1 -0
  8. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.d.ts +182 -0
  9. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.d.ts.map +1 -0
  10. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.js +603 -0
  11. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.js.map +1 -0
  12. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +10 -0
  13. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  14. package/dist/plugins/specweave-github/lib/github-client-v2.js +26 -0
  15. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  16. package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
  17. package/dist/plugins/specweave-github/lib/task-sync.js +7 -0
  18. package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
  19. package/dist/plugins/specweave-github/lib/types.d.ts +34 -0
  20. package/dist/plugins/specweave-github/lib/types.d.ts.map +1 -1
  21. package/dist/src/cli/commands/init.d.ts.map +1 -1
  22. package/dist/src/cli/commands/init.js +60 -5
  23. package/dist/src/cli/commands/init.js.map +1 -1
  24. package/dist/src/cli/commands/migrate-to-profiles.d.ts +1 -0
  25. package/dist/src/cli/commands/migrate-to-profiles.d.ts.map +1 -1
  26. package/dist/src/cli/commands/migrate-to-profiles.js +12 -1
  27. package/dist/src/cli/commands/migrate-to-profiles.js.map +1 -1
  28. package/dist/src/cli/commands/next-command.d.ts +52 -0
  29. package/dist/src/cli/commands/next-command.d.ts.map +1 -0
  30. package/dist/src/cli/commands/next-command.js +204 -0
  31. package/dist/src/cli/commands/next-command.js.map +1 -0
  32. package/dist/src/cli/commands/sync-specs.d.ts +16 -0
  33. package/dist/src/cli/commands/sync-specs.d.ts.map +1 -0
  34. package/dist/src/cli/commands/sync-specs.js +130 -0
  35. package/dist/src/cli/commands/sync-specs.js.map +1 -0
  36. package/dist/src/cli/count-tasks.d.ts +20 -0
  37. package/dist/src/cli/count-tasks.d.ts.map +1 -0
  38. package/dist/src/cli/count-tasks.js +50 -0
  39. package/dist/src/cli/count-tasks.js.map +1 -0
  40. package/dist/src/config/ConfigManager.d.ts.map +1 -1
  41. package/dist/src/config/ConfigManager.js +2 -1
  42. package/dist/src/config/ConfigManager.js.map +1 -1
  43. package/dist/src/config/types.d.ts +58 -58
  44. package/dist/src/core/cicd/state-manager.d.ts +8 -0
  45. package/dist/src/core/cicd/state-manager.d.ts.map +1 -1
  46. package/dist/src/core/cicd/state-manager.js +60 -15
  47. package/dist/src/core/cicd/state-manager.js.map +1 -1
  48. package/dist/src/core/cost-tracker.d.ts.map +1 -1
  49. package/dist/src/core/cost-tracker.js +2 -1
  50. package/dist/src/core/cost-tracker.js.map +1 -1
  51. package/dist/src/core/iac/template-engine.d.ts.map +1 -1
  52. package/dist/src/core/iac/template-engine.js +28 -0
  53. package/dist/src/core/iac/template-engine.js.map +1 -1
  54. package/dist/src/core/iac/template-generator.d.ts +53 -0
  55. package/dist/src/core/iac/template-generator.d.ts.map +1 -0
  56. package/dist/src/core/iac/template-generator.js +125 -0
  57. package/dist/src/core/iac/template-generator.js.map +1 -0
  58. package/dist/src/core/increment/status-auto-transition.js +3 -3
  59. package/dist/src/core/increment/status-auto-transition.js.map +1 -1
  60. package/dist/src/core/living-docs/CodeValidator.js +1 -1
  61. package/dist/src/core/living-docs/CodeValidator.js.map +1 -1
  62. package/dist/src/core/living-docs/CompletionPropagator.d.ts.map +1 -1
  63. package/dist/src/core/living-docs/CompletionPropagator.js +4 -3
  64. package/dist/src/core/living-docs/CompletionPropagator.js.map +1 -1
  65. package/dist/src/core/living-docs/SpecDistributor.d.ts +5 -0
  66. package/dist/src/core/living-docs/SpecDistributor.d.ts.map +1 -1
  67. package/dist/src/core/living-docs/SpecDistributor.js +12 -0
  68. package/dist/src/core/living-docs/SpecDistributor.js.map +1 -1
  69. package/dist/src/core/living-docs/content-distributor.d.ts.map +1 -1
  70. package/dist/src/core/living-docs/content-distributor.js +11 -1
  71. package/dist/src/core/living-docs/content-distributor.js.map +1 -1
  72. package/dist/src/core/living-docs/living-docs-sync.d.ts +166 -0
  73. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -0
  74. package/dist/src/core/living-docs/living-docs-sync.js +726 -0
  75. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -0
  76. package/dist/src/core/living-docs/project-detector.d.ts.map +1 -1
  77. package/dist/src/core/living-docs/project-detector.js +38 -0
  78. package/dist/src/core/living-docs/project-detector.js.map +1 -1
  79. package/dist/src/core/living-docs/task-project-specific-generator.d.ts +7 -3
  80. package/dist/src/core/living-docs/task-project-specific-generator.d.ts.map +1 -1
  81. package/dist/src/core/living-docs/task-project-specific-generator.js +40 -24
  82. package/dist/src/core/living-docs/task-project-specific-generator.js.map +1 -1
  83. package/dist/src/core/plugin-loader.d.ts +7 -0
  84. package/dist/src/core/plugin-loader.d.ts.map +1 -1
  85. package/dist/src/core/plugin-loader.js +18 -1
  86. package/dist/src/core/plugin-loader.js.map +1 -1
  87. package/dist/src/core/serverless/platform-data-loader.d.ts +8 -0
  88. package/dist/src/core/serverless/platform-data-loader.d.ts.map +1 -1
  89. package/dist/src/core/serverless/platform-data-loader.js +14 -0
  90. package/dist/src/core/serverless/platform-data-loader.js.map +1 -1
  91. package/dist/src/core/serverless/types.d.ts +1 -1
  92. package/dist/src/core/serverless/types.d.ts.map +1 -1
  93. package/dist/src/core/status-line/status-line-manager.d.ts +6 -2
  94. package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
  95. package/dist/src/core/status-line/status-line-manager.js +11 -5
  96. package/dist/src/core/status-line/status-line-manager.js.map +1 -1
  97. package/dist/src/core/status-line/task-counter.d.ts +69 -0
  98. package/dist/src/core/status-line/task-counter.d.ts.map +1 -0
  99. package/dist/src/core/status-line/task-counter.js +107 -0
  100. package/dist/src/core/status-line/task-counter.js.map +1 -0
  101. package/dist/src/core/types/config.d.ts +23 -0
  102. package/dist/src/core/types/config.d.ts.map +1 -1
  103. package/dist/src/core/types/config.js +10 -0
  104. package/dist/src/core/types/config.js.map +1 -1
  105. package/dist/src/core/workflow/autonomous-executor.d.ts +111 -0
  106. package/dist/src/core/workflow/autonomous-executor.d.ts.map +1 -0
  107. package/dist/src/core/workflow/autonomous-executor.js +275 -0
  108. package/dist/src/core/workflow/autonomous-executor.js.map +1 -0
  109. package/dist/src/core/workflow/backlog-scanner.d.ts +94 -0
  110. package/dist/src/core/workflow/backlog-scanner.d.ts.map +1 -0
  111. package/dist/src/core/workflow/backlog-scanner.js +170 -0
  112. package/dist/src/core/workflow/backlog-scanner.js.map +1 -0
  113. package/dist/src/core/workflow/command-invoker.d.ts +86 -0
  114. package/dist/src/core/workflow/command-invoker.d.ts.map +1 -0
  115. package/dist/src/core/workflow/command-invoker.js +131 -0
  116. package/dist/src/core/workflow/command-invoker.js.map +1 -0
  117. package/dist/src/core/workflow/cost-estimator.d.ts +120 -0
  118. package/dist/src/core/workflow/cost-estimator.d.ts.map +1 -0
  119. package/dist/src/core/workflow/cost-estimator.js +222 -0
  120. package/dist/src/core/workflow/cost-estimator.js.map +1 -0
  121. package/dist/src/core/workflow/index.d.ts +20 -0
  122. package/dist/src/core/workflow/index.d.ts.map +1 -0
  123. package/dist/src/core/workflow/index.js +24 -0
  124. package/dist/src/core/workflow/index.js.map +1 -0
  125. package/dist/src/core/workflow/state-manager.d.ts +107 -0
  126. package/dist/src/core/workflow/state-manager.d.ts.map +1 -0
  127. package/dist/src/core/workflow/state-manager.js +126 -0
  128. package/dist/src/core/workflow/state-manager.js.map +1 -0
  129. package/dist/src/core/workflow/workflow-orchestrator.d.ts +93 -0
  130. package/dist/src/core/workflow/workflow-orchestrator.d.ts.map +1 -0
  131. package/dist/src/core/workflow/workflow-orchestrator.js +195 -0
  132. package/dist/src/core/workflow/workflow-orchestrator.js.map +1 -0
  133. package/dist/src/init/ArchitecturePresenter.d.ts +47 -0
  134. package/dist/src/init/ArchitecturePresenter.d.ts.map +1 -0
  135. package/dist/src/init/ArchitecturePresenter.js +180 -0
  136. package/dist/src/init/ArchitecturePresenter.js.map +1 -0
  137. package/dist/src/init/InitFlow.d.ts.map +1 -1
  138. package/dist/src/init/InitFlow.js +30 -1
  139. package/dist/src/init/InitFlow.js.map +1 -1
  140. package/dist/src/init/architecture/CostEstimator.d.ts +52 -0
  141. package/dist/src/init/architecture/CostEstimator.d.ts.map +1 -0
  142. package/dist/src/init/architecture/CostEstimator.js +107 -0
  143. package/dist/src/init/architecture/CostEstimator.js.map +1 -0
  144. package/dist/src/init/architecture/InfrastructureMapper.d.ts +41 -0
  145. package/dist/src/init/architecture/InfrastructureMapper.d.ts.map +1 -0
  146. package/dist/src/init/architecture/InfrastructureMapper.js +140 -0
  147. package/dist/src/init/architecture/InfrastructureMapper.js.map +1 -0
  148. package/dist/src/init/architecture/ProjectGenerator.d.ts +44 -0
  149. package/dist/src/init/architecture/ProjectGenerator.d.ts.map +1 -0
  150. package/dist/src/init/architecture/ProjectGenerator.js +216 -0
  151. package/dist/src/init/architecture/ProjectGenerator.js.map +1 -0
  152. package/dist/src/init/architecture/types.d.ts +10 -10
  153. package/dist/src/init/research/src/config/types.d.ts +8 -8
  154. package/dist/src/metrics/dora-calculator.js +2 -2
  155. package/dist/src/metrics/dora-calculator.js.map +1 -1
  156. package/dist/src/utils/pricing-constants.d.ts +5 -2
  157. package/dist/src/utils/pricing-constants.d.ts.map +1 -1
  158. package/dist/src/utils/pricing-constants.js +3 -2
  159. package/dist/src/utils/pricing-constants.js.map +1 -1
  160. package/package.json +9 -8
  161. package/plugins/specweave/agents/infrastructure/AGENT.md +88 -46
  162. package/plugins/specweave/agents/pm/AGENT.md +58 -1
  163. package/plugins/specweave/commands/specweave-archive-features.md +1 -1
  164. package/plugins/specweave/commands/specweave-archive-increments.md +1 -1
  165. package/plugins/specweave/commands/specweave-check-hooks.md +5 -0
  166. package/plugins/specweave/commands/specweave-done.md +12 -0
  167. package/plugins/specweave/commands/specweave-plan.md +1 -1
  168. package/plugins/specweave/commands/specweave-progress.md +108 -379
  169. package/plugins/specweave/commands/specweave-reopen.md +1 -1
  170. package/plugins/specweave/commands/specweave-restore-feature.md +1 -1
  171. package/plugins/specweave/commands/specweave-sync-specs.md +20 -48
  172. package/plugins/specweave/hooks/lib/update-status-line.sh +44 -35
  173. package/plugins/specweave/hooks/lib/validate-spec-status.sh +163 -0
  174. package/plugins/specweave/hooks/user-prompt-submit.sh +17 -35
  175. package/plugins/specweave/lib/hooks/update-tasks-md.js +52 -9
  176. package/plugins/specweave/lib/hooks/update-tasks-md.ts +77 -16
  177. package/plugins/specweave/templates/iac/aws-lambda/defaults.json +24 -0
  178. package/plugins/specweave/templates/iac/aws-lambda/templates/README.md.hbs +260 -0
  179. package/plugins/specweave/templates/iac/aws-lambda/templates/environments/dev.tfvars.hbs +34 -0
  180. package/plugins/specweave/templates/iac/aws-lambda/templates/environments/prod.tfvars.hbs +37 -0
  181. package/plugins/specweave/templates/iac/aws-lambda/templates/environments/staging.tfvars.hbs +35 -0
  182. package/plugins/specweave/templates/iac/aws-lambda/templates/outputs.tf.hbs +77 -0
  183. package/plugins/specweave/templates/iac/aws-lambda/templates/providers.tf.hbs +36 -0
  184. package/plugins/specweave/templates/iac/aws-lambda/templates/variables.tf.hbs +115 -0
  185. package/plugins/specweave/templates/iac/azure-functions/defaults.json +25 -0
  186. package/plugins/specweave/templates/iac/azure-functions/templates/README.md.hbs +268 -0
  187. package/plugins/specweave/templates/iac/azure-functions/templates/environments/dev.tfvars.hbs +34 -0
  188. package/plugins/specweave/templates/iac/azure-functions/templates/environments/prod.tfvars.hbs +46 -0
  189. package/plugins/specweave/templates/iac/azure-functions/templates/environments/staging.tfvars.hbs +34 -0
  190. package/plugins/specweave/templates/iac/azure-functions/templates/main.tf.hbs +225 -0
  191. package/plugins/specweave/templates/iac/azure-functions/templates/outputs.tf.hbs +89 -0
  192. package/plugins/specweave/templates/iac/azure-functions/templates/provider.tf.hbs +27 -0
  193. package/plugins/specweave/templates/iac/azure-functions/templates/providers.tf.hbs +35 -0
  194. package/plugins/specweave/templates/iac/azure-functions/templates/variables.tf.hbs +124 -0
  195. package/plugins/specweave/templates/iac/firebase/defaults.json +29 -0
  196. package/plugins/specweave/templates/iac/firebase/templates/README.md.hbs +35 -0
  197. package/plugins/specweave/templates/iac/firebase/templates/environments/dev.tfvars.hbs +7 -0
  198. package/plugins/specweave/templates/iac/firebase/templates/environments/prod.tfvars.hbs +7 -0
  199. package/plugins/specweave/templates/iac/firebase/templates/environments/staging.tfvars.hbs +7 -0
  200. package/plugins/specweave/templates/iac/firebase/templates/main.tf.hbs +90 -0
  201. package/plugins/specweave/templates/iac/firebase/templates/outputs.tf.hbs +15 -0
  202. package/plugins/specweave/templates/iac/firebase/templates/providers.tf.hbs +23 -0
  203. package/plugins/specweave/templates/iac/firebase/templates/variables.tf.hbs +42 -0
  204. package/plugins/specweave/templates/iac/gcp-cloud-functions/defaults.json +26 -0
  205. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/README.md.hbs +299 -0
  206. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/environments/dev.tfvars.hbs +36 -0
  207. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/environments/prod.tfvars.hbs +48 -0
  208. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/environments/staging.tfvars.hbs +41 -0
  209. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/main.tf.hbs +192 -0
  210. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/outputs.tf.hbs +66 -0
  211. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/providers.tf.hbs +25 -0
  212. package/plugins/specweave/templates/iac/gcp-cloud-functions/templates/variables.tf.hbs +119 -0
  213. package/plugins/specweave/templates/iac/supabase/defaults.json +15 -0
  214. package/plugins/specweave/templates/iac/supabase/templates/README.md.hbs +46 -0
  215. package/plugins/specweave/templates/iac/supabase/templates/main.tf.hbs +50 -0
  216. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
  217. package/plugins/specweave-github/agents/github-manager/AGENT.md +39 -7
  218. package/plugins/specweave-github/commands/specweave-github-create-issue.md +5 -5
  219. package/plugins/specweave-github/lib/CodeValidator.js +195 -0
  220. package/plugins/specweave-github/lib/CodeValidator.ts +284 -0
  221. package/plugins/specweave-github/lib/ThreeLayerSyncManager.js +545 -0
  222. package/plugins/specweave-github/lib/ThreeLayerSyncManager.ts +809 -0
  223. package/plugins/specweave-github/lib/github-client-v2.js +29 -0
  224. package/plugins/specweave-github/lib/github-client-v2.ts +30 -0
  225. package/plugins/specweave-github/lib/task-sync.js +4 -0
  226. package/plugins/specweave-github/lib/task-sync.ts +7 -0
  227. package/plugins/specweave-github/lib/types.ts +38 -0
  228. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
  229. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +3222 -0
  230. package/src/templates/AGENTS.md.template +22 -1
  231. package/src/templates/CLAUDE.md.template +31 -0
  232. package/dist/src/core/living-docs/ThreeLayerSyncManager.d.ts +0 -116
  233. package/dist/src/core/living-docs/ThreeLayerSyncManager.d.ts.map +0 -1
  234. package/dist/src/core/living-docs/ThreeLayerSyncManager.js +0 -356
  235. package/dist/src/core/living-docs/ThreeLayerSyncManager.js.map +0 -1
@@ -0,0 +1,545 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { execFileNoThrow } from "../../../src/utils/execFileNoThrow.js";
4
+ import { CodeValidator } from "./CodeValidator.js";
5
+ class ThreeLayerSyncManager {
6
+ constructor(projectRoot = process.cwd()) {
7
+ this.userStoryFileCache = /* @__PURE__ */ new Map();
8
+ this.projectRoot = projectRoot;
9
+ this.codeValidator = new CodeValidator({ projectRoot });
10
+ }
11
+ /**
12
+ * Clear User Story file cache
13
+ * Call this when file structure changes or for testing
14
+ */
15
+ clearCache() {
16
+ this.userStoryFileCache.clear();
17
+ }
18
+ /**
19
+ * Sync from GitHub to Increment (Flow 1: GitHub → Living Docs → Increment)
20
+ *
21
+ * Triggered when stakeholder checks AC/Task checkbox in GitHub issue.
22
+ */
23
+ async syncGitHubToIncrement(issueNumber, incrementPath, livingDocsPath) {
24
+ const result = {
25
+ acsUpdated: 0,
26
+ tasksUpdated: 0,
27
+ tasksReopened: 0,
28
+ conflicts: [],
29
+ errors: []
30
+ };
31
+ try {
32
+ console.log(`\u{1F504} Syncing GitHub issue #${issueNumber} \u2192 Increment`);
33
+ const githubState = await this.fetchGitHubIssueState(issueNumber);
34
+ const githubAcs = this.extractAcsFromIssue(githubState.body);
35
+ const githubTasks = this.extractTasksFromIssue(githubState.body);
36
+ const acUpdatePromises = githubAcs.map(async (ac) => {
37
+ const userStoryPath = await this.findUserStoryFile(livingDocsPath, ac.userStoryId);
38
+ if (userStoryPath) {
39
+ await this.updateUserStoryAc(userStoryPath, ac.id, ac.completed);
40
+ result.acsUpdated++;
41
+ }
42
+ });
43
+ const taskUpdatePromises = githubTasks.map(async (task) => {
44
+ const userStoryPath = await this.findUserStoryFileForTask(livingDocsPath, task.id);
45
+ if (userStoryPath) {
46
+ await this.updateUserStoryTask(userStoryPath, task.id, task.completed);
47
+ result.tasksUpdated++;
48
+ }
49
+ });
50
+ await Promise.all([...acUpdatePromises, ...taskUpdatePromises]);
51
+ await this.updateIncrementAcsWithConflictResolution(incrementPath, githubAcs, result);
52
+ await this.updateIncrementTasksWithConflictResolution(incrementPath, githubTasks, result);
53
+ const completedTasks = githubTasks.filter((t) => t.completed);
54
+ const validationPromises = completedTasks.map(async (task) => {
55
+ const codeExists = await this.validateCodeExists(task);
56
+ if (!codeExists) {
57
+ console.log(`\u26A0\uFE0F Task ${task.id} marked complete but code missing - reopening`);
58
+ await this.reopenTask(task.id, incrementPath, livingDocsPath, issueNumber);
59
+ result.tasksReopened++;
60
+ }
61
+ });
62
+ await Promise.all(validationPromises);
63
+ console.log(`\u2705 Sync complete: ${result.acsUpdated} ACs, ${result.tasksUpdated} tasks updated`);
64
+ if (result.tasksReopened > 0) {
65
+ console.log(` ${result.tasksReopened} tasks reopened due to missing code`);
66
+ }
67
+ } catch (error) {
68
+ const errorMsg = error instanceof Error ? error.message : String(error);
69
+ result.errors.push(errorMsg);
70
+ console.error(`\u274C Error syncing GitHub \u2192 Increment:`, error);
71
+ }
72
+ return result;
73
+ }
74
+ /**
75
+ * Sync from Increment to GitHub (Flow 2: Increment → Living Docs → GitHub)
76
+ *
77
+ * Triggered when developer completes work and updates increment files.
78
+ */
79
+ async syncIncrementToGitHub(incrementPath, livingDocsPath, issueNumber) {
80
+ const result = {
81
+ acsUpdated: 0,
82
+ tasksUpdated: 0,
83
+ tasksReopened: 0,
84
+ conflicts: [],
85
+ errors: []
86
+ };
87
+ try {
88
+ console.log(`\u{1F504} Syncing Increment \u2192 GitHub issue #${issueNumber}`);
89
+ const specPath = path.join(incrementPath, "spec.md");
90
+ const tasksPath = path.join(incrementPath, "tasks.md");
91
+ const specContent = await fs.readFile(specPath, "utf-8");
92
+ const tasksContent = await fs.readFile(tasksPath, "utf-8");
93
+ const acs = this.parseAcceptanceCriteria(specContent);
94
+ const tasks = this.parseTasks(tasksContent);
95
+ const acsByUserStory = this.groupAcsByUserStory(acs);
96
+ for (const [userStoryId, userStoryAcs] of Object.entries(acsByUserStory)) {
97
+ const userStoryPath = await this.findUserStoryFile(livingDocsPath, userStoryId);
98
+ if (userStoryPath) {
99
+ await this.updateUserStoryAcs(userStoryPath, userStoryAcs);
100
+ result.acsUpdated += userStoryAcs.length;
101
+ const acIds = userStoryAcs.map((ac) => ac.id);
102
+ const userStoryTasks = this.filterTasksByAcIds(tasks, acIds);
103
+ await this.updateUserStoryTasks(userStoryPath, userStoryTasks);
104
+ result.tasksUpdated += userStoryTasks.length;
105
+ }
106
+ }
107
+ await this.updateGitHubIssue(issueNumber, acs, tasks);
108
+ console.log(`\u2705 Sync complete: ${result.acsUpdated} ACs, ${result.tasksUpdated} tasks updated`);
109
+ } catch (error) {
110
+ const errorMsg = error instanceof Error ? error.message : String(error);
111
+ result.errors.push(errorMsg);
112
+ console.error(`\u274C Error syncing Increment \u2192 GitHub:`, error);
113
+ }
114
+ return result;
115
+ }
116
+ /**
117
+ * Validate that code exists for completed task
118
+ * Uses CodeValidator for comprehensive validation
119
+ */
120
+ async validateCodeExists(task) {
121
+ const validationResult = await this.codeValidator.validateTask(task.title, task.id);
122
+ return validationResult.valid;
123
+ }
124
+ /**
125
+ * Reopen task if code validation fails
126
+ */
127
+ async reopenTask(taskId, incrementPath, livingDocsPath, issueNumber) {
128
+ const tasksPath = path.join(incrementPath, "tasks.md");
129
+ let tasksContent = await fs.readFile(tasksPath, "utf-8");
130
+ const taskRegex = new RegExp(`(### ${taskId}:[^#]*?)\\*\\*Completed\\*\\*:[^\\n]*\\n`, "s");
131
+ tasksContent = tasksContent.replace(taskRegex, "$1");
132
+ await fs.writeFile(tasksPath, tasksContent, "utf-8");
133
+ const userStoryPath = await this.findUserStoryFileForTask(livingDocsPath, taskId);
134
+ if (userStoryPath) {
135
+ await this.updateUserStoryTask(userStoryPath, taskId, false);
136
+ }
137
+ await this.addGitHubComment(
138
+ issueNumber,
139
+ `\u26A0\uFE0F Task ${taskId} reopened: Code validation failed (missing or empty file). Please implement the required code.`
140
+ );
141
+ }
142
+ /**
143
+ * Propagate completion from Tasks → ACs → User Stories (bottom-up)
144
+ */
145
+ async propagateCompletion(incrementPath, livingDocsPath) {
146
+ const specPath = path.join(incrementPath, "spec.md");
147
+ const tasksPath = path.join(incrementPath, "tasks.md");
148
+ const specContent = await fs.readFile(specPath, "utf-8");
149
+ const tasksContent = await fs.readFile(tasksPath, "utf-8");
150
+ const acs = this.parseAcceptanceCriteria(specContent);
151
+ const tasks = this.parseTasks(tasksContent);
152
+ for (const ac of acs) {
153
+ const acTasks = tasks.filter((t) => t.acIds.includes(ac.id));
154
+ const allTasksComplete = acTasks.length > 0 && acTasks.every((t) => t.completed);
155
+ if (allTasksComplete && !ac.completed) {
156
+ console.log(`\u2705 All tasks for ${ac.id} complete - marking AC as complete`);
157
+ await this.updateIncrementAc(incrementPath, ac.id, true);
158
+ const userStoryPath = await this.findUserStoryFile(livingDocsPath, ac.userStoryId);
159
+ if (userStoryPath) {
160
+ await this.updateUserStoryAc(userStoryPath, ac.id, true);
161
+ }
162
+ }
163
+ }
164
+ const acsByUserStory = this.groupAcsByUserStory(acs);
165
+ for (const [userStoryId, userStoryAcs] of Object.entries(acsByUserStory)) {
166
+ const allAcsComplete = userStoryAcs.every((ac) => ac.completed);
167
+ if (allAcsComplete) {
168
+ console.log(`\u2705 All ACs for ${userStoryId} complete - User Story complete`);
169
+ }
170
+ }
171
+ }
172
+ // ==================== Helper Methods ====================
173
+ /**
174
+ * Fetch GitHub issue state
175
+ */
176
+ async fetchGitHubIssueState(issueNumber) {
177
+ const result = await execFileNoThrow("gh", [
178
+ "issue",
179
+ "view",
180
+ String(issueNumber),
181
+ "--json",
182
+ "body,state"
183
+ ]);
184
+ if (result.stderr) {
185
+ throw new Error(`Failed to fetch GitHub issue: ${result.stderr}`);
186
+ }
187
+ return JSON.parse(result.stdout);
188
+ }
189
+ /**
190
+ * Extract ACs from GitHub issue body
191
+ */
192
+ extractAcsFromIssue(body) {
193
+ const acs = [];
194
+ const lines = body.split("\n");
195
+ const acRegex = /^- \[([ x])\] (AC-[A-Z0-9]+-\d+):\s*(.+)$/;
196
+ for (const line of lines) {
197
+ const match = line.match(acRegex);
198
+ if (match) {
199
+ const completed = match[1] === "x";
200
+ const id = match[2];
201
+ const description = match[3];
202
+ const userStoryMatch = id.match(/AC-([A-Z0-9]+)-\d+/);
203
+ const userStoryId = userStoryMatch ? userStoryMatch[1] : "";
204
+ acs.push({
205
+ id,
206
+ userStoryId,
207
+ description,
208
+ completed,
209
+ projects: [],
210
+ rawLine: line
211
+ });
212
+ }
213
+ }
214
+ return acs;
215
+ }
216
+ /**
217
+ * Extract Tasks from GitHub issue body
218
+ */
219
+ extractTasksFromIssue(body) {
220
+ const tasks = [];
221
+ const lines = body.split("\n");
222
+ const taskRegex = /^- \[([ x])\] (T-\d+):\s*(.+)$/;
223
+ for (const line of lines) {
224
+ const match = line.match(taskRegex);
225
+ if (match) {
226
+ const completed = match[1] === "x";
227
+ const id = match[2];
228
+ const title = match[3];
229
+ tasks.push({
230
+ id,
231
+ title,
232
+ completed,
233
+ acIds: []
234
+ });
235
+ }
236
+ }
237
+ return tasks;
238
+ }
239
+ /**
240
+ * Parse ACs from spec.md
241
+ */
242
+ parseAcceptanceCriteria(specContent) {
243
+ const acs = [];
244
+ const lines = specContent.split("\n");
245
+ const acRegex = /^- \[([ x])\] (AC-[A-Z0-9]+-\d+):\s*(.+)$/;
246
+ for (const line of lines) {
247
+ const match = line.match(acRegex);
248
+ if (match) {
249
+ const completed = match[1] === "x";
250
+ const id = match[2];
251
+ const description = match[3];
252
+ const userStoryMatch = id.match(/AC-([A-Z0-9]+)-\d+/);
253
+ const userStoryId = userStoryMatch ? userStoryMatch[1] : "";
254
+ acs.push({
255
+ id,
256
+ userStoryId,
257
+ description,
258
+ completed,
259
+ projects: [],
260
+ rawLine: line
261
+ });
262
+ }
263
+ }
264
+ return acs;
265
+ }
266
+ /**
267
+ * Parse Tasks from tasks.md
268
+ */
269
+ parseTasks(tasksContent) {
270
+ const tasks = [];
271
+ const lines = tasksContent.split("\n");
272
+ let currentTask = null;
273
+ for (const line of lines) {
274
+ const headerMatch = line.match(/^### (T-\d+):\s*(.+?)\s*\(P\d+\)$/);
275
+ if (headerMatch) {
276
+ if (currentTask && currentTask.id) {
277
+ tasks.push(currentTask);
278
+ }
279
+ currentTask = {
280
+ id: headerMatch[1],
281
+ title: headerMatch[2],
282
+ completed: false,
283
+ acIds: []
284
+ };
285
+ continue;
286
+ }
287
+ const completedMatch = line.match(/^\*\*Completed\*\*:\s*(.+)$/);
288
+ if (completedMatch && currentTask) {
289
+ currentTask.completed = true;
290
+ currentTask.completedDate = completedMatch[1];
291
+ }
292
+ const acMatch = line.match(/\*\*AC\*\*:\s*(.+)$/);
293
+ if (acMatch && currentTask) {
294
+ const acIds = acMatch[1].split(",").map((id) => id.trim());
295
+ currentTask.acIds = acIds;
296
+ }
297
+ }
298
+ if (currentTask && currentTask.id) {
299
+ tasks.push(currentTask);
300
+ }
301
+ return tasks;
302
+ }
303
+ /**
304
+ * Group ACs by User Story ID
305
+ */
306
+ groupAcsByUserStory(acs) {
307
+ const grouped = {};
308
+ for (const ac of acs) {
309
+ if (!grouped[ac.userStoryId]) {
310
+ grouped[ac.userStoryId] = [];
311
+ }
312
+ grouped[ac.userStoryId].push(ac);
313
+ }
314
+ return grouped;
315
+ }
316
+ /**
317
+ * Filter tasks by AC IDs
318
+ */
319
+ filterTasksByAcIds(tasks, acIds) {
320
+ return tasks.filter(
321
+ (task) => task.acIds.some((acId) => acIds.includes(acId))
322
+ );
323
+ }
324
+ /**
325
+ * Find User Story file by ID (with caching for performance)
326
+ */
327
+ async findUserStoryFile(livingDocsPath, userStoryId) {
328
+ const cacheKey = `${livingDocsPath}:${userStoryId}`;
329
+ if (this.userStoryFileCache.has(cacheKey)) {
330
+ return this.userStoryFileCache.get(cacheKey);
331
+ }
332
+ const specsPath = path.join(livingDocsPath, "internal", "specs");
333
+ const pattern = `**/us-*${userStoryId.toLowerCase()}*.md`;
334
+ const { glob } = await import("glob");
335
+ const files = await glob(pattern, { cwd: specsPath, absolute: true });
336
+ const result = files.length > 0 ? files[0] : null;
337
+ this.userStoryFileCache.set(cacheKey, result);
338
+ return result;
339
+ }
340
+ /**
341
+ * Find User Story file that contains specific task
342
+ */
343
+ async findUserStoryFileForTask(livingDocsPath, taskId) {
344
+ const specsPath = path.join(livingDocsPath, "internal", "specs");
345
+ const { glob } = await import("glob");
346
+ const files = await glob("**/us-*.md", { cwd: specsPath, absolute: true });
347
+ for (const file of files) {
348
+ const content = await fs.readFile(file, "utf-8");
349
+ if (content.includes(taskId)) {
350
+ return file;
351
+ }
352
+ }
353
+ return null;
354
+ }
355
+ /**
356
+ * Update AC in User Story file
357
+ */
358
+ async updateUserStoryAc(userStoryPath, acId, completed) {
359
+ let content = await fs.readFile(userStoryPath, "utf-8");
360
+ const checkboxState = completed ? "x" : " ";
361
+ const regex = new RegExp(`(- \\[)[ x](\\] ${acId}:)`, "g");
362
+ content = content.replace(regex, `$1${checkboxState}$2`);
363
+ await fs.writeFile(userStoryPath, content, "utf-8");
364
+ }
365
+ /**
366
+ * Update Task in User Story file
367
+ */
368
+ async updateUserStoryTask(userStoryPath, taskId, completed) {
369
+ let content = await fs.readFile(userStoryPath, "utf-8");
370
+ const checkboxState = completed ? "x" : " ";
371
+ const regex = new RegExp(`(- \\[)[ x](\\] ${taskId}:)`, "g");
372
+ content = content.replace(regex, `$1${checkboxState}$2`);
373
+ await fs.writeFile(userStoryPath, content, "utf-8");
374
+ }
375
+ /**
376
+ * Update multiple ACs in User Story file
377
+ */
378
+ async updateUserStoryAcs(userStoryPath, acs) {
379
+ let content = await fs.readFile(userStoryPath, "utf-8");
380
+ for (const ac of acs) {
381
+ const checkboxState = ac.completed ? "x" : " ";
382
+ const regex = new RegExp(`(- \\[)[ x](\\] ${ac.id}:)`, "g");
383
+ content = content.replace(regex, `$1${checkboxState}$2`);
384
+ }
385
+ await fs.writeFile(userStoryPath, content, "utf-8");
386
+ }
387
+ /**
388
+ * Update multiple Tasks in User Story file
389
+ */
390
+ async updateUserStoryTasks(userStoryPath, tasks) {
391
+ let content = await fs.readFile(userStoryPath, "utf-8");
392
+ for (const task of tasks) {
393
+ const checkboxState = task.completed ? "x" : " ";
394
+ const regex = new RegExp(`(- \\[)[ x](\\] ${task.id}:)`, "g");
395
+ content = content.replace(regex, `$1${checkboxState}$2`);
396
+ }
397
+ await fs.writeFile(userStoryPath, content, "utf-8");
398
+ }
399
+ /**
400
+ * Update AC in increment spec.md
401
+ */
402
+ async updateIncrementAc(incrementPath, acId, completed) {
403
+ const specPath = path.join(incrementPath, "spec.md");
404
+ let content = await fs.readFile(specPath, "utf-8");
405
+ const checkboxState = completed ? "x" : " ";
406
+ const regex = new RegExp(`(- \\[)[ x](\\] ${acId}:)`, "g");
407
+ content = content.replace(regex, `$1${checkboxState}$2`);
408
+ await fs.writeFile(specPath, content, "utf-8");
409
+ }
410
+ /**
411
+ * Update multiple ACs in increment spec.md
412
+ */
413
+ async updateIncrementAcs(incrementPath, acs) {
414
+ const specPath = path.join(incrementPath, "spec.md");
415
+ let content = await fs.readFile(specPath, "utf-8");
416
+ for (const ac of acs) {
417
+ const checkboxState = ac.completed ? "x" : " ";
418
+ const regex = new RegExp(`(- \\[)[ x](\\] ${ac.id}:)`, "g");
419
+ content = content.replace(regex, `$1${checkboxState}$2`);
420
+ }
421
+ await fs.writeFile(specPath, content, "utf-8");
422
+ }
423
+ /**
424
+ * Update multiple Tasks in increment tasks.md
425
+ */
426
+ async updateIncrementTasks(incrementPath, tasks) {
427
+ const tasksPath = path.join(incrementPath, "tasks.md");
428
+ let content = await fs.readFile(tasksPath, "utf-8");
429
+ for (const task of tasks) {
430
+ if (task.completed) {
431
+ const taskHeader = `### ${task.id}:`;
432
+ const completedMarker = `**Completed**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
433
+ if (!content.includes(`${taskHeader}`) || !content.includes(`**Completed**`)) {
434
+ const regex = new RegExp(`(### ${task.id}:[^#]*?)(###|$)`, "s");
435
+ content = content.replace(regex, `$1
436
+ ${completedMarker}
437
+
438
+ $2`);
439
+ }
440
+ }
441
+ }
442
+ await fs.writeFile(tasksPath, content, "utf-8");
443
+ }
444
+ /**
445
+ * Update ACs in increment with conflict resolution (Increment wins)
446
+ *
447
+ * If GitHub and Increment disagree, Increment state wins as it's the source of truth.
448
+ * Conflicts are logged for transparency.
449
+ */
450
+ async updateIncrementAcsWithConflictResolution(incrementPath, githubAcs, result) {
451
+ const specPath = path.join(incrementPath, "spec.md");
452
+ const currentContent = await fs.readFile(specPath, "utf-8");
453
+ const currentAcs = this.parseAcceptanceCriteria(currentContent);
454
+ let content = currentContent;
455
+ for (const githubAc of githubAcs) {
456
+ const currentAc = currentAcs.find((ac) => ac.id === githubAc.id);
457
+ if (currentAc && currentAc.completed !== githubAc.completed) {
458
+ const conflictMsg = `AC ${githubAc.id}: GitHub says ${githubAc.completed ? "complete" : "incomplete"}, Increment says ${currentAc.completed ? "complete" : "incomplete"} \u2192 Increment wins`;
459
+ result.conflicts.push(conflictMsg);
460
+ console.log(`\u26A0\uFE0F CONFLICT RESOLVED: ${conflictMsg}`);
461
+ continue;
462
+ }
463
+ const checkboxState = githubAc.completed ? "x" : " ";
464
+ const regex = new RegExp(`(- \\[)[ x](\\] ${githubAc.id}:)`, "g");
465
+ content = content.replace(regex, `$1${checkboxState}$2`);
466
+ }
467
+ await fs.writeFile(specPath, content, "utf-8");
468
+ }
469
+ /**
470
+ * Update Tasks in increment with conflict resolution (Increment wins)
471
+ */
472
+ async updateIncrementTasksWithConflictResolution(incrementPath, githubTasks, result) {
473
+ const tasksPath = path.join(incrementPath, "tasks.md");
474
+ const currentContent = await fs.readFile(tasksPath, "utf-8");
475
+ const currentTasks = this.parseTasks(currentContent);
476
+ let content = currentContent;
477
+ for (const githubTask of githubTasks) {
478
+ const currentTask = currentTasks.find((t) => t.id === githubTask.id);
479
+ if (currentTask && currentTask.completed !== githubTask.completed) {
480
+ const conflictMsg = `Task ${githubTask.id}: GitHub says ${githubTask.completed ? "complete" : "incomplete"}, Increment says ${currentTask.completed ? "complete" : "incomplete"} \u2192 Increment wins`;
481
+ result.conflicts.push(conflictMsg);
482
+ console.log(`\u26A0\uFE0F CONFLICT RESOLVED: ${conflictMsg}`);
483
+ continue;
484
+ }
485
+ if (githubTask.completed) {
486
+ const taskHeader = `### ${githubTask.id}:`;
487
+ const completedMarker = `**Completed**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
488
+ if (!content.includes(`${taskHeader}`) || !content.includes(`**Completed**`)) {
489
+ const regex = new RegExp(`(### ${githubTask.id}:[^#]*?)(###|$)`, "s");
490
+ content = content.replace(regex, `$1
491
+ ${completedMarker}
492
+
493
+ $2`);
494
+ }
495
+ }
496
+ }
497
+ await fs.writeFile(tasksPath, content, "utf-8");
498
+ }
499
+ /**
500
+ * Update GitHub issue with ACs and Tasks
501
+ */
502
+ async updateGitHubIssue(issueNumber, acs, tasks) {
503
+ const result = await execFileNoThrow("gh", [
504
+ "issue",
505
+ "view",
506
+ String(issueNumber),
507
+ "--json",
508
+ "body"
509
+ ]);
510
+ const { body } = JSON.parse(result.stdout);
511
+ let updatedBody = body;
512
+ for (const ac of acs) {
513
+ const checkboxState = ac.completed ? "x" : " ";
514
+ const regex = new RegExp(`(- \\[)[ x](\\] ${ac.id}:)`, "g");
515
+ updatedBody = updatedBody.replace(regex, `$1${checkboxState}$2`);
516
+ }
517
+ for (const task of tasks) {
518
+ const checkboxState = task.completed ? "x" : " ";
519
+ const regex = new RegExp(`(- \\[)[ x](\\] ${task.id}:)`, "g");
520
+ updatedBody = updatedBody.replace(regex, `$1${checkboxState}$2`);
521
+ }
522
+ await execFileNoThrow("gh", [
523
+ "issue",
524
+ "edit",
525
+ String(issueNumber),
526
+ "--body",
527
+ updatedBody
528
+ ]);
529
+ }
530
+ /**
531
+ * Add comment to GitHub issue
532
+ */
533
+ async addGitHubComment(issueNumber, comment) {
534
+ await execFileNoThrow("gh", [
535
+ "issue",
536
+ "comment",
537
+ String(issueNumber),
538
+ "--body",
539
+ comment
540
+ ]);
541
+ }
542
+ }
543
+ export {
544
+ ThreeLayerSyncManager
545
+ };