speccrew 0.1.0

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 (153) hide show
  1. package/.speccrew/agents/speccrew-feature-designer.md +142 -0
  2. package/.speccrew/agents/speccrew-product-manager.md +61 -0
  3. package/.speccrew/agents/speccrew-system-designer.md +200 -0
  4. package/.speccrew/agents/speccrew-system-developer.md +238 -0
  5. package/.speccrew/agents/speccrew-task-worker.md +80 -0
  6. package/.speccrew/agents/speccrew-team-leader.md +92 -0
  7. package/.speccrew/agents/speccrew-test-manager.md +313 -0
  8. package/.speccrew/skills/speccrew-create-agents/SKILL.md +98 -0
  9. package/.speccrew/skills/speccrew-create-agents/templates/agents/designer-agent.md +54 -0
  10. package/.speccrew/skills/speccrew-create-agents/templates/agents/dev-agent.md +79 -0
  11. package/.speccrew/skills/speccrew-create-agents/templates/agents/test-agent.md +80 -0
  12. package/.speccrew/skills/speccrew-dev-backend/SKILL.md +205 -0
  13. package/.speccrew/skills/speccrew-dev-backend/templates/TASK-RECORD-TEMPLATE.md +118 -0
  14. package/.speccrew/skills/speccrew-dev-desktop/SKILL.md +258 -0
  15. package/.speccrew/skills/speccrew-dev-desktop/templates/TASK-RECORD-TEMPLATE.md +161 -0
  16. package/.speccrew/skills/speccrew-dev-frontend/SKILL.md +202 -0
  17. package/.speccrew/skills/speccrew-dev-frontend/templates/TASK-RECORD-TEMPLATE.md +115 -0
  18. package/.speccrew/skills/speccrew-dev-mobile/SKILL.md +200 -0
  19. package/.speccrew/skills/speccrew-dev-mobile/templates/TASK-RECORD-TEMPLATE.md +125 -0
  20. package/.speccrew/skills/speccrew-fd-api-contract/SKILL.md +73 -0
  21. package/.speccrew/skills/speccrew-fd-api-contract/templates/API-CONTRACT-TEMPLATE.md +96 -0
  22. package/.speccrew/skills/speccrew-fd-feature-design/SKILL.md +395 -0
  23. package/.speccrew/skills/speccrew-fd-feature-design/templates/FEATURE-SPEC-TEMPLATE.md +387 -0
  24. package/.speccrew/skills/speccrew-get-timestamp/SKILL.md +80 -0
  25. package/.speccrew/skills/speccrew-get-timestamp/scripts/get-timestamp.js +35 -0
  26. package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/SKILL.md +1116 -0
  27. package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE-FASTAPI.md +462 -0
  28. package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE-JAVA.md +480 -0
  29. package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE-NET.md +464 -0
  30. package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE.md +480 -0
  31. package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/MODULE-OVERVIEW-TEMPLATE.md +367 -0
  32. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/SKILL.md +667 -0
  33. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/STATUS-FORMATS.md +74 -0
  34. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/batch-orchestrator.js +176 -0
  35. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-next-batch.js +150 -0
  36. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-pending-features.js +106 -0
  37. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/mark-stale.js +249 -0
  38. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/process-batch-results.js +848 -0
  39. package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/update-feature-status.js +226 -0
  40. package/.speccrew/skills/speccrew-knowledge-bizs-init-features/SKILL.md +264 -0
  41. package/.speccrew/skills/speccrew-knowledge-bizs-init-features/examples/features.json +34 -0
  42. package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/generate-inventory.js +867 -0
  43. package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/test-inventory.js +26 -0
  44. package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/SKILL.md +165 -0
  45. package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/scripts/apply-module-mapping.js +208 -0
  46. package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/scripts/extract-module-summary.js +180 -0
  47. package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/scripts/reindex-modules.js +358 -0
  48. package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/SKILL.md +1055 -0
  49. package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-DESKTOP.md +303 -0
  50. package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-ELECTRON.md +327 -0
  51. package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-MINIAPP.md +292 -0
  52. package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-MOBILE.md +281 -0
  53. package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI.md +324 -0
  54. package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/SKILL.md +270 -0
  55. package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/templates/COMPONENT-PATTERN-TEMPLATE.md +33 -0
  56. package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/templates/LAYOUT-PATTERN-TEMPLATE.md +33 -0
  57. package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/templates/PAGE-TYPE-TEMPLATE.md +33 -0
  58. package/.speccrew/skills/speccrew-knowledge-graph-query/SKILL.md +229 -0
  59. package/.speccrew/skills/speccrew-knowledge-graph-query/scripts/graph-query.js +549 -0
  60. package/.speccrew/skills/speccrew-knowledge-graph-write/SKILL.md +181 -0
  61. package/.speccrew/skills/speccrew-knowledge-graph-write/scripts/graph-write.js +651 -0
  62. package/.speccrew/skills/speccrew-knowledge-module-summarize/SKILL.md +305 -0
  63. package/.speccrew/skills/speccrew-knowledge-module-summarize/templates/MODULE-OVERVIEW-TEMPLATE.md +400 -0
  64. package/.speccrew/skills/speccrew-knowledge-system-summarize/SKILL.md +351 -0
  65. package/.speccrew/skills/speccrew-knowledge-system-summarize/templates/SYSTEM-OVERVIEW-TEMPLATE.md +294 -0
  66. package/.speccrew/skills/speccrew-knowledge-techs-dispatch/SKILL.md +683 -0
  67. package/.speccrew/skills/speccrew-knowledge-techs-dispatch/STATUS-FORMATS.md +550 -0
  68. package/.speccrew/skills/speccrew-knowledge-techs-dispatch/templates/techs-manifest-EXAMPLE.json +35 -0
  69. package/.speccrew/skills/speccrew-knowledge-techs-generate/SKILL.md +1087 -0
  70. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/ARCHITECTURE-TEMPLATE.md +240 -0
  71. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/COLOR-SYSTEM-TEMPLATE.md +68 -0
  72. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/COMPONENT-LIBRARY-TEMPLATE.md +86 -0
  73. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-BUILD-TEMPLATE.md +466 -0
  74. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-DATA-TEMPLATE.md +432 -0
  75. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-DESIGN-TEMPLATE.md +1209 -0
  76. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-DEV-TEMPLATE.md +1433 -0
  77. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-SYSTEM-TEST-TEMPLATE.md +1052 -0
  78. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-UNIT-TEST-TEMPLATE.md +946 -0
  79. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/INDEX-TEMPLATE.md +29 -0
  80. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/PAGE-LAYOUTS-TEMPLATE.md +69 -0
  81. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/PAGE-TYPE-SUMMARY-TEMPLATE.md +74 -0
  82. package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/TECH-STACK-TEMPLATE.md +232 -0
  83. package/.speccrew/skills/speccrew-knowledge-techs-generate-conventions/SKILL.md +628 -0
  84. package/.speccrew/skills/speccrew-knowledge-techs-generate-ui-style/SKILL.md +392 -0
  85. package/.speccrew/skills/speccrew-knowledge-techs-index/SKILL.md +489 -0
  86. package/.speccrew/skills/speccrew-knowledge-techs-index/templates/INDEX-TEMPLATE.md +243 -0
  87. package/.speccrew/skills/speccrew-knowledge-techs-init/SKILL.md +269 -0
  88. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/SKILL.md +562 -0
  89. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/BUSINESS-COMPONENTS-TEMPLATE.md +171 -0
  90. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/COMMON-COMPONENTS-TEMPLATE.md +177 -0
  91. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/COMPONENT-INDIVIDUAL-TEMPLATE.md +80 -0
  92. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/COMPONENT-LIBRARY-TEMPLATE.md +118 -0
  93. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/LAYOUT-INDIVIDUAL-TEMPLATE.md +97 -0
  94. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/LAYOUT-PATTERNS-TEMPLATE.md +208 -0
  95. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/NAVIGATION-PATTERNS-TEMPLATE.md +157 -0
  96. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/PAGE-TYPE-INDIVIDUAL-TEMPLATE.md +123 -0
  97. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/PAGE-TYPE-SUMMARY-TEMPLATE.md +58 -0
  98. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/SPACING-TEMPLATE.md +119 -0
  99. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/STYLE-SYSTEM-TEMPLATE.md +117 -0
  100. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/TYPOGRAPHY-TEMPLATE.md +107 -0
  101. package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/UI-STYLE-GUIDE-TEMPLATE.md +171 -0
  102. package/.speccrew/skills/speccrew-pm-requirement-analysis/SKILL.md +434 -0
  103. package/.speccrew/skills/speccrew-pm-requirement-analysis/templates/BIZS-MODELING-TEMPLATE.md +332 -0
  104. package/.speccrew/skills/speccrew-pm-requirement-analysis/templates/PRD-TEMPLATE.md +200 -0
  105. package/.speccrew/skills/speccrew-pm-requirement-assess/SKILL.md +195 -0
  106. package/.speccrew/skills/speccrew-project-diagnosis/SKILL.md +208 -0
  107. package/.speccrew/skills/speccrew-project-diagnosis/templates/DIAGNOSIS-REPORT-TEMPLATE.md +202 -0
  108. package/.speccrew/skills/speccrew-sd-backend/SKILL.md +188 -0
  109. package/.speccrew/skills/speccrew-sd-backend/templates/INDEX-TEMPLATE.md +85 -0
  110. package/.speccrew/skills/speccrew-sd-backend/templates/SD-BACKEND-TEMPLATE.md +269 -0
  111. package/.speccrew/skills/speccrew-sd-desktop/SKILL.md +192 -0
  112. package/.speccrew/skills/speccrew-sd-desktop/templates/INDEX-TEMPLATE.md +271 -0
  113. package/.speccrew/skills/speccrew-sd-desktop/templates/SD-DESKTOP-TEMPLATE.md +673 -0
  114. package/.speccrew/skills/speccrew-sd-frontend/SKILL.md +176 -0
  115. package/.speccrew/skills/speccrew-sd-frontend/templates/INDEX-TEMPLATE.md +184 -0
  116. package/.speccrew/skills/speccrew-sd-frontend/templates/SD-FRONTEND-TEMPLATE.md +382 -0
  117. package/.speccrew/skills/speccrew-sd-mobile/SKILL.md +189 -0
  118. package/.speccrew/skills/speccrew-sd-mobile/templates/INDEX-TEMPLATE.md +219 -0
  119. package/.speccrew/skills/speccrew-sd-mobile/templates/SD-MOBILE-TEMPLATE.md +534 -0
  120. package/.speccrew/skills/speccrew-test-case-design/SKILL.md +284 -0
  121. package/.speccrew/skills/speccrew-test-case-design/templates/TEST-CASE-DESIGN-TEMPLATE.md +263 -0
  122. package/.speccrew/skills/speccrew-test-code-gen/SKILL.md +313 -0
  123. package/.speccrew/skills/speccrew-test-code-gen/templates/TEST-CODE-PLAN-TEMPLATE.md +180 -0
  124. package/.speccrew/skills/speccrew-test-execute/SKILL.md +283 -0
  125. package/.speccrew/skills/speccrew-test-execute/templates/BUG-REPORT-TEMPLATE.md +50 -0
  126. package/.speccrew/skills/speccrew-test-execute/templates/TEST-REPORT-TEMPLATE.md +57 -0
  127. package/.speccrew/skills/speccrew-workflow-diagnose/SKILL.md +155 -0
  128. package/LICENSE +21 -0
  129. package/README.ar.md +318 -0
  130. package/README.en.md +318 -0
  131. package/README.es.md +318 -0
  132. package/README.md +340 -0
  133. package/bin/cli.js +62 -0
  134. package/lib/commands/doctor.js +138 -0
  135. package/lib/commands/init.js +231 -0
  136. package/lib/commands/list.js +114 -0
  137. package/lib/commands/uninstall.js +117 -0
  138. package/lib/commands/update.js +351 -0
  139. package/lib/ide-adapters.js +73 -0
  140. package/lib/utils.js +104 -0
  141. package/package.json +28 -0
  142. package/workspace-template/docs/configs/document-templates.json +667 -0
  143. package/workspace-template/docs/configs/platform-mapping.json +194 -0
  144. package/workspace-template/docs/configs/tech-stack-mappings.json +313 -0
  145. package/workspace-template/docs/configs/validation-rules.json +87 -0
  146. package/workspace-template/docs/rules/mermaid-rule.md +114 -0
  147. package/workspace-template/docs/solutions/Agent/346/212/200/350/203/275/345/256/232/344/271/211+/351/234/200/346/261/202/346/226/207/346/241/243+UML/344/275/277/347/224/250/346/250/241/346/235/277/357/274/210ISA-95/345/205/255/346/256/265/345/274/217/350/236/215/345/220/210/347/211/210/357/274/211.md +586 -0
  148. package/workspace-template/docs/solutions/agent-knowledge-map.md +238 -0
  149. package/workspace-template/docs/solutions/bizs-knowledge-pipeline.md +678 -0
  150. package/workspace-template/docs/solutions/harness.md +410 -0
  151. package/workspace-template/docs/solutions/knowledge-incremental-sync-spec.md +943 -0
  152. package/workspace-template/docs/solutions/techs-knowledge-pipeline.md +803 -0
  153. package/workspace-template/docs/solutions/workspace-structure.md +318 -0
