speccrew 0.5.7 → 0.5.8
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/package.json
CHANGED
|
@@ -225,7 +225,10 @@ function parseArgs() {
|
|
|
225
225
|
description: null,
|
|
226
226
|
startedAt: null,
|
|
227
227
|
completedAt: null,
|
|
228
|
-
confirmedAt: null
|
|
228
|
+
confirmedAt: null,
|
|
229
|
+
featuresDir: null,
|
|
230
|
+
platforms: null,
|
|
231
|
+
force: false
|
|
229
232
|
};
|
|
230
233
|
|
|
231
234
|
// 第一个参数是命令
|
|
@@ -314,6 +317,18 @@ function parseArgs() {
|
|
|
314
317
|
case '-Overview':
|
|
315
318
|
result.overview = true;
|
|
316
319
|
break;
|
|
320
|
+
case '--features-dir':
|
|
321
|
+
case '-Features-Dir':
|
|
322
|
+
result.featuresDir = args[++i];
|
|
323
|
+
break;
|
|
324
|
+
case '--platforms':
|
|
325
|
+
case '-Platforms':
|
|
326
|
+
result.platforms = args[++i];
|
|
327
|
+
break;
|
|
328
|
+
case '--force':
|
|
329
|
+
case '-Force':
|
|
330
|
+
result.force = true;
|
|
331
|
+
break;
|
|
317
332
|
}
|
|
318
333
|
}
|
|
319
334
|
|
|
@@ -785,6 +800,154 @@ function cmdUpdateWorkflow(args) {
|
|
|
785
800
|
}
|
|
786
801
|
}
|
|
787
802
|
|
|
803
|
+
/**
|
|
804
|
+
* 命令:init-tasks - 扫描 feature-design 目录生成任务列表
|
|
805
|
+
*/
|
|
806
|
+
function cmdInitTasks(args) {
|
|
807
|
+
// 参数验证
|
|
808
|
+
if (!args.file || !args.stage || !args.featuresDir || !args.platforms) {
|
|
809
|
+
outputError('Usage: init-tasks --file <path> --stage <stage_name> --features-dir <dir> --platforms <comma-separated> [--force]');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const filePath = path.resolve(args.file);
|
|
813
|
+
const featuresDir = path.resolve(args.featuresDir);
|
|
814
|
+
const platforms = args.platforms.split(',').map(p => p.trim()).filter(p => p);
|
|
815
|
+
|
|
816
|
+
// 验证 platforms 非空
|
|
817
|
+
if (platforms.length === 0) {
|
|
818
|
+
outputError('Platforms list cannot be empty');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// 验证 features-dir 存在
|
|
822
|
+
if (!fs.existsSync(featuresDir)) {
|
|
823
|
+
outputError(`Features directory not found: ${featuresDir}`);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// 扫描 .feature-spec.md 文件
|
|
827
|
+
const featureFiles = [];
|
|
828
|
+
const files = fs.readdirSync(featuresDir);
|
|
829
|
+
for (const file of files) {
|
|
830
|
+
if (file.endsWith('.feature-spec.md')) {
|
|
831
|
+
featureFiles.push(file);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (featureFiles.length === 0) {
|
|
836
|
+
outputError(`No .feature-spec.md files found in: ${featuresDir}`);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// 从文件名提取 feature 信息
|
|
840
|
+
// 格式: F-{MODULE}-{NNN}-{feature-name}.feature-spec.md
|
|
841
|
+
const featurePattern = /^(F-([A-Z]+)-\d+)-(.+)\.feature-spec\.md$/;
|
|
842
|
+
const features = [];
|
|
843
|
+
|
|
844
|
+
for (const file of featureFiles) {
|
|
845
|
+
const match = file.match(featurePattern);
|
|
846
|
+
if (match) {
|
|
847
|
+
features.push({
|
|
848
|
+
feature_id: match[1], // F-APPT-001
|
|
849
|
+
module: match[2], // APPT
|
|
850
|
+
name: match[3], // 预约信息CRUD
|
|
851
|
+
file: file
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (features.length === 0) {
|
|
857
|
+
outputError('No valid feature files found. Expected format: F-{MODULE}-{NNN}-{feature-name}.feature-spec.md');
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// 按 feature ID 排序
|
|
861
|
+
features.sort((a, b) => a.feature_id.localeCompare(b.feature_id));
|
|
862
|
+
|
|
863
|
+
// 检查目标文件是否已有 tasks
|
|
864
|
+
if (fs.existsSync(filePath)) {
|
|
865
|
+
const existingData = readJsonFile(filePath);
|
|
866
|
+
if (existingData.tasks && existingData.tasks.length > 0 && !args.force) {
|
|
867
|
+
outputError(`Progress file already has ${existingData.tasks.length} tasks. Use --force to overwrite.`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// 生成任务列表
|
|
872
|
+
const tasks = [];
|
|
873
|
+
const now = getTimestamp();
|
|
874
|
+
|
|
875
|
+
// Module 排序顺序
|
|
876
|
+
const moduleOrder = ['APPT', 'BASE', 'CUST', 'EMP', 'ITEM', 'KNW', 'REPORT', 'REV', 'SERV'];
|
|
877
|
+
const getModuleIndex = (module) => {
|
|
878
|
+
const idx = moduleOrder.indexOf(module);
|
|
879
|
+
return idx === -1 ? 999 : idx;
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// 按 module 分组
|
|
883
|
+
const featuresByModule = {};
|
|
884
|
+
for (const feature of features) {
|
|
885
|
+
if (!featuresByModule[feature.module]) {
|
|
886
|
+
featuresByModule[feature.module] = [];
|
|
887
|
+
}
|
|
888
|
+
featuresByModule[feature.module].push(feature);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// 每个 module 内按 feature ID 排序
|
|
892
|
+
for (const module of Object.keys(featuresByModule)) {
|
|
893
|
+
featuresByModule[module].sort((a, b) => a.feature_id.localeCompare(b.feature_id));
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// 按 module 顺序生成任务
|
|
897
|
+
const sortedModules = Object.keys(featuresByModule).sort((a, b) => getModuleIndex(a) - getModuleIndex(b));
|
|
898
|
+
|
|
899
|
+
for (const module of sortedModules) {
|
|
900
|
+
for (const feature of featuresByModule[module]) {
|
|
901
|
+
for (const platform of platforms) {
|
|
902
|
+
tasks.push({
|
|
903
|
+
id: `sd-${platform}-${feature.feature_id}`,
|
|
904
|
+
name: `System Design - ${platform} - ${feature.feature_id} ${feature.name}`,
|
|
905
|
+
status: 'pending',
|
|
906
|
+
platform: platform,
|
|
907
|
+
feature_id: feature.feature_id,
|
|
908
|
+
module: feature.module,
|
|
909
|
+
created_at: now
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// 创建进度文件结构
|
|
916
|
+
const progressData = {
|
|
917
|
+
stage: args.stage,
|
|
918
|
+
created_at: now,
|
|
919
|
+
updated_at: now,
|
|
920
|
+
counts: calculateCounts(tasks),
|
|
921
|
+
tasks: tasks,
|
|
922
|
+
checkpoints: {}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// 确保目录存在
|
|
926
|
+
const dir = path.dirname(filePath);
|
|
927
|
+
if (!fs.existsSync(dir)) {
|
|
928
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// 获取锁并写入
|
|
932
|
+
let lockPath = null;
|
|
933
|
+
try {
|
|
934
|
+
lockPath = acquireLock(filePath);
|
|
935
|
+
atomicWriteJson(filePath, progressData);
|
|
936
|
+
outputSuccess(
|
|
937
|
+
`Generated ${tasks.length} tasks from ${features.length} features × ${platforms.length} platforms`,
|
|
938
|
+
{
|
|
939
|
+
file: filePath,
|
|
940
|
+
stage: args.stage,
|
|
941
|
+
features_count: features.length,
|
|
942
|
+
platforms: platforms,
|
|
943
|
+
counts: progressData.counts
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
} finally {
|
|
947
|
+
if (lockPath) releaseLock(lockPath);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
788
951
|
// ============================================================================
|
|
789
952
|
// 主入口
|
|
790
953
|
// ============================================================================
|
|
@@ -803,6 +966,7 @@ function main() {
|
|
|
803
966
|
console.error(' update-counts Recalculate task counts');
|
|
804
967
|
console.error(' write-checkpoint Write or update a checkpoint');
|
|
805
968
|
console.error(' update-workflow Update a workflow stage status');
|
|
969
|
+
console.error(' init-tasks Generate tasks from feature-spec files');
|
|
806
970
|
console.error('');
|
|
807
971
|
console.error('Run "node update-progress.js <command> --help" for more information.');
|
|
808
972
|
process.exit(1);
|
|
@@ -829,6 +993,9 @@ function main() {
|
|
|
829
993
|
case 'update-workflow':
|
|
830
994
|
cmdUpdateWorkflow(args);
|
|
831
995
|
break;
|
|
996
|
+
case 'init-tasks':
|
|
997
|
+
cmdInitTasks(args);
|
|
998
|
+
break;
|
|
832
999
|
default:
|
|
833
1000
|
outputError(`Unknown command: ${args.command}`);
|
|
834
1001
|
}
|