@dommaker/harness 0.18.0 → 1.0.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 (366) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/harness.js +136 -552
  3. package/dist/cli/commands/check.d.ts.map +1 -1
  4. package/dist/cli/commands/check.js +2 -13
  5. package/dist/cli/commands/check.js.map +1 -1
  6. package/dist/cli/commands/definitions.d.ts +90 -0
  7. package/dist/cli/commands/definitions.d.ts.map +1 -0
  8. package/dist/cli/commands/definitions.js +519 -0
  9. package/dist/cli/commands/definitions.js.map +1 -0
  10. package/dist/cli/commands/knowledge.js.map +1 -1
  11. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +3 -25
  12. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  13. package/dist/cli/commands/sync-docs/capabilities-syncer.js +3 -93
  14. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  15. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  16. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  17. package/dist/cli/commands/sync-docs/index.js +4 -4
  18. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  19. package/dist/cli/commands/update-user-model.d.ts +1 -0
  20. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  21. package/dist/cli/commands/update-user-model.js +12 -5
  22. package/dist/cli/commands/update-user-model.js.map +1 -1
  23. package/dist/context/index.d.ts +1 -5
  24. package/dist/context/index.d.ts.map +1 -1
  25. package/dist/context/index.js +1 -5
  26. package/dist/context/index.js.map +1 -1
  27. package/dist/context/knowledge-injector.d.ts +3 -3
  28. package/dist/context/knowledge-injector.d.ts.map +1 -1
  29. package/dist/context/knowledge-injector.js +1 -1
  30. package/dist/context/session-manager.d.ts +1 -12
  31. package/dist/context/session-manager.d.ts.map +1 -1
  32. package/dist/context/session-manager.js +0 -63
  33. package/dist/context/session-manager.js.map +1 -1
  34. package/dist/context/types.d.ts +1 -39
  35. package/dist/context/types.d.ts.map +1 -1
  36. package/dist/context/types.js +2 -15
  37. package/dist/context/types.js.map +1 -1
  38. package/dist/core/constraints/agent-prompt-renderer.d.ts +32 -0
  39. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -0
  40. package/dist/core/constraints/agent-prompt-renderer.js +58 -0
  41. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -0
  42. package/dist/core/constraints/capabilities-parser.d.ts +17 -2
  43. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  44. package/dist/core/constraints/capabilities-parser.js +84 -2
  45. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  46. package/dist/core/constraints/check-cache.d.ts +48 -3
  47. package/dist/core/constraints/check-cache.d.ts.map +1 -1
  48. package/dist/core/constraints/check-cache.js +53 -5
  49. package/dist/core/constraints/check-cache.js.map +1 -1
  50. package/dist/core/constraints/checker.d.ts +3 -17
  51. package/dist/core/constraints/checker.d.ts.map +1 -1
  52. package/dist/core/constraints/checker.js +7 -47
  53. package/dist/core/constraints/checker.js.map +1 -1
  54. package/dist/core/constraints/definitions.d.ts +0 -7
  55. package/dist/core/constraints/definitions.d.ts.map +1 -1
  56. package/dist/core/constraints/definitions.js +1 -8
  57. package/dist/core/constraints/definitions.js.map +1 -1
  58. package/dist/core/constraints/doc-freshness/runner.d.ts +0 -2
  59. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  60. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  61. package/dist/core/constraints/index.d.ts +5 -1
  62. package/dist/core/constraints/index.d.ts.map +1 -1
  63. package/dist/core/constraints/index.js +8 -3
  64. package/dist/core/constraints/index.js.map +1 -1
  65. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  66. package/dist/core/constraints/interceptor.js +1 -5
  67. package/dist/core/constraints/interceptor.js.map +1 -1
  68. package/dist/core/effective-constraints.d.ts.map +1 -1
  69. package/dist/core/effective-constraints.js +0 -1
  70. package/dist/core/effective-constraints.js.map +1 -1
  71. package/dist/core/project-config-loader.d.ts.map +1 -1
  72. package/dist/core/project-config-loader.js +1 -8
  73. package/dist/core/project-config-loader.js.map +1 -1
  74. package/dist/dashboard/stats.d.ts.map +1 -1
  75. package/dist/dashboard/stats.js.map +1 -1
  76. package/dist/dashboard/types.d.ts +2 -2
  77. package/dist/dashboard/types.d.ts.map +1 -1
  78. package/dist/failure/constraint-handler.d.ts.map +1 -1
  79. package/dist/failure/constraint-handler.js +0 -2
  80. package/dist/failure/constraint-handler.js.map +1 -1
  81. package/dist/gates/acceptance.d.ts +9 -1
  82. package/dist/gates/acceptance.d.ts.map +1 -1
  83. package/dist/gates/acceptance.js +21 -0
  84. package/dist/gates/acceptance.js.map +1 -1
  85. package/dist/gates/checker-gate.d.ts +23 -0
  86. package/dist/gates/checker-gate.d.ts.map +1 -0
  87. package/dist/gates/checker-gate.js +53 -0
  88. package/dist/gates/checker-gate.js.map +1 -0
  89. package/dist/gates/command.d.ts +9 -2
  90. package/dist/gates/command.d.ts.map +1 -1
  91. package/dist/gates/command.js +10 -0
  92. package/dist/gates/command.js.map +1 -1
  93. package/dist/gates/contract.d.ts +8 -2
  94. package/dist/gates/contract.d.ts.map +1 -1
  95. package/dist/gates/contract.js +9 -0
  96. package/dist/gates/contract.js.map +1 -1
  97. package/dist/gates/decision.d.ts +17 -0
  98. package/dist/gates/decision.d.ts.map +1 -0
  99. package/dist/gates/decision.js +25 -0
  100. package/dist/gates/decision.js.map +1 -0
  101. package/dist/gates/definitions.d.ts +63 -0
  102. package/dist/gates/definitions.d.ts.map +1 -0
  103. package/dist/gates/definitions.js +160 -0
  104. package/dist/gates/definitions.js.map +1 -0
  105. package/dist/gates/effective-gates.d.ts +30 -0
  106. package/dist/gates/effective-gates.d.ts.map +1 -0
  107. package/dist/gates/effective-gates.js +85 -0
  108. package/dist/gates/effective-gates.js.map +1 -0
  109. package/dist/gates/index.d.ts +25 -1
  110. package/dist/gates/index.d.ts.map +1 -1
  111. package/dist/gates/index.js +37 -1
  112. package/dist/gates/index.js.map +1 -1
  113. package/dist/gates/performance.d.ts +8 -2
  114. package/dist/gates/performance.d.ts.map +1 -1
  115. package/dist/gates/performance.js +9 -0
  116. package/dist/gates/performance.js.map +1 -1
  117. package/dist/gates/registry.d.ts +32 -0
  118. package/dist/gates/registry.d.ts.map +1 -0
  119. package/dist/gates/registry.js +90 -0
  120. package/dist/gates/registry.js.map +1 -0
  121. package/dist/gates/review.d.ts +8 -2
  122. package/dist/gates/review.d.ts.map +1 -1
  123. package/dist/gates/review.js +9 -0
  124. package/dist/gates/review.js.map +1 -1
  125. package/dist/gates/runner.d.ts +30 -0
  126. package/dist/gates/runner.d.ts.map +1 -0
  127. package/dist/gates/runner.js +38 -0
  128. package/dist/gates/runner.js.map +1 -0
  129. package/dist/gates/security.d.ts +8 -2
  130. package/dist/gates/security.d.ts.map +1 -1
  131. package/dist/gates/security.js +9 -0
  132. package/dist/gates/security.js.map +1 -1
  133. package/dist/gates/types.d.ts +41 -1
  134. package/dist/gates/types.d.ts.map +1 -1
  135. package/dist/hooks/bootstrap.d.ts.map +1 -1
  136. package/dist/hooks/bootstrap.js +0 -2
  137. package/dist/hooks/bootstrap.js.map +1 -1
  138. package/dist/hooks/config.d.ts +26 -0
  139. package/dist/hooks/config.d.ts.map +1 -0
  140. package/dist/hooks/config.js +30 -0
  141. package/dist/hooks/config.js.map +1 -0
  142. package/dist/hooks/index.d.ts +2 -1
  143. package/dist/hooks/index.d.ts.map +1 -1
  144. package/dist/hooks/index.js +4 -1
  145. package/dist/hooks/index.js.map +1 -1
  146. package/dist/hooks/registry.d.ts +18 -1
  147. package/dist/hooks/registry.d.ts.map +1 -1
  148. package/dist/hooks/registry.js +45 -0
  149. package/dist/hooks/registry.js.map +1 -1
  150. package/dist/hooks/types.d.ts +20 -0
  151. package/dist/hooks/types.d.ts.map +1 -1
  152. package/dist/index.d.ts +8 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +13 -21
  155. package/dist/index.js.map +1 -1
  156. package/dist/knowledge/import.d.ts +2 -2
  157. package/dist/knowledge/import.d.ts.map +1 -1
  158. package/dist/knowledge/ingest.js.map +1 -1
  159. package/dist/knowledge/lifecycle-hooks.d.ts +2 -2
  160. package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
  161. package/dist/knowledge/types.d.ts +5 -7
  162. package/dist/knowledge/types.d.ts.map +1 -1
  163. package/dist/knowledge/types.js +1 -1
  164. package/dist/knowledge/types.js.map +1 -1
  165. package/dist/monitoring/constraint-doctor.d.ts +4 -22
  166. package/dist/monitoring/constraint-doctor.d.ts.map +1 -1
  167. package/dist/monitoring/constraint-doctor.js +7 -94
  168. package/dist/monitoring/constraint-doctor.js.map +1 -1
  169. package/dist/presets/standard.d.ts +1 -1
  170. package/dist/presets/standard.d.ts.map +1 -1
  171. package/dist/presets/standard.js +1 -2
  172. package/dist/presets/standard.js.map +1 -1
  173. package/dist/spec/annotation-checker.d.ts.map +1 -1
  174. package/dist/spec/annotation-checker.js +6 -26
  175. package/dist/spec/annotation-checker.js.map +1 -1
  176. package/dist/tools/index.d.ts +0 -4
  177. package/dist/tools/index.d.ts.map +1 -1
  178. package/dist/tools/index.js +0 -4
  179. package/dist/tools/index.js.map +1 -1
  180. package/dist/types/constraint.d.ts +1 -12
  181. package/dist/types/constraint.d.ts.map +1 -1
  182. package/dist/types/constraint.js +0 -3
  183. package/dist/types/constraint.js.map +1 -1
  184. package/dist/types/project-config.d.ts +0 -2
  185. package/dist/types/project-config.d.ts.map +1 -1
  186. package/dist/utils/file-walk.d.ts +2 -2
  187. package/dist/utils/file-walk.js +2 -2
  188. package/package.json +9 -4
  189. package/src/CONTEXT.md +14 -0
  190. package/src/__tests__/checker-extra.test.ts +2 -59
  191. package/src/__tests__/constraint-doctor.test.ts +1 -140
  192. package/src/__tests__/constraint-handler.test.ts +0 -4
  193. package/src/__tests__/project-config-loader-extra.test.ts +0 -20
  194. package/src/__tests__/project-config-loader.test.ts +0 -1
  195. package/src/cli/commands/CONTEXT.md +15 -7
  196. package/src/cli/commands/__tests__/analyze-sessions.test.ts +178 -0
  197. package/src/cli/commands/__tests__/check-drift.test.ts +0 -3
  198. package/src/cli/commands/__tests__/check-skip-output.test.ts +0 -5
  199. package/src/cli/commands/__tests__/check.test.ts +7 -75
  200. package/src/cli/commands/__tests__/registry.test.ts +201 -0
  201. package/src/cli/commands/__tests__/release.test.ts +162 -0
  202. package/src/cli/commands/__tests__/update-user-model.test.ts +205 -0
  203. package/src/cli/commands/check.ts +2 -14
  204. package/src/cli/commands/definitions.ts +593 -0
  205. package/src/cli/commands/knowledge.ts +3 -3
  206. package/src/cli/commands/sync-docs/capabilities-syncer.ts +4 -110
  207. package/src/cli/commands/sync-docs/index.ts +8 -5
  208. package/src/cli/commands/update-user-model.ts +15 -5
  209. package/src/context/CONTEXT.md +2 -7
  210. package/src/context/__tests__/session-manager.test.ts +0 -91
  211. package/src/context/index.ts +1 -5
  212. package/src/context/knowledge-injector.ts +3 -3
  213. package/src/context/session-manager.ts +0 -74
  214. package/src/context/types.ts +2 -63
  215. package/src/core/CONTEXT.md +1 -1
  216. package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +129 -0
  217. package/src/core/constraints/__tests__/check-cache.test.ts +227 -0
  218. package/src/core/constraints/__tests__/interceptor.test.ts +0 -2
  219. package/src/core/constraints/agent-prompt-renderer.ts +71 -0
  220. package/src/core/constraints/capabilities-parser.ts +95 -2
  221. package/src/core/constraints/check-cache.ts +75 -3
  222. package/src/core/constraints/checker.ts +8 -53
  223. package/src/core/constraints/definitions.ts +0 -8
  224. package/src/core/constraints/doc-freshness/runner.ts +1 -2
  225. package/src/core/constraints/index.ts +11 -2
  226. package/src/core/constraints/interceptor.ts +1 -6
  227. package/src/core/effective-constraints.ts +0 -1
  228. package/src/core/project-config-loader.ts +1 -7
  229. package/src/dashboard/stats.ts +2 -2
  230. package/src/dashboard/types.ts +2 -2
  231. package/src/failure/constraint-handler.ts +0 -2
  232. package/src/gates/CONTEXT.md +16 -13
  233. package/src/gates/__tests__/checker-gate.test.ts +45 -0
  234. package/src/gates/__tests__/contract.test.ts +73 -0
  235. package/src/gates/__tests__/effective-gates.test.ts +82 -0
  236. package/src/gates/__tests__/gate-interface.test.ts +202 -0
  237. package/src/gates/__tests__/registry.test.ts +87 -0
  238. package/src/gates/__tests__/runner.test.ts +81 -0
  239. package/src/gates/acceptance.ts +24 -1
  240. package/src/gates/checker-gate.ts +54 -0
  241. package/src/gates/command.ts +13 -2
  242. package/src/gates/contract.ts +12 -2
  243. package/src/gates/decision.ts +27 -0
  244. package/src/gates/definitions.ts +207 -0
  245. package/src/gates/effective-gates.ts +106 -0
  246. package/src/gates/index.ts +38 -0
  247. package/src/gates/performance.ts +12 -2
  248. package/src/gates/registry.ts +107 -0
  249. package/src/gates/review.ts +12 -2
  250. package/src/gates/runner.ts +57 -0
  251. package/src/gates/security.ts +12 -2
  252. package/src/gates/types.ts +48 -1
  253. package/src/hooks/CONTEXT.md +12 -2
  254. package/src/hooks/__tests__/bootstrap.test.ts +0 -1
  255. package/src/hooks/__tests__/config.test.ts +19 -0
  256. package/src/hooks/__tests__/pipeline.test.ts +79 -0
  257. package/src/hooks/__tests__/registry.test.ts +67 -0
  258. package/src/hooks/bootstrap.ts +0 -2
  259. package/src/hooks/config.ts +29 -0
  260. package/src/hooks/index.ts +2 -1
  261. package/src/hooks/registry.ts +60 -1
  262. package/src/hooks/types.ts +21 -0
  263. package/src/index.ts +13 -22
  264. package/src/knowledge/import.ts +3 -3
  265. package/src/knowledge/ingest.ts +4 -4
  266. package/src/knowledge/lifecycle-hooks.ts +3 -3
  267. package/src/knowledge/types.ts +5 -8
  268. package/src/monitoring/constraint-doctor.ts +7 -106
  269. package/src/presets/__tests__/standard.test.ts +0 -2
  270. package/src/presets/standard.ts +1 -2
  271. package/src/spec/__tests__/annotation-checker.test.ts +9 -3
  272. package/src/spec/annotation-checker.ts +7 -29
  273. package/src/tools/CONTEXT.md +7 -14
  274. package/src/tools/index.ts +0 -4
  275. package/src/types/constraint.ts +1 -14
  276. package/src/types/project-config.ts +0 -3
  277. package/src/utils/file-walk.ts +2 -2
  278. package/dist/architecture/constraint-engine.d.ts +0 -67
  279. package/dist/architecture/constraint-engine.d.ts.map +0 -1
  280. package/dist/architecture/constraint-engine.js +0 -295
  281. package/dist/architecture/constraint-engine.js.map +0 -1
  282. package/dist/architecture/cross-project-checker.d.ts +0 -54
  283. package/dist/architecture/cross-project-checker.d.ts.map +0 -1
  284. package/dist/architecture/cross-project-checker.js +0 -467
  285. package/dist/architecture/cross-project-checker.js.map +0 -1
  286. package/dist/cli/commands/index.d.ts +0 -30
  287. package/dist/cli/commands/index.d.ts.map +0 -1
  288. package/dist/cli/commands/index.js +0 -91
  289. package/dist/cli/commands/index.js.map +0 -1
  290. package/dist/context/file-budget.d.ts +0 -40
  291. package/dist/context/file-budget.d.ts.map +0 -1
  292. package/dist/context/file-budget.js +0 -147
  293. package/dist/context/file-budget.js.map +0 -1
  294. package/dist/context/progressive-loader.d.ts +0 -109
  295. package/dist/context/progressive-loader.d.ts.map +0 -1
  296. package/dist/context/progressive-loader.js +0 -166
  297. package/dist/context/progressive-loader.js.map +0 -1
  298. package/dist/context/token-pipeline.d.ts +0 -55
  299. package/dist/context/token-pipeline.d.ts.map +0 -1
  300. package/dist/context/token-pipeline.js +0 -220
  301. package/dist/context/token-pipeline.js.map +0 -1
  302. package/dist/context/tool-output-budget.d.ts +0 -59
  303. package/dist/context/tool-output-budget.d.ts.map +0 -1
  304. package/dist/context/tool-output-budget.js +0 -177
  305. package/dist/context/tool-output-budget.js.map +0 -1
  306. package/dist/core/constraints/default-executors.d.ts +0 -10
  307. package/dist/core/constraints/default-executors.d.ts.map +0 -1
  308. package/dist/core/constraints/default-executors.js +0 -120
  309. package/dist/core/constraints/default-executors.js.map +0 -1
  310. package/dist/llm/adapter.d.ts +0 -36
  311. package/dist/llm/adapter.d.ts.map +0 -1
  312. package/dist/llm/adapter.js +0 -118
  313. package/dist/llm/adapter.js.map +0 -1
  314. package/dist/llm/index.d.ts +0 -3
  315. package/dist/llm/index.d.ts.map +0 -1
  316. package/dist/llm/index.js +0 -19
  317. package/dist/llm/index.js.map +0 -1
  318. package/dist/llm/types.d.ts +0 -62
  319. package/dist/llm/types.d.ts.map +0 -1
  320. package/dist/llm/types.js +0 -8
  321. package/dist/llm/types.js.map +0 -1
  322. package/dist/tools/core/index.d.ts +0 -13
  323. package/dist/tools/core/index.d.ts.map +0 -1
  324. package/dist/tools/core/index.js +0 -198
  325. package/dist/tools/core/index.js.map +0 -1
  326. package/dist/tools/loader.d.ts +0 -21
  327. package/dist/tools/loader.d.ts.map +0 -1
  328. package/dist/tools/loader.js +0 -199
  329. package/dist/tools/loader.js.map +0 -1
  330. package/dist/tools/registry.d.ts +0 -59
  331. package/dist/tools/registry.d.ts.map +0 -1
  332. package/dist/tools/registry.js +0 -115
  333. package/dist/tools/registry.js.map +0 -1
  334. package/dist/tools/types.d.ts +0 -38
  335. package/dist/tools/types.d.ts.map +0 -1
  336. package/dist/tools/types.js +0 -6
  337. package/dist/tools/types.js.map +0 -1
  338. package/src/__tests__/architecture-engine.test.ts +0 -619
  339. package/src/__tests__/context/progressive-loader.test.ts +0 -131
  340. package/src/__tests__/cross-project-checker.test.ts +0 -318
  341. package/src/architecture/CONTEXT.md +0 -22
  342. package/src/architecture/constraint-engine.ts +0 -351
  343. package/src/architecture/cross-project-checker.ts +0 -545
  344. package/src/cli/commands/index.ts +0 -30
  345. package/src/context/__tests__/file-budget.test.ts +0 -142
  346. package/src/context/__tests__/progressive-loader.test.ts +0 -264
  347. package/src/context/__tests__/token-pipeline.test.ts +0 -178
  348. package/src/context/__tests__/tool-output-budget.test.ts +0 -95
  349. package/src/context/file-budget.ts +0 -142
  350. package/src/context/progressive-loader.ts +0 -260
  351. package/src/context/token-pipeline.ts +0 -265
  352. package/src/context/tool-output-budget.ts +0 -178
  353. package/src/core/constraints/__tests__/default-executors.test.ts +0 -57
  354. package/src/core/constraints/default-executors.ts +0 -92
  355. package/src/llm/CONTEXT.md +0 -21
  356. package/src/llm/__tests__/adapter.test.ts +0 -172
  357. package/src/llm/adapter.ts +0 -149
  358. package/src/llm/index.ts +0 -2
  359. package/src/llm/types.ts +0 -81
  360. package/src/tools/__tests__/loader.test.ts +0 -252
  361. package/src/tools/__tests__/registry.test.ts +0 -157
  362. package/src/tools/core/__tests__/core.test.ts +0 -122
  363. package/src/tools/core/index.ts +0 -201
  364. package/src/tools/loader.ts +0 -216
  365. package/src/tools/registry.ts +0 -128
  366. package/src/tools/types.ts +0 -46
