sillyspec 3.28.0 → 3.28.2

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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/archive-delta.js +419 -0
  3. package/src/design-facts.js +354 -0
  4. package/src/docs-check.js +79 -8
  5. package/src/doctor-diagnostics.js +11 -2
  6. package/src/endpoint-baseline.js +109 -0
  7. package/src/git-helper.js +31 -0
  8. package/src/index.js +213 -4
  9. package/src/modules.js +41 -12
  10. package/src/progress/change-registry.js +20 -0
  11. package/src/progress/consistency-doctor.js +124 -5
  12. package/src/progress/stage-machine.js +13 -2
  13. package/src/progress.js +3 -0
  14. package/src/quicklog.js +51 -0
  15. package/src/run/command.js +70 -1
  16. package/src/run/complete-handlers.js +53 -4
  17. package/src/run/complete.js +46 -1
  18. package/src/run/gates.js +333 -2
  19. package/src/run/prompt.js +70 -0
  20. package/src/run/scan-profile.js +98 -6
  21. package/src/run/shared.js +12 -1
  22. package/src/run/stage.js +7 -1
  23. package/src/scan-diff.js +54 -1
  24. package/src/scan-facts.js +173 -0
  25. package/src/scan-postcheck.js +110 -3
  26. package/src/spec-sync.js +19 -2
  27. package/src/stage-review.js +62 -3
  28. package/src/stages/archive.js +5 -5
  29. package/src/stages/brainstorm.js +10 -10
  30. package/src/stages/execute.js +15 -13
  31. package/src/stages/plan-postcheck.js +194 -1
  32. package/src/stages/plan.js +6 -2
  33. package/src/stages/scan.js +44 -102
  34. package/src/stages/verify.js +9 -7
  35. package/src/taskcard.js +8 -1
  36. package/src/verify-postcheck.js +610 -2
  37. package/src/verify-probes.js +89 -10
  38. package/src/workflow.js +30 -0
  39. package/templates/agents-instruction.md +1 -1
  40. package/templates/prompts/taskcard-rules.md +5 -0
  41. package/templates/workflows/scan-docs.yaml +6 -0
