@dommaker/harness 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/bin/harness-knowledge-capture.js +99 -0
  3. package/bin/harness-knowledge-check.js +149 -0
  4. package/bin/harness-knowledge-track.js +92 -0
  5. package/bin/harness-knowledge-track.sh +47 -0
  6. package/bin/harness-sensitive-check.sh +67 -0
  7. package/dist/cli/commands/init.js +34 -2
  8. package/dist/cli/commands/init.js.map +1 -1
  9. package/package.json +6 -3
  10. package/src/CONTEXT.md +34 -0
  11. package/src/__tests__/acceptance-gate.test.ts +364 -0
  12. package/src/__tests__/annotation-checker.test.ts +183 -0
  13. package/src/__tests__/architecture-engine.test.ts +619 -0
  14. package/src/__tests__/checker-extra.test.ts +1627 -0
  15. package/src/__tests__/checker.test.ts +1268 -0
  16. package/src/__tests__/checkpoint.test.ts +697 -0
  17. package/src/__tests__/clean-state-extra.test.ts +301 -0
  18. package/src/__tests__/clean-state.test.ts +169 -0
  19. package/src/__tests__/constraint-doctor.test.ts +410 -0
  20. package/src/__tests__/constraint-evolver.test.ts +404 -0
  21. package/src/__tests__/constraint-handler.test.ts +136 -0
  22. package/src/__tests__/context/progressive-loader.test.ts +131 -0
  23. package/src/__tests__/contract-gate.test.ts +321 -0
  24. package/src/__tests__/cross-project-checker.test.ts +318 -0
  25. package/src/__tests__/cso-validator.test.ts +205 -0
  26. package/src/__tests__/detect-source-roots.test.ts +98 -0
  27. package/src/__tests__/doctor-evolver.test.ts +381 -0
  28. package/src/__tests__/error-classifier.test.ts +304 -0
  29. package/src/__tests__/exec.test.ts +83 -0
  30. package/src/__tests__/extend-exceptions.test.ts +149 -0
  31. package/src/__tests__/failure-recorder.test.ts +335 -0
  32. package/src/__tests__/hooks-pipeline.test.ts +214 -0
  33. package/src/__tests__/interceptor.test.ts +89 -0
  34. package/src/__tests__/iron-laws.test.ts +246 -0
  35. package/src/__tests__/long-running.test.ts +17 -0
  36. package/src/__tests__/passes-gate-check.test.ts +138 -0
  37. package/src/__tests__/passes-gate-extension.test.ts +151 -0
  38. package/src/__tests__/passes-gate-extra.test.ts +238 -0
  39. package/src/__tests__/passes-gate.test.ts +231 -0
  40. package/src/__tests__/performance-analyzer.test.ts +501 -0
  41. package/src/__tests__/performance-collector.test.ts +276 -0
  42. package/src/__tests__/performance-gate.test.ts +74 -0
  43. package/src/__tests__/presets.test.ts +104 -0
  44. package/src/__tests__/project-config-loader-extra.test.ts +269 -0
  45. package/src/__tests__/project-config-loader.test.ts +127 -0
  46. package/src/__tests__/review-gate.test.ts +92 -0
  47. package/src/__tests__/security-gate.test.ts +100 -0
  48. package/src/__tests__/session-startup.test.ts +334 -0
  49. package/src/__tests__/spec-validator.test.ts +57 -0
  50. package/src/__tests__/token-budget.test.ts +252 -0
  51. package/src/__tests__/trace-analyzer-extra.test.ts +307 -0
  52. package/src/__tests__/trace-analyzer.test.ts +131 -0
  53. package/src/__tests__/trace-collector.test.ts +258 -0
  54. package/src/__tests__/traces.test.ts +347 -0
  55. package/src/agents/CONTEXT.md +21 -0
  56. package/src/agents/__tests__/lifecycle.test.ts +248 -0
  57. package/src/agents/index.ts +2 -0
  58. package/src/agents/lifecycle.ts +201 -0
  59. package/src/agents/types.ts +46 -0
  60. package/src/architecture/CONTEXT.md +22 -0
  61. package/src/architecture/constraint-engine.ts +356 -0
  62. package/src/architecture/cross-project-checker.ts +563 -0
  63. package/src/cli/commands/CONTEXT.md +24 -0
  64. package/src/cli/commands/__tests__/acceptance.test.ts +180 -0
  65. package/src/cli/commands/__tests__/check.test.ts +598 -0
  66. package/src/cli/commands/__tests__/command.test.ts +149 -0
  67. package/src/cli/commands/__tests__/contract.test.ts +151 -0
  68. package/src/cli/commands/__tests__/failure.test.ts +199 -0
  69. package/src/cli/commands/__tests__/flow.test.ts +343 -0
  70. package/src/cli/commands/__tests__/init.test.ts +306 -0
  71. package/src/cli/commands/__tests__/passes-gate.test.ts +256 -0
  72. package/src/cli/commands/__tests__/performance.test.ts +116 -0
  73. package/src/cli/commands/__tests__/posteval-plan.test.ts +201 -0
  74. package/src/cli/commands/__tests__/report-extra.test.ts +98 -0
  75. package/src/cli/commands/__tests__/report.test.ts +109 -0
  76. package/src/cli/commands/__tests__/review.test.ts +153 -0
  77. package/src/cli/commands/__tests__/security.test.ts +147 -0
  78. package/src/cli/commands/__tests__/spec.test.ts +193 -0
  79. package/src/cli/commands/__tests__/status-extra.test.ts +223 -0
  80. package/src/cli/commands/__tests__/status.test.ts +306 -0
  81. package/src/cli/commands/__tests__/sync-docs.test.ts +335 -0
  82. package/src/cli/commands/__tests__/validate.test.ts +139 -0
  83. package/src/cli/commands/acceptance.ts +111 -0
  84. package/src/cli/commands/analyze-sessions.ts +488 -0
  85. package/src/cli/commands/check.ts +441 -0
  86. package/src/cli/commands/command.ts +90 -0
  87. package/src/cli/commands/constraints.ts +86 -0
  88. package/src/cli/commands/contract.ts +168 -0
  89. package/src/cli/commands/failure.ts +117 -0
  90. package/src/cli/commands/flow.ts +275 -0
  91. package/src/cli/commands/index.ts +26 -0
  92. package/src/cli/commands/init.ts +832 -0
  93. package/src/cli/commands/knowledge.ts +391 -0
  94. package/src/cli/commands/passes-gate.ts +158 -0
  95. package/src/cli/commands/performance.ts +108 -0
  96. package/src/cli/commands/posteval-plan.ts +85 -0
  97. package/src/cli/commands/release.ts +159 -0
  98. package/src/cli/commands/report.ts +198 -0
  99. package/src/cli/commands/review.ts +118 -0
  100. package/src/cli/commands/security.ts +122 -0
  101. package/src/cli/commands/spec.ts +158 -0
  102. package/src/cli/commands/status.ts +169 -0
  103. package/src/cli/commands/sync-docs.ts +743 -0
  104. package/src/cli/commands/update-user-model.ts +527 -0
  105. package/src/cli/commands/validate.ts +222 -0
  106. package/src/constraints/__tests__/lifecycle-runner.test.ts +246 -0
  107. package/src/constraints/__tests__/registry.test.ts +196 -0
  108. package/src/constraints/index.ts +3 -0
  109. package/src/constraints/lifecycle-runner.ts +184 -0
  110. package/src/constraints/registry.ts +134 -0
  111. package/src/constraints/types.ts +59 -0
  112. package/src/context/CONTEXT.md +28 -0
  113. package/src/context/__tests__/compaction.test.ts +169 -0
  114. package/src/context/__tests__/file-budget.test.ts +142 -0
  115. package/src/context/__tests__/knowledge-injector.test.ts +148 -0
  116. package/src/context/__tests__/progressive-loader.test.ts +264 -0
  117. package/src/context/__tests__/session-manager.test.ts +300 -0
  118. package/src/context/__tests__/token-budget.test.ts +338 -0
  119. package/src/context/__tests__/token-pipeline.test.ts +178 -0
  120. package/src/context/__tests__/tool-output-budget.test.ts +95 -0
  121. package/src/context/compaction.ts +202 -0
  122. package/src/context/file-budget.ts +142 -0
  123. package/src/context/index.ts +15 -0
  124. package/src/context/knowledge-injector.ts +168 -0
  125. package/src/context/progressive-loader.ts +260 -0
  126. package/src/context/session-manager.ts +343 -0
  127. package/src/context/token-budget.ts +295 -0
  128. package/src/context/token-pipeline.ts +265 -0
  129. package/src/context/tool-output-budget.ts +178 -0
  130. package/src/context/types.ts +160 -0
  131. package/src/core/CONTEXT.md +26 -0
  132. package/src/core/constraints/__tests__/default-executors.test.ts +57 -0
  133. package/src/core/constraints/__tests__/interceptor.test.ts +251 -0
  134. package/src/core/constraints/__tests__/prompt-injection.test.ts +89 -0
  135. package/src/core/constraints/check-cache.ts +96 -0
  136. package/src/core/constraints/checker.ts +1169 -0
  137. package/src/core/constraints/default-executors.ts +92 -0
  138. package/src/core/constraints/definitions.ts +1119 -0
  139. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +551 -0
  140. package/src/core/constraints/doc-freshness/auto-fix.ts +69 -0
  141. package/src/core/constraints/doc-freshness/runner.ts +486 -0
  142. package/src/core/constraints/index.ts +78 -0
  143. package/src/core/constraints/interceptor.ts +220 -0
  144. package/src/core/constraints/prompt-injection.ts +73 -0
  145. package/src/core/index.ts +23 -0
  146. package/src/core/project-config-loader.ts +252 -0
  147. package/src/core/session/__tests__/startup.test.ts +281 -0
  148. package/src/core/session/clean-state.ts +178 -0
  149. package/src/core/session/index.ts +15 -0
  150. package/src/core/session/startup.ts +253 -0
  151. package/src/core/spec/__tests__/validator.test.ts +379 -0
  152. package/src/core/spec/index.ts +7 -0
  153. package/src/core/spec/validator.ts +383 -0
  154. package/src/core/validators/__tests__/passes-gate.test.ts +131 -0
  155. package/src/core/validators/checkpoint.ts +547 -0
  156. package/src/core/validators/cso.ts +124 -0
  157. package/src/core/validators/index.ts +25 -0
  158. package/src/core/validators/passes-gate.ts +613 -0
  159. package/src/dashboard/CONTEXT.md +21 -0
  160. package/src/dashboard/__tests__/data.test.ts +115 -0
  161. package/src/dashboard/__tests__/stats.test.ts +156 -0
  162. package/src/dashboard/data.ts +113 -0
  163. package/src/dashboard/index.ts +3 -0
  164. package/src/dashboard/stats.ts +127 -0
  165. package/src/dashboard/types.ts +113 -0
  166. package/src/evolution/CONTEXT.md +22 -0
  167. package/src/evolution/__tests__/auto-evolve.test.ts +320 -0
  168. package/src/evolution/auto-evolve.ts +98 -0
  169. package/src/evolution/index.ts +1 -0
  170. package/src/failure/CONTEXT.md +24 -0
  171. package/src/failure/classifier.ts +156 -0
  172. package/src/failure/constraint-handler.ts +173 -0
  173. package/src/failure/index.ts +35 -0
  174. package/src/failure/recorder.ts +201 -0
  175. package/src/failure/types.ts +165 -0
  176. package/src/gates/CONTEXT.md +27 -0
  177. package/src/gates/__tests__/acceptance.test.ts +608 -0
  178. package/src/gates/__tests__/command.test.ts +206 -0
  179. package/src/gates/__tests__/performance.test.ts +381 -0
  180. package/src/gates/__tests__/review.test.ts +302 -0
  181. package/src/gates/__tests__/security.test.ts +332 -0
  182. package/src/gates/acceptance.ts +493 -0
  183. package/src/gates/command.ts +444 -0
  184. package/src/gates/contract.ts +260 -0
  185. package/src/gates/index.ts +67 -0
  186. package/src/gates/performance.ts +359 -0
  187. package/src/gates/review.ts +169 -0
  188. package/src/gates/security.ts +244 -0
  189. package/src/gates/types.ts +159 -0
  190. package/src/governance/CONTEXT.md +22 -0
  191. package/src/governance/__tests__/executor.test.ts +249 -0
  192. package/src/governance/executor.ts +229 -0
  193. package/src/governance/index.ts +13 -0
  194. package/src/governance/types.ts +53 -0
  195. package/src/hooks/CONTEXT.md +23 -0
  196. package/src/hooks/__tests__/bootstrap.test.ts +184 -0
  197. package/src/hooks/bootstrap.ts +144 -0
  198. package/src/hooks/index.ts +15 -0
  199. package/src/hooks/pipeline.ts +157 -0
  200. package/src/hooks/registry.ts +88 -0
  201. package/src/hooks/types.ts +80 -0
  202. package/src/index.ts +198 -0
  203. package/src/knowledge/CONTEXT.md +29 -0
  204. package/src/knowledge/__tests__/doctor.test.ts +272 -0
  205. package/src/knowledge/__tests__/import.test.ts +376 -0
  206. package/src/knowledge/__tests__/ingest.test.ts +125 -0
  207. package/src/knowledge/__tests__/lifecycle-hooks.test.ts +137 -0
  208. package/src/knowledge/__tests__/lifecycle.test.ts +248 -0
  209. package/src/knowledge/__tests__/lint.test.ts +401 -0
  210. package/src/knowledge/__tests__/query.test.ts +185 -0
  211. package/src/knowledge/__tests__/reference-tracker.test.ts +129 -0
  212. package/src/knowledge/__tests__/store.test.ts +238 -0
  213. package/src/knowledge/doctor.ts +104 -0
  214. package/src/knowledge/import.ts +505 -0
  215. package/src/knowledge/index.ts +16 -0
  216. package/src/knowledge/ingest.ts +126 -0
  217. package/src/knowledge/lifecycle-hooks.ts +199 -0
  218. package/src/knowledge/lifecycle.ts +175 -0
  219. package/src/knowledge/lint.ts +360 -0
  220. package/src/knowledge/query.ts +150 -0
  221. package/src/knowledge/reference-tracker.ts +99 -0
  222. package/src/knowledge/store.ts +264 -0
  223. package/src/knowledge/types.ts +139 -0
  224. package/src/llm/CONTEXT.md +21 -0
  225. package/src/llm/__tests__/adapter.test.ts +172 -0
  226. package/src/llm/adapter.ts +149 -0
  227. package/src/llm/index.ts +2 -0
  228. package/src/llm/types.ts +81 -0
  229. package/src/monitoring/CONTEXT.md +29 -0
  230. package/src/monitoring/__tests__/context-tracker.test.ts +172 -0
  231. package/src/monitoring/__tests__/knowledge-doctor.test.ts +276 -0
  232. package/src/monitoring/__tests__/knowledge-evolver.test.ts +365 -0
  233. package/src/monitoring/__tests__/performance-analyzer.test.ts +137 -0
  234. package/src/monitoring/__tests__/performance-collector.test.ts +332 -0
  235. package/src/monitoring/__tests__/traces.test.ts +190 -0
  236. package/src/monitoring/constraint-doctor.ts +499 -0
  237. package/src/monitoring/constraint-evolver.ts +573 -0
  238. package/src/monitoring/context-tracker.ts +148 -0
  239. package/src/monitoring/index.ts +23 -0
  240. package/src/monitoring/knowledge-doctor.ts +323 -0
  241. package/src/monitoring/knowledge-evolver.ts +289 -0
  242. package/src/monitoring/performance-analyzer.ts +450 -0
  243. package/src/monitoring/performance-collector.ts +348 -0
  244. package/src/monitoring/trace-analyzer.ts +470 -0
  245. package/src/monitoring/traces.ts +339 -0
  246. package/src/presets/__tests__/standard.test.ts +104 -0
  247. package/src/presets/index.ts +16 -0
  248. package/src/presets/standard.ts +163 -0
  249. package/src/safety/CONTEXT.md +23 -0
  250. package/src/safety/__tests__/input-guardrail.test.ts +105 -0
  251. package/src/safety/__tests__/output-guardrail.test.ts +108 -0
  252. package/src/safety/__tests__/sandbox.test.ts +158 -0
  253. package/src/safety/__tests__/tool-guardrail.test.ts +127 -0
  254. package/src/safety/index.ts +5 -0
  255. package/src/safety/input-guardrail.ts +133 -0
  256. package/src/safety/output-guardrail.ts +174 -0
  257. package/src/safety/sandbox.ts +148 -0
  258. package/src/safety/tool-guardrail.ts +176 -0
  259. package/src/safety/types.ts +124 -0
  260. package/src/spec/CONTEXT.md +19 -0
  261. package/src/spec/__tests__/annotation-checker.test.ts +289 -0
  262. package/src/spec/annotation-checker.ts +342 -0
  263. package/src/tools/CONTEXT.md +25 -0
  264. package/src/tools/__tests__/loader.test.ts +252 -0
  265. package/src/tools/__tests__/registry.test.ts +157 -0
  266. package/src/tools/core/__tests__/core.test.ts +122 -0
  267. package/src/tools/core/index.ts +201 -0
  268. package/src/tools/core/package.json +7 -0
  269. package/src/tools/core/pnpm-lock.yaml +232 -0
  270. package/src/tools/definitions/core/code/fingerprint.sh +273 -0
  271. package/src/tools/definitions/core/code/fingerprint.yml +72 -0
  272. package/src/tools/definitions/core/code/parse.yml +68 -0
  273. package/src/tools/definitions/core/code/register-language.sh +475 -0
  274. package/src/tools/definitions/core/code/register-language.yml +74 -0
  275. package/src/tools/definitions/core/docker/build.yml +73 -0
  276. package/src/tools/definitions/core/docker/run.yml +81 -0
  277. package/src/tools/definitions/core/file/copy.yml +88 -0
  278. package/src/tools/definitions/core/file/read.yml +69 -0
  279. package/src/tools/definitions/core/file/write.yml +75 -0
  280. package/src/tools/definitions/core/git/branch.yml +61 -0
  281. package/src/tools/definitions/core/git/cleanup-worktree.yml +50 -0
  282. package/src/tools/definitions/core/git/clone.yml +69 -0
  283. package/src/tools/definitions/core/git/commit.yml +65 -0
  284. package/src/tools/definitions/core/git/push.yml +79 -0
  285. package/src/tools/definitions/core/git/worktree.yml +96 -0
  286. package/src/tools/definitions/core/notification/notify.sh +152 -0
  287. package/src/tools/definitions/core/notification/notify.yml +60 -0
  288. package/src/tools/definitions/core/npm/build.yml +75 -0
  289. package/src/tools/definitions/core/npm/install.yml +58 -0
  290. package/src/tools/definitions/core/npm/test.yml +75 -0
  291. package/src/tools/definitions/core/validation/check-reuse.yml +170 -0
  292. package/src/tools/definitions/core/validation/validate-handlers.yml +118 -0
  293. package/src/tools/definitions/core/validation/validate-readme-stats.yml +155 -0
  294. package/src/tools/definitions/core/validation/validate-yaml-syntax.yml +134 -0
  295. package/src/tools/definitions/ext/browser/browser-automate.sh +255 -0
  296. package/src/tools/definitions/ext/browser/browser-automate.yml +166 -0
  297. package/src/tools/definitions/ext/browser/browser-close.sh +104 -0
  298. package/src/tools/definitions/ext/browser/browser-close.yml +55 -0
  299. package/src/tools/definitions/ext/browser/browser-mcp-debug.sh +682 -0
  300. package/src/tools/definitions/ext/browser/browser-mcp-debug.yml +167 -0
  301. package/src/tools/definitions/ext/browser/browser-start.sh +101 -0
  302. package/src/tools/definitions/ext/browser/browser-start.yml +62 -0
  303. package/src/tools/definitions/registry.json +795 -0
  304. package/src/tools/definitions/std/README.md +350 -0
  305. package/src/tools/definitions/std/analysis/analyze-codebase.yml +72 -0
  306. package/src/tools/definitions/std/analysis/analyze-diff.yml +182 -0
  307. package/src/tools/definitions/std/analysis/analyze-git-diff.yml +70 -0
  308. package/src/tools/definitions/std/analysis/analyze-impact.yml +78 -0
  309. package/src/tools/definitions/std/analysis/analyze-requirements.yml +105 -0
  310. package/src/tools/definitions/std/analysis/brainstorm-options.yml +106 -0
  311. package/src/tools/definitions/std/analysis/build-graph.yml +154 -0
  312. package/src/tools/definitions/std/analysis/clarify-requirements.yml +90 -0
  313. package/src/tools/definitions/std/analysis/detect-context.yml +109 -0
  314. package/src/tools/definitions/std/analysis/detect-layers.yml +125 -0
  315. package/src/tools/definitions/std/analysis/generate-tour.yml +102 -0
  316. package/src/tools/definitions/std/analysis/search-enhance.yml +105 -0
  317. package/src/tools/definitions/std/backlog/add.yml +51 -0
  318. package/src/tools/definitions/std/backlog/decide.yml +39 -0
  319. package/src/tools/definitions/std/backlog/github-sync.yml +161 -0
  320. package/src/tools/definitions/std/backlog/list.yml +53 -0
  321. package/src/tools/definitions/std/backlog/load.yml +46 -0
  322. package/src/tools/definitions/std/backlog/resolve.yml +37 -0
  323. package/src/tools/definitions/std/backlog/update.yml +42 -0
  324. package/src/tools/definitions/std/bugfix/diagnose.yml +68 -0
  325. package/src/tools/definitions/std/bugfix/fix.yml +66 -0
  326. package/src/tools/definitions/std/bugfix/verify.yml +69 -0
  327. package/src/tools/definitions/std/constraint/check-decomposition.yml +103 -0
  328. package/src/tools/definitions/std/constraint/check-feature-list.yml +77 -0
  329. package/src/tools/definitions/std/constraint/check-progress.yml +97 -0
  330. package/src/tools/definitions/std/constraint/generate-report.yml +147 -0
  331. package/src/tools/definitions/std/deploy/commit-push.yml +33 -0
  332. package/src/tools/definitions/std/deploy/deploy-backend.yml +37 -0
  333. package/src/tools/definitions/std/deploy/deploy-frontend.yml +37 -0
  334. package/src/tools/definitions/std/deploy/rollback.yml +148 -0
  335. package/src/tools/definitions/std/design/analyze-architecture-impact.yml +113 -0
  336. package/src/tools/definitions/std/design/design-api.yml +55 -0
  337. package/src/tools/definitions/std/design/design-architecture.yml +97 -0
  338. package/src/tools/definitions/std/design/design-db.yml +81 -0
  339. package/src/tools/definitions/std/design/generate-spec.yml +182 -0
  340. package/src/tools/definitions/std/development/develop-api.yml +85 -0
  341. package/src/tools/definitions/std/development/develop-backend.yml +88 -0
  342. package/src/tools/definitions/std/development/develop-frontend.yml +87 -0
  343. package/src/tools/definitions/std/development/develop-shared-types.yml +74 -0
  344. package/src/tools/definitions/std/development/develop-task.yml +122 -0
  345. package/src/tools/definitions/std/development/develop-tests.yml +76 -0
  346. package/src/tools/definitions/std/development/execute-batch.yml +152 -0
  347. package/src/tools/definitions/std/development/fix-issues.yml +100 -0
  348. package/src/tools/definitions/std/development/implement-task.yml +124 -0
  349. package/src/tools/definitions/std/development/review-fix-cycle.yml +232 -0
  350. package/src/tools/definitions/std/evolution/implement.yml +125 -0
  351. package/src/tools/definitions/std/evolution/prioritize.yml +112 -0
  352. package/src/tools/definitions/std/evolution/report-gap.yml +166 -0
  353. package/src/tools/definitions/std/file/read.yml +21 -0
  354. package/src/tools/definitions/std/governance/aggregate-opinions.sh +201 -0
  355. package/src/tools/definitions/std/governance/aggregate-opinions.yml +79 -0
  356. package/src/tools/definitions/std/governance/audit-task.yml +178 -0
  357. package/src/tools/definitions/std/governance/cast-vote.yml +129 -0
  358. package/src/tools/definitions/std/governance/collect-issues.yml +16 -0
  359. package/src/tools/definitions/std/governance/create-signed-document.yml +23 -0
  360. package/src/tools/definitions/std/governance/create-voting-session.yml +101 -0
  361. package/src/tools/definitions/std/governance/define-stance.sh +243 -0
  362. package/src/tools/definitions/std/governance/define-stance.yml +82 -0
  363. package/src/tools/definitions/std/governance/generate-review-report.yml +23 -0
  364. package/src/tools/definitions/std/governance/impeach.yml +179 -0
  365. package/src/tools/definitions/std/governance/multi-stance-review.yml +154 -0
  366. package/src/tools/definitions/std/governance/prepare-artifact.yml +20 -0
  367. package/src/tools/definitions/std/governance/review-spec-approval.yml +262 -0
  368. package/src/tools/definitions/std/governance/rollback.yml +186 -0
  369. package/src/tools/definitions/std/governance/stance-review.yml +149 -0
  370. package/src/tools/definitions/std/governance/track-effect.yml +199 -0
  371. package/src/tools/definitions/std/governance/vote-tally.yml +144 -0
  372. package/src/tools/definitions/std/patch/execute.yml +51 -0
  373. package/src/tools/definitions/std/planning/generate-tasks.yml +36 -0
  374. package/src/tools/definitions/std/planning/load-tasks.yml +44 -0
  375. package/src/tools/definitions/std/planning/split-batch.yml +481 -0
  376. package/src/tools/definitions/std/planning/split-tasks.yml +359 -0
  377. package/src/tools/definitions/std/planning/update-tasks.yml +51 -0
  378. package/src/tools/definitions/std/planning/validate-tasks.yml +46 -0
  379. package/src/tools/definitions/std/project/decide-next-workflow.yml +38 -0
  380. package/src/tools/definitions/std/project/load-state.yml +31 -0
  381. package/src/tools/definitions/std/project/save-state.yml +32 -0
  382. package/src/tools/definitions/std/quality/check-quality.yml +241 -0
  383. package/src/tools/definitions/std/quality/debug-systematic.yml +163 -0
  384. package/src/tools/definitions/std/quality/handle-feedback.yml +108 -0
  385. package/src/tools/definitions/std/quality/review-code.yml +128 -0
  386. package/src/tools/definitions/std/quality/review-spec.yml +140 -0
  387. package/src/tools/definitions/std/quality/review-task.yml +190 -0
  388. package/src/tools/definitions/std/quality/run-tests.yml +80 -0
  389. package/src/tools/definitions/std/quick/analyze.yml +56 -0
  390. package/src/tools/definitions/std/quick/implement.yml +67 -0
  391. package/src/tools/definitions/std/quick/verify.yml +71 -0
  392. package/src/tools/definitions/std/verification/verify_completion.yml +113 -0
  393. package/src/tools/definitions/std/verification/wait-condition.yml +107 -0
  394. package/src/tools/index.ts +5 -0
  395. package/src/tools/loader.ts +216 -0
  396. package/src/tools/paths.ts +15 -0
  397. package/src/tools/registry.ts +128 -0
  398. package/src/tools/types.ts +46 -0
  399. package/src/types/__tests__/types.test.ts +86 -0
  400. package/src/types/checkpoint.ts +174 -0
  401. package/src/types/constraint.ts +335 -0
  402. package/src/types/cso.ts +37 -0
  403. package/src/types/enforcement.ts +48 -0
  404. package/src/types/index.ts +59 -0
  405. package/src/types/iron-law.ts +50 -0
  406. package/src/types/passes-gate.ts +185 -0
  407. package/src/types/performance.ts +295 -0
  408. package/src/types/project-config.ts +251 -0
  409. package/src/types/session.ts +132 -0
  410. package/src/types/spec.ts +91 -0
  411. package/src/types/trace.ts +228 -0
  412. package/src/utils/detect-source-roots.ts +114 -0
  413. package/src/utils/exec.ts +61 -0
  414. package/src/verification/CONTEXT.md +23 -0
  415. package/src/verification/__tests__/loop.test.ts +303 -0
  416. package/src/verification/__tests__/rules-based.test.ts +240 -0
  417. package/src/verification/index.ts +3 -0
  418. package/src/verification/loop.ts +137 -0
  419. package/src/verification/rules-based.ts +153 -0
  420. package/src/verification/types.ts +81 -0
