sillyspec 3.32.9 → 3.33.0

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 (176) hide show
  1. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  2. package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
  3. package/.claude/skills/sillyspec-explore/SKILL.md +107 -107
  4. package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  6. package/.github/workflows/ci.yml +58 -0
  7. package/.husky/pre-push +7 -6
  8. package/AGENTS.md +1 -1
  9. package/assets/command-cards/flow.md +1 -1
  10. package/eval/eval.config.json +3 -3
  11. package/eval/lib/config.mjs +6 -3
  12. package/eval/lib/util.mjs +16 -3
  13. package/eval/lib/workdir.mjs +4 -1
  14. package/eval/results/eval.db +0 -0
  15. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
  16. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
  17. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
  18. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
  19. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
  20. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
  21. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
  22. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
  23. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
  24. package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
  25. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
  26. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
  27. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
  28. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
  29. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
  30. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
  31. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
  32. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
  33. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
  34. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
  35. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
  36. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
  37. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
  38. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
  39. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
  40. package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
  41. package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
  42. package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
  43. package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
  44. package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
  45. package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
  46. package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  47. package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
  48. package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
  49. package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
  50. package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
  51. package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
  52. package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
  53. package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
  54. package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
  55. package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
  56. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
  57. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
  58. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
  59. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
  60. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
  61. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
  62. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
  63. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  64. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
  65. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  66. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  67. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
  68. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  69. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
  70. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
  71. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
  72. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
  73. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
  74. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
  75. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  76. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
  77. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  78. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
  79. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  80. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
  81. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
  82. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  83. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
  84. package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
  85. package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
  86. package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
  87. package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
  88. package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
  89. package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
  90. package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
  91. package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
  92. package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
  93. package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  94. package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
  95. package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
  96. package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
  97. package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
  98. package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
  99. package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
  100. package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
  101. package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
  102. package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
  103. package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
  104. package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  105. package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
  106. package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
  107. package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
  108. package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
  109. package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
  110. package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
  111. package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
  112. package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
  113. package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
  114. package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
  115. package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
  116. package/eval/tasks/004-phase-stats/task.json +6 -0
  117. package/eval/tasks/004-phase-stats/verify.mjs +38 -0
  118. package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
  119. package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
  120. package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
  121. package/eval/tasks/005-orders-export/task.json +6 -0
  122. package/eval/tasks/005-orders-export/verify.mjs +28 -0
  123. package/package.json +1 -1
  124. package/quicklog/tmp-fix-review.mjs +21 -0
  125. package/src/ceremony-tier.js +2 -1
  126. package/src/commit-suggest.js +45 -4
  127. package/src/config-schema.js +14 -0
  128. package/src/decision-distill.js +106 -24
  129. package/src/decision-redomain.js +148 -0
  130. package/src/decision-renumber.js +157 -0
  131. package/src/design-facts.js +3 -3
  132. package/src/docs-check.js +30 -15
  133. package/src/docs-debt.js +6 -5
  134. package/src/doctor-diagnostics.js +3 -1
  135. package/src/flow-parity.js +28 -4
  136. package/src/flow.js +76 -13
  137. package/src/forensics.js +236 -0
  138. package/src/fr-index.js +17 -2
  139. package/src/hooks/worktree-guard.js +3 -3
  140. package/src/index.js +41 -6
  141. package/src/knowledge-digest.js +23 -6
  142. package/src/knowledge-graph.js +95 -18
  143. package/src/knowledge-match.js +34 -9
  144. package/src/knowledge-stats.js +12 -13
  145. package/src/knowledge-vector.js +98 -7
  146. package/src/machine-interface.js +26 -0
  147. package/src/module-impact.js +2 -5
  148. package/src/modules.js +31 -0
  149. package/src/quicklog.js +6 -1
  150. package/src/run/archive-distill.js +3 -0
  151. package/src/run/command.js +3 -3
  152. package/src/run/complete-handlers.js +8 -3
  153. package/src/run/complete.js +113 -44
  154. package/src/run/gates.js +100 -103
  155. package/src/run/lint-ownership.js +37 -0
  156. package/src/run/prompt.js +43 -28
  157. package/src/run/stage.js +1 -1
  158. package/src/run/verify-quality-scan.js +10 -1
  159. package/src/sentinel-assertions.js +43 -1
  160. package/src/spec-sync.js +81 -2
  161. package/src/stage-contract.js +26 -3
  162. package/src/stage-review.js +46 -12
  163. package/src/stage-templates.js +2 -0
  164. package/src/stages/brainstorm.js +76 -21
  165. package/src/stages/knowledge.js +56 -5
  166. package/src/stages/plan-postcheck.js +13 -2
  167. package/src/stages/plan.js +19 -0
  168. package/src/sync.js +28 -2
  169. package/src/task-tick.js +12 -0
  170. package/src/test-bindings.js +32 -3
  171. package/src/timeline.js +60 -19
  172. package/src/ui-visual.js +119 -4
  173. package/src/verify-postcheck.js +166 -18
  174. package/src/verify-probes.js +61 -18
  175. package/src/worktree-apply.js +26 -2
  176. package/templates/agents-instruction.md +1 -1
