@dommaker/harness 1.3.0 → 1.5.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 (532) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +104 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +27 -219
  43. package/dist/cli/commands/definitions.js.map +1 -1
  44. package/dist/cli/commands/failure.d.ts +5 -4
  45. package/dist/cli/commands/failure.d.ts.map +1 -1
  46. package/dist/cli/commands/failure.js +31 -25
  47. package/dist/cli/commands/failure.js.map +1 -1
  48. package/dist/cli/commands/init.d.ts +7 -5
  49. package/dist/cli/commands/init.d.ts.map +1 -1
  50. package/dist/cli/commands/init.js +147 -211
  51. package/dist/cli/commands/init.js.map +1 -1
  52. package/dist/cli/commands/knowledge.d.ts +17 -30
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +183 -273
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +69 -59
  71. package/dist/cli/commands/release.js.map +1 -1
  72. package/dist/cli/commands/report.d.ts +2 -1
  73. package/dist/cli/commands/report.d.ts.map +1 -1
  74. package/dist/cli/commands/report.js +9 -5
  75. package/dist/cli/commands/report.js.map +1 -1
  76. package/dist/cli/commands/review.d.ts +3 -2
  77. package/dist/cli/commands/review.d.ts.map +1 -1
  78. package/dist/cli/commands/review.js +33 -29
  79. package/dist/cli/commands/review.js.map +1 -1
  80. package/dist/cli/commands/sdd.d.ts +2 -1
  81. package/dist/cli/commands/sdd.d.ts.map +1 -1
  82. package/dist/cli/commands/sdd.js +7 -5
  83. package/dist/cli/commands/sdd.js.map +1 -1
  84. package/dist/cli/commands/security.d.ts +3 -2
  85. package/dist/cli/commands/security.d.ts.map +1 -1
  86. package/dist/cli/commands/security.js +29 -26
  87. package/dist/cli/commands/security.js.map +1 -1
  88. package/dist/cli/commands/spec-baseline-check.d.ts +2 -1
  89. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  90. package/dist/cli/commands/spec-baseline-check.js +15 -11
  91. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  92. package/dist/cli/commands/spec.d.ts +7 -2
  93. package/dist/cli/commands/spec.d.ts.map +1 -1
  94. package/dist/cli/commands/spec.js +61 -51
  95. package/dist/cli/commands/spec.js.map +1 -1
  96. package/dist/cli/commands/status.d.ts +2 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +57 -53
  99. package/dist/cli/commands/status.js.map +1 -1
  100. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  101. package/dist/cli/commands/sync-docs/agents-syncer.js +5 -2
  102. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  103. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  104. package/dist/cli/commands/sync-docs/context-syncer.js +3 -2
  105. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  106. package/dist/cli/commands/sync-docs/index.d.ts +2 -1
  107. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  108. package/dist/cli/commands/sync-docs/index.js +68 -88
  109. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  110. package/dist/cli/commands/sync-docs/project-reader.d.ts +6 -1
  111. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  112. package/dist/cli/commands/sync-docs/project-reader.js +14 -16
  113. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  114. package/dist/cli/commands/update-user-model.d.ts +9 -3
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +56 -61
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/context/session-manager.d.ts.map +1 -1
  127. package/dist/context/session-manager.js +19 -32
  128. package/dist/context/session-manager.js.map +1 -1
  129. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  130. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  131. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  132. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  133. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  134. package/dist/core/constraints/capabilities-parser.js +32 -28
  135. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  136. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  137. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  138. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  139. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  140. package/dist/core/constraints/checker.d.ts +21 -43
  141. package/dist/core/constraints/checker.d.ts.map +1 -1
  142. package/dist/core/constraints/checker.js +68 -117
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +20 -8
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +66 -77
  147. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  148. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  149. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  150. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  151. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  152. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  153. package/dist/core/constraints/checkers/docs-freshness.js +49 -66
  154. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  155. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  156. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  158. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  159. package/dist/core/constraints/checkers/index.d.ts +2 -2
  160. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  161. package/dist/core/constraints/checkers/index.js +2 -1
  162. package/dist/core/constraints/checkers/index.js.map +1 -1
  163. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  164. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  165. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  166. package/dist/core/constraints/checkers/types.d.ts +43 -4
  167. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  168. package/dist/core/constraints/checkers/types.js +33 -0
  169. package/dist/core/constraints/checkers/types.js.map +1 -1
  170. package/dist/core/constraints/context-builder.d.ts +15 -14
  171. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  172. package/dist/core/constraints/context-builder.js +32 -96
  173. package/dist/core/constraints/context-builder.js.map +1 -1
  174. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  175. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  176. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  177. package/dist/core/constraints/git-evidence.d.ts +60 -0
  178. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  179. package/dist/core/constraints/git-evidence.js +88 -0
  180. package/dist/core/constraints/git-evidence.js.map +1 -0
  181. package/dist/core/constraints/injection-drift.d.ts +3 -20
  182. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  183. package/dist/core/constraints/injection-drift.js +10 -57
  184. package/dist/core/constraints/injection-drift.js.map +1 -1
  185. package/dist/core/constraints/injection-writer.d.ts +113 -0
  186. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  187. package/dist/core/constraints/injection-writer.js +228 -0
  188. package/dist/core/constraints/injection-writer.js.map +1 -0
  189. package/dist/core/constraints/usage-report.d.ts +22 -1
  190. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  191. package/dist/core/constraints/usage-report.js +27 -20
  192. package/dist/core/constraints/usage-report.js.map +1 -1
  193. package/dist/core/index.d.ts +2 -2
  194. package/dist/core/index.d.ts.map +1 -1
  195. package/dist/core/index.js +4 -2
  196. package/dist/core/index.js.map +1 -1
  197. package/dist/core/project-config-loader.d.ts +32 -1
  198. package/dist/core/project-config-loader.d.ts.map +1 -1
  199. package/dist/core/project-config-loader.js +39 -9
  200. package/dist/core/project-config-loader.js.map +1 -1
  201. package/dist/core/validators/passes-gate.d.ts +15 -64
  202. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  203. package/dist/core/validators/passes-gate.js +84 -222
  204. package/dist/core/validators/passes-gate.js.map +1 -1
  205. package/dist/core/validators/test-output.d.ts +58 -0
  206. package/dist/core/validators/test-output.d.ts.map +1 -0
  207. package/dist/core/validators/test-output.js +109 -0
  208. package/dist/core/validators/test-output.js.map +1 -0
  209. package/dist/failure/recorder.d.ts +6 -0
  210. package/dist/failure/recorder.d.ts.map +1 -1
  211. package/dist/failure/recorder.js +12 -8
  212. package/dist/failure/recorder.js.map +1 -1
  213. package/dist/gates/acceptance.d.ts +1 -48
  214. package/dist/gates/acceptance.d.ts.map +1 -1
  215. package/dist/gates/acceptance.js +6 -38
  216. package/dist/gates/acceptance.js.map +1 -1
  217. package/dist/gates/checker-gate.d.ts +2 -1
  218. package/dist/gates/checker-gate.d.ts.map +1 -1
  219. package/dist/gates/checker-gate.js +11 -13
  220. package/dist/gates/checker-gate.js.map +1 -1
  221. package/dist/gates/command.d.ts.map +1 -1
  222. package/dist/gates/command.js +10 -22
  223. package/dist/gates/command.js.map +1 -1
  224. package/dist/gates/contract.d.ts.map +1 -1
  225. package/dist/gates/contract.js +22 -61
  226. package/dist/gates/contract.js.map +1 -1
  227. package/dist/gates/performance.d.ts.map +1 -1
  228. package/dist/gates/performance.js +13 -31
  229. package/dist/gates/performance.js.map +1 -1
  230. package/dist/gates/review.d.ts.map +1 -1
  231. package/dist/gates/review.js +21 -53
  232. package/dist/gates/review.js.map +1 -1
  233. package/dist/gates/security.d.ts.map +1 -1
  234. package/dist/gates/security.js +14 -32
  235. package/dist/gates/security.js.map +1 -1
  236. package/dist/gates/types.d.ts +28 -5
  237. package/dist/gates/types.d.ts.map +1 -1
  238. package/dist/gates/types.js +38 -1
  239. package/dist/gates/types.js.map +1 -1
  240. package/dist/hooks/bootstrap.d.ts +2 -0
  241. package/dist/hooks/bootstrap.d.ts.map +1 -1
  242. package/dist/hooks/bootstrap.js +5 -2
  243. package/dist/hooks/bootstrap.js.map +1 -1
  244. package/dist/index.d.ts +1 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/knowledge/audit.d.ts +7 -2
  248. package/dist/knowledge/audit.d.ts.map +1 -1
  249. package/dist/knowledge/audit.js +59 -79
  250. package/dist/knowledge/audit.js.map +1 -1
  251. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  252. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  253. package/dist/knowledge/flywheel-metrics.js +47 -0
  254. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  255. package/dist/knowledge/import.d.ts.map +1 -1
  256. package/dist/knowledge/import.js +4 -14
  257. package/dist/knowledge/import.js.map +1 -1
  258. package/dist/knowledge/index-generator.d.ts.map +1 -1
  259. package/dist/knowledge/index-generator.js +17 -14
  260. package/dist/knowledge/index-generator.js.map +1 -1
  261. package/dist/knowledge/ingest.d.ts.map +1 -1
  262. package/dist/knowledge/ingest.js +9 -4
  263. package/dist/knowledge/ingest.js.map +1 -1
  264. package/dist/knowledge/migration.d.ts.map +1 -1
  265. package/dist/knowledge/migration.js +11 -8
  266. package/dist/knowledge/migration.js.map +1 -1
  267. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  268. package/dist/knowledge/reference-tracker.js +12 -9
  269. package/dist/knowledge/reference-tracker.js.map +1 -1
  270. package/dist/knowledge/store.d.ts +15 -0
  271. package/dist/knowledge/store.d.ts.map +1 -1
  272. package/dist/knowledge/store.js +59 -10
  273. package/dist/knowledge/store.js.map +1 -1
  274. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  275. package/dist/monitoring/context-tracker.js +11 -20
  276. package/dist/monitoring/context-tracker.js.map +1 -1
  277. package/dist/monitoring/index.d.ts +1 -1
  278. package/dist/monitoring/index.d.ts.map +1 -1
  279. package/dist/monitoring/index.js +1 -2
  280. package/dist/monitoring/index.js.map +1 -1
  281. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  282. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  283. package/dist/monitoring/trace-analyzer.js +19 -2
  284. package/dist/monitoring/trace-analyzer.js.map +1 -1
  285. package/dist/monitoring/traces.d.ts +21 -2
  286. package/dist/monitoring/traces.d.ts.map +1 -1
  287. package/dist/monitoring/traces.js +38 -29
  288. package/dist/monitoring/traces.js.map +1 -1
  289. package/dist/release/index.d.ts +2 -2
  290. package/dist/release/index.d.ts.map +1 -1
  291. package/dist/release/index.js +1 -5
  292. package/dist/release/index.js.map +1 -1
  293. package/dist/sdd/index-generator.js +10 -11
  294. package/dist/sdd/index-generator.js.map +1 -1
  295. package/dist/test-setup/project-fixture.d.ts +48 -0
  296. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  297. package/dist/test-setup/project-fixture.js +106 -0
  298. package/dist/test-setup/project-fixture.js.map +1 -0
  299. package/dist/types/constraint.d.ts +8 -18
  300. package/dist/types/constraint.d.ts.map +1 -1
  301. package/dist/types/constraint.js +6 -1
  302. package/dist/types/constraint.js.map +1 -1
  303. package/dist/types/passes-gate.d.ts +10 -25
  304. package/dist/types/passes-gate.d.ts.map +1 -1
  305. package/dist/types/project-config.d.ts +1 -1
  306. package/dist/types/session.d.ts +6 -5
  307. package/dist/types/session.d.ts.map +1 -1
  308. package/dist/types/trace.d.ts +9 -0
  309. package/dist/types/trace.d.ts.map +1 -1
  310. package/dist/utils/exec.d.ts +10 -0
  311. package/dist/utils/exec.d.ts.map +1 -1
  312. package/dist/utils/exec.js +12 -0
  313. package/dist/utils/exec.js.map +1 -1
  314. package/dist/utils/file-walk.d.ts +9 -1
  315. package/dist/utils/file-walk.d.ts.map +1 -1
  316. package/dist/utils/file-walk.js +10 -1
  317. package/dist/utils/file-walk.js.map +1 -1
  318. package/dist/utils/frontmatter.d.ts +73 -0
  319. package/dist/utils/frontmatter.d.ts.map +1 -0
  320. package/dist/utils/frontmatter.js +141 -0
  321. package/dist/utils/frontmatter.js.map +1 -0
  322. package/dist/utils/jsonl.d.ts +92 -0
  323. package/dist/utils/jsonl.d.ts.map +1 -0
  324. package/dist/utils/jsonl.js +166 -0
  325. package/dist/utils/jsonl.js.map +1 -0
  326. package/dist/utils/package-version.d.ts +16 -0
  327. package/dist/utils/package-version.d.ts.map +1 -0
  328. package/dist/utils/package-version.js +63 -0
  329. package/dist/utils/package-version.js.map +1 -0
  330. package/package.json +1 -1
  331. package/src/CONTEXT.md +8 -2
  332. package/src/__tests__/acceptance-gate.test.ts +3 -1
  333. package/src/__tests__/checker-extra.test.ts +7 -1218
  334. package/src/__tests__/context-files-resolution.test.ts +116 -0
  335. package/src/__tests__/effective-constraints.test.ts +10 -13
  336. package/src/__tests__/exec.test.ts +35 -1
  337. package/src/__tests__/layering.test.ts +152 -0
  338. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  339. package/src/__tests__/passes-gate.test.ts +130 -101
  340. package/src/__tests__/skip-semantics.test.ts +5 -9
  341. package/src/cli/__tests__/command-contract.test.ts +170 -0
  342. package/src/cli/command-contract.ts +108 -0
  343. package/src/cli/commands/CONTEXT.md +23 -8
  344. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  345. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  346. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +107 -0
  347. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  348. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  349. package/src/cli/commands/__tests__/check.test.ts +448 -378
  350. package/src/cli/commands/__tests__/command.test.ts +85 -70
  351. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  352. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  353. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  354. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  355. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  356. package/src/cli/commands/__tests__/init.test.ts +77 -48
  357. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  358. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  359. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  360. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  361. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  362. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  363. package/src/cli/commands/__tests__/project-path-convention.test.ts +231 -0
  364. package/src/cli/commands/__tests__/registry.test.ts +75 -35
  365. package/src/cli/commands/__tests__/release.test.ts +209 -80
  366. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  367. package/src/cli/commands/__tests__/report.test.ts +25 -25
  368. package/src/cli/commands/__tests__/review.test.ts +33 -31
  369. package/src/cli/commands/__tests__/security.test.ts +23 -27
  370. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  371. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  372. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  373. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  374. package/src/cli/commands/__tests__/status.test.ts +34 -34
  375. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  376. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  377. package/src/cli/commands/__tests__/update-user-model.test.ts +97 -34
  378. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  379. package/src/cli/commands/acceptance.ts +35 -24
  380. package/src/cli/commands/analyze-sessions.ts +25 -24
  381. package/src/cli/commands/check.ts +127 -66
  382. package/src/cli/commands/command.ts +30 -17
  383. package/src/cli/commands/constraints-report.ts +37 -34
  384. package/src/cli/commands/constraints-retire.ts +58 -55
  385. package/src/cli/commands/constraints.ts +10 -27
  386. package/src/cli/commands/contract.ts +51 -42
  387. package/src/cli/commands/definitions.ts +34 -227
  388. package/src/cli/commands/failure.ts +33 -26
  389. package/src/cli/commands/init.ts +166 -220
  390. package/src/cli/commands/knowledge.ts +192 -289
  391. package/src/cli/commands/passes-gate.ts +58 -68
  392. package/src/cli/commands/performance.ts +23 -18
  393. package/src/cli/commands/posteval-plan.ts +29 -15
  394. package/src/cli/commands/release.ts +70 -59
  395. package/src/cli/commands/report.ts +9 -5
  396. package/src/cli/commands/review.ts +39 -29
  397. package/src/cli/commands/sdd.ts +7 -5
  398. package/src/cli/commands/security.ts +35 -26
  399. package/src/cli/commands/spec-baseline-check.ts +16 -11
  400. package/src/cli/commands/spec.ts +66 -53
  401. package/src/cli/commands/status.ts +59 -53
  402. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  403. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  404. package/src/cli/commands/sync-docs/index.ts +73 -100
  405. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  406. package/src/cli/commands/update-user-model.ts +64 -61
  407. package/src/cli/commands/validate.ts +31 -25
  408. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  409. package/src/cli/session-mining/transcript.ts +26 -8
  410. package/src/context/session-manager.ts +19 -33
  411. package/src/core/CONTEXT.md +10 -6
  412. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  413. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  414. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  415. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  416. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  417. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  418. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  419. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  420. package/src/core/constraints/capabilities-parser.ts +53 -35
  421. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  422. package/src/core/constraints/checker.ts +82 -133
  423. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +273 -0
  424. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
  425. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  426. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +126 -0
  427. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  428. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  429. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  430. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  431. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  432. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +153 -0
  433. package/src/core/constraints/checkers/capability-sync.ts +75 -96
  434. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  435. package/src/core/constraints/checkers/docs-freshness.ts +54 -70
  436. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  437. package/src/core/constraints/checkers/index.ts +9 -2
  438. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  439. package/src/core/constraints/checkers/types.ts +60 -4
  440. package/src/core/constraints/context-builder.ts +39 -91
  441. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  442. package/src/core/constraints/git-evidence.ts +120 -0
  443. package/src/core/constraints/injection-drift.ts +13 -60
  444. package/src/core/constraints/injection-writer.ts +212 -0
  445. package/src/core/constraints/usage-report.ts +36 -17
  446. package/src/core/index.ts +4 -2
  447. package/src/core/project-config-loader.ts +49 -9
  448. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  449. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  450. package/src/core/validators/passes-gate.ts +86 -248
  451. package/src/core/validators/test-output.ts +128 -0
  452. package/src/failure/__tests__/recorder.test.ts +168 -0
  453. package/src/failure/recorder.ts +12 -10
  454. package/src/gates/CONTEXT.md +7 -4
  455. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  456. package/src/gates/__tests__/acceptance.test.ts +4 -86
  457. package/src/gates/__tests__/checker-gate.test.ts +24 -0
  458. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  459. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  460. package/src/gates/acceptance.ts +19 -84
  461. package/src/gates/checker-gate.ts +12 -15
  462. package/src/gates/command.ts +10 -16
  463. package/src/gates/contract.ts +22 -61
  464. package/src/gates/performance.ts +10 -22
  465. package/src/gates/review.ts +21 -41
  466. package/src/gates/security.ts +10 -22
  467. package/src/gates/types.ts +70 -5
  468. package/src/hooks/CONTEXT.md +1 -1
  469. package/src/hooks/bootstrap.ts +5 -2
  470. package/src/index.ts +0 -4
  471. package/src/knowledge/CONTEXT.md +8 -3
  472. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  473. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  474. package/src/knowledge/__tests__/import.test.ts +44 -0
  475. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  476. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  477. package/src/knowledge/__tests__/migration.test.ts +47 -0
  478. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  479. package/src/knowledge/__tests__/store.test.ts +194 -0
  480. package/src/knowledge/audit.ts +67 -71
  481. package/src/knowledge/flywheel-metrics.ts +71 -0
  482. package/src/knowledge/import.ts +4 -18
  483. package/src/knowledge/index-generator.ts +19 -14
  484. package/src/knowledge/ingest.ts +9 -4
  485. package/src/knowledge/migration.ts +11 -8
  486. package/src/knowledge/reference-tracker.ts +12 -8
  487. package/src/knowledge/store.ts +64 -11
  488. package/src/monitoring/CONTEXT.md +8 -3
  489. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  490. package/src/monitoring/__tests__/traces.test.ts +57 -0
  491. package/src/monitoring/context-tracker.ts +11 -23
  492. package/src/monitoring/index.ts +1 -1
  493. package/src/monitoring/trace-analyzer.ts +21 -2
  494. package/src/monitoring/traces.ts +39 -36
  495. package/src/release/CONTEXT.md +1 -0
  496. package/src/release/__tests__/barrel.test.ts +60 -0
  497. package/src/release/index.ts +2 -13
  498. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  499. package/src/sdd/index-generator.ts +9 -9
  500. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  501. package/src/test-setup/project-fixture.ts +87 -0
  502. package/src/tools/CONTEXT.md +6 -0
  503. package/src/tools/__tests__/registry.test.ts +221 -0
  504. package/src/types/__tests__/types.test.ts +4 -5
  505. package/src/types/constraint.ts +16 -27
  506. package/src/types/passes-gate.ts +10 -30
  507. package/src/types/project-config.ts +1 -1
  508. package/src/types/session.ts +5 -5
  509. package/src/types/trace.ts +10 -0
  510. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  511. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  512. package/src/utils/__tests__/jsonl.test.ts +187 -0
  513. package/src/utils/__tests__/package-version.test.ts +97 -0
  514. package/src/utils/exec.ts +15 -0
  515. package/src/utils/file-walk.ts +11 -2
  516. package/src/utils/frontmatter.ts +121 -0
  517. package/src/utils/jsonl.ts +167 -0
  518. package/src/utils/package-version.ts +27 -0
  519. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  520. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  521. package/dist/cli/commands/doc-freshness-check.js +0 -352
  522. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  523. package/dist/types/index.d.ts +0 -14
  524. package/dist/types/index.d.ts.map +0 -1
  525. package/dist/types/index.js +0 -39
  526. package/dist/types/index.js.map +0 -1
  527. package/src/__tests__/long-running.test.ts +0 -17
  528. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  529. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  530. package/src/cli/commands/doc-freshness-check.ts +0 -386
  531. package/src/spec/CONTEXT.md +0 -11
  532. package/src/types/index.ts +0 -47