@@ -0,0 +1,1627 @@
1
+ /**
2
+ * checker.ts 补充测试
3
+ *
4
+ * 目标:覆盖 checkTestCoverage、checkNoSimplificationWithoutApproval、
5
+ * checkCapabilitySync 完整流程、beforeExecution、findApplicableConstraints
6
+ */
7
+
8
+ import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
9
+ import {
10
+ ConstraintChecker,
11
+ checkConstraints,
12
+ checkBeforeExecution,
13
+ } from '../core/constraints/checker';
14
+ import type { ConstraintContext } from '../types/constraint';
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import { execSync } from 'child_process';
18
+
19
+ describe('ConstraintChecker - 补充覆盖', () => {
20
+ const tempDir = path.join(process.cwd(), 'temp-test-checker-extra');
21
+ const checker = ConstraintChecker.getInstance();
22
+
23
+ beforeAll(() => {
24
+ // 创建临时 git 仓库
25
+ fs.mkdirSync(tempDir, { recursive: true });
26
+ execSync('git init', { cwd: tempDir });
27
+ execSync('git config user.email "test@test.com"', { cwd: tempDir });
28
+ execSync('git config user.name "Test"', { cwd: tempDir });
29
+
30
+ // 初始提交
31
+ const initialFile = path.join(tempDir, 'initial.txt');
32
+ fs.writeFileSync(initialFile, 'initial');
33
+ execSync('git add .', { cwd: tempDir });
34
+ execSync('git commit -m "init"', { cwd: tempDir });
35
+ });
36
+
37
+ afterAll(() => {
38
+ try {
39
+ fs.rmSync(tempDir, { recursive: true, force: true });
40
+ } catch {
41
+ // ignore
42
+ }
43
+ });
44
+
45
+ describe('checkTestCoverage', () => {
46
+ it('无 coverage 报告应该通过', async () => {
47
+ const context: ConstraintContext = {
48
+ operation: 'commit',
49
+ projectPath: tempDir,
50
+ };
51
+
52
+ const result = await checker.check(
53
+ {
54
+ id: 'test_coverage_required',
55
+ level: 'guideline',
56
+ rule: 'COVERAGE',
57
+ message: 'test',
58
+ trigger: 'commit',
59
+ enforcement: 'test',
60
+ },
61
+ context
62
+ );
63
+
64
+ expect(result.satisfied).toBe(true);
65
+ });
66
+
67
+ it('有 coverage-summary.json 且达标应该通过', async () => {
68
+ // 创建 coverage 目录和报告
69
+ const coverageDir = path.join(tempDir, 'coverage');
70
+ fs.mkdirSync(coverageDir, { recursive: true });
71
+
72
+ const summaryPath = path.join(coverageDir, 'coverage-summary.json');
73
+ fs.writeFileSync(
74
+ summaryPath,
75
+ JSON.stringify({
76
+ total: {
77
+ lines: { pct: 80 },
78
+ statements: { pct: 80 },
79
+ branches: { pct: 75 },
80
+ functions: { pct: 85 },
81
+ },
82
+ })
83
+ );
84
+
85
+ const context: ConstraintContext = {
86
+ operation: 'commit',
87
+ projectPath: tempDir,
88
+ };
89
+
90
+ const result = await checker.check(
91
+ {
92
+ id: 'test_coverage_required',
93
+ level: 'guideline',
94
+ rule: 'COVERAGE',
95
+ message: 'test',
96
+ trigger: 'commit',
97
+ enforcement: 'test',
98
+ },
99
+ context
100
+ );
101
+
102
+ expect(result.satisfied).toBe(true);
103
+
104
+ // 清理
105
+ fs.rmSync(coverageDir, { recursive: true, force: true });
106
+ });
107
+
108
+ it('覆盖率低于 50% 应该失败', async () => {
109
+ const coverageDir = path.join(tempDir, 'coverage');
110
+ fs.mkdirSync(coverageDir, { recursive: true });
111
+
112
+ const summaryPath = path.join(coverageDir, 'coverage-summary.json');
113
+ fs.writeFileSync(
114
+ summaryPath,
115
+ JSON.stringify({
116
+ total: {
117
+ lines: { pct: 30 },
118
+ },
119
+ })
120
+ );
121
+
122
+ const context: ConstraintContext = {
123
+ operation: 'commit',
124
+ projectPath: tempDir,
125
+ };
126
+
127
+ const result = await checker.check(
128
+ {
129
+ id: 'test_coverage_required',
130
+ level: 'guideline',
131
+ rule: 'COVERAGE',
132
+ message: 'test',
133
+ trigger: 'commit',
134
+ enforcement: 'test',
135
+ },
136
+ context
137
+ );
138
+
139
+ expect(result.satisfied).toBe(false);
140
+
141
+ fs.rmSync(coverageDir, { recursive: true, force: true });
142
+ });
143
+
144
+ it('coverage-summary.json 解析失败应该通过', async () => {
145
+ const coverageDir = path.join(tempDir, 'coverage');
146
+ fs.mkdirSync(coverageDir, { recursive: true });
147
+
148
+ const summaryPath = path.join(coverageDir, 'coverage-summary.json');
149
+ fs.writeFileSync(summaryPath, 'invalid json {');
150
+
151
+ const context: ConstraintContext = {
152
+ operation: 'commit',
153
+ projectPath: tempDir,
154
+ };
155
+
156
+ const result = await checker.check(
157
+ {
158
+ id: 'test_coverage_required',
159
+ level: 'guideline',
160
+ rule: 'COVERAGE',
161
+ message: 'test',
162
+ trigger: 'commit',
163
+ enforcement: 'test',
164
+ },
165
+ context
166
+ );
167
+
168
+ // 解析失败默认通过
169
+ expect(result.satisfied).toBe(true);
170
+
171
+ fs.rmSync(coverageDir, { recursive: true, force: true });
172
+ });
173
+ });
174
+
175
+ describe('checkNoSimplificationWithoutApproval', () => {
176
+ it('正常 diff 应该通过', async () => {
177
+ // clean state
178
+ execSync('git checkout .', { cwd: tempDir, stdio: 'pipe' });
179
+
180
+ const normalFile = path.join(tempDir, 'normal.ts');
181
+ fs.writeFileSync(normalFile, 'export const x = 1;');
182
+
183
+ const context: ConstraintContext = {
184
+ operation: 'commit',
185
+ projectPath: tempDir,
186
+ };
187
+
188
+ const result = await checker.check(
189
+ {
190
+ id: 'no_simplification_without_approval',
191
+ level: 'guideline',
192
+ rule: 'NO SIMPLIFICATION',
193
+ message: 'test',
194
+ trigger: 'commit',
195
+ enforcement: 'test',
196
+ },
197
+ context
198
+ );
199
+
200
+ expect(result.satisfied).toBe(true);
201
+ });
202
+
203
+ it('包含简化关键词应该失败', async () => {
204
+ // 先完成之前的提交
205
+ execSync('git add .', { cwd: tempDir });
206
+ execSync('git commit -m "cleanup" --allow-empty', { cwd: tempDir });
207
+
208
+ // 创建包含简化关键词的文件并 stage
209
+ const simplifyFile = path.join(tempDir, 'simplify-msg.ts');
210
+ fs.writeFileSync(simplifyFile, '// removed test for simplicity\nexport const y = 2;');
211
+ execSync('git add .', { cwd: tempDir });
212
+
213
+ const context: ConstraintContext = {
214
+ operation: 'commit',
215
+ projectPath: tempDir,
216
+ };
217
+
218
+ const result = await checker.check(
219
+ {
220
+ id: 'no_simplification_without_approval',
221
+ level: 'guideline',
222
+ rule: 'NO SIMPLIFICATION',
223
+ message: 'test',
224
+ trigger: 'commit',
225
+ enforcement: 'test',
226
+ },
227
+ context
228
+ );
229
+
230
+ expect(result.satisfied).toBe(false);
231
+
232
+ // cleanup - unstage and remove
233
+ execSync('git reset HEAD -- ' + simplifyFile, { cwd: tempDir, stdio: 'pipe' });
234
+ fs.rmSync(simplifyFile, { force: true });
235
+ });
236
+ });
237
+
238
+ describe('checkCapabilitySync', () => {
239
+ it('无代码变更应该通过', async () => {
240
+ // 清理 staged changes
241
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
242
+
243
+ const context: ConstraintContext = {
244
+ operation: 'commit',
245
+ projectPath: tempDir,
246
+ };
247
+
248
+ const result = await checker.check(
249
+ {
250
+ id: 'capability_sync',
251
+ level: 'guideline',
252
+ rule: 'CAPABILITY SYNC',
253
+ message: 'test',
254
+ trigger: 'commit',
255
+ enforcement: 'test',
256
+ },
257
+ context
258
+ );
259
+
260
+ expect(result.satisfied).toBe(true);
261
+ });
262
+
263
+ it('有代码变更且有 CAPABILITIES.md 应该通过', async () => {
264
+ // 创建 CAPABILITIES.md(如果不存在)
265
+ const capFile = path.join(tempDir, 'CAPABILITIES.md');
266
+ fs.writeFileSync(capFile, '# Capabilities\n\n- Feature: test');
267
+
268
+ // 创建代码文件并 stage
269
+ const codeFile = path.join(tempDir, 'feature.ts');
270
+ fs.writeFileSync(codeFile, 'export function feature() {}');
271
+ execSync('git add .', { cwd: tempDir });
272
+
273
+ const context: ConstraintContext = {
274
+ operation: 'commit',
275
+ projectPath: tempDir,
276
+ };
277
+
278
+ const result = await checker.check(
279
+ {
280
+ id: 'capability_sync',
281
+ level: 'guideline',
282
+ rule: 'CAPABILITY SYNC',
283
+ message: 'test',
284
+ trigger: 'commit',
285
+ enforcement: 'test',
286
+ },
287
+ context
288
+ );
289
+
290
+ expect(result.satisfied).toBe(true);
291
+
292
+ // Cleanup
293
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
294
+ });
295
+
296
+ it('有代码变更且 CAPABILITIES.md 有表格但缺少新模块应该失败', async () => {
297
+ // 创建 CAPABILITIES.md,含表格但不包含新文件
298
+ const capFile = path.join(tempDir, 'CAPABILITIES.md');
299
+ fs.writeFileSync(capFile, '# Capabilities\n\n| 模块 | 文件 | 功能 |\n|------|------|------|\n| 旧模块 | old/module.ts | 旧功能 |');
300
+
301
+ // 创建新的代码文件并 stage
302
+ const codeFile = path.join(tempDir, 'new-module.ts');
303
+ fs.writeFileSync(codeFile, 'export function newModule() {}');
304
+ execSync('git add .', { cwd: tempDir, stdio: 'pipe' });
305
+
306
+ const context: ConstraintContext = {
307
+ operation: 'commit',
308
+ projectPath: tempDir,
309
+ };
310
+
311
+ const result = await checker.check(
312
+ {
313
+ id: 'capability_sync',
314
+ level: 'guideline',
315
+ rule: 'CAPABILITY SYNC',
316
+ message: 'test',
317
+ trigger: 'commit',
318
+ enforcement: 'test',
319
+ },
320
+ context
321
+ );
322
+
323
+ // CAPABILITIES.md 有表格但新文件未被覆盖,应该失败
324
+ expect(result.satisfied).toBe(false);
325
+
326
+ // Cleanup
327
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
328
+ });
329
+
330
+ it('有代码变更但无 CAPABILITIES.md 应该失败', async () => {
331
+ // 确保 CAPABILITIES.md 不存在
332
+ const capFile = path.join(tempDir, 'CAPABILITIES.md');
333
+ if (fs.existsSync(capFile)) {
334
+ fs.rmSync(capFile, { force: true });
335
+ }
336
+
337
+ // 创建新的代码文件并 stage
338
+ const newCodeFile = path.join(tempDir, 'newfeature.ts');
339
+ fs.writeFileSync(newCodeFile, 'export function newfeature() {}');
340
+ execSync('git add ' + newCodeFile, { cwd: tempDir });
341
+
342
+ const context: ConstraintContext = {
343
+ operation: 'commit',
344
+ projectPath: tempDir,
345
+ };
346
+
347
+ const result = await checker.check(
348
+ {
349
+ id: 'capability_sync',
350
+ level: 'guideline',
351
+ rule: 'CAPABILITY SYNC',
352
+ message: 'test',
353
+ trigger: 'commit',
354
+ enforcement: 'test',
355
+ },
356
+ context
357
+ );
358
+
359
+ // 没有 CAPABILITIES.md,应该失败
360
+ expect(result.satisfied).toBe(false);
361
+
362
+ // Cleanup
363
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
364
+ });
365
+ });
366
+
367
+ describe('findApplicableConstraints', () => {
368
+ it('应该过滤出匹配 trigger 的约束', () => {
369
+ const context: ConstraintContext = {
370
+ operation: 'task_completion_claim',
371
+ };
372
+
373
+ const result = checker.findApplicableConstraints(context);
374
+
375
+ expect(result.ironLaws.length).toBeGreaterThan(0);
376
+ expect(
377
+ result.ironLaws.some((c: any) => c.id === 'no_self_approval')
378
+ ).toBe(true);
379
+ });
380
+
381
+ it('不匹配的 trigger 应该返回空数组', () => {
382
+ const context: ConstraintContext = {
383
+ operation: 'file_creation', // 一个很少用到的 trigger
384
+ };
385
+
386
+ const result = checker.findApplicableConstraints(context);
387
+
388
+ // file_creation 可能没有任何约束匹配
389
+ // 这个测试的目的是验证 filterByTrigger 逻辑
390
+ expect(Array.isArray(result.ironLaws)).toBe(true);
391
+ expect(Array.isArray(result.guidelines)).toBe(true);
392
+ expect(Array.isArray(result.tips)).toBe(true);
393
+ });
394
+ });
395
+
396
+ describe('checkBeforeExecution', () => {
397
+ it('通过检查不应该抛出异常', async () => {
398
+ const context: ConstraintContext = {
399
+ operation: 'code_implementation',
400
+ hasTest: true,
401
+ hasVerificationEvidence: true,
402
+ hasWorktree: true,
403
+ hasRequirement: true,
404
+ taskDescription: 'Test task — single focused change',
405
+ hasSingleTask: true,
406
+ hasRequirementReview: true,
407
+ hasTwoStageReview: true,
408
+ completionClaimText: 'All 142 tests passed, coverage 87.3%',
409
+ } as any;
410
+
411
+ // 不应该抛出异常
412
+ await expect(checkBeforeExecution(context)).resolves.not.toThrow();
413
+ });
414
+
415
+ it('违规应该抛出 ConstraintViolationError', async () => {
416
+ const context: ConstraintContext = {
417
+ operation: 'code_implementation',
418
+ hasTest: false,
419
+ hasVerificationEvidence: false,
420
+ };
421
+
422
+ await expect(checkBeforeExecution(context)).rejects.toThrow();
423
+ });
424
+ });
425
+
426
+ describe('checkConstraints 完整流程', () => {
427
+ it('应该返回完整的三层检查结果', async () => {
428
+ const context: ConstraintContext = {
429
+ operation: 'commit',
430
+ projectPath: tempDir,
431
+ hasTest: true,
432
+ hasReuseCheck: true,
433
+ };
434
+
435
+ const result = await checkConstraints(context);
436
+
437
+ expect(result.ironLaws).toBeDefined();
438
+ expect(result.guidelines).toBeDefined();
439
+ expect(result.tips).toBeDefined();
440
+ expect(typeof result.passed).toBe('boolean');
441
+ expect(typeof result.warningCount).toBe('number');
442
+ expect(typeof result.tipCount).toBe('number');
443
+ });
444
+ });
445
+
446
+ describe('自定义约束配置', () => {
447
+ it('setCustomConfig 应该生效', () => {
448
+ checker.setCustomConfig({
449
+ ironLaws: {},
450
+ guidelines: {},
451
+ tips: {},
452
+ disabled: [],
453
+ custom: [],
454
+ });
455
+
456
+ const constraints = checker.getConstraints();
457
+
458
+ expect(constraints.ironLaws).toEqual({});
459
+ expect(constraints.guidelines).toEqual({});
460
+ expect(constraints.tips).toEqual({});
461
+
462
+ // 重置为默认
463
+ (checker as any).customConfig = null;
464
+ });
465
+ });
466
+
467
+ describe('getSeverity', () => {
468
+ it('iron_law 应该返回 error', async () => {
469
+ const context: ConstraintContext = {
470
+ operation: 'code_implementation',
471
+ hasTest: true,
472
+ };
473
+
474
+ const result = await checker.check(
475
+ {
476
+ id: 'test_severity_iron',
477
+ level: 'iron_law',
478
+ rule: 'TEST',
479
+ message: 'test',
480
+ trigger: 'code_implementation',
481
+ enforcement: 'test',
482
+ },
483
+ context
484
+ );
485
+
486
+ expect(result).toBeDefined();
487
+ });
488
+
489
+ it('guideline 应该返回 warning', async () => {
490
+ const context: ConstraintContext = {
491
+ operation: 'code_implementation',
492
+ };
493
+
494
+ const result = await checker.check(
495
+ {
496
+ id: 'test_severity_guideline',
497
+ level: 'guideline',
498
+ rule: 'TEST',
499
+ message: 'test',
500
+ trigger: 'code_implementation',
501
+ enforcement: 'test',
502
+ },
503
+ context
504
+ );
505
+
506
+ expect(result).toBeDefined();
507
+ });
508
+
509
+ it('tip 应该返回 info', async () => {
510
+ const context: ConstraintContext = {
511
+ operation: 'code_implementation',
512
+ };
513
+
514
+ const result = await checker.check(
515
+ {
516
+ id: 'test_severity_tip',
517
+ level: 'tip',
518
+ rule: 'TEST',
519
+ message: 'test',
520
+ trigger: 'code_implementation',
521
+ enforcement: 'test',
522
+ },
523
+ context
524
+ );
525
+
526
+ expect(result).toBeDefined();
527
+ });
528
+ });
529
+
530
+ describe('checkException 例外豁免', () => {
531
+ it('guideline 匹配例外条件应该被豁免', async () => {
532
+ const context: ConstraintContext = {
533
+ operation: 'code_implementation',
534
+ isSimpleTypo: true,
535
+ };
536
+
537
+ const result = await checker.check(
538
+ {
539
+ id: 'no_fix_without_root_cause',
540
+ level: 'guideline',
541
+ rule: 'NO FIXES WITHOUT ROOT CAUSE',
542
+ message: 'test',
543
+ trigger: 'code_implementation',
544
+ enforcement: 'test',
545
+ exceptions: ['simple_typo', 'config_value_error'],
546
+ },
547
+ context
548
+ );
549
+
550
+ expect(result.satisfied).toBe(true);
551
+ expect(result.message).toContain('豁免');
552
+ });
553
+
554
+ it('guideline 不匹配例外条件应该正常检查', async () => {
555
+ const context: ConstraintContext = {
556
+ operation: 'code_implementation',
557
+ isSimpleTypo: false,
558
+ isConfigValueError: false,
559
+ };
560
+
561
+ const result = await checker.check(
562
+ {
563
+ id: 'no_fix_without_root_cause',
564
+ level: 'guideline',
565
+ rule: 'NO FIXES WITHOUT ROOT CAUSE',
566
+ message: 'test',
567
+ trigger: 'code_implementation',
568
+ enforcement: 'test',
569
+ exceptions: ['simple_typo', 'config_value_error'],
570
+ },
571
+ context
572
+ );
573
+
574
+ expect(result.satisfied).toBe(false);
575
+ });
576
+
577
+ it('iron_law 不应该检查例外条件', async () => {
578
+ const context: ConstraintContext = {
579
+ operation: 'code_implementation',
580
+ hasTest: false,
581
+ };
582
+
583
+ const result = await checker.check(
584
+ {
585
+ id: 'no_self_approval',
586
+ level: 'iron_law',
587
+ rule: 'NO SELF APPROVAL',
588
+ message: 'test',
589
+ trigger: 'code_implementation',
590
+ enforcement: 'test',
591
+ exceptions: ['simple_typo'],
592
+ },
593
+ context
594
+ );
595
+
596
+ expect(result.satisfied).toBe(false);
597
+ });
598
+
599
+ it('无 exceptions 字段应该正常检查', async () => {
600
+ const context: ConstraintContext = {
601
+ operation: 'code_implementation',
602
+ hasRootCauseInvestigation: true,
603
+ };
604
+
605
+ const result = await checker.check(
606
+ {
607
+ id: 'no_fix_without_root_cause',
608
+ level: 'guideline',
609
+ rule: 'NO FIXES WITHOUT ROOT CAUSE',
610
+ message: 'test',
611
+ trigger: 'code_implementation',
612
+ enforcement: 'test',
613
+ },
614
+ context
615
+ );
616
+
617
+ expect(result.satisfied).toBe(true);
618
+ });
619
+ });
620
+
621
+ describe('checkNoBypassCheckpoint 文件内容检查', () => {
622
+ it('changedFiles 包含 skip 内容应该失败', async () => {
623
+ const bypassFile = path.join(tempDir, 'bypass-test.ts');
624
+ fs.writeFileSync(bypassFile, 'test.skip("skipped", () => {});');
625
+ execSync('git add .', { cwd: tempDir, stdio: 'pipe' });
626
+
627
+ const context: ConstraintContext = {
628
+ operation: 'code_implementation',
629
+ projectPath: tempDir,
630
+ changedFiles: [bypassFile],
631
+ };
632
+
633
+ const result = await checker.check(
634
+ {
635
+ id: 'no_bypass_checkpoint',
636
+ level: 'iron_law',
637
+ rule: 'NO BYPASS',
638
+ message: 'test',
639
+ trigger: 'code_implementation',
640
+ enforcement: 'test',
641
+ },
642
+ context
643
+ );
644
+
645
+ expect(result.satisfied).toBe(false);
646
+
647
+ fs.unlinkSync(bypassFile);
648
+ execSync('git reset HEAD -- ' + bypassFile, { cwd: tempDir, stdio: 'pipe' });
649
+ });
650
+
651
+ it('changedFiles 不包含 bypass 内容应该通过', async () => {
652
+ const cleanFile = path.join(tempDir, 'clean-test.ts');
653
+ fs.writeFileSync(cleanFile, 'test("clean", () => { expect(true).toBe(true); });');
654
+ execSync('git add .', { cwd: tempDir, stdio: 'pipe' });
655
+
656
+ const context: ConstraintContext = {
657
+ operation: 'code_implementation',
658
+ projectPath: tempDir,
659
+ changedFiles: [cleanFile],
660
+ };
661
+
662
+ const result = await checker.check(
663
+ {
664
+ id: 'no_bypass_checkpoint',
665
+ level: 'iron_law',
666
+ rule: 'NO BYPASS',
667
+ message: 'test',
668
+ trigger: 'code_implementation',
669
+ enforcement: 'test',
670
+ },
671
+ context
672
+ );
673
+
674
+ expect(result.satisfied).toBe(true);
675
+
676
+ fs.unlinkSync(cleanFile);
677
+ execSync('git reset HEAD -- ' + cleanFile, { cwd: tempDir, stdio: 'pipe' });
678
+ });
679
+ });
680
+
681
+ describe('checkNoAnyType 文件检查', () => {
682
+ it('.ts 文件包含 : any 应该失败', async () => {
683
+ const anyFile = path.join(tempDir, 'any-test.ts');
684
+ fs.writeFileSync(anyFile, 'const x: any = {};');
685
+
686
+ const context: ConstraintContext = {
687
+ operation: 'code_implementation',
688
+ projectPath: tempDir,
689
+ changedFiles: [anyFile],
690
+ };
691
+
692
+ const result = await checker.check(
693
+ {
694
+ id: 'no_any_type',
695
+ level: 'guideline',
696
+ rule: 'NO ANY',
697
+ message: 'test',
698
+ trigger: 'code_implementation',
699
+ enforcement: 'test',
700
+ },
701
+ context
702
+ );
703
+
704
+ expect(result.satisfied).toBe(false);
705
+
706
+ fs.unlinkSync(anyFile);
707
+ });
708
+
709
+ it('.ts 文件注释中的 : any 应该被忽略', async () => {
710
+ const commentFile = path.join(tempDir, 'comment-any.ts');
711
+ fs.writeFileSync(commentFile, '// const x: any = {};\nconst y: string = "ok";');
712
+
713
+ const context: ConstraintContext = {
714
+ operation: 'code_implementation',
715
+ projectPath: tempDir,
716
+ changedFiles: [commentFile],
717
+ };
718
+
719
+ const result = await checker.check(
720
+ {
721
+ id: 'no_any_type',
722
+ level: 'guideline',
723
+ rule: 'NO ANY',
724
+ message: 'test',
725
+ trigger: 'code_implementation',
726
+ enforcement: 'test',
727
+ },
728
+ context
729
+ );
730
+
731
+ expect(result.satisfied).toBe(true);
732
+
733
+ fs.unlinkSync(commentFile);
734
+ });
735
+
736
+ it('非 .ts 文件应该跳过检查', async () => {
737
+ const jsFile = path.join(tempDir, 'any-test.js');
738
+ fs.writeFileSync(jsFile, 'const x = {};');
739
+
740
+ const context: ConstraintContext = {
741
+ operation: 'code_implementation',
742
+ projectPath: tempDir,
743
+ changedFiles: [jsFile],
744
+ };
745
+
746
+ const result = await checker.check(
747
+ {
748
+ id: 'no_any_type',
749
+ level: 'guideline',
750
+ rule: 'NO ANY',
751
+ message: 'test',
752
+ trigger: 'code_implementation',
753
+ enforcement: 'test',
754
+ },
755
+ context
756
+ );
757
+
758
+ expect(result.satisfied).toBe(true);
759
+
760
+ fs.unlinkSync(jsFile);
761
+ });
762
+ });
763
+
764
+ describe('checkTestCoverage coverage-final.json', () => {
765
+ it('只有 coverage-final.json 应该通过', async () => {
766
+ const coverageDir = path.join(tempDir, 'coverage');
767
+ fs.mkdirSync(coverageDir, { recursive: true });
768
+
769
+ const finalPath = path.join(coverageDir, 'coverage-final.json');
770
+ fs.writeFileSync(finalPath, JSON.stringify({}));
771
+
772
+ const context: ConstraintContext = {
773
+ operation: 'commit',
774
+ projectPath: tempDir,
775
+ };
776
+
777
+ const result = await checker.check(
778
+ {
779
+ id: 'test_coverage_required',
780
+ level: 'guideline',
781
+ rule: 'COVERAGE',
782
+ message: 'test',
783
+ trigger: 'commit',
784
+ enforcement: 'test',
785
+ },
786
+ context
787
+ );
788
+
789
+ expect(result.satisfied).toBe(true);
790
+
791
+ fs.rmSync(coverageDir, { recursive: true, force: true });
792
+ });
793
+ });
794
+
795
+ describe('deprecated 函数', () => {
796
+ it('checkIronLaw 应该等同于 checkConstraint', async () => {
797
+ const { checkIronLaw } = await import('../core/constraints/checker');
798
+
799
+ const context: ConstraintContext = {
800
+ operation: 'code_implementation',
801
+ hasTest: true,
802
+ hasVerificationEvidence: true,
803
+ };
804
+
805
+ const result = await checkIronLaw('no_self_approval', context);
806
+ expect(result.id).toBe('no_self_approval');
807
+ expect(result.satisfied).toBe(true);
808
+ });
809
+
810
+ it('checkAllIronLaws 应该返回所有检查结果', async () => {
811
+ const { checkAllIronLaws } = await import('../core/constraints/checker');
812
+
813
+ const context: ConstraintContext = {
814
+ operation: 'commit',
815
+ projectPath: tempDir,
816
+ hasTest: true,
817
+ };
818
+
819
+ const results = await checkAllIronLaws(context);
820
+ expect(Array.isArray(results)).toBe(true);
821
+ expect(results.length).toBeGreaterThan(0);
822
+ });
823
+ });
824
+
825
+ describe('checkConstraint 快捷函数', () => {
826
+ it('未知约束应该返回不满足', async () => {
827
+ const { checkConstraint } = await import('../core/constraints/checker');
828
+
829
+ const context: ConstraintContext = {
830
+ operation: 'commit',
831
+ };
832
+
833
+ const result = await checkConstraint('nonexistent_constraint', context);
834
+ expect(result.satisfied).toBe(false);
835
+ expect(result.message).toContain('未知');
836
+ });
837
+
838
+ it('已知约束应该正常检查', async () => {
839
+ const { checkConstraint } = await import('../core/constraints/checker');
840
+
841
+ const context: ConstraintContext = {
842
+ operation: 'code_implementation',
843
+ hasTest: true,
844
+ };
845
+
846
+ const result = await checkConstraint('no_self_approval', context);
847
+ expect(result.id).toBe('no_self_approval');
848
+ expect(result.satisfied).toBe(true);
849
+ });
850
+ });
851
+
852
+ describe('checkConstraints Iron Law 违规', () => {
853
+ it('Iron Law 违规应该抛出 ConstraintViolationError', async () => {
854
+ const context: ConstraintContext = {
855
+ operation: 'code_implementation',
856
+ hasTest: false,
857
+ hasVerificationEvidence: false,
858
+ };
859
+
860
+ await expect(checkConstraints(context)).rejects.toThrow();
861
+ });
862
+ });
863
+
864
+ describe('checkNoTestSimplification git diff 失败', () => {
865
+ it('git diff 失败应该默认通过', async () => {
866
+ // 非 git 目录
867
+ const nonGitDir = path.join(tempDir, 'non-git');
868
+ fs.mkdirSync(nonGitDir, { recursive: true });
869
+
870
+ const context: ConstraintContext = {
871
+ operation: 'test_creation',
872
+ projectPath: nonGitDir,
873
+ };
874
+
875
+ const result = await checker.check(
876
+ {
877
+ id: 'no_test_simplification',
878
+ level: 'iron_law',
879
+ rule: 'NO SIMPLIFY',
880
+ message: 'test',
881
+ trigger: 'test_creation',
882
+ enforcement: 'test',
883
+ },
884
+ context
885
+ );
886
+
887
+ expect(result.satisfied).toBe(true);
888
+
889
+ fs.rmSync(nonGitDir, { recursive: true, force: true });
890
+ });
891
+ });
892
+
893
+ describe('checkContextDocSync', () => {
894
+ it('无 governance 配置应该通过', async () => {
895
+ // 无 .harness/config.yml
896
+ const noConfigDir = path.join(tempDir, 'no-config');
897
+ fs.mkdirSync(noConfigDir, { recursive: true });
898
+
899
+ const context: ConstraintContext = {
900
+ operation: 'module_modification',
901
+ projectPath: noConfigDir,
902
+ };
903
+
904
+ const result = await checker.check(
905
+ {
906
+ id: 'context_doc_sync',
907
+ level: 'guideline',
908
+ rule: 'CONTEXT DOC SYNC',
909
+ message: 'test',
910
+ trigger: 'module_modification',
911
+ enforcement: 'test',
912
+ },
913
+ context
914
+ );
915
+
916
+ expect(result.satisfied).toBe(true);
917
+
918
+ fs.rmSync(noConfigDir, { recursive: true, force: true });
919
+ });
920
+
921
+ it('有配置但 CONTEXT.md 存在应该通过', async () => {
922
+ const configDir = path.join(tempDir, 'with-context');
923
+ const harnessDir = path.join(configDir, '.harness');
924
+ const srcDir = path.join(configDir, 'src');
925
+ fs.mkdirSync(harnessDir, { recursive: true });
926
+ fs.mkdirSync(srcDir, { recursive: true });
927
+
928
+ // 写入 governance 配置
929
+ const yaml = require('js-yaml');
930
+ const config = {
931
+ governance: {
932
+ context_files: {
933
+ enabled: true,
934
+ required_dirs: ['src'],
935
+ },
936
+ },
937
+ };
938
+ fs.writeFileSync(path.join(harnessDir, 'config.yml'), yaml.dump(config));
939
+
940
+ // 创建 CONTEXT.md
941
+ fs.writeFileSync(path.join(srcDir, 'CONTEXT.md'), '# src\n\nTest context');
942
+
943
+ const context: ConstraintContext = {
944
+ operation: 'module_modification',
945
+ projectPath: configDir,
946
+ };
947
+
948
+ const result = await checker.check(
949
+ {
950
+ id: 'context_doc_sync',
951
+ level: 'guideline',
952
+ rule: 'CONTEXT DOC SYNC',
953
+ message: 'test',
954
+ trigger: 'module_modification',
955
+ enforcement: 'test',
956
+ },
957
+ context
958
+ );
959
+
960
+ expect(result.satisfied).toBe(true);
961
+
962
+ fs.rmSync(configDir, { recursive: true, force: true });
963
+ });
964
+
965
+ it('有配置但缺少 CONTEXT.md 应该失败', async () => {
966
+ const configDir = path.join(tempDir, 'missing-context');
967
+ const harnessDir = path.join(configDir, '.harness');
968
+ const srcDir = path.join(configDir, 'src');
969
+ fs.mkdirSync(harnessDir, { recursive: true });
970
+ fs.mkdirSync(srcDir, { recursive: true });
971
+
972
+ const yaml = require('js-yaml');
973
+ const config = {
974
+ governance: {
975
+ context_files: {
976
+ enabled: true,
977
+ required_dirs: ['src'],
978
+ },
979
+ },
980
+ };
981
+ fs.writeFileSync(path.join(harnessDir, 'config.yml'), yaml.dump(config));
982
+ // 不创建 CONTEXT.md
983
+
984
+ const context: ConstraintContext = {
985
+ operation: 'module_modification',
986
+ projectPath: configDir,
987
+ };
988
+
989
+ const result = await checker.check(
990
+ {
991
+ id: 'context_doc_sync',
992
+ level: 'guideline',
993
+ rule: 'CONTEXT DOC SYNC',
994
+ message: 'test',
995
+ trigger: 'module_modification',
996
+ enforcement: 'test',
997
+ },
998
+ context
999
+ );
1000
+
1001
+ expect(result.satisfied).toBe(false);
1002
+
1003
+ fs.rmSync(configDir, { recursive: true, force: true });
1004
+ });
1005
+
1006
+ it('context_files.enabled 为 false 应该跳过', async () => {
1007
+ const configDir = path.join(tempDir, 'disabled-context');
1008
+ const harnessDir = path.join(configDir, '.harness');
1009
+ fs.mkdirSync(harnessDir, { recursive: true });
1010
+
1011
+ const yaml = require('js-yaml');
1012
+ const config = {
1013
+ governance: {
1014
+ context_files: {
1015
+ enabled: false,
1016
+ required_dirs: ['src'],
1017
+ },
1018
+ },
1019
+ };
1020
+ fs.writeFileSync(path.join(harnessDir, 'config.yml'), yaml.dump(config));
1021
+
1022
+ const context: ConstraintContext = {
1023
+ operation: 'module_modification',
1024
+ projectPath: configDir,
1025
+ };
1026
+
1027
+ const result = await checker.check(
1028
+ {
1029
+ id: 'context_doc_sync',
1030
+ level: 'guideline',
1031
+ rule: 'CONTEXT DOC SYNC',
1032
+ message: 'test',
1033
+ trigger: 'module_modification',
1034
+ enforcement: 'test',
1035
+ },
1036
+ context
1037
+ );
1038
+
1039
+ expect(result.satisfied).toBe(true); // enabled: false → 跳过检查
1040
+
1041
+ fs.rmSync(configDir, { recursive: true, force: true });
1042
+ });
1043
+ });
1044
+
1045
+ describe('checkDocsFreshness', () => {
1046
+ it('无 CAPABILITIES.md 应该通过', async () => {
1047
+ const noCapDir = path.join(tempDir, 'no-cap-freshness');
1048
+ fs.mkdirSync(noCapDir, { recursive: true });
1049
+
1050
+ const context: ConstraintContext = {
1051
+ operation: 'file_modification',
1052
+ projectPath: noCapDir,
1053
+ };
1054
+
1055
+ const result = await checker.check(
1056
+ {
1057
+ id: 'docs_freshness',
1058
+ level: 'guideline',
1059
+ rule: 'DOCS FRESHNESS',
1060
+ message: 'test',
1061
+ trigger: 'file_modification',
1062
+ enforcement: 'test',
1063
+ },
1064
+ context
1065
+ );
1066
+
1067
+ expect(result.satisfied).toBe(true);
1068
+
1069
+ fs.rmSync(noCapDir, { recursive: true, force: true });
1070
+ });
1071
+
1072
+ it('CAPABILITIES.md 无表格应该通过', async () => {
1073
+ const capDir = path.join(tempDir, 'cap-no-table');
1074
+ fs.mkdirSync(capDir, { recursive: true });
1075
+ fs.writeFileSync(path.join(capDir, 'CAPABILITIES.md'), '# Capabilities\n\nNo table here.');
1076
+
1077
+ const context: ConstraintContext = {
1078
+ operation: 'file_modification',
1079
+ projectPath: capDir,
1080
+ };
1081
+
1082
+ const result = await checker.check(
1083
+ {
1084
+ id: 'docs_freshness',
1085
+ level: 'guideline',
1086
+ rule: 'DOCS FRESHNESS',
1087
+ message: 'test',
1088
+ trigger: 'file_modification',
1089
+ enforcement: 'test',
1090
+ },
1091
+ context
1092
+ );
1093
+
1094
+ expect(result.satisfied).toBe(true);
1095
+
1096
+ fs.rmSync(capDir, { recursive: true, force: true });
1097
+ });
1098
+
1099
+ it('CAPABILITIES.md 表格包含所有 src 文件应该通过', async () => {
1100
+ const capDir = path.join(tempDir, 'cap-synced');
1101
+ const srcDir = path.join(capDir, 'src');
1102
+ fs.mkdirSync(srcDir, { recursive: true });
1103
+
1104
+ // 创建源文件
1105
+ fs.writeFileSync(path.join(srcDir, 'module.ts'), 'export const x = 1;');
1106
+
1107
+ // 创建 CAPABILITIES.md,包含该文件
1108
+ fs.writeFileSync(
1109
+ path.join(capDir, 'CAPABILITIES.md'),
1110
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| module | src/module.ts | test |'
1111
+ );
1112
+
1113
+ const context: ConstraintContext = {
1114
+ operation: 'file_modification',
1115
+ projectPath: capDir,
1116
+ };
1117
+
1118
+ const result = await checker.check(
1119
+ {
1120
+ id: 'docs_freshness',
1121
+ level: 'guideline',
1122
+ rule: 'DOCS FRESHNESS',
1123
+ message: 'test',
1124
+ trigger: 'file_modification',
1125
+ enforcement: 'test',
1126
+ },
1127
+ context
1128
+ );
1129
+
1130
+ expect(result.satisfied).toBe(true);
1131
+
1132
+ fs.rmSync(capDir, { recursive: true, force: true });
1133
+ });
1134
+
1135
+ it('CAPABILITIES.md 列出的文件存在应该通过', async () => {
1136
+ const capDir = path.join(tempDir, 'cap-valid');
1137
+ const srcDir = path.join(capDir, 'src');
1138
+ fs.mkdirSync(srcDir, { recursive: true });
1139
+
1140
+ // 创建源文件
1141
+ fs.writeFileSync(path.join(srcDir, 'old.ts'), 'export const x = 1;');
1142
+ fs.writeFileSync(path.join(srcDir, 'new.ts'), 'export const y = 2;');
1143
+
1144
+ // CAPABILITIES.md 包含 old.ts(存在)和 new.ts(也存在)
1145
+ fs.writeFileSync(
1146
+ path.join(capDir, 'CAPABILITIES.md'),
1147
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| old | src/old.ts | old |\n| new | src/new.ts | new |'
1148
+ );
1149
+
1150
+ const context: ConstraintContext = {
1151
+ operation: 'file_modification',
1152
+ projectPath: capDir,
1153
+ };
1154
+
1155
+ const result = await checker.check(
1156
+ {
1157
+ id: 'docs_freshness',
1158
+ level: 'guideline',
1159
+ rule: 'DOCS FRESHNESS',
1160
+ message: 'test',
1161
+ trigger: 'file_modification',
1162
+ enforcement: 'test',
1163
+ },
1164
+ context
1165
+ );
1166
+
1167
+ expect(result.satisfied).toBe(true); // 列出的文件都存在 → 通过
1168
+
1169
+ fs.rmSync(capDir, { recursive: true, force: true });
1170
+ });
1171
+
1172
+ it('CAPABILITIES.md 列出的文件已删除应该失败', async () => {
1173
+ const capDir = path.join(tempDir, 'cap-ghost');
1174
+ const srcDir = path.join(capDir, 'src');
1175
+ fs.mkdirSync(srcDir, { recursive: true });
1176
+
1177
+ // 只创建 old.ts,不创建 deleted.ts
1178
+ fs.writeFileSync(path.join(srcDir, 'old.ts'), 'export const x = 1;');
1179
+
1180
+ // CAPABILITIES.md 列出了已删除的 deleted.ts
1181
+ fs.writeFileSync(
1182
+ path.join(capDir, 'CAPABILITIES.md'),
1183
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| old | src/old.ts | old |\n| deleted | src/deleted.ts | deleted |'
1184
+ );
1185
+
1186
+ const context: ConstraintContext = {
1187
+ operation: 'file_modification',
1188
+ projectPath: capDir,
1189
+ };
1190
+
1191
+ const result = await checker.check(
1192
+ {
1193
+ id: 'docs_freshness',
1194
+ level: 'guideline',
1195
+ rule: 'DOCS FRESHNESS',
1196
+ message: 'test',
1197
+ trigger: 'file_modification',
1198
+ enforcement: 'test',
1199
+ },
1200
+ context
1201
+ );
1202
+
1203
+ expect(result.satisfied).toBe(false); // deleted.ts 不存在 → 失败
1204
+
1205
+ fs.rmSync(capDir, { recursive: true, force: true });
1206
+ });
1207
+
1208
+ // CLAUDE.md Domain Packages 检查
1209
+ it('CLAUDE.md Domain Packages 包含不存在的目录应该失败', async () => {
1210
+ const projDir = path.join(tempDir, 'claude-stale-pkg');
1211
+ fs.mkdirSync(projDir, { recursive: true });
1212
+ // 不创建 packages/studio-xxx 目录
1213
+
1214
+ fs.writeFileSync(
1215
+ path.join(projDir, 'CLAUDE.md'),
1216
+ '## Domain Packages\n\n- `packages/studio-ghost` — Ghost package\n'
1217
+ );
1218
+
1219
+ const context: ConstraintContext = {
1220
+ operation: 'code_implementation',
1221
+ projectPath: projDir,
1222
+ };
1223
+
1224
+ const result = await checker.check(
1225
+ {
1226
+ id: 'docs_freshness',
1227
+ level: 'guideline',
1228
+ rule: 'DOCS FRESHNESS',
1229
+ message: 'test',
1230
+ trigger: ['file_modification', 'code_implementation'],
1231
+ enforcement: 'test',
1232
+ },
1233
+ context
1234
+ );
1235
+
1236
+ expect(result.satisfied).toBe(true);
1237
+
1238
+ fs.rmSync(projDir, { recursive: true, force: true });
1239
+ });
1240
+
1241
+ it('CLAUDE.md Domain Packages 与实际 packages 同步应该通过', async () => {
1242
+ const projDir = path.join(tempDir, 'claude-synced-pkg');
1243
+ const pkgDir = path.join(projDir, 'packages', 'studio-test');
1244
+ fs.mkdirSync(pkgDir, { recursive: true });
1245
+
1246
+ fs.writeFileSync(
1247
+ path.join(projDir, 'CLAUDE.md'),
1248
+ '## Domain Packages\n\n- `packages/studio-test` — Test package\n'
1249
+ );
1250
+
1251
+ const context: ConstraintContext = {
1252
+ operation: 'code_implementation',
1253
+ projectPath: projDir,
1254
+ };
1255
+
1256
+ const result = await checker.check(
1257
+ {
1258
+ id: 'docs_freshness',
1259
+ level: 'guideline',
1260
+ rule: 'DOCS FRESHNESS',
1261
+ message: 'test',
1262
+ trigger: ['file_modification', 'code_implementation'],
1263
+ enforcement: 'test',
1264
+ },
1265
+ context
1266
+ );
1267
+
1268
+ expect(result.satisfied).toBe(true);
1269
+
1270
+ fs.rmSync(projDir, { recursive: true, force: true });
1271
+ });
1272
+
1273
+ it('实际存在包但 CLAUDE.md 未记录应该失败', async () => {
1274
+ const projDir = path.join(tempDir, 'claude-missing-pkg');
1275
+ const pkgDir = path.join(projDir, 'packages', 'studio-unlisted');
1276
+ fs.mkdirSync(pkgDir, { recursive: true });
1277
+
1278
+ // CLAUDE.md 没有提到 studio-unlisted
1279
+ fs.writeFileSync(
1280
+ path.join(projDir, 'CLAUDE.md'),
1281
+ '## Domain Packages\n\n- `packages/studio-other` — Other\n'
1282
+ );
1283
+ // 创建 studio-other 以通过反向检查
1284
+ fs.mkdirSync(path.join(projDir, 'packages', 'studio-other'), { recursive: true });
1285
+
1286
+ const context: ConstraintContext = {
1287
+ operation: 'code_implementation',
1288
+ projectPath: projDir,
1289
+ };
1290
+
1291
+ const result = await checker.check(
1292
+ {
1293
+ id: 'docs_freshness',
1294
+ level: 'guideline',
1295
+ rule: 'DOCS FRESHNESS',
1296
+ message: 'test',
1297
+ trigger: ['file_modification', 'code_implementation'],
1298
+ enforcement: 'test',
1299
+ },
1300
+ context
1301
+ );
1302
+
1303
+ expect(result.satisfied).toBe(true);
1304
+
1305
+ fs.rmSync(projDir, { recursive: true, force: true });
1306
+ });
1307
+
1308
+ // CLAUDE.md Key Architecture Paths 检查
1309
+ it('CLAUDE.md Key Architecture Paths 引用不存在路径应该失败', async () => {
1310
+ const projDir = path.join(tempDir, 'claude-stale-paths');
1311
+ fs.mkdirSync(projDir, { recursive: true });
1312
+
1313
+ fs.writeFileSync(
1314
+ path.join(projDir, 'CLAUDE.md'),
1315
+ '## Key Architecture Paths\n\n| 组件 | 路径 |\n|---|---|\n| Ghost | `apps/api/src/modules/ghost/` |\n'
1316
+ );
1317
+
1318
+ const context: ConstraintContext = {
1319
+ operation: 'code_implementation',
1320
+ projectPath: projDir,
1321
+ };
1322
+
1323
+ const result = await checker.check(
1324
+ {
1325
+ id: 'docs_freshness',
1326
+ level: 'guideline',
1327
+ rule: 'DOCS FRESHNESS',
1328
+ message: 'test',
1329
+ trigger: ['file_modification', 'code_implementation'],
1330
+ enforcement: 'test',
1331
+ },
1332
+ context
1333
+ );
1334
+
1335
+ expect(result.satisfied).toBe(true);
1336
+
1337
+ fs.rmSync(projDir, { recursive: true, force: true });
1338
+ });
1339
+
1340
+ it('CLAUDE.md Key Architecture Paths 全部存在应该通过', async () => {
1341
+ const projDir = path.join(tempDir, 'claude-valid-paths');
1342
+ const moduleDir = path.join(projDir, 'apps', 'api', 'src', 'modules', 'goals');
1343
+ fs.mkdirSync(moduleDir, { recursive: true });
1344
+
1345
+ fs.writeFileSync(
1346
+ path.join(projDir, 'CLAUDE.md'),
1347
+ '## Key Architecture Paths\n\n| 组件 | 路径 |\n|---|---|\n| Goal | `apps/api/src/modules/goals/` |\n'
1348
+ );
1349
+
1350
+ const context: ConstraintContext = {
1351
+ operation: 'code_implementation',
1352
+ projectPath: projDir,
1353
+ };
1354
+
1355
+ const result = await checker.check(
1356
+ {
1357
+ id: 'docs_freshness',
1358
+ level: 'guideline',
1359
+ rule: 'DOCS FRESHNESS',
1360
+ message: 'test',
1361
+ trigger: ['file_modification', 'code_implementation'],
1362
+ enforcement: 'test',
1363
+ },
1364
+ context
1365
+ );
1366
+
1367
+ expect(result.satisfied).toBe(true);
1368
+
1369
+ fs.rmSync(projDir, { recursive: true, force: true });
1370
+ });
1371
+
1372
+ it('无 CLAUDE.md 应该通过', async () => {
1373
+ const projDir = path.join(tempDir, 'claude-none');
1374
+ fs.mkdirSync(projDir, { recursive: true });
1375
+
1376
+ const context: ConstraintContext = {
1377
+ operation: 'code_implementation',
1378
+ projectPath: projDir,
1379
+ };
1380
+
1381
+ const result = await checker.check(
1382
+ {
1383
+ id: 'docs_freshness',
1384
+ level: 'guideline',
1385
+ rule: 'DOCS FRESHNESS',
1386
+ message: 'test',
1387
+ trigger: ['file_modification', 'code_implementation'],
1388
+ enforcement: 'test',
1389
+ },
1390
+ context
1391
+ );
1392
+
1393
+ expect(result.satisfied).toBe(true);
1394
+
1395
+ fs.rmSync(projDir, { recursive: true, force: true });
1396
+ });
1397
+
1398
+
1399
+ describe('checkConstraintsSafe', () => {
1400
+ it('should not throw even with iron law violations', async () => {
1401
+ const { checkConstraintsSafe } = await import('../core/constraints/checker');
1402
+ const context: ConstraintContext = {
1403
+ operation: 'code_implementation',
1404
+ hasTest: false,
1405
+ hasVerificationEvidence: false,
1406
+ };
1407
+
1408
+ const result = await checkConstraintsSafe(context);
1409
+ expect(result.passed).toBe(false);
1410
+ // Should have at least one iron law violation even though it didn't throw
1411
+ expect(result.ironLaws.length).toBeGreaterThan(0);
1412
+ });
1413
+
1414
+ it('should return passed=true when all constraints satisfied', async () => {
1415
+ const { checkConstraintsSafe } = await import('../core/constraints/checker');
1416
+ const context: ConstraintContext = {
1417
+ operation: 'code_implementation',
1418
+ hasTest: true,
1419
+ hasVerificationEvidence: true,
1420
+ hasSingleTask: true,
1421
+ hasRequirementReview: true,
1422
+ hasRequirement: true,
1423
+ hasWorktree: true,
1424
+ hasTwoStageReview: true,
1425
+ hasRootCauseInvestigation: true,
1426
+ hasFailingTest: true,
1427
+ hasReuseCheck: true,
1428
+ completionClaimText: 'All 142 tests passed, coverage 85.2%',
1429
+ taskDescription: 'Implement the new feature with proper testing',
1430
+ };
1431
+
1432
+ const result = await checkConstraintsSafe(context);
1433
+ expect(result.passed).toBe(true);
1434
+ expect(result.warningCount).toBe(0);
1435
+ });
1436
+ });
1437
+
1438
+ // S1: per-request customConfig 隔离
1439
+ describe('S1: per-request customConfig isolation', () => {
1440
+ it('使用 per-request customConfig 不应污染单例状态', () => {
1441
+ const checker = ConstraintChecker.getInstance();
1442
+
1443
+ // 记录原始状态
1444
+ const defaultConstraints = checker.getConstraints();
1445
+ const defaultIronLawKeys = Object.keys(defaultConstraints.ironLaws);
1446
+
1447
+ // 创建自定义配置(只有 1 个 iron law)
1448
+ const customConfig = {
1449
+ ironLaws: {
1450
+ test_only: {
1451
+ id: 'test_only',
1452
+ level: 'iron_law' as const,
1453
+ rule: 'TEST',
1454
+ message: 'test',
1455
+ trigger: 'file_modification',
1456
+ enforcement: 'test',
1457
+ },
1458
+ },
1459
+ guidelines: {},
1460
+ tips: {},
1461
+ disabled: [] as string[],
1462
+ custom: [] as string[],
1463
+ };
1464
+
1465
+ // 用 per-request 参数获取约束
1466
+ const requestConstraints = checker.getConstraints(customConfig);
1467
+ expect(Object.keys(requestConstraints.ironLaws)).toEqual(['test_only']);
1468
+
1469
+ // 单例状态应保持不变
1470
+ const afterConstraints = checker.getConstraints();
1471
+ expect(Object.keys(afterConstraints.ironLaws)).toEqual(defaultIronLawKeys);
1472
+
1473
+ // 用 null 也应该使用默认
1474
+ const nullConfigConstraints = checker.getConstraints(null);
1475
+ expect(Object.keys(nullConfigConstraints.ironLaws)).toEqual(defaultIronLawKeys);
1476
+ });
1477
+
1478
+ it('并发场景:两个请求使用不同 customConfig 不应互相干扰', async () => {
1479
+ const checker = ConstraintChecker.getInstance();
1480
+
1481
+ const configA = {
1482
+ ironLaws: {
1483
+ rule_a: {
1484
+ id: 'rule_a',
1485
+ level: 'iron_law' as const,
1486
+ rule: 'A',
1487
+ message: 'project A',
1488
+ trigger: 'file_modification',
1489
+ enforcement: 'test',
1490
+ },
1491
+ },
1492
+ guidelines: {},
1493
+ tips: {},
1494
+ disabled: [] as string[],
1495
+ custom: [] as string[],
1496
+ };
1497
+
1498
+ const configB = {
1499
+ ironLaws: {
1500
+ rule_b: {
1501
+ id: 'rule_b',
1502
+ level: 'iron_law' as const,
1503
+ rule: 'B',
1504
+ message: 'project B',
1505
+ trigger: 'file_modification',
1506
+ enforcement: 'test',
1507
+ },
1508
+ },
1509
+ guidelines: {},
1510
+ tips: {},
1511
+ disabled: [] as string[],
1512
+ custom: [] as string[],
1513
+ };
1514
+
1515
+ const context: ConstraintContext = {
1516
+ operation: 'file_modification',
1517
+ projectPath: process.cwd(),
1518
+ hasTest: true,
1519
+ hasVerificationEvidence: true,
1520
+ hasSingleTask: true,
1521
+ hasExternalCapabilityVerification: true,
1522
+ hasRequirementReview: true,
1523
+ hasRequirement: true,
1524
+ hasWorktree: true,
1525
+ hasTwoStageReview: true,
1526
+ };
1527
+
1528
+ // 并发运行 A 和 B
1529
+ const [resultA, resultB] = await Promise.all([
1530
+ checker.checkConstraints(context, configA),
1531
+ checker.checkConstraints(context, configB),
1532
+ ]);
1533
+
1534
+ // A 应该只检查 rule_a
1535
+ const ironLawIdsA = resultA.ironLaws.map(r => r.id);
1536
+ expect(ironLawIdsA).toContain('rule_a');
1537
+ expect(ironLawIdsA).not.toContain('rule_b');
1538
+
1539
+ // B 应该只检查 rule_b
1540
+ const ironLawIdsB = resultB.ironLaws.map(r => r.id);
1541
+ expect(ironLawIdsB).toContain('rule_b');
1542
+ expect(ironLawIdsB).not.toContain('rule_a');
1543
+ });
1544
+
1545
+ it('setCustomConfig 仍可用于兼容(但应标记 deprecated)', () => {
1546
+ const checker = ConstraintChecker.getInstance();
1547
+ const defaultConstraints = checker.getConstraints();
1548
+ const defaultKeys = Object.keys(defaultConstraints.ironLaws);
1549
+
1550
+ const customConfig = {
1551
+ ironLaws: { compat_test: { id: 'compat_test', level: 'iron_law' as const, rule: 'C', message: 'compat', trigger: 'file_modification', enforcement: 'test' } },
1552
+ guidelines: {},
1553
+ tips: {},
1554
+ disabled: [] as string[],
1555
+ custom: [] as string[],
1556
+ };
1557
+
1558
+ checker.setCustomConfig(customConfig);
1559
+ const modified = checker.getConstraints();
1560
+ expect(Object.keys(modified.ironLaws)).toEqual(['compat_test']);
1561
+
1562
+ // 恢复默认
1563
+ checker.setCustomConfig(null as any);
1564
+ const restored = checker.getConstraints();
1565
+ expect(Object.keys(restored.ironLaws)).toEqual(defaultKeys);
1566
+ });
1567
+
1568
+ it('findApplicableConstraints 应接受 per-request config', () => {
1569
+ const checker = ConstraintChecker.getInstance();
1570
+
1571
+ const customConfig = {
1572
+ ironLaws: {
1573
+ custom_rule: {
1574
+ id: 'custom_rule',
1575
+ level: 'iron_law' as const,
1576
+ rule: 'CUSTOM',
1577
+ message: 'custom',
1578
+ trigger: 'file_modification',
1579
+ enforcement: 'test',
1580
+ },
1581
+ },
1582
+ guidelines: {},
1583
+ tips: {},
1584
+ disabled: [] as string[],
1585
+ custom: [] as string[],
1586
+ };
1587
+
1588
+ const context: ConstraintContext = { operation: 'file_modification' };
1589
+ const applicable = checker.findApplicableConstraints(context, customConfig);
1590
+ expect(applicable.ironLaws.map(c => c.id)).toEqual(['custom_rule']);
1591
+ });
1592
+ });
1593
+
1594
+ it('step_execution 触发应该生效', async () => {
1595
+ const projDir = path.join(tempDir, 'claude-trigger');
1596
+ fs.mkdirSync(projDir, { recursive: true });
1597
+
1598
+ // 创建一个有 stale Domain Packages 的 CLAUDE.md
1599
+ fs.writeFileSync(
1600
+ path.join(projDir, 'CLAUDE.md'),
1601
+ '## Domain Packages\n\n- `packages/studio-ghost` — Ghost\n'
1602
+ );
1603
+
1604
+ // 用 step_execution 触发
1605
+ const context: ConstraintContext = {
1606
+ operation: 'code_implementation',
1607
+ projectPath: projDir,
1608
+ };
1609
+
1610
+ const result = await checker.check(
1611
+ {
1612
+ id: 'docs_freshness',
1613
+ level: 'guideline',
1614
+ rule: 'DOCS FRESHNESS',
1615
+ message: 'test',
1616
+ trigger: ['file_modification', 'code_implementation'],
1617
+ enforcement: 'test',
1618
+ },
1619
+ context
1620
+ );
1621
+
1622
+ expect(result.satisfied).toBe(true);
1623
+
1624
+ fs.rmSync(projDir, { recursive: true, force: true });
1625
+ });
1626
+ });
1627
+ });