sillyspec 3.32.10 → 3.33.1

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 (197) 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 +66 -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/archive-delta.js +4 -3
  126. package/src/ceremony-tier.js +2 -1
  127. package/src/change-delete.js +6 -5
  128. package/src/contract-matrix.js +2 -1
  129. package/src/cross-placement.js +5 -1
  130. package/src/db.js +8 -2
  131. package/src/decision-distill.js +29 -3
  132. package/src/decision-redomain.js +148 -0
  133. package/src/deleted-ledger.js +2 -1
  134. package/src/design-facts.js +3 -3
  135. package/src/docs-debt.js +3 -3
  136. package/src/doctor-diagnostics.js +8 -3
  137. package/src/flow-draft.js +2 -1
  138. package/src/flow-parity.js +10 -2
  139. package/src/flow.js +112 -63
  140. package/src/forensics.js +236 -0
  141. package/src/fr-index.js +2 -1
  142. package/src/friction-ledger.js +2 -1
  143. package/src/friction-tally.js +8 -4
  144. package/src/handoff.js +2 -1
  145. package/src/hooks/worktree-guard.js +5 -5
  146. package/src/index.js +75 -8
  147. package/src/knowledge-graph.js +3 -4
  148. package/src/knowledge-hits.js +2 -1
  149. package/src/knowledge-vector.js +2 -1
  150. package/src/module-impact.js +2 -5
  151. package/src/modules.js +31 -0
  152. package/src/progress/consistency-doctor.js +5 -2
  153. package/src/progress/stage-machine.js +6 -2
  154. package/src/progress.js +19 -8
  155. package/src/quicklog.js +194 -9
  156. package/src/review-dispatch.js +4 -3
  157. package/src/review-tier.js +2 -1
  158. package/src/route-hindsight.js +4 -3
  159. package/src/run/bg-sync.js +10 -3
  160. package/src/run/command.js +13 -11
  161. package/src/run/complete-handlers.js +40 -37
  162. package/src/run/complete.js +12 -8
  163. package/src/run/gate-snapshot-ledger.js +2 -1
  164. package/src/run/gate-snapshot.js +57 -11
  165. package/src/run/gates.js +38 -21
  166. package/src/run/lint-ownership.js +37 -0
  167. package/src/run/prompt.js +46 -30
  168. package/src/run/scan-profile.js +2 -1
  169. package/src/run/shared.js +109 -5
  170. package/src/run/stage.js +5 -4
  171. package/src/run/test-ledger.js +2 -1
  172. package/src/run/verify-quality-scan.js +21 -6
  173. package/src/runtime-paths.js +189 -0
  174. package/src/scan-refresh.js +4 -3
  175. package/src/scope-audit.js +7 -6
  176. package/src/sentinel-assertions.js +37 -0
  177. package/src/spec-sync.js +118 -23
  178. package/src/stage-contract.js +24 -2
  179. package/src/stage-review.js +5 -3
  180. package/src/stages/brainstorm.js +72 -21
  181. package/src/stages/plan-postcheck.js +13 -2
  182. package/src/stages/plan.js +19 -0
  183. package/src/sync-noise.js +3 -2
  184. package/src/sync.js +71 -15
  185. package/src/task-done.js +2 -1
  186. package/src/task-review.js +7 -5
  187. package/src/task-tick.js +23 -1
  188. package/src/test-bindings.js +32 -3
  189. package/src/ui-visual.js +119 -4
  190. package/src/validate-artifacts.js +2 -1
  191. package/src/verify-draft.js +2 -1
  192. package/src/verify-postcheck.js +128 -27
  193. package/src/verify-probes.js +157 -52
  194. package/src/watcher.js +15 -9
  195. package/src/worktree-apply.js +28 -3
  196. package/src/write-audit.js +2 -1
  197. package/templates/agents-instruction.md +1 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * decision-redomain.js — 决策条目跨域迁移(2026-10-15-decision-redomain)
