sillyspec 3.27.2 → 3.27.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +8 -0
  2. package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
  3. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  4. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  5. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  6. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  7. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  8. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  9. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  10. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  11. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
  12. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
  13. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  14. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  15. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  16. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  17. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  18. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
  19. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
  20. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  21. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  22. package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
  23. package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
  24. package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
  25. package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
  26. package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
  27. package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
  28. package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
  29. package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
  30. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  31. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  32. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
  33. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
  34. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
  35. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
  36. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
  37. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
  38. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
  39. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
  40. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
  41. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
  42. package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
  43. package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
  44. package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
  45. package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
  46. package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
  47. package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
  48. package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
  49. package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
  50. package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
  51. package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
  52. package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
  53. package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
  54. package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
  55. package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
  56. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
  57. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
  58. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
  59. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
  60. package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
  61. package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
  62. package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
  63. package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
  64. package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
  65. package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
  66. package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
  67. package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
  68. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
  69. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
  70. package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
  71. package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
  72. package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
  73. package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
  74. package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
  75. package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
  76. package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
  77. package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
  78. package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
  79. package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
  80. package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
  81. package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
  82. package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
  83. package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
  84. package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
  85. package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
  86. package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
  87. package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
  88. package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
  89. package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
  90. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
  91. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
  92. package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
  93. package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
  94. package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
  95. package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
  96. package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
  97. package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
  98. package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
  99. package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
  100. package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
  101. package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
  102. package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
  103. package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
  104. package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
  105. package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
  106. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
  107. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
  108. package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
  109. package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
  110. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
  111. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
  112. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
  113. package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
  114. package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
  115. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
  116. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
  117. package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
  118. package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
  119. package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
  120. package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
  121. package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
  122. package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
  123. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
  124. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
  125. package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
  126. package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
  127. package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
  128. package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
  129. package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
  130. package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
  131. package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
  132. package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
  133. package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
  134. package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
  135. package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
  136. package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
  137. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
  138. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
  139. package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
  140. package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
  141. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
  142. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
  143. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
  144. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
  145. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
  146. package/eval/README.md +86 -0
  147. package/eval/eval.config.json +28 -0
  148. package/eval/lib/adapters/cli.mjs +63 -0
  149. package/eval/lib/adapters/dryrun.mjs +27 -0
  150. package/eval/lib/config.mjs +82 -0
  151. package/eval/lib/db.mjs +76 -0
  152. package/eval/lib/report.mjs +120 -0
  153. package/eval/lib/runner.mjs +157 -0
  154. package/eval/lib/tasks.mjs +73 -0
  155. package/eval/lib/util.mjs +107 -0
  156. package/eval/lib/workdir.mjs +30 -0
  157. package/eval/run.mjs +83 -0
  158. package/eval/tasks/001-slugify/files/package.json +6 -0
  159. package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  160. package/eval/tasks/001-slugify/task.json +6 -0
  161. package/eval/tasks/001-slugify/verify.mjs +28 -0
  162. package/eval/tasks/002-each-day/files/package.json +6 -0
  163. package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  164. package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  165. package/eval/tasks/002-each-day/task.json +6 -0
  166. package/eval/tasks/002-each-day/verify.mjs +31 -0
  167. package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  168. package/eval/tasks/003-word-report/files/package.json +6 -0
  169. package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  170. package/eval/tasks/003-word-report/task.json +6 -0
  171. package/eval/tasks/003-word-report/verify.mjs +38 -0
  172. package/package.json +4 -2
  173. package/src/agent-session-log.js +45 -5
  174. package/src/change-risk-profile.js +83 -14
  175. package/src/config-schema.js +18 -3
  176. package/src/contract-matrix.js +90 -29
  177. package/src/datetime.js +20 -0
  178. package/src/decision-distill.js +517 -0
  179. package/src/dispatch/backends/local-agent.js +4 -4
  180. package/src/docs-check.js +227 -0
  181. package/src/docs-debt.js +187 -24
  182. package/src/doctor-diagnostics.js +102 -2
  183. package/src/foreign-declared.js +78 -0
  184. package/src/index.js +89 -9
  185. package/src/init.js +9 -7
  186. package/src/knowledge-match.js +107 -6
  187. package/src/local-register.js +7 -1
  188. package/src/module-resolve.js +12 -1
  189. package/src/modules.js +2 -1
  190. package/src/plan-adopt-waves.js +126 -0
  191. package/src/quicklog.js +50 -4
  192. package/src/run/command.js +122 -43
  193. package/src/run/complete-handlers.js +48 -19
  194. package/src/run/complete.js +5 -2
  195. package/src/run/gates.js +87 -22
  196. package/src/run/prompt.js +84 -42
  197. package/src/run/quick-audit.js +11 -0
  198. package/src/run/shared.js +152 -10
  199. package/src/run.js +2 -2
  200. package/src/scan-postcheck.js +30 -17
  201. package/src/stage-contract-spec.js +2 -1
  202. package/src/stage-contract.js +68 -4
  203. package/src/stages/archive.js +53 -12
  204. package/src/stages/brainstorm-auto.js +7 -0
  205. package/src/stages/brainstorm.js +11 -0
  206. package/src/stages/doctor.js +157 -1
  207. package/src/stages/execute.js +32 -6
  208. package/src/stages/plan-postcheck.js +76 -17
  209. package/src/stages/plan.js +1 -0
  210. package/src/stages/quick.js +6 -1
  211. package/src/stages/verify.js +31 -14
  212. package/src/sync.js +41 -3
  213. package/src/task-review.js +2 -2
  214. package/src/taskcard-placeholders.js +21 -0
  215. package/src/taskcard.js +10 -1
  216. package/src/verify-postcheck.js +315 -13
  217. package/src/verify-probes.js +46 -4
  218. package/src/worktree-apply.js +184 -7
  219. package/src/worktree-deps.js +57 -0
  220. package/src/worktree.js +201 -58
  221. package/templates/prompts/taskcard-rules.md +1 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * 统一公共 git 调用入口(数组形式 execFileSync,不经 shell)。
