speccrew 0.7.10 → 0.7.11
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.
|
@@ -155,6 +155,78 @@ function getBatch(args) {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// Parse featureId to extract platformId, module, and fileName
|
|
159
|
+
// featureId format: {platformId}-{module}-{fileNameWithoutExt}
|
|
160
|
+
function parseFeatureId(featureId) {
|
|
161
|
+
const parts = featureId.split('-');
|
|
162
|
+
if (parts.length >= 3) {
|
|
163
|
+
const platformId = parts[0];
|
|
164
|
+
const module = parts[1];
|
|
165
|
+
const fileNameWithoutExt = parts.slice(2).join('-'); // Handle fileName with hyphens
|
|
166
|
+
return { platformId, module, fileNameWithoutExt };
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Update features JSON file with analysis results
|
|
172
|
+
function updateFeaturesJson(syncStatePath, featureId, doneData) {
|
|
173
|
+
const parsed = parseFeatureId(featureId);
|
|
174
|
+
if (!parsed) {
|
|
175
|
+
console.error(JSON.stringify({ warning: `Cannot parse featureId: ${featureId}` }));
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const { platformId, module, fileNameWithoutExt } = parsed;
|
|
180
|
+
const featuresFilePath = path.join(syncStatePath, `features-${platformId}.json`);
|
|
181
|
+
|
|
182
|
+
if (!fs.existsSync(featuresFilePath)) {
|
|
183
|
+
console.error(JSON.stringify({ warning: `Features file not found: ${featuresFilePath}` }));
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const featuresData = readJsonSafe(featuresFilePath);
|
|
188
|
+
if (!featuresData || !Array.isArray(featuresData.features)) {
|
|
189
|
+
console.error(JSON.stringify({ warning: `Invalid features data in: ${featuresFilePath}` }));
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Find matching feature
|
|
194
|
+
let found = false;
|
|
195
|
+
for (const feature of featuresData.features) {
|
|
196
|
+
const featureFileName = feature.fileName || feature.sourceFile || 'unknown';
|
|
197
|
+
const featureFileNameWithoutExt = featureFileName.replace(/\.[^.]+$/, '');
|
|
198
|
+
|
|
199
|
+
// Match by module and fileName (platformId already matched by filename)
|
|
200
|
+
if (feature.module === module && featureFileNameWithoutExt === fileNameWithoutExt) {
|
|
201
|
+
// Skip if already analyzed (idempotency)
|
|
202
|
+
if (feature.analyzed === true) {
|
|
203
|
+
found = true;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update feature fields
|
|
208
|
+
feature.analyzed = true;
|
|
209
|
+
feature.startedAt = doneData.startedAt || doneData.timestamp || null;
|
|
210
|
+
feature.completedAt = doneData.completedAt || doneData.timestamp || null;
|
|
211
|
+
feature.analysisNotes = doneData.notes || 'completed';
|
|
212
|
+
found = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!found) {
|
|
217
|
+
console.error(JSON.stringify({ warning: `Feature not found in ${featuresFilePath}: ${featureId}` }));
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Update top-level counts
|
|
222
|
+
featuresData.analyzedCount = featuresData.features.filter(f => f.analyzed).length;
|
|
223
|
+
featuresData.pendingCount = featuresData.features.filter(f => !f.analyzed).length;
|
|
224
|
+
|
|
225
|
+
// Write back to file (atomic write)
|
|
226
|
+
fs.writeFileSync(featuresFilePath, JSON.stringify(featuresData, null, 2));
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
158
230
|
// process-results subcommand
|
|
159
231
|
function processResults(args) {
|
|
160
232
|
const { syncStatePath, graphRoot, completedDir } = args;
|
|
@@ -165,9 +237,10 @@ function processResults(args) {
|
|
|
165
237
|
let success = 0;
|
|
166
238
|
let failed = 0;
|
|
167
239
|
let graphUpdated = false;
|
|
240
|
+
let featuresUpdated = 0;
|
|
168
241
|
|
|
169
242
|
if (!fs.existsSync(completedDir)) {
|
|
170
|
-
console.log(JSON.stringify({ success, failed, graphUpdated }));
|
|
243
|
+
console.log(JSON.stringify({ success, failed, graphUpdated, featuresUpdated }));
|
|
171
244
|
return;
|
|
172
245
|
}
|
|
173
246
|
|
|
@@ -178,14 +251,26 @@ function processResults(args) {
|
|
|
178
251
|
for (const file of doneFiles) {
|
|
179
252
|
const filePath = path.join(completedDir, file);
|
|
180
253
|
const data = readJsonSafe(filePath);
|
|
254
|
+
|
|
255
|
+
// Extract featureId from filename: {featureId}.done.json
|
|
256
|
+
const featureId = file.replace('.done.json', '');
|
|
257
|
+
|
|
181
258
|
if (data) {
|
|
182
259
|
if (data.status === 'success' || data.status === 'completed') {
|
|
183
260
|
success++;
|
|
261
|
+
// Update features JSON for successful analysis
|
|
262
|
+
if (updateFeaturesJson(syncStatePath, featureId, data)) {
|
|
263
|
+
featuresUpdated++;
|
|
264
|
+
}
|
|
184
265
|
} else if (data.status === 'failed' || data.status === 'error') {
|
|
185
266
|
failed++;
|
|
186
267
|
} else {
|
|
187
268
|
// Default to success if no status field
|
|
188
269
|
success++;
|
|
270
|
+
// Update features JSON for successful analysis
|
|
271
|
+
if (updateFeaturesJson(syncStatePath, featureId, data)) {
|
|
272
|
+
featuresUpdated++;
|
|
273
|
+
}
|
|
189
274
|
}
|
|
190
275
|
} else {
|
|
191
276
|
// Invalid JSON, assume success
|
|
@@ -260,7 +345,8 @@ function processResults(args) {
|
|
|
260
345
|
console.log(JSON.stringify({
|
|
261
346
|
success,
|
|
262
347
|
failed,
|
|
263
|
-
graphUpdated
|
|
348
|
+
graphUpdated,
|
|
349
|
+
featuresUpdated
|
|
264
350
|
}));
|
|
265
351
|
}
|
|
266
352
|
|
package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/generate-inventory.js
CHANGED
|
@@ -63,13 +63,25 @@ function toRelativePath(absolutePath, projectRoot) {
|
|
|
63
63
|
return normalizedAbs;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Generate unique feature ID from source path
|
|
67
|
+
// Converts path separators to hyphens, removes extension
|
|
68
|
+
// e.g., 'src/views/bpm/form/data.ts' -> 'src-views-bpm-form-data'
|
|
69
|
+
// e.g., 'ruoyi-fastapi-app/src/pages/common/agreement/index.vue' -> 'ruoyi-fastapi-app-src-pages-common-agreement-index'
|
|
70
|
+
function generateFeatureId(relativeSourcePath) {
|
|
71
|
+
// Remove file extension
|
|
72
|
+
const pathWithoutExt = relativeSourcePath.replace(/\.[^.]+$/, '');
|
|
73
|
+
|
|
74
|
+
// Replace path separators with hyphens
|
|
75
|
+
const id = pathWithoutExt.replace(/\//g, '-').replace(/\\/g, '-');
|
|
76
|
+
|
|
77
|
+
return id;
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
// Generate document path for a feature
|
|
67
|
-
// Format: speccrew-workspace/knowledges/bizs/{platformId}/{module}/{subpath}/{
|
|
81
|
+
// Format: speccrew-workspace/knowledges/bizs/{platformId}/{module}/{subpath}/{uniqueName}.md
|
|
82
|
+
// The documentPath must be unique to avoid overwriting
|
|
68
83
|
function generateDocumentPath(platformId, module, sourcePath, projectRoot) {
|
|
69
|
-
//
|
|
70
|
-
const basename = path.basename(sourcePath, path.extname(sourcePath));
|
|
71
|
-
|
|
72
|
-
// Get directory relative to module root
|
|
84
|
+
// Get the relative path from project root
|
|
73
85
|
const relativePath = toRelativePath(sourcePath, projectRoot);
|
|
74
86
|
|
|
75
87
|
// Parse the source path to extract module and subpath
|
|
@@ -92,6 +104,22 @@ function generateDocumentPath(platformId, module, sourcePath, projectRoot) {
|
|
|
92
104
|
subpath = pathParts.slice(moduleIndex + 1, pathParts.length - 1).join('/');
|
|
93
105
|
}
|
|
94
106
|
|
|
107
|
+
// Extract filename without extension
|
|
108
|
+
const basename = path.basename(sourcePath, path.extname(sourcePath));
|
|
109
|
+
|
|
110
|
+
// Generate unique document name to avoid collisions
|
|
111
|
+
// Use the path after module to create a unique name
|
|
112
|
+
let uniqueDocName;
|
|
113
|
+
if (moduleIndex >= 0 && moduleIndex < pathParts.length - 1) {
|
|
114
|
+
// Get all path parts after module (excluding extension)
|
|
115
|
+
const pathAfterModule = pathParts.slice(moduleIndex + 1);
|
|
116
|
+
// Join with hyphens to create unique name
|
|
117
|
+
uniqueDocName = pathAfterModule.join('-').replace(/\.[^.]+$/, '');
|
|
118
|
+
} else {
|
|
119
|
+
// Fallback: use basename
|
|
120
|
+
uniqueDocName = basename;
|
|
121
|
+
}
|
|
122
|
+
|
|
95
123
|
// Construct document path using platformId (which follows {platformType}-{techStack} format)
|
|
96
124
|
// e.g., backend-fastapi, web-vue3, mobile-uniapp
|
|
97
125
|
const docPathParts = ['speccrew-workspace', 'knowledges', 'bizs', platformId, module];
|
|
@@ -100,7 +128,7 @@ function generateDocumentPath(platformId, module, sourcePath, projectRoot) {
|
|
|
100
128
|
docPathParts.push(subpath);
|
|
101
129
|
}
|
|
102
130
|
|
|
103
|
-
docPathParts.push(`${
|
|
131
|
+
docPathParts.push(`${uniqueDocName}.md`);
|
|
104
132
|
|
|
105
133
|
return docPathParts.join('/');
|
|
106
134
|
}
|
|
@@ -203,10 +231,14 @@ function main() {
|
|
|
203
231
|
const relativeSourcePath = toRelativePath(sourceFile, projectRoot);
|
|
204
232
|
const fileName = path.basename(sourceFile, path.extname(sourceFile));
|
|
205
233
|
|
|
206
|
-
// Generate
|
|
234
|
+
// Generate unique feature ID based on full relative path
|
|
235
|
+
const featureId = generateFeatureId(relativeSourcePath);
|
|
236
|
+
|
|
237
|
+
// Generate document path using platformId (now with unique filename)
|
|
207
238
|
const documentPath = generateDocumentPath(platformId, moduleName, sourceFile, projectRoot);
|
|
208
239
|
|
|
209
240
|
features.push({
|
|
241
|
+
id: featureId,
|
|
210
242
|
fileName: fileName,
|
|
211
243
|
sourcePath: relativeSourcePath,
|
|
212
244
|
documentPath: documentPath,
|
|
@@ -227,6 +259,9 @@ function main() {
|
|
|
227
259
|
});
|
|
228
260
|
}
|
|
229
261
|
|
|
262
|
+
// Determine analysis method based on platform type
|
|
263
|
+
const analysisMethod = platformType === 'backend' ? 'api-based' : 'ui-based';
|
|
264
|
+
|
|
230
265
|
// Build output JSON
|
|
231
266
|
const outputData = {
|
|
232
267
|
platformName: platformName,
|
|
@@ -235,6 +270,7 @@ function main() {
|
|
|
235
270
|
platformId: platformId,
|
|
236
271
|
sourcePath: sourceRoot,
|
|
237
272
|
techStack: techStack,
|
|
273
|
+
analysisMethod: analysisMethod,
|
|
238
274
|
modules: modules,
|
|
239
275
|
totalFiles: features.length,
|
|
240
276
|
analyzedCount: 0,
|