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.
- package/.speccrew/agents/speccrew-task-worker.md +1 -1
- package/.speccrew/agents/speccrew-team-leader.md +336 -189
- package/.speccrew/skills/speccrew-agentflow-manager/SKILL.md +161 -0
- package/.speccrew/skills/speccrew-agentflow-manager/workflow.agentflow.xml +347 -0
- package/.speccrew/skills/speccrew-deploy-build/SKILL.md +3 -56
- package/.speccrew/skills/speccrew-deploy-build/workflow.agentflow.xml +125 -0
- package/.speccrew/skills/speccrew-deploy-migrate/SKILL.md +3 -64
- package/.speccrew/skills/speccrew-deploy-migrate/workflow.agentflow.xml +135 -0
- package/.speccrew/skills/speccrew-deploy-smoke-test/SKILL.md +4 -156
- package/.speccrew/skills/speccrew-deploy-smoke-test/workflow.agentflow.xml +178 -0
- package/.speccrew/skills/speccrew-deploy-startup/SKILL.md +3 -135
- package/.speccrew/skills/speccrew-deploy-startup/workflow.agentflow.xml +223 -0
- package/.speccrew/skills/speccrew-dev-backend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-backend/workflow.agentflow.xml +254 -0
- package/.speccrew/skills/speccrew-dev-desktop-electron/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-desktop-electron/workflow.agentflow.xml +259 -0
- package/.speccrew/skills/speccrew-dev-desktop-tauri/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-desktop-tauri/workflow.agentflow.xml +245 -0
- package/.speccrew/skills/speccrew-dev-frontend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-frontend/workflow.agentflow.xml +262 -0
- package/.speccrew/skills/speccrew-dev-mobile/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-mobile/workflow.agentflow.xml +244 -0
- package/.speccrew/skills/speccrew-dev-review-backend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-backend/workflow.agentflow.xml +251 -0
- package/.speccrew/skills/speccrew-dev-review-desktop/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-desktop/workflow.agentflow.xml +214 -0
- package/.speccrew/skills/speccrew-dev-review-frontend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-frontend/workflow.agentflow.xml +213 -0
- package/.speccrew/skills/speccrew-dev-review-mobile/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-mobile/workflow.agentflow.xml +214 -0
- package/.speccrew/skills/speccrew-fd-api-contract/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-fd-api-contract/workflow.agentflow.xml +222 -0
- package/.speccrew/skills/speccrew-fd-feature-analyze/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-fd-feature-analyze/workflow.agentflow.xml +223 -0
- package/.speccrew/skills/speccrew-fd-feature-design/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-fd-feature-design/workflow.agentflow.xml +322 -0
- package/.speccrew/skills/speccrew-get-timestamp/SKILL.md +3 -39
- package/.speccrew/skills/speccrew-get-timestamp/workflow.agentflow.xml +43 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/SKILL.md +57 -508
- package/.speccrew/skills/{speccrew-knowledge-bizs-api-analyze-xml/SKILL.md → speccrew-knowledge-bizs-api-analyze/workflow.agentflow.xml} +1 -154
- package/.speccrew/skills/speccrew-knowledge-bizs-api-graph/SKILL.md +73 -283
- package/.speccrew/skills/{speccrew-knowledge-bizs-api-graph-xml/SKILL.md → speccrew-knowledge-bizs-api-graph/workflow.agentflow.xml} +0 -298
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/SKILL.md +931 -801
- package/.speccrew/skills/{speccrew-knowledge-bizs-dispatch-xml/SKILL.md → speccrew-knowledge-bizs-dispatch/workflow.agentflow.xml} +42 -272
- package/.speccrew/skills/speccrew-knowledge-bizs-identify-entries/SKILL.md +263 -71
- package/.speccrew/skills/{speccrew-knowledge-bizs-identify-entries-xml/SKILL.md → speccrew-knowledge-bizs-identify-entries/workflow.agentflow.xml} +8 -184
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/SKILL.md +200 -181
- package/.speccrew/skills/{speccrew-knowledge-bizs-init-features-xml/SKILL.md → speccrew-knowledge-bizs-init-features/workflow.agentflow.xml} +7 -134
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/SKILL.md +5 -89
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/workflow.agentflow.xml +129 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/SKILL.md +454 -326
- package/.speccrew/skills/{speccrew-knowledge-bizs-ui-analyze-xml/SKILL.md → speccrew-knowledge-bizs-ui-analyze/workflow.agentflow.xml} +8 -128
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-graph/SKILL.md +302 -247
- package/.speccrew/skills/{speccrew-knowledge-bizs-ui-graph-xml/SKILL.md → speccrew-knowledge-bizs-ui-graph/workflow.agentflow.xml} +7 -199
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/SKILL.md +267 -156
- package/.speccrew/skills/{speccrew-knowledge-bizs-ui-style-extract-xml/SKILL.md → speccrew-knowledge-bizs-ui-style-extract/workflow.agentflow.xml} +7 -151
- package/.speccrew/skills/speccrew-knowledge-graph-query/SKILL.md +3 -122
- package/.speccrew/skills/speccrew-knowledge-graph-query/workflow.agentflow.xml +106 -0
- package/.speccrew/skills/speccrew-knowledge-graph-write/SKILL.md +3 -80
- package/.speccrew/skills/speccrew-knowledge-graph-write/workflow.agentflow.xml +152 -0
- package/.speccrew/skills/speccrew-knowledge-module-summarize/SKILL.md +371 -265
- package/.speccrew/skills/{speccrew-knowledge-module-summarize-xml/SKILL.md → speccrew-knowledge-module-summarize/workflow.agentflow.xml} +7 -197
- package/.speccrew/skills/speccrew-knowledge-system-summarize/SKILL.md +45 -333
- package/.speccrew/skills/{speccrew-knowledge-system-summarize-xml/SKILL.md → speccrew-knowledge-system-summarize/workflow.agentflow.xml} +0 -177
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/SKILL.md +174 -727
- package/.speccrew/skills/{speccrew-knowledge-techs-dispatch-xml/SKILL.md → speccrew-knowledge-techs-dispatch/workflow.agentflow.xml} +10 -351
- package/.speccrew/skills/speccrew-knowledge-techs-generate/SKILL.md +20 -150
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-xml/SKILL.md → speccrew-knowledge-techs-generate/workflow.agentflow.xml} +0 -169
- package/.speccrew/skills/speccrew-knowledge-techs-generate-conventions/SKILL.md +75 -587
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-conventions-xml/SKILL.md → speccrew-knowledge-techs-generate-conventions/workflow.agentflow.xml} +0 -153
- package/.speccrew/skills/speccrew-knowledge-techs-generate-quality/SKILL.md +463 -297
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-quality-xml/SKILL.md → speccrew-knowledge-techs-generate-quality/workflow.agentflow.xml} +0 -164
- package/.speccrew/skills/speccrew-knowledge-techs-generate-ui-style/SKILL.md +57 -292
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-ui-style-xml/SKILL.md → speccrew-knowledge-techs-generate-ui-style/workflow.agentflow.xml} +2 -193
- package/.speccrew/skills/speccrew-knowledge-techs-index/SKILL.md +49 -335
- package/.speccrew/skills/{speccrew-knowledge-techs-index-xml/SKILL.md → speccrew-knowledge-techs-index/workflow.agentflow.xml} +0 -167
- package/.speccrew/skills/speccrew-knowledge-techs-init/SKILL.md +28 -109
- package/.speccrew/skills/{speccrew-knowledge-techs-init-xml/SKILL.md → speccrew-knowledge-techs-init/workflow.agentflow.xml} +0 -189
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/SKILL.md +3 -487
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/workflow.agentflow.xml +278 -0
- package/.speccrew/skills/speccrew-pm-knowledge-detector/SKILL.md +3 -71
- package/.speccrew/skills/speccrew-pm-knowledge-detector/workflow.agentflow.xml +108 -0
- package/.speccrew/skills/speccrew-pm-module-initializer/SKILL.md +3 -107
- package/.speccrew/skills/speccrew-pm-module-initializer/workflow.agentflow.xml +139 -0
- package/.speccrew/skills/speccrew-pm-module-matcher/SKILL.md +3 -115
- package/.speccrew/skills/speccrew-pm-module-matcher/workflow.agentflow.xml +146 -0
- package/.speccrew/skills/speccrew-pm-requirement-analysis/SKILL.md +3 -343
- package/.speccrew/skills/speccrew-pm-requirement-analysis/workflow.agentflow.xml +174 -0
- package/.speccrew/skills/speccrew-pm-requirement-assess/SKILL.md +3 -91
- package/.speccrew/skills/speccrew-pm-requirement-assess/workflow.agentflow.xml +173 -0
- package/.speccrew/skills/speccrew-pm-requirement-clarify/SKILL.md +3 -224
- package/.speccrew/skills/speccrew-pm-requirement-clarify/workflow.agentflow.xml +159 -0
- package/.speccrew/skills/speccrew-pm-requirement-model/SKILL.md +3 -275
- package/.speccrew/skills/speccrew-pm-requirement-model/workflow.agentflow.xml +210 -0
- package/.speccrew/skills/speccrew-pm-requirement-simple/SKILL.md +3 -76
- package/.speccrew/skills/speccrew-pm-requirement-simple/workflow.agentflow.xml +120 -0
- package/.speccrew/skills/speccrew-pm-sub-prd-generate/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-pm-sub-prd-generate/workflow.agentflow.xml +218 -0
- package/.speccrew/skills/speccrew-sd-backend/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-backend/workflow.agentflow.xml +264 -0
- package/.speccrew/skills/speccrew-sd-desktop/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-desktop/workflow.agentflow.xml +288 -0
- package/.speccrew/skills/speccrew-sd-framework-evaluate/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-framework-evaluate/workflow.agentflow.xml +235 -0
- package/.speccrew/skills/speccrew-sd-frontend/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-frontend/workflow.agentflow.xml +299 -0
- package/.speccrew/skills/speccrew-sd-mobile/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-mobile/workflow.agentflow.xml +301 -0
- package/.speccrew/skills/speccrew-test-case-design/SKILL.md +165 -284
- package/.speccrew/skills/speccrew-test-case-design/workflow.agentflow.xml +210 -0
- package/.speccrew/skills/speccrew-test-code-gen/SKILL.md +204 -324
- package/.speccrew/skills/speccrew-test-code-gen/workflow.agentflow.xml +265 -0
- package/.speccrew/skills/speccrew-test-reporter/SKILL.md +205 -184
- package/.speccrew/skills/speccrew-test-reporter/workflow.agentflow.xml +284 -0
- package/.speccrew/skills/speccrew-test-runner/SKILL.md +242 -241
- package/.speccrew/skills/speccrew-test-runner/workflow.agentflow.xml +314 -0
- package/bin/cli.js +8 -1
- package/lib/commands/init.js +11 -3
- package/lib/commands/update.js +11 -3
- package/lib/commands/validate.js +565 -0
- package/lib/utils.js +43 -0
- package/package.json +1 -1
- package/workspace-template/docs/rules/{xml-workflow-spec.md → agentflow-spec.md} +5 -5
- package/workspace-template/scripts/validate-agentflow.js +637 -0
- package/.speccrew/agents/speccrew-team-leader-xml.md +0 -480
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/STATUS-FORMATS.md +0 -99
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/batch-orchestrator.js +0 -176
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-next-batch.js +0 -150
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-pending-features.js +0 -106
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/mark-stale.js +0 -249
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/merge-features.js +0 -300
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/process-batch-results.js +0 -915
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/update-feature-status.js +0 -226
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/examples/features.json +0 -34
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/generate-inventory.js +0 -1087
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/test-inventory.js +0 -26
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/STATUS-FORMATS.md +0 -550
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Batch Orchestrator - Main control script for Stage 2 loop.
|
|
5
|
-
*
|
|
6
|
-
* This script simplifies the Stage 2 loop in SKILL.md by merging
|
|
7
|
-
* "get batch" and "process previous batch results" steps into a single call.
|
|
8
|
-
*
|
|
9
|
-
* Modes:
|
|
10
|
-
* 1. get-batch: Get the next batch of features to process
|
|
11
|
-
* 2. process-results: Process the previous batch results
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const { execFileSync } = require('child_process');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
|
|
17
|
-
// Parse command line arguments
|
|
18
|
-
function parseArgs() {
|
|
19
|
-
const args = process.argv.slice(2);
|
|
20
|
-
const result = {
|
|
21
|
-
mode: null,
|
|
22
|
-
syncStatePath: null,
|
|
23
|
-
batchSize: 5,
|
|
24
|
-
graphRoot: null,
|
|
25
|
-
graphWriteScript: null,
|
|
26
|
-
platformId: null
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// First non-option argument is the mode
|
|
30
|
-
let modeFound = false;
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < args.length; i++) {
|
|
33
|
-
const arg = args[i];
|
|
34
|
-
|
|
35
|
-
// Skip if it's a flag
|
|
36
|
-
if (arg.startsWith('--')) {
|
|
37
|
-
switch (arg) {
|
|
38
|
-
case '--syncStatePath':
|
|
39
|
-
case '-SyncStatePath':
|
|
40
|
-
result.syncStatePath = args[++i];
|
|
41
|
-
break;
|
|
42
|
-
case '--batchSize':
|
|
43
|
-
case '-BatchSize':
|
|
44
|
-
result.batchSize = parseInt(args[++i], 10) || 5;
|
|
45
|
-
break;
|
|
46
|
-
case '--graphRoot':
|
|
47
|
-
case '-GraphRoot':
|
|
48
|
-
result.graphRoot = args[++i];
|
|
49
|
-
break;
|
|
50
|
-
case '--graphWriteScript':
|
|
51
|
-
case '-GraphWriteScript':
|
|
52
|
-
result.graphWriteScript = args[++i];
|
|
53
|
-
break;
|
|
54
|
-
case '--platformId':
|
|
55
|
-
case '-PlatformId':
|
|
56
|
-
result.platformId = args[++i];
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
} else if (!modeFound) {
|
|
60
|
-
// First non-flag argument is the mode
|
|
61
|
-
result.mode = arg;
|
|
62
|
-
modeFound = true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Get the path to a script in the same directory
|
|
70
|
-
function getScriptPath(scriptName) {
|
|
71
|
-
return path.join(__dirname, scriptName);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Mode 1: Get the next batch of features
|
|
75
|
-
function getBatch(args) {
|
|
76
|
-
if (!args.syncStatePath) {
|
|
77
|
-
throw new Error('--syncStatePath is required for get-batch mode');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const getNextBatchScript = getScriptPath('get-next-batch.js');
|
|
81
|
-
|
|
82
|
-
const commandArgs = [
|
|
83
|
-
getNextBatchScript,
|
|
84
|
-
'--syncStatePath', args.syncStatePath,
|
|
85
|
-
'--batchSize', String(args.batchSize)
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
// Execute get-next-batch.js and capture output
|
|
89
|
-
const output = execFileSync('node', commandArgs, { encoding: 'utf8' });
|
|
90
|
-
|
|
91
|
-
// Parse the output (JSON array of features)
|
|
92
|
-
const batch = JSON.parse(output);
|
|
93
|
-
|
|
94
|
-
// Determine the response based on whether there are features
|
|
95
|
-
if (batch.length === 0) {
|
|
96
|
-
return {
|
|
97
|
-
action: 'done',
|
|
98
|
-
message: 'All features have been processed'
|
|
99
|
-
};
|
|
100
|
-
} else {
|
|
101
|
-
return {
|
|
102
|
-
action: 'process',
|
|
103
|
-
batch: batch,
|
|
104
|
-
batchSize: batch.length,
|
|
105
|
-
iteration: null // Could be enhanced with state tracking
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Mode 2: Process the previous batch results
|
|
111
|
-
function processResults(args) {
|
|
112
|
-
if (!args.syncStatePath) {
|
|
113
|
-
throw new Error('--syncStatePath is required for process-results mode');
|
|
114
|
-
}
|
|
115
|
-
if (!args.graphRoot) {
|
|
116
|
-
throw new Error('--graphRoot is required for process-results mode');
|
|
117
|
-
}
|
|
118
|
-
if (!args.graphWriteScript) {
|
|
119
|
-
throw new Error('--graphWriteScript is required for process-results mode');
|
|
120
|
-
}
|
|
121
|
-
if (!args.platformId) {
|
|
122
|
-
throw new Error('--platformId is required for process-results mode');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const processBatchResultsScript = getScriptPath('process-batch-results.js');
|
|
126
|
-
|
|
127
|
-
const commandArgs = [
|
|
128
|
-
processBatchResultsScript,
|
|
129
|
-
'--syncStatePath', args.syncStatePath,
|
|
130
|
-
'--graphRoot', args.graphRoot,
|
|
131
|
-
'--graphWriteScript', args.graphWriteScript,
|
|
132
|
-
'--platformId', args.platformId
|
|
133
|
-
];
|
|
134
|
-
|
|
135
|
-
// Execute process-batch-results.js and capture output
|
|
136
|
-
const output = execFileSync('node', commandArgs, { encoding: 'utf8' });
|
|
137
|
-
|
|
138
|
-
// Parse and return the output (JSON result)
|
|
139
|
-
return JSON.parse(output);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function main() {
|
|
143
|
-
try {
|
|
144
|
-
const args = parseArgs();
|
|
145
|
-
|
|
146
|
-
if (!args.mode) {
|
|
147
|
-
console.error('Error: Mode is required. Use "get-batch" or "process-results"');
|
|
148
|
-
console.error('Usage:');
|
|
149
|
-
console.error(' node batch-orchestrator.js get-batch --syncStatePath <path> [--batchSize 5]');
|
|
150
|
-
console.error(' node batch-orchestrator.js process-results --syncStatePath <path> --graphRoot <path> --graphWriteScript <path> --platformId <id>');
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
let result;
|
|
155
|
-
|
|
156
|
-
switch (args.mode) {
|
|
157
|
-
case 'get-batch':
|
|
158
|
-
result = getBatch(args);
|
|
159
|
-
break;
|
|
160
|
-
case 'process-results':
|
|
161
|
-
result = processResults(args);
|
|
162
|
-
break;
|
|
163
|
-
default:
|
|
164
|
-
throw new Error(`Unknown mode: ${args.mode}. Use "get-batch" or "process-results"`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Output result as JSON
|
|
168
|
-
console.log(JSON.stringify(result, null, 2));
|
|
169
|
-
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error(`Error: ${error.message}`);
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
main();
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get the next batch of features to process from features-*.json files.
|
|
5
|
-
*
|
|
6
|
-
* Scans the sync-state directory for all features-*.json files and extracts
|
|
7
|
-
* features where analyzed=false or analyzed field is missing.
|
|
8
|
-
* Additionally excludes features that have a corresponding .done.json file in the
|
|
9
|
-
* completed/ directory (indicating Worker has finished but results not yet processed).
|
|
10
|
-
* Returns a JSON array limited to BatchSize items.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
|
|
16
|
-
// Parse command line arguments
|
|
17
|
-
function parseArgs() {
|
|
18
|
-
const args = process.argv.slice(2);
|
|
19
|
-
const result = {
|
|
20
|
-
syncStatePath: null,
|
|
21
|
-
batchSize: 5
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < args.length; i++) {
|
|
25
|
-
const arg = args[i];
|
|
26
|
-
switch (arg) {
|
|
27
|
-
case '--syncStatePath':
|
|
28
|
-
case '-SyncStatePath':
|
|
29
|
-
result.syncStatePath = args[++i];
|
|
30
|
-
break;
|
|
31
|
-
case '--batchSize':
|
|
32
|
-
case '-BatchSize':
|
|
33
|
-
result.batchSize = parseInt(args[++i], 10) || 5;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function main() {
|
|
42
|
-
try {
|
|
43
|
-
const args = parseArgs();
|
|
44
|
-
|
|
45
|
-
if (!args.syncStatePath) {
|
|
46
|
-
console.error('Error: --syncStatePath is required');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Resolve full path
|
|
51
|
-
const fullPath = path.resolve(args.syncStatePath);
|
|
52
|
-
if (!fs.existsSync(fullPath)) {
|
|
53
|
-
console.error(`SyncStatePath not found: ${args.syncStatePath}`);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Path to completed directory
|
|
58
|
-
const completedDir = path.join(fullPath, 'completed');
|
|
59
|
-
|
|
60
|
-
// Build set of completed feature fileNames (for fast lookup)
|
|
61
|
-
const completedFeatureFileNames = new Set();
|
|
62
|
-
const completedFeatureIds = new Set();
|
|
63
|
-
if (fs.existsSync(completedDir) && fs.statSync(completedDir).isDirectory()) {
|
|
64
|
-
const doneFiles = fs.readdirSync(completedDir).filter(f => f.endsWith('.done.json'));
|
|
65
|
-
for (const doneFile of doneFiles) {
|
|
66
|
-
// Read .done.json file content to extract fileName or featureId
|
|
67
|
-
const doneFilePath = path.join(completedDir, doneFile);
|
|
68
|
-
try {
|
|
69
|
-
const doneRawContent = fs.readFileSync(doneFilePath, 'utf8');
|
|
70
|
-
const doneContent = JSON.parse(doneRawContent);
|
|
71
|
-
// Use fileName field if available, fallback to featureId
|
|
72
|
-
if (doneContent.fileName) {
|
|
73
|
-
completedFeatureFileNames.add(doneContent.fileName);
|
|
74
|
-
}
|
|
75
|
-
if (doneContent.featureId) {
|
|
76
|
-
completedFeatureIds.add(doneContent.featureId);
|
|
77
|
-
}
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.warn(`Warning: Failed to read .done.json file ${doneFile}: ${error.message}`);
|
|
80
|
-
// Fallback: use filename without extension (legacy behavior)
|
|
81
|
-
const baseName = path.basename(doneFile, '.done.json');
|
|
82
|
-
completedFeatureIds.add(baseName);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Find all features-*.json files
|
|
88
|
-
const featureFiles = fs.readdirSync(fullPath).filter(f => {
|
|
89
|
-
return f.startsWith('features-') && f.endsWith('.json') && fs.statSync(path.join(fullPath, f)).isFile();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const candidateFeatures = [];
|
|
93
|
-
|
|
94
|
-
for (const fileName of featureFiles) {
|
|
95
|
-
const filePath = path.join(fullPath, fileName);
|
|
96
|
-
const rawContent = fs.readFileSync(filePath, 'utf8');
|
|
97
|
-
const content = JSON.parse(rawContent);
|
|
98
|
-
|
|
99
|
-
const platformType = content.platformType;
|
|
100
|
-
const platformSubtype = content.platformSubtype || null;
|
|
101
|
-
const sourcePath = content.sourcePath;
|
|
102
|
-
const techStack = content.techStack;
|
|
103
|
-
const platformName = content.platformName;
|
|
104
|
-
|
|
105
|
-
if (!content.features || !Array.isArray(content.features)) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
for (const feature of content.features) {
|
|
110
|
-
// Check if feature needs analysis: analyzed=false or analyzed field missing
|
|
111
|
-
const needsAnalysis = !('analyzed' in feature) || feature.analyzed === false;
|
|
112
|
-
|
|
113
|
-
if (!needsAnalysis) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Use feature's id and fileName fields
|
|
118
|
-
const featureId = feature.id;
|
|
119
|
-
const featureFileName = feature.fileName;
|
|
120
|
-
|
|
121
|
-
// Skip if already completed (has .done.json file)
|
|
122
|
-
// Check both fileName (new format) and featureId (legacy format)
|
|
123
|
-
if (completedFeatureFileNames.has(featureFileName) || completedFeatureIds.has(featureId)) {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
candidateFeatures.push({
|
|
128
|
-
sourceFile: fileName,
|
|
129
|
-
platformName: platformName,
|
|
130
|
-
platformType: platformType,
|
|
131
|
-
platformSubtype: platformSubtype,
|
|
132
|
-
sourcePath: sourcePath,
|
|
133
|
-
techStack: techStack,
|
|
134
|
-
feature: feature
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Take only the first BatchSize items
|
|
140
|
-
const batchFeatures = candidateFeatures.slice(0, args.batchSize);
|
|
141
|
-
|
|
142
|
-
// Output as JSON
|
|
143
|
-
console.log(JSON.stringify(batchFeatures, null, 2));
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.error(`Error: ${error.message}`);
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
main();
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get all pending features from features-*.json files.
|
|
5
|
-
*
|
|
6
|
-
* Scans the sync-state directory for all features-*.json files and extracts
|
|
7
|
-
* features where status='pending' or status field is missing (backward compatibility).
|
|
8
|
-
* Returns a flat JSON array with platform metadata attached to each feature for easy dispatch.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
|
|
14
|
-
// Parse command line arguments
|
|
15
|
-
function parseArgs() {
|
|
16
|
-
const args = process.argv.slice(2);
|
|
17
|
-
const result = {
|
|
18
|
-
syncStatePath: null,
|
|
19
|
-
platformId: null
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
for (let i = 0; i < args.length; i++) {
|
|
23
|
-
const arg = args[i];
|
|
24
|
-
switch (arg) {
|
|
25
|
-
case '--syncStatePath':
|
|
26
|
-
case '-SyncStatePath':
|
|
27
|
-
result.syncStatePath = args[++i];
|
|
28
|
-
break;
|
|
29
|
-
case '--platformId':
|
|
30
|
-
case '-platformId':
|
|
31
|
-
result.platformId = args[++i];
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function main() {
|
|
40
|
-
try {
|
|
41
|
-
const args = parseArgs();
|
|
42
|
-
|
|
43
|
-
if (!args.syncStatePath) {
|
|
44
|
-
console.error('Error: --syncStatePath is required');
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Resolve full path
|
|
49
|
-
const fullPath = path.resolve(args.syncStatePath);
|
|
50
|
-
if (!fs.existsSync(fullPath)) {
|
|
51
|
-
console.error(`SyncStatePath not found: ${args.syncStatePath}`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Find features-*.json files, filtered by platformId if specified
|
|
56
|
-
let featureFiles = fs.readdirSync(fullPath).filter(f => {
|
|
57
|
-
return f.startsWith('features-') && f.endsWith('.json') && fs.statSync(path.join(fullPath, f)).isFile();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// If platformId is specified, filter to only the matching file
|
|
61
|
-
if (args.platformId) {
|
|
62
|
-
const targetFile = `features-${args.platformId}.json`;
|
|
63
|
-
featureFiles = featureFiles.filter(f => f === targetFile);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const pendingFeatures = [];
|
|
67
|
-
|
|
68
|
-
for (const fileName of featureFiles) {
|
|
69
|
-
const filePath = path.join(fullPath, fileName);
|
|
70
|
-
const rawContent = fs.readFileSync(filePath, 'utf8');
|
|
71
|
-
const content = JSON.parse(rawContent);
|
|
72
|
-
|
|
73
|
-
const platformType = content.platformType;
|
|
74
|
-
const platformSubtype = content.platformSubtype || null;
|
|
75
|
-
const sourcePath = content.sourcePath;
|
|
76
|
-
const techStack = content.techStack;
|
|
77
|
-
const platformName = content.platformName;
|
|
78
|
-
|
|
79
|
-
if (!content.features || !Array.isArray(content.features)) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const feature of content.features) {
|
|
84
|
-
if (!('status' in feature) || feature.status === 'pending') {
|
|
85
|
-
pendingFeatures.push({
|
|
86
|
-
sourceFile: fileName,
|
|
87
|
-
platformName: platformName,
|
|
88
|
-
platformType: platformType,
|
|
89
|
-
platformSubtype: platformSubtype,
|
|
90
|
-
sourcePath: sourcePath,
|
|
91
|
-
techStack: techStack,
|
|
92
|
-
feature: feature
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Output as JSON
|
|
99
|
-
console.log(JSON.stringify(pendingFeatures, null, 2));
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.error(`Error: ${error.message}`);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
main();
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Mark features as stale (pending re-analysis) for specified paths.
|
|
5
|
-
*
|
|
6
|
-
* Scans all features-*.json files in the SyncStatePath directory and marks
|
|
7
|
-
* features as stale when their sourcePath matches one of the provided paths.
|
|
8
|
-
* Matching supports exact path match or directory prefix match (when a
|
|
9
|
-
* directory path is provided, all features under that directory are marked).
|
|
10
|
-
*
|
|
11
|
-
* Resets the following fields for matched features:
|
|
12
|
-
* - analyzed = false
|
|
13
|
-
* - status = "pending" (if the field exists)
|
|
14
|
-
* - startedAt = null
|
|
15
|
-
* - completedAt = null
|
|
16
|
-
* - analysisNotes = null
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
const path = require('path');
|
|
21
|
-
|
|
22
|
-
// Parse command line arguments
|
|
23
|
-
function parseArgs() {
|
|
24
|
-
const args = process.argv.slice(2);
|
|
25
|
-
const result = {
|
|
26
|
-
syncStatePath: null,
|
|
27
|
-
paths: []
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
for (let i = 0; i < args.length; i++) {
|
|
31
|
-
const arg = args[i];
|
|
32
|
-
switch (arg) {
|
|
33
|
-
case '--syncStatePath':
|
|
34
|
-
case '-SyncStatePath':
|
|
35
|
-
result.syncStatePath = args[++i];
|
|
36
|
-
break;
|
|
37
|
-
case '--paths':
|
|
38
|
-
case '-Paths':
|
|
39
|
-
// Support comma-separated paths or multiple --paths arguments
|
|
40
|
-
const pathsValue = args[++i];
|
|
41
|
-
if (pathsValue) {
|
|
42
|
-
result.paths.push(...pathsValue.split(',').map(p => p.trim()).filter(p => p));
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Helper function to safely set property on object
|
|
52
|
-
function setFeatureProperty(obj, propertyName, value) {
|
|
53
|
-
obj[propertyName] = value;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Helper function to safely remove property from object
|
|
57
|
-
function removeFeatureProperty(obj, propertyName) {
|
|
58
|
-
if (propertyName in obj) {
|
|
59
|
-
delete obj[propertyName];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Helper function to normalize path for comparison
|
|
64
|
-
// - Convert backslashes to forward slashes
|
|
65
|
-
// - Remove trailing slashes
|
|
66
|
-
// - Convert to lowercase for case-insensitive comparison (Windows)
|
|
67
|
-
function normalizePath(inputPath) {
|
|
68
|
-
if (!inputPath) {
|
|
69
|
-
return '';
|
|
70
|
-
}
|
|
71
|
-
let normalized = inputPath.replace(/\\/g, '/');
|
|
72
|
-
normalized = normalized.replace(/\/$/, '');
|
|
73
|
-
return normalized.toLowerCase();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function main() {
|
|
77
|
-
try {
|
|
78
|
-
const args = parseArgs();
|
|
79
|
-
|
|
80
|
-
if (!args.syncStatePath) {
|
|
81
|
-
console.error('Error: --syncStatePath is required');
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (args.paths.length === 0) {
|
|
86
|
-
console.error('Error: --paths is required');
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Validate SyncStatePath exists
|
|
91
|
-
const resolvedSyncStatePath = path.resolve(args.syncStatePath);
|
|
92
|
-
if (!fs.existsSync(resolvedSyncStatePath)) {
|
|
93
|
-
console.error(`SyncStatePath not found: ${args.syncStatePath}`);
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Normalize input paths for matching
|
|
98
|
-
const normalizedInputPaths = args.paths.map(p => normalizePath(p));
|
|
99
|
-
|
|
100
|
-
// Find all features-*.json files
|
|
101
|
-
const featureFiles = fs.readdirSync(resolvedSyncStatePath).filter(f => {
|
|
102
|
-
return f.startsWith('features-') && f.endsWith('.json') && fs.statSync(path.join(resolvedSyncStatePath, f)).isFile();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
if (featureFiles.length === 0) {
|
|
106
|
-
// No feature files found, output empty result
|
|
107
|
-
const result = {
|
|
108
|
-
totalAffected: 0,
|
|
109
|
-
features: []
|
|
110
|
-
};
|
|
111
|
-
console.log(JSON.stringify(result, null, 2));
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const affectedFeatures = [];
|
|
116
|
-
|
|
117
|
-
for (const fileName of featureFiles) {
|
|
118
|
-
const filePath = path.join(resolvedSyncStatePath, fileName);
|
|
119
|
-
const lockPath = `${filePath}.lock`;
|
|
120
|
-
const maxRetries = 30;
|
|
121
|
-
let retryCount = 0;
|
|
122
|
-
let lockAcquired = false;
|
|
123
|
-
|
|
124
|
-
// Acquire file lock
|
|
125
|
-
while (!lockAcquired && retryCount < maxRetries) {
|
|
126
|
-
try {
|
|
127
|
-
// Try to create lock file exclusively
|
|
128
|
-
const fd = fs.openSync(lockPath, 'wx');
|
|
129
|
-
fs.closeSync(fd);
|
|
130
|
-
lockAcquired = true;
|
|
131
|
-
} catch (error) {
|
|
132
|
-
retryCount++;
|
|
133
|
-
if (retryCount >= maxRetries) {
|
|
134
|
-
const errorMsg = `Failed to acquire file lock for '${fileName}' after ${maxRetries} attempts (waited ${maxRetries} seconds). The file may be locked by another process.`;
|
|
135
|
-
console.warn(errorMsg);
|
|
136
|
-
// Continue to next file instead of exiting
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
if (retryCount % 5 === 0) {
|
|
140
|
-
console.warn(`Waiting for file lock on '${fileName}'... (attempt ${retryCount} of ${maxRetries})`);
|
|
141
|
-
}
|
|
142
|
-
// Wait 1 second before retry
|
|
143
|
-
const start = Date.now();
|
|
144
|
-
while (Date.now() - start < 1000) {
|
|
145
|
-
// Busy wait
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Skip this file if lock could not be acquired
|
|
151
|
-
if (!lockAcquired) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
// Read the JSON file
|
|
157
|
-
const rawContent = fs.readFileSync(filePath, 'utf8');
|
|
158
|
-
const content = JSON.parse(rawContent);
|
|
159
|
-
let fileModified = false;
|
|
160
|
-
|
|
161
|
-
if (content.features && Array.isArray(content.features)) {
|
|
162
|
-
for (let i = 0; i < content.features.length; i++) {
|
|
163
|
-
const feature = content.features[i];
|
|
164
|
-
const featureSourcePath = feature.sourcePath;
|
|
165
|
-
|
|
166
|
-
if (!featureSourcePath) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const normalizedFeaturePath = normalizePath(featureSourcePath);
|
|
171
|
-
|
|
172
|
-
// Check if any input path matches this feature
|
|
173
|
-
let matched = false;
|
|
174
|
-
for (const inputPath of normalizedInputPaths) {
|
|
175
|
-
// Exact match or directory prefix match
|
|
176
|
-
if (normalizedFeaturePath === inputPath) {
|
|
177
|
-
matched = true;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
// Directory prefix match: input path is a directory and feature path starts with it
|
|
181
|
-
if (normalizedFeaturePath.startsWith(`${inputPath}/`)) {
|
|
182
|
-
matched = true;
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (matched) {
|
|
188
|
-
// Reset fields
|
|
189
|
-
setFeatureProperty(feature, 'analyzed', false);
|
|
190
|
-
|
|
191
|
-
// Only set status if the field already exists
|
|
192
|
-
if ('status' in feature) {
|
|
193
|
-
setFeatureProperty(feature, 'status', 'pending');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Clear timestamp fields
|
|
197
|
-
removeFeatureProperty(feature, 'startedAt');
|
|
198
|
-
removeFeatureProperty(feature, 'completedAt');
|
|
199
|
-
removeFeatureProperty(feature, 'analysisNotes');
|
|
200
|
-
|
|
201
|
-
content.features[i] = feature;
|
|
202
|
-
fileModified = true;
|
|
203
|
-
|
|
204
|
-
// Add to affected list
|
|
205
|
-
affectedFeatures.push({
|
|
206
|
-
sourcePath: featureSourcePath,
|
|
207
|
-
module: feature.module,
|
|
208
|
-
sourceFile: fileName
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Update counters and write back if modified
|
|
215
|
-
if (fileModified) {
|
|
216
|
-
content.analyzedCount = content.features.filter(f => f.analyzed === true).length;
|
|
217
|
-
content.pendingCount = content.features.filter(f => f.analyzed === false).length;
|
|
218
|
-
|
|
219
|
-
// Atomic write: temp file + rename
|
|
220
|
-
const tempFile = `${filePath}.tmp`;
|
|
221
|
-
fs.writeFileSync(tempFile, JSON.stringify(content, null, 2), 'utf8');
|
|
222
|
-
fs.renameSync(tempFile, filePath);
|
|
223
|
-
}
|
|
224
|
-
} finally {
|
|
225
|
-
// Release lock - remove lock file
|
|
226
|
-
try {
|
|
227
|
-
if (fs.existsSync(lockPath)) {
|
|
228
|
-
fs.unlinkSync(lockPath);
|
|
229
|
-
}
|
|
230
|
-
} catch (e) {
|
|
231
|
-
// Ignore cleanup errors
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Output result as JSON
|
|
237
|
-
const result = {
|
|
238
|
-
totalAffected: affectedFeatures.length,
|
|
239
|
-
features: affectedFeatures
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
console.log(JSON.stringify(result, null, 2));
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error(`Error: ${error.message}`);
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
main();
|