@@ -220,13 +220,14 @@ const fixedPrefix = [
220
220
  {
221
221
  name: '进度确认',
222
222
  migratedFrom: ['状态检查'],
223
- prompt: `检查当前进度,确认可以执行 execute。用 \`sillyspec progress show\` 查流程进度,不要用 \`sillyspec status\`(项目级快照,不推进流程)。
223
+ prompt: `检查当前进度,确认可以执行 execute(快照已由 CLI 注入,勿再跑 progress show;\`sillyspec status\` 是项目级快照,也不推进流程)。
224
+
225
+ ### 进度快照(CLI 注入)
226
+ {PROGRESS_SNAPSHOT}
224
227
 
225
228
  ### 操作
226
- 1. 运行 \`sillyspec progress show\`
227
- 2. 确认 currentStage 为 execute
228
- 3. 如果不是 → 检查是否有未完成的 tasks.md
229
- 4. 确认执行范围($ARGUMENTS 指定 wave/task 或全部)
229
+ 1. 如果快照显示不是 execute 阶段 → 检查是否有未完成的 tasks.md
230
+ 2. 确认执行范围($ARGUMENTS 指定 wave/task 或全部)
230
231
 
231
232
  ### 输出
232
233
  当前状态 + 执行范围确认`,
@@ -284,18 +285,19 @@ const fixedPrefix = [
284
285
  },
285
286
  {
286
287
  name: '确认 worktree 路径',
287
- prompt: `确认当前 worktree 状态,提取隔离路径。
288
+ prompt: `确认当前 worktree 状态(meta 已由 CLI 注入,勿再跑 worktree meta)。
289
+
290
+ ### worktree 快照(CLI 注入)
291
+ {WORKTREE_META}
288
292
 
289
293
  ### 操作
290
- 1. 运行 \`sillyspec worktree meta <change-name>\` 读取 meta.json
291
- 2. 从输出中提取 worktreePath、branch、mode 字段
292
- 3. 确认 worktree 目录存在(如果是 worktree/native-worktree 模式)
293
- 4. **确认工具链可用**:worktree 内项目工具链(lint/format/test 二进制,如 ruff / prettier / uv)可能不全——对本次会用到的工具先跑一次 \`--version\` 确认;缺失则按项目方式安装(Python 项目 \`uv tool install ruff\` / \`uv sync\`,Node 项目 node_modules 已由 CLI 链接主仓)。不要等到 commit 才发现二进制不在 PATH 被 hook 拦。**Python 项目注意**:worktree 自建 .venv 只含 pyproject 声明依赖,pytest 等 dev 工具可能缺失——优先在 worktree 内补装(\`uv sync --group dev\` / \`uv pip install pytest\`),**不要回退用主仓 venv 跑测试**(主仓 venv 加载的可能是主仓代码而非 worktree 代码,环境不一致会掩真 bug)
294
+ 1. **确认工具链可用**:worktree 内项目工具链(lint/format/test 二进制,如 ruff / prettier / uv)可能不全——对本次会用到的工具先跑一次 \`--version\` 确认;缺失则按项目方式安装(Python 项目 \`uv tool install ruff\` / \`uv sync\`,Node 项目 node_modules 已由 CLI 链接主仓)。不要等到 commit 才发现二进制不在 PATH 被 hook 拦。**Python 项目注意**:worktree 自建 .venv 只含 pyproject 声明依赖,pytest 等 dev 工具可能缺失——优先在 worktree 内补装(\`uv sync --group dev\` / \`uv pip install pytest\`),**不要回退用主仓 venv 跑测试**(主仓 venv 加载的可能是主仓代码而非 worktree 代码,环境不一致会掩真 bug)
295
+ 2. **涉及后端端点的变更拍基线**:跑 \`sillyspec endpoints baseline --change <change-name>\` 拍变更前基线(幂等,已拍过会跳过;在 worktree 内跑即可——CLI 自动把扫描根与落点锚定主仓)——归档时用于计算端点增删
294
296
 
295
297
  ### 铁律
296
298
  - **worktree 已由 CLI 在 execute 阶段启动时自动创建,不要自行创建或跳过**
297
- - **后续所有子代理的 cwd 必须设为该 worktree 路径**
298
- - 如果 meta.json 不存在(说明创建失败),停止并报错
299
+ - **后续所有子代理的 cwd 必须设为快照中的 worktreePath**
300
+ - 快照显示 meta 不可读(说明创建失败)→ 停止并报错
299
301
  - **不要自行检查 git dirty/uncommitted 状态来判断是否可以进入 worktree,CLI 已自动处理**
300
302
 
301
303
  ### 输出
@@ -354,7 +356,7 @@ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
354
356
  3. 组装行为(全量测试/构建/启动通过——单 task 测试全绿 ≠ 组装正确)
355
357
 
356
358
  **产物唯一化(省重复消耗)**:本步逐项对照结论**只落盘一份**——直接写进 review.json 的 \`checklist\` 数组(item=设计要点/FR/决策,note=实现状态 ✅/⚠️/❌ + 偏差说明 + commit 锚点),reviewerNotes 写汇总。**不要**另写独立的 design-check.md 长文(同一份 design×diff 二次消费;2026-08-22 实测该重复一遍 ≈8 分钟全量重读)。
357
- **gate 重试修复**:review.json 落盘后若后续 gate 报 docHash 失配 / 路径错,跑 \`sillyspec register-stage-review --change <变更名> --stage execute --refresh-hash\` 一键重算修复——**不要重做审查**(重做=同一材料第三遍)。
359
+ **gate 重试修复**:review.json 落盘后若 design.md 又有改版,gate 会**自动机械重算 docHash 放行**(verdict/checklist 保留,结论是否仍适用于新文档需人工确认)——**不要重做审查**(重做=同一材料第三遍);主文档路径错/缺失不会被自动修复,按 gate 报错修正 reviewedFiles[0],或跑 \`sillyspec register-stage-review --change <变更名> --stage execute --refresh-hash\`。
358
360
 
359
361
  ### 操作
360
362
  1. 读取 design.md(技术方案)——按章节精准读(design-check 对照表可借 tasks 卡 §锚点定位),避免反复整读全文
@@ -94,6 +94,68 @@ export function parseAllowedPaths(content) {
94
94
  return []
95
95
  }
96
96
 
97
+ /**
98
+ * 解析 task-NN.md frontmatter 的 target_files 字段(对账意图声明,design 总体方案 Wave 1 / X-10)。
99
+ *
100
+ * 与 parseAllowedPaths 同族的列表解析(inline [] + 顶格/缩进块列表双形态、CRLF 入口归一),
101
+ * 但语义相反——**严格模式**:allowed_paths 是 worktree 写入守卫的白名单(目录前缀 / glob /
102
+ * 引号皆合法、pathMatches 容差匹配);target_files 是 verify 对账(task-04 reconcileTargetFiles)
103
+ * 用的逐文件精确意图声明——白名单容差若用于对账,目录前缀会把整目录算成「预期」使 scope
104
+ * creep 检出恒空(D-002)。故此处不剥引号、不放行通配,非法形态记入 entry.invalid 交
105
+ * validateTargetFiles 报 ERROR。解析器纯函数无 IO、不 throw——依赖链已核实无环
106
+ * (verify-postcheck → plan-postcheck 单向),供对账侧跨文件 import 复用。
107
+ *
108
+ * 条目格式:`[NEW:]<仓根相对精确文件路径>`,NEW: 前缀 = 当前不存在的待建文件。
109
+ * 非列表形态(标量 / 未闭合)与 parseAllowedPaths 家族同口径视同未声明(missing),
110
+ * 走调用方的汇总 WARNING 信号,不在解析器内造第三种状态。
111
+ *
112
+ * @param {string} content - task 文件内容(同 parseAllowedPaths:frontmatter 自提取)
113
+ * @returns {{ entries: Array<{raw: string, isNew: boolean, path: string, invalid: string|null}>, missing: boolean }}
114
+ * raw = 条目原文(含 NEW: 前缀);path = 剥 NEW: 与 ./ 前缀、反斜杠归一正斜杠的路径;
115
+ * invalid = 非法形态原因(null = 合法);missing = 字段缺失或空列表(存量卡兼容口径)
116
+ */
117
+ export function parseTargetFiles(content) {
118
+ content = content.replace(/\r\n/g, '\n') // 同 parseAllowedPaths:入口归一,保护喂原始 CRLF 的外部调用方(task-04)
119
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
120
+ if (!fmMatch) return { entries: [], missing: true }
121
+ const fm = fmMatch[1]
122
+ let raws = null
123
+ const inlineMatch = fm.match(/target_files:[ \t]*\[([^\]]*)\]/)
124
+ if (inlineMatch) {
125
+ raws = inlineMatch[1].split(',').map(s => s.trim()).filter(Boolean)
126
+ } else {
127
+ // 块列表 [ \t]*\n 不吃换行(坑6① 家族教训:\s* 贪婪吞顶格列表导致静默判空)
128
+ const blockMatch = fm.match(/target_files:[ \t]*\n((?:[ \t]*-[ \t]+.+\n?)+)/)
129
+ if (blockMatch) {
130
+ raws = blockMatch[1].match(/[ \t]*-[ \t]+(.+)/g)
131
+ ?.map(s => s.replace(/^[ \t]*-[ \t]+/, '').trim()).filter(Boolean) || []
132
+ }
133
+ }
134
+ if (raws === null || raws.length === 0) return { entries: [], missing: true }
135
+
136
+ const entries = raws.map(raw => {
137
+ let path = raw
138
+ let isNew = false
139
+ if (path.startsWith('NEW:')) { isNew = true; path = path.slice('NEW:'.length).trim() }
140
+ // 路径归一正斜杠(design 数据流标注)——UNC `\\srv\x` 归一后 `//srv/x` 恰落入下方绝对路径判定
141
+ path = path.replace(/\\/g, '/')
142
+ // 剥 ./ 前缀(P1 修复,2026-09-07):声明 `./src/a.js` vs actual `src/a.js` 曾字面失配落②类假红
143
+ // (normalizeReconcilePath 只归一 actual 侧)——此处的单源归一同时覆盖 plan 校验与 verify 对账。
144
+ // (\./)+ 贪婪多层:`././src/a.js` 双前缀也归一到层底(对抗复审 Q-1,actual 侧 git 路径永远干净)。
145
+ path = path.replace(/^(\.\/)+/, '')
146
+ // 严格形态判定(X-10:每条 = 精确文件路径,禁 glob/目录前缀/引号/绝对路径)。
147
+ // 只记原因不 throw:解析器供对账复用,非法条目的处置(ERROR)归 plan-postcheck 校验层
148
+ let invalid = null
149
+ if (path === '') invalid = '剥 NEW: / ./ 前缀后为空'
150
+ else if (/[*?]/.test(path)) invalid = '含通配符(* 或 ?)——target_files 须逐文件精确声明,禁 glob(目录级覆盖会令 verify 对账的 scope creep 检出失效)'
151
+ else if (path.endsWith('/')) invalid = '以 / 结尾(目录前缀)——须精确到文件'
152
+ else if (/["'`]/.test(path)) invalid = '含引号/反引号——须裸路径书写(严格口径拒容差,防对账字面不匹配)'
153
+ else if (path.startsWith('/') || /^[A-Za-z]:/.test(path)) invalid = '是绝对路径——须写仓根相对路径'
154
+ return { raw, isNew, path, invalid }
155
+ })
156
+ return { entries, missing: false }
157
+ }
158
+
97
159
  /**
98
160
  * 从 task-NN.md frontmatter 解析标量字段(repo / base_commit / head_commit)。
99
161
  * 复用 parseTaskContracts 的 frontmatter 提取 + js-yaml load 模式(标量可选字段,正则对引号/空值脆弱)。
@@ -985,6 +1047,122 @@ export function validateDesignFileCoverage(changeDir) {
985
1047
  return { ok: errors.length === 0, errors, warnings, designFiles, uncovered }
986
1048
  }
987
1049
 
1050
+ /**
1051
+ * 流程产物前缀(target_files 不该声明的路径域)。
1052
+ * 与 worktree-apply filterDeliverableFiles 同口径硬编码——不 import 复用是因为
1053
+ * worktree-apply → plan-postcheck 已有依赖边(line 21 parseAllowedPaths),反向 import 成环。
1054
+ * 注意 .sillyspec/docs/ 不在此列:dogfood 模块文档 = 交付物(D-2 keepSillyspecDocs),
1055
+ * 声明它是合法的(verify 对账也保留 docs 路径)。
1056
+ */
1057
+ const TARGET_FILES_PROCESS_PREFIXES = ['.sillyspec/changes/', '.sillyspec/.runtime/', '.sillyspec/quicklog/']
1058
+
1059
+ /**
1060
+ * task 卡 target_files 声明核验器(plan-postcheck 第 7 项检查,P3a Wave 1 / X-10 / X-13)。
1061
+ *
1062
+ * 修复场景(design 背景):plan 侧「计划动了不存在的文件」类幻觉此前零核验,agent 写出
1063
+ * 幻觉路径要到 execute 子代理落盘失败才暴露。此处逐条机器核验意图声明,把幻觉/格式问题
1064
+ * 拦在 plan --done;同时为 task-04 verify 对账提供可信的声明侧(对账复用 parseTargetFiles)。
1065
+ *
1066
+ * 分级口径:
1067
+ * ERROR:格式非法(glob/目录前缀/引号/绝对路径——parseTargetFiles.invalid);幻觉路径
1068
+ * (repoRoot 下不存在且未加 NEW: 前缀)。
1069
+ * WARNING(不阻断,存量兼容):字段缺失**每变更汇总一条**(X-13:每卡一条会被存量卡
1070
+ * 噪音淹没);已存在文件带 NEW: 前缀(应去前缀);流程产物路径;越出该卡
1071
+ * allowed_paths 白名单(提前暴露到 apply Gate1 才发现的越权,X-5);design 清单外
1072
+ * 路径(声明与 design 漂移提示);跨仓卡剔除提示(路径相对其声明仓根,主仓核验
1073
+ * 恒假误报,与对账侧 D-004 同口径剔除)。
1074
+ *
1075
+ * fail-open 边界:tasks/ 目录缺失或无卡(同既有检查行为);design.md 缺失/无清单章节
1076
+ * (缺清单 error 归 validateDesignFileCoverage 把关,不重复);卡无 allowed_paths
1077
+ * (feasibility 已报 error,跳过交叉防双重噪音);repoRoot 缺省(纯函数测试场景)跳过存在性。
1078
+ *
1079
+ * @param {string} changeDir - 变更目录(读 tasks/ 卡与 design.md 清单)
1080
+ * @param {string|null} [repoRoot=null] - 主仓根目录(target_files 存在性基准)
1081
+ * @returns {{ ok: boolean, errors: string[], warnings: string[] }}
1082
+ */
1083
+ export function validateTargetFiles(changeDir, repoRoot = null) {
1084
+ const errors = []
1085
+ const warnings = []
1086
+
1087
+ const tasksDir = pJoin(changeDir, 'tasks')
1088
+ if (!existsSync(tasksDir)) {
1089
+ return { ok: true, errors, warnings }
1090
+ }
1091
+ const taskFiles = readdirSync(tasksDir).filter(f => /^task-\d+\.md$/.test(f))
1092
+ if (taskFiles.length === 0) {
1093
+ return { ok: true, errors, warnings }
1094
+ }
1095
+
1096
+ // design 清单交叉用清单(双交叉之一):与 validateDesignFileCoverage 同源解析(含按仓分段、
1097
+ // .sillyspec/docs 交付物口径),design 缺失/无清单 → 空列表 → 交叉自动跳过。检查间不共享
1098
+ // 解析结果(各检查独立只读 changeDir 产物),重复解析是聚合架构的既定代价。
1099
+ const designFiles = parseDesignCoverageByRepo(pJoin(changeDir, 'design.md')).allFiles
1100
+
1101
+ let missingCount = 0
1102
+ for (const file of taskFiles) {
1103
+ const content = readFileSync(pJoin(tasksDir, file), 'utf8')
1104
+ if (!/^---\n([\s\S]*?)\n---/.test(content)) continue // feasibility 已报 frontmatter 缺失,不重复
1105
+ const taskId = parseTaskId(content, file) || file
1106
+ const label = `${taskId} (${file})`
1107
+
1108
+ // 跨仓卡剔除:其 target_files 相对声明仓根,主仓 repoRoot 下 existsSync 恒假 → 全员
1109
+ // 误报幻觉路径。verify 对账(task-04)同口径剔除(D-004),此处只提示不核验
1110
+ const repo = parseRepo(content)
1111
+ if (repo) {
1112
+ warnings.push(`${label}: 跨仓卡(repo: ${repo})不参与主仓 target_files 核验——跨仓对账不在本变更范围,声明留待后续分期`)
1113
+ continue
1114
+ }
1115
+
1116
+ const { entries, missing } = parseTargetFiles(content)
1117
+ if (missing) { missingCount++; continue }
1118
+
1119
+ const allowedPaths = parseAllowedPaths(content)
1120
+ for (const e of entries) {
1121
+ if (e.invalid) {
1122
+ errors.push(`${label}: target_files「${e.raw}」格式非法——${e.invalid}。修复:改写为仓根相对的精确文件路径;当前不存在的文件加 NEW: 前缀(如 NEW:src/new.js)`)
1123
+ continue
1124
+ }
1125
+ // 流程产物:声明了也对不上账(verify 对账 filterDeliverableFiles 同口径会滤掉),
1126
+ // 且 changes/ 下路径属流程自产文件、不该是对账意图。提示后跳过后续核验(存在性/交叉
1127
+ // 对流程产物无意义,只会叠加噪音)
1128
+ if (TARGET_FILES_PROCESS_PREFIXES.some(p => e.path.startsWith(p)) || e.path === 'meta.json') {
1129
+ warnings.push(`${label}: target_files「${e.raw}」是流程产物路径——流程产物不进 target_files 声明(verify 对账同口径会将其滤掉,声明了也对不上账)`)
1130
+ continue
1131
+ }
1132
+ // 存在性:非 NEW 且不存在 = 幻觉路径(ERROR,本检查核心价值);NEW 且已存在 = 前缀误用(WARNING)
1133
+ if (repoRoot) {
1134
+ const fileExists = existsSync(pJoin(repoRoot, e.path))
1135
+ if (!e.isNew && !fileExists) {
1136
+ errors.push(`${label}: target_files「${e.raw}」在仓库中不存在且未加 NEW: 前缀——幻觉路径。修复:核对真实路径;确属新建文件则写 NEW:${e.path}`)
1137
+ continue // 路径本身不成立,allowed_paths/design 交叉对它无意义
1138
+ }
1139
+ if (e.isNew && fileExists) {
1140
+ warnings.push(`${label}: target_files「${e.raw}」已存在于仓库——NEW: 前缀仅用于当前不存在的文件,应去前缀`)
1141
+ }
1142
+ }
1143
+ // allowed_paths 交叉(双交叉之二,X-5):声明文件不在该卡白名单 → execute worktree
1144
+ // 写入守卫(apply Gate1)才暴露越权,此处提前到 plan 提示补白名单(容差匹配:目录
1145
+ // 前缀/glob 的 allowed_paths 可覆盖精确 target_file,与 coverage 对账同 pathMatches 语义)
1146
+ if (allowedPaths.length > 0 && !allowedPaths.some(ap => pathMatches(e.path, ap))) {
1147
+ warnings.push(`${label}: target_files「${e.path}」不在本卡 allowed_paths 白名单内——execute 写入守卫会拦(apply Gate1 才暴露越权)。修复:补进 allowed_paths(目录前缀/glob 写法均可)或修正 target_files`)
1148
+ }
1149
+ // design 清单交叉:声明与 design 漂移提示(design 清单是覆盖对账的基准,target_files
1150
+ // 声明清单外文件 = 两份意图清单分叉的早期信号)
1151
+ if (designFiles.length > 0 && !designFiles.some(dp => pathMatches(e.path, dp))) {
1152
+ warnings.push(`${label}: target_files「${e.path}」不在 design.md 文件变更清单内——请对齐(补 design 清单条目或修正声明),防声明与 design 漂移`)
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ // 字段缺失汇总(X-13):每变更一条而非每卡一条——存量卡大量无此字段,每卡一条 WARNING
1158
+ // 会淹没真正有价值的交叉警告;汇总粒度保信号可见又不阻断存量变更
1159
+ if (missingCount > 0) {
1160
+ warnings.push(`${missingCount} 张 task 卡未声明 target_files(存量卡可忽略——verify 对账将跳过;新卡请补:每条为仓根相对精确文件路径,不存在的文件加 NEW: 前缀)`)
1161
+ }
1162
+
1163
+ return { ok: errors.length === 0, errors, warnings }
1164
+ }
1165
+
988
1166
  /**
989
1167
  * Plan 可行性校验器(本地代码证明 execute 前置条件)
990
1168
  * 检查 TaskCard 的完整性和可行性
@@ -1236,7 +1414,7 @@ export async function executePlanPostcheck(context) {
1236
1414
 
1237
1415
  console.log(` 📂 变更目录: ${changeDir}`)
1238
1416
 
1239
- // ── 报错聚合(坑6③):六个检查全部跑完后统一输出全部失败项,一轮 --done 暴露全部问题,
1417
+ // ── 报错聚合(坑6③):七个检查全部跑完后统一输出全部失败项,一轮 --done 暴露全部问题,
1240
1418
  // 不再「失败一个抛一个、修一个冒一个」的迭代盲盒。各检查相互独立(都只读 changeDir 产物),
1241
1419
  // 前置检查失败不使后续检查失真——唯一例外是 tasks/ 目录缺失时后续检查都无意义,仍提前短路。
1242
1420
  const failures = [] // { name, errors, hint }
@@ -1333,6 +1511,21 @@ export async function executePlanPostcheck(context) {
1333
1511
  }
1334
1512
  }
1335
1513
 
1514
+ // ── 1f. task 卡 target_files 声明核验(P3a Wave 1)──
1515
+ // 对账意图声明的 plan 期机器核验:幻觉路径 / 非法格式 → error 硬拦(消灭「计划动不存在
1516
+ // 文件」的幻觉出口);存量卡缺字段仅每变更一条汇总 WARNING(X-13,不阻断零红门禁)。
1517
+ // 核验通过 = task-04 verify 对账的声明侧可信(对账 import parseTargetFiles 同源解析)。
1518
+ // 追加在既有 6 项之后,不触碰其语义与顺序;本检查同样独立、只读 changeDir 产物。
1519
+ const targetFiles = validateTargetFiles(changeDir, context.cwd)
1520
+ if (targetFiles.errors.length > 0) {
1521
+ failures.push({
1522
+ name: 'task 卡 target_files 声明核验(幻觉路径/非法格式)',
1523
+ errors: targetFiles.errors,
1524
+ hint: '修复方式:核对仓库真实路径——已存在文件裸写、新建文件加 NEW: 前缀;每条须为仓根相对的精确文件路径(禁 glob/目录前缀/引号/绝对路径)。',
1525
+ })
1526
+ }
1527
+ printSectionWarnings('target_files 声明', targetFiles.warnings)
1528
+
1336
1529
  // ── 聚合输出:一轮 --done 暴露全部失败项(坑6③)──
1337
1530
  if (failures.length > 0) {
1338
1531
  console.error(`\n❌ plan postcheck 失败(${failures.length} 类问题,已全部列出——一次修复后重跑,无需逐个迭代):`)
@@ -134,7 +134,7 @@ const stepGeneratePlan = {
134
134
  1. 读取 plan.md frontmatter 的 \`plan_level:\` 字段(上一步已落盘为持久锚点;文件不存在或无该字段时回退读上一步输出的分类结果)
135
135
  2. 读取 tasks.md 和 design.md 了解需求范围
136
136
  3. 按 plan_level 选择对应模板输出
137
- 4. **写回任务清单**:把展开后的任务清单写回 tasks.md(checkbox 行 \`- [ ] task-XX: 一句话任务名\`,可附 \`[model:xxx]\`/\`(depends_on: task-01,02)\` 行内标注)。**写回规则(D-002@v1)**:保留 frontmatter/中文标题/所有非 task-XX 行(quick 挂载的 ql-xxx 勾选行、注记等逐行保留),仅重写 task-XX checkbox 行集合——防摧毁 quick 挂载条目
137
+ 4. **写回任务清单**:把展开后的任务清单写回 tasks.md(checkbox 行 \`- [ ] task-XX: 一句话任务名\`,可附 \`[model:xxx]\`/\`(depends_on: task-01,02)\` 行内标注)。**写回规则(D-002@v1)**:保留 frontmatter/中文标题/所有非 task-XX 行(quick 挂载的 ql-xxx 勾选行、注记等逐行保留),仅重写 task-XX checkbox 行集合——防摧毁 quick 挂载条目。**展开任务时为每个 task 确定 target_files**(该 task 计划要改动的文件清单,随下一步「生成 TaskCard」按统一格式落进卡 frontmatter 的 target_files 字段):每项为精确仓根相对路径(✅ \`src/foo.js\`),计划新建的文件加 \`NEW:\` 前缀(✅ \`NEW:src/bar.js\`);❌ 禁 glob(\`src/**\`)/目录前缀(\`src/dir/\`)/绝对路径。target_files 与 allowed_paths 语义不同:前者=计划要动的具体文件,后者=权限范围
138
138
  5. 保存 plan.md(审查在下一步"审查计划"独立进行,不在本步自审——避免生成与自审同一次输出);frontmatter 保留 plan_level 字段不删。**plan.md Wave 段下任务一律纯 ID 引用行**(\`- task-XX\`,不重抄任务名——任务名唯一真相在 tasks.md,重抄即双写漂移)
139
139
 
140
140
  > ⚠️ **Wave 段格式铁律(踩坑:旧先例 \`- [ ] task-XX: 描述\` 格式会导致 postcheck 告警):**
@@ -416,6 +416,8 @@ decision_ids: [D-XXX@vN]
416
416
  repo: <repo-key> # 可选。仅跨仓 task 填:local.yaml repos: 注册的仓 key(缺省=main,主仓 task 省略此行)
417
417
  allowed_paths: # ⚠️ 路径相对 repo 声明的仓根(跨仓=跨仓仓根,主仓=主仓根),禁止带仓库名前缀/绝对路径
418
418
  - frontend/src/lib/errors.ts
419
+ target_files: # 可选。本 task 计划要改动的文件清单(文件级意图,供对账;与 allowed_paths 语义不同——那是权限范围)。骨架已预置 [] 占位行,无明确意图保留 [] 不动
420
+ - src/foo.js # ✅ 精确仓根相对路径(口径同 allowed_paths);计划新建文件加 NEW: 前缀(NEW:src/bar.js)。❌ 禁 glob(src/**)/目录前缀(src/dir/)/绝对路径
419
421
  provides: # 可选。仅当本 task 给其他 task 提供接口/DTO/响应时填
420
422
  - contract: <DTO或响应类型名> # 如 DaemonRuntimeRead
421
423
  fields: [field_a, field_b]
@@ -454,6 +456,8 @@ related_tests: # 可选。当本 task 改动会导致
454
456
 
455
457
  ⚠️ **TaskCard 必备字段(缺一 postcheck 直接阻断):** id、title(英文)、**title_zh(中文标题,必填)**、allowed_paths、goal、implementation、acceptance、verify、constraints。骨架已由 taskcard CLI 预生成(含 title_zh 占位),子代理只需 Edit 填值,不要删除任何必备字段。
456
458
 
459
+ 可选字段 **target_files**(本 task 计划要改动的文件清单;骨架已预置 \`target_files: []\` 占位行,按任务计划 Edit 填值,无明确文件级意图保留 []):✅ 精确仓根相对路径 \`src/foo.js\`、计划新建文件 \`NEW:src/bar.js\`;❌ \`src/**\`(glob)、\`src/dir/\`(目录前缀)、绝对路径。语义区分:target_files=计划要动的具体文件,allowed_paths=权限范围。
460
+
457
461
  ## 任务清单
458
462
  ${taskList}
459
463
 
@@ -501,7 +505,7 @@ TaskCard 的契约字段全部在 **frontmatter**(首对 --- 包裹的 YAML
501
505
  1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解整体上下文
502
506
  2. 读取本 batch 涉及的相关源文件
503
507
  3. **骨架已由主 agent 预生成**(\`${changeDir}/tasks/task-NN.md\` 已存在,LF 行尾 + 闭合 frontmatter + 硬校验 9 字段齐全)。**禁止再运行 \`sillyspec taskcard\` CLI**——并行子代理各起 CLI 进程会撞进度库 SQLite 锁(2026-08-25 实证);若发现本 batch 某卡骨架缺失,报告主 agent 补跑,不要自己跑
504
- 4. 用 Edit tool 逐卡填充骨架占位符(allowed_paths/goal/implementation/acceptance/verify/constraints 等)。**禁止用 Write 整文件重写**——手写整卡是 CRLF 行尾/漏闭合 --- /漏硬校验字段三类 postcheck 拒绝的根源,骨架 + Edit 从源头消灭
508
+ 4. 用 Edit tool 逐卡填充骨架占位符(allowed_paths/target_files/goal/implementation/acceptance/verify/constraints 等)。**禁止用 Write 整文件重写**——手写整卡是 CRLF 行尾/漏闭合 --- /漏硬校验字段三类 postcheck 拒绝的根源,骨架 + Edit 从源头消灭
505
509
  5. 骨架字段含义与可选字段(provides/expects_from/related_tests,按需插进 frontmatter)参考下述模板:
506
510
 
507
511
  ${taskcardTemplate}
@@ -5,35 +5,13 @@ export const definition = {
5
5
  auxiliary: true,
6
6
  steps: [
7
7
  {
8
+ // noAI 化(2026-09-05 ①):ls 级探测不值一整轮 agent 往返——CLI 直跑(scanDetectProjects),
9
+ // 只打印建议不注册(原红线语义不变)。步骤名保持不变(存量进度库按名匹配迁移)。
8
10
  name: '探测项目结构并建议子项目',
9
- prompt: `扫描项目顶层目录结构,自动发现可能的子项目,**需用户确认后才创建 projects 配置**。
10
-
11
- ### 操作
12
- 1. 列出项目顶层目录:\`ls -d */ 2>/dev/null | grep -v node_modules | grep -v '.git' | grep -v '.sillyspec'\`
13
- 2. 对每个顶层目录,快速判断是否为独立项目(检查 package.json / pom.xml / build.gradle / pyproject.toml / go.mod 等构建文件)
14
- 3. 对每个疑似独立项目,检测技术栈:\`cat <dir>/package.json 2>/dev/null | head -5\` 或类似
15
- 4. 对比 \`{PROJECTS_ROOT}/\` 已有配置,找出未注册的子项目
16
-
17
- ### 判断标准(满足任一即为子项目)
18
- - 有独立的构建文件(package.json, pom.xml, build.gradle, pyproject.toml 等)
19
- - 有独立的源码目录结构(src/, app/, lib/ 等)
20
- - 有独立的测试目录(test/, tests/, __tests__/ 等)
21
- - 不是 .git / node_modules / .sillyspec / dist / build 等工具目录
22
-
23
- ### 输出格式
24
- 列出发现的可能子项目列表,每个含:
25
- - 目录名
26
- - 技术栈(如 Next.js + TypeScript、FastAPI + Python)
27
- - 是否已注册到 projects/
28
- - 建议:注册 / 跳过
29
-
30
- ### ⛔ 红线
31
- - **不要自动创建 projects 配置文件**,只列出建议供用户确认
32
- - **不要修改任何文件**,只做探测和报告
33
-
34
- ### 输出
35
- 子项目建议列表(含技术栈和注册状态)`,
36
- outputHint: '子项目建议列表',
11
+ noAI: true,
12
+ _cliAction: 'scanDetectProjects',
13
+ prompt: '',
14
+ outputHint: '子项目建议列表(CLI 探测输出)',
37
15
  optional: false
38
16
  },
39
17
  {
@@ -90,34 +68,34 @@ export const definition = {
90
68
  {
91
69
  name: '构建环境探测',
92
70
  perProject: true,
93
- prompt: `探测当前项目的构建环境和依赖。
71
+ prompt: `生成事实底稿(机械事实由 CLI 预咀嚼)+ 补充机器拿不到的线索。
94
72
 
95
73
  ### 操作
96
74
  对扫描列表中的每个项目重复以下操作:
97
- 1. 进入项目目录(子项目用其 path,如 \`packages/dashboard/\`)
98
- 2. \`cat package.json pom.xml build.gradle go.mod Cargo.toml requirements.txt pyproject.toml Gemfile composer.json 2>/dev/null\`
99
- 3. \`find <project-dir> -maxdepth 2 -name "*.config.*" -not -path "*/node_modules/*" -not -path "*/.git/*" | head -20 | xargs cat 2>/dev/null\`
100
- 4. 结果保存到 \`{DOCS_ROOT}/scan/_env-detect.md\`(临时文件,扫描完删除)
75
+ 1. **先跑 CLI 确定性抽取**(端点/依赖/脚本/源码规模/git 基线,零 AI token):
76
+ \`sillyspec scan facts\`(主项目;子项目加 \`--path <仓库相对路径> --project <项目名>\`)。
77
+ 产物落 \`{DOCS_ROOT}/scan/_facts.md\`——后续所有子代理共享这一份底稿,**不要重复 grep 发现这些事实**。
78
+ 2. 补充机器拿不到的线索(保持精简):README / CI 配置里的构建与测试命令、特殊环境要求、
79
+ 配置文件要点(\`cat <project-dir>/*.config.* 2>/dev/null | head -80\`),
80
+ 追加写入 \`{DOCS_ROOT}/scan/_env-detect.md\`(临时文件,扫描完删除)。
81
+
82
+ ### ⛔ 红线
83
+ - **禁止自己 grep/rg 重新发现端点、路由、依赖清单**——以 \`_facts.md\` 为准;发现与源码冲突时以底稿为准并在文档中标注差异
84
+ - 只读探测,不修改任何源文件
101
85
 
102
86
  ### 输出
103
- 每个项目的环境探测结果摘要`,
104
- outputHint: '环境探测摘要',
87
+ 每个项目的 _facts.md 状态(类型/依赖数/端点数)+ 补充线索摘要`,
88
+ outputHint: '_facts.md 状态 + 环境探测补充摘要',
105
89
  optional: false
106
90
  },
107
91
  {
92
+ // noAI 化(2026-09-05 ①):existsSync×7 的机械检查——CLI 一次遍历全部已注册项目(scanResumeCheck)。
93
+ // 不再 perProject(单次 CLI 调用覆盖多项目,注册表步数不变仍为 11)。
108
94
  name: '断点续扫检测',
109
- perProject: true,
110
- prompt: `检测当前项目已有扫描文档,列出缺失的。
111
-
112
- ### 操作
113
- 对扫描列表中的每个项目分别执行:
114
- 1. 检查 7 份文档是否存在:ARCHITECTURE、STRUCTURE、CONVENTIONS、INTEGRATIONS、TESTING、CONCERNS、PROJECT
115
- 路径:\`{DOCS_ROOT}/scan/<DOC>.md\`
116
- 2. 列出已有 ✅ 和缺失 ⬜
117
-
118
- ### 输出
119
- 每个项目的已有/缺失文档列表`,
120
- outputHint: '断点续扫状态',
95
+ noAI: true,
96
+ _cliAction: 'scanResumeCheck',
97
+ prompt: '',
98
+ outputHint: '各项目已有/缺失文档清单(CLI 输出)',
121
99
  optional: false
122
100
  },
123
101
  {
@@ -134,8 +112,8 @@ export const definition = {
134
112
  a. 将 \`<project>\` 替换为实际项目名,得到该项目的目标文件路径
135
113
  b. 为每个角色启动独立子代理(可并行),每个子代理负责 1-2 份文档
136
114
  c. 子代理的搜索范围限定在该项目目录内(子项目如 \`packages/dashboard/\`,不要搜索主项目源码)
137
- d. 子代理直接用 grep/rg 搜索源码并写入文件,结果不回传到你的上下文
138
- e. 等待该项目所有子代理完成后,验证文件是否生成且非空
115
+ d. 子代理在**事实底稿覆盖范围内的机械事实**(端点/依赖/规模)直接消费注入的 \`_facts.md\`,不重新 grep 发现;底稿之外的架构理解仍用 grep/rg 搜索源码并写入文件,结果不回传到你的上下文
116
+ e. 等待该项目所有子代理完成后,直接进入下一项目——产物存在性/引用有效性由第 3 步 workflow check 与 postcheck **机器校验**,不要自己读回文件核对
139
117
  f. 该项目完成后,继续下一个项目
140
118
  3. 所有项目完成后,运行以下命令检查产物:
141
119
  \`sillyspec workflow check scan-docs --project <project>\`(CLI 子命令,任意项目根可用)
@@ -144,19 +122,9 @@ export const definition = {
144
122
 
145
123
  ### 覆盖保护
146
124
  - **scan_depth: quick 的浅层文档允许覆盖升级**:读取旧 frontmatter 时,若旧文档含 scan_depth: quick(由 --quick 快速接入生成的浅层版本),即使其 source_commit 与当前 HEAD 一致、updated_at 未变,也允许覆盖重写——深度扫描的目的就是把它升级为完整文档。
147
- - 生成每份 scan 文档时,frontmatter 必须包含:
148
- \`\`\`yaml
149
- ---
150
- author: <git 用户名(git config user.name 的输出,如 qinyi)>
151
- created_at: <精确到秒的时间,如 2026-07-27 10:30:00>
152
- source_commit: <git-head-short>
153
- updated_at: <now-iso-datetime>
154
- generator: sillyspec-scan
155
- ---
156
- \`\`\`
157
- (\`<git-head-short>\` / \`<now-iso-datetime>\` 两占位符由 CLI 注入 prompt 时自动替换为当前 HEAD 短哈希与时间——照 prompt 里已替换的真实值抄写,勿自跑 git rev-parse;漏 header 事后 \`sillyspec scan-fix-headers\` 一键补 author/created_at)
125
+ - **frontmatter 由 CLI 自动写入(2026-09-05 ②):子代理不写 frontmatter、不跑 git 取值**。文档正文第 1 行直接写中文标题;终检步骤(scanFinalize)的 CLI 会按需注入 author / created_at / source_commit / updated_at / generator,只补缺不覆盖。
158
126
  - 覆盖已有 scan 文档前先读取旧 frontmatter;如果旧文档的 \`source_commit\` 与当前 HEAD 不一致,或旧文档 \`updated_at\` 晚于本次 scan 开始时间,不要覆盖。
159
- - 如果用户明确传入 \`--force-rescan\`,允许覆盖,但仍需写入新的 \`source_commit\` 和 \`updated_at\`。
127
+ - 如果用户明确传入 \`--force-rescan\`,允许覆盖(新 frontmatter 仍由 CLI 注入)。
160
128
 
161
129
  ### 子代理上下文注入
162
130
  启动每个子代理前,将以下信息拼入子代理 prompt:
@@ -164,9 +132,10 @@ export const definition = {
164
132
  - 目标文件路径(从 workflow YAML 中 \`<project>\` 替换后的路径)
165
133
  - 检查要求(从 workflow YAML 中该角色的 checks)
166
134
  - 断点续扫步骤列出的缺失文档列表
167
- - 环境探测结果摘要(如有 _env-detect.md,直接贴入)
135
+ - **事实底稿 \`{DOCS_ROOT}/scan/_facts.md\` 全文直接贴入**(CLI 确定性抽取的端点/依赖/规模清单——子代理**禁止重新 grep 发现**这些机械事实,只做解读与成文;INTEGRATIONS/ARCHITECTURE 中的端点与集成条目必须与底稿一致,并直接使用底稿里的 file:line 引用)
136
+ - 环境探测补充摘要(如有 _env-detect.md,直接贴入)
168
137
  - **⚠️ 必须强调:子代理必须用 write 工具将文件写入磁盘**
169
- - **文件标题用中文**(sillyhub 平台解析识别用):frontmatter 必须在文件最前(第 1 行 --- 起),frontmatter 结束的 --- 之后空一行,再写 # 中文名(English)作为标题。严禁把 # 标题放到 frontmatter 之前(否则 frontmatter 不在头部、「检查产物完整性」步的 frontmatter 检查会报缺 author/created_at)。各文档标准标题格式:
138
+ - **文件标题用中文**(sillyhub 平台解析识别用):正文第 1 行写 # 中文名(English)作为标题(CLI 会把 frontmatter 自动插到标题之前,你不用管)。各文档标准标题格式:
170
139
  - STRUCTURE.md = # 目录结构(Structure)
171
140
  - CONVENTIONS.md = # 代码约定(Conventions)
172
141
  - ARCHITECTURE.md = # 架构(Architecture)
@@ -174,6 +143,11 @@ export const definition = {
174
143
  - CONCERNS.md = # 关注点(Concerns)
175
144
  - INTEGRATIONS.md = # 集成(Integrations)
176
145
  - PROJECT.md = # 项目(Project)
146
+ - **事实引用纪律**:子代理写入文档的关键事实(模块职责、技术栈组件、外部集成、核心命令)必须带反引号包裹的 path:line 引用(如 \`src/db.js:42\`),且引用必须真实——CLI postcheck 会逐条核验 file:line,失效引用产生 warning。**禁止猜行号**:不确定时只写文件路径不写行号;无法确证的结论标「待确认」,不写成事实。
147
+
148
+ ### 收敛规则(防打转)
149
+ - \`sillyspec workflow check scan-docs\` 报告失败项后,**只修复报告点名的角色/文件对应的问题**,不要重写未失败的文档
150
+ - 每轮修复后重跑检查,跟踪失败项数量:连续两轮失败项数量没有下降 → 停止重试,在 --output 里如实报告未解决的失败项与已尝试的修复,等待人工决策。**禁止为通过检查而编造内容或降低文档质量**
177
151
 
178
152
  ### 完成后
179
153
  列出每个项目的 7 份文档状态:
@@ -562,46 +536,14 @@ INDEX.md 维护索引,格式(每行:关键词1|关键词2 → [条目名](
562
536
  optional: false
563
537
  },
564
538
  {
539
+ // noAI 化(2026-09-05 ①②):原 40 行自检 prompt 的职责全部机器化——
540
+ // 文档存在性/header/路径污染/API 错误信号 → runScanPostCheck;frontmatter → stampScanDocHeaders;
541
+ // _env-detect 清理 + 非平台 git add → CLI 直做。failed_post_check 时 throw 保持 step pending。
565
542
  name: '自检和提交',
566
- perProject: true,
567
- prompt: `验证当前项目的扫描完整性,清理并提交。
568
-
569
- ### 操作
570
- 对扫描列表中的每个项目分别执行:
571
- 1. 检查 7 份 scan 文档是否全部生成(\`{DOCS_ROOT}/scan/\`)
572
- 2. 检查模块文档状态(\`{DOCS_ROOT}/modules/\`)
573
- 3. 自检门控:ARCHITECTURE(技术栈+Schema摘要)、CONVENTIONS(隐形规则+代码风格)、STRUCTURE(目录结构)、INTEGRATIONS(外部依赖)、TESTING(测试现状)、CONCERNS(技术债务)、PROJECT(项目概览)
574
- 4. 检查 flows/ 和 glossary.md 是否已生成(如有)
575
- 5. 清理:\`rm -f {DOCS_ROOT}/scan/_env-detect.md\`
576
- 6. 如果非平台模式:\`git add {DOCS_ROOT}/ {KNOWLEDGE_ROOT}/\` — 暂存本次扫描产物(scan 文档 / 模块文档 / flows / glossary / knowledge;勿用 .sillyspec/ 整目录——会裹挟 changes/ 下其他活跃变更;不要 commit,由用户通过统一提交工具处理)。如果平台模式:跳过 git add(specRoot 不在 sourceRoot 的 git repo 内)。
577
-
578
- ### ⛔ 路径合规检查(平台模式下必须执行)
579
- 7. 确认所有文档都写入 \`{DOCS_ROOT}/\`(spec-root 下),**而非源码目录下的 .sillyspec/**
580
- 8. 检查是否出现 tool_use_error 或 API Error 未恢复
581
- 9. 检查 7 份文档 header 是否包含 author 和 created_at
582
- 10. 复查 detect 核验结果——commands 键已由 detect 核验 scripts 存在性,agent 无需重复核验或标 unavailable,仅确认 local.yaml 与 detect 产出一致
583
-
584
- ### ⛔ API 错误处理
585
- - 遇到 API Error 529(服务过载)或 rate_limit 时,**停止当前操作并报告**,不要自动重试
586
- - 遇到 tool_use_error 时,记录错误信息并跳过该文件/操作,继续处理下一项
587
- - 如果连续 3 次操作失败,输出失败摘要并停止
588
-
589
- ### ⛔ 最终状态判定
590
- 如果出现以下**任意**情况,最终状态**不能**写"全部通过",只能写 \`completed_with_warnings\` 或 \`failed_post_check\`:
591
- - 源码目录下存在 docs(路径合规检查失败)
592
- - source_commit 为 null
593
- - Write 工具出现过失败
594
- - API Error 529 或 rate_limit
595
- - fallback / retry / skipped validation
596
- - 文档引用不存在的文件或模块
597
- - 文档内容包含 .sillyspec/ 等工具目录的扫描结果
598
-
599
- ### 输出
600
- 每个项目的扫描完整性报告(必须包含路径合规检查结果和最终状态)
601
-
602
- ### 注意
603
- - ❌ 修改代码 / 编造路径 / 读源码全文`,
604
- outputHint: '自检报告',
543
+ noAI: true,
544
+ _cliAction: 'scanFinalize',
545
+ prompt: '',
546
+ outputHint: 'CLI 终检报告(frontmatter 注入 + postcheck + 清理 + 暂存)',
605
547
  optional: false
606
548
  }
607
549
  ]
@@ -47,13 +47,13 @@ export const definition = {
47
47
  migratedFrom: ['状态检查'],
48
48
  prompt: `> 💡 先说清楚:\`sillyspec run verify\`(不带 --done)**只下发执行指令,不会替你跑测试/构建**——真正的测试由 CLI 在最后 --done 时统一执行(local.yaml 的 commands.test,同步对账可能耗时较长);「运行测试和质量扫描」那步**不需要你重复手动跑全量**(避免与 CLI 对账重复耗时),只做 lint/静态检查 + 可选快速冒烟。别以为敲了 run verify 就自动验证了。
49
49
 
50
- 检查当前进度,确认可以执行 verify。
50
+ 检查当前进度,确认可以执行 verify(快照已由 CLI 注入,勿再跑 progress show;\`sillyspec status\` 是项目级快照,也不推进流程)。
51
51
 
52
- > ⚠️ 本步用 \`sillyspec progress show\` 查**流程进度**(推进工作流用),不要用 \`sillyspec status\`(那是**项目级快照**,只读、不推进流程,是另一条命令)。
52
+ ### 进度快照(CLI 注入)
53
+ {PROGRESS_SNAPSHOT}
53
54
 
54
55
  ### 操作
55
- 1. 运行 \`sillyspec progress show\`
56
- 2. 确认 currentStage 为 "verify"
56
+ 1. 如果快照显示阶段不符,输出正确提示并停止
57
57
 
58
58
  ### 输出
59
59
  当前进度摘要`,
@@ -214,9 +214,11 @@ export const definition = {
214
214
  你只需:在 verify-result.md 的「变更风险等级」section 如实记录变更性质;若变更涉及 daemon/backend 跨进程、session/lease/lifecycle 状态机、或部署启动路径,在「Runtime Evidence」section 提供真实集成证据(启动命令、daemon↔backend 调用与日志关键片段、终态断言)。
215
215
  - **集成证据是自报告、CLI 不独立运行时核验**:「Runtime Evidence」由你如实填写,CLI 只校验其**字面存在**(是否含关键词),**不会替你启动 daemon、打真实请求或跑迁移**——它是否名副其实取决于你是否实跑过。务必实跑后据实填写,不得凭堆关键词通过门控。(测试套件对账另算:commands.test 由 CLI 真实执行,那块谎报无效。)
216
216
 
217
- 3. **生成 verify-result.md 骨架(勿从零手写)**:先跑 \`sillyspec verify-probes --change <change-name> --init\`——一条命令生成九章节骨架(已存在不覆盖),其中**探针结果章节已机械预填**(探针 1 的 TODO/FIXME 命中清单、探针 3 的测试覆盖、探针 5 的 API 契约对账表、探针 6 的删除对账三态判定),文件落 \`{SPEC_ROOT}/changes/<change-name>/verify-result.md\`(CLI 替换出的**主仓绝对路径**;若当前 cwd 在 worktree 内也绝不落 worktree 副本——CLI 校验读主仓,副本随 worktree 清理蒸发)。你只需把各 \`<!--TODO-->\` 占位替换为语义结论;半语义探针(2 关键词覆盖 / 4 决策追踪)与断言抽查、集成盲区标注由你补在对应 TODO 处
218
- 4. 给出结论:PASS / PASS WITH NOTES / FAIL(受风险门控约束)——**结论必须写明 PASS/FAIL 字样,留「待填」会被 gate 判不过**
219
- 5. **核对 module-impact.md**(若 \`{SPEC_ROOT}/changes/<change>/module-impact.md\` 存在):对照本次实际代码变更(git diff)与 module-impact.md 的模块影响矩阵,发现不一致(漏标受影响模块 / 影响类型错误 / 实际未触碰的模块被误标)则在 verify-result.md 标注。module-impact 由 plan 首版生成、execute 各 Wave 更新,verify 是最后一次核对机会(archive 仅终审不再生成)。这是 advisory 核对(不阻断 verify 完成),但 module-impact 与实际严重背离应记为风险。
217
+ 3. **生成 verify-result.md 骨架(勿从零手写)**:先跑 \`sillyspec verify-probes --change <change-name> --init\`——一条命令生成十章节骨架(已存在不覆盖),其中**探针结果章节已机械预填**(探针 1 的 TODO/FIXME 命中清单、探针 3 的测试覆盖、探针 5 的 API 契约对账表、探针 6 的删除对账三态判定),文件落 \`{SPEC_ROOT}/changes/<change-name>/verify-result.md\`(CLI 替换出的**主仓绝对路径**;若当前 cwd 在 worktree 内也绝不落 worktree 副本——CLI 校验读主仓,副本随 worktree 清理蒸发)。你只需把各 \`<!--TODO-->\` 占位替换为语义结论;半语义探针(2 关键词覆盖 / 4 决策追踪)与断言抽查、集成盲区标注由你补在对应 TODO 处
218
+ 4. **预填探针段不可篡改或删除**:verify \`--done\` gate 会重跑探针对比 verify-result.md 正文,不符即拦(ERROR);对探针结果有异议只能在预填段旁追加说明,不得改写原文
219
+ 5. **verify-facts.json 为机器底稿,勿手改**:防篡改对比的基准是正文,改底稿无意义
220
+ 6. 给出结论:PASS / PASS WITH NOTES / FAIL(受风险门控约束)——**结论必须写明 PASS/FAIL 字样,留「待填」会被 gate 判不过**
221
+ 7. **核对 module-impact.md**(若 \`{SPEC_ROOT}/changes/<change>/module-impact.md\` 存在):对照本次实际代码变更(git diff)与 module-impact.md 的模块影响矩阵,发现不一致(漏标受影响模块 / 影响类型错误 / 实际未触碰的模块被误标)则在 verify-result.md 标注。module-impact 由 plan 首版生成、execute 各 Wave 更新,verify 是最后一次核对机会(archive 仅终审不再生成)。这是 advisory 核对(不阻断 verify 完成),但 module-impact 与实际严重背离应记为风险。
220
222
 
221
223
  ### verify-result.md 章节结构(骨架已含,占位替换即可)
222
224
 
package/src/taskcard.js CHANGED
@@ -55,7 +55,9 @@ function yamlScalar(v) {
55
55
  *
56
56
  * 硬校验 9 字段(plan-postcheck feasibility)+ 规范字段全部就位,占位符由子代理 Edit 填充;
57
57
  * 可选字段(provides/expects_from/related_tests)默认不生成——缩小 YAML 出错面,何时加的
58
- * 判据写在文件尾注释(规则详情见 templates/prompts/taskcard-rules.md)。
58
+ * 判据写在文件尾注释(规则详情见 templates/prompts/taskcard-rules.md)。例外:target_files
59
+ * 预置空占位注释行([] + 行内 # 注释)——占位符硬拦基于 taskcard-placeholders.js 的封闭
60
+ * 字面清单,本行值与注释刻意不含任何占位标记字面量,不会被 plan --done 拦截。
59
61
  * 字符串只含 \n:writeFileSync 直写不经任何行尾转换,Windows 下天然 LF 安全。
60
62
  * title/title_zh/author 经 yamlScalar 转义(自由文本防 YAML 指示符炸解析)。
61
63
  *
@@ -99,6 +101,7 @@ requirement_ids: ${fm.requirement_ids}
99
101
  decision_ids: ${fm.decision_ids}
100
102
  allowed_paths:
101
103
  - src/example/file.ts
104
+ target_files: [] # 可选:本 task 计划改动的文件(对账用精确路径清单,格式见下方注释;不填保留 [])
102
105
  goal: >
103
106
  一句话说明这个 task 要做什么、为什么。
104
107
  implementation:
@@ -119,6 +122,10 @@ constraints:
119
122
  勿用 Write 整文件重写——会引入 CRLF 行尾/漏闭合 ---/漏字段回归。
120
123
  ⚠️ plan --done 硬校验会拦截未替换的占位符(FR-XX / D-XXX / src/example/file.ts /
121
124
  一句话说明这个 task / 具体步骤 1 / 可验证的验收条件 1 / 边界约束 1)——占位符视同缺字段。
125
+ target_files 格式(可选,对账用精确文件级意图声明,与 allowed_paths 语义不同):
126
+ 精确文件路径(仓根相对、正斜杠),当前不存在、将由本 task 新建的文件加
127
+ NEW: 前缀(如 NEW:src/foo.js);禁 glob(src/**)、禁目录前缀(src/dir/)、
128
+ 禁绝对路径;无明确文件级意图时保留 [] 占位行不动。
122
129
  可选字段按需插进上方 frontmatter(规则见 taskcard-rules):
123
130
  repo: 仅跨仓 task 填(local.yaml repos: 注册的仓 key;缺省=main。allowed_paths 相对该仓根写,
124
131
  禁止带仓库名前缀/绝对路径——review 对账按仓根相对路径匹配,带前缀永不命中)