@@ -3,15 +3,18 @@
3
3
  */
4
4
 
5
5
  import * as fs from 'fs';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as path from 'path';
7
8
 
9
+ let io: CapturingIO;
10
+ beforeEach(() => {
11
+ io = captureIO();
12
+ });
13
+
8
14
  describe('postevalPlan', () => {
9
15
  jest.setTimeout(30000); // Retry-based tests with exponential backoff need longer timeout
10
16
  const tempDir = path.join(process.cwd(), 'temp-test-posteval');
11
17
  const planPath = path.join(tempDir, 'plan.md');
12
- let consoleErrorSpy: jest.SpyInstance;
13
- let consoleLogSpy: jest.SpyInstance;
14
- let mockExit: jest.SpyInstance;
15
18
  let originalFetch: typeof fetch;
16
19
  let originalEnv: NodeJS.ProcessEnv;
17
20
 
@@ -34,19 +37,11 @@ describe('postevalPlan', () => {
34
37
  originalEnv = { ...process.env };
35
38
  delete process.env.API_PORT;
36
39
  delete process.env.POSTEVAL_GRACE_MS;
37
- consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
38
- consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
39
- mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
40
- // Don't actually exit
41
- }) as any);
42
40
  });
43
41
 
44
42
  afterEach(() => {
45
43
  global.fetch = originalFetch;
46
44
  process.env = originalEnv;
47
- consoleErrorSpy.mockRestore();
48
- consoleLogSpy.mockRestore();
49
- mockExit.mockRestore();
50
45
  jest.useRealTimers();
51
46
  });
