sillyspec 3.28.3 → 3.28.5
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 +54 -54
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-continue/SKILL.md +17 -17
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
- package/.claude/skills/sillyspec-knowledge/SKILL.md +40 -40
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-propose/SKILL.md +16 -16
- package/.claude/skills/sillyspec-resume/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +25 -25
- package/.claude/skills/sillyspec-workspace/SKILL.md +33 -33
- package/AGENTS.md +25 -0
- package/INSTRUCTIONS.md +12 -0
- package/package.json +1 -1
- package/results-review-tier.md +18 -0
- package/src/agent-session-log.js +4 -3
- package/src/archive-delta.js +82 -0
- package/src/change-delete.js +2 -1
- package/src/change-list.js +18 -3
- package/src/change-risk-profile.js +77 -6
- package/src/commit-suggest.js +52 -1
- package/src/config-cat.js +17 -1
- package/src/constants.js +3 -1
- package/src/decision-distill.js +45 -5
- package/src/decisions-io.js +96 -0
- package/src/design-facts.js +3 -0
- package/src/dispatch/probe.js +31 -8
- package/src/docs-check.js +102 -3
- package/src/doctor-diagnostics.js +441 -5
- package/src/foreign-declared.js +53 -2
- package/src/index.js +606 -22
- package/src/mcp-server.js +144 -0
- package/src/module-impact.js +84 -12
- package/src/modules.js +46 -34
- package/src/plan-adopt-waves.js +13 -5
- package/src/progress/change-registry.js +34 -0
- package/src/progress.js +75 -2
- package/src/quick-recommend.js +6 -1
- package/src/quicklog.js +70 -8
- package/src/review-dispatch.js +377 -0
- package/src/run/archive-distill.js +48 -0
- package/src/run/command.js +48 -13
- package/src/run/complete-handlers.js +215 -9
- package/src/run/complete.js +184 -2
- package/src/run/gate-snapshot.js +88 -0
- package/src/run/gates.js +206 -58
- package/src/run/prompt.js +150 -14
- package/src/run/quick-audit.js +38 -13
- package/src/run/shared.js +183 -12
- package/src/run/stage.js +52 -2
- package/src/run/verify-quality-scan.js +336 -0
- package/src/runtime-hygiene.js +84 -0
- package/src/scope-audit.js +735 -0
- package/src/sillyhub-mcp/client.js +58 -3
- package/src/spec-sync.js +63 -8
- package/src/stage-contract.js +83 -3
- package/src/stage-review-checklist.js +44 -0
- package/src/stage-review.js +125 -4
- package/src/stages/archive.js +60 -167
- package/src/stages/brainstorm.js +7 -17
- package/src/stages/doctor.js +41 -515
- package/src/stages/execute.js +13 -10
- package/src/stages/plan-postcheck.js +191 -7
- package/src/stages/plan.js +7 -35
- package/src/stages/quick.js +11 -11
- package/src/stages/verify.js +24 -39
- package/src/sync-noise.js +158 -0
- package/src/sync.js +93 -27
- package/src/task-review.js +133 -17
- package/src/taskcard.js +46 -3
- package/src/validate-artifacts.js +163 -0
- package/src/verify-facts-schema.js +141 -0
- package/src/verify-postcheck.js +347 -29
- package/src/verify-probes.js +275 -24
- package/src/workflow.js +6 -0
- package/src/worktree-apply.js +167 -24
- package/src/worktree.js +5 -1
- package/templates/agents-instruction.md +1 -0
package/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
10
10
|
import { writeAtomicSync } from './fs-atomic.js';
|
|
11
|
-
import { basename, dirname, extname, join, resolve, isAbsolute, sep } from 'path';
|
|
11
|
+
import { basename, dirname, extname, join, resolve, isAbsolute, sep, relative } from 'path';
|
|
12
12
|
import { safeGit, git } from './git-helper.js';
|
|
13
13
|
import { getVersion } from './version.js';
|
|
14
14
|
|
|
@@ -98,11 +98,14 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
98
98
|
单 task review.json 命令式写入:verdict/notes 你给,base/head/changedFiles/diffPaths CLI 代算
|
|
99
99
|
sillyspec symbol-impact --change <name> 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(gate 拒绝未替换占位,防骨架直接过门)
|
|
100
100
|
sillyspec design-init --change <name> [--force] 从 decisions.md 生成 design.md 十三章节骨架(决策追踪表预填;已存在不覆盖)
|
|
101
|
+
sillyspec fourpiece-init --change <name> 生成 proposal/requirements/decisions 骨架(frontmatter+章节+模板占位;已存在不覆盖)
|
|
101
102
|
sillyspec delta --change <name> [--json] 生成变更 delta.md(Before/Delta/After 三段式聚合;幂等覆盖重跑即刷新)
|
|
102
103
|
sillyspec next 项目状态探测:输出当前状态 + 下一步命令 + 依据(吸收 continue/resume 手工探测表)
|
|
103
104
|
sillyspec commit [--json] 智能提交建议:收集 QUICKLOG/已勾 task/阶段产出语义,生成建议 message(只建议不执行)
|
|
104
105
|
sillyspec verify-probes --change <name> [--init] verify 机械探针(TODO 标记/测试覆盖/API 对账/删除对账);--init 生成 verify-result.md 骨架
|
|
105
106
|
sillyspec module-impact --change <name> 生成 module-impact.md 骨架(文件×模块归属按 module-map 预填 + 未匹配清单)
|
|
107
|
+
sillyspec scope-audit --change <name> [--json] 变更范围对账:计划×实际三态全表 + 行数(✓ 计划内/⚠️ 计划外/⚠️ 计划未动;quick 会话传 quick-<id> 出归属表;已归档变更可查——快照记录态;advisory 只读不设门禁)
|
|
108
|
+
sillyspec module-docs-sync --change <name> [--note ...] diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等)
|
|
106
109
|
sillyspec endpoints extract --change <name> [--task task-NN | --all-tasks] [--dir <dir>|--files <a.py,b.js>] 静态扫描路由装饰器生成 endpoints.json
|
|
107
110
|
sillyspec endpoints baseline --change <name> [--spec-dir <path>] [--json] 拍变更前端点基线(幂等不覆盖;worktree 内跑自动锚主仓;归档 delta 端点增删 before 侧)
|
|
108
111
|
sillyspec scan-fix-headers [--project <名>] scan 文档补 author/created_at header(幂等)
|
|
@@ -117,6 +120,7 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
117
120
|
(无子命令) 跑诊断,列出问题
|
|
118
121
|
--align-execute-progress [--change <名>] [--confirm] 基于 plan.md 对齐 execute 派生戳(默认 dry-run)
|
|
119
122
|
--cleanup-remnant [--confirm] 删除 0 字节空占位 db(默认 dry-run,仅 --confirm 落盘)
|
|
123
|
+
--gc-unstamped-runs [--confirm] 回收已归档变更留下的无 change 戳 execute-runs(默认 dry-run)
|
|
120
124
|
--dump-db --path <db 路径> dump 指定 db 内容到文件(取证用)
|
|
121
125
|
--json 结构化诊断 + 落盘 .sillyspec/.runtime/doctor-diagnosis.json
|
|
122
126
|
sillyspec modules <rebuild | status | migrate>
|
|
@@ -625,6 +629,13 @@ async function main() {
|
|
|
625
629
|
} else if (rwResult.ok) {
|
|
626
630
|
console.log(`✅ review.json 已写入:${rwResult.reviewPath}`);
|
|
627
631
|
console.log(` executeRunId: ${rwResult.executeRunId}`);
|
|
632
|
+
// P2-f(noai-ir-roadmap §5)task 真源归一:review 落盘即由 CLI 渲染 checkbox——
|
|
633
|
+
// agent 永不手勾(review.json verdict 是唯一真源,tasks.md 勾选是它的显示态)。
|
|
634
|
+
try {
|
|
635
|
+
const { autoCheckPlanFromReviews } = await import('./run/complete.js');
|
|
636
|
+
const ac = await autoCheckPlanFromReviews({ stageName: 'execute', changeName: rwChange, cwd: dir, platformOpts: rwPlatformOpts });
|
|
637
|
+
if (ac.autoChecked && ac.checkedCount > 0) console.log(` ✅ tasks.md checkbox 已按 review verdict 自动勾选(${ac.checkedCount} 个;task 真源 = review.json,CLI 唯一勾选者——勿手动勾选)`);
|
|
638
|
+
} catch { /* 勾选 fail-soft:--done 时 autoCheck 兜底 */ }
|
|
628
639
|
for (const w of rwResult.warnings) console.log(`⚠️ ${w}`);
|
|
629
640
|
} else {
|
|
630
641
|
console.error('❌ review write 失败:');
|
|
@@ -814,8 +825,22 @@ async function main() {
|
|
|
814
825
|
// 项目状态探测(2026-08-21 审计第二批 G7/G8):吸收 continue/resume 两个 skill 的
|
|
815
826
|
// 手工探测表(文件存在性状态机),一条命令输出「状态 + 下一步命令 + 依据」。
|
|
816
827
|
// 纯 fs 零 token;活跃进度的权威状态仍归 progress show,本命令管无活跃进度时反推。
|
|
828
|
+
// --apply(P2,noai-ir-roadmap §5):建议是本 CLI 命令形态时直接代跑(恢复链 noAI——
|
|
829
|
+
// 只建议不执行让 agent 当转述员);非 CLI 命令形态的建议(自由文本)不代跑。
|
|
817
830
|
const { detectNextStep } = await import('./run/next.js');
|
|
818
831
|
const nxResult = detectNextStep({ cwd: dir, specDir });
|
|
832
|
+
if (args.includes('--apply')) {
|
|
833
|
+
const m = String(nxResult.next || '').match(/^sillyspec\s+(.+)$/);
|
|
834
|
+
if (!m) {
|
|
835
|
+
console.error(`❌ next --apply:建议不是可代跑的 CLI 命令形态(实得「${nxResult.next}」)——按建议手动执行`);
|
|
836
|
+
process.exit(2);
|
|
837
|
+
}
|
|
838
|
+
const childArgs = m[1].trim().split(/\s+/);
|
|
839
|
+
console.log(`🤖 next --apply 代跑:sillyspec ${childArgs.join(' ')}\n`);
|
|
840
|
+
const { spawnSync } = await import('node:child_process');
|
|
841
|
+
const r = spawnSync(process.execPath, [join(__dirname, '..', 'bin', 'sillyspec.js'), '--dir', dir, ...childArgs], { stdio: 'inherit' });
|
|
842
|
+
process.exit(r.status === null ? 1 : r.status);
|
|
843
|
+
}
|
|
819
844
|
if (json) {
|
|
820
845
|
console.log(JSON.stringify({ command: 'next', ...nxResult }, null, 2));
|
|
821
846
|
break;
|
|
@@ -837,7 +862,30 @@ async function main() {
|
|
|
837
862
|
// 智能提交建议(2026-08-21 审计第二批 G1-G3):commit skill 此前让 agent 手工收集
|
|
838
863
|
// 语义(LAST_COMMIT_TIME / cat QUICKLOG / 扫变更目录 / 按路径模式分类)——进度库、
|
|
839
864
|
// quicklog、git 全在 CLI 手里。一条命令产出统计 + 语义来源 + conventional 建议 +
|
|
840
|
-
// 可照抄的 git
|
|
865
|
+
// 可照抄的 git 命令。**默认只建议不执行**(确认权在人,对齐 skill 绝对规则「不要自动提交」);
|
|
866
|
+
// --apply(P1-6,noai-ir-roadmap §4)唯一例外且只限归档语境(文件已验收 + pathspec
|
|
867
|
+
// 显式 + pathspec 限定 commit 不扫并行会话暂存)——agent 上下文「需人确认」是伪约束,
|
|
868
|
+
// 通用自动 git 在多会话仓是历史重灾区,故收窄到这一处。
|
|
869
|
+
if (args.includes('--apply')) {
|
|
870
|
+
const apChangeIdx = args.indexOf('--change');
|
|
871
|
+
const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] ? args[apChangeIdx + 1] : null;
|
|
872
|
+
if (!apChange) {
|
|
873
|
+
console.error('用法: sillyspec commit --apply --change <名>\n 只限归档语境(changes.status=archived 或 current_stage=archive)执行归档产物提交');
|
|
874
|
+
process.exit(2);
|
|
875
|
+
}
|
|
876
|
+
assertSafeChangeName(apChange, '--change 变更名');
|
|
877
|
+
const { applyArchiveCommit } = await import('./commit-suggest.js');
|
|
878
|
+
const r = applyArchiveCommit({ cwd: dir, specDir, changeName: apChange });
|
|
879
|
+
if (!r.ok) {
|
|
880
|
+
console.error(`❌ commit --apply 拒绝: ${r.reason}`);
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
console.log(`✅ 归档提交完成(pathspec 限定 ${r.paths.length} 个路径,不含并行会话暂存):`);
|
|
884
|
+
for (const p of r.paths) console.log(` - ${p}`);
|
|
885
|
+
console.log(`\n💬 ${r.subject}`);
|
|
886
|
+
if (r.output) console.log(r.output.split('\n').map(l => ' ' + l).join('\n'));
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
841
889
|
const { collectCommitContext } = await import('./commit-suggest.js');
|
|
842
890
|
const cc = collectCommitContext({ cwd: dir, specDir });
|
|
843
891
|
if (json) {
|
|
@@ -884,7 +932,8 @@ async function main() {
|
|
|
884
932
|
console.log(cc.suggestion.body.split('\n').map(l => ' ' + l).join('\n'));
|
|
885
933
|
}
|
|
886
934
|
const bodyArgs = cc.suggestion.body ? ` -m "${cc.suggestion.body.replace(/"/g, '\\"').replace(/\n/g, ' ')}"` : '';
|
|
887
|
-
|
|
935
|
+
// 规则 18(2026-09-10 目录级 git add 事故):建议命令用显式 pathspec,不再教 git add -A
|
|
936
|
+
console.log(`\n 执行:git add -- <本次文件…> && git commit -m "${cc.suggestion.subject.replace(/"/g, '\\"')}"${bodyArgs} -- <本次文件…>`);
|
|
888
937
|
} else {
|
|
889
938
|
console.log('\n💡 无语义来源匹配——请结合上方 diff stat 手写 message');
|
|
890
939
|
}
|
|
@@ -905,7 +954,7 @@ async function main() {
|
|
|
905
954
|
process.exit(2);
|
|
906
955
|
}
|
|
907
956
|
assertSafeChangeName(vpChange, '--change 变更名');
|
|
908
|
-
const { runVerifyProbes, renderVerifyProbesReport, generateVerifyResultSkeleton, resolveVerifyProbesSpecBase, writeVerifyFacts } = await import('./verify-probes.js');
|
|
957
|
+
const { runVerifyProbes, renderVerifyProbesReport, generateVerifyResultSkeleton, resolveVerifyProbesSpecBase, writeVerifyFacts, formatPlatformPathNote } = await import('./verify-probes.js');
|
|
909
958
|
// spec 根统一走漂移锚定(坑 worktree-spec-artifact-misplace):在 worktree 内跑时锚回主仓,
|
|
910
959
|
// 探针读取与 --init 骨架都落主仓——与 plan/execute/verify/archive 的 command.js 守卫同口径。
|
|
911
960
|
// resolvePlatformSpecDir 仍先调(保留平台接管 fail-closed 检查副作用),但仅 pointer 存在时
|
|
@@ -914,6 +963,19 @@ async function main() {
|
|
|
914
963
|
const vpResolved = resolvePlatformSpecDir(dir, specDir);
|
|
915
964
|
const vpPlatformBase = existsSync(join(dir, '.sillyspec-platform.json')) ? vpResolved : null;
|
|
916
965
|
const vpSpecBase = resolveVerifyProbesSpecBase(dir, specDir, vpPlatformBase);
|
|
966
|
+
// 平台模式回显注记(2026-09-10 驾驭小结②):物理路径在 hub 镜像根,产物经 daemon
|
|
967
|
+
// spec-sync 落主仓——回显补主仓同步位置,消「回显镜像路径 / 核对主仓路径」的显示混乱
|
|
968
|
+
const vpPlatformNote = formatPlatformPathNote(vpPlatformBase, vpChange, 'verify-result.md');
|
|
969
|
+
// 平台模式双写镜像(坑 platform-init-artifact-daemon-dir-only,2026-09-10 驾驭小结第三批②):
|
|
970
|
+
// agent 本地(pointer 态、非显式 --spec-dir)--init 产物同时镜像主仓 changeDir,不再单点
|
|
971
|
+
// 依赖 spec-sync 回程(用户两次实锤产物落 daemon specs 同步目录)。
|
|
972
|
+
const mirrorInitArtifact = async (platformPath, file, content) => {
|
|
973
|
+
if (!vpPlatformBase || specDir) return null
|
|
974
|
+
try {
|
|
975
|
+
const { mirrorPlatformArtifactToMainRepo } = await import('./run/shared.js')
|
|
976
|
+
return mirrorPlatformArtifactToMainRepo({ cwd: dir, changeName: vpChange, file, content, platformBase: vpPlatformBase })
|
|
977
|
+
} catch { return null }
|
|
978
|
+
}
|
|
917
979
|
const vpResult = runVerifyProbes({ cwd: dir, changeName: vpChange, specDir: vpSpecBase });
|
|
918
980
|
if (json) {
|
|
919
981
|
console.log(JSON.stringify({ command: 'verify-probes', change: vpChange, ...vpResult }, null, 2));
|
|
@@ -933,17 +995,155 @@ async function main() {
|
|
|
933
995
|
// 指引「重跑 --init」对已存在报告是 no-op。修法:探针段是 CLI 拥有的机械段,检测缺失时
|
|
934
996
|
// 尾部补注入(正文其余不动),判别子自此有子节可对账。
|
|
935
997
|
const vpSection = `\n## 探针结果(CLI 机械预填,--init 补注入) [层:可复跑探针——gate 抽查防篡改]\n${renderVerifyProbesReport(vpResult)}\n`;
|
|
936
|
-
|
|
937
|
-
|
|
998
|
+
const vpInjected = vpExisting.replace(/\s*$/, '\n') + vpSection;
|
|
999
|
+
writeFileSync(vpReportPath, vpInjected);
|
|
1000
|
+
await mirrorInitArtifact(vpReportPath, 'verify-result.md', vpInjected);
|
|
1001
|
+
console.log(`\n📄 存量旧格式 verify-result.md 已补注入探针预填段: ${vpReportPath}(正文其余未动;补注后按新预填段如实核对结论)${vpPlatformNote}`);
|
|
938
1002
|
} else {
|
|
939
|
-
console.log(`\nℹ️ verify-result.md 已存在,不覆盖: ${vpReportPath}`);
|
|
1003
|
+
console.log(`\nℹ️ verify-result.md 已存在,不覆盖: ${vpReportPath}${vpPlatformNote}`);
|
|
940
1004
|
}
|
|
941
1005
|
} else {
|
|
942
|
-
|
|
943
|
-
|
|
1006
|
+
const vpSkeleton = generateVerifyResultSkeleton(vpResult);
|
|
1007
|
+
writeFileSync(vpReportPath, vpSkeleton);
|
|
1008
|
+
await mirrorInitArtifact(vpReportPath, 'verify-result.md', vpSkeleton);
|
|
1009
|
+
console.log(`\n📄 已生成 verify-result.md 骨架: ${vpReportPath}(探针已预填;结论必须写明 PASS/FAIL,留待填会被 gate 判不过)${vpPlatformNote}`);
|
|
1010
|
+
}
|
|
1011
|
+
const vpFacts = writeVerifyFacts(join(vpSpecBase, 'changes', vpChange), vpResult, vpChange, {
|
|
1012
|
+
platformNote: formatPlatformPathNote(vpPlatformBase, vpChange, 'verify-facts.json'),
|
|
1013
|
+
}); // P3b:facts 底稿随 --init 刷新(骨架已存在也刷新;v2 起分段合并保留固化段)
|
|
1014
|
+
if (vpFacts && vpFacts.path) {
|
|
1015
|
+
try { await mirrorInitArtifact(vpFacts.path, 'verify-facts.json', readFileSync(vpFacts.path, 'utf8')) } catch { /* 读回失败跳过镜像 */ }
|
|
1016
|
+
}
|
|
1017
|
+
// 2026-09-08-ir-verify-facts R-02:已存在 md 缺证据账/回执槽段时仅追加骨架(幂等二跑
|
|
1018
|
+
// 零改动);task 行按 verify-required-evidence.json 预填(存在时)。
|
|
1019
|
+
if (existsSync(vpReportPath)) {
|
|
1020
|
+
let vpReqItems = []
|
|
1021
|
+
try {
|
|
1022
|
+
const vpReqPath = join(vpSpecBase, 'changes', vpChange, 'verify-required-evidence.json')
|
|
1023
|
+
if (existsSync(vpReqPath)) {
|
|
1024
|
+
const vpReq = JSON.parse(readFileSync(vpReqPath, 'utf8'))
|
|
1025
|
+
vpReqItems = Array.isArray(vpReq.items) ? vpReq.items.map(it => ({ task: it.task, evidence: it.evidence || [] })) : []
|
|
1026
|
+
}
|
|
1027
|
+
} catch { /* 读取失败 → 空预填,不阻断 */ }
|
|
1028
|
+
const { backfillMissingEvidenceSlots } = await import('./verify-probes.js')
|
|
1029
|
+
const vpAdded = backfillMissingEvidenceSlots(vpReportPath, vpReqItems)
|
|
1030
|
+
if (vpAdded.added.length > 0) {
|
|
1031
|
+
console.log(`📄 已补齐缺失槽段(不触碰既有正文): ${vpAdded.added.join('、')}`)
|
|
1032
|
+
}
|
|
944
1033
|
}
|
|
945
|
-
|
|
1034
|
+
// P0-1(noai-ir-roadmap §3)两件机械预填,均幂等 fail-soft:
|
|
1035
|
+
// ①决策追踪矩阵机械半边——D→FR→task 链自 decisions.md × tasks/*.md frontmatter
|
|
1036
|
+
// 结构化字段构建,Evidence/状态两列留 agent 逐格复核;
|
|
1037
|
+
// ②结论草稿——机械事实全绿(noAI 实测 + 探针 1/3/5/6 + 风险门)才预填 PASS,
|
|
1038
|
+
// 「草稿待确认」形态,gate 判定链不动(前置三)。
|
|
1039
|
+
try {
|
|
1040
|
+
const { injectDecisionChainDraft } = await import('./verify-probes.js')
|
|
1041
|
+
const vpChain = injectDecisionChainDraft(vpReportPath, join(vpSpecBase, 'changes', vpChange))
|
|
1042
|
+
if (vpChain) {
|
|
1043
|
+
try { await mirrorInitArtifact(vpReportPath, 'verify-result.md', readFileSync(vpReportPath, 'utf8')) } catch { /* 镜像失败不阻断 */ }
|
|
1044
|
+
console.log(`🔗 决策追踪矩阵机械半边已预填:${vpChain.decisions} 条决策 × ${vpChain.tasks} 张 task 卡(Evidence/状态两列待逐格复核;未闭环行须在报告标注风险)`)
|
|
1045
|
+
}
|
|
1046
|
+
} catch { /* 链路预填失败不阻断 --init */ }
|
|
1047
|
+
try {
|
|
1048
|
+
const { extractVerifyConclusionSlot } = await import('./stage-contract.js')
|
|
1049
|
+
const { evaluateConclusionDraft } = await import('./run/verify-quality-scan.js')
|
|
1050
|
+
const { applyConclusionDraftToText } = await import('./verify-probes.js')
|
|
1051
|
+
const vpMd = readFileSync(vpReportPath, 'utf8')
|
|
1052
|
+
if (extractVerifyConclusionSlot(vpMd) === '') {
|
|
1053
|
+
const vpDraftEval = evaluateConclusionDraft({ cwd: dir, specBase: vpSpecBase, changeName: vpChange, changeDir: join(vpSpecBase, 'changes', vpChange), probesResult: vpResult })
|
|
1054
|
+
if (vpDraftEval.draft) {
|
|
1055
|
+
const vpNext = applyConclusionDraftToText(vpMd, vpDraftEval.draft)
|
|
1056
|
+
writeFileSync(vpReportPath, vpNext)
|
|
1057
|
+
try { await mirrorInitArtifact(vpReportPath, 'verify-result.md', vpNext) } catch { /* 镜像失败不阻断 */ }
|
|
1058
|
+
console.log(`🤖 结论草稿已预填 ${vpDraftEval.draft}(机械事实全绿:noAI 实测通过 + 探针 1/3/5/6 干净 + 风险门非 integration/deployment)——草稿待确认,复核后不同意即改写槽行;gate 独立复核不受预填影响。`)
|
|
1059
|
+
} else {
|
|
1060
|
+
console.log(`ℹ️ 结论草稿未预填(${vpDraftEval.reasons.join(';')})——槽行由你填写。`)
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
} catch { /* 草稿预填失败不阻断 --init */ }
|
|
1064
|
+
}
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
case 'review-dispatch': {
|
|
1068
|
+
// tier=independent 独立审查的平台派发命令(2026-09-10-review-dispatch FR-01~04 / D-001~003@1):
|
|
1069
|
+
// 三形态——缺省=create(probe 三层前置 + createMission(external) + dispatchWorker(read_only),
|
|
1070
|
+
// 异步立即返回)、--status=轮询+停滞检测+终态回收(artifacts→schema/docHash 校验→落盘既有
|
|
1071
|
+
// stage-reviews 路径)、--kill=清在途记录+平台处置指引(处置权在人)。薄壳:全部逻辑在
|
|
1072
|
+
// src/review-dispatch.js(可测核心),此处只做 flag 解析与退出码。
|
|
1073
|
+
const rdChangeIdx = args.indexOf('--change');
|
|
1074
|
+
const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] ? args[rdChangeIdx + 1] : null;
|
|
1075
|
+
const rdStageIdx = args.indexOf('--stage');
|
|
1076
|
+
const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] ? args[rdStageIdx + 1] : null;
|
|
1077
|
+
const rdStatus = args.includes('--status');
|
|
1078
|
+
const rdKill = args.includes('--kill');
|
|
1079
|
+
const rdMode = rdKill ? 'kill' : rdStatus ? 'status' : 'create';
|
|
1080
|
+
const rdUsage = '用法: sillyspec review-dispatch --change <名> --stage <brainstorm|plan|execute> [--status] [--kill]\n' +
|
|
1081
|
+
' 缺省=派发(probe 前置→create_mission(external)→dispatch_worker(read_only),异步返回 missionId)\n' +
|
|
1082
|
+
' --status=轮询状态+停滞提示;worker completed 自动回收落盘 review.json(reviewer.channel=platform)\n' +
|
|
1083
|
+
' --kill=清本地在途记录+平台处置指引(不自动 kill,处置权在人)';
|
|
1084
|
+
if (!rdChange) { console.error(rdUsage); process.exit(2); }
|
|
1085
|
+
assertSafeChangeName(rdChange, '--change 变更名');
|
|
1086
|
+
if (rdMode === 'create' && !['brainstorm', 'plan', 'execute'].includes(rdStage)) {
|
|
1087
|
+
console.error(`❌ create 形态必须 --stage ∈ brainstorm|plan|execute(实际:${rdStage || '缺'})\n${rdUsage}`);
|
|
1088
|
+
process.exit(2);
|
|
1089
|
+
}
|
|
1090
|
+
const { runReviewDispatch, readReviewDispatchConfig } = await import('./review-dispatch.js');
|
|
1091
|
+
const rdCfg = readReviewDispatchConfig(dir);
|
|
1092
|
+
// MCP client:kill 不需要网络;create/status 需要(未配置时核心层 probe no-config 退出并给降级指引)
|
|
1093
|
+
let rdClient = null;
|
|
1094
|
+
if (rdMode !== 'kill') {
|
|
1095
|
+
const { SillyHubMcpClient } = await import('./sillyhub-mcp/client.js');
|
|
1096
|
+
rdClient = new SillyHubMcpClient({ cwd: dir });
|
|
1097
|
+
}
|
|
1098
|
+
// specBase:与其他顶级命令同口径(平台接管优先;无 pointer 回退本地 .sillyspec)
|
|
1099
|
+
const rdSpecBase = resolvePlatformSpecDir(dir, specDir);
|
|
1100
|
+
const r = await runReviewDispatch({
|
|
1101
|
+
mode: rdMode, cwd: dir, specBase: rdSpecBase, changeName: rdChange, stage: rdStage,
|
|
1102
|
+
client: rdClient, budgetUsd: rdCfg.budget_usd, stallMs: rdCfg.stall_ms,
|
|
1103
|
+
});
|
|
1104
|
+
if (r.ok) {
|
|
1105
|
+
if (r.statusLine) console.log(`🛰️ ${r.statusLine}`);
|
|
1106
|
+
if (r.state === 'completed') {
|
|
1107
|
+
console.log(`✅ 平台审查回收完成:review.json 已落 ${r.reviewPath}(verdict ${r.verdict})——重跑阶段 --done 走 Stage Review Gate`);
|
|
1108
|
+
} else if (rdMode === 'create') {
|
|
1109
|
+
console.log(`✅ 已派发:mission ${r.missionId} / worker ${r.workerId}(reviewRunId ${r.reviewRunId})`);
|
|
1110
|
+
}
|
|
1111
|
+
if (r.stalled) console.warn(`⚠️ ${r.stallHint}`);
|
|
1112
|
+
if (r.nextStep && rdMode !== 'kill') console.log(`👉 ${r.nextStep}`);
|
|
1113
|
+
if (rdMode === 'kill') console.log(`👉 ${r.platformHint}`);
|
|
1114
|
+
} else {
|
|
1115
|
+
console.error(`❌ review-dispatch(${rdMode}): ${r.reason}`);
|
|
1116
|
+
if (r.statusLine) console.error(` ${r.statusLine}`);
|
|
1117
|
+
if (r.errors) for (const e of r.errors) console.error(` - ${e}`);
|
|
1118
|
+
if (r.guidance) console.error(` 下一步:\n${r.guidance}`);
|
|
1119
|
+
process.exitCode = 1;
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
case 'module-docs-sync': {
|
|
1124
|
+
// 模块文档 sidecar 同步命令化(2026-09-09 刀批):diff 归属模块 → sidecar 追加变更索引行 +
|
|
1125
|
+
// 卡 updated_at 戳——机械部分 CLI 代算,卡片正文语义更新仍归 agent/归档流程。幂等。
|
|
1126
|
+
const mdsChangeIdx = args.indexOf('--change');
|
|
1127
|
+
const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] ? args[mdsChangeIdx + 1] : null;
|
|
1128
|
+
const mdsNoteIdx = args.indexOf('--note');
|
|
1129
|
+
const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] ? args[mdsNoteIdx + 1] : null;
|
|
1130
|
+
if (!mdsChange) {
|
|
1131
|
+
console.error('用法: sillyspec module-docs-sync --change <name> [--note "一句话"] [--json]\n diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等;卡片正文语义更新仍归 agent)');
|
|
1132
|
+
process.exit(2);
|
|
946
1133
|
}
|
|
1134
|
+
const { syncModuleDocSidecars } = await import('./module-impact.js');
|
|
1135
|
+
const mdsResult = syncModuleDocSidecars({ cwd: dir, changeName: mdsChange, note: mdsNote, specDir });
|
|
1136
|
+
if (json) {
|
|
1137
|
+
console.log(JSON.stringify({ command: 'module-docs-sync', change: mdsChange, ...mdsResult }, null, 2));
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
if (mdsResult.reason) console.log(mdsResult.reason);
|
|
1141
|
+
if (mdsResult.synced.length > 0) {
|
|
1142
|
+
console.log(String.fromCharCode(9989) + ' sidecar 已同步 ' + mdsResult.synced.length + ' 个模块: ' + mdsResult.synced.join(', '));
|
|
1143
|
+
console.log(' 卡 updated_at 已戳;卡片正文语义更新(如需要)仍归归档流程/agent。');
|
|
1144
|
+
}
|
|
1145
|
+
if (mdsResult.skipped.length > 0) console.log(String.fromCharCode(8505) + ' 已含本变更行跳过: ' + mdsResult.skipped.join(', '));
|
|
1146
|
+
if (mdsResult.unmatched.length > 0) console.log(String.fromCharCode(9888) + ' 未匹配模块的文件 ' + mdsResult.unmatched.length + ' 个(不入 sidecar): ' + mdsResult.unmatched.slice(0, 5).join(', '));
|
|
947
1147
|
break;
|
|
948
1148
|
}
|
|
949
1149
|
case 'plan-adopt-waves': {
|
|
@@ -1023,6 +1223,96 @@ async function main() {
|
|
|
1023
1223
|
console.log(` 归类 ${miResult.matchedCount} 个文件,未匹配 ${miResult.unmatchedCount} 个;影响类型列逐行替换 <!--TODO-->。`);
|
|
1024
1224
|
break;
|
|
1025
1225
|
}
|
|
1226
|
+
case 'scope-audit': {
|
|
1227
|
+
// 变更范围对账命令化(2026-09-10-change-scope-audit task-03,FR-02):Wave 1 纯函数
|
|
1228
|
+
// computeChangeScopeAudit 的 CLI 形态——用户与 agent 不依赖阶段 --done 时点,随时查
|
|
1229
|
+
// 「计划改动 × 实际改动」三态全表(✓ 计划内 / ⚠️ 计划外 / ⚠️ 计划未动;quick 会话出
|
|
1230
|
+
// 归属表)。advisory only(D-006):只读不落盘、不写门禁状态、既有命令行为零变化;
|
|
1231
|
+
// 路由形态对齐 verify-probes(:897)/ module-impact(:1041)先例——--change 必填缺参
|
|
1232
|
+
// 用法错 exit 2、assertSafeChangeName 消毒、动态 import、--json 结构化输出、运行错
|
|
1233
|
+
// fail-soft 出错误摘要 exit 1 不抛栈。
|
|
1234
|
+
const saChangeIdx = args.indexOf('--change');
|
|
1235
|
+
const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] ? args[saChangeIdx + 1] : null;
|
|
1236
|
+
if (!saChange) {
|
|
1237
|
+
console.error('用法: sillyspec scope-audit --change <name> [--file <path>] [--json] [--spec-dir <path>]\n 变更范围对账:计划×实际三态全表 + 行数(quick 会话传 quick-<8hex> 出归属表;已归档变更可查——快照冻结记录态);--file <path> 看单文件变化内容(对账同源锚点的 git diff);advisory 只读展示,不构成门禁');
|
|
1238
|
+
process.exit(2);
|
|
1239
|
+
}
|
|
1240
|
+
assertSafeChangeName(saChange, '--change 变更名');
|
|
1241
|
+
// --file(quick-63776328):单文件 diff 内容查看——锚点与表格行数同源(getFileDiff 内部
|
|
1242
|
+
// 复用 computeChangeScopeAudit 锚 + resolveDiffRoot),用户无需手记基点/worktree 路径。
|
|
1243
|
+
const saFileIdx = args.indexOf('--file');
|
|
1244
|
+
const saFile = saFileIdx >= 0 && args[saFileIdx + 1] && !String(args[saFileIdx + 1]).startsWith('--') ? args[saFileIdx + 1] : null;
|
|
1245
|
+
const { computeChangeScopeAudit, renderScopeAuditTable, getFileDiff } = await import('./scope-audit.js');
|
|
1246
|
+
// specBase 解析对齐 verify-probes 的 worktree 锚定主仓口径(坑 worktree-spec-artifact-misplace):
|
|
1247
|
+
// resolvePlatformSpecDir 仍先调(保留平台接管 fail-closed 检查副作用),但仅 pointer 存在时才把
|
|
1248
|
+
// 返回值当平台根——无 pointer 时它回退 resolveSpecDir(cwd) 会返回 worktree 副本根,误当
|
|
1249
|
+
// platformBase 会让锚定被跳过。无显式 --spec-dir 时 detectWorktreeSpecDrift 锚回主仓
|
|
1250
|
+
// (worktree 副本里查不到主仓 change 目录——dogfood 自测即此形态)。
|
|
1251
|
+
const saResolved = resolvePlatformSpecDir(dir, specDir);
|
|
1252
|
+
const saPlatformBase = existsSync(join(dir, '.sillyspec-platform.json')) ? saResolved : null;
|
|
1253
|
+
let saSpecBase = saPlatformBase || resolveSpecDir(dir, { specDir: specDir || undefined });
|
|
1254
|
+
let saDriftAnchor = null;
|
|
1255
|
+
if (!saPlatformBase && !specDir) {
|
|
1256
|
+
const saWt = detectWorktreeSpecDrift(saSpecBase);
|
|
1257
|
+
if (saWt) {
|
|
1258
|
+
console.warn(`⚠️ 已自动锚定主仓 spec:${saWt.mainSpecBase}(原 cwd 命中 worktree 副本 ${saWt.changeName},scope-audit 对账读主仓,流程继续)`);
|
|
1259
|
+
saSpecBase = saWt.mainSpecBase;
|
|
1260
|
+
saDriftAnchor = saWt.mainSpecBase;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
// full-flow 变更目录存在性预检(endpoints baseline 先例同款):post-apply 形态下实际侧三源
|
|
1264
|
+
// 对「变更不存在」不敏感(status 成功即 ok=true,会把主仓脏区误当实际侧),靠预检给明确
|
|
1265
|
+
// 错误摘要 exit 1;quick 会话 id 无 changes/ 目录(guard 祖先链定位),不预检。
|
|
1266
|
+
// 归档形态兼容(quick-8aa52289):changes/<名> 不在 → 接受 changes/archive/<名>(scope-audit
|
|
1267
|
+
// 内部走快照记录态/HEAD 未提交窗口兜底)。
|
|
1268
|
+
if (!/^quick-[0-9a-f]{8}$/.test(saChange)
|
|
1269
|
+
&& !existsSync(join(saSpecBase, 'changes', saChange))
|
|
1270
|
+
&& !existsSync(join(saSpecBase, 'changes', 'archive', saChange))) {
|
|
1271
|
+
console.error(`❌ 变更目录不存在(活跃与归档区均未找到): ${join(saSpecBase, 'changes', saChange)}(确认 --change 名,或 --spec-dir 指向主仓 .sillyspec)`);
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
// --file 分支:单文件 diff(预检同表模式——锚点依赖变更上下文;json 面出结构化)
|
|
1275
|
+
if (saFile) {
|
|
1276
|
+
let fd;
|
|
1277
|
+
try {
|
|
1278
|
+
fd = await getFileDiff({ cwd: dir, specBase: saSpecBase, changeName: saChange, platformOpts: saDriftAnchor ? { specDriftAnchor: saDriftAnchor } : null, filePath: saFile });
|
|
1279
|
+
} catch (e) {
|
|
1280
|
+
console.error(`❌ scope-audit --file 执行失败: ${e && e.message ? String(e.message).split('\n')[0] : e}`);
|
|
1281
|
+
process.exit(1);
|
|
1282
|
+
}
|
|
1283
|
+
if (json) {
|
|
1284
|
+
console.log(JSON.stringify({ command: 'scope-audit', change: saChange, file: saFile, ...fd }, null, 2));
|
|
1285
|
+
} else if (fd.ok && fd.diff) {
|
|
1286
|
+
console.log(`📄 ${saFile}(${fd.mode}|锚点 ${fd.anchorLabel}|git diff 原生格式)`);
|
|
1287
|
+
console.log(fd.diff);
|
|
1288
|
+
} else {
|
|
1289
|
+
console.log(`ℹ️ ${saFile}(${fd.mode}|锚点 ${fd.anchorLabel || '—'}):${fd.note || '无 diff'}`);
|
|
1290
|
+
}
|
|
1291
|
+
if (!fd.ok) process.exitCode = 1;
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
// 计算包在 withJsonOutput 内(gate/derive 先例同款):quick 模式重跑 auditQuickCompletion
|
|
1295
|
+
// 的既有 console 输出(并发 warn 等,R-07)不污染 --json 的 stdout。
|
|
1296
|
+
let saResult;
|
|
1297
|
+
try {
|
|
1298
|
+
saResult = await withJsonOutput(json, () => computeChangeScopeAudit({ cwd: dir, specBase: saSpecBase, changeName: saChange, platformOpts: saDriftAnchor ? { specDriftAnchor: saDriftAnchor } : null }));
|
|
1299
|
+
} catch (e) {
|
|
1300
|
+
console.error(`❌ scope-audit 计算失败: ${e && e.message ? String(e.message).split('\n')[0] : e}`);
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
if (json) {
|
|
1304
|
+
console.log(JSON.stringify({ command: 'scope-audit', change: saChange, ...saResult }, null, 2));
|
|
1305
|
+
} else {
|
|
1306
|
+
console.log(renderScopeAuditTable(saResult));
|
|
1307
|
+
}
|
|
1308
|
+
// ok=false(quick 会话不存在 / 实际侧整体失败)→ 明确错误摘要 + exit 1(json 面摘要走
|
|
1309
|
+
// stderr,stdout 保持合法 JSON)
|
|
1310
|
+
if (saResult && saResult.ok === false) {
|
|
1311
|
+
console.error(`❌ scope-audit 对账不可用: ${saResult.degradedReason || '未知原因'}`);
|
|
1312
|
+
process.exitCode = 1;
|
|
1313
|
+
}
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1026
1316
|
case 'endpoints': {
|
|
1027
1317
|
// endpoints artifact 生成(2026-08-21 审计第三批 H6):后端 router task 的 endpoints.json
|
|
1028
1318
|
// 此前靠 agent 手扫装饰器手写(易漏 endpoint)。CLI 复用 endpoint-extractor 全套提取器
|
|
@@ -1313,6 +1603,97 @@ async function main() {
|
|
|
1313
1603
|
console.log(' 逐行替换 <!--TODO--> 为结论(无签名级变更也显式写「无」);gate 拒绝未替换的占位行。');
|
|
1314
1604
|
break;
|
|
1315
1605
|
}
|
|
1606
|
+
case 'fourpiece-init': {
|
|
1607
|
+
// 四件套骨架预生成(2026-09-09 ql-20260909-003,轮次经济学 §3.2):brainstorm 期 agent
|
|
1608
|
+
// 手写 proposal/requirements/decisions 的 frontmatter/章节骨架是格式返工根源——本命令
|
|
1609
|
+
// 逐文件生成骨架(已存在不覆盖),agent 只填语义。与 design-init 同族(幂等)。
|
|
1610
|
+
const fpChangeIdx = args.indexOf('--change');
|
|
1611
|
+
const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] ? args[fpChangeIdx + 1] : null;
|
|
1612
|
+
if (!fpChange) {
|
|
1613
|
+
console.error('用法: sillyspec fourpiece-init --change <name> [--json] [--spec-dir <path>]\n 生成 proposal.md/requirements.md/decisions.md 骨架(frontmatter+章节标题+模板占位;已存在不覆盖;design.md 归 design-init)');
|
|
1614
|
+
process.exit(2);
|
|
1615
|
+
}
|
|
1616
|
+
const fpSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
1617
|
+
const fpDir = join(fpSpecBase, 'changes', fpChange);
|
|
1618
|
+
mkdirSync(fpDir, { recursive: true });
|
|
1619
|
+
const fpNow = new Date();
|
|
1620
|
+
const fpDate = fpNow.toISOString().slice(0, 10);
|
|
1621
|
+
const fpStamp = fpNow.toISOString().slice(0, 19).replace('T', ' ');
|
|
1622
|
+
const generated = [];
|
|
1623
|
+
const mk = (fname, body) => {
|
|
1624
|
+
const p = join(fpDir, fname);
|
|
1625
|
+
if (existsSync(p)) { console.log(`ℹ️ ${fname} 已存在,不覆盖`); return; }
|
|
1626
|
+
writeFileSync(p, body);
|
|
1627
|
+
generated.push(fname);
|
|
1628
|
+
console.log(`✅ 已生成 ${fname} 骨架: ${p}`);
|
|
1629
|
+
};
|
|
1630
|
+
mk('proposal.md', `---
|
|
1631
|
+
author: qinyi
|
|
1632
|
+
created_at: ${fpStamp}
|
|
1633
|
+
generated_by: sillyspec-fourpiece-init
|
|
1634
|
+
---
|
|
1635
|
+
# 提案书(Proposal)
|
|
1636
|
+
|
|
1637
|
+
## 动机
|
|
1638
|
+
<!--TODO: 为什么做、解决什么核心问题-->
|
|
1639
|
+
|
|
1640
|
+
## 关键问题
|
|
1641
|
+
<!--TODO: 为什么现有方案不够(2-3 个痛点)-->
|
|
1642
|
+
|
|
1643
|
+
## 变更范围
|
|
1644
|
+
<!--TODO: 本次做什么-->
|
|
1645
|
+
|
|
1646
|
+
## 不在范围内(显式清单)
|
|
1647
|
+
- <!--TODO: 不做 X-->
|
|
1648
|
+
|
|
1649
|
+
## 成功标准(可验证)
|
|
1650
|
+
- <!--TODO: 可验证条目-->
|
|
1651
|
+
`);
|
|
1652
|
+
mk('requirements.md', `---
|
|
1653
|
+
author: qinyi
|
|
1654
|
+
created_at: ${fpStamp}
|
|
1655
|
+
generated_by: sillyspec-fourpiece-init
|
|
1656
|
+
---
|
|
1657
|
+
# 需求规格(Requirements)
|
|
1658
|
+
|
|
1659
|
+
## 角色
|
|
1660
|
+
| 角色 | 说明 |
|
|
1661
|
+
|---|---|
|
|
1662
|
+
| <!--TODO--> | <!--TODO--> |
|
|
1663
|
+
|
|
1664
|
+
## 功能需求
|
|
1665
|
+
|
|
1666
|
+
### FR-01: <!--TODO-->
|
|
1667
|
+
Given <!--TODO-->
|
|
1668
|
+
When <!--TODO-->
|
|
1669
|
+
Then <!--TODO-->
|
|
1670
|
+
|
|
1671
|
+
## 非功能需求
|
|
1672
|
+
- 兼容性:<!--TODO-->
|
|
1673
|
+
|
|
1674
|
+
## 决策覆盖矩阵(如存在 decisions.md)
|
|
1675
|
+
| 决策 ID | 覆盖的 FR | 说明 |
|
|
1676
|
+
|---|---|---|
|
|
1677
|
+
`);
|
|
1678
|
+
mk('decisions.md', `---
|
|
1679
|
+
author: qinyi
|
|
1680
|
+
created_at: ${fpStamp}
|
|
1681
|
+
generated_by: sillyspec-fourpiece-init
|
|
1682
|
+
change: ${fpChange}
|
|
1683
|
+
---
|
|
1684
|
+
|
|
1685
|
+
# 决策记录(Decisions)
|
|
1686
|
+
|
|
1687
|
+
<!-- 增量落盘:每解决一个有实现影响的问题当场追加一条(格式见 brainstorm Step 3 模板);幂等按 D-xxx@vN 判重 -->
|
|
1688
|
+
`);
|
|
1689
|
+
if (json) {
|
|
1690
|
+
console.log(JSON.stringify({ command: 'fourpiece-init', change: fpChange, generated }, null, 2));
|
|
1691
|
+
} else if (generated.length > 0) {
|
|
1692
|
+
console.log(`
|
|
1693
|
+
${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语义内容(骨架勿手删章节);design.md 用 sillyspec design-init。`);
|
|
1694
|
+
}
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1316
1697
|
case 'design-init': {
|
|
1317
1698
|
// design.md 十三章节骨架生成(change: 2026-09-07-ir-stage-p3c,task-02,FR-03):agent 此前
|
|
1318
1699
|
// 从零手写整份设计文档,章节漏项到 stage-contract/plan 门禁才暴露。本命令从 decisions.md
|
|
@@ -1358,6 +1739,17 @@ async function main() {
|
|
|
1358
1739
|
break;
|
|
1359
1740
|
}
|
|
1360
1741
|
writeFileSync(diDesignPath, diSkeleton);
|
|
1742
|
+
// 平台模式双写镜像(坑 platform-init-artifact-daemon-dir-only,2026-09-10 驾驭小结第三批②):
|
|
1743
|
+
// agent 本地(pointer 态、非显式 --spec-dir)跑 design-init 时 design.md 同时镜像主仓
|
|
1744
|
+
// changeDir——不再单点依赖 spec-sync 回程(两次实锤产物落 daemon specs 同步目录)。
|
|
1745
|
+
try {
|
|
1746
|
+
const { mirrorPlatformArtifactToMainRepo } = await import('./run/shared.js');
|
|
1747
|
+
mirrorPlatformArtifactToMainRepo({
|
|
1748
|
+
cwd: dir, changeName: diChange, file: 'design.md', content: diSkeleton,
|
|
1749
|
+
platformBase: existsSync(join(dir, '.sillyspec-platform.json')) ? diSpecBase : null,
|
|
1750
|
+
specDirExplicit: Boolean(specDir),
|
|
1751
|
+
});
|
|
1752
|
+
} catch { /* 镜像失败不阻断主写入 */ }
|
|
1361
1753
|
if (json) {
|
|
1362
1754
|
console.log(JSON.stringify({ command: 'design-init', change: diChange, ok: true, path: diDesignPath, written: true, overwritten: diExisted }, null, 2));
|
|
1363
1755
|
break;
|
|
@@ -1658,23 +2050,36 @@ async function main() {
|
|
|
1658
2050
|
// 只读 stdout 可捕获);stderr 仅留 ⚠️ 诊断(console.warn)与配置错误(exit 2 路径)。
|
|
1659
2051
|
// 文本逐字不变,只改通道。
|
|
1660
2052
|
console.log(`\n❌ docs check: ${result.invalid.length}/${result.total} 处引用失效:`);
|
|
2053
|
+
// 显示去重(2026-09-10 驾驭小结第二批③):同 doc+docLine+ref 的重复 invalid(同一
|
|
2054
|
+
// 引用在行内多次出现时逐条收集)只显示一次、带 ×N 次数——invalid 集与 exit code
|
|
2055
|
+
// 不动,纯渲染降噪。
|
|
2056
|
+
const shownInvalid = [];
|
|
2057
|
+
const dupCountByIdentity = new Map();
|
|
2058
|
+
for (const inv of result.invalid) {
|
|
2059
|
+
const key = `${inv.doc}#${inv.docLine}#${inv.ref}`;
|
|
2060
|
+
dupCountByIdentity.set(key, (dupCountByIdentity.get(key) || 0) + 1);
|
|
2061
|
+
if (dupCountByIdentity.get(key) === 1) shownInvalid.push(inv);
|
|
2062
|
+
}
|
|
2063
|
+
const dupTotal = result.invalid.length - shownInvalid.length;
|
|
1661
2064
|
let appliedLeft = fixResult ? fixResult.applied : 0;
|
|
1662
2065
|
const skippedQueue = fixResult ? [...fixResult.skipped] : [];
|
|
1663
|
-
for (const inv of
|
|
2066
|
+
for (const inv of shownInvalid) {
|
|
2067
|
+
const dupN = dupCountByIdentity.get(`${inv.doc}#${inv.docLine}#${inv.ref}`);
|
|
2068
|
+
const dupTag = dupN > 1 ? `(同引用重复 ×${dupN})` : '';
|
|
1664
2069
|
const newRef = newRefByInv.get(inv);
|
|
1665
2070
|
if (newRef !== undefined && appliedLeft > 0) {
|
|
1666
2071
|
// 已应用(dry-run 为将应用):归因按构造顺序消耗 applied 计数——写回 skipped 仅
|
|
1667
2072
|
// 防御路径(文档消失/行内失配)触发,CLI 正常流 fixable 必然落位
|
|
1668
|
-
console.log(` ✅ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${newRef}${dryRun ? '(dry-run 未写盘)' : ''}`);
|
|
2073
|
+
console.log(` ✅ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${newRef}${dryRun ? '(dry-run 未写盘)' : ''}${dupTag}`);
|
|
1669
2074
|
appliedLeft--;
|
|
1670
2075
|
} else if (newRef !== undefined) {
|
|
1671
2076
|
const s = skippedQueue.shift();
|
|
1672
|
-
console.log(` ⚠️ [${inv.doc}:L${inv.docLine}] ${inv.ref} → 写回跳过:${s ? s.reason : '未知原因'}`);
|
|
2077
|
+
console.log(` ⚠️ [${inv.doc}:L${inv.docLine}] ${inv.ref} → 写回跳过:${s ? s.reason : '未知原因'}${dupTag}`);
|
|
1673
2078
|
} else if (fixResult) {
|
|
1674
2079
|
// needs-manual:多命中歧义/零命中/无 token——fix.reason 含候选行号,交人工
|
|
1675
|
-
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${(inv.fix && inv.fix.reason) || inv.reason}
|
|
2080
|
+
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${(inv.fix && inv.fix.reason) || inv.reason}(待人工)${dupTag}`);
|
|
1676
2081
|
} else {
|
|
1677
|
-
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${inv.reason}`);
|
|
2082
|
+
console.log(` ❌ [${inv.doc}:L${inv.docLine}] ${inv.ref} → ${inv.reason}${dupTag}`);
|
|
1678
2083
|
}
|
|
1679
2084
|
if (inv.suggest && inv.suggest.length > 0) {
|
|
1680
2085
|
// 2026-09-07-ir-hardening task-08:原 --suggest 旗标删除(no-op 首选路径退役),
|
|
@@ -1682,6 +2087,9 @@ async function main() {
|
|
|
1682
2087
|
console.log(` 💡 候选行号: ${inv.suggest.join(', ')}(token 命中行,人工确认后更新文档锚)`);
|
|
1683
2088
|
}
|
|
1684
2089
|
}
|
|
2090
|
+
if (dupTotal > 0) {
|
|
2091
|
+
console.log(` ℹ️ 已折叠 ${dupTotal} 条同文档行同引用的重复条目(计数 ×N 已并入上文)。`);
|
|
2092
|
+
}
|
|
1685
2093
|
if (fixResult) {
|
|
1686
2094
|
console.log(`\n🔧 重锚报告:${fixResult.applied} 处已${dryRun ? '预览' : '改写'}${dryRun ? '(dry-run 未写盘)' : ''}、${result.invalid.length - fixes.length} 处待人工${fixResult.skipped.length > 0 ? `、${fixResult.skipped.length} 处写回跳过` : ''}。`);
|
|
1687
2095
|
if (fixReceipt) {
|
|
@@ -1694,9 +2102,21 @@ async function main() {
|
|
|
1694
2102
|
// 变更名提名 advisory 段(主结果之后、exit 之前;不参与 ok/invalid 与 exit code)。
|
|
1695
2103
|
// 零输出原则(docs-check-fix 契约:无新 flag 时 stdout 逐字节一致;决策规则族同款
|
|
1696
2104
|
// 「无信号零输出」):仅 findings 非空才输出——悬空即信号,无悬空不打扰。
|
|
2105
|
+
// 输出降噪(2026-09-10 驾驭小结第二批③):①advisory 固定尾部分区(硬失效区块与
|
|
2106
|
+
// 修复指引之后),标题明示「不阻断、不进 exit code」,与硬失效 ❌ 视觉分离;②同一
|
|
2107
|
+
// 悬空名多处提名按名聚合一行(名 + 提及处数 + 首处定位),不再逐处刷屏。
|
|
1697
2108
|
if (changeNameReport && changeNameReport.findings.length > 0) {
|
|
1698
|
-
console.
|
|
1699
|
-
|
|
2109
|
+
console.log(`\n⚠️ ── 变更名提名悬空(advisory:不阻断、不进 exit code / docs gate)──`);
|
|
2110
|
+
const byName = new Map(); // name -> { count, first }
|
|
2111
|
+
for (const f of changeNameReport.findings) {
|
|
2112
|
+
const e = byName.get(f.name) || { count: 0, first: f };
|
|
2113
|
+
e.count++;
|
|
2114
|
+
byName.set(f.name, e);
|
|
2115
|
+
}
|
|
2116
|
+
for (const [name, e] of byName) {
|
|
2117
|
+
console.log(` - 「${name}」${e.count} 处提及悬空(活跃∪归档变更名单中不存在;首处 ${e.first.doc}:L${e.first.docLine})——拼错 / 改名未同步 / 他仓变更?修正后复跑,或 local.yaml known_failures 加 "change-name.${name}" 豁免`);
|
|
2118
|
+
}
|
|
2119
|
+
console.log(` (共 ${changeNameReport.findings.length} 处 / ${changeNameReport.uniqueNames} 个名字,全文提及 ${changeNameReport.mentions} 处)`);
|
|
1700
2120
|
if (changeNameReport.exempted.length > 0) {
|
|
1701
2121
|
console.log(`ℹ️ 另有 ${changeNameReport.exempted.length} 处经 known_failures change-name.* 键豁免`);
|
|
1702
2122
|
}
|
|
@@ -1810,9 +2230,10 @@ async function main() {
|
|
|
1810
2230
|
// slice(1) 去掉的是 'run' 字面量,这里 command 本身就是 stage 名不能丢。
|
|
1811
2231
|
case 'doctor': {
|
|
1812
2232
|
const doctorEffectiveDir = specDir ? dir : resolveEffectiveDir(dir);
|
|
1813
|
-
// 执行流:--cleanup-remnant / --cleanup-ghosts / --dump-db(结构化诊断之外的修复/取证动作)
|
|
2233
|
+
// 执行流:--cleanup-remnant / --cleanup-ghosts / --gc-unstamped-runs / --dump-db(结构化诊断之外的修复/取证动作)
|
|
1814
2234
|
const cleanupRemnant = filteredArgs.includes('--cleanup-remnant');
|
|
1815
2235
|
const cleanupGhosts = filteredArgs.includes('--cleanup-ghosts');
|
|
2236
|
+
const gcUnstampedRuns = filteredArgs.includes('--gc-unstamped-runs');
|
|
1816
2237
|
const dumpDbFlag = filteredArgs.includes('--dump-db');
|
|
1817
2238
|
const doctorConfirm = filteredArgs.includes('--confirm');
|
|
1818
2239
|
const pathIdx = filteredArgs.indexOf('--path');
|
|
@@ -1918,6 +2339,29 @@ async function main() {
|
|
|
1918
2339
|
process.exitCode = r.errors.length > 0 ? 1 : 0;
|
|
1919
2340
|
break;
|
|
1920
2341
|
}
|
|
2342
|
+
if (gcUnstampedRuns) {
|
|
2343
|
+
// ql-20260908-006-5f04:无戳 execute-runs 一次性清扫。默认 dry-run,--confirm 才删。
|
|
2344
|
+
// 不进 archive 热路径(pruneArchivedChangeRuntime 故意不猜无戳 run)。
|
|
2345
|
+
const { gcUnstampedExecuteRuns } = await import('./doctor-diagnostics.js');
|
|
2346
|
+
const r = await gcUnstampedExecuteRuns({ cwd: doctorEffectiveDir, specDir, confirm: doctorConfirm });
|
|
2347
|
+
if (json) {
|
|
2348
|
+
console.log(JSON.stringify(r, null, 2));
|
|
2349
|
+
} else {
|
|
2350
|
+
const verb = doctorConfirm ? '已删除' : '待删除(dry-run)';
|
|
2351
|
+
console.log(`🧹 无戳 execute-runs(仅已归档变更、交叉核验通过)${verb}:${r.count} 个 [runtime: ${r.runtime_root || '(无)'}]`);
|
|
2352
|
+
for (const c of r.candidates || []) {
|
|
2353
|
+
console.log(` ${doctorConfirm ? '✅' : '-'} ${c.runId} ← ${c.owner}(via ${c.via})`);
|
|
2354
|
+
}
|
|
2355
|
+
for (const s of r.skipped || []) console.log(` ⏭️ ${s.runId}(${s.reason})`);
|
|
2356
|
+
for (const e of r.errors || []) console.log(` ❌ ${e.runId || ''}: ${e.error}`);
|
|
2357
|
+
if (!doctorConfirm && r.count > 0) {
|
|
2358
|
+
console.log(`\n加 --confirm 执行删除(只删上面列出的无戳 run,有 change 戳 / 命中活跃变更 / 归属不明一律不动)。`);
|
|
2359
|
+
}
|
|
2360
|
+
if (r.reason) console.log(`ℹ️ ${r.reason}`);
|
|
2361
|
+
}
|
|
2362
|
+
process.exitCode = (r.errors && r.errors.length > 0) ? 1 : 0;
|
|
2363
|
+
break;
|
|
2364
|
+
}
|
|
1921
2365
|
if (dumpDbFlag) {
|
|
1922
2366
|
if (!dbPath) { console.error('❌ --dump-db 需要 --path <db 路径>'); process.exitCode = 2; break; }
|
|
1923
2367
|
const { dumpDb } = await import('./doctor-diagnostics.js');
|
|
@@ -1944,9 +2388,21 @@ async function main() {
|
|
|
1944
2388
|
process.exitCode = output.overall_status === 'pass' ? 0 : 1;
|
|
1945
2389
|
break;
|
|
1946
2390
|
}
|
|
1947
|
-
//
|
|
1948
|
-
|
|
1949
|
-
|
|
2391
|
+
// 2026-09-09-doctor-noai:--status 拦截(审查 B-03:等价只读渲染——阶段进度视图,
|
|
2392
|
+
// 零副作用);非 --json 其余形态改道直跑诊断 + renderDoctorSummary(不走
|
|
2393
|
+
// runCommand/initChange/不刷 lastActive——只读零副作用承诺保真)。
|
|
2394
|
+
if (filteredArgs.includes('--status')) {
|
|
2395
|
+
// 等价只读渲染:复用 progress show(cwd 无活跃变更时提示无——与原只读短路视图同语义级)
|
|
2396
|
+
const pm = new ProgressManager({ specDir: resolvePlatformSpecDir(dir, specDir) });
|
|
2397
|
+
pm.show(doctorEffectiveDir);
|
|
2398
|
+
break;
|
|
2399
|
+
}
|
|
2400
|
+
{
|
|
2401
|
+
const { runDoctorDiagnostics, renderDoctorSummary, writeDoctorDiagnosis } = await import('./doctor-diagnostics.js')
|
|
2402
|
+
const diag = await runDoctorDiagnostics({ cwd: doctorEffectiveDir })
|
|
2403
|
+
console.log(renderDoctorSummary(diag))
|
|
2404
|
+
try { writeDoctorDiagnosis(diag, resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec')) } catch { /* fail-soft */ }
|
|
2405
|
+
}
|
|
1950
2406
|
break;
|
|
1951
2407
|
}
|
|
1952
2408
|
case 'scan': {
|
|
@@ -2852,6 +3308,108 @@ SillySpec platform — SillyHub 平台同步
|
|
|
2852
3308
|
}
|
|
2853
3309
|
break;
|
|
2854
3310
|
}
|
|
3311
|
+
case 'mcp': {
|
|
3312
|
+
// MCP Phase 2(P2-g,noai-ir-roadmap §5):最小 stdio MCP server——只读 tools
|
|
3313
|
+
//(next/gate/derive/progress)经子进程 --json 隔离执行,schema 化参数锁死 flag 幻觉面。
|
|
3314
|
+
// hosts 配 command=sillyspec args=[mcp]。Phase 3(--done 状态推进走 MCP)另计。
|
|
3315
|
+
const { startMcpServer } = await import('./mcp-server.js');
|
|
3316
|
+
await startMcpServer();
|
|
3317
|
+
break;
|
|
3318
|
+
}
|
|
3319
|
+
case 'validate': {
|
|
3320
|
+
// 产物 schema 校验总命令(P2-d,noai-ir-roadmap §5):聚合既有校验器(verify-facts /
|
|
3321
|
+
// task review / stage review / module-map)+ 轻形状检查(required-evidence / endpoints)
|
|
3322
|
+
// 为一把伞——「新解析器写之前先过 schema」的统一落点。skip = 未生成不算失败。
|
|
3323
|
+
const vaChangeIdx = args.indexOf('--change');
|
|
3324
|
+
const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] ? args[vaChangeIdx + 1] : null;
|
|
3325
|
+
if (!vaChange) {
|
|
3326
|
+
console.error('用法: sillyspec validate --change <名> [--json]');
|
|
3327
|
+
console.error(' 校验该变更的全部结构化产物(verify-facts/required-evidence/task reviews/stage reviews/endpoints + module-map)');
|
|
3328
|
+
process.exit(2);
|
|
3329
|
+
}
|
|
3330
|
+
assertSafeChangeName(vaChange, '--change 变更名');
|
|
3331
|
+
const { validateChangeArtifacts, renderValidateReport } = await import('./validate-artifacts.js');
|
|
3332
|
+
const vaSpecBase = resolveSpecDir(dir, specDir && typeof specDir === 'string' ? { specDir } : undefined);
|
|
3333
|
+
const vaResult = await validateChangeArtifacts({ cwd: dir, specBase: vaSpecBase, changeName: vaChange });
|
|
3334
|
+
if (json) {
|
|
3335
|
+
console.log(JSON.stringify({ command: 'validate', change: vaChange, ok: vaResult.ok, checks: vaResult.checks }, null, 2));
|
|
3336
|
+
} else {
|
|
3337
|
+
console.log(renderValidateReport(vaResult));
|
|
3338
|
+
}
|
|
3339
|
+
if (!vaResult.ok) process.exit(1);
|
|
3340
|
+
break;
|
|
3341
|
+
}
|
|
3342
|
+
case 'decisions': {
|
|
3343
|
+
// P1-5(noai-ir-roadmap §4):decisions.md 单一写入者——canonical 格式只此一家(标题式
|
|
3344
|
+
// 条目 + applyField 白名单中文标签),agent 不再手拼自由 markdown(扁平列表静默 0 条 /
|
|
3345
|
+
// 字段拼写漂移事故族退役;读取侧 parseDecisions 零改动双向兼容)。
|
|
3346
|
+
// decisions add --change <c> --id D-001 [--version N] --title "…" [--status confirmed]
|
|
3347
|
+
// [--type architecture] [--domain a(可重复)] [--question/--answer/--anchor]
|
|
3348
|
+
// [--reject-reason/--revisit-when/--supersedes/--impacts] [--note "散文行(可重复)"]
|
|
3349
|
+
// 同 id@v 整块替换(幂等);新 id/version 追加。
|
|
3350
|
+
// decisions list --change <c>——parseDecisions 只读视图(id/状态/类型/模块域/标题)。
|
|
3351
|
+
const decSub = filteredArgs[1];
|
|
3352
|
+
const decChangeIdx = args.indexOf('--change');
|
|
3353
|
+
const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] ? args[decChangeIdx + 1] : null;
|
|
3354
|
+
if (!decChange || !['add', 'list'].includes(decSub)) {
|
|
3355
|
+
console.error('用法: sillyspec decisions add --change <名> --id D-001 --title "…" [--version N] [--status confirmed] [--type architecture] [--domain x(可重复)] [--question/--answer/--anchor/--reject-reason/--revisit-when/--supersedes/--impacts/--note]\n sillyspec decisions list --change <名>');
|
|
3356
|
+
process.exit(2);
|
|
3357
|
+
}
|
|
3358
|
+
assertSafeChangeName(decChange, '--change 变更名');
|
|
3359
|
+
const decChangeDir = join(resolveSpecDir(dir), 'changes', decChange);
|
|
3360
|
+
if (decSub === 'list') {
|
|
3361
|
+
const { parseDecisions } = await import('./decision-distill.js');
|
|
3362
|
+
const parsed = parseDecisions(decChangeDir);
|
|
3363
|
+
if (parsed.missing) { console.error(`❌ decisions.md 不存在: ${join(decChangeDir, 'decisions.md')}`); process.exit(1); }
|
|
3364
|
+
for (const e of parsed.entries) {
|
|
3365
|
+
console.log(`${e.id} [${e.status || '?'}${e.type ? '/' + e.type : ''}]${(e.domains || []).length ? `[${e.domains.join(',')}]` : ''} ${e.title || '(无标题)'}`);
|
|
3366
|
+
}
|
|
3367
|
+
console.log(`共 ${parsed.entries.length} 条`);
|
|
3368
|
+
break;
|
|
3369
|
+
}
|
|
3370
|
+
// add:flag 收集(多值 --domain/--note 重复收集)
|
|
3371
|
+
const decFlag = (name) => { const i = args.indexOf(name); return i >= 0 && args[i + 1] && !String(args[i + 1]).startsWith('--') ? args[i + 1] : null; };
|
|
3372
|
+
const decMulti = (name) => { const out = []; for (let i = 0; i < args.length; i++) if (args[i] === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) out.push(args[i + 1]); return out; };
|
|
3373
|
+
const decId = decFlag('--id');
|
|
3374
|
+
const decTitle = decFlag('--title');
|
|
3375
|
+
if (!decId || !decTitle) { console.error('❌ decisions add 需要 --id D-xxx 与 --title'); process.exit(2); }
|
|
3376
|
+
if (!/^D-\d+$/.test(decId)) { console.error(`❌ --id 形态非法(期望 D-xxx,实得 ${decId})`); process.exit(2); }
|
|
3377
|
+
const decVersionRaw = decFlag('--version');
|
|
3378
|
+
const decVersion = decVersionRaw === null ? null : parseInt(decVersionRaw, 10);
|
|
3379
|
+
const { parseDecisions } = await import('./decision-distill.js');
|
|
3380
|
+
const decParsed = parseDecisions(decChangeDir);
|
|
3381
|
+
let version = decVersion;
|
|
3382
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
3383
|
+
const maxV = decParsed.entries.filter(e => e.number === decId).reduce((m, e) => Math.max(m, e.version), 0);
|
|
3384
|
+
version = maxV + 1;
|
|
3385
|
+
}
|
|
3386
|
+
const decision = {
|
|
3387
|
+
number: decId,
|
|
3388
|
+
version,
|
|
3389
|
+
title: decTitle,
|
|
3390
|
+
status: decFlag('--status') || undefined,
|
|
3391
|
+
type: decFlag('--type') || undefined,
|
|
3392
|
+
domains: decMulti('--domain'),
|
|
3393
|
+
question: decFlag('--question') || undefined,
|
|
3394
|
+
answer: decFlag('--answer') || undefined,
|
|
3395
|
+
anchor: decFlag('--anchor') || undefined,
|
|
3396
|
+
rejectReason: decFlag('--reject-reason') || undefined,
|
|
3397
|
+
revisitWhen: decFlag('--revisit-when') || undefined,
|
|
3398
|
+
supersedes: decFlag('--supersedes') || undefined,
|
|
3399
|
+
impacts: decFlag('--impacts') || undefined,
|
|
3400
|
+
body: decMulti('--note'),
|
|
3401
|
+
};
|
|
3402
|
+
const { upsertDecision } = await import('./decisions-io.js');
|
|
3403
|
+
try {
|
|
3404
|
+
const r = upsertDecision(decChangeDir, decision);
|
|
3405
|
+
console.log(`📝 决策 ${decId}@v${version} 已${r.action === 'replaced' ? '整块替换(幂等)' : '追加'}: ${r.path}`);
|
|
3406
|
+
if (!decVersionRaw) console.log(` 版本缺省 → 既有最高 v${version - 1} + 1(显式版本用 --version)`);
|
|
3407
|
+
} catch (e) {
|
|
3408
|
+
console.error(`❌ 决策写入被拒: ${e.message}`);
|
|
3409
|
+
process.exit(1);
|
|
3410
|
+
}
|
|
3411
|
+
break;
|
|
3412
|
+
}
|
|
2855
3413
|
case 'taskcard': {
|
|
2856
3414
|
const tcName = filteredArgs.slice(1).find(a => !a.startsWith('-'));
|
|
2857
3415
|
if (!tcName || tcName === 'help' || tcName === '--help' || tcName === '-h') {
|
|
@@ -2920,7 +3478,33 @@ checkbox 行;depends_on 自动反填行内注解 "(depends_on: task-01,02)";
|
|
|
2920
3478
|
|
|
2921
3479
|
const { cmdTaskcard } = await import('./taskcard.js');
|
|
2922
3480
|
try {
|
|
2923
|
-
|
|
3481
|
+
// 坑 platform-sync-progress-rollback-and-db-corruption 坑4:taskcard 此前只走
|
|
3482
|
+
// resolveSpecDir(祖先遍历本地 .sillyspec),不感知平台接管指针——平台模式下
|
|
3483
|
+
// 仓库根裸跑报「变更目录不存在」,需显式 --spec-dir(与 run/plan 系列不一致)。
|
|
3484
|
+
// 对齐 endpoints 等命令:先 resolvePlatformSpecDir(指针 fail-closed 语义同源)。
|
|
3485
|
+
const tcPlatformSpecDir = resolvePlatformSpecDir(dir, specDir) || specDir;
|
|
3486
|
+
const result = cmdTaskcard(tcName, { cwd: dir, specDir: tcPlatformSpecDir, taskIds, title: titleVal, titleZh: titleZhVal, force, sets });
|
|
3487
|
+
// 平台模式产物双写镜像(坑 platform-init-artifact-daemon-dir-only 同族,2026-09-10 驾驭
|
|
3488
|
+
// 小结第六批①,用户实证 taskcard --all 落 daemon 镜像「中间一度两份副本」):agent 本地
|
|
3489
|
+
// (pointer 态、非显式 --spec-dir)生成的卡片即时镜像主仓 changeDir——不再等 spec-sync
|
|
3490
|
+
// 回程。镜像失败不阻断(镜像根照写,spec-sync 仍是兜底)。
|
|
3491
|
+
if (result.created.length > 0 && existsSync(join(dir, '.sillyspec-platform.json')) && !specDir) {
|
|
3492
|
+
try {
|
|
3493
|
+
const { mirrorPlatformArtifactToMainRepo } = await import('./run/shared.js');
|
|
3494
|
+
const changesAbs = join(tcPlatformSpecDir, 'changes', tcName);
|
|
3495
|
+
for (const created of result.created) {
|
|
3496
|
+
const rel = relative(changesAbs, created).split('\\').join('/');
|
|
3497
|
+
if (!rel || rel.startsWith('..')) continue;
|
|
3498
|
+
try {
|
|
3499
|
+
mirrorPlatformArtifactToMainRepo({
|
|
3500
|
+
cwd: dir, changeName: tcName, file: rel,
|
|
3501
|
+
content: readFileSync(created, 'utf8'),
|
|
3502
|
+
platformBase: tcPlatformSpecDir, ensureParentDir: true,
|
|
3503
|
+
});
|
|
3504
|
+
} catch { /* 单卡镜像失败不连坐 */ }
|
|
3505
|
+
}
|
|
3506
|
+
} catch { /* 镜像链路异常不阻断主命令 */ }
|
|
3507
|
+
}
|
|
2924
3508
|
for (const f of result.created) console.log(`✅ 已生成: ${f}`);
|
|
2925
3509
|
for (const f of result.skipped) console.log(`⏭️ 已存在,跳过(--force 覆盖): ${f}`);
|
|
2926
3510
|
if (result.created.length > 0) {
|