specweave 0.22.14 → 0.23.1

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 (211) hide show
  1. package/CLAUDE.md +178 -1
  2. package/dist/src/cli/commands/import-external.d.ts +22 -0
  3. package/dist/src/cli/commands/import-external.d.ts.map +1 -0
  4. package/dist/src/cli/commands/import-external.js +282 -0
  5. package/dist/src/cli/commands/import-external.js.map +1 -0
  6. package/dist/src/cli/commands/init.d.ts.map +1 -1
  7. package/dist/src/cli/commands/init.js +46 -0
  8. package/dist/src/cli/commands/init.js.map +1 -1
  9. package/dist/src/cli/helpers/github-repo-selector.d.ts +59 -0
  10. package/dist/src/cli/helpers/github-repo-selector.d.ts.map +1 -0
  11. package/dist/src/cli/helpers/github-repo-selector.js +265 -0
  12. package/dist/src/cli/helpers/github-repo-selector.js.map +1 -0
  13. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  14. package/dist/src/cli/helpers/issue-tracker/index.js +5 -17
  15. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  16. package/dist/src/config/types.d.ts +16 -16
  17. package/dist/src/core/increment/ac-status-manager.d.ts.map +1 -1
  18. package/dist/src/core/increment/ac-status-manager.js +4 -2
  19. package/dist/src/core/increment/ac-status-manager.js.map +1 -1
  20. package/dist/src/core/increment/completion-validator.d.ts +30 -1
  21. package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
  22. package/dist/src/core/increment/completion-validator.js +151 -3
  23. package/dist/src/core/increment/completion-validator.js.map +1 -1
  24. package/dist/src/core/increment/increment-archiver.d.ts +25 -0
  25. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  26. package/dist/src/core/increment/increment-archiver.js +130 -3
  27. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  28. package/dist/src/core/living-docs/feature-archiver.d.ts +37 -0
  29. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  30. package/dist/src/core/living-docs/feature-archiver.js +262 -18
  31. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  32. package/dist/src/core/living-docs/feature-id-manager.d.ts +17 -0
  33. package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
  34. package/dist/src/core/living-docs/feature-id-manager.js +25 -0
  35. package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
  36. package/dist/src/core/living-docs/living-docs-sync.d.ts +14 -0
  37. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  38. package/dist/src/core/living-docs/living-docs-sync.js +46 -0
  39. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  40. package/dist/src/core/repo-structure/repo-id-generator.d.ts +20 -0
  41. package/dist/src/core/repo-structure/repo-id-generator.d.ts.map +1 -1
  42. package/dist/src/core/repo-structure/repo-id-generator.js +44 -0
  43. package/dist/src/core/repo-structure/repo-id-generator.js.map +1 -1
  44. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  45. package/dist/src/core/repo-structure/repo-structure-manager.js +5 -2
  46. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  47. package/dist/src/core/sync/sync-event-logger.d.ts +15 -1
  48. package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -1
  49. package/dist/src/core/sync/sync-event-logger.js +39 -1
  50. package/dist/src/core/sync/sync-event-logger.js.map +1 -1
  51. package/dist/src/core/types/sync-config-validator.d.ts +57 -0
  52. package/dist/src/core/types/sync-config-validator.d.ts.map +1 -0
  53. package/dist/src/core/types/sync-config-validator.js +116 -0
  54. package/dist/src/core/types/sync-config-validator.js.map +1 -0
  55. package/dist/src/importers/duplicate-detector.d.ts +107 -0
  56. package/dist/src/importers/duplicate-detector.d.ts.map +1 -0
  57. package/dist/src/importers/duplicate-detector.js +189 -0
  58. package/dist/src/importers/duplicate-detector.js.map +1 -0
  59. package/dist/src/importers/import-coordinator.d.ts +15 -0
  60. package/dist/src/importers/import-coordinator.d.ts.map +1 -1
  61. package/dist/src/importers/import-coordinator.js +43 -1
  62. package/dist/src/importers/import-coordinator.js.map +1 -1
  63. package/dist/src/importers/item-converter.d.ts +5 -0
  64. package/dist/src/importers/item-converter.d.ts.map +1 -1
  65. package/dist/src/importers/item-converter.js +27 -2
  66. package/dist/src/importers/item-converter.js.map +1 -1
  67. package/dist/src/importers/rate-limiter.d.ts +128 -0
  68. package/dist/src/importers/rate-limiter.d.ts.map +1 -0
  69. package/dist/src/importers/rate-limiter.js +200 -0
  70. package/dist/src/importers/rate-limiter.js.map +1 -0
  71. package/dist/src/init/compliance/types.d.ts +2 -2
  72. package/dist/src/integrations/ado/ado-client.d.ts +6 -0
  73. package/dist/src/integrations/ado/ado-client.d.ts.map +1 -1
  74. package/dist/src/integrations/ado/ado-client.js +23 -0
  75. package/dist/src/integrations/ado/ado-client.js.map +1 -1
  76. package/dist/src/integrations/jira/jira-client.d.ts +6 -0
  77. package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
  78. package/dist/src/integrations/jira/jira-client.js +38 -0
  79. package/dist/src/integrations/jira/jira-client.js.map +1 -1
  80. package/dist/src/sync/external-item-sync-service.d.ts +150 -0
  81. package/dist/src/sync/external-item-sync-service.d.ts.map +1 -0
  82. package/dist/src/sync/external-item-sync-service.js +241 -0
  83. package/dist/src/sync/external-item-sync-service.js.map +1 -0
  84. package/dist/src/sync/format-preservation-sync.d.ts +90 -0
  85. package/dist/src/sync/format-preservation-sync.d.ts.map +1 -0
  86. package/dist/src/sync/format-preservation-sync.js +173 -0
  87. package/dist/src/sync/format-preservation-sync.js.map +1 -0
  88. package/dist/src/sync/index.d.ts +8 -0
  89. package/dist/src/sync/index.d.ts.map +1 -0
  90. package/dist/src/sync/index.js +6 -0
  91. package/dist/src/sync/index.js.map +1 -0
  92. package/dist/src/sync/sync-coordinator.d.ts +49 -0
  93. package/dist/src/sync/sync-coordinator.d.ts.map +1 -0
  94. package/dist/src/sync/sync-coordinator.js +248 -0
  95. package/dist/src/sync/sync-coordinator.js.map +1 -0
  96. package/dist/src/sync/sync-metadata.d.ts +75 -0
  97. package/dist/src/sync/sync-metadata.d.ts.map +1 -0
  98. package/dist/src/sync/sync-metadata.js +100 -0
  99. package/dist/src/sync/sync-metadata.js.map +1 -0
  100. package/dist/src/types/living-docs-us-file.d.ts +63 -0
  101. package/dist/src/types/living-docs-us-file.d.ts.map +1 -0
  102. package/dist/src/types/living-docs-us-file.js +27 -0
  103. package/dist/src/types/living-docs-us-file.js.map +1 -0
  104. package/dist/src/validators/format-preservation-validator.d.ts +127 -0
  105. package/dist/src/validators/format-preservation-validator.d.ts.map +1 -0
  106. package/dist/src/validators/format-preservation-validator.js +187 -0
  107. package/dist/src/validators/format-preservation-validator.js.map +1 -0
  108. package/package.json +3 -2
  109. package/plugins/specweave/.claude-plugin/plugin.json +20 -0
  110. package/plugins/specweave/commands/specweave-archive-features.md +11 -1
  111. package/plugins/specweave/commands/specweave-archive.md +51 -15
  112. package/plugins/specweave/commands/specweave-import-docs.md +88 -278
  113. package/plugins/specweave/commands/specweave-import-external.md +407 -0
  114. package/plugins/specweave/hooks/post-edit-spec.sh +94 -0
  115. package/plugins/specweave/hooks/post-increment-completion.sh +0 -0
  116. package/plugins/specweave/hooks/post-spec-update.sh +0 -0
  117. package/plugins/specweave/hooks/post-task-completion.sh +13 -3
  118. package/plugins/specweave/hooks/post-write-spec.sh +91 -0
  119. package/plugins/specweave/lib/hooks/auto-transition.js +1 -1
  120. package/plugins/specweave/lib/hooks/auto-transition.js.bak +50 -0
  121. package/plugins/specweave/lib/hooks/auto-transition.ts +1 -1
  122. package/plugins/specweave/lib/hooks/auto-transition.ts.bak +84 -0
  123. package/plugins/specweave/lib/hooks/git-diff-analyzer.d.js.bak +0 -0
  124. package/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts.bak +89 -0
  125. package/plugins/specweave/lib/hooks/git-diff-analyzer.js.bak +142 -0
  126. package/plugins/specweave/lib/hooks/git-diff-analyzer.ts.bak +269 -0
  127. package/plugins/specweave/lib/hooks/invoke-translator-skill.d.js.bak +0 -0
  128. package/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts.bak +60 -0
  129. package/plugins/specweave/lib/hooks/invoke-translator-skill.js +1 -1
  130. package/plugins/specweave/lib/hooks/invoke-translator-skill.js.bak +155 -0
  131. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +1 -1
  132. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts.bak +264 -0
  133. package/plugins/specweave/lib/hooks/prepare-reflection-context.d.js.bak +0 -0
  134. package/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts.bak +42 -0
  135. package/plugins/specweave/lib/hooks/prepare-reflection-context.js.bak +110 -0
  136. package/plugins/specweave/lib/hooks/prepare-reflection-context.ts.bak +178 -0
  137. package/plugins/specweave/lib/hooks/reflection-config-loader.d.js.bak +0 -0
  138. package/plugins/specweave/lib/hooks/reflection-config-loader.d.ts.bak +45 -0
  139. package/plugins/specweave/lib/hooks/reflection-config-loader.js.bak +92 -0
  140. package/plugins/specweave/lib/hooks/reflection-config-loader.ts.bak +156 -0
  141. package/plugins/specweave/lib/hooks/reflection-parser.d.js.bak +0 -0
  142. package/plugins/specweave/lib/hooks/reflection-parser.d.ts.bak +33 -0
  143. package/plugins/specweave/lib/hooks/reflection-parser.js.bak +301 -0
  144. package/plugins/specweave/lib/hooks/reflection-parser.ts.bak +484 -0
  145. package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.js.bak +0 -0
  146. package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts.bak +56 -0
  147. package/plugins/specweave/lib/hooks/reflection-prompt-builder.js.bak +182 -0
  148. package/plugins/specweave/lib/hooks/reflection-prompt-builder.ts.bak +306 -0
  149. package/plugins/specweave/lib/hooks/reflection-storage.d.js.bak +0 -0
  150. package/plugins/specweave/lib/hooks/reflection-storage.d.ts.bak +64 -0
  151. package/plugins/specweave/lib/hooks/reflection-storage.js.bak +231 -0
  152. package/plugins/specweave/lib/hooks/reflection-storage.ts.bak +369 -0
  153. package/plugins/specweave/lib/hooks/run-self-reflection.d.js.bak +0 -0
  154. package/plugins/specweave/lib/hooks/run-self-reflection.d.ts.bak +43 -0
  155. package/plugins/specweave/lib/hooks/run-self-reflection.js.bak +132 -0
  156. package/plugins/specweave/lib/hooks/run-self-reflection.ts.bak +258 -0
  157. package/plugins/specweave/lib/hooks/sync-cache.js.bak +294 -0
  158. package/plugins/specweave/lib/hooks/sync-living-docs.d.js.bak +1 -0
  159. package/plugins/specweave/lib/hooks/sync-living-docs.d.ts.bak +27 -0
  160. package/plugins/specweave/lib/hooks/sync-living-docs.js +35 -1
  161. package/plugins/specweave/lib/hooks/sync-living-docs.js.bak +339 -0
  162. package/plugins/specweave/lib/hooks/sync-us-tasks.js +179 -3
  163. package/plugins/specweave/lib/hooks/sync-us-tasks.js.bak +476 -0
  164. package/plugins/specweave/lib/hooks/translate-file.d.js.bak +0 -0
  165. package/plugins/specweave/lib/hooks/translate-file.d.ts.bak +59 -0
  166. package/plugins/specweave/lib/hooks/translate-file.js +1 -1
  167. package/plugins/specweave/lib/hooks/translate-file.js.bak +289 -0
  168. package/plugins/specweave/lib/hooks/translate-file.ts +1 -1
  169. package/plugins/specweave/lib/hooks/translate-file.ts.bak +428 -0
  170. package/plugins/specweave/lib/hooks/translate-living-docs.d.js.bak +0 -0
  171. package/plugins/specweave/lib/hooks/translate-living-docs.d.ts.bak +13 -0
  172. package/plugins/specweave/lib/hooks/translate-living-docs.js.bak +119 -0
  173. package/plugins/specweave/lib/hooks/translate-living-docs.ts.bak +224 -0
  174. package/plugins/specweave/lib/hooks/update-ac-status.js +1 -1
  175. package/plugins/specweave/lib/hooks/update-ac-status.js.bak +51 -0
  176. package/plugins/specweave/lib/hooks/update-ac-status.ts +1 -1
  177. package/plugins/specweave/lib/hooks/update-ac-status.ts.bak +103 -0
  178. package/plugins/specweave/lib/hooks/update-tasks-md.d.js.bak +1 -0
  179. package/plugins/specweave/lib/hooks/update-tasks-md.d.ts.bak +29 -0
  180. package/plugins/specweave/lib/hooks/update-tasks-md.js.bak +296 -0
  181. package/plugins/specweave/lib/hooks/update-tasks-md.ts.bak +489 -0
  182. package/plugins/specweave/lib/vendor/core/increment/ac-status-manager.d.ts +115 -0
  183. package/plugins/specweave/lib/vendor/core/increment/ac-status-manager.js +345 -0
  184. package/plugins/specweave/lib/vendor/core/increment/ac-status-manager.js.map +1 -0
  185. package/plugins/specweave/lib/vendor/core/increment/active-increment-manager.d.ts +106 -0
  186. package/plugins/specweave/lib/vendor/core/increment/active-increment-manager.js +220 -0
  187. package/plugins/specweave/lib/vendor/core/increment/active-increment-manager.js.map +1 -0
  188. package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.d.ts +60 -0
  189. package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js +192 -0
  190. package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js.map +1 -0
  191. package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.d.ts +52 -0
  192. package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js +276 -0
  193. package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js.map +1 -0
  194. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +163 -0
  195. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +541 -0
  196. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -0
  197. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +157 -0
  198. package/plugins/specweave/lib/vendor/core/types/increment-metadata.js +191 -0
  199. package/plugins/specweave/lib/vendor/core/types/increment-metadata.js.map +1 -0
  200. package/plugins/specweave/lib/vendor/generators/spec/task-parser.d.ts +95 -0
  201. package/plugins/specweave/lib/vendor/generators/spec/task-parser.js +301 -0
  202. package/plugins/specweave/lib/vendor/generators/spec/task-parser.js.map +1 -0
  203. package/plugins/specweave/lib/vendor/utils/logger.d.ts +48 -0
  204. package/plugins/specweave/lib/vendor/utils/logger.js +53 -0
  205. package/plugins/specweave/lib/vendor/utils/logger.js.map +1 -0
  206. package/plugins/specweave/lib/vendor/utils/translation.d.ts +187 -0
  207. package/plugins/specweave/lib/vendor/utils/translation.js +414 -0
  208. package/plugins/specweave/lib/vendor/utils/translation.js.map +1 -0
  209. package/plugins/specweave-github/commands/specweave-github-update-user-story.md +1 -1
  210. package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +1 -1
  211. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +531 -0
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * US-Task Synchronization Module
4
+ *
5
+ * Syncs task completion status from tasks.md to living docs User Story files.
6
+ * Updates:
7
+ * - Task lists in US files (replaces "No tasks defined")
8
+ * - AC checkboxes based on task completion
9
+ *
10
+ * Part of increment 0047-us-task-linkage implementation.
11
+ */
12
+
13
+ import fs from 'fs-extra';
14
+ import path from 'path';
15
+ import { parseTasksWithUSLinks, getAllTasks } from '../vendor/generators/spec/task-parser.js';
16
+ import { glob } from 'glob';
17
+ import { getCachedTasks, needsSync, recordSync, batchFileUpdates } from './sync-cache.js';
18
+
19
+ /**
20
+ * Sync tasks from tasks.md to living docs User Story files
21
+ *
22
+ * @param {string} incrementId - Increment ID (e.g., "0047-us-task-linkage")
23
+ * @param {string} projectRoot - Project root directory
24
+ * @param {string} featureId - Feature ID (e.g., "FS-047")
25
+ * @param {object} options - Sync options
26
+ * @returns {Promise<SyncResult>} Sync result
27
+ */
28
+ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureId, options = {}) {
29
+ try {
30
+ console.log(` 📋 Syncing tasks to living docs User Stories...`);
31
+
32
+ const tasksPath = path.join(projectRoot, '.specweave', 'increments', incrementId, 'tasks.md');
33
+
34
+ // Check if tasks.md exists
35
+ if (!fs.existsSync(tasksPath)) {
36
+ console.log(` ⚠️ tasks.md not found, skipping task sync`);
37
+ return { success: true, updatedFiles: [], errors: [] };
38
+ }
39
+
40
+ // Parse tasks with US linkage (with caching for performance)
41
+ let tasksByUS;
42
+ try {
43
+ tasksByUS = getCachedTasks(tasksPath, parseTasksWithUSLinks);
44
+ } catch (error) {
45
+ console.error(` ❌ Failed to parse tasks.md:`, error.message);
46
+ return { success: false, updatedFiles: [], errors: [error.message] };
47
+ }
48
+
49
+ const allTasks = getAllTasks(tasksByUS);
50
+ console.log(` ✓ Parsed ${allTasks.length} tasks from tasks.md`);
51
+
52
+ // Check for tasks without US linkage (backward compatibility)
53
+ const unassignedTasks = tasksByUS['unassigned'] || [];
54
+ if (unassignedTasks.length > 0) {
55
+ console.log(` ⚠️ ${unassignedTasks.length} tasks without User Story linkage (old format)`);
56
+ }
57
+
58
+ // Get project ID (default to "specweave" for now, can be enhanced later)
59
+ const projectId = getProjectId(projectRoot, incrementId) || 'specweave';
60
+
61
+ const updatedFiles = [];
62
+ const errors = [];
63
+ const filesToUpdate = []; // Batch file updates
64
+
65
+ // For each User Story with tasks, update its living docs file
66
+ for (const [usId, tasks] of Object.entries(tasksByUS)) {
67
+ if (usId === 'unassigned') continue; // Skip unassigned tasks
68
+
69
+ try {
70
+ // Find US file in living docs
71
+ const usFilePath = await findUSFile(projectRoot, projectId, featureId, usId);
72
+
73
+ if (!usFilePath) {
74
+ console.log(` ⚠️ Living docs file not found for ${usId}, skipping`);
75
+ continue;
76
+ }
77
+
78
+ // Incremental sync: Check if sync is needed
79
+ if (!needsSync(usFilePath, tasks, tasksPath)) {
80
+ console.log(` ⏭️ ${usId} unchanged, skipping sync`);
81
+ continue;
82
+ }
83
+
84
+ // Update US file with task list
85
+ const result = await updateUSFile(usFilePath, tasks, incrementId);
86
+
87
+ if (result.updated) {
88
+ filesToUpdate.push({ path: usFilePath, content: result.content });
89
+ recordSync(usFilePath, tasks); // Cache sync result for next run
90
+ console.log(` ✓ Prepared update for ${usId} (${tasks.length} tasks)`);
91
+ }
92
+ } catch (error) {
93
+ console.error(` ❌ Error updating ${usId}:`, error.message);
94
+ errors.push(`${usId}: ${error.message}`);
95
+ }
96
+ }
97
+
98
+ // Batch write all file updates (reduce I/O)
99
+ if (filesToUpdate.length > 0) {
100
+ await batchFileUpdates(filesToUpdate);
101
+ updatedFiles.push(...filesToUpdate.map(f => f.path));
102
+ }
103
+
104
+ if (updatedFiles.length > 0) {
105
+ console.log(` ✅ Updated ${updatedFiles.length} User Story file(s)`);
106
+ } else {
107
+ console.log(` ℹ️ No User Story files updated`);
108
+ }
109
+
110
+ return {
111
+ success: errors.length === 0,
112
+ updatedFiles,
113
+ errors
114
+ };
115
+ } catch (error) {
116
+ console.error(` ❌ US-Task sync failed:`, error);
117
+ return { success: false, updatedFiles: [], errors: [error.message] };
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Find User Story file in living docs
123
+ *
124
+ * @param {string} projectRoot - Project root
125
+ * @param {string} projectId - Project ID (e.g., "specweave")
126
+ * @param {string} featureId - Feature ID (e.g., "FS-047")
127
+ * @param {string} usId - User Story ID (e.g., "US-001")
128
+ * @returns {Promise<string|null>} Path to US file or null if not found
129
+ */
130
+ async function findUSFile(projectRoot, projectId, featureId, usId) {
131
+ // Pattern: .specweave/docs/internal/specs/{project}/{feature}/us-{id}-*.md
132
+ const pattern = path.join(
133
+ projectRoot,
134
+ '.specweave',
135
+ 'docs',
136
+ 'internal',
137
+ 'specs',
138
+ projectId,
139
+ featureId,
140
+ `${usId.toLowerCase()}-*.md`
141
+ );
142
+
143
+ const files = await glob(pattern);
144
+
145
+ if (files.length === 0) {
146
+ return null;
147
+ }
148
+
149
+ if (files.length > 1) {
150
+ console.log(` ⚠️ Multiple files found for ${usId}, using first: ${path.basename(files[0])}`);
151
+ }
152
+
153
+ return files[0];
154
+ }
155
+
156
+ /**
157
+ * Update User Story file with task list and AC checkboxes
158
+ *
159
+ * @param {string} usFilePath - Path to US markdown file
160
+ * @param {Array} tasks - Tasks linked to this US
161
+ * @param {string} incrementId - Increment ID
162
+ * @returns {Promise<{updated: boolean, content: string}>} Update result
163
+ */
164
+ async function updateUSFile(usFilePath, tasks, incrementId) {
165
+ let content = await fs.readFile(usFilePath, 'utf-8');
166
+ let updated = false;
167
+
168
+ // 1. Update origin badge (NEW - T-034)
169
+ const updatedOrigin = updateOriginBadge(content, usFilePath);
170
+ if (updatedOrigin !== content) {
171
+ content = updatedOrigin;
172
+ updated = true;
173
+ }
174
+
175
+ // 2. Update task list section
176
+ const taskList = generateTaskList(tasks, incrementId);
177
+ const newTasksSection = `## Tasks\n\n${taskList}`;
178
+
179
+ // Replace existing tasks section
180
+ if (content.includes('## Tasks')) {
181
+ // Replace everything from "## Tasks" to next "##" or end of file
182
+ const tasksRegex = /(## Tasks\n\n)(?:.*?)(?=\n## |\n---\n|$)/s;
183
+ const newContent = content.replace(tasksRegex, newTasksSection);
184
+
185
+ if (newContent !== content) {
186
+ content = newContent;
187
+ updated = true;
188
+ }
189
+ }
190
+
191
+ // 3. Update AC checkboxes based on task completion
192
+ const updatedACs = updateACCheckboxes(content, tasks);
193
+ if (updatedACs !== content) {
194
+ content = updatedACs;
195
+ updated = true;
196
+ }
197
+
198
+ return { updated, content };
199
+ }
200
+
201
+ /**
202
+ * Update origin badge in User Story file
203
+ * Detects origin from US ID (E suffix = external)
204
+ *
205
+ * @param {string} content - US file content
206
+ * @param {string} usFilePath - Path to US file
207
+ * @returns {string} Updated content with origin badge
208
+ */
209
+ function updateOriginBadge(content, usFilePath) {
210
+ // Extract US ID from filename (e.g., us-001e-title.md -> US-001E)
211
+ const filename = path.basename(usFilePath);
212
+ const usIdMatch = filename.match(/^(us-\d{3}e?)-/i);
213
+
214
+ if (!usIdMatch) {
215
+ return content; // Can't determine US ID, skip
216
+ }
217
+
218
+ const usId = usIdMatch[1].toUpperCase();
219
+ const isExternal = usId.endsWith('E');
220
+
221
+ // Validate origin immutability (prevent internal ↔ external changes)
222
+ const existingOrigin = extractExistingOrigin(content);
223
+ if (existingOrigin) {
224
+ const existingIsExternal = existingOrigin !== 'internal';
225
+ if (isExternal !== existingIsExternal) {
226
+ console.log(` ⚠️ Origin immutable: Cannot change ${usId} from ${existingOrigin} to ${isExternal ? 'external' : 'internal'}`);
227
+ return content; // Don't modify origin
228
+ }
229
+ }
230
+
231
+ // Determine origin badge
232
+ let originBadge;
233
+ if (isExternal) {
234
+ // Try to detect external source from metadata
235
+ const externalSource = detectExternalSource(content);
236
+ originBadge = getExternalOriginBadge(externalSource, content);
237
+ } else {
238
+ originBadge = '🏠 **Internal**';
239
+ }
240
+
241
+ // Check if origin badge already exists
242
+ const originPattern = /\*\*Origin\*\*:\s*.*/;
243
+ if (originPattern.test(content)) {
244
+ // Replace existing origin badge
245
+ return content.replace(originPattern, `**Origin**: ${originBadge}`);
246
+ }
247
+
248
+ // Add origin badge after frontmatter (if exists) or at beginning
249
+ const frontmatterEnd = content.match(/^---\n.*?\n---\n/s);
250
+ if (frontmatterEnd) {
251
+ const insertPos = frontmatterEnd[0].length;
252
+ return content.slice(0, insertPos) + `\n**Origin**: ${originBadge}\n\n` + content.slice(insertPos);
253
+ }
254
+
255
+ // No frontmatter, add at beginning after title
256
+ const titleMatch = content.match(/^#\s+.+\n/);
257
+ if (titleMatch) {
258
+ const insertPos = titleMatch[0].length;
259
+ return content.slice(0, insertPos) + `\n**Origin**: ${originBadge}\n\n` + content.slice(insertPos);
260
+ }
261
+
262
+ // Fallback: add at very beginning
263
+ return `**Origin**: ${originBadge}\n\n` + content;
264
+ }
265
+
266
+ /**
267
+ * Extract existing origin from content (for immutability validation)
268
+ *
269
+ * @param {string} content - US file content
270
+ * @returns {string|null} Existing origin (internal, github, jira, ado) or null
271
+ */
272
+ function extractExistingOrigin(content) {
273
+ const originMatch = content.match(/\*\*Origin\*\*:\s*(.+)/);
274
+ if (!originMatch) {
275
+ return null;
276
+ }
277
+
278
+ const originText = originMatch[1].toLowerCase();
279
+ if (originText.includes('internal')) {
280
+ return 'internal';
281
+ }
282
+ if (originText.includes('github')) {
283
+ return 'github';
284
+ }
285
+ if (originText.includes('jira')) {
286
+ return 'jira';
287
+ }
288
+ if (originText.includes('ado') || originText.includes('azure')) {
289
+ return 'ado';
290
+ }
291
+ if (originText.includes('external')) {
292
+ return 'external'; // Generic external
293
+ }
294
+
295
+ return null;
296
+ }
297
+
298
+ /**
299
+ * Detect external source from content metadata
300
+ *
301
+ * @param {string} content - US file content
302
+ * @returns {string} External source (github, jira, ado) or 'unknown'
303
+ */
304
+ function detectExternalSource(content) {
305
+ // Check frontmatter for external_source or externalSource
306
+ const frontmatterMatch = content.match(/^---\n(.*?)\n---/s);
307
+ if (frontmatterMatch) {
308
+ const frontmatter = frontmatterMatch[1];
309
+ if (frontmatter.includes('external_source: github') || frontmatter.includes('externalSource: github')) {
310
+ return 'github';
311
+ }
312
+ if (frontmatter.includes('external_source: jira') || frontmatter.includes('externalSource: jira')) {
313
+ return 'jira';
314
+ }
315
+ if (frontmatter.includes('external_source: ado') || frontmatter.includes('externalSource: ado')) {
316
+ return 'ado';
317
+ }
318
+ }
319
+
320
+ // Check for external ID patterns
321
+ if (content.includes('externalId: GH-') || content.includes('external_id: GH-')) {
322
+ return 'github';
323
+ }
324
+ if (content.includes('externalId: JIRA-') || content.includes('external_id: JIRA-')) {
325
+ return 'jira';
326
+ }
327
+ if (content.includes('externalId: ADO-') || content.includes('external_id: ADO-')) {
328
+ return 'ado';
329
+ }
330
+
331
+ return 'unknown';
332
+ }
333
+
334
+ /**
335
+ * Get external origin badge with link if available
336
+ *
337
+ * @param {string} source - External source (github, jira, ado)
338
+ * @param {string} content - US file content
339
+ * @returns {string} Origin badge markdown
340
+ */
341
+ function getExternalOriginBadge(source, content) {
342
+ // Extract external ID and URL from content
343
+ const externalIdMatch = content.match(/external_?[iI]d:\s*([^\n]+)/);
344
+ const externalUrlMatch = content.match(/external_?[uU]rl:\s*([^\n]+)/);
345
+
346
+ const externalId = externalIdMatch ? externalIdMatch[1].trim() : null;
347
+ const externalUrl = externalUrlMatch ? externalUrlMatch[1].trim() : null;
348
+
349
+ switch (source) {
350
+ case 'github':
351
+ if (externalId && externalUrl) {
352
+ return `🔗 [GitHub ${externalId}](${externalUrl})`;
353
+ }
354
+ return '🔗 **GitHub**';
355
+ case 'jira':
356
+ if (externalId && externalUrl) {
357
+ return `🎫 [JIRA ${externalId}](${externalUrl})`;
358
+ }
359
+ return '🎫 **JIRA**';
360
+ case 'ado':
361
+ if (externalId && externalUrl) {
362
+ return `📋 [ADO ${externalId}](${externalUrl})`;
363
+ }
364
+ return '📋 **Azure DevOps**';
365
+ default:
366
+ return '🔗 **External**';
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Generate task list markdown
372
+ *
373
+ * @param {Array} tasks - Tasks to list
374
+ * @param {string} incrementId - Increment ID
375
+ * @returns {string} Markdown task list
376
+ */
377
+ function generateTaskList(tasks, incrementId) {
378
+ if (tasks.length === 0) {
379
+ return '_No tasks defined for this user story_';
380
+ }
381
+
382
+ return tasks.map(task => {
383
+ const checkbox = task.status === 'completed' ? 'x' : ' ';
384
+ const link = `../../../../increments/${incrementId}/tasks.md#${task.id}`;
385
+ return `- [${checkbox}] [${task.id}](${link}): ${task.title}`;
386
+ }).join('\n');
387
+ }
388
+
389
+ /**
390
+ * Update AC checkboxes based on task completion
391
+ *
392
+ * @param {string} content - US file content
393
+ * @param {Array} tasks - Tasks linked to this US
394
+ * @returns {string} Updated content
395
+ */
396
+ function updateACCheckboxes(content, tasks) {
397
+ // Collect all AC-IDs satisfied by completed tasks
398
+ const satisfiedACs = new Set();
399
+
400
+ tasks.forEach(task => {
401
+ if (task.status === 'completed' && task.satisfiesACs) {
402
+ task.satisfiesACs.forEach(acId => satisfiedACs.add(acId));
403
+ }
404
+ });
405
+
406
+ // Update AC checkboxes
407
+ let updatedContent = content;
408
+
409
+ satisfiedACs.forEach(acId => {
410
+ // Replace: - [ ] **AC-US1-01** with - [x] **AC-US1-01**
411
+ const uncheckedRegex = new RegExp(`- \\[ \\] \\*\\*${escapeRegex(acId)}\\*\\*`, 'g');
412
+ updatedContent = updatedContent.replace(uncheckedRegex, `- [x] **${acId}**`);
413
+ });
414
+
415
+ // Also uncheck ACs that are no longer satisfied (tasks incomplete/pending)
416
+ // Extract all AC-IDs from content
417
+ const acPattern = /- \[[x ]\] \*\*(AC-US\d+-\d{2})\*\*/g;
418
+ let match;
419
+ const allACs = [];
420
+
421
+ while ((match = acPattern.exec(content)) !== null) {
422
+ allACs.push(match[1]);
423
+ }
424
+
425
+ // Uncheck ACs not in satisfiedACs set
426
+ allACs.forEach(acId => {
427
+ if (!satisfiedACs.has(acId)) {
428
+ const checkedRegex = new RegExp(`- \\[x\\] \\*\\*${escapeRegex(acId)}\\*\\*`, 'g');
429
+ updatedContent = updatedContent.replace(checkedRegex, `- [ ] **${acId}**`);
430
+ }
431
+ });
432
+
433
+ return updatedContent;
434
+ }
435
+
436
+ /**
437
+ * Escape special regex characters
438
+ */
439
+ function escapeRegex(str) {
440
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
441
+ }
442
+
443
+ /**
444
+ * Get project ID from metadata or config
445
+ *
446
+ * @param {string} projectRoot - Project root
447
+ * @param {string} incrementId - Increment ID
448
+ * @returns {string|null} Project ID or null
449
+ */
450
+ function getProjectId(projectRoot, incrementId) {
451
+ try {
452
+ // Try to get from metadata.json
453
+ const metadataPath = path.join(projectRoot, '.specweave', 'increments', incrementId, 'metadata.json');
454
+ if (fs.existsSync(metadataPath)) {
455
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
456
+ if (metadata.project) {
457
+ return metadata.project;
458
+ }
459
+ }
460
+
461
+ // Try to get from config.json
462
+ const configPath = path.join(projectRoot, '.specweave', 'config.json');
463
+ if (fs.existsSync(configPath)) {
464
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
465
+ if (config.defaultProject) {
466
+ return config.defaultProject;
467
+ }
468
+ }
469
+
470
+ // Default to "specweave" (single-project mode)
471
+ return 'specweave';
472
+ } catch (error) {
473
+ console.log(` ⚠️ Could not determine project ID, using default: "specweave"`);
474
+ return 'specweave';
475
+ }
476
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * File Translation CLI Utility
3
+ *
4
+ * Translates a single file from detected source language to target language
5
+ * using the translation utilities and LLM invocation.
6
+ *
7
+ * This script is called from:
8
+ * - Post-increment-planning hook (auto-translate spec.md, plan.md, tasks.md)
9
+ * - Post-task-completion hook (auto-translate living docs)
10
+ * - Manual /specweave:translate command
11
+ *
12
+ * Usage:
13
+ * node translate-file.js <file-path> [--target-lang en] [--preview]
14
+ *
15
+ * @see src/utils/translation.ts
16
+ * @see .specweave/increments/0006-llm-native-i18n/reports/DESIGN-POST-GENERATION-TRANSLATION.md
17
+ */
18
+ import { type SupportedLanguage } from '../../../../src/utils/translation.js';
19
+ /**
20
+ * CLI options
21
+ */
22
+ interface CLIOptions {
23
+ filePath: string;
24
+ targetLang: SupportedLanguage;
25
+ preview: boolean;
26
+ verbose: boolean;
27
+ }
28
+ /**
29
+ * Translation result
30
+ */
31
+ interface FileTranslationResult {
32
+ success: boolean;
33
+ filePath: string;
34
+ sourceLanguage: SupportedLanguage;
35
+ targetLanguage: SupportedLanguage;
36
+ warnings: string[];
37
+ cost: number;
38
+ tokensUsed: number;
39
+ preview?: string;
40
+ }
41
+ /**
42
+ * Main translation function
43
+ *
44
+ * @param options - CLI options
45
+ * @returns Translation result
46
+ */
47
+ export declare function translateFile(options: CLIOptions): Promise<FileTranslationResult>;
48
+ /**
49
+ * Batch translate multiple files
50
+ *
51
+ * @param filePaths - Array of file paths to translate
52
+ * @param targetLang - Target language
53
+ * @param preview - Preview mode
54
+ * @param verbose - Verbose output
55
+ * @returns Array of translation results
56
+ */
57
+ export declare function batchTranslateFiles(filePaths: string[], targetLang?: SupportedLanguage, preview?: boolean, verbose?: boolean): Promise<FileTranslationResult[]>;
58
+ export {};
59
+ //# sourceMappingURL=translate-file.d.ts.map
@@ -6,7 +6,7 @@ import {
6
6
  validateTranslation,
7
7
  getLanguageName,
8
8
  formatCost
9
- } from "../../../../dist/src/utils/translation.js";
9
+ } from "../vendor/utils/translation.js";
10
10
  async function translateFile(options) {
11
11
  const { filePath, targetLang, preview, verbose } = options;
12
12
  if (!await fs.pathExists(filePath)) {