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