3
+ *
4
+ * 背景:worktree 链路原用 `execSync(\`git ${args}\`)` 字符串拼接(经 shell),
5
+ * 文件名/路径/用户输入插值进 shell 存在空格拆词 + 命令注入面;
6
+ * run/shared.js 的 safeGit 已用 execFileSync 数组形式(注释明示避免拆词)。
7
+ * 本模块把 safeGit 收口为单一实现,并补抛错版 git / 静默版 gitQuiet,
8
+ * 供 worktree 链与 run 层共用,消除口径分裂。
9
+ *
10
+ * 三者统一带 `-c safe.directory=<cwd>` per-command(不污染全局 config)+ `-C cwd` 前缀;
11
+ * 默认 timeout 5000ms(opts.timeout 可覆盖),默认 trim(opts.trim:false 保留原样输出)。
12
+ * 数组形式天然规避 shell 拆词,路径含空格 / 元字符安全。
13
+ * safeGit 额外支持 opts.retryOnTimeout:ETIMEDOUT(机器忙瞬时抖动)时用 2× timeout 重试一次。
14
+ */
15
+ import { execFileSync } from 'node:child_process'
16
+
17
+ /**
18
+ * git C 风格引号路径解码:`\\` `\"` `\n` `\t` 与 `\NNN` 八进制**原始字节**。
19
+ * 八进制转义的字节需聚合后按 UTF-8 解码(quotepath 关闭前非 ASCII 整段转义、
20
+ * 关闭后控制字符路径仍会转义),逐字节单独 toString 会拆出 U+FFFD。
21
+ * @param {string} s 已去外层引号的内容
22
+ * @returns {string}
23
+ */
24
+ export function unquoteGitPath(s) {
25
+ const parts = []
26
+ let out = ''
27
+ let bytes = []
28
+ const flushText = () => {
29
+ if (bytes.length) {
30
+ parts.push(Buffer.from(bytes).toString('utf8'))
31
+ bytes = []
32
+ }
33
+ }
34
+ for (let i = 0; i < s.length; i++) {
35
+ const ch = s[i]
36
+ if (ch !== '\\') {
37
+ out += ch
38
+ continue
39
+ }
40
+ const n = s[i + 1]
41
+ if (n === 'n' || n === 't' || n === '"' || n === '\\') {
42
+ flushText()
43
+ out += n === 'n' ? '\n' : n === 't' ? '\t' : n
44
+ i++
45
+ continue
46
+ }
47
+ const m = /^[0-7]{3}/.exec(s.slice(i + 1, i + 4))
48
+ if (m) {
49
+ if (out) {
50
+ parts.push(out)
51
+ out = ''
52
+ }
53
+ bytes.push(parseInt(m[0], 8))
54
+ i += 3
55
+ continue
56
+ }
57
+ out += ch // 未知转义保留原样
58
+ }
59
+ flushText()
60
+ if (out) parts.push(out)
61
+ return parts.join('')
62
+ }
63
+
64
+ /**
65
+ * 构造带统一 per-command 配置的 git 参数前缀。
66
+ * core.quotepath=false:非 ASCII 路径在 porcelain/diff 输出原始 UTF-8 而非 "\346\226\207" 八进制
67
+ * 转义(中文文件名经 parsePorcelainPath 的 \\(.) 解引会被拆成裸数字拼坏路径)。
68
+ */
69
+ function buildFullArgs(cwd, args) {
70
+ return ['-c', `safe.directory=${cwd}`, '-c', 'core.quotepath=false', '-C', cwd, ...args]
71
+ }
72
+
73
+ // maxBuffer:execFileSync 默认 1MB,untracked 清单/大 diff 超限抛 ENOBUFS——
74
+ // safeGit 吞成 null、调用方 `|| ''` 会把"输出很大"静默变成"输出为空"(补丁漏文件级联)。
75
+ // 32MB 对齐 verify-postcheck 的 diff 读取口径。
76
+ // 256MB(坑 apply-spawnsync-enobufs,2026-08-22 实证:大 diff binary patch 超 32MB 抛
77
+ // ENOBUFS 只能 git merge 绕行)——上限只在真输出那么大时才占用
78
+ const GIT_MAX_BUFFER = 256 * 1024 * 1024
79
+
80
+ /**
81
+ * 安全执行 git:失败不抛,返回 { value, error } 结构。
82
+ * @param {string} cwd
83
+ * @param {string[]} args
84
+ * @param {{ trim?: boolean, timeout?: number, retryOnTimeout?: boolean }} [opts]
85
+ * - trim:false 保留原样输出(git status --porcelain 首行前导空格
86
+ * 是状态码的一部分,trim 会削掉致 parsePorcelainPath 丢首字符,坑见 auditQuickCompletion 注释)
87
+ * - timeout 默认 5000ms,长操作(大 diff / worktree list)可传更大值
88
+ * - retryOnTimeout:true 时,ETIMEDOUT 用 2× timeout 重试一次(默认 false 向后兼容)。
89
+ * 机器忙时 git 子进程启动慢易瞬时超时;审计锚点 git 调用失败即 blocked 中断 quick,
90
+ * 重试一次能消化绝大多数瞬时抖动,免去用户手工重跑。权限 / 损坏等非超时错误不重试(救不回)。
91
+ * @returns {{ value: string|null, error: string|null }}
92
+ */
93
+ export function safeGit(cwd, args, opts = {}) {
94
+ const { trim = true, timeout = 5000, retryOnTimeout = false } = opts
95
+ const fullArgs = buildFullArgs(cwd, args)
96
+ // 单次尝试:返回 { value, errorObj }(errorObj 为原始 Error 或 null),不在内部格式化 message。
97
+ // 格式化(取首行)推迟到最终返回,避免重试分支重复格式化,并保留原始 code 供重试判定。
98
+ const attempt = (t) => {
99
+ try {
100
+ let value = execFileSync('git', fullArgs, { encoding: 'utf8', timeout: t, maxBuffer: GIT_MAX_BUFFER, stdio: ['ignore', 'pipe', 'pipe'] })
101
+ if (trim) value = value.trim()
102
+ return { value, errorObj: null }
103
+ } catch (e) {
104
+ return { value: null, errorObj: e }
105
+ }
106
+ }
107
+ let { value, errorObj } = attempt(timeout)
108
+ if (errorObj && retryOnTimeout && errorObj.code === 'ETIMEDOUT') {
109
+ ({ value, errorObj } = attempt(timeout * 2))
110
+ }
111
+ return { value, error: errorObj ? errorObj.message.split('\n')[0] : null }
112
+ }
113
+
114
+ /**
115
+ * 抛错版 git:失败抛异常,成功返回 trim 后 string(对齐 worktree 本地 git() 语义)。
116
+ * 调用方自行 catch;不经 shell,参数以数组元素传递。
117
+ * @param {string} cwd
118
+ * @param {string[]} args
119
+ * @param {{ trim?: boolean, timeout?: number, encoding?: string }} [opts]
120
+ * - encoding:'buffer' 返回原始 Buffer(git diff --binary 等**二进制输出**专用——
121
+ * utf8 解码会破坏 NUL 字节使补丁不可应用;Buffer 输出跳过 trim)
122
+ * @returns {string|Buffer}
123
+ */
124
+ export function git(cwd, args, opts = {}) {
125
+ const { trim = true, timeout = 5000, encoding = 'utf8' } = opts
126
+ const fullArgs = buildFullArgs(cwd, args)
127
+ const value = execFileSync('git', fullArgs, { encoding, timeout, maxBuffer: GIT_MAX_BUFFER, stdio: ['ignore', 'pipe', 'pipe'] })
128
+ if (Buffer.isBuffer(value)) return value
129
+ return trim ? value.trim() : value
130
+ }
131
+
132
+ /**
133
+ * 静默版 git:内部调 git,失败 catch 返回 null(对齐 worktree 本地 gitQuiet() 语义)。
134
+ * ENOBUFS(输出超 32MB)不是"无输出",静默成 null 会让调用方把大输出误当空清单——
135
+ * 至少 stderr 留一行,不与正常空结果同语义。
136
+ * @param {string} cwd
137
+ * @param {string[]} args
138
+ * @param {{ trim?: boolean, timeout?: number }} [opts]
139
+ * @returns {string|null}
140
+ */
141
+ export function gitQuiet(cwd, args, opts = {}) {
142
+ try {
143
+ return git(cwd, args, opts)
144
+ } catch (e) {
145
+ if (e && e.code === 'ENOBUFS') {
146
+ console.error(`[sillyspec] git 输出超过 32MB 上限被截断:git ${args.join(' ')}`)
147
+ }
148
+ return null
149
+ }
150
+ }
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code PreToolUse hook 入口
4
+ *
5
+ * 从 stdin 读取 JSON,调用 worktree-guard 判断是否拦截。
6
+ *
7
+ * Claude Code hooks.json 配置:
8
+ * {
9
+ * "hooks": {
10
+ * "PreToolUse": [
11
+ * {
12
+ * "matcher": "Edit|Write|MultiEdit|Bash",
13
+ * "hooks": [
14
+ * {
15
+ * "type": "command",
16
+ * "command": "node /path/to/sillyspec/src/hooks/claude-pre-tool-use.cjs"
17
+ * }
18
+ * ]
19
+ * }
20
+ * ]
21
+ * }
22
+ * }
23
+ */
24
+
25
+ 'use strict'
26
+
27
+ const path = require('path')
28
+ const fs = require('fs')
29
+
30
+ // 动态 import ESM 模块
31
+ async function main() {
32
+ // Claude Code hook 通过 stdin 传入 JSON
33
+ let input = ''
34
+ try {
35
+ input = await readStdin()
36
+ } catch (e) {
37
+ // 无法读取 stdin → 放行(安全优先于阻断),但留痕——本 hook 是阶段门禁唯一执行点,
38
+ // 静默解除全部防护会让故障无从诊断
39
+ console.error(`[sillyspec-hook] stdin 读取失败(放行): ${e.message}`)
40
+ process.exit(0)
41
+ }
42
+
43
+ let parsed
44
+ try {
45
+ parsed = JSON.parse(input)
46
+ } catch (e) {
47
+ console.error(`[sillyspec-hook] stdin JSON 解析失败(放行): ${e.message};input 长度 ${input.length}`)
48
+ process.exit(0)
49
+ }
50
+
51
+ // 解析 Claude Code hook 输入格式
52
+ // 格式:{ tool_name: string, tool_input: { ... } }
53
+ const toolName = parsed.tool_name || parsed.tool || ''
54
+ const toolInput = parsed.tool_input || {}
55
+
56
+ // 转换为 shouldBlock 的输入格式
57
+ const toolMap = {
58
+ 'Write': 'Write',
59
+ 'Edit': 'Edit',
60
+ 'MultiEdit': 'MultiEdit',
61
+ 'Bash': 'Bash',
62
+ }
63
+
64
+ const mappedTool = toolMap[toolName]
65
+ if (!mappedTool) {
66
+ // 不在拦截范围内的工具 → 放行
67
+ process.exit(0)
68
+ }
69
+
70
+ // 构造 opts
71
+ const opts = { tool: mappedTool }
72
+ if (mappedTool === 'Bash') {
73
+ opts.command = toolInput.command || ''
74
+ } else {
75
+ // Write/Edit/MultiEdit
76
+ const fp = toolInput.file_path || toolInput.filePath
77
+ if (fp) opts.filePath = fp
78
+ // MultiEdit 可能有多个文件
79
+ if (mappedTool === 'MultiEdit') {
80
+ const fps = toolInput.edits
81
+ ? toolInput.edits.map(e => e.file_path || e.filePath).filter(Boolean)
82
+ : []
83
+ if (fps.length > 0) opts.filePaths = fps
84
+ }
85
+ }
86
+
87
+ // 加载 worktree-guard(ESM)
88
+ let shouldBlock
89
+ try {
90
+ const mod = await import('./worktree-guard.js')
91
+ shouldBlock = mod.shouldBlock
92
+ } catch (e) {
93
+ // 模块加载失败 → 放行(不因为 hook 出错阻断工作流)
94
+ // eslint-disable-next-line no-console
95
+ console.error(`[sillyspec-hook] 模块加载失败: ${e.message}`)
96
+ process.exit(0)
97
+ }
98
+
99
+ const result = shouldBlock(opts)
100
+
101
+ if (result.blocked) {
102
+ // 输出错误信息(Claude Code 会显示给 agent)
103
+ // eslint-disable-next-line no-console
104
+ console.error(`[sillyspec] ❌ ${result.reason || 'blocked by worktree guard'}`)
105
+ process.exit(2) // exit code 2 = 阻止工具执行
106
+ }
107
+
108
+ // 放行
109
+ process.exit(0)
110
+ }
111
+
112
+ function readStdin() {
113
+ return new Promise((resolve, reject) => {
114
+ const chunks = []
115
+ process.stdin.setEncoding('utf8')
116
+ process.stdin.on('data', chunk => chunks.push(chunk))
117
+ process.stdin.on('end', () => resolve(chunks.join('')))
118
+ process.stdin.on('error', reject)
119
+ // 超时保护(3秒)
120
+ setTimeout(() => {
121
+ process.stdin.destroy()
122
+ resolve('')
123
+ }, 3000)
124
+ })
125
+ }
126
+
127
+ main().catch(e => {
128
+ console.error(`[sillyspec-hook] 未捕获异常(放行): ${e && e.message}`)
129
+ process.exit(0)
130
+ })