@@ -0,0 +1,848 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Batch process completed analysis results.
5
+ *
6
+ * Scans the completed/ directory for .done.json and .graph.json marker files,
7
+ * updates feature statuses, writes graph data, updates metadata, and cleans up.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { execFileSync } = require('child_process');
13
+
14
+ // Helper: Validate and normalize .done.json file content
15
+ function validateAndNormalizeDoneContent(content, doneFile) {
16
+ // Strip file extensions from fileName if present
17
+ if (content.fileName && /\.\w+$/.test(content.fileName)) {
18
+ const original = content.fileName;
19
+ content.fileName = content.fileName.replace(/\.\w+$/, '');
20
+ console.warn(`[WARN] .done.json file ${doneFile}: stripped extension from fileName "${original}" → "${content.fileName}"`);
21
+ }
22
+
23
+ // Validate required fields
24
+ if (!content.fileName) {
25
+ throw new Error(`Missing required field "fileName" in ${doneFile}`);
26
+ }
27
+ if (!content.sourceFile) {
28
+ throw new Error(`Missing required field "sourceFile" in ${doneFile}`);
29
+ }
30
+
31
+ // Validate sourcePath format if present
32
+ if (content.sourcePath && typeof content.sourcePath !== 'string') {
33
+ console.warn(`[WARN] .done.json file ${doneFile}: invalid sourcePath type, converting to string`);
34
+ content.sourcePath = String(content.sourcePath);
35
+ }
36
+
37
+ return content;
38
+ }
39
+
40
+ // Helper: Get project root directory (traverse upward to find speccrew-workspace or .git)
41
+ function getProjectRoot(startPath) {
42
+ let currentDir = path.resolve(startPath);
43
+ while (currentDir !== path.dirname(currentDir)) {
44
+ // Check for common project root markers
45
+ if (fs.existsSync(path.join(currentDir, 'speccrew-workspace')) ||
46
+ fs.existsSync(path.join(currentDir, '.git'))) {
47
+ return currentDir;
48
+ }
49
+ currentDir = path.dirname(currentDir);
50
+ }
51
+ // Fallback: return the parent of syncStatePath
52
+ return path.dirname(path.resolve(startPath));
53
+ }
54
+
55
+ // Helper: Find feature document path from features-{platform}.json
56
+ function findFeatureDocumentPath(syncStatePath, sourceFile, fileName, featureSourcePath) {
57
+ try {
58
+ // Read the source features JSON file
59
+ const sourceFilePath = path.join(syncStatePath, sourceFile);
60
+ if (!fs.existsSync(sourceFilePath)) {
61
+ return null;
62
+ }
63
+
64
+ const content = JSON.parse(fs.readFileSync(sourceFilePath, 'utf8'));
65
+ if (!content.features || !Array.isArray(content.features)) {
66
+ return null;
67
+ }
68
+
69
+ // Find matching feature by fileName and sourcePath
70
+ const feature = content.features.find(f => {
71
+ const nameMatch = f.fileName === fileName;
72
+ const pathMatch = featureSourcePath ? f.sourcePath === featureSourcePath : true;
73
+ return nameMatch && pathMatch;
74
+ });
75
+
76
+ // Fallback: match by fileName only if sourcePath mismatch (Worker may use different path format)
77
+ if (!feature && featureSourcePath) {
78
+ const fallbackFeature = content.features.find(f => f.fileName === fileName);
79
+ if (fallbackFeature) {
80
+ console.warn(`[WARN] sourcePath mismatch for ${fileName}: .done has "${featureSourcePath}", features JSON has "${fallbackFeature.sourcePath}". Using fileName-only match.`);
81
+ return fallbackFeature.documentPath || null;
82
+ }
83
+ }
84
+
85
+ return feature ? feature.documentPath : null;
86
+ } catch (error) {
87
+ console.warn(`[WARN] Failed to find document path for ${fileName}: ${error.message}`);
88
+ return null;
89
+ }
90
+ }
91
+
92
+ // Helper: Check if document exists for a feature
93
+ function checkDocumentExists(syncStatePath, sourceFile, fileName, featureSourcePath) {
94
+ const documentPath = findFeatureDocumentPath(syncStatePath, sourceFile, fileName, featureSourcePath);
95
+ if (!documentPath) {
96
+ return { exists: false, path: null };
97
+ }
98
+
99
+ const projectRoot = getProjectRoot(syncStatePath);
100
+ // documentPath is already a full relative path from project root (e.g. speccrew-workspace/knowledges/bizs/backend-ai/chat/Foo.md)
101
+ const fullPath = path.join(projectRoot, documentPath);
102
+ return { exists: fs.existsSync(fullPath), path: documentPath };
103
+ }
104
+
105
+ // Validate document existence for all analyzed features
106
+ function validateDocumentExistence(syncStatePath) {
107
+ console.error('=== Document Existence Validation ===');
108
+
109
+ const syncStateDir = path.resolve(syncStatePath);
110
+ if (!fs.existsSync(syncStateDir)) {
111
+ console.error(`SyncStatePath not found: ${syncStatePath}`);
112
+ process.exit(1);
113
+ }
114
+
115
+ // Find all features-{platform}.json files
116
+ const files = fs.readdirSync(syncStateDir).filter(f => f.startsWith('features-') && f.endsWith('.json'));
117
+
118
+ let totalAnalyzed = 0;
119
+ let missingDocs = 0;
120
+ const missingList = [];
121
+
122
+ for (const file of files) {
123
+ const filePath = path.join(syncStateDir, file);
124
+ try {
125
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
126
+ if (!content.features || !Array.isArray(content.features)) {
127
+ continue;
128
+ }
129
+
130
+ // Extract platform from filename (features-{platform}.json)
131
+ const platform = file.replace('features-', '').replace('.json', '');
132
+ const projectRoot = getProjectRoot(syncStatePath);
133
+
134
+ for (const feature of content.features) {
135
+ if (feature.analyzed === true || feature.analyzed === 'true') {
136
+ totalAnalyzed++;
137
+
138
+ const docPath = feature.documentPath;
139
+ // documentPath is already a full relative path from project root
140
+ const fullPath = docPath ? path.join(projectRoot, docPath) : null;
141
+
142
+ if (!docPath || !fs.existsSync(fullPath)) {
143
+ missingDocs++;
144
+ const missingInfo = {
145
+ platform: platform,
146
+ feature: feature.fileName || feature.id,
147
+ documentPath: docPath || 'N/A'
148
+ };
149
+ missingList.push(missingInfo);
150
+ console.warn(`[MISSING] ${missingInfo.platform}/${missingInfo.feature}: ${missingInfo.documentPath}`);
151
+ }
152
+ }
153
+ }
154
+ } catch (error) {
155
+ console.warn(`[WARN] Failed to process ${file}: ${error.message}`);
156
+ }
157
+ }
158
+
159
+ console.error('\n=== Validation Summary ===');
160
+ console.error(`Total analyzed features: ${totalAnalyzed}`);
161
+ console.error(`Missing documents: ${missingDocs}`);
162
+
163
+ if (missingList.length > 0) {
164
+ console.error('\nMissing documents list:');
165
+ for (const item of missingList) {
166
+ console.error(` - ${item.platform}/${item.feature}: ${item.documentPath}`);
167
+ }
168
+ process.exit(1);
169
+ } else {
170
+ console.error('All analyzed features have corresponding documents.');
171
+ process.exit(0);
172
+ }
173
+ }
174
+
175
+ // Parse command line arguments
176
+ function parseArgs() {
177
+ const args = process.argv.slice(2);
178
+ const result = {
179
+ syncStatePath: null,
180
+ graphRoot: null,
181
+ graphWriteScript: null,
182
+ platformId: null,
183
+ validateDocs: false
184
+ };
185
+
186
+ for (let i = 0; i < args.length; i++) {
187
+ const arg = args[i];
188
+ switch (arg) {
189
+ case '--syncStatePath':
190
+ case '-SyncStatePath':
191
+ result.syncStatePath = args[++i];
192
+ break;
193
+ case '--graphRoot':
194
+ case '-GraphRoot':
195
+ result.graphRoot = args[++i];
196
+ break;
197
+ case '--graphWriteScript':
198
+ case '-GraphWriteScript':
199
+ result.graphWriteScript = args[++i];
200
+ break;
201
+ case '--platformId':
202
+ case '-PlatformId':
203
+ result.platformId = args[++i];
204
+ break;
205
+ case '--validateDocs':
206
+ result.validateDocs = true;
207
+ break;
208
+ }
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ function main() {
215
+ const args = parseArgs();
216
+
217
+ // Handle --validateDocs mode: validate document existence and exit
218
+ if (args.validateDocs) {
219
+ if (!args.syncStatePath) {
220
+ console.error('Error: --syncStatePath is required for --validateDocs mode');
221
+ process.exit(1);
222
+ }
223
+ validateDocumentExistence(args.syncStatePath);
224
+ return;
225
+ }
226
+
227
+ if (!args.syncStatePath || !args.graphRoot || !args.graphWriteScript) {
228
+ console.error('Error: --syncStatePath, --graphRoot, and --graphWriteScript are required');
229
+ process.exit(1);
230
+ }
231
+
232
+ if (!args.platformId) {
233
+ console.error('Error: --platformId is required');
234
+ process.exit(1);
235
+ }
236
+
237
+ const platformId = args.platformId;
238
+
239
+ // Ensure paths are absolute
240
+ const syncStatePath = path.resolve(args.syncStatePath);
241
+ const graphRoot = path.resolve(args.graphRoot);
242
+ const graphWriteScript = path.resolve(args.graphWriteScript);
243
+
244
+ if (!fs.existsSync(syncStatePath)) {
245
+ console.error(`SyncStatePath not found: ${args.syncStatePath}`);
246
+ process.exit(1);
247
+ }
248
+ if (!fs.existsSync(graphRoot)) {
249
+ console.error(`GraphRoot not found, creating: ${args.graphRoot}`);
250
+ fs.mkdirSync(graphRoot, { recursive: true });
251
+ }
252
+ if (!fs.existsSync(graphWriteScript)) {
253
+ console.error(`GraphWriteScript not found: ${args.graphWriteScript}`);
254
+ process.exit(1);
255
+ }
256
+
257
+ const completedDir = path.join(syncStatePath, 'completed');
258
+
259
+ // Diagnostic logging for completed directory
260
+ console.error(`Scanning for .done.json files in: ${completedDir}`);
261
+ console.error(`Directory exists: ${fs.existsSync(completedDir)}`);
262
+
263
+ // Auto-create completed directory if it doesn't exist
264
+ if (!fs.existsSync(completedDir)) {
265
+ console.error(`[ERROR] completed directory does not exist: ${completedDir}`);
266
+ console.error(`[INFO] Auto-creating completed directory...`);
267
+ try {
268
+ fs.mkdirSync(completedDir, { recursive: true });
269
+ console.error(`[INFO] Successfully created completed directory`);
270
+ } catch (error) {
271
+ console.error(`[ERROR] Failed to create completed directory: ${error.message}`);
272
+ }
273
+ }
274
+
275
+ // Check for .done files and warn if none found
276
+ if (fs.existsSync(completedDir)) {
277
+ const allFiles = fs.readdirSync(completedDir);
278
+ const doneFiles = allFiles.filter(f => f.endsWith('.done.json'));
279
+ if (doneFiles.length === 0) {
280
+ console.warn(`[WARNING] No .done.json files found in completed directory. Workers may not have created marker files correctly.`);
281
+ console.warn(`[INFO] Files in completed directory: ${allFiles.length > 0 ? allFiles.join(', ') : '(empty)'}`);
282
+ } else {
283
+ console.error(`[INFO] Found ${doneFiles.length} .done.json file(s)`);
284
+ }
285
+ }
286
+
287
+ // Result tracking
288
+ let processedCount = 0;
289
+ let skippedFilesCount = 0;
290
+ let docMissingCount = 0; // Features skipped due to missing document
291
+ const modulesUpdated = [];
292
+ const errors = [];
293
+ const failedOperations = [];
294
+
295
+ // Track successfully processed files for cleanup
296
+ const successfulDoneFiles = new Set();
297
+ const successfulGraphFiles = new Set();
298
+
299
+ // Helper: Write error and continue
300
+ function writeErrorContinue(message) {
301
+ errors.push(message);
302
+ console.warn(`Warning: ${message}`);
303
+ }
304
+
305
+ // Helper: Record failed operation
306
+ function addFailedOperation(module, operation, errorMessage) {
307
+ failedOperations.push({
308
+ module: module,
309
+ operation: operation,
310
+ error: errorMessage
311
+ });
312
+ }
313
+
314
+ // Helper: Call external script (graph-write)
315
+ function callGraphWriteScript(action, module, tempFile, graphRootPath, platformId) {
316
+ const ext = path.extname(graphWriteScript).toLowerCase();
317
+ let command, commandArgs;
318
+
319
+ if (ext === '.ps1') {
320
+ command = 'powershell';
321
+ commandArgs = ['-File', graphWriteScript, '-Action', action, '-Module', module, '-File', tempFile, '-GraphRoot', graphRootPath, '-PlatformId', platformId];
322
+ } else {
323
+ command = 'node';
324
+ commandArgs = [graphWriteScript, '--action', action, '--module', module, '--file', tempFile, '--graphRoot', graphRootPath, '--platformId', platformId];
325
+ }
326
+
327
+ execFileSync(command, commandArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
328
+ }
329
+
330
+ // Helper: Update feature status using update-feature-status.js
331
+ function updateFeatureStatus(sourceFilePath, fileName, featureSourcePath, analyzed, setCompleted, analysisNotes) {
332
+ const updateStatusScript = path.join(path.dirname(__filename), 'update-feature-status.js');
333
+ const commandArgs = [
334
+ updateStatusScript,
335
+ '--sourceFile', sourceFilePath,
336
+ '--fileName', fileName,
337
+ '--analyzed', analyzed
338
+ ];
339
+
340
+ if (featureSourcePath) {
341
+ commandArgs.push('--featureSourcePath', featureSourcePath);
342
+ }
343
+ if (setCompleted) {
344
+ commandArgs.push('--setCompleted');
345
+ }
346
+ if (analysisNotes) {
347
+ commandArgs.push('--analysisNotes', analysisNotes);
348
+ }
349
+
350
+ execFileSync('node', commandArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
351
+ }
352
+
353
+ // ── Step 1: Process .done.json files and update status ────────────────────────────
354
+
355
+ // Fallback: try to parse key=value format when JSON parsing fails
356
+ function parseFallbackDone(rawContent, fileName) {
357
+ const result = {};
358
+ const lines = rawContent.split(/\r?\n/);
359
+ for (const line of lines) {
360
+ const match = line.match(/^\s*(\w+)\s*[=:]\s*(.+?)\s*$/);
361
+ if (match) {
362
+ result[match[1]] = match[2].replace(/^["']|["']$/g, '');
363
+ }
364
+ }
365
+ if (result.fileName || result.status) {
366
+ console.warn(`[WARN] Parsed .done.json file ${fileName} using fallback key=value format`);
367
+ return result;
368
+ }
369
+ return null;
370
+ }
371
+
372
+ // Fallback: recover minimal info from filename and context when .done.json is non-JSON
373
+ function recoverDoneFileFromContext(doneFile, completedDir, syncStatePath) {
374
+ const fileName = doneFile.replace(/\.done\.json$/, '');
375
+ console.warn(`[WARN] .done.json file is not valid JSON: ${doneFile}. Attempting recovery from filename...`);
376
+
377
+ // Try to get module from same-named .graph.json
378
+ let module = 'unknown';
379
+ const graphFilePath = path.join(completedDir, fileName + '.graph.json');
380
+ if (fs.existsSync(graphFilePath)) {
381
+ try {
382
+ const graphContent = JSON.parse(fs.readFileSync(graphFilePath, 'utf8'));
383
+ if (graphContent.module) {
384
+ module = graphContent.module;
385
+ }
386
+ } catch (e) {
387
+ // Ignore parse errors
388
+ }
389
+ }
390
+
391
+ // Try to match sourceFile from features-*.json files
392
+ let sourceFile = 'unknown';
393
+ try {
394
+ const files = fs.readdirSync(syncStatePath);
395
+ const featureFiles = files.filter(f => f.startsWith('features-') && f.endsWith('.json'));
396
+
397
+ for (const f of featureFiles) {
398
+ const filePath = path.join(syncStatePath, f);
399
+ try {
400
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
401
+ if (content.features && Array.isArray(content.features)) {
402
+ const found = content.features.find(feat => feat.fileName === fileName);
403
+ if (found) {
404
+ sourceFile = f;
405
+ break;
406
+ }
407
+ }
408
+ } catch (e) {
409
+ // Continue checking next file
410
+ }
411
+ }
412
+ } catch (e) {
413
+ // Ignore read errors
414
+ }
415
+
416
+ console.warn(`[WARN] Recovered minimal info: fileName=${fileName}, module=${module}, sourceFile=${sourceFile}. Some fields may be inaccurate.`);
417
+
418
+ return {
419
+ fileName: fileName,
420
+ sourceFile: sourceFile,
421
+ module: module,
422
+ sourcePath: null,
423
+ status: 'success',
424
+ analysisNotes: 'Auto-recovered from non-JSON .done.json file'
425
+ };
426
+ }
427
+
428
+ function processDoneFiles() {
429
+ if (!fs.existsSync(completedDir)) {
430
+ return;
431
+ }
432
+
433
+ const doneFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.done.json'));
434
+
435
+ for (const doneFile of doneFiles) {
436
+ let rawContent;
437
+ try {
438
+ const doneFilePath = path.join(completedDir, doneFile);
439
+ rawContent = fs.readFileSync(doneFilePath, 'utf8');
440
+ if (!rawContent || rawContent.trim() === '') {
441
+ console.warn(`Empty .done.json file detected: ${doneFile} - Worker may have failed to write content`);
442
+ skippedFilesCount++;
443
+ continue;
444
+ }
445
+ let content = JSON.parse(rawContent);
446
+
447
+ // Validate and normalize content
448
+ content = validateAndNormalizeDoneContent(content, doneFile);
449
+
450
+ const fileName = content.fileName;
451
+ const featureSourcePath = content.sourcePath;
452
+ const sourceFile = content.sourceFile;
453
+ const module = content.module;
454
+ let analysisNotes = content.analysisNotes;
455
+
456
+ if (!fileName || !sourceFile) {
457
+ console.warn(`Invalid .done.json file format: ${doneFile}`);
458
+ console.warn(` Expected fields: fileName (got: '${fileName}'), sourceFile (got: '${sourceFile}')`);
459
+ console.warn(` File content preview: ${rawContent.substring(0, Math.min(200, rawContent.length))}`);
460
+ continue;
461
+ }
462
+
463
+ // Warn if module field is missing or empty (non-blocking)
464
+ if (!module) {
465
+ console.warn(`[WARN] module field missing from .done.json file: ${doneFile}`);
466
+ }
467
+
468
+ // Check if document exists before marking as analyzed
469
+ const docCheck = checkDocumentExists(syncStatePath, sourceFile, fileName, featureSourcePath);
470
+ if (!docCheck.exists) {
471
+ const warnMsg = `[WARN: document missing at ${docCheck.path || 'unknown path'}]`;
472
+ console.warn(`Document not found for feature ${fileName}: ${warnMsg}`);
473
+ console.warn(`[SKIP] Feature ${fileName} NOT marked as analyzed - document must exist. .done.json file PRESERVED for retry.`);
474
+ // Do NOT mark as successful - preserve .done.json for retry
475
+ docMissingCount++;
476
+ continue;
477
+ }
478
+
479
+ // Build source file path (relative to SyncStatePath)
480
+ const sourceFilePath = path.join(syncStatePath, sourceFile);
481
+
482
+ // Call update-feature-status.js
483
+ updateFeatureStatus(sourceFilePath, fileName, featureSourcePath, 'true', true, analysisNotes);
484
+
485
+ processedCount++;
486
+ successfulDoneFiles.add(doneFile);
487
+ } catch (error) {
488
+ // Defensive check: if rawContent is undefined, file read itself failed
489
+ if (rawContent === undefined) {
490
+ writeErrorContinue(`Failed to read .done.json file ${doneFile}: ${error.message}`);
491
+ continue;
492
+ }
493
+ // Try fallback parsing for non-JSON format
494
+ const fallbackContent = parseFallbackDone(rawContent, doneFile);
495
+ if (fallbackContent) {
496
+ try {
497
+ // Validate and normalize fallback content
498
+ validateAndNormalizeDoneContent(fallbackContent, doneFile);
499
+
500
+ const fileName = fallbackContent.fileName;
501
+ const featureSourcePath = fallbackContent.sourcePath;
502
+ const sourceFile = fallbackContent.sourceFile;
503
+ let analysisNotes = fallbackContent.analysisNotes;
504
+
505
+ if (fileName && sourceFile) {
506
+ const sourceFilePath = path.join(syncStatePath, sourceFile);
507
+
508
+ // Check if document exists before marking as analyzed (fallback path)
509
+ const docCheck = checkDocumentExists(syncStatePath, sourceFile, fileName, featureSourcePath);
510
+ if (!docCheck.exists) {
511
+ const warnMsg = `[WARN: document missing at ${docCheck.path || 'unknown path'}]`;
512
+ console.warn(`Document not found for feature ${fileName}: ${warnMsg}`);
513
+ console.warn(`[SKIP] Feature ${fileName} NOT marked as analyzed (fallback path) - document must exist. .done.json file PRESERVED.`);
514
+ docMissingCount++;
515
+ continue;
516
+ }
517
+
518
+ updateFeatureStatus(sourceFilePath, fileName, featureSourcePath, 'true', true, analysisNotes);
519
+ processedCount++;
520
+ successfulDoneFiles.add(doneFile);
521
+ continue;
522
+ }
523
+ } catch (fallbackError) {
524
+ writeErrorContinue(`Failed to process .done.json file ${doneFile} (fallback): ${fallbackError.message}`);
525
+ continue;
526
+ }
527
+ }
528
+
529
+ // Final fallback: recover from filename and context
530
+ const recoveredContent = recoverDoneFileFromContext(doneFile, completedDir, syncStatePath);
531
+ if (recoveredContent && recoveredContent.fileName && recoveredContent.sourceFile !== 'unknown') {
532
+ try {
533
+ const fileName = recoveredContent.fileName;
534
+ const featureSourcePath = recoveredContent.sourcePath;
535
+ const sourceFile = recoveredContent.sourceFile;
536
+ let analysisNotes = recoveredContent.analysisNotes;
537
+
538
+ const sourceFilePath = path.join(syncStatePath, sourceFile);
539
+
540
+ // Check if document exists before marking as analyzed (recovery path)
541
+ const docCheck = checkDocumentExists(syncStatePath, sourceFile, fileName, featureSourcePath);
542
+ if (!docCheck.exists) {
543
+ const warnMsg = `[WARN: document missing at ${docCheck.path || 'unknown path'}]`;
544
+ console.warn(`Document not found for feature ${fileName}: ${warnMsg}`);
545
+ console.warn(`[SKIP] Feature ${fileName} NOT marked as analyzed (recovery path) - document must exist. .done.json file PRESERVED.`);
546
+ docMissingCount++;
547
+ continue;
548
+ }
549
+
550
+ updateFeatureStatus(sourceFilePath, fileName, featureSourcePath, 'true', true, analysisNotes);
551
+ processedCount++;
552
+ successfulDoneFiles.add(doneFile);
553
+ continue;
554
+ } catch (recoveryError) {
555
+ writeErrorContinue(`Failed to process .done.json file ${doneFile} (recovery): ${recoveryError.message}`);
556
+ continue;
557
+ }
558
+ }
559
+
560
+ writeErrorContinue(`Failed to process .done.json file ${doneFile}: ${error.message}`);
561
+ }
562
+ }
563
+ }
564
+
565
+ // ── Step 2: Collect .graph.json files and write by module ────────────────────
566
+
567
+ function processGraphFiles() {
568
+ if (!fs.existsSync(completedDir)) {
569
+ return;
570
+ }
571
+
572
+ const graphFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.graph.json'));
573
+
574
+ if (graphFiles.length === 0) {
575
+ return;
576
+ }
577
+
578
+ // Group by module
579
+ const moduleGroups = {};
580
+
581
+ for (const graphFile of graphFiles) {
582
+ try {
583
+ const graphFilePath = path.join(completedDir, graphFile);
584
+ const rawContent = fs.readFileSync(graphFilePath, 'utf8');
585
+ if (!rawContent || rawContent.trim() === '') {
586
+ console.warn(`Empty .graph.json file detected: ${graphFile} - Worker may have failed to write content`);
587
+ skippedFilesCount++;
588
+ continue;
589
+ }
590
+ const content = JSON.parse(rawContent);
591
+ let module = content.module;
592
+
593
+ if (!module) {
594
+ // Try to read module from corresponding .done.json file
595
+ const doneFileName = graphFile.replace('.graph.json', '.done.json');
596
+ const doneFilePath = path.join(completedDir, doneFileName);
597
+
598
+ if (fs.existsSync(doneFilePath)) {
599
+ try {
600
+ const doneRawContent = fs.readFileSync(doneFilePath, 'utf8');
601
+ const doneContent = JSON.parse(doneRawContent);
602
+ if (doneContent.module) {
603
+ module = doneContent.module;
604
+ content.module = module;
605
+ console.warn(`[WARN] .graph.json missing root-level "module" field, falling back to .done.json file: ${graphFile}`);
606
+ }
607
+ } catch (doneError) {
608
+ console.warn(`Failed to read module from .done.json file ${doneFileName}: ${doneError.message}`);
609
+ }
610
+ }
611
+
612
+ // If still no module, skip this file
613
+ if (!module) {
614
+ console.warn(`Cannot determine module for: ${graphFile}, skipping`);
615
+ console.warn(` Tried to read from .done.json file: ${doneFileName}`);
616
+ console.warn(` File content preview: ${rawContent.substring(0, Math.min(200, rawContent.length))}`);
617
+ continue;
618
+ }
619
+ }
620
+
621
+ const effectiveModule = content.module;
622
+
623
+ if (!moduleGroups[effectiveModule]) {
624
+ moduleGroups[effectiveModule] = {
625
+ nodes: [],
626
+ edges: [],
627
+ files: []
628
+ };
629
+ }
630
+
631
+ if (content.nodes && Array.isArray(content.nodes)) {
632
+ moduleGroups[effectiveModule].nodes.push(...content.nodes);
633
+ } else if (content.nodes !== undefined) {
634
+ console.warn(`[WARN] .graph.json "nodes" field is not an array in: ${graphFile}`);
635
+ }
636
+ if (content.edges && Array.isArray(content.edges)) {
637
+ moduleGroups[effectiveModule].edges.push(...content.edges);
638
+ } else if (content.edges !== undefined) {
639
+ console.warn(`[WARN] .graph.json "edges" field is not an array in: ${graphFile}`);
640
+ }
641
+ moduleGroups[effectiveModule].files.push(graphFilePath);
642
+ } catch (error) {
643
+ writeErrorContinue(`Failed to read .graph.json file ${graphFile}: ${error.message}`);
644
+ }
645
+ }
646
+
647
+ // Process each module
648
+ for (const module of Object.keys(moduleGroups)) {
649
+ const group = moduleGroups[module];
650
+ let tempFile = null;
651
+ let nodesOk = true;
652
+ let edgesOk = true;
653
+
654
+ try {
655
+ // Create temp file with merged data
656
+ const tempFileName = `temp-graph-${module}-${new Date().toISOString().replace(/[:.]/g, '')}.json`;
657
+ tempFile = path.join(require('os').tmpdir(), tempFileName);
658
+
659
+ const mergedData = {
660
+ nodes: group.nodes,
661
+ edges: group.edges
662
+ };
663
+
664
+ fs.writeFileSync(tempFile, JSON.stringify(mergedData, null, 2), 'utf8');
665
+
666
+ // Call graph-write script for nodes
667
+ if (group.nodes.length > 0) {
668
+ try {
669
+ callGraphWriteScript('add-nodes', module, tempFile, graphRoot, platformId);
670
+ } catch (error) {
671
+ nodesOk = false;
672
+ const errorMsg = `Failed to add nodes for module '${module}': ${error.message}`;
673
+ console.warn(errorMsg);
674
+ console.warn(` Parameters: Action=add-nodes, Module=${module}, File=${tempFile}, GraphRoot=${graphRoot}, PlatformId=${platformId}`);
675
+ addFailedOperation(module, 'add-nodes', error.message);
676
+ }
677
+ }
678
+
679
+ // Call graph-write script for edges
680
+ if (group.edges.length > 0) {
681
+ try {
682
+ callGraphWriteScript('add-edges', module, tempFile, graphRoot, platformId);
683
+ } catch (error) {
684
+ edgesOk = false;
685
+ const errorMsg = `Failed to add edges for module '${module}': ${error.message}`;
686
+ console.warn(errorMsg);
687
+ console.warn(` Parameters: Action=add-edges, Module=${module}, File=${tempFile}, GraphRoot=${graphRoot}, PlatformId=${platformId}`);
688
+ addFailedOperation(module, 'add-edges', error.message);
689
+ }
690
+ }
691
+
692
+ // Track updated module
693
+ if (!modulesUpdated.includes(module)) {
694
+ modulesUpdated.push(module);
695
+ }
696
+ } catch (error) {
697
+ writeErrorContinue(`Failed to write graph data for module '${module}': ${error.message}`);
698
+ } finally {
699
+ // Mark files as successful only if both nodes and edges were written successfully
700
+ if (nodesOk && edgesOk) {
701
+ for (const filePath of group.files) {
702
+ successfulGraphFiles.add(path.basename(filePath));
703
+ }
704
+ }
705
+ // Clean up temp file
706
+ if (tempFile && fs.existsSync(tempFile)) {
707
+ try {
708
+ fs.unlinkSync(tempFile);
709
+ } catch (e) {
710
+ // Ignore cleanup errors
711
+ }
712
+ }
713
+ }
714
+ }
715
+ }
716
+
717
+ // ── Step 3: Update metadata ──────────────────────────────────────────────────
718
+
719
+ function updateMetadata() {
720
+ try {
721
+ const ext = path.extname(graphWriteScript).toLowerCase();
722
+ let command, commandArgs;
723
+
724
+ if (ext === '.ps1') {
725
+ command = 'powershell';
726
+ commandArgs = ['-File', graphWriteScript, '-Action', 'update-meta', '-GraphRoot', graphRoot];
727
+ } else {
728
+ command = 'node';
729
+ commandArgs = [graphWriteScript, '--action', 'update-meta', '--graphRoot', graphRoot];
730
+ }
731
+
732
+ execFileSync(command, commandArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
733
+ } catch (error) {
734
+ const errorMsg = `Failed to update metadata: ${error.message}`;
735
+ console.warn(errorMsg);
736
+ console.warn(` Parameters: Action=update-meta, GraphRoot=${graphRoot}`);
737
+ addFailedOperation('N/A', 'update-meta', error.message);
738
+ }
739
+ }
740
+
741
+ // ── Step 4: Clean up marker files ────────────────────────────────────────────
742
+
743
+ function removeMarkerFiles() {
744
+ if (!fs.existsSync(completedDir)) {
745
+ return;
746
+ }
747
+
748
+ // Log cleanup plan before execution
749
+ console.error('=== Cleanup Plan ===');
750
+ console.error(`[INFO] Successfully processed .done.json files to remove: ${successfulDoneFiles.size}`);
751
+ console.error(`[INFO] Successfully processed .graph.json files to remove: ${successfulGraphFiles.size}`);
752
+
753
+ const allDoneFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.done.json'));
754
+ const failedDoneFiles = allDoneFiles.filter(f => !successfulDoneFiles.has(f));
755
+ if (failedDoneFiles.length > 0) {
756
+ console.error(`[INFO] Failed/unprocessed .done.json files to PRESERVE: ${failedDoneFiles.length}`);
757
+ for (const file of failedDoneFiles) {
758
+ console.error(` [PRESERVE] ${file}`);
759
+ }
760
+ }
761
+
762
+ const allGraphFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.graph.json'));
763
+ const failedGraphFiles = allGraphFiles.filter(f => !successfulGraphFiles.has(f));
764
+ if (failedGraphFiles.length > 0) {
765
+ console.error(`[INFO] Failed/unprocessed .graph.json files to PRESERVE: ${failedGraphFiles.length}`);
766
+ for (const file of failedGraphFiles) {
767
+ console.error(` [PRESERVE] ${file}`);
768
+ }
769
+ }
770
+ console.error('====================');
771
+
772
+ // Remove .done.json files (ONLY successfully processed ones)
773
+ // IMPORTANT: Failed .done.json files are PRESERVED for retry after fixing the issue
774
+ const doneFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.done.json'));
775
+ for (const file of doneFiles) {
776
+ if (successfulDoneFiles.has(file)) {
777
+ try {
778
+ const filePath = path.join(completedDir, file);
779
+ console.error(`[CLEANUP] Removing successfully processed .done.json file: ${file}`);
780
+ fs.unlinkSync(filePath);
781
+ } catch (error) {
782
+ writeErrorContinue(`Failed to remove .done.json file ${file}: ${error.message}`);
783
+ }
784
+ } else {
785
+ // PRESERVE failed .done.json files for later retry - DO NOT DELETE
786
+ console.warn(`[PRESERVE] Keeping failed .done.json file for retry: ${file}`);
787
+ }
788
+ }
789
+
790
+ // Remove .graph.json files (ONLY successfully processed ones)
791
+ // IMPORTANT: Failed .graph.json files are PRESERVED for retry after fixing the issue
792
+ const graphFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.graph.json'));
793
+ for (const file of graphFiles) {
794
+ if (successfulGraphFiles.has(file)) {
795
+ try {
796
+ const filePath = path.join(completedDir, file);
797
+ console.error(`[CLEANUP] Removing successfully processed .graph.json file: ${file}`);
798
+ fs.unlinkSync(filePath);
799
+ } catch (error) {
800
+ writeErrorContinue(`Failed to remove .graph.json file ${file}: ${error.message}`);
801
+ }
802
+ } else {
803
+ // PRESERVE failed .graph.json files for later retry - DO NOT DELETE
804
+ console.warn(`[PRESERVE] Keeping failed .graph.json file for retry: ${file}`);
805
+ }
806
+ }
807
+ }
808
+
809
+ // ── Main Execution ───────────────────────────────────────────────────────────
810
+
811
+ try {
812
+ processDoneFiles();
813
+ processGraphFiles();
814
+ updateMetadata();
815
+ removeMarkerFiles();
816
+ } catch (error) {
817
+ writeErrorContinue(`Unexpected error during batch processing: ${error.message}`);
818
+ }
819
+
820
+ // ── Output Summary ───────────────────────────────────────────────────────────
821
+
822
+ const output = {
823
+ processed: processedCount,
824
+ skipped: skippedFilesCount,
825
+ doc_missing: docMissingCount,
826
+ modules_updated: modulesUpdated,
827
+ errors: errors,
828
+ failed_operations: failedOperations
829
+ };
830
+
831
+ console.log(JSON.stringify(output, null, 2));
832
+
833
+ // Output failed operations summary if any
834
+ if (failedOperations.length > 0) {
835
+ console.warn('=== Failed Operations Summary ===');
836
+ for (const op of failedOperations) {
837
+ console.warn(` Module: ${op.module}, Operation: ${op.operation}, Error: ${op.error}`);
838
+ }
839
+ }
840
+
841
+ // Exit with error code if features were skipped due to missing documents
842
+ if (docMissingCount > 0) {
843
+ console.error(`\n[ERROR] ${docMissingCount} feature(s) skipped - documents not found. .done.json files preserved in completed/ for retry.`);
844
+ process.exit(1);
845
+ }
846
+ }
847
+
848
+ main();