@@ -0,0 +1,162 @@
1
+ /**
2
+ * release 命令测试(O6)
3
+ *
4
+ * Seam:release(options) 公开入口。
5
+ * 隔离面:
6
+ * - child_process.execSync → 按命令分发的 fixture(git/npm/tsc 全部 mock,绝不真执行)
7
+ * - fs.existsSync / readFileSync → fixture 包(package.json + dist 关键文件)
8
+ * - process.exit → 抛异常断言化(exit code 可断言,且阻断后续真实副作用)
9
+ *
10
+ * 测试止于 dry-run 与「发布前闸门」(分支/同步/干净树/tag 冲突),
11
+ * 不覆盖真实 npm version/publish 路径——禁止真发版。
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import { execSync } from 'child_process';
16
+ import { release } from '../release';
17
+
18
+ jest.mock('child_process', () => ({
19
+ ...jest.requireActual('child_process'),
20
+ execSync: jest.fn(),
21
+ }));
22
+
23
+ jest.mock('fs', () => ({
24
+ ...jest.requireActual('fs'),
25
+ existsSync: jest.fn(),
26
+ readFileSync: jest.fn(),
27
+ }));
28
+
29
+ jest.mock('chalk', () => {
30
+ const id = (s: string) => s;
31
+ const chalkFn = Object.assign(id, {
32
+ gray: id,
33
+ blue: id,
34
+ green: id,
35
+ yellow: id,
36
+ cyan: id,
37
+ bold: id,
38
+ red: id,
39
+ });
40
+ return {
41
+ __esModule: true,
42
+ default: chalkFn,
43
+ gray: id,
44
+ blue: id,
45
+ green: id,
46
+ yellow: id,
47
+ cyan: id,
48
+ bold: id,
49
+ red: id,
50
+ };
51
+ });
52
+
53
+ const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
54
+ const mockFs = fs as jest.Mocked<typeof fs>;
55
+
56
+ const PKG_JSON = JSON.stringify({ name: '@dommaker/harness', version: '0.18.0' });
57
+
58
+ describe('release command', () => {
59
+ let consoleSpy: jest.SpyInstance;
60
+ let errorSpy: jest.SpyInstance;
61
+ let exitSpy: jest.SpyInstance;
62
+ let pkgExists: boolean;
63
+ let tagListResult: string;
64
+
65
+ beforeEach(() => {
66
+ jest.clearAllMocks();
67
+ pkgExists = true;
68
+ tagListResult = '';
69
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
70
+ errorSpy = jest.spyOn(console, 'error').mockImplementation();
71
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation((code?: string | number | null) => {
72
+ throw new Error(`__exit_${code}__`);
73
+ });
74
+
75
+ mockFs.existsSync.mockImplementation((p) => {
76
+ if (String(p).endsWith('package.json')) return pkgExists;
77
+ return true; // dist 关键文件视为存在
78
+ });
79
+ mockFs.readFileSync.mockReturnValue(PKG_JSON);
80
+
81
+ mockExecSync.mockImplementation((cmd: string) => {
82
+ const c = String(cmd);
83
+ if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
84
+ if (c.startsWith('git rev-list --count')) return '0';
85
+ if (c.startsWith('git status --porcelain')) return '';
86
+ if (c.startsWith('git tag -l')) return tagListResult;
87
+ if (c.startsWith('npx tsc')) return 'build ok';
88
+ return '';
89
+ });
90
+ });
91
+
92
+ afterEach(() => {
93
+ consoleSpy.mockRestore();
94
+ errorSpy.mockRestore();
95
+ exitSpy.mockRestore();
96
+ });
97
+
98
+ test('非包目录:exit 1 并提示 Not a package', async () => {
99
+ pkgExists = false;
100
+
101
+ await expect(release({})).rejects.toThrow('__exit_1__');
102
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Not a package'));
103
+ });
104
+
105
+ test('非 master/main 分支:exit 1 并提示', async () => {
106
+ mockExecSync.mockImplementation((cmd: string) =>
107
+ String(cmd).startsWith('git rev-parse --abbrev-ref') ? 'feat/x' : '');
108
+
109
+ await expect(release({})).rejects.toThrow('__exit_1__');
110
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Must be on master or main branch'));
111
+ });
112
+
113
+ test('落后于远程:exit 1 并提示 behind', async () => {
114
+ mockExecSync.mockImplementation((cmd: string) => {
115
+ const c = String(cmd);
116
+ if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
117
+ if (c.startsWith('git rev-list --count')) return '3';
118
+ return '';
119
+ });
120
+
121
+ await expect(release({})).rejects.toThrow('__exit_1__');
122
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('behind origin'));
123
+ });
124
+
125
+ test('工作树不干净:exit 1 并提示 Uncommitted changes', async () => {
126
+ mockExecSync.mockImplementation((cmd: string) => {
127
+ const c = String(cmd);
128
+ if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
129
+ if (c.startsWith('git rev-list --count')) return '0';
130
+ if (c.startsWith('git status --porcelain')) return ' M package.json';
131
+ return '';
132
+ });
133
+
134
+ await expect(release({})).rejects.toThrow('__exit_1__');
135
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Uncommitted changes'));
136
+ });
137
+
138
+ test('dry-run patch:计算目标版本 0.18.1,exit 0', async () => {
139
+ await expect(release({ bumpType: 'patch', dryRun: 'true' })).rejects.toThrow('__exit_0__');
140
+ expect(consoleSpy).toHaveBeenCalledWith(
141
+ expect.stringContaining('Dry-run complete. Would publish: @dommaker/harness@0.18.1'),
142
+ );
143
+ });
144
+
145
+ test('dry-run minor:计算目标版本 0.19.0', async () => {
146
+ await expect(release({ bumpType: 'minor', dryRun: 'true' })).rejects.toThrow('__exit_0__');
147
+ expect(consoleSpy).toHaveBeenCalledWith(
148
+ expect.stringContaining('Dry-run complete. Would publish: @dommaker/harness@0.19.0'),
149
+ );
150
+ });
151
+
152
+ test('目标 tag 已存在:发布前闸门 exit 1(不触 npm version)', async () => {
153
+ tagListResult = 'v0.18.1';
154
+
155
+ await expect(release({ bumpType: 'patch' })).rejects.toThrow('__exit_1__');
156
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('already exists'));
157
+ // 闸门阻断:npm version / npm publish 从未执行
158
+ const commands = mockExecSync.mock.calls.map(c => String(c[0]));
159
+ expect(commands.some(c => c.startsWith('npm version'))).toBe(false);
160
+ expect(commands.some(c => c.startsWith('npm publish'))).toBe(false);
161
+ });
162
+ });
@@ -0,0 +1,205 @@
1
+ /**
2
+ * update-user-model 命令测试(O1:--days flag + --json/--dry-run 兼容)
3
+ *
4
+ * Seam:updateUserModel(options) 公开入口。
5
+ * 隔离面:
6
+ * - os.homedir → 测试临时目录(state/profile 落 TEST_HOME,不触碰真实 ~/.claude)
7
+ * - readTranscriptSessions → fixture 会话(extractCorrectionMatches 等纯函数保持真实)
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { updateUserModel } from '../update-user-model';
13
+ import { readTranscriptSessions, type MinedSession } from '../../session-mining';
14
+
15
+ const TEST_HOME = '/tmp/harness-uum-test-home';
16
+
17
+ jest.mock('os', () => ({
18
+ ...jest.requireActual('os'),
19
+ homedir: () => '/tmp/harness-uum-test-home',
20
+ }));
21
+
22
+ jest.mock('../../session-mining', () => ({
23
+ ...jest.requireActual('../../session-mining'),
24
+ readTranscriptSessions: jest.fn(),
25
+ }));
26
+
27
+ jest.mock('chalk', () => {
28
+ const id = (s: string) => s;
29
+ const chalkFn = Object.assign(id, {
30
+ gray: id,
31
+ blue: id,
32
+ green: id,
33
+ yellow: id,
34
+ cyan: id,
35
+ bold: id,
36
+ red: id,
37
+ });
38
+ return {
39
+ __esModule: true,
40
+ default: chalkFn,
41
+ gray: id,
42
+ blue: id,
43
+ green: id,
44
+ yellow: id,
45
+ cyan: id,
46
+ bold: id,
47
+ red: id,
48
+ };
49
+ });
50
+
51
+ const mockReadTranscriptSessions = readTranscriptSessions as jest.MockedFunction<
52
+ typeof readTranscriptSessions
53
+ >;
54
+
55
+ const STATE_FILE = path.join(TEST_HOME, '.claude', 'user-model-state.json');
56
+
57
+ function todayStr(offsetDays = 0): string {
58
+ return new Date(Date.now() - offsetDays * 86_400_000).toISOString().slice(0, 10);
59
+ }
60
+
61
+ function mkSession(partial: Partial<MinedSession> = {}): MinedSession {
62
+ return {
63
+ id: 'session-1',
64
+ date: todayStr(),
65
+ mtimeMs: Date.now(),
66
+ turns: [
67
+ { role: 'user', content: '数据库迁移方案需要执行' },
68
+ { role: 'assistant', content: '好的' },
69
+ ],
70
+ toolCalls: ['Read'],
71
+ ...partial,
72
+ };
73
+ }
74
+
75
+ function lastJsonOutput(consoleSpy: jest.SpyInstance): Record<string, unknown> {
76
+ const jsonLine = consoleSpy.mock.calls.map(c => c[0]).join('\n');
77
+ return JSON.parse(jsonLine);
78
+ }
79
+
80
+ describe('update-user-model command', () => {
81
+ let consoleSpy: jest.SpyInstance;
82
+
83
+ beforeEach(() => {
84
+ jest.clearAllMocks();
85
+ fs.rmSync(TEST_HOME, { recursive: true, force: true });
86
+ fs.mkdirSync(path.join(TEST_HOME, '.claude', 'projects', '-root-projects', 'memory'), { recursive: true });
87
+ process.env.CLAUDE_TRANSCRIPTS_DIR = path.join(TEST_HOME, 'transcripts');
88
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
89
+ });
90
+
91
+ afterEach(() => {
92
+ consoleSpy.mockRestore();
93
+ delete process.env.CLAUDE_TRANSCRIPTS_DIR;
94
+ });
95
+
96
+ test('无新会话:提示 No new sessions to process', async () => {
97
+ mockReadTranscriptSessions.mockReturnValue([]);
98
+
99
+ await updateUserModel({});
100
+
101
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No new sessions to process'));
102
+ expect(fs.existsSync(STATE_FILE)).toBe(false);
103
+ });
104
+
105
+ test('--json:输出 newSessions 与 new_pattern 变化', async () => {
106
+ mockReadTranscriptSessions.mockReturnValue([
107
+ mkSession({ id: 'session-a' }),
108
+ mkSession({ id: 'session-b' }),
109
+ ]);
110
+
111
+ await updateUserModel({ json: true, dryRun: true });
112
+
113
+ const output = lastJsonOutput(consoleSpy);
114
+ expect(output.newSessions).toBe(2);
115
+ expect(output.changes).toEqual(
116
+ expect.arrayContaining([
117
+ expect.objectContaining({
118
+ type: 'new_pattern',
119
+ key: '数据库迁移方案需要执行',
120
+ }),
121
+ ]),
122
+ );
123
+ });
124
+
125
+ test('--dry-run:只展示变化,不写 state 文件', async () => {
126
+ mockReadTranscriptSessions.mockReturnValue([mkSession({ id: 'session-a' })]);
127
+
128
+ await updateUserModel({ dryRun: true });
129
+
130
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Processed 1 new sessions'));
131
+ expect(fs.existsSync(STATE_FILE)).toBe(false);
132
+ });
133
+
134
+ test('默认(非 dry-run):落盘 state 并记录已处理会话', async () => {
135
+ mockReadTranscriptSessions.mockReturnValue([mkSession({ id: 'session-a' })]);
136
+
137
+ await updateUserModel({});
138
+
139
+ expect(fs.existsSync(STATE_FILE)).toBe(true);
140
+ const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
141
+ expect(state.sessionsProcessed).toContain('session-a');
142
+ });
143
+
144
+ test('--days 1:只处理最近 1 天的会话', async () => {
145
+ mockReadTranscriptSessions.mockReturnValue([
146
+ mkSession({ id: 'today' }),
147
+ mkSession({
148
+ id: 'three-days-ago',
149
+ date: todayStr(3),
150
+ turns: [
151
+ { role: 'user', content: '旧会话概念内容测试' },
152
+ { role: 'assistant', content: '' },
153
+ ],
154
+ }),
155
+ ]);
156
+
157
+ await updateUserModel({ json: true, dryRun: true, days: 1 });
158
+
159
+ const output = lastJsonOutput(consoleSpy);
160
+ expect(output.newSessions).toBe(1);
161
+ });
162
+
163
+ test('缺省 --days:处理全部未处理会话(向后兼容)', async () => {
164
+ mockReadTranscriptSessions.mockReturnValue([
165
+ mkSession({ id: 'today' }),
166
+ mkSession({
167
+ id: 'three-days-ago',
168
+ date: todayStr(3),
169
+ turns: [
170
+ { role: 'user', content: '旧会话概念内容测试' },
171
+ { role: 'assistant', content: '' },
172
+ ],
173
+ }),
174
+ ]);
175
+
176
+ await updateUserModel({ json: true, dryRun: true });
177
+
178
+ const output = lastJsonOutput(consoleSpy);
179
+ expect(output.newSessions).toBe(2);
180
+ });
181
+
182
+ test('sessionsProcessed 去重:已处理会话不重复计入(与 --days 正交)', async () => {
183
+ fs.writeFileSync(
184
+ STATE_FILE,
185
+ JSON.stringify({
186
+ lastUpdated: '',
187
+ sessionsProcessed: ['session-a'],
188
+ patterns: {},
189
+ lensWeights: {},
190
+ principleWeights: {},
191
+ evolutionLog: [],
192
+ }),
193
+ 'utf-8',
194
+ );
195
+ mockReadTranscriptSessions.mockReturnValue([
196
+ mkSession({ id: 'session-a' }),
197
+ mkSession({ id: 'session-b' }),
198
+ ]);
199
+
200
+ await updateUserModel({ json: true, dryRun: true, days: 7 });
201
+
202
+ const output = lastJsonOutput(consoleSpy);
203
+ expect(output.newSessions).toBe(1);
204
+ });
205
+ });
@@ -49,7 +49,6 @@ export async function check(options: CheckOptions): Promise<void> {
49
49
  const merged = configLoader.hasCustomConfig()
50
50
  ? configLoader.mergeConstraints()
51
51
  : configLoader.mergeConstraints({ preset: options.preset || 'standard' });
52
- constraintChecker.setCustomConfig(merged);
53
52
  if (merged.custom.length > 0) {
54
53
  console.log(chalk.gray(`自定义约束: ${merged.custom.length} 条`));
55
54
  }
@@ -73,8 +72,8 @@ export async function check(options: CheckOptions): Promise<void> {
73
72
  }
74
73
  console.log(chalk.gray(`触发条件: ${[context.operation, ...(context.extraTriggers ?? [])].join(', ')}`));
75
74
 
76
- // 执行三层检查
77
- const result = await constraintChecker.checkConstraints(context);
75
+ // 执行三层检查(per-request 传 customConfig,避免单例状态污染)
76
+ const result = await constraintChecker.checkConstraints(context, merged);
78
77
 
79
78
  // 输出结果
80
79
  console.log();
@@ -83,7 +82,6 @@ export async function check(options: CheckOptions): Promise<void> {
83
82
  const skippedResults = [
84
83
  ...result.ironLaws,
85
84
  ...result.guidelines,
86
- ...result.tips,
87
85
  ].filter(r => r.skipped);
88
86
 
89
87
  // Iron Laws
@@ -118,16 +116,6 @@ export async function check(options: CheckOptions): Promise<void> {
118
116
  console.log(chalk.green(`✅ 指导原则: ${passedGuidelines}/${evaluatedGuidelines.length} 通过`));
119
117
  }
120
118
 
121
- // Tips
122
- if (result.tipCount > 0) {
123
- console.log(chalk.blue(`💡 提示: ${result.tipCount} 条`));
124
- result.tips.filter(r => !r.satisfied).forEach(r => {
125
- if (r.constraint) {
126
- console.log(chalk.blue(` - ${r.constraint.id}: ${r.constraint.message}`));
127
- }
128
- });
129
- }
130
-
131
119
  // Skipped:约定未采用 / 证据未接线,未评估(不计通过/失败)
132
120
  if (skippedResults.length > 0) {
133
121
  console.log(chalk.gray(`⏭️ 跳过评估: ${skippedResults.length} 条(约定未采用或证据未接线,不计通过/失败)`));