52
47
 
@@ -61,12 +56,12 @@ describe('postevalPlan', () => {
61
56
  }) as any;
62
57
 
63
58
  const { postevalPlan } = await import('../posteval-plan');
64
- await postevalPlan({ planPath });
59
+ await postevalPlan({ planPath }, io);
65
60
 
66
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('100%'));
61
+ expect(io.outText()).toContain('100%');
67
62
  });
68
63
 
69
- it('completeness < 1 应该 exit(1)', async () => {
64
+ it('completeness < 1:fail(覆盖率不足,含缺失项)', async () => {
70
65
  global.fetch = jest.fn().mockResolvedValue({
71
66
  ok: true,
72
67
  json: () => Promise.resolve({
@@ -77,10 +72,10 @@ describe('postevalPlan', () => {
77
72
  }) as any;
78
73
 
79
74
  const { postevalPlan } = await import('../posteval-plan');
80
- await postevalPlan({ planPath });
75
+ const result = await postevalPlan({ planPath }, io);
81
76
 
82
- expect(mockExit).toHaveBeenCalledWith(1);
83
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('50%'));
77
+ expect(result).toEqual({ kind: 'fail', reason: expect.stringContaining('plan coverage 50% < 100%') });
78
+ expect(io.errText()).toContain('50%');
84
79
  });
85
80
 
86
81
  it('completeness < 1 时应该列出缺失项', async () => {
@@ -94,10 +89,13 @@ describe('postevalPlan', () => {
94
89
  }) as any;
95
90
 
96
91
  const { postevalPlan } = await import('../posteval-plan');
97
- await postevalPlan({ planPath });
92
+ const result = await postevalPlan({ planPath }, io);
98
93
 
99
- expect(mockExit).toHaveBeenCalledWith(1);
100
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('AC-002'));
94
+ expect(result).toEqual({
95
+ kind: 'fail',
96
+ reason: 'plan coverage 33% < 100%,缺 2 项: AC-002; AC-003',
97
+ });
98
+ expect(io.errText()).toContain('AC-002');
101
99
  });
102
100
 
103
101
  it('4xx 错误应该 exit(1)', async () => {
@@ -108,13 +106,13 @@ describe('postevalPlan', () => {
108
106
  }) as any;
109
107
 
110
108
  const { postevalPlan } = await import('../posteval-plan');
111
- await postevalPlan({ planPath });
109
+ const result = await postevalPlan({ planPath }, io);
112
110
 
113
- expect(mockExit).toHaveBeenCalledWith(1);
114
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('400'));
111
+ expect(result).toEqual({ kind: 'fail', reason: 'PostEval API error: 400 Bad Request' });
112
+ expect(io.errText()).toContain('400');
115
113
  });
116
114
 
117
- it('5xx 错误应该 exit(0) 允许提交', async () => {
115
+ it('5xx 错误:skip(未判定,允许提交;退出码面 0 不变)', async () => {
118
116
  global.fetch = jest.fn().mockResolvedValue({
119
117
  ok: false,
120
118
  status: 503,
@@ -122,20 +120,20 @@ describe('postevalPlan', () => {
122
120
  }) as any;
123
121
 
124
122
  const { postevalPlan } = await import('../posteval-plan');
125
- await postevalPlan({ planPath });
123
+ const result = await postevalPlan({ planPath }, io);
126
124
 
127
- expect(mockExit).toHaveBeenCalledWith(0);
128
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('unavailable'));
125
+ expect(result).toEqual({ kind: 'skip', reason: expect.stringContaining('服务端错误 503') });
126
+ expect(io.errText()).toContain('unavailable');
129
127
  });
130
128
 
131
- it('网络错误重试耗尽后应该 exit(0)', async () => {
129
+ it('网络错误重试耗尽:skip(未判定,允许提交)', async () => {
132
130
  global.fetch = jest.fn().mockRejectedValue(new Error('ECONNREFUSED'));
133
131
 
134
132
  const { postevalPlan } = await import('../posteval-plan');
135
- await postevalPlan({ planPath });
133
+ const result = await postevalPlan({ planPath }, io);
136
134
 
137
- expect(mockExit).toHaveBeenCalledWith(0);
138
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('unreachable'));
135
+ expect(result).toEqual({ kind: 'skip', reason: expect.stringContaining('Studio API 不可达') });
136
+ expect(io.errText()).toContain('unreachable');
139
137
  });
140
138
 
141
139
  it('应该支持自定义 API_PORT', async () => {
@@ -150,7 +148,7 @@ describe('postevalPlan', () => {
150
148
  }) as any;
151
149
 
152
150
  const { postevalPlan } = await import('../posteval-plan');
153
- await postevalPlan({ planPath });
151
+ await postevalPlan({ planPath }, io);
154
152
 
155
153
  // Verify the fetch was called with port 3999
156
154
  const fetchCall = (global.fetch as jest.Mock).mock.calls[0][0];
@@ -174,12 +172,12 @@ describe('postevalPlan', () => {
174
172
  global.fetch = mockFetch as any;
175
173
 
176
174
  const { postevalPlan } = await import('../posteval-plan');
177
- await postevalPlan({ planPath });
175
+ const result = await postevalPlan({ planPath }, io);
178
176
 
179
177
  // Should have been called 3 times
180
178
  expect(mockFetch).toHaveBeenCalledTimes(3);
181
- expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('100%'));
182
- expect(mockExit).not.toHaveBeenCalled();
179
+ expect(io.outText()).toContain('100%');
180
+ expect(result.kind).toBe('ok');
183
181
  });
184
182
 
185
183
  it('completeness 为 0 时应该正确显示 0%', async () => {
@@ -193,9 +191,12 @@ describe('postevalPlan', () => {
193
191
  }) as any;
194
192
 
195
193
  const { postevalPlan } = await import('../posteval-plan');
196
- await postevalPlan({ planPath });
194
+ const result = await postevalPlan({ planPath }, io);
197
195
 
198
- expect(mockExit).toHaveBeenCalledWith(1);
199
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('0%'));
196
+ expect(result).toEqual({
197
+ kind: 'fail',
198
+ reason: 'plan coverage 0% < 100%,缺 1 项: AC-001',
199
+ });
200
+ expect(io.errText()).toContain('0%');
200
201
  });
201
202
  });
@@ -0,0 +1,157 @@
1
+ /**
2
+ * -p/--project-path 锚定回归(harness#95)
3
+ *
4
+ * 两条门禁命令都接受 -p,但真正执行/IO 的一侧过去取 process.cwd():不 cd 到目标工程调用,
5
+ * 就会拿 A 工程的命令、在 B 目录跑、把证据写进 B;acceptance 的「无 tasks.yml 即跳过」
6
+ * 本身是 passed:true,于是 -p 失效直接表现为假绿。
7
+ *
8
+ * 既有单测全部 mock fs/exec + 假 projectPath,恰好抹平两种取径的差异。本文件不 mock 任何 IO,
9
+ * 前提统一为 **cwd ≠ projectPath**(cwd = A,-p = B),断言读写位置落在 B。
10
+ * 站点 2 从两面各断言一次:CLI 命令面(`acceptance -p`)与门禁实例面(默认构造 = 注册表单例形状)。
11
+ */
12
+
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { createProjectFixture } from '../../../test-setup/project-fixture';
16
+ import { SpecAcceptanceGate } from '../../../gates/acceptance';
17
+ import { captureIO } from '../../command-contract';
18
+ import { runPassesGate } from '../passes-gate';
19
+ import { acceptance } from '../acceptance';
20
+
21
+ /** 在自身 cwd 落标记文件:哪一侧被真正调用,标记就出现在哪一侧的目录里 */
22
+ function markScript(marker: string): string {
23
+ return [
24
+ 'const fs = require("fs");',
25
+ `fs.writeFileSync("${marker}-exec-location.txt", process.cwd());`,
26
+ 'console.log("1 passed");',
27
+ '',
28
+ ].join('\n');
29
+ }
30
+
31
+ const PACKAGE_JSON = JSON.stringify({
32
+ name: 'fixture',
33
+ version: '1.0.0',
34
+ scripts: { test: 'node ./mark.js' },
35
+ });
36
+
37
+ /** acceptance_criteria 形状:checked 决定判定,两侧只差 checked 值 → 读错文件必然改变结论 */
38
+ function tasksYml(checked: boolean): string {
39
+ return [
40
+ 'tasks:',
41
+ ' - id: TASK-001',
42
+ ' title: 锚定用例',
43
+ ' acceptance_criteria:',
44
+ ' - id: crit-1',
45
+ ' description: 需要人工确认的验收条件',
46
+ ' type: manual',
47
+ ' required: true',
48
+ ` checked: ${checked}`,
49
+ '',
50
+ ].join('\n');
51
+ }
52
+
53
+ /** passes-gate 两侧夹具:A = 调用方所在目录(cwd),B = -p 指向的目标工程 */
54
+ function createPassesGateSides(): { cwdSide: string; projectSide: string } {
55
+ return {
56
+ cwdSide: createProjectFixture({
57
+ name: 'pganchor-cwd-side',
58
+ files: { 'package.json': PACKAGE_JSON, 'mark.js': markScript('cwd-side') },
59
+ }),
60
+ projectSide: createProjectFixture({
61
+ name: 'pganchor-project-side',
62
+ files: { 'package.json': PACKAGE_JSON, 'mark.js': markScript('project-side') },
63
+ }),
64
+ };
65
+ }
66
+
67
+ describe('-p 锚定:执行与 IO 必须落在 projectPath(cwd ≠ projectPath)', () => {
68
+ const originalCwd = process.cwd();
69
+
70
+ afterEach(() => {
71
+ process.chdir(originalCwd);
72
+ });
73
+
74
+ describe('passes-gate(站点 1:执行 + 证据落盘)', () => {
75
+ it('-p 指向 B → B 的 package.json 测试命令在 B 执行,证据落 B/.harness/evidence', async () => {
76
+ const { cwdSide, projectSide } = createPassesGateSides();
77
+ process.chdir(cwdSide);
78
+
79
+ const result = await runPassesGate({ projectPath: projectSide }, captureIO());
80
+
81
+ expect(result).toEqual({ kind: 'ok' });
82
+ expect(fs.readFileSync(path.join(projectSide, 'project-side-exec-location.txt'), 'utf-8'))
83
+ .toBe(projectSide);
84
+ // 跑在 B 的判据同时要求 A 侧什么都没被执行、没被写入
85
+ expect(fs.existsSync(path.join(cwdSide, 'cwd-side-exec-location.txt'))).toBe(false);
86
+ expect(fs.existsSync(path.join(cwdSide, '.harness', 'evidence'))).toBe(false);
87
+
88
+ const evidenceDir = path.join(projectSide, '.harness', 'evidence');
89
+ expect(fs.existsSync(evidenceDir)).toBe(true);
90
+ expect(fs.readdirSync(evidenceDir).filter(f => f.startsWith('test-'))).toHaveLength(1);
91
+ });
92
+ });
93
+
94
+ describe('acceptance(站点 2:tasks.yml 解析)', () => {
95
+ it('默认构造的门禁实例(registry / getEffectiveGates 形状)→ 读 B/tasks.yml,不读 cwd 的', async () => {
96
+ const cwdSide = createProjectFixture({
97
+ name: 'acccfg-cwd-side',
98
+ files: { 'tasks.yml': tasksYml(true) },
99
+ });
100
+ const projectSide = createProjectFixture({
101
+ name: 'acccfg-project-side',
102
+ files: { 'tasks.yml': tasksYml(false) },
103
+ });
104
+ process.chdir(cwdSide);
105
+
106
+ // 无 config 构造 = 注册表单例的形状(./tasks.yml 相对默认值在这一支按 cwd 解析)
107
+ const result = await new SpecAcceptanceGate().check({
108
+ projectPath: projectSide,
109
+ taskId: 'TASK-001',
110
+ });
111
+
112
+ expect(result.passed).toBe(false);
113
+ expect(result.message).toContain('1 unchecked acceptance criteria');
114
+ });
115
+
116
+ it('-p 指向 B → 读 B/tasks.yml,不因 cwd 无文件而假绿', async () => {
117
+ const cwdSide = createProjectFixture({
118
+ name: 'accanchor-cwd-side',
119
+ files: { 'tasks.yml': tasksYml(true) },
120
+ });
121
+ const projectSide = createProjectFixture({
122
+ name: 'accanchor-project-side',
123
+ files: { 'tasks.yml': tasksYml(false) },
124
+ });
125
+ process.chdir(cwdSide);
126
+
127
+ const result = await acceptance({ projectPath: projectSide, taskId: 'TASK-001' }, captureIO());
128
+
129
+ expect(result).toEqual({
130
+ kind: 'fail',
131
+ reason: expect.stringContaining('1 unchecked acceptance criteria'),
132
+ });
133
+ });
134
+
135
+ it('--tasks-path 相对路径按 projectPath 解析,不按 cwd', async () => {
136
+ const cwdSide = createProjectFixture({
137
+ name: 'accanchorrel-cwd-side',
138
+ files: { 'config/tasks.yml': tasksYml(true) },
139
+ });
140
+ const projectSide = createProjectFixture({
141
+ name: 'accanchorrel-project-side',
142
+ files: { 'config/tasks.yml': tasksYml(false) },
143
+ });
144
+ process.chdir(cwdSide);
145
+
146
+ const result = await acceptance(
147
+ { projectPath: projectSide, taskId: 'TASK-001', tasksPath: 'config/tasks.yml' },
148
+ captureIO(),
149
+ );
150
+
151
+ expect(result).toEqual({
152
+ kind: 'fail',
153
+ reason: expect.stringContaining('1 unchecked acceptance criteria'),
154
+ });
155
+ });
156
+ });
157
+ });
@@ -0,0 +1,231 @@
1
+ /**
2
+ * projectPath 传递约定守护(harness#95)
3
+ *
4
+ * 约定(正本见本目录 CONTEXT.md「约定」段):**projectPath 只在 CLI 入口做一次 `|| process.cwd()`
5
+ * 兜底,之后必须传到每个 IO/执行点**——下游禁止再取一次 cwd,也禁止用相对路径默认值
6
+ * (两者都会让 `-p` 半失效:读写/执行位置悄悄回到调用方的 cwd)。
7
+ *
8
+ * 本文件是这条约定的机器可检面,三道闸:
9
+ * 1. CLI 入口层:cwd 只能以 `xxx || process.cwd()` 的兜底形状出现,例外逐个点名并记理由
10
+ * 2. 下游层(= src 减 cli,含 core / gates / context / monitoring / hooks 等):cwd 站点**逐行**冻结
11
+ * (harness#98,不再只冻文件键集——豁免文件内新增站点/行变形同样失败),
12
+ * 要么把根传下去,要么在此记下豁免理由
13
+ * 3. 相对路径默认值同理冻结(harness#98 收紧:对象字面量 `xxxPath: '相对'` 之外,
14
+ * 参数默认值形 `xxxPath = '相对'`(含类型标注)与模板字面量同罪;扫描域 = 整个下游层)
15
+ *
16
+ * 冻结集合还要求**每条豁免仍然成立**(站点消失却不删条目 → 同样失败),豁免不会烂成化石。
17
+ * 「根参数已传入却又取 cwd」这类静态看不出的漂移,由 project-path-anchoring.test.ts 的真实 IO 行为用例守。
18
+ */
19
+
20
+ import * as fs from 'fs';
21
+ import * as path from 'path';
22
+
23
+ const SRC_ROOT = path.join(__dirname, '..', '..', '..', '..', 'src');
24
+
25
+ /** 注释行不参与匹配:文档里「缺省 process.cwd()」这类描述不是站点 */
26
+ function codeLines(file: string): string[] {
27
+ return fs
28
+ .readFileSync(file, 'utf-8')
29
+ .split('\n')
30
+ .map(line => line.trim())
31
+ .filter(line => !line.startsWith('*') && !line.startsWith('//') && !line.startsWith('/*'));
32
+ }
33
+
34
+ function listTsFiles(dir: string, skipDirs: string[] = []): string[] {
35
+ const found: string[] = [];
36
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
37
+ const full = path.join(dir, entry.name);
38
+ if (entry.isDirectory()) {
39
+ if (entry.name === '__tests__' || skipDirs.includes(entry.name)) continue;
40
+ found.push(...listTsFiles(full, skipDirs));
41
+ } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {
42
+ found.push(full);
43
+ }
44
+ }
45
+ return found.sort();
46
+ }
47
+
48
+ /** 仓库相对路径(豁免表以此为准,跨机器稳定) */
49
+ function repoPath(abs: string): string {
50
+ return path.relative(path.join(SRC_ROOT, '..'), abs).split(path.sep).join('/');
51
+ }
52
+
53
+ function cwdSites(files: string[]): Map<string, string[]> {
54
+ const sites = new Map<string, string[]>();
55
+ for (const file of files) {
56
+ const lines = codeLines(file).filter(line => line.includes('process.cwd()'));
57
+ if (lines.length > 0) sites.set(repoPath(file), lines);
58
+ }
59
+ return sites;
60
+ }
61
+
62
+ // ========================================
63
+ // 闸 1:CLI 入口层的非兜底形状 cwd
64
+ // ========================================
65
+
66
+ /** CLI 层的合法形状:入口对 `-p/--project-path` 做一次性兜底 */
67
+ const ENTRY_FALLBACK_SHAPE = /\|\|\s*process\.cwd\(\)/;
68
+
69
+ /** 兜底形状之外的 CLI cwd 站点:逐个裁决过,理由与站点原文一起冻结 */
70
+ const CLI_CWD_EXEMPTIONS: Record<string, { lines: string[]; reason: string }> = {
71
+ 'src/cli/commands/release.ts': {
72
+ lines: ['const pkgPath = process.cwd();'],
73
+ reason:
74
+ 'release 定义表里没有 -p/--project-path(只收 --bump/--dry-run),pkgPath 就是该命令唯一的根,' +
75
+ '且已逐个传给每条 run(cmd, pkgPath) 与 verifyReleaseArtifacts(pkgPath)——不构成 -p 半失效。' +
76
+ '给发布流水线新增 -p 属新能力(可指向任意目录做 push/publish),不在 #95 范围。',
77
+ },
78
+ };
79
+
80
+ // ========================================
81
+ // 闸 2:下游层的 cwd 站点
82
+ // ========================================
83
+
84
+ /**
85
+ * 下游豁免的共同理由:这些是**库层公开 API 的可选根参数默认值**(调用方没给根时的缺省),
86
+ * 不是「根已经传进来了,取 IO 时又绕回 cwd」。core 不能 import cli,兜底只能留在各自入口,
87
+ * 故冻结而非删除;新增站点必须在此逐个点名并单独说明。
88
+ *
89
+ * harness#98:冻结粒度从文件键集收紧到**行内容**——已豁免文件内悄悄新增 cwd 站点
90
+ * (不动键集)曾是绕过通道,现在新增行/行变形都会顶失败。
91
+ */
92
+ const DOWNSTREAM_CWD_EXEMPTIONS: Record<string, { lines: string[]; reason: string }> = {
93
+ 'src/context/session-manager.ts': {
94
+ lines: ['this.basePath = basePath || process.cwd();'],
95
+ reason: '构造参数 basePath 缺省(会话存储根,CLI 已显式传 projectPath)',
96
+ },
97
+ 'src/core/constraints/checker.ts': {
98
+ lines: [
99
+ 'const projectPath = context.projectPath || process.cwd();',
100
+ 'const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());',
101
+ 'const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());',
102
+ ],
103
+ reason: 'check 上下文缺 projectPath 时的根兜底(3 处同一形状)',
104
+ },
105
+ 'src/core/constraints/checkers/types.ts': {
106
+ lines: ['const projectPath = context.projectPath || process.cwd();'],
107
+ reason: 'checker 环境的根兜底(context.projectPath 可选)',
108
+ },
109
+ 'src/core/constraints/context-builder.ts': {
110
+ lines: [
111
+ 'const projectPath = options.projectPath || process.cwd();',
112
+ 'const projectPath = options.projectPath || process.cwd();',
113
+ ],
114
+ reason: 'buildCheckEnv 的 options.projectPath 缺省兜底(2 处)',
115
+ },
116
+ 'src/core/constraints/usage-report.ts': {
117
+ lines: ['projectRoot: string = process.cwd(),'],
118
+ reason: 'projectRoot 形参默认值(库层可选根)',
119
+ },
120
+ 'src/core/effective-constraints.ts': {
121
+ lines: [
122
+ 'projectRoot: string = process.cwd(),',
123
+ 'projectRoot: string = process.cwd(),',
124
+ 'export function lintEffectiveConfig(projectRoot: string = process.cwd()): EffectiveConfigLint {',
125
+ ],
126
+ reason: '生效集三函数 projectRoot 形参默认值(库层可选根,JSDoc 已声明)',
127
+ },
128
+ 'src/core/project-config-loader.ts': {
129
+ lines: ['this.projectPath = projectPath || process.cwd();'],
130
+ reason: 'config 加载器 projectPath 构造参数缺省兜底',
131
+ },
132
+ 'src/core/spec/validator.ts': {
133
+ lines: ['const cwd = projectPath || process.cwd();'],
134
+ reason: 'validateAll 的可选 projectPath 兜底(其相对 schemaPath 默认值见闸 3 豁免)',
135
+ },
136
+ 'src/gates/checker-gate.ts': {
137
+ lines: ['const projectPath = ctx.projectPath || process.cwd();'],
138
+ reason: 'GateContext.projectPath 缺省兜底(门禁适配器的根入口)',
139
+ },
140
+ 'src/gates/effective-gates.ts': {
141
+ lines: ['export function getEffectiveGates(projectRoot: string = process.cwd()): Gate[] {'],
142
+ reason: 'projectRoot 形参默认值(库层可选根)',
143
+ },
144
+ 'src/hooks/bootstrap.ts': {
145
+ lines: [
146
+ 'const resolvedPath = projectPath || process.cwd();',
147
+ 'const resolvedPath = projectPath || process.cwd();',
148
+ ],
149
+ reason: 'bootstrapHarness 的可选 projectPath 兜底(组合根入口,2 处)',
150
+ },
151
+ 'src/monitoring/context-tracker.ts': {
152
+ lines: ['const base = basePath || process.cwd();'],
153
+ reason: 'basePath 构造参数缺省兜底',
154
+ },
155
+ };
156
+
157
+ // ========================================
158
+ // 闸 3:下游层的相对路径默认值
159
+ // ========================================
160
+
161
+ /**
162
+ * key 必须 camelCase 以 Path 结尾,值为相对字面量——这类默认值按 cwd 解析,即 #95 的病根形状。
163
+ * harness#98 收紧匹配形状(原为只认对象字面量 `xxxPath: '相对'`):
164
+ * 参数默认值形 `xxxPath = '相对'`、带类型标注的 `xxxPath: string = '相对'`、模板字面量、
165
+ * 双引号形同罪(仓内 lint 不强制引号风格,三种引号都得认)。
166
+ */
167
+ const RELATIVE_PATH_DEFAULT = /\b\w+Path\s*(?::\s*[^=,)]+?)?[:=]\s*['"`](?!\/)/;
168
+
169
+ const RELATIVE_PATH_DEFAULT_EXEMPTIONS: Record<string, string> = {
170
+ 'src/core/spec/validator.ts':
171
+ 'schemaPath: "./specs/schemas" 确实按 cwd 解析(同型病灶)。不随 #95 修:validateFile()/loadSchema() ' +
172
+ '签名里没有根,补齐要把根穿透整个 spec 域(属 spec 重构,非本票两门禁)。CLI 侧 `spec -s` 已 ' +
173
+ 'path.resolve(projectPath, schema),给了 -s 时口径正确。留待 spec 域单票收口。',
174
+ };
175
+
176
+ describe('projectPath 传递约定(harness#95)', () => {
177
+ const cliFiles = listTsFiles(path.join(SRC_ROOT, 'cli'));
178
+ const downstreamFiles = listTsFiles(SRC_ROOT).filter(f => !f.startsWith(path.join(SRC_ROOT, 'cli')));
179
+
180
+ describe('闸 1:CLI 入口层的 cwd 只能是一次性兜底', () => {
181
+ it('非兜底形状的 cwd 站点 = 冻结豁免表,且每条豁免仍然成立', () => {
182
+ const offenders = new Map<string, string[]>();
183
+ for (const [rel, lines] of cwdSites(cliFiles)) {
184
+ const notFallback = lines.filter(line => !ENTRY_FALLBACK_SHAPE.test(line));
185
+ if (notFallback.length > 0) offenders.set(rel, notFallback);
186
+ }
187
+
188
+ expect([...offenders.keys()].sort()).toEqual(Object.keys(CLI_CWD_EXEMPTIONS).sort());
189
+ for (const [rel, exemption] of Object.entries(CLI_CWD_EXEMPTIONS)) {
190
+ expect({ rel, lines: offenders.get(rel) }).toEqual({ rel, lines: exemption.lines });
191
+ }
192
+ });
193
+ });
194
+
195
+ describe('闸 2:下游层不得二次取 cwd', () => {
196
+ it('下游 cwd 站点 = 冻结豁免表且逐行一致(新增文件/新增行/行变形/豁免化石都失败)', () => {
197
+ const sites = cwdSites(downstreamFiles);
198
+
199
+ expect([...sites.keys()].sort()).toEqual(Object.keys(DOWNSTREAM_CWD_EXEMPTIONS).sort());
200
+ for (const [rel, exemption] of Object.entries(DOWNSTREAM_CWD_EXEMPTIONS)) {
201
+ expect({ rel, lines: sites.get(rel) }).toEqual({ rel, lines: exemption.lines });
202
+ }
203
+ });
204
+
205
+ it('PassesGate 执行侧不再取 cwd(站点 1 的收口形状:workDir 由调用方传入)', () => {
206
+ const lines = codeLines(path.join(SRC_ROOT, 'core', 'validators', 'passes-gate.ts'));
207
+
208
+ expect(lines.some(line => line.includes('process.cwd()'))).toBe(false);
209
+ expect(lines.find(line => line.includes('async runTests'))).toContain('workDir');
210
+ });
211
+ });
212
+
213
+ describe('闸 3:相对路径默认值不再按 cwd 解析', () => {
214
+ it('下游层(= src 减 cli)的相对字面量路径默认值 = 冻结豁免表', () => {
215
+ const hits = new Set<string>();
216
+ for (const file of downstreamFiles) {
217
+ if (codeLines(file).some(line => RELATIVE_PATH_DEFAULT.test(line))) hits.add(repoPath(file));
218
+ }
219
+
220
+ expect([...hits].sort()).toEqual(Object.keys(RELATIVE_PATH_DEFAULT_EXEMPTIONS).sort());
221
+ });
222
+
223
+ it('acceptance 的 tasks.yml 默认不再有相对路径常量(站点 2 的收口形状)', () => {
224
+ // 多行调用 → 拼成空白归一的整段文本再断言形状
225
+ const body = codeLines(path.join(SRC_ROOT, 'gates', 'acceptance.ts')).join(' ').replace(/\s+/g, ' ');
226
+
227
+ expect(body).not.toMatch(/tasksPath\s*:\s*'/);
228
+ expect(body).toMatch(/path\.resolve\(\s*context\.projectPath\s*,\s*[^)]*'tasks\.yml'/);
229
+ });
230
+ });
231
+ });