3
+ *
4
+ * 背景:模块路径 glob 匹配失效(2026-10-15-module-path-glob-match)导致消费仓历史决策
5
+ * 大量落 decisions/unmapped.md(平台仓 442 条实证)——匹配器修复止增量后,存量需机器
6
+ * 通道清偿。redomain.js 只管 fr/ 条目;本模块为 decisions/ 面的孪生实现,复用
7
+ * decision-distill 参数化底座(splitKnowledgeSections/joinKnowledgeFile/syncIndexRoutingLines)。
8
+ *
9
+ * 与 FR 版的形态差异:
10
+ * - 段头 `## D-xxx@vN 标题`(全局号/局部号双形态混存)——默认 sectionRegex 即此格式;
11
+ * id = `<number>@v<version>`(迁域不换号,字段行内「原编号」等机器行随段整体搬)。
12
+ * - 跨变更同号合法共存(D 号是变更内局部序号)——冲突键 = id + 变更(目标域已有
13
+ * 同 id 同变更段 = 迁移语义不明,跳该段并报告,不覆盖)。
14
+ * - 路由行形态与 fr 不同(`- <域>|decision|决策 → [decisions/<域>.md]`)——syncIndexRoutingLines
15
+ * 默认参数即 decisions 面。
16
+ *
17
+ * 幂等:重跑同批——源域已无该段 → 0 条(或 anchor/byChange 无命中报错,与 FR 版同款)。
18
+ */
19
+ import { existsSync, readFileSync, unlinkSync } from 'fs'
20
+ import { join } from 'path'
21
+ import { splitKnowledgeSections, joinKnowledgeFile, syncIndexRoutingLines } from './decision-distill.js'
22
+ import { writeAtomicSync } from './fs-atomic.js'
23
+
24
+ const DECISION_HEADER_RE = /^## (D-(?:[A-Za-z0-9_\-\u4e00-\u9fff]+-)?\d+)@v(\d+)\s*(.*)$/
25
+
26
+ /** 目标域文件头(与 decision-distill 落域文件既有头逐字同款——平台真实库实测形态) */
27
+ function newDomainPreamble(domain) {
28
+ return [
29
+ `# 决策知识 — ${domain}`,
30
+ '',
31
+ '> decision-distill 从变更 decisions.md 幂等提炼(「最近确认」= 归档时 HEAD)。条目字段行为 docs-check 机械解析契约,勿手改。',
32
+ '',
33
+ ]
34
+ }
35
+
36
+ /** 段 → {id, number, version, title, change}(id=number@version 即迁域不换号的身份) */
37
+ function sectionMeta(s) {
38
+ const m = DECISION_HEADER_RE.exec(s.lines[0] || '')
39
+ if (!m) return null
40
+ const id = `${m[1]}@v${m[2]}`
41
+ const cm = s.lines.find((l) => /^变更[::]/.test(l)) || s.lines.find((l) => /^来源[::]/.test(l))
42
+ const change = cm ? cm.replace(/^(变更|来源)[::]\s*/, '').trim() || null : null
43
+ return { id, number: m[1], version: parseInt(m[2], 10), title: (m[3] || '').trim(), change }
44
+ }
45
+
46
+ function normalizeDomainArg(v) {
47
+ return String(v ?? '').trim().replace(/^decisions\//i, '').replace(/\.md$/i, '').trim()
48
+ }
49
+
50
+ /** 干跑预览:列出将迁移条目,不落盘(anchor/byChange 同执行侧过滤) */
51
+ export function planDecisionRedomain({ knowledgeRoot, from, to, anchors = null, byChange = null }) {
52
+ from = normalizeDomainArg(from)
53
+ to = normalizeDomainArg(to)
54
+ if (!from || !to) throw new Error('用法:--from <源域> --to <目标域>(域名可带 decisions/ 前缀与 .md 后缀)')
55
+ if (from === to) throw new Error('源域与目标域相同')
56
+ const dir = join(knowledgeRoot, 'decisions')
57
+ const fromPath = join(dir, `${from}.md`)
58
+ if (!existsSync(fromPath)) throw new Error(`源域文件不存在:decisions/${from}.md`)
59
+ const parsed = splitKnowledgeSections(readFileSync(fromPath, 'utf8'))
60
+ const anchorSet = Array.isArray(anchors) && anchors.length > 0 ? new Set(anchors) : null
61
+ const entries = []
62
+ for (const s of parsed.sections) {
63
+ const meta = sectionMeta(s)
64
+ if (!meta) continue
65
+ if (anchorSet && !anchorSet.has(meta.id)) continue
66
+ if (byChange && meta.change !== byChange) continue
67
+ entries.push(meta)
68
+ }
69
+ if (entries.length === 0) {
70
+ throw new Error(anchorSet
71
+ ? `anchor 未命中(${[...anchorSet].join('、')} 不在 decisions/${from}.md)`
72
+ : byChange ? `「变更:${byChange}」条目在 decisions/${from}.md 无命中` : `decisions/${from}.md 无决策条目段`)
73
+ }
74
+ return {
75
+ entries,
76
+ targetExists: existsSync(join(dir, `${to}.md`)),
77
+ sourceWillDelete: parsed.sections.length - entries.length === 0,
78
+ }
79
+ }
80
+
81
+ /** 执行迁移(纯文件手术——段字节整体搬运,id/机器行零改写) */
82
+ export function redomainDecisionEntries({ knowledgeRoot, from, to, anchors = null, byChange = null }) {
83
+ from = normalizeDomainArg(from)
84
+ to = normalizeDomainArg(to)
85
+ if (!from || !to) throw new Error('用法:--from <源域> --to <目标域>')
86
+ if (from === to) throw new Error('源域与目标域相同')
87
+ const dir = join(knowledgeRoot, 'decisions')
88
+ const fromPath = join(dir, `${from}.md`)
89
+ const toPath = join(dir, `${to}.md`)
90
+ if (!existsSync(fromPath)) throw new Error(`源域文件不存在:decisions/${from}.md`)
91
+
92
+ const fromParsed = splitKnowledgeSections(readFileSync(fromPath, 'utf8'))
93
+ const anchorSet = Array.isArray(anchors) && anchors.length > 0 ? new Set(anchors) : null
94
+ const targetCreated = !existsSync(toPath)
95
+ const toParsed = targetCreated
96
+ ? { preamble: newDomainPreamble(to), sections: [] }
97
+ : splitKnowledgeSections(readFileSync(toPath, 'utf8'))
98
+
99
+ const moving = []
100
+ const staying = []
101
+ const skippedConflicts = []
102
+ const movedKeys = new Set() // 批内双键(id|change):同批重复段第二条跳过(2026-10-15 平台存量实证幂等残留)
103
+ for (const s of fromParsed.sections) {
104
+ const meta = sectionMeta(s)
105
+ if (!meta) { staying.push(s); continue } // 非条目段(说明/杂项)留守
106
+ if (anchorSet && !anchorSet.has(meta.id)) { staying.push(s); continue }
107
+ if (byChange && meta.change !== byChange) { staying.push(s); continue }
108
+ // 冲突防线:目标域已有同 id + 同变更段(跨变更同号合法共存,双键才冲突)
109
+ const key = `${meta.id}|${meta.change || ''}`
110
+ const clash = movedKeys.has(key) || toParsed.sections.some((t) => {
111
+ const tm = sectionMeta(t)
112
+ return tm && `${tm.id}|${tm.change || ''}` === key
113
+ })
114
+ if (clash) { skippedConflicts.push(meta.id); staying.push(s); continue }
115
+ movedKeys.add(key)
116
+ moving.push({ meta, section: s })
117
+ }
118
+ if (moving.length === 0 && skippedConflicts.length === 0) {
119
+ throw new Error(anchorSet
120
+ ? `anchor 未命中(${[...anchorSet].join('、')} 不在 decisions/${from}.md)`
121
+ : byChange ? `「变更:${byChange}」条目在 decisions/${from}.md 无命中` : `decisions/${from}.md 无决策条目段`)
122
+ }
123
+ if (anchorSet && skippedConflicts.length + moving.length !== anchorSet.size) {
124
+ const got = new Set([...moving.map((m) => m.meta.id), ...skippedConflicts])
125
+ const miss = [...anchorSet].filter((a) => !got.has(a))
126
+ if (miss.length > 0) throw new Error(`anchor 未命中:${miss.join('、')}(不在 decisions/${from}.md)`)
127
+ }
128
+
129
+ toParsed.sections.push(...moving.map((m) => m.section))
130
+ writeAtomicSync(toPath, joinKnowledgeFile(toParsed.preamble, toParsed.sections))
131
+ const sourceDeleted = staying.length === 0
132
+ if (sourceDeleted) unlinkSync(fromPath)
133
+ else writeAtomicSync(fromPath, joinKnowledgeFile(fromParsed.preamble, staying))
134
+
135
+ // INDEX 路由行:decisions 面默认参数(幂等;缺席行补上)
136
+ const indexPath = join(knowledgeRoot, 'INDEX.md')
137
+ const before = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : ''
138
+ try { syncIndexRoutingLines(knowledgeRoot) } catch { /* INDEX 缺席/同步失败不拦迁移 */ }
139
+ const after = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : ''
140
+
141
+ return {
142
+ moved: moving.map((m) => ({ id: m.meta.id, title: m.meta.title })),
143
+ skippedConflicts,
144
+ sourceDeleted,
145
+ targetCreated,
146
+ indexLineAdded: after !== before,
147
+ }
148
+ }
@@ -14,12 +14,13 @@
14
14
  * 坏行容错:单行 JSON 解析失败跳过该行(外部编辑/半写截断不拖垮读取)。
15
15
  */
16
16
  import { appendFileSync, existsSync, readFileSync } from 'node:fs'
17
+ import { runtimeStatePath } from './runtime-paths.js'
17
18
  import { join } from 'node:path'
18
19
 
19
20
  const LEDGER_FILENAME = 'deleted-changes.jsonl'
20
21
 
21
22
  function ledgerPath(specDir) {
22
- return join(specDir, '.runtime', LEDGER_FILENAME)
23
+ return runtimeStatePath(join(specDir, '.runtime'), LEDGER_FILENAME)
23
24
  }
24
25
 
25
26
  /**
@@ -26,7 +26,7 @@ import { join, relative } from 'node:path'
26
26
  import { parseDecisions } from './decision-distill.js'
27
27
  import { parseFileChangeListDetailed } from './change-list.js'
28
28
  import { parseDesignCoverageByRepo, parseRepoRegistry } from './stages/plan-postcheck.js'
29
- import { parseModuleMapSimple } from './modules.js'
29
+ import { modulePathMatches, parseModuleMapSimple } from './modules.js'
30
30
 
31
31
  // ---------------------------------------------------------------------------
32
32
  // 条目解析(distill parseDecisions 的同源镜像,R-01)
@@ -173,7 +173,7 @@ export function deriveActualModules(filePaths, prefixPairs) {
173
173
  const t = String(f || '').replace(/\\/g, '/').replace(/\/+$/, '')
174
174
  if (!t) continue
175
175
  for (const { id, path: p } of prefixPairs) {
176
- if (t === p || t.startsWith(p + '/')) { actual.add(id); break }
176
+ if (modulePathMatches(t, p)) { actual.add(id); break }
177
177
  }
178
178
  }
179
179
  return actual
@@ -387,7 +387,7 @@ export function generateDesignSkeleton({ changeName, decisionsText, author, now
387
387
  lines.push('- [ ] frontmatter 字段齐全(author/created_at/scale)')
388
388
  lines.push('- [ ] 引用所有当前版本 D-xxx@vN')
389
389
  lines.push('- [ ] 涉及生命周期关键词时含「生命周期契约表」或紧邻豁免短语')
390
- lines.push('- [ ] UI 原型分级核对(涉前端文件时变更目录应有 prototype-*.html——新生成(定稿=与实现同方言的真码产物,手绘仅限粗选)或登记复用(复用须按 prototype-<名称>.html 命名,execute 才注入)或跳过原因记入风险登记)')
390
+ lines.push('- [ ] UI 原型分级核对(涉前端文件时变更目录应有 prototype-*.html——新生成(定稿=与实现同方言的真码产物,手绘仅限粗选,输出须带素材来源声明:token 源/组件来源/入口位置)或登记复用(复用须按 prototype-<名称>.html 命名,execute 才注入)或跳过原因记入风险登记;本仓形态未在 local.yaml ui_prototype 登记的,收口前补登记)')
391
391
  lines.push('- [ ] 不确定的问题标注「⚠️ 自审存疑」')
392
392
  lines.push('')
393
393
 
package/src/docs-debt.js CHANGED
@@ -13,7 +13,7 @@
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
18
  import { decisionChangeQualifier } from './knowledge-match.js'
19
19
 
@@ -55,9 +55,9 @@ export function matchFilesToModules(changedFiles, moduleIndex, opts = {}) {
55
55
 
56
56
  for (const f of changedFiles) {
57
57
  let matched = null
58
- // 一级:paths/core_files 前缀或字面量命中
58
+ // 一级:paths/core_files 前缀或字面量命中(含 glob 形态——modulePathMatches 统一入口)
59
59
  for (const [id, ps] of modulePaths) {
60
- 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))) {
61
61
  matched = id; break
62
62
  }
63
63
  }
@@ -23,6 +23,7 @@
23
23
  * writer 落盘到 <authoritySpecDir>/.runtime/。
24
24
  */
25
25
  import { openDatabase, pluckGet, pluckAll } from './db-engine.js';
26
+ import { runtimeTempPath } from './runtime-paths.js'
26
27
  import { safeGit } from './git-helper.js';
27
28
  import { createHash } from 'crypto';
28
29
  import { existsSync, statSync, readFileSync, readdirSync, mkdirSync, writeFileSync, unlinkSync, rmSync } from 'fs';
@@ -1109,12 +1110,15 @@ export function detectSelfMaintenanceTax(cwd, authoritySpecDir) {
1109
1110
  const warnings = [];
1110
1111
 
1111
1112
  // ① 活跃非零 tally 列示(events:{type:{count,lastAt}},X-10 结构)
1113
+ // 2026-10-18 布局分层:tally 落 state/;根目录扫描保留旧布局兜底(入口迁移收敛后恒空)
1112
1114
  try {
1113
- for (const f of readdirSync(runtimeRoot)) {
1115
+ const tallyDirs = [join(runtimeRoot, 'state'), runtimeRoot]
1116
+ for (const tallyDir of tallyDirs) {
1117
+ for (const f of (() => { try { return readdirSync(tallyDir) } catch { return [] } })()) {
1114
1118
  const m = f.match(/^friction-tally-(.+)\.json$/);
1115
1119
  if (!m) continue;
1116
1120
  let tally = null;
1117
- try { tally = JSON.parse(readFileSync(join(runtimeRoot, f), 'utf8')); } catch { continue; }
1121
+ try { tally = JSON.parse(readFileSync(join(tallyDir, f), 'utf8')); } catch { continue; }
1118
1122
  const events = tally && typeof tally === 'object' && tally.events && typeof tally.events === 'object' && !Array.isArray(tally.events) ? tally.events : {};
1119
1123
  const segs = [];
1120
1124
  let total = 0;
@@ -1131,6 +1135,7 @@ export function detectSelfMaintenanceTax(cwd, authoritySpecDir) {
1131
1135
  findings.push(`活跃摩擦:${m[1]}(${segs.join('、')}${lastAt ? `,最近 ${lastAt}` : ''})`);
1132
1136
  if (total >= TAX_HEAVY_TOTAL) warnings.push(`税重——评估该机制群简化/退役(对照其 decisions 退役判据):${m[1]}(活跃 total ${total})`);
1133
1137
  }
1138
+ }
1134
1139
  } catch { /* runtimeRoot 不可读 → 无活跃数据,不误报 */ }
1135
1140
 
1136
1141
  // ② 台账聚合(近 90 天 top5 + 累计总量);④ 缺失渲染提示不告警
@@ -1748,7 +1753,7 @@ export function writeDoctorDiagnosis(json, specDir) {
1748
1753
  try {
1749
1754
  const runtimeDir = join(specDir, '.runtime');
1750
1755
  mkdirSync(runtimeDir, { recursive: true });
1751
- const outPath = join(runtimeDir, 'doctor-diagnosis.json');
1756
+ const outPath = runtimeTempPath(runtimeDir, 'doctor-diagnosis.json');
1752
1757
  writeFileSync(outPath, JSON.stringify(json, null, 2) + '\n');
1753
1758
  return outPath;
1754
1759
  } catch (e) {
package/src/flow-draft.js CHANGED
@@ -23,6 +23,7 @@ import { wrapSection, verifyMarkers, reanchorText, bodyHash, parseMarkerBlocks }
23
23
  // 占位句常量真同源(2026-10-03-fr-skeleton-gate 评审 P3 清偿):起草端 Then 兜底与骨架判据
24
24
  // (fr-index.isThinSkeletonBodies)共用同一字面量——两处独立字符串会被评审实证为「镜像非同源」。
25
25
  import { SKELETON_THEN_PLACEHOLDER } from './fr-index.js'
26
+ import { runtimeStatePath } from './runtime-paths.js'
26
27
 
27
28
  /** 读文件并归一化 CRLF→LF(2026-09-25-feedback-fixes④:Python/编辑器写盘 CRLF 会破坏
28
29
  锚定的槽位识别 regex)。 */
@@ -88,7 +89,7 @@ const QUESTION_REPAIR_SIM_THRESHOLD = 0.5
88
89
 
89
90
  /** sidecar 台账路径(.runtime 下,verify-draft sidecar 同族;命名空间=文件:段键)。 */
90
91
  export function draftLedgerPath(runtimeRoot, changeName) {
91
- return join(runtimeRoot, `draft-ledger-${changeName}.json`)
92
+ return runtimeStatePath(runtimeRoot, `draft-ledger-${changeName}.json`)
92
93
  }
93
94
 
94
95
  const AGENT_SLOT = (n, hint) => `<!--AGENT:槽${n} ${hint}——例外裁决书写面(机器段之外合法) -->`
@@ -19,12 +19,14 @@
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'
@@ -72,7 +74,7 @@ export function reconcileModuleDocs({ cwd, specBase, ownFiles, committedRaw }) {
72
74
  .map((pp) => (map.prefix + normalizeMapPath(pp)).replace(/\/+$/, ''))
73
75
  .filter(Boolean)
74
76
  if (effs.length === 0) continue
75
- 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)))
76
78
  if (hitFiles.length === 0) continue
77
79
  hits++
78
80
  const docRel = entry.doc ? `docs/${map.project}/${String(entry.doc).replace(/^\/+/, '')}` : null
@@ -93,7 +95,7 @@ export function reconcileModuleDocs({ cwd, specBase, ownFiles, committedRaw }) {
93
95
  for (const pp of entry.paths || []) allPaths.push((map.prefix + normalizeMapPath(pp)).replace(/\/+$/, ''))
94
96
  }
95
97
  }
96
- const isCovered = (f) => allPaths.some((pp) => pp && (f === pp || f.startsWith(pp + '/')))
98
+ const isCovered = (f) => allPaths.some((pp) => pp && modulePathMatches(f, pp))
97
99
  const uncoveredMap = new Map()
98
100
  for (const f of deliverables) {
99
101
  if (isCovered(f)) continue
@@ -316,6 +318,11 @@ export function harvestSlot4Decision({ changeDir, change }) {
316
318
  answer = readV2SectionAnswer(dText, risksSec.heading, DESIGN_QUESTIONS)
317
319
  }
318
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}`] : []
319
326
  const text = [
320
327
  '---',
321
328
  `author: flow-machine-draft`,
@@ -328,6 +335,7 @@ export function harvestSlot4Decision({ changeDir, change }) {
328
335
  `## D-001@v1: 设计期风险与放弃方案(底稿)`,
329
336
  `- 类型:process`,
330
337
  `- 状态:confirmed`,
338
+ ...anchorLine,
331
339
  // 类型/状态字段(2026-09-26-slot4-distill-fix):蒸馏链入选要 status∈{confirmed|accepted|rejected}
332
340
  // 且 type∈七类白名单,字段解析只认 `- 字段:值` 列表行形态——此前收割条目零字段致永不入选,
333
341
  // 「随蒸馏链进 knowledge」断链(thin-agent-tasks 教训留档归档而 knowledge 零落地的实证)。