sillyspec 3.28.2 → 3.28.4
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/.claude/skills/sillyspec-auto/SKILL.md +16 -55
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -1
- package/.claude/skills/sillyspec-continue/SKILL.md +8 -38
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -2
- package/.claude/skills/sillyspec-knowledge/SKILL.md +19 -249
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-propose/SKILL.md +6 -77
- package/.claude/skills/sillyspec-resume/SKILL.md +8 -57
- package/.claude/skills/sillyspec-state/SKILL.md +7 -46
- package/.claude/skills/sillyspec-workspace/SKILL.md +14 -156
- package/AGENTS.md +24 -0
- package/INSTRUCTIONS.md +12 -0
- package/package.json +1 -1
- package/src/archive-delta.js +127 -2
- package/src/change-delete.js +2 -1
- package/src/change-list.js +7 -2
- package/src/change-risk-profile.js +77 -6
- package/src/config-cat.js +12 -1
- package/src/constants.js +9 -1
- package/src/design-facts.js +64 -0
- package/src/dispatch/backends/sillyhub-mcp.js +17 -5
- package/src/docs-check.js +153 -27
- package/src/docs-migrate.js +124 -0
- package/src/doctor-diagnostics.js +441 -5
- package/src/foreign-declared.js +53 -2
- package/src/index.js +353 -28
- package/src/module-impact.js +77 -12
- package/src/plan-adopt-waves.js +13 -5
- package/src/progress/change-registry.js +35 -0
- package/src/progress.js +64 -1
- package/src/quick-recommend.js +6 -1
- package/src/quicklog.js +10 -2
- package/src/run/command.js +159 -13
- package/src/run/complete-handlers.js +162 -9
- package/src/run/complete.js +104 -5
- package/src/run/gates.js +116 -8
- package/src/run/progress-confirm.js +66 -0
- package/src/run/prompt.js +235 -36
- package/src/run/quick-audit.js +12 -5
- package/src/run/scan-profile.js +20 -2
- package/src/run/shared.js +84 -11
- package/src/run/stage.js +42 -0
- package/src/runtime-hygiene.js +84 -0
- package/src/scan-postcheck.js +5 -1
- package/src/spec-sync.js +63 -8
- package/src/stage-contract.js +83 -3
- package/src/stage-review.js +3 -0
- package/src/stages/archive.js +4 -4
- package/src/stages/brainstorm-auto.js +2 -1
- package/src/stages/brainstorm.js +11 -25
- package/src/stages/doctor.js +41 -515
- package/src/stages/execute.js +10 -14
- package/src/stages/plan-postcheck.js +191 -7
- package/src/stages/plan.js +7 -27
- package/src/stages/quick.js +15 -13
- package/src/stages/verify.js +23 -21
- package/src/sync-noise.js +158 -0
- package/src/sync.js +42 -17
- package/src/task-review.js +225 -1
- package/src/verify-facts-schema.js +141 -0
- package/src/verify-postcheck.js +421 -49
- package/src/verify-probes.js +142 -13
- package/src/workflow.js +6 -0
- package/src/worktree-apply.js +51 -13
- package/src/worktree.js +5 -1
- package/templates/agents-instruction.md +1 -1
- package/.claude/skills/verify-per-user/SKILL.md +0 -6
package/src/index.js
CHANGED
|
@@ -93,13 +93,18 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
93
93
|
sillyspec gate <stage> --change <name> [--json] 机器门控:阶段能否标记完成(只读)
|
|
94
94
|
sillyspec derive <facet> --change <name> [--json] 单项事实核验(facet: execute-evidence|verify-test|task-reviews|artifacts)
|
|
95
95
|
sillyspec backfill-reviews --change <name> [--adopt] [--json] 缺 review.json 的 task 生成草稿;--adopt 重算代填已有 review 的 base/head 等机械字段(verdict 保留)
|
|
96
|
+
sillyspec review write --change <name> --task task-NN --spec <verdict> --quality <verdict>
|
|
97
|
+
[--notes ...] [--evidence ...] [--changed-files a,b] [--force]
|
|
98
|
+
单 task review.json 命令式写入:verdict/notes 你给,base/head/changedFiles/diffPaths CLI 代算
|
|
96
99
|
sillyspec symbol-impact --change <name> 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(gate 拒绝未替换占位,防骨架直接过门)
|
|
97
100
|
sillyspec design-init --change <name> [--force] 从 decisions.md 生成 design.md 十三章节骨架(决策追踪表预填;已存在不覆盖)
|
|
101
|
+
sillyspec fourpiece-init --change <name> 生成 proposal/requirements/decisions 骨架(frontmatter+章节+模板占位;已存在不覆盖)
|
|
98
102
|
sillyspec delta --change <name> [--json] 生成变更 delta.md(Before/Delta/After 三段式聚合;幂等覆盖重跑即刷新)
|
|
99
103
|
sillyspec next 项目状态探测:输出当前状态 + 下一步命令 + 依据(吸收 continue/resume 手工探测表)
|
|
100
104
|
sillyspec commit [--json] 智能提交建议:收集 QUICKLOG/已勾 task/阶段产出语义,生成建议 message(只建议不执行)
|
|
101
105
|
sillyspec verify-probes --change <name> [--init] verify 机械探针(TODO 标记/测试覆盖/API 对账/删除对账);--init 生成 verify-result.md 骨架
|
|
102
106
|
sillyspec module-impact --change <name> 生成 module-impact.md 骨架(文件×模块归属按 module-map 预填 + 未匹配清单)
|
|
107
|
+
sillyspec module-docs-sync --change <name> [--note ...] diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等)
|
|
103
108
|
sillyspec endpoints extract --change <name> [--task task-NN | --all-tasks] [--dir <dir>|--files <a.py,b.js>] 静态扫描路由装饰器生成 endpoints.json
|
|
104
109
|
sillyspec endpoints baseline --change <name> [--spec-dir <path>] [--json] 拍变更前端点基线(幂等不覆盖;worktree 内跑自动锚主仓;归档 delta 端点增删 before 侧)
|
|
105
110
|
sillyspec scan-fix-headers [--project <名>] scan 文档补 author/created_at header(幂等)
|
|
@@ -114,6 +119,7 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
114
119
|
(无子命令) 跑诊断,列出问题
|
|
115
120
|
--align-execute-progress [--change <名>] [--confirm] 基于 plan.md 对齐 execute 派生戳(默认 dry-run)
|
|
116
121
|
--cleanup-remnant [--confirm] 删除 0 字节空占位 db(默认 dry-run,仅 --confirm 落盘)
|
|
122
|
+
--gc-unstamped-runs [--confirm] 回收已归档变更留下的无 change 戳 execute-runs(默认 dry-run)
|
|
117
123
|
--dump-db --path <db 路径> dump 指定 db 内容到文件(取证用)
|
|
118
124
|
--json 结构化诊断 + 落盘 .sillyspec/.runtime/doctor-diagnosis.json
|
|
119
125
|
sillyspec modules <rebuild | status | migrate>
|
|
@@ -565,6 +571,72 @@ async function main() {
|
|
|
565
571
|
process.exitCode = exitCode;
|
|
566
572
|
break;
|
|
567
573
|
}
|
|
574
|
+
case 'review': {
|
|
575
|
+
// 单 task review.json 命令式写入(2026-09-07 noAI 主流程批次 T2-4):agent 只给判断字段
|
|
576
|
+
// (verdict/notes/evidence),mechanics(executeRunId/base/head/changedFiles/diffPaths)全由
|
|
577
|
+
// CLI 代算——消灭「手拼 JSON→gate 拒→重写」循环与手算 hash 错误面。
|
|
578
|
+
// sillyspec review write --change <名> --task task-NN --spec pass --quality pass
|
|
579
|
+
// [--notes "..."] [--evidence "..."](cannot_verify 必填)
|
|
580
|
+
// [--changed-files a,b](归属切片为空或纯验证任务时显式给出;""=空覆盖)
|
|
581
|
+
// [--base <rev> --head <rev>](无 worktree meta 时显式锡点)[--force]
|
|
582
|
+
const rwSub = filteredArgs[1];
|
|
583
|
+
if (rwSub !== 'write') {
|
|
584
|
+
console.error('用法: sillyspec review write --change <名> --task task-NN --spec <pass|fail|cannot_verify> --quality <verdict>\n [--notes "评审备注"] [--evidence "cannot_verify 证据说明"] [--changed-files a,b] [--base <rev> --head <rev>] [--force] [--json]\n 单 task review.json 命令式写入:verdict/notes 由你给,executeRunId/base/head/changedFiles/diffPaths 由 CLI 从 git + task 卡代算;已存在默认拒覆盖(--force 越过)');
|
|
585
|
+
process.exit(2);
|
|
586
|
+
}
|
|
587
|
+
const rwVal = (flag) => {
|
|
588
|
+
const i = filteredArgs.indexOf(flag);
|
|
589
|
+
return i >= 0 && filteredArgs[i + 1] ? filteredArgs[i + 1] : null;
|
|
590
|
+
};
|
|
591
|
+
const rwChange = rwVal('--change');
|
|
592
|
+
const rwTask = rwVal('--task');
|
|
593
|
+
const rwSpec = rwVal('--spec');
|
|
594
|
+
const rwQuality = rwVal('--quality');
|
|
595
|
+
const rwNotes = rwVal('--notes');
|
|
596
|
+
const rwEvidence = rwVal('--evidence');
|
|
597
|
+
const rwBase = rwVal('--base');
|
|
598
|
+
const rwHead = rwVal('--head');
|
|
599
|
+
const rwFilesIdx = filteredArgs.indexOf('--changed-files');
|
|
600
|
+
// --changed-files 存在即视为显式覆盖(含空串=纯验证任务零 diff 声明);不存在传 null 走归属切片
|
|
601
|
+
const rwFilesOverride = rwFilesIdx >= 0
|
|
602
|
+
? (filteredArgs[rwFilesIdx + 1] || '').split(',').map(s => s.trim()).filter(Boolean)
|
|
603
|
+
: null;
|
|
604
|
+
if (!rwChange || !rwTask || !rwSpec || !rwQuality) {
|
|
605
|
+
console.error('用法: sillyspec review write --change <名> --task task-NN --spec <verdict> --quality <verdict> [--notes ...] [--evidence ...] [--changed-files a,b] [--base <rev> --head <rev>] [--force]');
|
|
606
|
+
process.exit(2);
|
|
607
|
+
}
|
|
608
|
+
assertSafeChangeName(rwChange, '--change 变更名');
|
|
609
|
+
const { writeTaskReview } = await import('./task-review.js');
|
|
610
|
+
const rwPlatformOpts = {};
|
|
611
|
+
const rwResolved = resolvePlatformOpts(dir, specDir);
|
|
612
|
+
if (rwResolved) {
|
|
613
|
+
rwPlatformOpts.specRoot = rwResolved.specRoot;
|
|
614
|
+
if (rwResolved.runtimeRoot) rwPlatformOpts.runtimeRoot = rwResolved.runtimeRoot;
|
|
615
|
+
}
|
|
616
|
+
const rwResult = await writeTaskReview({
|
|
617
|
+
changeName: rwChange, cwd: dir, taskId: rwTask,
|
|
618
|
+
specVerdict: rwSpec, qualityVerdict: rwQuality,
|
|
619
|
+
reviewerNotes: rwNotes || '',
|
|
620
|
+
requiredEvidence: rwEvidence ? [rwEvidence] : [],
|
|
621
|
+
baseOverride: rwBase, headOverride: rwHead,
|
|
622
|
+
changedFilesOverride: rwFilesOverride,
|
|
623
|
+
force: filteredArgs.includes('--force'),
|
|
624
|
+
platformOpts: rwPlatformOpts,
|
|
625
|
+
});
|
|
626
|
+
if (json) {
|
|
627
|
+
console.log(JSON.stringify({ ok: rwResult.ok, ...rwResult }, null, 2));
|
|
628
|
+
} else if (rwResult.ok) {
|
|
629
|
+
console.log(`✅ review.json 已写入:${rwResult.reviewPath}`);
|
|
630
|
+
console.log(` executeRunId: ${rwResult.executeRunId}`);
|
|
631
|
+
for (const w of rwResult.warnings) console.log(`⚠️ ${w}`);
|
|
632
|
+
} else {
|
|
633
|
+
console.error('❌ review write 失败:');
|
|
634
|
+
for (const e of rwResult.errors) console.error(` - ${e}`);
|
|
635
|
+
for (const w of rwResult.warnings) console.error(`⚠️ ${w}`);
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
568
640
|
case 'backfill-reviews': {
|
|
569
641
|
// 手动补 task(reopen execute / 直接实现)缺 review.json 的官方入口(坑 verify-archive-flow-pitfalls 坑2)。
|
|
570
642
|
// 复用 generateTaskReviewDrafts(execute --done 同源兜底,幂等、fail-open):据 git diff base..head + working-tree
|
|
@@ -873,10 +945,53 @@ async function main() {
|
|
|
873
945
|
writeFileSync(vpReportPath, generateVerifyResultSkeleton(vpResult));
|
|
874
946
|
console.log(`\n📄 已生成 verify-result.md 骨架: ${vpReportPath}(探针已预填;结论必须写明 PASS/FAIL,留待填会被 gate 判不过)`);
|
|
875
947
|
}
|
|
876
|
-
writeVerifyFacts(join(vpSpecBase, 'changes', vpChange), vpResult, vpChange); // P3b:facts 底稿随 --init
|
|
948
|
+
writeVerifyFacts(join(vpSpecBase, 'changes', vpChange), vpResult, vpChange); // P3b:facts 底稿随 --init 刷新(骨架已存在也刷新;v2 起分段合并保留固化段)
|
|
949
|
+
// 2026-09-08-ir-verify-facts R-02:已存在 md 缺证据账/回执槽段时仅追加骨架(幂等二跑
|
|
950
|
+
// 零改动);task 行按 verify-required-evidence.json 预填(存在时)。
|
|
951
|
+
if (existsSync(vpReportPath)) {
|
|
952
|
+
let vpReqItems = []
|
|
953
|
+
try {
|
|
954
|
+
const vpReqPath = join(vpSpecBase, 'changes', vpChange, 'verify-required-evidence.json')
|
|
955
|
+
if (existsSync(vpReqPath)) {
|
|
956
|
+
const vpReq = JSON.parse(readFileSync(vpReqPath, 'utf8'))
|
|
957
|
+
vpReqItems = Array.isArray(vpReq.items) ? vpReq.items.map(it => ({ task: it.task, evidence: it.evidence || [] })) : []
|
|
958
|
+
}
|
|
959
|
+
} catch { /* 读取失败 → 空预填,不阻断 */ }
|
|
960
|
+
const { backfillMissingEvidenceSlots } = await import('./verify-probes.js')
|
|
961
|
+
const vpAdded = backfillMissingEvidenceSlots(vpReportPath, vpReqItems)
|
|
962
|
+
if (vpAdded.added.length > 0) {
|
|
963
|
+
console.log(`📄 已补齐缺失槽段(不触碰既有正文): ${vpAdded.added.join('、')}`)
|
|
964
|
+
}
|
|
965
|
+
}
|
|
877
966
|
}
|
|
878
967
|
break;
|
|
879
968
|
}
|
|
969
|
+
case 'module-docs-sync': {
|
|
970
|
+
// 模块文档 sidecar 同步命令化(2026-09-09 刀批):diff 归属模块 → sidecar 追加变更索引行 +
|
|
971
|
+
// 卡 updated_at 戳——机械部分 CLI 代算,卡片正文语义更新仍归 agent/归档流程。幂等。
|
|
972
|
+
const mdsChangeIdx = args.indexOf('--change');
|
|
973
|
+
const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] ? args[mdsChangeIdx + 1] : null;
|
|
974
|
+
const mdsNoteIdx = args.indexOf('--note');
|
|
975
|
+
const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] ? args[mdsNoteIdx + 1] : null;
|
|
976
|
+
if (!mdsChange) {
|
|
977
|
+
console.error('用法: sillyspec module-docs-sync --change <name> [--note "一句话"] [--json]\n diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等;卡片正文语义更新仍归 agent)');
|
|
978
|
+
process.exit(2);
|
|
979
|
+
}
|
|
980
|
+
const { syncModuleDocSidecars } = await import('./module-impact.js');
|
|
981
|
+
const mdsResult = syncModuleDocSidecars({ cwd: dir, changeName: mdsChange, note: mdsNote, specDir });
|
|
982
|
+
if (json) {
|
|
983
|
+
console.log(JSON.stringify({ command: 'module-docs-sync', change: mdsChange, ...mdsResult }, null, 2));
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
if (mdsResult.reason) console.log(mdsResult.reason);
|
|
987
|
+
if (mdsResult.synced.length > 0) {
|
|
988
|
+
console.log(String.fromCharCode(9989) + ' sidecar 已同步 ' + mdsResult.synced.length + ' 个模块: ' + mdsResult.synced.join(', '));
|
|
989
|
+
console.log(' 卡 updated_at 已戳;卡片正文语义更新(如需要)仍归归档流程/agent。');
|
|
990
|
+
}
|
|
991
|
+
if (mdsResult.skipped.length > 0) console.log(String.fromCharCode(8505) + ' 已含本变更行跳过: ' + mdsResult.skipped.join(', '));
|
|
992
|
+
if (mdsResult.unmatched.length > 0) console.log(String.fromCharCode(9888) + ' 未匹配模块的文件 ' + mdsResult.unmatched.length + ' 个(不入 sidecar): ' + mdsResult.unmatched.slice(0, 5).join(', '));
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
880
995
|
case 'plan-adopt-waves': {
|
|
881
996
|
// plan.md Wave 段一键重排(坑 wave-manual-mismatch-noise,2026-08-24 用户反馈二期):
|
|
882
997
|
// 手排 Wave 与拓扑比对不一致需手改;依赖方向硬拦(plan-postcheck)落地后给一条零手写出路。
|
|
@@ -1244,6 +1359,94 @@ async function main() {
|
|
|
1244
1359
|
console.log(' 逐行替换 <!--TODO--> 为结论(无签名级变更也显式写「无」);gate 拒绝未替换的占位行。');
|
|
1245
1360
|
break;
|
|
1246
1361
|
}
|
|
1362
|
+
case 'fourpiece-init': {
|
|
1363
|
+
// 四件套骨架预生成(2026-09-09 ql-20260909-003,轮次经济学 §3.2):brainstorm 期 agent
|
|
1364
|
+
// 手写 proposal/requirements/decisions 的 frontmatter/章节骨架是格式返工根源——本命令
|
|
1365
|
+
// 逐文件生成骨架(已存在不覆盖),agent 只填语义。与 design-init 同族(幂等)。
|
|
1366
|
+
const fpChangeIdx = args.indexOf('--change');
|
|
1367
|
+
const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] ? args[fpChangeIdx + 1] : null;
|
|
1368
|
+
if (!fpChange) {
|
|
1369
|
+
console.error('用法: sillyspec fourpiece-init --change <name> [--json] [--spec-dir <path>]\n 生成 proposal.md/requirements.md/decisions.md 骨架(frontmatter+章节标题+模板占位;已存在不覆盖;design.md 归 design-init)');
|
|
1370
|
+
process.exit(2);
|
|
1371
|
+
}
|
|
1372
|
+
const fpSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
1373
|
+
const fpDir = join(fpSpecBase, 'changes', fpChange);
|
|
1374
|
+
mkdirSync(fpDir, { recursive: true });
|
|
1375
|
+
const fpNow = new Date();
|
|
1376
|
+
const fpDate = fpNow.toISOString().slice(0, 10);
|
|
1377
|
+
const fpStamp = fpNow.toISOString().slice(0, 19).replace('T', ' ');
|
|
1378
|
+
const generated = [];
|
|
1379
|
+
const mk = (fname, body) => {
|
|
1380
|
+
const p = join(fpDir, fname);
|
|
1381
|
+
if (existsSync(p)) { console.log(`ℹ️ ${fname} 已存在,不覆盖`); return; }
|
|
1382
|
+
writeFileSync(p, body);
|
|
1383
|
+
generated.push(fname);
|
|
1384
|
+
console.log(`✅ 已生成 ${fname} 骨架: ${p}`);
|
|
1385
|
+
};
|
|
1386
|
+
mk('proposal.md', `---
|
|
1387
|
+
author: qinyi
|
|
1388
|
+
created_at: ${fpStamp}
|
|
1389
|
+
---
|
|
1390
|
+
# 提案书(Proposal)
|
|
1391
|
+
|
|
1392
|
+
## 动机
|
|
1393
|
+
<!--TODO: 为什么做、解决什么核心问题-->
|
|
1394
|
+
|
|
1395
|
+
## 关键问题
|
|
1396
|
+
<!--TODO: 为什么现有方案不够(2-3 个痛点)-->
|
|
1397
|
+
|
|
1398
|
+
## 变更范围
|
|
1399
|
+
<!--TODO: 本次做什么-->
|
|
1400
|
+
|
|
1401
|
+
## 不在范围内(显式清单)
|
|
1402
|
+
- <!--TODO: 不做 X-->
|
|
1403
|
+
|
|
1404
|
+
## 成功标准(可验证)
|
|
1405
|
+
- <!--TODO: 可验证条目-->
|
|
1406
|
+
`);
|
|
1407
|
+
mk('requirements.md', `---
|
|
1408
|
+
author: qinyi
|
|
1409
|
+
created_at: ${fpStamp}
|
|
1410
|
+
---
|
|
1411
|
+
# 需求规格(Requirements)
|
|
1412
|
+
|
|
1413
|
+
## 角色
|
|
1414
|
+
| 角色 | 说明 |
|
|
1415
|
+
|---|---|
|
|
1416
|
+
| <!--TODO--> | <!--TODO--> |
|
|
1417
|
+
|
|
1418
|
+
## 功能需求
|
|
1419
|
+
|
|
1420
|
+
### FR-01: <!--TODO-->
|
|
1421
|
+
Given <!--TODO-->
|
|
1422
|
+
When <!--TODO-->
|
|
1423
|
+
Then <!--TODO-->
|
|
1424
|
+
|
|
1425
|
+
## 非功能需求
|
|
1426
|
+
- 兼容性:<!--TODO-->
|
|
1427
|
+
|
|
1428
|
+
## 决策覆盖矩阵(如存在 decisions.md)
|
|
1429
|
+
| 决策 ID | 覆盖的 FR | 说明 |
|
|
1430
|
+
|---|---|---|
|
|
1431
|
+
`);
|
|
1432
|
+
mk('decisions.md', `---
|
|
1433
|
+
author: qinyi
|
|
1434
|
+
created_at: ${fpStamp}
|
|
1435
|
+
change: ${fpChange}
|
|
1436
|
+
---
|
|
1437
|
+
|
|
1438
|
+
# 决策记录(Decisions)
|
|
1439
|
+
|
|
1440
|
+
<!-- 增量落盘:每解决一个有实现影响的问题当场追加一条(格式见 brainstorm Step 3 模板);幂等按 D-xxx@vN 判重 -->
|
|
1441
|
+
`);
|
|
1442
|
+
if (json) {
|
|
1443
|
+
console.log(JSON.stringify({ command: 'fourpiece-init', change: fpChange, generated }, null, 2));
|
|
1444
|
+
} else if (generated.length > 0) {
|
|
1445
|
+
console.log(`
|
|
1446
|
+
${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语义内容(骨架勿手删章节);design.md 用 sillyspec design-init。`);
|
|
1447
|
+
}
|
|
1448
|
+
break;
|
|
1449
|
+
}
|
|
1247
1450
|
case 'design-init': {
|
|
1248
1451
|
// design.md 十三章节骨架生成(change: 2026-09-07-ir-stage-p3c,task-02,FR-03):agent 此前
|
|
1249
1452
|
// 从零手写整份设计文档,章节漏项到 stage-contract/plan 门禁才暴露。本命令从 decisions.md
|
|
@@ -1328,16 +1531,29 @@ async function main() {
|
|
|
1328
1531
|
const { resolveRuntimeRoot } = await import('./run/shared.js');
|
|
1329
1532
|
const dlRuntimeRoot = resolveRuntimeRoot(dlPlatformOpts, dlSpecBase);
|
|
1330
1533
|
const { buildDeltaReport } = await import('./archive-delta.js');
|
|
1331
|
-
//
|
|
1332
|
-
//
|
|
1333
|
-
|
|
1534
|
+
// project 同口径修复(change: 2026-09-07-ir-hardening D-005@v1):此前硬编码 null →
|
|
1535
|
+
// 手动补跑恒走 loadModuleMap 降级(报告恒「无 module-map」注记)。改从 progress.project
|
|
1536
|
+
// 取值(与归档确认步自动生成同源),null 兜底降级不变。
|
|
1537
|
+
let dlProject = null;
|
|
1538
|
+
try {
|
|
1539
|
+
const { ProgressManager } = await import('./progress.js');
|
|
1540
|
+
const dlPm = new ProgressManager({ specDir: dlSpecBase });
|
|
1541
|
+
const dlProgress = dlPm.read(cwd, dlChange);
|
|
1542
|
+
dlProject = dlProgress?.project || null;
|
|
1543
|
+
} catch { /* 读 progress 失败 → null 兜底降级(fail-soft) */ }
|
|
1544
|
+
const dlResult = buildDeltaReport({
|
|
1334
1545
|
changeDir: dlChangeDir,
|
|
1335
1546
|
specRoot: dlSpecBase,
|
|
1336
|
-
project:
|
|
1547
|
+
project: dlProject,
|
|
1337
1548
|
runtimeRoot: dlRuntimeRoot,
|
|
1549
|
+
withSummary: true,
|
|
1338
1550
|
});
|
|
1551
|
+
const dlMarkdown = dlResult.markdown;
|
|
1339
1552
|
const dlPath = join(dlChangeDir, 'delta.md');
|
|
1340
1553
|
writeFileSync(dlPath, dlMarkdown);
|
|
1554
|
+
// 回灌 sidecar(2026-09-07-ir-hardening D-006):fail-soft,写失败不阻断 delta 输出
|
|
1555
|
+
const { writeLastDeltaSidecar } = await import('./archive-delta.js');
|
|
1556
|
+
writeLastDeltaSidecar(dlRuntimeRoot, dlResult);
|
|
1341
1557
|
if (json) {
|
|
1342
1558
|
console.log(JSON.stringify({ command: 'delta', change: dlChange, ok: true, path: dlPath, written: true }, null, 2));
|
|
1343
1559
|
break;
|
|
@@ -1419,8 +1635,40 @@ async function main() {
|
|
|
1419
1635
|
case 'docs': {
|
|
1420
1636
|
const docsSubCmd = filteredArgs[1];
|
|
1421
1637
|
if (docsSubCmd === 'migrate') {
|
|
1422
|
-
|
|
1423
|
-
|
|
1638
|
+
// docs migrate 双形态(2026-09-08-docs-fix-capability / D-002+D-004):
|
|
1639
|
+
// 有 --from/--to → 新语义(路径引用前缀迁移,薄模块 docs-migrate.js)
|
|
1640
|
+
// 无 flag → 旧语义(.sillyspec 结构一次性迁移 migrateDocs,兼容保留)
|
|
1641
|
+
const rawMigrateArgs = filteredArgs.slice(2);
|
|
1642
|
+
let mFrom = null, mTo = null, mApply = false;
|
|
1643
|
+
for (let i = 0; i < rawMigrateArgs.length; i++) {
|
|
1644
|
+
const a = rawMigrateArgs[i];
|
|
1645
|
+
if (a === '--from' && rawMigrateArgs[i + 1] !== undefined) { mFrom = rawMigrateArgs[i + 1]; i++; }
|
|
1646
|
+
else if (a === '--to' && rawMigrateArgs[i + 1] !== undefined) { mTo = rawMigrateArgs[i + 1]; i++; }
|
|
1647
|
+
else if (a === '--apply') { mApply = true; }
|
|
1648
|
+
else if (a === '--from' || a === '--to') {
|
|
1649
|
+
console.error(`❌ docs migrate: ${a} 缺值(如 --from "modules/" --to "backend/app/modules/")`);
|
|
1650
|
+
process.exit(2);
|
|
1651
|
+
} else if (a.startsWith('--')) {
|
|
1652
|
+
console.error(`❌ docs migrate: 未知 flag「${a}」。已知 flag:--from --to --apply`);
|
|
1653
|
+
process.exit(2);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (mFrom !== null || mTo !== null) {
|
|
1657
|
+
if (mFrom === null || mTo === null) {
|
|
1658
|
+
console.error('❌ docs migrate: --from 与 --to 必须成对提供');
|
|
1659
|
+
process.exit(2);
|
|
1660
|
+
}
|
|
1661
|
+
const { runDocsMigrate } = await import('./docs-migrate.js');
|
|
1662
|
+
const result = runDocsMigrate({ projectRoot: dir, from: mFrom, to: mTo, apply: mApply });
|
|
1663
|
+
// exit code 契约:0=dry-run 零计划或 --apply 后 postCheck 全绿;1=--apply 后仍失效或
|
|
1664
|
+
// unverified>0;dry-run 有计划恒 0(预览不判成败)
|
|
1665
|
+
if (!mApply) { process.exit(0); }
|
|
1666
|
+
const fail = result.unverified > 0 || (result.postCheck && result.postCheck.invalid > 0);
|
|
1667
|
+
process.exit(fail ? 1 : 0);
|
|
1668
|
+
} else {
|
|
1669
|
+
const { migrateDocs } = await import('./migrate.js');
|
|
1670
|
+
migrateDocs(dir);
|
|
1671
|
+
}
|
|
1424
1672
|
} else if (docsSubCmd === 'check') {
|
|
1425
1673
|
// docs check(2026-08-15 docs-check-productize D-6):文档 file:line 引用校验。
|
|
1426
1674
|
// exit code 三档(D-003):0 全绿 / 1 存在无效引用 / 2 配置/IO 错误(DocsCheckConfigError)。
|
|
@@ -1433,14 +1681,14 @@ async function main() {
|
|
|
1433
1681
|
// 写回);--dry-run 预览不写盘,单独传(无 --fix)也走预览路径——design §5.2 行为矩阵
|
|
1434
1682
|
// --dry-run 列独立存在(「报告修复预览 + exit 1」),即 dryRun 置位 = fix 语义自动生效
|
|
1435
1683
|
// 但零写盘(本口径记录于此,防歧义)。
|
|
1436
|
-
const BARE_FLAGS = ['--
|
|
1684
|
+
const BARE_FLAGS = ['--fix', '--dry-run', '--no-exempt'];
|
|
1437
1685
|
const PAIRED_FLAGS = ['--paths'];
|
|
1438
1686
|
const rawDocsArgs = filteredArgs.slice(2);
|
|
1439
1687
|
const docsCheckFlags = [];
|
|
1440
1688
|
let cliPaths = null;
|
|
1441
|
-
let suggest = false;
|
|
1442
1689
|
let fix = false;
|
|
1443
1690
|
let dryRun = false;
|
|
1691
|
+
let noExempt = false;
|
|
1444
1692
|
for (let i = 0; i < rawDocsArgs.length; i++) {
|
|
1445
1693
|
const a = rawDocsArgs[i];
|
|
1446
1694
|
if (a === '--paths' && rawDocsArgs[i + 1] !== undefined) {
|
|
@@ -1450,9 +1698,9 @@ async function main() {
|
|
|
1450
1698
|
console.error('❌ docs check: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
|
|
1451
1699
|
process.exit(2);
|
|
1452
1700
|
} else if (BARE_FLAGS.includes(a)) {
|
|
1453
|
-
if (a === '--
|
|
1454
|
-
else if (a === '--fix') fix = true;
|
|
1701
|
+
if (a === '--fix') fix = true;
|
|
1455
1702
|
else if (a === '--dry-run') dryRun = true;
|
|
1703
|
+
else if (a === '--no-exempt') noExempt = true;
|
|
1456
1704
|
} else if (a.startsWith('--')) {
|
|
1457
1705
|
console.error(`❌ docs check: 未知 flag「${a}」。已知 flag:${[...BARE_FLAGS, ...PAIRED_FLAGS].join(' ')}(--json 为全局 flag)`);
|
|
1458
1706
|
process.exit(2);
|
|
@@ -1471,6 +1719,7 @@ async function main() {
|
|
|
1471
1719
|
skip: cfg.skip,
|
|
1472
1720
|
keywordAssert: cfg.keywordAssert,
|
|
1473
1721
|
crossRepoRoots: cfg.crossRepoRoots,
|
|
1722
|
+
exempt: !noExempt,
|
|
1474
1723
|
});
|
|
1475
1724
|
// task-03 修复链路(FR-04,platform-map-auto-anchors):fixActive 才构造 fixes——
|
|
1476
1725
|
// 仅 fix.fixable===true 且 newLine 为整数的条目可重锚;newRef = ref 的行号部分替换为
|
|
@@ -1491,6 +1740,31 @@ async function main() {
|
|
|
1491
1740
|
}
|
|
1492
1741
|
}
|
|
1493
1742
|
const fixResult = fixActive ? applyFixes(dir, fixes, dryRun ? { dryRun: true } : {}) : null;
|
|
1743
|
+
// 修复回执(change: 2026-09-07-ir-hardening D-007@v1,FR-04):--fix 落盘后用与首查
|
|
1744
|
+
// 完全相同的口径重跑一次失效收集,得 after 计数——「前 N → 重锚 M → 后 K(消除 N-K)」
|
|
1745
|
+
// 即修复建议被机器证明消除了多少诊断(acceptsFix 最小件)。仅 --fix(写盘)路径;
|
|
1746
|
+
// --dry-run 不写盘重跑无意义,receipt 为 null;非 fix 路径零变化。
|
|
1747
|
+
let fixReceipt = null;
|
|
1748
|
+
if (fix && !dryRun && fixResult && fixResult.applied > 0) {
|
|
1749
|
+
try {
|
|
1750
|
+
const after = runDocsCheck({
|
|
1751
|
+
projectRoot: dir,
|
|
1752
|
+
docs: docsCheckFlags.length > 0 ? docsCheckFlags : null,
|
|
1753
|
+
paths: cliPaths || cfg.paths,
|
|
1754
|
+
skip: cfg.skip,
|
|
1755
|
+
keywordAssert: cfg.keywordAssert,
|
|
1756
|
+
crossRepoRoots: cfg.crossRepoRoots,
|
|
1757
|
+
});
|
|
1758
|
+
fixReceipt = {
|
|
1759
|
+
invalidBefore: result.invalid.length,
|
|
1760
|
+
reAnchored: fixResult.applied,
|
|
1761
|
+
invalidAfter: after.invalid.length,
|
|
1762
|
+
eliminated: result.invalid.length - after.invalid.length,
|
|
1763
|
+
};
|
|
1764
|
+
} catch (e) {
|
|
1765
|
+
console.warn(`⚠️ 修复回执重算失败(不影响修复本身):${e && e.message ? e.message : e}`);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1494
1768
|
// 变更名提名 advisory(2026-08-31 变更关联审计 P2):铁律同决策规则族——只 warn,
|
|
1495
1769
|
// 不进 invalid、不影响下方 exit code 与 docs gate。降级容错:advisory 自身异常
|
|
1496
1770
|
// 不拖垮主检查(try-catch 吞并提示)。
|
|
@@ -1503,7 +1777,7 @@ async function main() {
|
|
|
1503
1777
|
if (json) {
|
|
1504
1778
|
// --fix 与 --json 组合保持 stdout 纯 JSON 可解析(constraints):修复统计作 result.fixReport
|
|
1505
1779
|
// 附加后整体 stringify,主体仍是 runDocsCheck 返回(invalid[].fix 是 task-01 设计内增量)
|
|
1506
|
-
if (fixResult) result.fixReport = { applied: fixResult.applied, skipped: fixResult.skipped.length, dryRun };
|
|
1780
|
+
if (fixResult) result.fixReport = { applied: fixResult.applied, skipped: fixResult.skipped.length, dryRun, ...(fixReceipt ? { receipt: fixReceipt } : {}) };
|
|
1507
1781
|
if (changeNameReport) result.changeNameReport = changeNameReport;
|
|
1508
1782
|
console.log(JSON.stringify(result, null, 2));
|
|
1509
1783
|
} else {
|
|
@@ -1514,7 +1788,10 @@ async function main() {
|
|
|
1514
1788
|
if (result.ok) {
|
|
1515
1789
|
console.log(`✅ docs check: ${result.total} 处引用全通过(其中 ${result.kwChecked} 处带关键词断言)`);
|
|
1516
1790
|
} else {
|
|
1517
|
-
|
|
1791
|
+
// FR-5(D-005,2026-09-08-docs-fix-capability):报告内容统一 stdout(脚本 capture_output
|
|
1792
|
+
// 只读 stdout 可捕获);stderr 仅留 ⚠️ 诊断(console.warn)与配置错误(exit 2 路径)。
|
|
1793
|
+
// 文本逐字不变,只改通道。
|
|
1794
|
+
console.log(`\n❌ docs check: ${result.invalid.length}/${result.total} 处引用失效:`);
|
|
1518
1795
|
let appliedLeft = fixResult ? fixResult.applied : 0;
|
|
1519
1796
|
const skippedQueue = fixResult ? [...fixResult.skipped] : [];
|
|
1520
1797
|
for (const inv of result.invalid) {
|
|
@@ -1522,26 +1799,31 @@ async function main() {
|
|
|
1522
1799
|
if (newRef !== undefined && appliedLeft > 0) {
|
|
1523
1800
|
// 已应用(dry-run 为将应用):归因按构造顺序消耗 applied 计数——写回 skipped 仅
|
|
1524
1801
|
// 防御路径(文档消失/行内失配)触发,CLI 正常流 fixable 必然落位
|
|
1525
|
-
console.
|
|
1802
|
+
console.log(` ✅ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${newRef}${dryRun ? '(dry-run 未写盘)' : ''}`);
|
|
1526
1803
|
appliedLeft--;
|
|
1527
1804
|
} else if (newRef !== undefined) {
|
|
1528
1805
|
const s = skippedQueue.shift();
|
|
1529
|
-
console.
|
|
1806
|
+
console.log(` ⚠️ [${inv.doc}:L${inv.docLine}] ${inv.ref} → 写回跳过:${s ? s.reason : '未知原因'}`);
|
|
1530
1807
|
} else if (fixResult) {
|
|
1531
1808
|
// needs-manual:多命中歧义/零命中/无 token——fix.reason 含候选行号,交人工
|
|
1532
|
-
console.
|
|
1809
|
+
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${(inv.fix && inv.fix.reason) || inv.reason}(待人工)`);
|
|
1533
1810
|
} else {
|
|
1534
|
-
console.
|
|
1811
|
+
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${inv.reason}`);
|
|
1535
1812
|
}
|
|
1536
|
-
if (
|
|
1537
|
-
|
|
1813
|
+
if (inv.suggest && inv.suggest.length > 0) {
|
|
1814
|
+
// 2026-09-07-ir-hardening task-08:原 --suggest 旗标删除(no-op 首选路径退役),
|
|
1815
|
+
// 候选行号提示改为 needs-manual 默认输出——旗标唯一用途,默认开更有信息量
|
|
1816
|
+
console.log(` 💡 候选行号: ${inv.suggest.join(', ')}(token 命中行,人工确认后更新文档锚)`);
|
|
1538
1817
|
}
|
|
1539
1818
|
}
|
|
1540
1819
|
if (fixResult) {
|
|
1541
|
-
console.
|
|
1820
|
+
console.log(`\n🔧 重锚报告:${fixResult.applied} 处已${dryRun ? '预览' : '改写'}${dryRun ? '(dry-run 未写盘)' : ''}、${result.invalid.length - fixes.length} 处待人工${fixResult.skipped.length > 0 ? `、${fixResult.skipped.length} 处写回跳过` : ''}。`);
|
|
1821
|
+
if (fixReceipt) {
|
|
1822
|
+
console.log(`🔁 修复回执(机器复跑证明):修复前 ${fixReceipt.invalidBefore} 处失效 → 重锚 ${fixReceipt.reAnchored} 处 → 修复后 ${fixReceipt.invalidAfter} 处(消除 ${fixReceipt.eliminated} 处)。`);
|
|
1542
1823
|
}
|
|
1543
|
-
|
|
1544
|
-
console.
|
|
1824
|
+
}
|
|
1825
|
+
console.log(`\n修复指引:行号漂移 → 更新文档行号到当前源码;文件删改名 → 更新引用路径;`);
|
|
1826
|
+
console.log(`关键词缺失但行号正确 → 确认符号是否改名,改文档 token 或行号。`);
|
|
1545
1827
|
}
|
|
1546
1828
|
// 变更名提名 advisory 段(主结果之后、exit 之前;不参与 ok/invalid 与 exit code)。
|
|
1547
1829
|
// 零输出原则(docs-check-fix 契约:无新 flag 时 stdout 逐字节一致;决策规则族同款
|
|
@@ -1622,7 +1904,9 @@ async function main() {
|
|
|
1622
1904
|
}
|
|
1623
1905
|
process.exit(g.exitCode);
|
|
1624
1906
|
} else {
|
|
1625
|
-
console.log('用法: sillyspec docs migrate | sillyspec docs check [--paths <glob,...>] [--json] [--
|
|
1907
|
+
console.log('用法: sillyspec docs migrate [--from <旧前缀> --to <新前缀> [--apply]] | sillyspec docs check [--paths <glob,...>] [--json] [--fix] [--dry-run] [--no-exempt] | sillyspec docs gate [--init-baseline] [--json]');
|
|
1908
|
+
console.log(' docs migrate:--from/--to 路径前缀迁移(默认 dry-run,--apply 写盘+自动复核);无 flag = 旧 .sillyspec 结构迁移');
|
|
1909
|
+
console.log(' docs check --no-exempt:archive/finished 与 doc_type: snapshot 文档照常校验(缺省豁免)');
|
|
1626
1910
|
process.exit(2);
|
|
1627
1911
|
}
|
|
1628
1912
|
break;
|
|
@@ -1660,9 +1944,10 @@ async function main() {
|
|
|
1660
1944
|
// slice(1) 去掉的是 'run' 字面量,这里 command 本身就是 stage 名不能丢。
|
|
1661
1945
|
case 'doctor': {
|
|
1662
1946
|
const doctorEffectiveDir = specDir ? dir : resolveEffectiveDir(dir);
|
|
1663
|
-
// 执行流:--cleanup-remnant / --cleanup-ghosts / --dump-db(结构化诊断之外的修复/取证动作)
|
|
1947
|
+
// 执行流:--cleanup-remnant / --cleanup-ghosts / --gc-unstamped-runs / --dump-db(结构化诊断之外的修复/取证动作)
|
|
1664
1948
|
const cleanupRemnant = filteredArgs.includes('--cleanup-remnant');
|
|
1665
1949
|
const cleanupGhosts = filteredArgs.includes('--cleanup-ghosts');
|
|
1950
|
+
const gcUnstampedRuns = filteredArgs.includes('--gc-unstamped-runs');
|
|
1666
1951
|
const dumpDbFlag = filteredArgs.includes('--dump-db');
|
|
1667
1952
|
const doctorConfirm = filteredArgs.includes('--confirm');
|
|
1668
1953
|
const pathIdx = filteredArgs.indexOf('--path');
|
|
@@ -1768,6 +2053,29 @@ async function main() {
|
|
|
1768
2053
|
process.exitCode = r.errors.length > 0 ? 1 : 0;
|
|
1769
2054
|
break;
|
|
1770
2055
|
}
|
|
2056
|
+
if (gcUnstampedRuns) {
|
|
2057
|
+
// ql-20260908-006-5f04:无戳 execute-runs 一次性清扫。默认 dry-run,--confirm 才删。
|
|
2058
|
+
// 不进 archive 热路径(pruneArchivedChangeRuntime 故意不猜无戳 run)。
|
|
2059
|
+
const { gcUnstampedExecuteRuns } = await import('./doctor-diagnostics.js');
|
|
2060
|
+
const r = await gcUnstampedExecuteRuns({ cwd: doctorEffectiveDir, specDir, confirm: doctorConfirm });
|
|
2061
|
+
if (json) {
|
|
2062
|
+
console.log(JSON.stringify(r, null, 2));
|
|
2063
|
+
} else {
|
|
2064
|
+
const verb = doctorConfirm ? '已删除' : '待删除(dry-run)';
|
|
2065
|
+
console.log(`🧹 无戳 execute-runs(仅已归档变更、交叉核验通过)${verb}:${r.count} 个 [runtime: ${r.runtime_root || '(无)'}]`);
|
|
2066
|
+
for (const c of r.candidates || []) {
|
|
2067
|
+
console.log(` ${doctorConfirm ? '✅' : '-'} ${c.runId} ← ${c.owner}(via ${c.via})`);
|
|
2068
|
+
}
|
|
2069
|
+
for (const s of r.skipped || []) console.log(` ⏭️ ${s.runId}(${s.reason})`);
|
|
2070
|
+
for (const e of r.errors || []) console.log(` ❌ ${e.runId || ''}: ${e.error}`);
|
|
2071
|
+
if (!doctorConfirm && r.count > 0) {
|
|
2072
|
+
console.log(`\n加 --confirm 执行删除(只删上面列出的无戳 run,有 change 戳 / 命中活跃变更 / 归属不明一律不动)。`);
|
|
2073
|
+
}
|
|
2074
|
+
if (r.reason) console.log(`ℹ️ ${r.reason}`);
|
|
2075
|
+
}
|
|
2076
|
+
process.exitCode = (r.errors && r.errors.length > 0) ? 1 : 0;
|
|
2077
|
+
break;
|
|
2078
|
+
}
|
|
1771
2079
|
if (dumpDbFlag) {
|
|
1772
2080
|
if (!dbPath) { console.error('❌ --dump-db 需要 --path <db 路径>'); process.exitCode = 2; break; }
|
|
1773
2081
|
const { dumpDb } = await import('./doctor-diagnostics.js');
|
|
@@ -1794,9 +2102,21 @@ async function main() {
|
|
|
1794
2102
|
process.exitCode = output.overall_status === 'pass' ? 0 : 1;
|
|
1795
2103
|
break;
|
|
1796
2104
|
}
|
|
1797
|
-
//
|
|
1798
|
-
|
|
1799
|
-
|
|
2105
|
+
// 2026-09-09-doctor-noai:--status 拦截(审查 B-03:等价只读渲染——阶段进度视图,
|
|
2106
|
+
// 零副作用);非 --json 其余形态改道直跑诊断 + renderDoctorSummary(不走
|
|
2107
|
+
// runCommand/initChange/不刷 lastActive——只读零副作用承诺保真)。
|
|
2108
|
+
if (filteredArgs.includes('--status')) {
|
|
2109
|
+
// 等价只读渲染:复用 progress show(cwd 无活跃变更时提示无——与原只读短路视图同语义级)
|
|
2110
|
+
const pm = new ProgressManager({ specDir: resolvePlatformSpecDir(dir, specDir) });
|
|
2111
|
+
pm.show(doctorEffectiveDir);
|
|
2112
|
+
break;
|
|
2113
|
+
}
|
|
2114
|
+
{
|
|
2115
|
+
const { runDoctorDiagnostics, renderDoctorSummary, writeDoctorDiagnosis } = await import('./doctor-diagnostics.js')
|
|
2116
|
+
const diag = await runDoctorDiagnostics({ cwd: doctorEffectiveDir })
|
|
2117
|
+
console.log(renderDoctorSummary(diag))
|
|
2118
|
+
try { writeDoctorDiagnosis(diag, resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec')) } catch { /* fail-soft */ }
|
|
2119
|
+
}
|
|
1800
2120
|
break;
|
|
1801
2121
|
}
|
|
1802
2122
|
case 'scan': {
|
|
@@ -2770,7 +3090,12 @@ checkbox 行;depends_on 自动反填行内注解 "(depends_on: task-01,02)";
|
|
|
2770
3090
|
|
|
2771
3091
|
const { cmdTaskcard } = await import('./taskcard.js');
|
|
2772
3092
|
try {
|
|
2773
|
-
|
|
3093
|
+
// 坑 platform-sync-progress-rollback-and-db-corruption 坑4:taskcard 此前只走
|
|
3094
|
+
// resolveSpecDir(祖先遍历本地 .sillyspec),不感知平台接管指针——平台模式下
|
|
3095
|
+
// 仓库根裸跑报「变更目录不存在」,需显式 --spec-dir(与 run/plan 系列不一致)。
|
|
3096
|
+
// 对齐 endpoints 等命令:先 resolvePlatformSpecDir(指针 fail-closed 语义同源)。
|
|
3097
|
+
const tcPlatformSpecDir = resolvePlatformSpecDir(dir, specDir) || specDir;
|
|
3098
|
+
const result = cmdTaskcard(tcName, { cwd: dir, specDir: tcPlatformSpecDir, taskIds, title: titleVal, titleZh: titleZhVal, force, sets });
|
|
2774
3099
|
for (const f of result.created) console.log(`✅ 已生成: ${f}`);
|
|
2775
3100
|
for (const f of result.skipped) console.log(`⏭️ 已存在,跳过(--force 覆盖): ${f}`);
|
|
2776
3101
|
if (result.created.length > 0) {
|