speccrew 0.7.35 → 0.7.36
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-feature-designer.md +4 -4
- package/.speccrew/skills/speccrew-fd-api-contract/SKILL.md +6 -6
- package/.speccrew/skills/speccrew-fd-api-contract/workflow.agentflow.xml +15 -15
- package/.speccrew/skills/speccrew-feature-designer-orchestration/workflow.agentflow.xml +2 -2
- package/package.json +1 -1
- package/workspace-template/scripts/update-progress.js +208 -1
|
@@ -742,7 +742,7 @@ Invoke API Contract skill directly:
|
|
|
742
742
|
- `feature_spec_path`: Path to the Feature Spec document
|
|
743
743
|
- `feature_id`: Feature ID (e.g., `F-CRM-01`)
|
|
744
744
|
- `feature_type`: `Page+API` or `API-only`
|
|
745
|
-
- `output_path`: `{iterations_dir}/{iteration}/
|
|
745
|
+
- `output_path`: `{iterations_dir}/{iteration}/03.api-contract/{feature_id}-{feature-name-slug}-api-contract.md`
|
|
746
746
|
|
|
747
747
|
**Note**: Both `Page+API` and `API-only` Features require API Contract documents.
|
|
748
748
|
|
|
@@ -800,7 +800,7 @@ Phase 3 deletes `.tasks-temp.json` after init. Phase 4 MUST regenerate it before
|
|
|
800
800
|
- `feature_id`: Feature ID (e.g., `F-CRM-01`)
|
|
801
801
|
- `feature_name`: Feature name — **MUST be the exact value from .checkpoints.json, used verbatim for output filename**
|
|
802
802
|
- `feature_type`: `Page+API` or `API-only`
|
|
803
|
-
- `output_path`: `{iterations_dir}/{iteration}/
|
|
803
|
+
- `output_path`: `{iterations_dir}/{iteration}/03.api-contract/{feature_id}-{feature-name-slug}-api-contract.md`
|
|
804
804
|
|
|
805
805
|
3. **Wait for batch completion**, update progress per worker:
|
|
806
806
|
```bash
|
|
@@ -864,7 +864,7 @@ After user confirms Joint Confirmation:
|
|
|
864
864
|
node {update_progress_script} update-workflow \
|
|
865
865
|
--file {iterations_dir}/{iteration}/WORKFLOW-PROGRESS.json \
|
|
866
866
|
--stage 02_feature_design --status confirmed \
|
|
867
|
-
--output "02.feature-design/F-CRM-01-customer-list-feature-spec.md,
|
|
867
|
+
--output "02.feature-design/F-CRM-01-customer-list-feature-spec.md,03.api-contract/F-CRM-01-customer-list-api-contract.md,..."
|
|
868
868
|
```
|
|
869
869
|
|
|
870
870
|
2. **Confirm Transition**:
|
|
@@ -876,7 +876,7 @@ After user confirms Joint Confirmation:
|
|
|
876
876
|
| Deliverable | Path | Notes |
|
|
877
877
|
|-------------|------|-------|
|
|
878
878
|
| Feature Spec | `{iterations_dir}/{iteration}/02.feature-design/{feature-id}-{feature-name}-feature-spec.md` | One document per Feature |
|
|
879
|
-
| API Contract | `{iterations_dir}/{iteration}/
|
|
879
|
+
| API Contract | `{iterations_dir}/{iteration}/03.api-contract/{feature-id}-{feature-name}-api-contract.md` | One document per Feature |
|
|
880
880
|
|
|
881
881
|
## Naming Convention
|
|
882
882
|
|
|
@@ -80,9 +80,9 @@ Complete definition for each API:
|
|
|
80
80
|
1. **Read the template file**: `templates/API-CONTRACT-TEMPLATE.md`
|
|
81
81
|
2. **Replace top-level placeholders** (feature name, date, feature_id if provided, etc.)
|
|
82
82
|
3. **Create the document** using `create_file`:
|
|
83
|
-
- **新格式(当提供了 feature_id 时)**:`speccrew-workspace/iterations/{number}-{type}-{name}/
|
|
83
|
+
- **新格式(当提供了 feature_id 时)**:`speccrew-workspace/iterations/{number}-{type}-{name}/03.api-contract/{feature-id}-{feature-name}-api-contract.md`
|
|
84
84
|
- 示例:`F-CRM-01-customer-list-api-contract.md`
|
|
85
|
-
- **旧格式(向后兼容,未提供 feature_id 时)**:`speccrew-workspace/iterations/{number}-{type}-{name}/
|
|
85
|
+
- **旧格式(向后兼容,未提供 feature_id 时)**:`speccrew-workspace/iterations/{number}-{type}-{name}/03.api-contract/[feature-name]-api-contract.md`
|
|
86
86
|
- Content: Template with top-level placeholders replaced
|
|
87
87
|
4. **Verify**: Document has complete section structure ready for filling
|
|
88
88
|
|
|
@@ -127,7 +127,7 @@ Feature 设计阶段交付物已准备就绪:
|
|
|
127
127
|
|
|
128
128
|
📄 文档清单:
|
|
129
129
|
- Feature Spec: speccrew-workspace/iterations/{number}-{type}-{name}/02.feature-design/{feature-id}-{feature-name}-feature-spec.md
|
|
130
|
-
- API Contract: speccrew-workspace/iterations/{number}-{type}-{name}/
|
|
130
|
+
- API Contract: speccrew-workspace/iterations/{number}-{type}-{name}/03.api-contract/{feature-id}-{feature-name}-api-contract.md
|
|
131
131
|
|
|
132
132
|
请确认以下关键点:
|
|
133
133
|
1. 当前 Feature 的技术方案是否可行?
|
|
@@ -146,7 +146,7 @@ Feature 设计阶段交付物已准备就绪:
|
|
|
146
146
|
```
|
|
147
147
|
Feature design phase deliverables are ready:
|
|
148
148
|
- Feature Spec: speccrew-workspace/iterations/{number}-{type}-{name}/02.feature-design/[feature-name]-feature-spec.md
|
|
149
|
-
- API Contract: speccrew-workspace/iterations/{number}-{type}-{name}/
|
|
149
|
+
- API Contract: speccrew-workspace/iterations/{number}-{type}-{name}/03.api-contract/[feature-name]-api-contract.md
|
|
150
150
|
|
|
151
151
|
Please confirm the following key points:
|
|
152
152
|
1. Is the overall technical solution feasible?
|
|
@@ -249,7 +249,7 @@ Update `WORKFLOW-PROGRESS.json` to reflect current feature/module status.
|
|
|
249
249
|
"confirmed_at": "{current_timestamp}",
|
|
250
250
|
"outputs": [
|
|
251
251
|
"02.feature-design/{feature-id}-{feature-name}-feature-spec.md",
|
|
252
|
-
"
|
|
252
|
+
"03.api-contract/${feature_id}-${feature_name}-api-contract.md"
|
|
253
253
|
]
|
|
254
254
|
}
|
|
255
255
|
}
|
|
@@ -274,7 +274,7 @@ Update `WORKFLOW-PROGRESS.json` to reflect current feature/module status.
|
|
|
274
274
|
"confirmed_at": "{current_timestamp}",
|
|
275
275
|
"outputs": [
|
|
276
276
|
"02.feature-design/[feature-name]-feature-spec.md",
|
|
277
|
-
"
|
|
277
|
+
"03.api-contract/${feature_name}-api-contract.md"
|
|
278
278
|
]
|
|
279
279
|
}
|
|
280
280
|
}
|
|
@@ -81,21 +81,21 @@
|
|
|
81
81
|
- {Feature ID} → ${feature_id}
|
|
82
82
|
- {Date} → current date
|
|
83
83
|
</field>
|
|
84
|
-
<field name="output_path">${iteration_path}/
|
|
85
|
-
<field name="fallback_path">${iteration_path}/
|
|
84
|
+
<field name="output_path">${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md</field>
|
|
85
|
+
<field name="fallback_path">${iteration_path}/03.api-contract/${feature_name}-api-contract.md</field>
|
|
86
86
|
<field name="output" var="document_created"/>
|
|
87
87
|
</block>
|
|
88
88
|
|
|
89
89
|
<!-- Checkpoint: Verify document created -->
|
|
90
90
|
<block type="checkpoint" id="CP1" name="document-created" desc="Verify API contract document created">
|
|
91
|
-
<field name="file" value="${iteration_path}/
|
|
92
|
-
<field name="verify" value="file_exists(${iteration_path}/
|
|
91
|
+
<field name="file" value="${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md"/>
|
|
92
|
+
<field name="verify" value="file_exists(${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md)"/>
|
|
93
93
|
</block>
|
|
94
94
|
|
|
95
95
|
<!-- Step 4b: Fill API List Overview Section -->
|
|
96
96
|
<block type="task" id="B5" action="edit-file" desc="Fill API List Overview section">
|
|
97
|
-
<field name="path">${iteration_path}/
|
|
98
|
-
<field name="fallback_path">${iteration_path}/
|
|
97
|
+
<field name="path">${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md</field>
|
|
98
|
+
<field name="fallback_path">${iteration_path}/03.api-contract/${feature_name}-api-contract.md</field>
|
|
99
99
|
<field name="operation">search_replace</field>
|
|
100
100
|
<field name="search">## API List Overview
|
|
101
101
|
|
|
@@ -123,8 +123,8 @@ ${api_list.table}</field>
|
|
|
123
123
|
</block>
|
|
124
124
|
|
|
125
125
|
<block type="task" id="B7" action="edit-file" desc="Fill API contract section">
|
|
126
|
-
<field name="path">${iteration_path}/
|
|
127
|
-
<field name="fallback_path">${iteration_path}/
|
|
126
|
+
<field name="path">${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md</field>
|
|
127
|
+
<field name="fallback_path">${iteration_path}/03.api-contract/${feature_name}-api-contract.md</field>
|
|
128
128
|
<field name="operation">search_replace</field>
|
|
129
129
|
<field name="search">### ${api.name}
|
|
130
130
|
|
|
@@ -137,8 +137,8 @@ ${api_contract_details.content}</field>
|
|
|
137
137
|
|
|
138
138
|
<!-- Step 6: Fill Error Code Summary -->
|
|
139
139
|
<block type="task" id="B8" action="edit-file" desc="Fill Error Code Summary section">
|
|
140
|
-
<field name="path">${iteration_path}/
|
|
141
|
-
<field name="fallback_path">${iteration_path}/
|
|
140
|
+
<field name="path">${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md</field>
|
|
141
|
+
<field name="fallback_path">${iteration_path}/03.api-contract/${feature_name}-api-contract.md</field>
|
|
142
142
|
<field name="operation">search_replace</field>
|
|
143
143
|
<field name="search">## Error Code Summary
|
|
144
144
|
|
|
@@ -161,7 +161,7 @@ Feature Name: ${feature_name}
|
|
|
161
161
|
|
|
162
162
|
Documents:
|
|
163
163
|
- Feature Spec: ${iteration_path}/02.feature-design/${feature_id}-${feature_name}-feature-spec.md
|
|
164
|
-
- API Contract: ${iteration_path}/
|
|
164
|
+
- API Contract: ${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md
|
|
165
165
|
|
|
166
166
|
Please confirm:
|
|
167
167
|
1. Is the technical solution feasible?
|
|
@@ -175,7 +175,7 @@ After confirmation, API Contract becomes the sole baseline for frontend-backend
|
|
|
175
175
|
<block type="event" id="E2" action="confirm" title="Feature Design Confirmation" type="yesno" desc="Request user confirmation">
|
|
176
176
|
<field name="preview">Feature design phase deliverables ready:
|
|
177
177
|
- Feature Spec: ${iteration_path}/02.feature-design/${feature_name}-feature-spec.md
|
|
178
|
-
- API Contract: ${iteration_path}/
|
|
178
|
+
- API Contract: ${iteration_path}/03.api-contract/${feature_name}-api-contract.md
|
|
179
179
|
|
|
180
180
|
Please confirm:
|
|
181
181
|
1. Is the overall technical solution feasible?
|
|
@@ -197,12 +197,12 @@ After confirmation, API Contract becomes the sole baseline for frontend-backend
|
|
|
197
197
|
<block type="gateway" id="G4" mode="exclusive" desc="Update progress based on mode">
|
|
198
198
|
<branch test="${feature_id} != null" name="Feature mode">
|
|
199
199
|
<block type="task" id="B10" action="run-script" desc="Update workflow progress for feature">
|
|
200
|
-
<field name="command">node "${workspace_path}/scripts/update-progress.js" update-task --file "${iteration_path}/WORKFLOW-PROGRESS.json" --task-id "${feature_id}" --status confirmed --outputs '["02.feature-design/${feature_id}-${feature_name}-feature-spec.md","
|
|
200
|
+
<field name="command">node "${workspace_path}/scripts/update-progress.js" update-task --file "${iteration_path}/WORKFLOW-PROGRESS.json" --task-id "${feature_id}" --status confirmed --outputs '["02.feature-design/${feature_id}-${feature_name}-feature-spec.md","03.api-contract/${feature_id}-${feature_name}-api-contract.md"]'</field>
|
|
201
201
|
</block>
|
|
202
202
|
</branch>
|
|
203
203
|
<branch default="true" name="Module mode">
|
|
204
204
|
<block type="task" id="B11" action="run-script" desc="Update workflow progress for module">
|
|
205
|
-
<field name="command">node "${workspace_path}/scripts/update-progress.js" update-stage --file "${iteration_path}/WORKFLOW-PROGRESS.json" --stage 02_feature_design --status confirmed --outputs '["02.feature-design/${feature_name}-feature-spec.md","
|
|
205
|
+
<field name="command">node "${workspace_path}/scripts/update-progress.js" update-stage --file "${iteration_path}/WORKFLOW-PROGRESS.json" --stage 02_feature_design --status confirmed --outputs '["02.feature-design/${feature_name}-feature-spec.md","03.api-contract/${feature_name}-api-contract.md"]'</field>
|
|
206
206
|
</block>
|
|
207
207
|
</branch>
|
|
208
208
|
</block>
|
|
@@ -213,7 +213,7 @@ After confirmation, API Contract becomes the sole baseline for frontend-backend
|
|
|
213
213
|
Output Results
|
|
214
214
|
============================================================ -->
|
|
215
215
|
<block type="output" id="O1" desc="Workflow output results">
|
|
216
|
-
<field name="api_contract_path" value="${iteration_path}/
|
|
216
|
+
<field name="api_contract_path" value="${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md" type="string" desc="Path to generated API contract"/>
|
|
217
217
|
<field name="feature_spec_path" value="${iteration_path}/02.feature-design/${feature_id}-${feature_name}-feature-spec.md" type="string" desc="Path to Feature Spec"/>
|
|
218
218
|
<field name="api_count" from="${api_list.count}" type="number" desc="Number of APIs documented"/>
|
|
219
219
|
<field name="confirmation_status" value="confirmed" type="string" desc="User confirmation status"/>
|
|
@@ -341,7 +341,7 @@
|
|
|
341
341
|
feature_id: ${feature_list.modules[0].features[0].feature_id}
|
|
342
342
|
feature_name: ${feature_list.modules[0].features[0].feature_name}
|
|
343
343
|
feature_type: ${feature_list.modules[0].features[0].feature_type}
|
|
344
|
-
output_path: ${iteration_path}/
|
|
344
|
+
output_path: ${iteration_path}/03.api-contract/${feature_id}-${feature_name}-api-contract.md
|
|
345
345
|
</field>
|
|
346
346
|
<field name="output" var="api_contract_result"/>
|
|
347
347
|
</block>
|
|
@@ -382,7 +382,7 @@
|
|
|
382
382
|
feature_id: ${item.feature_id}
|
|
383
383
|
feature_name: ${item.feature_name}
|
|
384
384
|
feature_type: ${item.feature_type}
|
|
385
|
-
output_path: ${iteration_path}/
|
|
385
|
+
output_path: ${iteration_path}/03.api-contract/${item.feature_id}-${item.feature_name}-api-contract.md
|
|
386
386
|
</field>
|
|
387
387
|
</block>
|
|
388
388
|
|
package/package.json
CHANGED
|
@@ -65,6 +65,14 @@
|
|
|
65
65
|
* --features-dir <dir> Directory containing features-*.json files (required)
|
|
66
66
|
* --force Overwrite existing file
|
|
67
67
|
*
|
|
68
|
+
* 8. sync - Sync task status with actual output files
|
|
69
|
+
* node update-progress.js sync --file <path> --dir <dir> --suffix <suffix> [--strict]
|
|
70
|
+
* Options:
|
|
71
|
+
* --file <path> Progress file path (required)
|
|
72
|
+
* --dir <path> Output directory absolute path (required)
|
|
73
|
+
* --suffix <suffix> File suffix to match, e.g., -api-contract.md (required)
|
|
74
|
+
* --strict Also mark completed tasks as pending if file is missing
|
|
75
|
+
*
|
|
68
76
|
* Output Format:
|
|
69
77
|
* Success: {"success": true, "message": "...", "data": {...}}
|
|
70
78
|
* Failure: {"success": false, "error": "..."} (output to stderr, exit code 1)
|
|
@@ -254,7 +262,10 @@ function parseArgs() {
|
|
|
254
262
|
featuresDir: null,
|
|
255
263
|
platforms: null,
|
|
256
264
|
force: false,
|
|
257
|
-
metadata: null
|
|
265
|
+
metadata: null,
|
|
266
|
+
dir: null,
|
|
267
|
+
suffix: null,
|
|
268
|
+
strict: false
|
|
258
269
|
};
|
|
259
270
|
|
|
260
271
|
// First argument is the command
|
|
@@ -363,6 +374,18 @@ function parseArgs() {
|
|
|
363
374
|
case '-Metadata':
|
|
364
375
|
result.metadata = args[++i];
|
|
365
376
|
break;
|
|
377
|
+
case '--dir':
|
|
378
|
+
case '-Dir':
|
|
379
|
+
result.dir = args[++i];
|
|
380
|
+
break;
|
|
381
|
+
case '--suffix':
|
|
382
|
+
case '-Suffix':
|
|
383
|
+
result.suffix = args[++i];
|
|
384
|
+
break;
|
|
385
|
+
case '--strict':
|
|
386
|
+
case '-Strict':
|
|
387
|
+
result.strict = true;
|
|
388
|
+
break;
|
|
366
389
|
}
|
|
367
390
|
}
|
|
368
391
|
|
|
@@ -1039,6 +1062,186 @@ function cmdInitKnowledgeTasks(args) {
|
|
|
1039
1062
|
}
|
|
1040
1063
|
}
|
|
1041
1064
|
|
|
1065
|
+
/**
|
|
1066
|
+
* Command: sync - Sync task status with actual output files
|
|
1067
|
+
* Scans directory for files matching suffix, extracts task IDs from filenames,
|
|
1068
|
+
* and updates task status accordingly.
|
|
1069
|
+
*/
|
|
1070
|
+
function cmdSync(args) {
|
|
1071
|
+
if (!args.file) { outputError('--file is required'); process.exit(1); }
|
|
1072
|
+
if (!args.dir) { outputError('--dir is required'); process.exit(1); }
|
|
1073
|
+
if (!args.suffix) { outputError('--suffix is required'); process.exit(1); }
|
|
1074
|
+
|
|
1075
|
+
const filePath = path.resolve(args.file);
|
|
1076
|
+
const dirPath = path.resolve(args.dir);
|
|
1077
|
+
|
|
1078
|
+
let lockPath = null;
|
|
1079
|
+
|
|
1080
|
+
try {
|
|
1081
|
+
lockPath = acquireLock(filePath);
|
|
1082
|
+
|
|
1083
|
+
const data = readJsonFile(filePath);
|
|
1084
|
+
if (!data || !data.tasks) { outputError('Invalid progress file'); process.exit(1); }
|
|
1085
|
+
|
|
1086
|
+
// Check if directory exists
|
|
1087
|
+
if (!fs.existsSync(dirPath)) {
|
|
1088
|
+
outputError(`Directory not found: ${dirPath}`);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// Scan directory for matching files
|
|
1092
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith(args.suffix));
|
|
1093
|
+
|
|
1094
|
+
// Extract task IDs from filenames
|
|
1095
|
+
// Format: {task-id}-{feature-name}{suffix}
|
|
1096
|
+
// task-id pattern: F-Mxx-xx (starts with F-, contains module and feature number)
|
|
1097
|
+
const fileTaskIds = new Set();
|
|
1098
|
+
const fileMap = {};
|
|
1099
|
+
for (const file of files) {
|
|
1100
|
+
// Extract task ID: match F-M followed by digits, dash, digits
|
|
1101
|
+
const match = file.match(/^(F-M\d+-\d+)/);
|
|
1102
|
+
if (match) {
|
|
1103
|
+
fileTaskIds.add(match[1]);
|
|
1104
|
+
fileMap[match[1]] = file;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
let synced = 0;
|
|
1109
|
+
let alreadyCorrect = 0;
|
|
1110
|
+
let missing = 0;
|
|
1111
|
+
const now = getTimestamp();
|
|
1112
|
+
|
|
1113
|
+
for (const task of data.tasks) {
|
|
1114
|
+
const taskId = task.id;
|
|
1115
|
+
if (fileTaskIds.has(taskId)) {
|
|
1116
|
+
if (task.status !== 'completed') {
|
|
1117
|
+
task.status = 'completed';
|
|
1118
|
+
task.output = fileMap[taskId];
|
|
1119
|
+
task.completed_at = now;
|
|
1120
|
+
task.updated_at = now;
|
|
1121
|
+
synced++;
|
|
1122
|
+
} else {
|
|
1123
|
+
alreadyCorrect++;
|
|
1124
|
+
}
|
|
1125
|
+
} else {
|
|
1126
|
+
if (task.status === 'completed' && args.strict) {
|
|
1127
|
+
task.status = 'pending';
|
|
1128
|
+
delete task.output;
|
|
1129
|
+
delete task.completed_at;
|
|
1130
|
+
task.updated_at = now;
|
|
1131
|
+
missing++;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Recalculate counts
|
|
1137
|
+
data.counts = calculateCounts(data.tasks);
|
|
1138
|
+
data.updated_at = now;
|
|
1139
|
+
|
|
1140
|
+
// Atomic write
|
|
1141
|
+
atomicWriteJson(filePath, data);
|
|
1142
|
+
|
|
1143
|
+
outputSuccess('Sync completed', {
|
|
1144
|
+
scanned_files: files.length,
|
|
1145
|
+
synced: synced,
|
|
1146
|
+
already_correct: alreadyCorrect,
|
|
1147
|
+
missing_files: missing,
|
|
1148
|
+
counts: data.counts
|
|
1149
|
+
});
|
|
1150
|
+
} finally {
|
|
1151
|
+
if (lockPath) releaseLock(lockPath);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Command: sync - Sync task status with actual output files
|
|
1157
|
+
* Scans directory for files matching suffix, extracts task IDs from filenames,
|
|
1158
|
+
* and updates task status accordingly.
|
|
1159
|
+
*/
|
|
1160
|
+
function cmdSync(args) {
|
|
1161
|
+
if (!args.file) { outputError('--file is required'); process.exit(1); }
|
|
1162
|
+
if (!args.dir) { outputError('--dir is required'); process.exit(1); }
|
|
1163
|
+
if (!args.suffix) { outputError('--suffix is required'); process.exit(1); }
|
|
1164
|
+
|
|
1165
|
+
const filePath = path.resolve(args.file);
|
|
1166
|
+
const dirPath = path.resolve(args.dir);
|
|
1167
|
+
|
|
1168
|
+
let lockPath = null;
|
|
1169
|
+
|
|
1170
|
+
try {
|
|
1171
|
+
lockPath = acquireLock(filePath);
|
|
1172
|
+
|
|
1173
|
+
const data = readJsonFile(filePath);
|
|
1174
|
+
if (!data || !data.tasks) { outputError('Invalid progress file'); process.exit(1); }
|
|
1175
|
+
|
|
1176
|
+
// Check if directory exists
|
|
1177
|
+
if (!fs.existsSync(dirPath)) {
|
|
1178
|
+
outputError(`Directory not found: ${dirPath}`);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Scan directory for matching files
|
|
1182
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith(args.suffix));
|
|
1183
|
+
|
|
1184
|
+
// Extract task IDs from filenames
|
|
1185
|
+
// Format: {task-id}-{feature-name}{suffix}
|
|
1186
|
+
// task-id pattern: F-Mxx-xx (starts with F-, contains module and feature number)
|
|
1187
|
+
const fileTaskIds = new Set();
|
|
1188
|
+
const fileMap = {};
|
|
1189
|
+
for (const file of files) {
|
|
1190
|
+
// Extract task ID: match F-M followed by digits, dash, digits
|
|
1191
|
+
const match = file.match(/^(F-M\d+-\d+)/);
|
|
1192
|
+
if (match) {
|
|
1193
|
+
fileTaskIds.add(match[1]);
|
|
1194
|
+
fileMap[match[1]] = file;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
let synced = 0;
|
|
1199
|
+
let alreadyCorrect = 0;
|
|
1200
|
+
let missing = 0;
|
|
1201
|
+
const now = getTimestamp();
|
|
1202
|
+
|
|
1203
|
+
for (const task of data.tasks) {
|
|
1204
|
+
const taskId = task.id;
|
|
1205
|
+
if (fileTaskIds.has(taskId)) {
|
|
1206
|
+
if (task.status !== 'completed') {
|
|
1207
|
+
task.status = 'completed';
|
|
1208
|
+
task.output = fileMap[taskId];
|
|
1209
|
+
task.completed_at = now;
|
|
1210
|
+
task.updated_at = now;
|
|
1211
|
+
synced++;
|
|
1212
|
+
} else {
|
|
1213
|
+
alreadyCorrect++;
|
|
1214
|
+
}
|
|
1215
|
+
} else {
|
|
1216
|
+
if (task.status === 'completed' && args.strict) {
|
|
1217
|
+
task.status = 'pending';
|
|
1218
|
+
delete task.output;
|
|
1219
|
+
delete task.completed_at;
|
|
1220
|
+
task.updated_at = now;
|
|
1221
|
+
missing++;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Recalculate counts
|
|
1227
|
+
data.counts = calculateCounts(data.tasks);
|
|
1228
|
+
data.updated_at = now;
|
|
1229
|
+
|
|
1230
|
+
// Atomic write
|
|
1231
|
+
atomicWriteJson(filePath, data);
|
|
1232
|
+
|
|
1233
|
+
outputSuccess('Sync completed', {
|
|
1234
|
+
scanned_files: files.length,
|
|
1235
|
+
synced: synced,
|
|
1236
|
+
already_correct: alreadyCorrect,
|
|
1237
|
+
missing_files: missing,
|
|
1238
|
+
counts: data.counts
|
|
1239
|
+
});
|
|
1240
|
+
} finally {
|
|
1241
|
+
if (lockPath) releaseLock(lockPath);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1042
1245
|
/**
|
|
1043
1246
|
* Command: init-tasks - Scan feature-design directory to generate task list
|
|
1044
1247
|
*/
|
|
@@ -1207,6 +1410,7 @@ function main() {
|
|
|
1207
1410
|
console.error(' update-workflow Update a workflow stage status');
|
|
1208
1411
|
console.error(' init-tasks Generate tasks from feature-spec files');
|
|
1209
1412
|
console.error(' init-knowledge-tasks Generate knowledge initialization tasks from matcher results');
|
|
1413
|
+
console.error(' sync Sync task status with actual output files');
|
|
1210
1414
|
console.error('');
|
|
1211
1415
|
console.error('Run "node update-progress.js <command> --help" for more information.');
|
|
1212
1416
|
process.exit(1);
|
|
@@ -1239,6 +1443,9 @@ function main() {
|
|
|
1239
1443
|
case 'init-knowledge-tasks':
|
|
1240
1444
|
cmdInitKnowledgeTasks(args);
|
|
1241
1445
|
break;
|
|
1446
|
+
case 'sync':
|
|
1447
|
+
cmdSync(args);
|
|
1448
|
+
break;
|
|
1242
1449
|
default:
|
|
1243
1450
|
outputError(`Unknown command: ${args.command}`);
|
|
1244
1451
|
}
|