package/src/docs-check.js CHANGED
@@ -1038,12 +1038,20 @@ export function readDecisionRulesConfig(projectRoot) {
1038
1038
  }
1039
1039
  }
1040
1040
 
1041
- /** 豁免键匹配(本变更范围先支持键匹配,非子串):规则级 `decisions.<id>.<kind>` 精确命中,
1042
- * 或条目级 `decisions.<id>` 伞形命中(一条豁免该决策全部规则)。无 decisions. 前缀的键不参与。 */
1043
- function isDecisionExempt(knownFailures, entryId, kind) {
1044
- const ruleKey = `decisions.${entryId}.${kind}`
1045
- const entryKey = `decisions.${entryId}`
1046
- return knownFailures.some(k => k === ruleKey || k === entryKey)
1041
+ /** 豁免键匹配(键匹配非子串):域限定键 `decisions.<域>/<id>.<kind>`(规则级精确)与
1042
+ * `decisions.<域>/<id>`(条目级伞形)——2026-10-14-decision-id-p2-hardening:裸键无域限定,
1043
+ * 一条 decisions.<id> 可静默豁免全库同号条目(D-001@v1 ×296 潜伏面),域限定键收窄命中面;
1044
+ * 裸键 `decisions.<id>[.<kind>]` 保留兼容(消费仓既有配置不失效)。id 与 originId(原编号,
1045
+ * 2026-10-14-decision-legacy-renumber)双匹配——存量迁移后旧号键继续命中。
1046
+ * 无 decisions. 前缀的键不参与。导出(测试面先例 anchorFilePath)。 */
1047
+ export function isDecisionExempt(knownFailures, entryId, kind, domain = null, originId = null) {
1048
+ const ids = originId ? [entryId, originId] : [entryId]
1049
+ const keys = []
1050
+ for (const id of ids) {
1051
+ keys.push(`decisions.${id}.${kind}`, `decisions.${id}`)
1052
+ if (domain) keys.push(`decisions.${domain}/${id}.${kind}`, `decisions.${domain}/${id}`)
1053
+ }
1054
+ return knownFailures.some(k => keys.includes(k))
1047
1055
  }
1048
1056
 
1049
1057
  /**
@@ -1051,25 +1059,32 @@ function isDecisionExempt(knownFailures, entryId, kind) {
1051
1059
  * 的写入契约对偶:producer=decision-distill → consumer=本规则族;字段行非列表项,顶层
1052
1060
  * 「状态:/锚点:/最近确认:」标签精确匹配,防误吞正文)。旧格式字段缺失容错为 null。
1053
1061
  * @param {string} md 域文件全文
1054
- * @returns {Array<{ id: string, title: string, status: string|null, anchor: string|null, lastConfirmed: string|null }>}
1062
+ * @returns {Array<{ id: string, title: string, status: string|null, anchor: string|null, lastConfirmed: string|null, change: string|null }>}
1055
1063
  */
1056
1064
  export function parseDecisionEntries(md) {
1057
1065
  const entries = []
1058
1066
  let cur = null
1059
1067
  const flush = () => { if (cur) entries.push(cur) }
1060
1068
  for (const line of String(md || '').replace(/\r\n?/g, '\n').split('\n')) {
1061
- const h = line.match(/^##\s+(D-\d+)(?:@v(\d+))?\s*(.*)$/)
1069
+ const h = line.match(/^##\s+(D-(?:[A-Za-z0-9_\-\u4e00-\u9fff]+-)?\d+)(?:@v(\d+))?\s*(.*)$/)
1062
1070
  if (h) {
1063
1071
  flush()
1064
1072
  const version = h[2] ? parseInt(h[2], 10) : 1
1065
- cur = { id: `${h[1]}@v${version}`, title: h[3].trim(), status: null, anchor: null, lastConfirmed: null }
1073
+ cur = { id: `${h[1]}@v${version}`, title: h[3].trim(), status: null, anchor: null, lastConfirmed: null, change: null, originId: null }
1066
1074
  continue
1067
1075
  }
1068
1076
  if (!cur) continue
1069
- const f = line.match(/^(状态|锚点|最近确认)\s*[::]\s*(.*)$/)
1077
+ // 变更/来源/原编号:标签读入(变更:2026-10-14-supersedes-mislink-and-change-display 加法字段——
1078
+ // docs-debt 触碰事实行渲染变更名限定词消歧需读入;来源:2026-10-14-decision-id-p2-hardening legacy
1079
+ // 兜底——变更行无条件覆盖在前、来源仅补缺;原编号:2026-10-14-decision-legacy-renumber——存量迁移
1080
+ // 后条目 id 为全局号,消费仓旧豁免键(decisions.D-002@vN)经原编号继续命中;旧条目缺行容错 null)
1081
+ const f = line.match(/^(状态|锚点|最近确认|变更|来源|原编号)\s*[::]\s*(.*)$/)
1070
1082
  if (!f) continue
1071
1083
  if (f[1] === '状态') cur.status = f[2].trim().split(/[\s((]/)[0] || null
1072
1084
  else if (f[1] === '锚点') cur.anchor = f[2].trim()
1085
+ else if (f[1] === '变更') cur.change = f[2].trim() || null
1086
+ else if (f[1] === '来源') cur.change = cur.change || (f[2].trim() || null)
1087
+ else if (f[1] === '原编号') cur.originId = f[2].trim() || null
1073
1088
  else cur.lastConfirmed = f[2].trim()
1074
1089
  }
1075
1090
  flush()
@@ -1148,8 +1163,8 @@ export async function runDecisionRules(opts) {
1148
1163
  let implementedTotal = 0
1149
1164
  const treeCache = new Map() // resolveCandidates 全树扫描复用(同 docs check 主链路口径)
1150
1165
 
1151
- const emit = (id, domain, file, kind, extra, message) => {
1152
- if (isDecisionExempt(failures, id, kind)) {
1166
+ const emit = (id, domain, file, kind, extra, message, originId = null) => {
1167
+ if (isDecisionExempt(failures, id, kind, domain, originId)) {
1153
1168
  exempted.push({ key: `decisions.${id}.${kind}`, id, kind })
1154
1169
  return
1155
1170
  }
@@ -1169,10 +1184,10 @@ export async function runDecisionRules(opts) {
1169
1184
  // 规则 1:锚点存在性(未记录 → 补录提示;路径不存在 → 失效提示)
1170
1185
  if (!anchorPath) {
1171
1186
  emit(e.id, domain, rel, 'anchor', { anchor: e.anchor || null },
1172
- `「${e.id}」锚点未记录(${rel})——旧格式条目,补「锚点:src/…」后可机械校验(advisory)`)
1187
+ `「${e.id}」锚点未记录(${rel})——旧格式条目,补「锚点:src/…」后可机械校验(advisory)`, e.originId)
1173
1188
  } else if (resolveCandidates(projectRoot, anchorPath, treeCache).length === 0) {
1174
1189
  emit(e.id, domain, rel, 'anchor', { anchor: e.anchor },
1175
- `「${e.id}」锚点文件不存在:${anchorPath}(${rel})——决策落点已漂移,人工复核(advisory)`)
1190
+ `「${e.id}」锚点文件不存在:${anchorPath}(${rel})——决策落点已漂移,人工复核(advisory)`, e.originId)
1176
1191
  }
1177
1192
  // 规则 2:behind 复核(「最近确认」后锚定模块源码前进数超阈值 → 决策待复核;
1178
1193
  // 「最近确认」未记录/域无源码集(无 map 无锚点)→ 无法计算,静默跳过——advisory 不产噪声)
@@ -1185,7 +1200,7 @@ export async function runDecisionRules(opts) {
1185
1200
  const { behind } = computeModuleBehind(domain, e.lastConfirmed, { projectRoot, srcPaths })
1186
1201
  if (behind !== null && behind > threshold) {
1187
1202
  emit(e.id, domain, rel, 'behind', { behind, threshold },
1188
- `「${e.id}」决策待复核:锚定模块 ${domain} 在最近确认 ${e.lastConfirmed} 后源码已前进 ${behind} commit,超阈值 ${threshold}(${rel})——复核后更新「最近确认」`)
1203
+ `「${e.id}」决策待复核:锚定模块 ${domain} 在最近确认 ${e.lastConfirmed} 后源码已前进 ${behind} commit,超阈值 ${threshold}(${rel})——复核后更新「最近确认」`, e.originId)
1189
1204
  }
1190
1205
  }
1191
1206
  }
package/src/docs-debt.js CHANGED
@@ -13,8 +13,9 @@
13
13
  import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs'
14
14
  import { join, relative } from 'node:path'
15
15
  import { safeGit } from './git-helper.js'
16
- import { parseModuleMapSimple } from './modules.js'
16
+ import { modulePathMatches, parseModuleMapSimple } from './modules.js'
17
17
  import { runDocsCheck, readDocsCheckConfig, parseDecisionEntries, anchorFilePath } from './docs-check.js'
18
+ import { decisionChangeQualifier } from './knowledge-match.js'
18
19
 
19
20
  /** safeGit 超时(大仓 git log 慢时降级该模块,不挂起 Wave 渲染) */
20
21
  const GIT_TIMEOUT_MS = 5000
@@ -54,9 +55,9 @@ export function matchFilesToModules(changedFiles, moduleIndex, opts = {}) {
54
55
 
55
56
  for (const f of changedFiles) {
56
57
  let matched = null
57
- // 一级:paths/core_files 前缀或字面量命中
58
+ // 一级:paths/core_files 前缀或字面量命中(含 glob 形态——modulePathMatches 统一入口)
58
59
  for (const [id, ps] of modulePaths) {
59
- if (ps.some(p => p === f || f.startsWith(p.replace(/\/$/, '') + '/') || p.startsWith(f.split('/')[0] + '/') && p === f)) {
60
+ if (ps.some((p) => modulePathMatches(f, p))) {
60
61
  matched = id; break
61
62
  }
62
63
  }
@@ -323,7 +324,7 @@ export function computeDecisionTouches(changedFiles, knowledgeRoot) {
323
324
  for (const c of changed) {
324
325
  // 前缀匹配粒度 = 文件或其子路径(R-03:锚点是文件级非目录级,防同前缀不同文件误报)
325
326
  if (c === anchorPath || c.startsWith(anchorPath + '/')) {
326
- touches.push({ id: e.id, title: e.title, anchorFile: anchorPath, touchedFile: c, file })
327
+ touches.push({ id: e.id, title: e.title, anchorFile: anchorPath, touchedFile: c, file, change: e.change || '' })
327
328
  }
328
329
  }
329
330
  }
@@ -344,7 +345,7 @@ export function renderDecisionTouchFacts(touches) {
344
345
  const shown = touches.slice(0, DECISION_TOUCH_RENDER_LIMIT)
345
346
  const lines = [`[decision-touch] 本次变更触碰 ${touches.length} 条决策锚点(改动可能使该决策需复核):`]
346
347
  for (const t of shown) {
347
- lines.push(` - ${t.id}(${t.title || '无标题'},锚点 ${t.anchorFile})← 触碰文件 ${t.touchedFile}(${t.file})`)
348
+ lines.push(` - ${t.id}(${t.title || '无标题'},锚点 ${t.anchorFile})← 触碰文件 ${t.touchedFile}(${t.file}|${decisionChangeQualifier(t.change)})`)
348
349
  }
349
350
  if (touches.length > DECISION_TOUCH_RENDER_LIMIT) {
350
351
  lines.push(` …另有 ${touches.length - DECISION_TOUCH_RENDER_LIMIT} 条`)
@@ -946,7 +946,9 @@ function detectDecisionDrift(cwd) {
946
946
  let count = 0
947
947
  for (const f of readdirSync(decDir)) {
948
948
  if (!f.endsWith('.md')) continue
949
- count += (readFileSync(join(decDir, f), 'utf8').match(/^## D-\d+@\d+/gm) || []).length
949
+ // 双形态计数(2026-10-14-decision-global-id-epoch):局部 D-011@v1 与全局 D-<域>-042@v1;
950
+ // 旧正则 @\d+ 漏 @v 前缀恒零——随本单修正
951
+ count += (readFileSync(join(decDir, f), 'utf8').match(/^## D-(?:[A-Za-z0-9_\-\u4e00-\u9fff]+-)?\d+@v\d+/gm) || []).length
950
952
  }
951
953
  return { ...base, findings: [`decisions 库 ${count} 条(behind 精确复核在 verify evidence-auto 与 docs-check 决策规则族)`], pass: true, severity: CHECK_SEVERITY.PASSED }
952
954
  } catch (e) {
@@ -19,18 +19,21 @@
19
19
  * 命中模块变更索引机械回写(sidecar 优先、幂等)、start 注入面欠账事实行(提示前移)。
20
20
  */
21
21
  import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'
22
+ import { modulePathMatches } from './modules.js'
22
23
  import { join, relative } from 'node:path'
23
24
  import { createHash } from 'node:crypto'
24
25
  import { execFileSync } from 'node:child_process'
25
26
  import { splitOwnVsForeignDiffFiles, commitAttributionForChange, isForeignByCommit, parseChangeNamesFromSubject } from './foreign-declared.js'
26
27
  import { collectModuleMaps, normalizeMapPath } from './module-resolve.js'
27
28
  import { computeDocsDebt } from './docs-debt.js'
29
+ import { anchorBackfillCandidate } from './decision-distill.js'
28
30
  import { writeAtomicSync } from './fs-atomic.js'
29
31
  import { normalizePath } from './change-list.js'
30
32
  import { readV2SectionAnswer } from './flow-review.js'
31
33
  import { DESIGN_QUESTIONS } from './flow-draft.js'
32
34
  import { pathMatches } from './change-list.js'
33
35
  import { classifyToolScaffold, filterDeliverableFiles } from './worktree-apply.js'
36
+ import { deriveChangeTitle } from './quicklog.js'
34
37
 
35
38
  /**
36
39
  * 模块文档对账(advisory)+ 结构化落盘面(2026-09-27-thin-module-scope-persist)。
@@ -71,7 +74,7 @@ export function reconcileModuleDocs({ cwd, specBase, ownFiles, committedRaw }) {
71
74
  .map((pp) => (map.prefix + normalizeMapPath(pp)).replace(/\/+$/, ''))
72
75
  .filter(Boolean)
73
76
  if (effs.length === 0) continue
74
- const hitFiles = deliverables.filter((f) => effs.some((e) => f === e || f.startsWith(e + '/')))
77
+ const hitFiles = deliverables.filter((f) => effs.some((e) => modulePathMatches(f, e)))
75
78
  if (hitFiles.length === 0) continue
76
79
  hits++
77
80
  const docRel = entry.doc ? `docs/${map.project}/${String(entry.doc).replace(/^\/+/, '')}` : null
@@ -92,7 +95,7 @@ export function reconcileModuleDocs({ cwd, specBase, ownFiles, committedRaw }) {
92
95
  for (const pp of entry.paths || []) allPaths.push((map.prefix + normalizeMapPath(pp)).replace(/\/+$/, ''))
93
96
  }
94
97
  }
95
- const isCovered = (f) => allPaths.some((pp) => pp && (f === pp || f.startsWith(pp + '/')))
98
+ const isCovered = (f) => allPaths.some((pp) => pp && modulePathMatches(f, pp))
96
99
  const uncoveredMap = new Map()
97
100
  for (const f of deliverables) {
98
101
  if (isCovered(f)) continue
@@ -133,6 +136,19 @@ export function evaluateModuleGate({ moduleScope, ignore } = {}) {
133
136
  return { block: maps.length > 0 && dirs.length > 0, dirs, maps }
134
137
  }
135
138
 
139
+ /**
140
+ * ⑥-0 motiveFromInput:模块变更索引摘要提取(2026-10-15-title-label-skip)。与变更标题
141
+ * 同源走 deriveChangeTitle——此前 flow.js 裸取 --input 首行,「动机与背景:」段头行整行
142
+ * 入摘要(stages.changelog 实证)。段头行跳过/标签剥离随 deriveChangeTitle 单源;提取为
143
+ * 空回退变更名(可辨识不达意优于空串;appendChangeIndexEntries 侧另有 '轻量变更' 二级兜底)。
144
+ * @returns {string} 摘要(≤50 字)
145
+ */
146
+ export function motiveFromInput(input, change = '') {
147
+ let derived = ''
148
+ try { derived = deriveChangeTitle(input) } catch { derived = '' }
149
+ return derived || String(change || '')
150
+ }
151
+
136
152
  /**
137
153
  * ⑥ appendChangeIndexEntries:命中模块变更索引机械回写(二档)。零语义判断——日期/变更名/
138
154
  * 动机首行全是已知量,与 decision-distill 降 noAI 同性质。目标判定遵守 split-changelog 契约
@@ -302,6 +318,11 @@ export function harvestSlot4Decision({ changeDir, change }) {
302
318
  answer = readV2SectionAnswer(dText, risksSec.heading, DESIGN_QUESTIONS)
303
319
  }
304
320
  if (!answer || /^不适用/.test(answer)) return { harvested: false, reason: '槽4 空/不适用' }
321
+ // 锚点自动回填(2026-10-15-decision-anchor-backfill 收割端):风险段作答文本恰含一个
322
+ // 路径 token → 写入 - 锚点:行(源 decisions.md 是真相源,蒸馏自然带上、人可改);
323
+ // 0/多候选不写(蒸馏端渲染时还会再兜底一次同款判定)。单一源 anchorBackfillCandidate。
324
+ const anchorCand = anchorBackfillCandidate(answer)
325
+ const anchorLine = anchorCand ? [`- 锚点:${anchorCand}`] : []
305
326
  const text = [
306
327
  '---',
307
328
  `author: flow-machine-draft`,
@@ -309,9 +330,12 @@ export function harvestSlot4Decision({ changeDir, change }) {
309
330
  '---',
310
331
  `# 决策记录(Decisions)— ${change}`,
311
332
  '',
312
- `## D-001@v1: 风险与死路(design 槽4 收割)`,
333
+ // 标题自解释(2026-10-15-harvest-title-clarify):读者面是 decisions 文档与 knowledge,
334
+ // 机制出处(槽4 收割)由 frontmatter author: flow-machine-draft 承载,标题只说内容与性质。
335
+ `## D-001@v1: 设计期风险与放弃方案(底稿)`,
313
336
  `- 类型:process`,
314
337
  `- 状态:confirmed`,
338
+ ...anchorLine,
315
339
  // 类型/状态字段(2026-09-26-slot4-distill-fix):蒸馏链入选要 status∈{confirmed|accepted|rejected}
316
340
  // 且 type∈七类白名单,字段解析只认 `- 字段:值` 列表行形态——此前收割条目零字段致永不入选,
317
341
  // 「随蒸馏链进 knowledge」断链(thin-agent-tasks 教训留档归档而 knowledge 零落地的实证)。
@@ -326,7 +350,7 @@ export function harvestSlot4Decision({ changeDir, change }) {
326
350
  return { harvested: true }
327
351
  }
328
352
 
329
- export default { reconcileModuleDocs, renderVerifyReceipt, harvestSlot4Decision, collectFreezeFiles, backfillGateSummary }
353
+ export default { reconcileModuleDocs, renderVerifyReceipt, harvestSlot4Decision, collectFreezeFiles, backfillGateSummary, motiveFromInput }
330
354
 
331
355
  /**
332
356
  * patch 冻结面的提交面过滤(2026-09-27-tool-debt-cleanup 抽出为纯函数供单测锁定):
package/src/flow.js CHANGED
@@ -224,6 +224,23 @@ export async function extractRoutingInputPaths(cwd, specBase, input) {
224
224
  })
225
225
  }
226
226
 
227
+ /**
228
+ * 轻量道知识注入查询串瘦身(2026-10-15-thin-query-slim):--input 的「成功标准:」段是验收
229
+ * 判据语料(文件路径/测试名/断言词——对知识匹配是纯噪音,且路径词会过触路由关键词把命中面
230
+ * 撑爆:本仓实证整段 input 匹配出 76 条目、58 个 fr 域文件全命中)。查询只取动机段——
231
+ * 「成功标准:」标记前正文;标记缺席=无标准段,全文照旧(兼容旧形态);标记前为空白则退全 input。
232
+ * @param {string} change 变更名(查询首行,既有口径)
233
+ * @param {string} input --input 全文
234
+ * @returns {string} 瘦身后的查询串
235
+ */
236
+ export function thinKnowledgeQuery(change, input) {
237
+ const raw = String(input || '')
238
+ const markerIdx = raw.search(/成功标准[::]/)
239
+ const motivation = markerIdx >= 0 ? raw.slice(0, markerIdx) : raw
240
+ const body = motivation.trim() || raw.trim()
241
+ return `${change || ''}\n${body}`
242
+ }
243
+
227
244
  /**
228
245
  * 轻量道知识注入段(2026-09-25-thin-fr-inject-parity):默认快道读取面对齐——
229
246
  * {FR_INDEX_DIGEST}/{DECISION_HITS}/execute 知识命中报告此前只在 run 族装配,轻量道 fresh
@@ -295,10 +312,11 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
295
312
  }
296
313
  }
297
314
  const { matchKnowledgeHybrid } = await import('./knowledge-vector.js')
298
- const { deathPathNote } = await import('./knowledge-match.js')
315
+ const { deathPathNote, decisionChangeQualifier } = await import('./knowledge-match.js')
299
316
  // scope 遍历召回(2026-10-08-knowledge-graph,FR-04):touched=机器算的图查询键
300
317
  // (filesOverride ∪ design 交付表——上方既有变量),词面三层零命中时沿强边保底防复潮
301
- const km = await matchKnowledgeHybrid(knowledgeRoot, `${change}\n${input || ''}`, { cwd: dirname(specBase), scopeFiles: touched })
318
+ const kmQuery = thinKnowledgeQuery(change, input)
319
+ const km = await matchKnowledgeHybrid(knowledgeRoot, kmQuery, { cwd: dirname(specBase), scopeFiles: touched })
302
320
  // 防复潮面 = 死路注记 ∪ 有主题重叠的 rejected(2026-09-28-knowledge-inject-ranking 起 rejected∪死路;
303
321
  // 2026-09-28-knowledge-gate-denoise 收紧:score 零的 rejected 与查询零主题重叠——空标题条目靠状态
304
322
  // 蹭进回显是行为实测三例的噪音源,不再注入)
@@ -306,20 +324,51 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
306
324
  summary.rejectedDecisions = rejected.length
307
325
  summary.knowledgeEntries = km.matched ? (km.entries || []).length : 0
308
326
  if (rejected.length > 0) {
309
- lines.push(` ⚠️ 否决决策/死路注记(历史已否决或已记死路,防复潮——除非复潮条件满足勿重提,复潮须在本变更 decisions.md 记新版本):`)
327
+ lines.push(` ⚠️ 否决决策/死路注记(历史已否决或已记死路,防复潮——除非复潮条件满足勿重提,复潮须在本变更 decisions.md 记新版本;跨变更引用带变更名 变更名#D-xxx@vN):`)
310
328
  for (const h of rejected.slice(0, 5)) {
311
329
  const note = h.status === 'rejected' ? (h.reason || '(未记录)') : `死路——${deathPathNote(h.reason)}`
312
- lines.push(` - ${h.id} ${h.title}(${h.file})${h.status === 'rejected' ? '否决理由' : '⚰️'}:${note}`)
330
+ lines.push(` - ${h.id} ${h.title}(${h.file}|${decisionChangeQualifier(h.change)})${h.status === 'rejected' ? '否决理由' : '⚰️'}:${note}`)
313
331
  }
314
332
  }
315
333
  if (km.matched && (km.entries || []).length > 0) {
316
334
  const src = km.entries.slice(0, 3).map((e) => (e.anchor ? `${e.file}#${e.anchor}` : e.file)).join(';')
317
335
  lines.push(` 📚 知识命中(INDEX 关键词,按需 Read):${src}`)
318
336
  }
337
+ // 遥测导线(2026-10-15-thin-inject-telemetry):轻量道注入此前不落 hits.jsonl——thin 常态化后
338
+ // inject 事件流断流(本仓 10-09 后归零实证),knowledge stats 从未命中判定被盲区污染。
339
+ // 分级字段(A2-lite):injectedFiles=正文实际注入(thin 面恒空——只出按需 Read 行)、
340
+ // mentionedFiles=显式展示给 agent 的锚点(📚 行 top-3)。既有 matchedFiles 口径不变
341
+ // (knowledge-stats 消费面零回归)。仅命中面非空才落盘;fail-soft(R-04)不阻断注入。
342
+ const thinMatchedFiles = km.matched ? (km.entries || []).map((e) => (e.anchor ? `${e.file}#${e.anchor}` : e.file)) : []
343
+ if (thinMatchedFiles.length > 0 || rejected.length > 0) {
344
+ try {
345
+ const { appendKnowledgeHit } = await import('./knowledge-hits.js')
346
+ appendKnowledgeHit(join(specBase, '.runtime'), {
347
+ type: 'inject',
348
+ change,
349
+ query: kmQuery,
350
+ matchedFiles: thinMatchedFiles,
351
+ injectedFiles: [],
352
+ mentionedFiles: thinMatchedFiles.slice(0, 3),
353
+ rejectedCount: rejected.length,
354
+ source: 'thin-start',
355
+ })
356
+ } catch { /* 遥测 fail-soft(R-04):hits 落盘失败不影响注入本体 */ }
357
+ }
319
358
  if (domains.length === 0 && rejected.length === 0 && !km.matched) {
320
359
  lines.length = 0
321
360
  lines.push(`🧠 知识注入(轻量道读取面):语料未命中知识库(无触达域依据、无否决命中、INDEX 无对齐条目)——可自行查 knowledge/INDEX.md。`)
322
361
  }
362
+ // 向量开关发现性提示(2026-10-15-vector-switch-discoverability):零命中=正需要语义召回
363
+ // 而未获得的时刻——平台已连接但开关未开(缺省关)时给一行可执行提示;有命中/未连接/
364
+ // 已开启零新增(vectorSwitchHint 单源,同款提示也挂 knowledge search hint 键)。
365
+ if (!km.matched && rejected.length === 0) {
366
+ try {
367
+ const { vectorSwitchHint } = await import('./knowledge-vector.js')
368
+ const hint = vectorSwitchHint(dirname(specBase))
369
+ if (hint) lines.push(` ${hint}`)
370
+ } catch { /* 提示 fail-open */ }
371
+ }
323
372
  // 模块卡欠账提示前移(2026-10-12-thin-module-gate-and-feed 三档):触碰命中模块 behind>0
324
373
  // 才出事实行——动手前看到「你要动的卡已落后」比收口时警告有效;computeDocsDebt(厚道
325
374
  // execute {DOCS_DEBT})同源,空债/无图零输出,异常走外层 catch fail-open 不阻断 start。
@@ -1089,7 +1138,7 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
1089
1138
  ` ②③断点 agent 自主跳过。`,
1090
1139
  ` 随时可查进度:sillyspec flow status --change ${change}`,
1091
1140
  ...(detectUiTouch(input)
1092
- ? [...buildUiGuidanceLines(), '']
1141
+ ? [...buildUiGuidanceLines(specBase), '']
1093
1142
  : []),
1094
1143
  ``,
1095
1144
  `【协议调用 2/2(干完后)】sillyspec flow done --change ${change}`,
@@ -1425,11 +1474,22 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1425
1474
  // 采样去重(2026-10-07-thin-tasks-v3):task tick 直写的精确事件(source:'task-tick')为
1426
1475
  // 权威口径,watcher 3s 采样合并跳被去重——CLI 快速连续 tick 不再误伤;观测旁路缺席
1427
1476
  // (无流/读失败)→ 跳过(fail-open,watcher 非真相源);--allow-batch-tick 显式旁路留痕。
1477
+ // 侧车突发回退(2026-10-14-tick-burst-sidecar-gate):CLI 收口端快连点对事件流不可见(单格
1478
+ // 精确跳),空结果时回退 task-ticks.jsonl 毫秒时戳判突发——CLI 与直接 Edit 两条路径同判。
1428
1479
  try {
1429
1480
  const { readWatcherEvents } = await import('./watcher.js')
1430
- const { detectBatchCheckCadence, resolveBatchTickAction } = await import('./sentinel-assertions.js')
1481
+ const { detectBatchCheckCadence, detectTickBurst, resolveBatchTickAction } = await import('./sentinel-assertions.js')
1431
1482
  const stream = readWatcherEvents({ runtimeRoot, change })
1432
- const batch = stream.exists ? detectBatchCheckCadence(stream.events) : null
1483
+ let batch = stream.exists ? detectBatchCheckCadence(stream.events) : null
1484
+ if (!batch) {
1485
+ // 侧车突发回退(2026-10-14-tick-burst-sidecar-gate):事件流空结果时读 change 目录
1486
+ // task-ticks.jsonl(CLI tick 毫秒时戳事实源,随归档进 git、不受观测盲窗影响)判快连点;
1487
+ // 事件流结果优先不双重报告;侧车缺失/坏文件按无证据静默(fail-open)。
1488
+ try {
1489
+ const { parseTickLog } = await import('./timeline.js')
1490
+ batch = detectTickBurst(parseTickLog(readFileSync(join(changeDir, 'task-ticks.jsonl'), 'utf8')))
1491
+ } catch { /* 侧车缺失/不可读 → 无侧车证据(fail-open) */ }
1492
+ }
1433
1493
  if (batch) {
1434
1494
  // 代勾计数取「本拍实时代勾 ∨ 持久化代勾」较大者(二轮 P2-B:重入漂移防护);决策纯函数
1435
1495
  // 单源(resolveBatchTickAction)四态与豁免优先序见其 JSDoc——代勾须解释整跳才降级
@@ -1454,7 +1514,8 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1454
1514
  console.warn(`⚠️ 勾选节奏:单拍多格勾选(${batch.detail},${at})——${why},降级提醒不拒`)
1455
1515
  }
1456
1516
  } else if (v.action === 'reject') {
1457
- console.error(`🚫 单拍勾选拒收:事件流观测到一拍勾选 ${batch.detail}(${at})——勾选纪律要求逐格(做一件→勾一格→下一个),勾选任务 ${_sentinelNonMirrorTasks} 格`)
1517
+ const _via = batch.source === 'tick-burst' ? '侧车时戳观测到' : '事件流观测到'
1518
+ console.error(`🚫 单拍勾选拒收:${_via}一拍勾选 ${batch.detail}(${at})——勾选纪律要求逐格(做一件→勾一格→下一个),勾选任务 ${_sentinelNonMirrorTasks} 格`)
1458
1519
  console.error(' 出口:①节奏违例已既成——认知后重跑带 --allow-batch-tick 显式留痕过门(平台时间线可见旁路);②疑观测误判→sillyspec doctor 核对事件流')
1459
1520
  appendTelemetry({ sentinel: 'batch-tick', jump: batch.detail })
1460
1521
  reportMidFail('ledger')
@@ -2272,8 +2333,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2272
2333
  // 回读块保证在场(patch 本轮算出或 change-patch.json 回读)。
2273
2334
  try {
2274
2335
  if (moduleScope && Array.isArray(moduleScope.modules) && moduleScope.modules.length > 0) {
2275
- const { appendChangeIndexEntries } = await import('./flow-parity.js')
2276
- const motive = String(st.input || '').split('\n').map((s) => s.trim()).find(Boolean) || ''
2336
+ const { appendChangeIndexEntries, motiveFromInput } = await import('./flow-parity.js')
2337
+ // 摘要与变更标题同源(2026-10-15-title-label-skip):段头行(动机与背景:等)跳过取次行,
2338
+ // 空提取回退变更名——此前裸取首行致「动机与背景:」整行入摘要(stages.changelog 实证)。
2339
+ const motive = motiveFromInput(st.input, change)
2277
2340
  const wr = appendChangeIndexEntries({ specBase, modules: moduleScope.modules, change, summary: motive, date: new Date().toISOString().slice(0, 10) })
2278
2341
  changelogAppended = wr.appended
2279
2342
  if (wr.appended.length > 0) console.log(`📖 模块变更索引回写:${wr.appended.length} 个目标(${wr.appended.join('、')})——sidecar 优先/卡内节次之,随归档窄化入暂存`)
@@ -2600,7 +2663,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
2600
2663
  console.error(`❌ ${e.message}`)
2601
2664
  // 可照抄实例(2026-10-05-input-teach-copyable):引号内换行合法,行级 trim 容忍缩进
2602
2665
  console.error(' 重试(--input 引号内换行合法,可照抄):')
2603
- console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "<动机与背景>')
2666
+ console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "<一句中文概括(首行即变更标题);随后动机与背景')
2604
2667
  console.error('')
2605
2668
  console.error(' 成功标准:')
2606
2669
  console.error(' - <可验证标准>"')
@@ -2829,9 +2892,9 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
2829
2892
  console.log(`✅ flow amend-draft 留痕重锚:${r.reanchored.join('、')}——ledger amendment 审计在案;editRatio=${maxRatio}(段级最大;基准=首版原文,AGENT 槽不计入)`)
2830
2893
  return r
2831
2894
  }
2832
- console.error('用法: sillyspec flow start --change <名> --input "<动机与背景+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] [--no-prestage(hindsight 标记在场时声明需求已含决策,留痕放行)] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json] | sillyspec flow diff --change <名> [--json](只读文件统计)')
2895
+ console.error('用法: sillyspec flow start --change <名> --input "<一句中文概括(首行即变更标题)+动机与背景详述+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] [--no-prestage(hindsight 标记在场时声明需求已含决策,留痕放行)] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json] | sillyspec flow diff --change <名> [--json](只读文件统计)')
2833
2896
  console.error(' --input 可照抄形态(引号内换行合法):')
2834
- console.error(' sillyspec flow start --change <名> --input "<动机与背景>')
2897
+ console.error(' sillyspec flow start --change <名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
2835
2898
  console.error('')
2836
2899
  console.error(' 成功标准:')
2837
2900
  console.error(' - <可验证标准>"')