@dommaker/harness 1.6.0 → 1.8.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 (560) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/bin/harness.js +17 -1
  3. package/dist/agents/index.d.ts +6 -2
  4. package/dist/agents/index.d.ts.map +1 -1
  5. package/dist/agents/index.js +6 -16
  6. package/dist/agents/index.js.map +1 -1
  7. package/dist/cli/commands/__tests__/gate-decision.d.ts +18 -0
  8. package/dist/cli/commands/__tests__/gate-decision.d.ts.map +1 -0
  9. package/dist/cli/commands/__tests__/gate-decision.js +26 -0
  10. package/dist/cli/commands/__tests__/gate-decision.js.map +1 -0
  11. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  12. package/dist/cli/commands/acceptance.js +17 -28
  13. package/dist/cli/commands/acceptance.js.map +1 -1
  14. package/dist/cli/commands/check.d.ts +11 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +26 -11
  17. package/dist/cli/commands/check.js.map +1 -1
  18. package/dist/cli/commands/command.d.ts +0 -1
  19. package/dist/cli/commands/command.d.ts.map +1 -1
  20. package/dist/cli/commands/command.js +8 -6
  21. package/dist/cli/commands/command.js.map +1 -1
  22. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  23. package/dist/cli/commands/constraints-retire.js +36 -57
  24. package/dist/cli/commands/constraints-retire.js.map +1 -1
  25. package/dist/cli/commands/contract.d.ts.map +1 -1
  26. package/dist/cli/commands/contract.js +36 -42
  27. package/dist/cli/commands/contract.js.map +1 -1
  28. package/dist/cli/commands/definitions.d.ts +2 -0
  29. package/dist/cli/commands/definitions.d.ts.map +1 -1
  30. package/dist/cli/commands/definitions.js +11 -6
  31. package/dist/cli/commands/definitions.js.map +1 -1
  32. package/dist/cli/commands/failure.d.ts.map +1 -1
  33. package/dist/cli/commands/failure.js +16 -2
  34. package/dist/cli/commands/failure.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +14 -8
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +172 -364
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/knowledge-view.d.ts +90 -0
  40. package/dist/cli/commands/knowledge-view.d.ts.map +1 -0
  41. package/dist/cli/commands/knowledge-view.js +198 -0
  42. package/dist/cli/commands/knowledge-view.js.map +1 -0
  43. package/dist/cli/commands/knowledge.d.ts +137 -52
  44. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  45. package/dist/cli/commands/knowledge.js +534 -400
  46. package/dist/cli/commands/knowledge.js.map +1 -1
  47. package/dist/cli/commands/passes-gate.d.ts +4 -1
  48. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  49. package/dist/cli/commands/passes-gate.js +11 -2
  50. package/dist/cli/commands/passes-gate.js.map +1 -1
  51. package/dist/cli/commands/performance.d.ts.map +1 -1
  52. package/dist/cli/commands/performance.js +19 -31
  53. package/dist/cli/commands/performance.js.map +1 -1
  54. package/dist/cli/commands/report.d.ts.map +1 -1
  55. package/dist/cli/commands/report.js +2 -1
  56. package/dist/cli/commands/report.js.map +1 -1
  57. package/dist/cli/commands/review.d.ts.map +1 -1
  58. package/dist/cli/commands/review.js +13 -25
  59. package/dist/cli/commands/review.js.map +1 -1
  60. package/dist/cli/commands/scaffold-templates.d.ts +73 -0
  61. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -0
  62. package/dist/cli/commands/scaffold-templates.js +424 -0
  63. package/dist/cli/commands/scaffold-templates.js.map +1 -0
  64. package/dist/cli/commands/scaffold.d.ts +105 -0
  65. package/dist/cli/commands/scaffold.d.ts.map +1 -0
  66. package/dist/cli/commands/scaffold.js +263 -0
  67. package/dist/cli/commands/scaffold.js.map +1 -0
  68. package/dist/cli/commands/security.d.ts.map +1 -1
  69. package/dist/cli/commands/security.js +22 -24
  70. package/dist/cli/commands/security.js.map +1 -1
  71. package/dist/cli/commands/spec-baseline-check.d.ts +43 -0
  72. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  73. package/dist/cli/commands/spec-baseline-check.js +109 -34
  74. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  75. package/dist/cli/commands/sync-docs/context-syncer.d.ts +21 -2
  76. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  77. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  78. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  80. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/index.js +86 -27
  82. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  84. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  86. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  87. package/dist/cli/commands/validate.d.ts +0 -16
  88. package/dist/cli/commands/validate.d.ts.map +1 -1
  89. package/dist/cli/commands/validate.js +2 -97
  90. package/dist/cli/commands/validate.js.map +1 -1
  91. package/dist/cli/gate-command.d.ts +37 -0
  92. package/dist/cli/gate-command.d.ts.map +1 -0
  93. package/dist/cli/gate-command.js +64 -0
  94. package/dist/cli/gate-command.js.map +1 -0
  95. package/dist/completion-checkers/index.d.ts +1 -1
  96. package/dist/completion-checkers/index.d.ts.map +1 -1
  97. package/dist/completion-checkers/index.js +0 -15
  98. package/dist/completion-checkers/index.js.map +1 -1
  99. package/dist/context/knowledge-injector.d.ts +4 -2
  100. package/dist/context/knowledge-injector.d.ts.map +1 -1
  101. package/dist/context/knowledge-injector.js +13 -6
  102. package/dist/context/knowledge-injector.js.map +1 -1
  103. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  104. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  105. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  106. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  107. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  108. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  109. package/dist/core/constraints/check-cache.d.ts +1 -1
  110. package/dist/core/constraints/check-cache.js +1 -1
  111. package/dist/core/constraints/checker.d.ts +8 -3
  112. package/dist/core/constraints/checker.d.ts.map +1 -1
  113. package/dist/core/constraints/checker.js +30 -19
  114. package/dist/core/constraints/checker.js.map +1 -1
  115. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  116. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  117. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  118. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  119. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  120. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  121. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  122. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  123. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  124. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  125. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  126. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  127. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  128. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  129. package/dist/core/constraints/checkers/types.d.ts +10 -6
  130. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  131. package/dist/core/constraints/checkers/types.js +7 -3
  132. package/dist/core/constraints/checkers/types.js.map +1 -1
  133. package/dist/core/constraints/context-builder.d.ts +11 -10
  134. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  135. package/dist/core/constraints/context-builder.js +31 -27
  136. package/dist/core/constraints/context-builder.js.map +1 -1
  137. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  138. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  139. package/dist/core/constraints/context-reconcile.js +161 -0
  140. package/dist/core/constraints/context-reconcile.js.map +1 -0
  141. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  142. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  143. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  144. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  145. package/dist/core/constraints/git-evidence.js +1 -1
  146. package/dist/core/constraints/git-evidence.js.map +1 -1
  147. package/dist/core/constraints/injection-drift.d.ts +7 -2
  148. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  149. package/dist/core/constraints/injection-drift.js +13 -7
  150. package/dist/core/constraints/injection-drift.js.map +1 -1
  151. package/dist/core/constraints/run-env.d.ts +92 -0
  152. package/dist/core/constraints/run-env.d.ts.map +1 -0
  153. package/dist/core/constraints/run-env.js +175 -0
  154. package/dist/core/constraints/run-env.js.map +1 -0
  155. package/dist/core/constraints/triggers.d.ts +22 -0
  156. package/dist/core/constraints/triggers.d.ts.map +1 -0
  157. package/dist/core/constraints/triggers.js +31 -0
  158. package/dist/core/constraints/triggers.js.map +1 -0
  159. package/dist/core/effective-constraints.d.ts +13 -4
  160. package/dist/core/effective-constraints.d.ts.map +1 -1
  161. package/dist/core/effective-constraints.js +16 -6
  162. package/dist/core/effective-constraints.js.map +1 -1
  163. package/dist/core/effective-set.d.ts +8 -15
  164. package/dist/core/effective-set.d.ts.map +1 -1
  165. package/dist/core/effective-set.js +7 -12
  166. package/dist/core/effective-set.js.map +1 -1
  167. package/dist/core/index.d.ts +2 -1
  168. package/dist/core/index.d.ts.map +1 -1
  169. package/dist/core/index.js.map +1 -1
  170. package/dist/core/project-config-loader.d.ts +39 -17
  171. package/dist/core/project-config-loader.d.ts.map +1 -1
  172. package/dist/core/project-config-loader.js +51 -122
  173. package/dist/core/project-config-loader.js.map +1 -1
  174. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  175. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  176. package/dist/core/validators/check-handlers/http.js +12 -7
  177. package/dist/core/validators/check-handlers/http.js.map +1 -1
  178. package/dist/core/validators/index.d.ts +1 -1
  179. package/dist/core/validators/index.d.ts.map +1 -1
  180. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  181. package/dist/core/validators/passes-gate.js +2 -2
  182. package/dist/core/validators/passes-gate.js.map +1 -1
  183. package/dist/failure/classifier.d.ts +1 -1
  184. package/dist/failure/classifier.d.ts.map +1 -1
  185. package/dist/failure/classifier.js +8 -8
  186. package/dist/failure/classifier.js.map +1 -1
  187. package/dist/failure/index.d.ts +2 -1
  188. package/dist/failure/index.d.ts.map +1 -1
  189. package/dist/failure/index.js +9 -17
  190. package/dist/failure/index.js.map +1 -1
  191. package/dist/failure/recorder.d.ts +1 -1
  192. package/dist/failure/recorder.d.ts.map +1 -1
  193. package/dist/gates/checker-gate.d.ts +2 -1
  194. package/dist/gates/checker-gate.d.ts.map +1 -1
  195. package/dist/gates/checker-gate.js +3 -2
  196. package/dist/gates/checker-gate.js.map +1 -1
  197. package/dist/gates/command.d.ts +10 -4
  198. package/dist/gates/command.d.ts.map +1 -1
  199. package/dist/gates/command.js +36 -62
  200. package/dist/gates/command.js.map +1 -1
  201. package/dist/gates/contract.d.ts.map +1 -1
  202. package/dist/gates/contract.js +0 -4
  203. package/dist/gates/contract.js.map +1 -1
  204. package/dist/gates/definitions.d.ts +2 -1
  205. package/dist/gates/definitions.d.ts.map +1 -1
  206. package/dist/gates/definitions.js +6 -5
  207. package/dist/gates/definitions.js.map +1 -1
  208. package/dist/gates/index.d.ts +0 -4
  209. package/dist/gates/index.d.ts.map +1 -1
  210. package/dist/gates/index.js +1 -6
  211. package/dist/gates/index.js.map +1 -1
  212. package/dist/gates/performance.d.ts.map +1 -1
  213. package/dist/gates/performance.js +0 -4
  214. package/dist/gates/performance.js.map +1 -1
  215. package/dist/gates/runner.d.ts +1 -1
  216. package/dist/gates/runner.js +1 -1
  217. package/dist/gates/security.d.ts +1 -1
  218. package/dist/gates/security.d.ts.map +1 -1
  219. package/dist/gates/security.js +5 -6
  220. package/dist/gates/security.js.map +1 -1
  221. package/dist/gates/types.d.ts +14 -17
  222. package/dist/gates/types.d.ts.map +1 -1
  223. package/dist/gates/types.js.map +1 -1
  224. package/dist/hooks/bootstrap.d.ts +6 -4
  225. package/dist/hooks/bootstrap.d.ts.map +1 -1
  226. package/dist/hooks/bootstrap.js +25 -13
  227. package/dist/hooks/bootstrap.js.map +1 -1
  228. package/dist/hooks/config.d.ts +6 -2
  229. package/dist/hooks/config.d.ts.map +1 -1
  230. package/dist/hooks/config.js +5 -1
  231. package/dist/hooks/config.js.map +1 -1
  232. package/dist/hooks/index.d.ts +1 -1
  233. package/dist/hooks/index.d.ts.map +1 -1
  234. package/dist/hooks/index.js +0 -15
  235. package/dist/hooks/index.js.map +1 -1
  236. package/dist/hooks/pipeline.d.ts.map +1 -1
  237. package/dist/hooks/pipeline.js +2 -4
  238. package/dist/hooks/pipeline.js.map +1 -1
  239. package/dist/hooks/registry.d.ts +11 -8
  240. package/dist/hooks/registry.d.ts.map +1 -1
  241. package/dist/hooks/registry.js +22 -7
  242. package/dist/hooks/registry.js.map +1 -1
  243. package/dist/hooks/types.d.ts +23 -7
  244. package/dist/hooks/types.d.ts.map +1 -1
  245. package/dist/index.d.ts +4 -4
  246. package/dist/index.d.ts.map +1 -1
  247. package/dist/index.js +3 -4
  248. package/dist/index.js.map +1 -1
  249. package/dist/knowledge/audit-scoring.d.ts +87 -0
  250. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  251. package/dist/knowledge/audit-scoring.js +470 -0
  252. package/dist/knowledge/audit-scoring.js.map +1 -0
  253. package/dist/knowledge/audit.d.ts +11 -59
  254. package/dist/knowledge/audit.d.ts.map +1 -1
  255. package/dist/knowledge/audit.js +47 -522
  256. package/dist/knowledge/audit.js.map +1 -1
  257. package/dist/knowledge/import.d.ts.map +1 -1
  258. package/dist/knowledge/import.js +2 -0
  259. package/dist/knowledge/import.js.map +1 -1
  260. package/dist/knowledge/index-generator.d.ts.map +1 -1
  261. package/dist/knowledge/index-generator.js +4 -5
  262. package/dist/knowledge/index-generator.js.map +1 -1
  263. package/dist/knowledge/index.d.ts +3 -2
  264. package/dist/knowledge/index.d.ts.map +1 -1
  265. package/dist/knowledge/index.js +3 -16
  266. package/dist/knowledge/index.js.map +1 -1
  267. package/dist/knowledge/ingest.js +1 -1
  268. package/dist/knowledge/ingest.js.map +1 -1
  269. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  270. package/dist/knowledge/lifecycle.js +4 -2
  271. package/dist/knowledge/lifecycle.js.map +1 -1
  272. package/dist/knowledge/migration.d.ts +3 -0
  273. package/dist/knowledge/migration.d.ts.map +1 -1
  274. package/dist/knowledge/migration.js +8 -2
  275. package/dist/knowledge/migration.js.map +1 -1
  276. package/dist/knowledge/query.d.ts +6 -0
  277. package/dist/knowledge/query.d.ts.map +1 -1
  278. package/dist/knowledge/query.js +8 -2
  279. package/dist/knowledge/query.js.map +1 -1
  280. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  281. package/dist/knowledge/reference-tracker.js +4 -6
  282. package/dist/knowledge/reference-tracker.js.map +1 -1
  283. package/dist/knowledge/store.d.ts +22 -16
  284. package/dist/knowledge/store.d.ts.map +1 -1
  285. package/dist/knowledge/store.js +53 -12
  286. package/dist/knowledge/store.js.map +1 -1
  287. package/dist/knowledge/tree-walker.d.ts +19 -0
  288. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  289. package/dist/knowledge/tree-walker.js +30 -0
  290. package/dist/knowledge/tree-walker.js.map +1 -0
  291. package/dist/knowledge/types.d.ts +16 -0
  292. package/dist/knowledge/types.d.ts.map +1 -1
  293. package/dist/monitoring/traces.d.ts +5 -3
  294. package/dist/monitoring/traces.d.ts.map +1 -1
  295. package/dist/monitoring/traces.js +19 -5
  296. package/dist/monitoring/traces.js.map +1 -1
  297. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  298. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  299. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  300. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  301. package/dist/tools/index.d.ts +4 -1
  302. package/dist/tools/index.d.ts.map +1 -1
  303. package/dist/tools/index.js +7 -15
  304. package/dist/tools/index.js.map +1 -1
  305. package/dist/types/checkpoint.d.ts +5 -0
  306. package/dist/types/checkpoint.d.ts.map +1 -1
  307. package/dist/types/passes-gate.d.ts +0 -13
  308. package/dist/types/passes-gate.d.ts.map +1 -1
  309. package/dist/types/project-config.d.ts +18 -0
  310. package/dist/types/project-config.d.ts.map +1 -1
  311. package/dist/types/trace.d.ts +13 -4
  312. package/dist/types/trace.d.ts.map +1 -1
  313. package/dist/types/trace.js +4 -3
  314. package/dist/types/trace.js.map +1 -1
  315. package/dist/utils/exec.d.ts +4 -27
  316. package/dist/utils/exec.d.ts.map +1 -1
  317. package/dist/utils/exec.js +4 -94
  318. package/dist/utils/exec.js.map +1 -1
  319. package/dist/utils/jsonl.d.ts +17 -7
  320. package/dist/utils/jsonl.d.ts.map +1 -1
  321. package/dist/utils/jsonl.js +144 -17
  322. package/dist/utils/jsonl.js.map +1 -1
  323. package/dist/utils/numeric-flag.d.ts +44 -0
  324. package/dist/utils/numeric-flag.d.ts.map +1 -0
  325. package/dist/utils/numeric-flag.js +62 -0
  326. package/dist/utils/numeric-flag.js.map +1 -0
  327. package/package.json +1 -1
  328. package/src/CONTEXT.md +5 -2
  329. package/src/__tests__/acceptance-gate.test.ts +7 -4
  330. package/src/__tests__/checker-extra.test.ts +3 -2
  331. package/src/__tests__/checker.test.ts +4 -3
  332. package/src/__tests__/checkpoint.test.ts +26 -9
  333. package/src/__tests__/context-files-resolution.test.ts +29 -15
  334. package/src/__tests__/contract-gate.test.ts +19 -40
  335. package/src/__tests__/error-classifier.test.ts +1 -1
  336. package/src/__tests__/exec.test.ts +45 -107
  337. package/src/__tests__/failure-recorder.test.ts +7 -5
  338. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  339. package/src/__tests__/layering.test.ts +217 -25
  340. package/src/__tests__/passes-gate.test.ts +9 -4
  341. package/src/__tests__/performance-gate.test.ts +3 -15
  342. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  343. package/src/__tests__/project-config-loader.test.ts +6 -3
  344. package/src/__tests__/public-exports.test.ts +27 -6
  345. package/src/__tests__/public-type-surface.test.ts +360 -0
  346. package/src/__tests__/public-value-surface.test.ts +182 -0
  347. package/src/__tests__/review-gate.test.ts +4 -6
  348. package/src/__tests__/security-gate.test.ts +5 -20
  349. package/src/__tests__/skip-semantics.test.ts +5 -3
  350. package/src/__tests__/spec-validator.test.ts +3 -2
  351. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  352. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  353. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  354. package/src/__tests__/trace-analyzer.test.ts +5 -3
  355. package/src/__tests__/trace-collector.test.ts +5 -3
  356. package/src/agents/index.ts +13 -2
  357. package/src/cli/__tests__/gate-command.test.ts +162 -0
  358. package/src/cli/commands/CONTEXT.md +29 -5
  359. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  360. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  361. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  362. package/src/cli/commands/__tests__/check-read-count.test.ts +167 -0
  363. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  364. package/src/cli/commands/__tests__/check.test.ts +43 -36
  365. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  366. package/src/cli/commands/__tests__/command.test.ts +33 -17
  367. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  368. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  369. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  370. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  371. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  372. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  373. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  374. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  375. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  376. package/src/cli/commands/__tests__/init.test.ts +82 -6
  377. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  378. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  379. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  380. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  381. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  382. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  383. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +89 -1
  384. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  385. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  386. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  387. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  388. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  389. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  390. package/src/cli/commands/__tests__/report.test.ts +23 -1
  391. package/src/cli/commands/__tests__/review.test.ts +10 -17
  392. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  393. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  394. package/src/cli/commands/__tests__/security.test.ts +21 -24
  395. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  396. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  397. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  398. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  399. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  400. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  401. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  402. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  403. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  404. package/src/cli/commands/acceptance.ts +24 -29
  405. package/src/cli/commands/check.ts +40 -17
  406. package/src/cli/commands/command.ts +9 -8
  407. package/src/cli/commands/constraints-retire.ts +57 -70
  408. package/src/cli/commands/contract.ts +44 -47
  409. package/src/cli/commands/definitions.ts +12 -6
  410. package/src/cli/commands/failure.ts +16 -3
  411. package/src/cli/commands/init.ts +242 -389
  412. package/src/cli/commands/knowledge-view.ts +224 -0
  413. package/src/cli/commands/knowledge.ts +643 -448
  414. package/src/cli/commands/passes-gate.ts +12 -3
  415. package/src/cli/commands/performance.ts +31 -38
  416. package/src/cli/commands/report.ts +3 -2
  417. package/src/cli/commands/review.ts +22 -28
  418. package/src/cli/commands/scaffold-templates.ts +406 -0
  419. package/src/cli/commands/scaffold.ts +302 -0
  420. package/src/cli/commands/security.ts +27 -23
  421. package/src/cli/commands/spec-baseline-check.ts +158 -33
  422. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  423. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  424. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  425. package/src/cli/commands/sync-docs/index.ts +103 -28
  426. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  427. package/src/cli/commands/validate.ts +1 -104
  428. package/src/cli/gate-command.ts +87 -0
  429. package/src/completion-checkers/index.ts +10 -1
  430. package/src/context/CONTEXT.md +2 -1
  431. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  432. package/src/context/__tests__/session-manager.test.ts +19 -12
  433. package/src/context/knowledge-injector.ts +14 -7
  434. package/src/core/CONTEXT.md +10 -7
  435. package/src/core/__tests__/effective-set.test.ts +36 -60
  436. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  437. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  438. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  439. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  440. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  441. package/src/core/constraints/check-cache.ts +1 -1
  442. package/src/core/constraints/checker.ts +30 -14
  443. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  444. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  445. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  446. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  447. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  448. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  449. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  450. package/src/core/constraints/checkers/types.ts +14 -8
  451. package/src/core/constraints/context-builder.ts +35 -26
  452. package/src/core/constraints/context-reconcile.ts +200 -0
  453. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  454. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  455. package/src/core/constraints/git-evidence.ts +1 -1
  456. package/src/core/constraints/injection-drift.ts +19 -8
  457. package/src/core/constraints/run-env.ts +207 -0
  458. package/src/core/constraints/triggers.ts +33 -0
  459. package/src/core/effective-constraints.ts +17 -6
  460. package/src/core/effective-set.ts +8 -24
  461. package/src/core/index.ts +2 -1
  462. package/src/core/project-config-loader.ts +54 -102
  463. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  464. package/src/core/validators/check-handlers/http.ts +18 -7
  465. package/src/core/validators/index.ts +0 -1
  466. package/src/core/validators/passes-gate.ts +2 -3
  467. package/src/failure/CONTEXT.md +2 -1
  468. package/src/failure/classifier.ts +2 -2
  469. package/src/failure/index.ts +14 -2
  470. package/src/failure/recorder.ts +1 -1
  471. package/src/gates/CONTEXT.md +13 -7
  472. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  473. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  474. package/src/gates/__tests__/command.test.ts +8 -15
  475. package/src/gates/__tests__/contract.test.ts +5 -5
  476. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  477. package/src/gates/__tests__/performance.test.ts +0 -13
  478. package/src/gates/__tests__/review.test.ts +0 -1
  479. package/src/gates/__tests__/runner.test.ts +1 -1
  480. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  481. package/src/gates/__tests__/security.test.ts +0 -13
  482. package/src/gates/checker-gate.ts +3 -2
  483. package/src/gates/command.ts +51 -74
  484. package/src/gates/contract.ts +0 -5
  485. package/src/gates/definitions.ts +6 -5
  486. package/src/gates/index.ts +0 -5
  487. package/src/gates/performance.ts +1 -6
  488. package/src/gates/runner.ts +1 -1
  489. package/src/gates/security.ts +9 -11
  490. package/src/gates/types.ts +20 -23
  491. package/src/hooks/CONTEXT.md +10 -9
  492. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  493. package/src/hooks/__tests__/pipeline.test.ts +28 -17
  494. package/src/hooks/__tests__/registry.test.ts +51 -3
  495. package/src/hooks/bootstrap.ts +35 -15
  496. package/src/hooks/config.ts +6 -2
  497. package/src/hooks/index.ts +10 -1
  498. package/src/hooks/pipeline.ts +4 -6
  499. package/src/hooks/registry.ts +31 -12
  500. package/src/hooks/types.ts +24 -7
  501. package/src/index.ts +1 -3
  502. package/src/knowledge/CONTEXT.md +14 -7
  503. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  504. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  505. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  506. package/src/knowledge/__tests__/audit.test.ts +291 -208
  507. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  508. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  509. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  510. package/src/knowledge/__tests__/migration.test.ts +35 -6
  511. package/src/knowledge/__tests__/query.test.ts +3 -2
  512. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  513. package/src/knowledge/__tests__/store.test.ts +149 -4
  514. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  515. package/src/knowledge/audit-scoring.ts +589 -0
  516. package/src/knowledge/audit.ts +55 -599
  517. package/src/knowledge/import.ts +2 -0
  518. package/src/knowledge/index-generator.ts +4 -6
  519. package/src/knowledge/index.ts +27 -2
  520. package/src/knowledge/ingest.ts +1 -1
  521. package/src/knowledge/lifecycle.ts +5 -2
  522. package/src/knowledge/migration.ts +8 -2
  523. package/src/knowledge/query.ts +8 -1
  524. package/src/knowledge/reference-tracker.ts +5 -7
  525. package/src/knowledge/store.ts +75 -16
  526. package/src/knowledge/tree-walker.ts +29 -0
  527. package/src/knowledge/types.ts +19 -0
  528. package/src/monitoring/CONTEXT.md +5 -2
  529. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  530. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  531. package/src/monitoring/traces.ts +19 -5
  532. package/src/release/CONTEXT.md +1 -1
  533. package/src/release/__tests__/integrity.test.ts +21 -12
  534. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  535. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  536. package/src/tools/index.ts +5 -1
  537. package/src/types/checkpoint.ts +6 -0
  538. package/src/types/passes-gate.ts +0 -17
  539. package/src/types/project-config.ts +21 -0
  540. package/src/types/trace.ts +14 -4
  541. package/src/utils/__tests__/file-walk.test.ts +3 -2
  542. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  543. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  544. package/src/utils/__tests__/jsonl.test.ts +61 -14
  545. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  546. package/src/utils/__tests__/package-version.test.ts +2 -2
  547. package/src/utils/exec.ts +4 -64
  548. package/src/utils/jsonl.ts +159 -20
  549. package/src/utils/numeric-flag.ts +64 -0
  550. package/dist/failure/types.d.ts +0 -9
  551. package/dist/failure/types.d.ts.map +0 -1
  552. package/dist/failure/types.js +0 -25
  553. package/dist/failure/types.js.map +0 -1
  554. package/dist/gates/effective-gates.d.ts +0 -30
  555. package/dist/gates/effective-gates.d.ts.map +0 -1
  556. package/dist/gates/effective-gates.js +0 -81
  557. package/dist/gates/effective-gates.js.map +0 -1
  558. package/src/failure/types.ts +0 -9
  559. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  560. package/src/gates/effective-gates.ts +0 -99
@@ -0,0 +1,232 @@
1
+ /**
2
+ * sync-docs 的 CONTEXT.md 内容漂移判定接线测试(harness#142)
3
+ *
4
+ * 钉四件事:
5
+ * 1. 内容漂移(幽灵 / barrel 未登记)在 `--check` 下判 fail,reason 点名文件与符号;
6
+ * 2. mtime 漂移降级为本地提示——不再影响退出码,且 CI 环境下连提示都抑制;
7
+ * 3. `--json` 有 contentDrift 结构(供 LLM/CI 消费);
8
+ * 4. 写入模式不改写 CONTEXT.md(散文不可机械生成),退出码面不变。
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import * as os from 'os';
14
+ import { captureIO, type CapturingIO, type CommandResult } from '../../command-contract';
15
+ import { syncDocs } from '../sync-docs';
16
+
17
+ jest.mock('chalk', () => ({
18
+ blue: jest.fn((s: string) => s),
19
+ yellow: jest.fn((s: string) => s),
20
+ green: jest.fn((s: string) => s),
21
+ gray: jest.fn((s: string) => s),
22
+ red: jest.fn((s: string) => s),
23
+ cyan: jest.fn((s: string) => s),
24
+ bold: jest.fn((s: string) => s),
25
+ }));
26
+
27
+ /** 取 fail 的 reason(判别联合窄化只在测试里做一次) */
28
+ function failReason(result: CommandResult): string {
29
+ if (result.kind !== 'fail' && result.kind !== 'usage-error') {
30
+ throw new Error(`期望 fail,实得 ${result.kind}`);
31
+ }
32
+ return result.reason;
33
+ }
34
+
35
+ let tempDir: string;
36
+ const CI = process.env.CI;
37
+
38
+ let io: CapturingIO;
39
+
40
+ /** 建一个「CAPABILITIES 已覆盖 src/」的最小项目,把漂移轴隔离到 CONTEXT.md */
41
+ function makeProject(name: string, contextMd: string, indexTs: string): string {
42
+ const projectPath = path.join(tempDir, name);
43
+ const modDir = path.join(projectPath, 'src', 'mod');
44
+ fs.mkdirSync(modDir, { recursive: true });
45
+ fs.writeFileSync(
46
+ path.join(projectPath, 'CAPABILITIES.md'),
47
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| all | src/ | 全量记录 |\n'
48
+ );
49
+ fs.writeFileSync(path.join(modDir, 'CONTEXT.md'), contextMd);
50
+ fs.writeFileSync(path.join(modDir, 'index.ts'), indexTs);
51
+ fs.writeFileSync(
52
+ path.join(modDir, 'impl.ts'),
53
+ 'export class Widget {}\nexport function createWidget() {}\nexport const WIDGET_KIND = "a";\n'
54
+ );
55
+ return projectPath;
56
+ }
57
+
58
+ const CLEAN_DOC = [
59
+ '# mod/',
60
+ '',
61
+ '## 核心导出',
62
+ '- `Widget` — 主体',
63
+ '- `createWidget` — 工厂',
64
+ '- `WIDGET_KIND` — 常量',
65
+ '',
66
+ '## 约定',
67
+ '- 无',
68
+ '',
69
+ ].join('\n');
70
+
71
+ const BARREL = [
72
+ "export { Widget, createWidget, WIDGET_KIND } from './impl';",
73
+ "export type { WidgetConfig } from './impl';",
74
+ ].join('\n');
75
+
76
+ beforeEach(() => {
77
+ io = captureIO();
78
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'temp-test-sync-docs-drift-'));
79
+ delete process.env.CI;
80
+ });
81
+
82
+ afterEach(() => {
83
+ fs.rmSync(tempDir, { recursive: true, force: true });
84
+ if (CI === undefined) delete process.env.CI;
85
+ else process.env.CI = CI;
86
+ });
87
+
88
+ describe('sync-docs — CONTEXT.md 内容漂移判 fail(--check)', () => {
89
+ it('文档与导出面一致 → ok(新闸不制造存量噪音)', async () => {
90
+ const projectPath = makeProject('clean', CLEAN_DOC, BARREL);
91
+ await expect(syncDocs({ projectPath, check: true }, io)).resolves.toEqual({ kind: 'ok' });
92
+ });
93
+
94
+ it('幽灵符号(代码改名不回灌文档)→ fail 并指名文件与符号', async () => {
95
+ const projectPath = makeProject(
96
+ 'ghost',
97
+ CLEAN_DOC.replace('`Widget`', '`WidgetRenamed`'),
98
+ BARREL
99
+ );
100
+
101
+ const result = await syncDocs({ projectPath, check: true }, io);
102
+
103
+ expect(failReason(result)).toContain('src/mod/CONTEXT.md');
104
+ expect(failReason(result)).toContain('WidgetRenamed');
105
+ expect(io.outText()).toContain('WidgetRenamed');
106
+ });
107
+
108
+ it('barrel 未跟着改名的虚再导出不洗白幽灵(正本改名即撞)', async () => {
109
+ const projectPath = makeProject('ghost-barrel', CLEAN_DOC, BARREL);
110
+ // 只改声明侧(Widget → WidgetSink),index.ts 的再导出与文档都不动
111
+ fs.writeFileSync(
112
+ path.join(projectPath, 'src', 'mod', 'impl.ts'),
113
+ 'export class WidgetSink {}\nexport function createWidget() {}\nexport const WIDGET_KIND = "a";\n'
114
+ );
115
+
116
+ const result = await syncDocs({ projectPath, check: true }, io);
117
+
118
+ expect(failReason(result)).toContain('src/mod/CONTEXT.md');
119
+ expect(failReason(result)).toContain('Widget');
120
+ });
121
+
122
+ it('barrel 新增值符号未登记 → fail 并指名该符号', async () => {
123
+ const projectPath = makeProject(
124
+ 'unlisted',
125
+ CLEAN_DOC.replace('- `WIDGET_KIND` — 常量\n', ''),
126
+ BARREL
127
+ );
128
+
129
+ const result = await syncDocs({ projectPath, check: true }, io);
130
+
131
+ expect(failReason(result)).toContain('src/mod/CONTEXT.md');
132
+ expect(failReason(result)).toContain('WIDGET_KIND');
133
+ });
134
+
135
+ it('barrel 新增类型符号 → 不要求登记(类型面豁免)', async () => {
136
+ const projectPath = makeProject(
137
+ 'type-only',
138
+ CLEAN_DOC,
139
+ [BARREL, "export { type ExtraShape } from './impl';"].join('\n')
140
+ );
141
+
142
+ await expect(syncDocs({ projectPath, check: true }, io)).resolves.toEqual({ kind: 'ok' });
143
+ });
144
+
145
+ it('无 index.ts 的目录跳过覆盖方向(内部导出不要求登记)', async () => {
146
+ const projectPath = makeProject('no-barrel', CLEAN_DOC, BARREL);
147
+ fs.rmSync(path.join(projectPath, 'src', 'mod', 'index.ts'));
148
+
149
+ await expect(syncDocs({ projectPath, check: true }, io)).resolves.toEqual({ kind: 'ok' });
150
+ });
151
+ });
152
+
153
+ describe('sync-docs — mtime 判定降级为提示', () => {
154
+ /** 只把源码时间戳推到 CONTEXT.md 之后,内容一字不改 */
155
+ function touchSourceLater(projectPath: string): void {
156
+ const contextPath = path.join(projectPath, 'src', 'mod', 'CONTEXT.md');
157
+ const contextMtime = new Date(Date.now() - 60_000);
158
+ fs.utimesSync(contextPath, contextMtime, contextMtime);
159
+ const impl = path.join(projectPath, 'src', 'mod', 'impl.ts');
160
+ const later = new Date();
161
+ fs.utimesSync(impl, later, later);
162
+ }
163
+
164
+ it('mtime 漂移不再判 fail,只在本地给提示', async () => {
165
+ const projectPath = makeProject('mtime', CLEAN_DOC, BARREL);
166
+ touchSourceLater(projectPath);
167
+
168
+ await expect(syncDocs({ projectPath, check: true }, io)).resolves.toEqual({ kind: 'ok' });
169
+ expect(io.outText()).toContain('源码比文档新');
170
+ });
171
+
172
+ it('CI 环境下 mtime 提示抑制(全新 checkout 的 mtime 无意义)', async () => {
173
+ const projectPath = makeProject('mtime-ci', CLEAN_DOC, BARREL);
174
+ touchSourceLater(projectPath);
175
+ process.env.CI = 'true';
176
+
177
+ await expect(syncDocs({ projectPath, check: true }, io)).resolves.toEqual({ kind: 'ok' });
178
+ expect(io.outText()).not.toContain('源码比文档新');
179
+ });
180
+
181
+ it('内容漂移在 CI 环境下照样 fail', async () => {
182
+ const projectPath = makeProject('content-ci', CLEAN_DOC.replace('`Widget`', '`Gone`'), BARREL);
183
+ process.env.CI = 'true';
184
+
185
+ const result = await syncDocs({ projectPath, check: true }, io);
186
+ expect(failReason(result)).toContain('Gone');
187
+ });
188
+ });
189
+
190
+ describe('sync-docs — --json 与写入模式', () => {
191
+ it('--json 输出 contentDrift(dir/file/ghosts/unlisted)', async () => {
192
+ const projectPath = makeProject(
193
+ 'json',
194
+ CLEAN_DOC.replace('- `WIDGET_KIND` — 常量\n', '').replace('`Widget`', '`Ghost`'),
195
+ BARREL
196
+ );
197
+
198
+ await syncDocs({ projectPath, check: true, json: true }, io);
199
+
200
+ const payload = JSON.parse(io.outText());
201
+ expect(payload.stale).toBe(true);
202
+ expect(payload.summary.contextContentDrift).toBe(1);
203
+ expect(payload.contentDrift).toEqual([
204
+ {
205
+ dir: 'src/mod',
206
+ file: 'src/mod/CONTEXT.md',
207
+ ghosts: ['Ghost'],
208
+ unlisted: ['Widget', 'WIDGET_KIND'],
209
+ },
210
+ ]);
211
+ });
212
+
213
+ it('--json 干净时 contentDrift 为空数组', async () => {
214
+ const projectPath = makeProject('json-clean', CLEAN_DOC, BARREL);
215
+
216
+ await syncDocs({ projectPath, check: true, json: true }, io);
217
+
218
+ expect(JSON.parse(io.outText()).contentDrift).toEqual([]);
219
+ });
220
+
221
+ it('写入模式给提示但不改 CONTEXT.md、退出码面不变', async () => {
222
+ const projectPath = makeProject('write', CLEAN_DOC.replace('`Widget`', '`Ghost`'), BARREL);
223
+ const contextPath = path.join(projectPath, 'src', 'mod', 'CONTEXT.md');
224
+ const before = fs.readFileSync(contextPath, 'utf-8');
225
+
226
+ const result = await syncDocs({ projectPath }, io);
227
+
228
+ expect(result.kind).toBe('ok');
229
+ expect(io.outText()).toContain('Ghost');
230
+ expect(fs.readFileSync(contextPath, 'utf-8')).toBe(before);
231
+ });
232
+ });
@@ -0,0 +1,111 @@
1
+ /**
2
+ * sync-docs 生成的模块清单顺序冻结(#147 B 票)
3
+ *
4
+ * `sync-docs` 写出的 CAPABILITIES.md 模块表格行序 = 源码目录遍历序,这个顺序此前
5
+ * **无任何测试覆盖**,而它对外可见(`--check` 逐行比对、人工看 diff)。#147 把描述
6
+ * 提取从串行改并发,票面裁决「输出顺序逐字保持现状」,本文件就是那条裁决的闸。
7
+ *
8
+ * 不写死字面顺序:`readdir` 的返回顺序由文件系统决定(本仓 tmpfs 上是字典序、ext4 上
9
+ * 是哈希序),写死等于把断言建立在机器上。这里的口径是**目录遍历序**本身——用测试内
10
+ * 独立推导的遍历序(`readdirSync` + 同一份跳过口径)作对照,另加两次运行的自复现,
11
+ * 任何「按并发完成顺序 append」「改成字典序」「子目录结果挪到末尾」都会撞红。
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import * as os from 'os';
16
+ import * as path from 'path';
17
+ import { syncDocs } from '../sync-docs';
18
+ import { scanSourceModules } from '../sync-docs/project-reader';
19
+ import { captureIO } from '../../command-contract';
20
+ import { DEFAULT_SKIP_DIRS, isTsSourceFile } from '../../../utils/file-walk';
21
+
22
+ let tempDir: string;
23
+ const io = captureIO();
24
+
25
+ function write(rel: string, content: string): void {
26
+ const target = path.join(tempDir, rel);
27
+ fs.mkdirSync(path.dirname(target), { recursive: true });
28
+ fs.writeFileSync(target, content, 'utf-8');
29
+ }
30
+
31
+ /** 口径对照:目录遍历序 = readdir 序,子目录结果就地展开在父条目所在位置 */
32
+ function traversalOrder(dir: string, root: string): string[] {
33
+ const out: string[] = [];
34
+ for (const entry of fs.readdirSync(dir)) {
35
+ if (DEFAULT_SKIP_DIRS.includes(entry)) continue;
36
+ const entryPath = path.join(dir, entry);
37
+ if (fs.statSync(entryPath).isDirectory()) {
38
+ out.push(...traversalOrder(entryPath, root));
39
+ } else if (isTsSourceFile(entry, { skipIndex: true, includeTsx: true })) {
40
+ out.push(path.relative(root, entryPath));
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+
46
+ /** 生成后的 CAPABILITIES.md 表格行序(文件列) */
47
+ function tableRows(capabilitiesPath: string): string[] {
48
+ return fs
49
+ .readFileSync(capabilitiesPath, 'utf-8')
50
+ .split('\n')
51
+ .filter((line) => /^\|[^|]+\|[^|]+\.tsx?\s*\|[^|]+\|\s*$/.test(line))
52
+ .map((line) => line.split('|')[2].trim());
53
+ }
54
+
55
+ beforeAll(() => {
56
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sd147-module-order-'));
57
+ // 一个子目录恰好排在两份顶层文件之间(顺序若被重排,这里最容易露馅)
58
+ write('src/alpha.ts', '// 描述-甲\nexport const a = 1;\n');
59
+ write('src/mid/beta.ts', '/**\n * 描述-乙\n */\nexport const b = 1;\n');
60
+ write('src/mid/gamma.ts', '// 描述-丙\nexport const c = 1;\n');
61
+ write('src/zeta.ts', '// 描述-丁\nexport const d = 1;\n');
62
+ // 同一条规则下的旁支:barrel 与 .d.ts 不进清单
63
+ write('src/index.ts', 'export * from "./alpha";\n');
64
+ write('src/ambient.d.ts', 'export const t = 1;\n');
65
+ });
66
+
67
+ afterAll(() => {
68
+ fs.rmSync(tempDir, { recursive: true, force: true });
69
+ });
70
+
71
+ describe('sync-docs 模块清单顺序冻结(#147)', () => {
72
+ it('扫描产出的清单顺序 = 目录遍历序,且描述与文件配对不串位', async () => {
73
+ const modules = await scanSourceModules(path.join(tempDir, 'src'), tempDir);
74
+ const expected = traversalOrder(path.join(tempDir, 'src'), tempDir);
75
+
76
+ expect(modules.map((m) => m.file)).toEqual(expected);
77
+ // 并发取数后每条描述仍是自己那份文件的(回填按原索引,不按完成先后)
78
+ expect(modules.map((m) => `${m.file}=${m.description}`).sort()).toEqual(
79
+ [
80
+ 'src/alpha.ts=描述-甲',
81
+ 'src/mid/beta.ts=描述-乙',
82
+ 'src/mid/gamma.ts=描述-丙',
83
+ 'src/zeta.ts=描述-丁',
84
+ ].sort()
85
+ );
86
+ });
87
+
88
+ it('写出的 CAPABILITIES.md 行序 = 同一次扫描的清单顺序(对外可见面被锁住)', async () => {
89
+ const capPath = path.join(tempDir, 'CAPABILITIES.md');
90
+ expect(fs.existsSync(capPath)).toBe(false);
91
+
92
+ await syncDocs({ projectPath: tempDir }, io);
93
+
94
+ expect(tableRows(capPath)).toEqual(traversalOrder(path.join(tempDir, 'src'), tempDir));
95
+ });
96
+
97
+ it('同一次生成的清单顺序两次运行逐字复现(并发不引入运行间抖动)', async () => {
98
+ const capPath = path.join(tempDir, 'CAPABILITIES.md');
99
+
100
+ fs.rmSync(capPath, { force: true });
101
+ await syncDocs({ projectPath: tempDir }, io);
102
+ const first = tableRows(capPath);
103
+ expect(first.length).toBe(4);
104
+
105
+ fs.rmSync(capPath, { force: true });
106
+ await syncDocs({ projectPath: tempDir }, io);
107
+
108
+ expect(tableRows(capPath)).toEqual(first);
109
+ fs.rmSync(capPath, { force: true });
110
+ });
111
+ });
@@ -0,0 +1,192 @@
1
+ /**
2
+ * sync-docs 的 .ts 内容读取计数闸(#147 B 票,母票 #140「verifier 自己找文件」之二)
3
+ *
4
+ * 形状照 `check-read-count.test.ts` / `spec-baseline-read-count.test.ts`:真执行、
5
+ * 只在中间记一笔,不改任何行为。数的是**底层文件内容读取发生了几次**。
6
+ *
7
+ * 本票的承诺面 = **capability-listing 格式下模块清单无人消费,一份 `.ts` 内容都不读**
8
+ * (改造前:入口无条件全树扫描,逐文件 `await readFile` 只为首行注释)。
9
+ * 反证用同一棵源码树换成 file-table 格式:描述照旧提取,每份文件一次运行内恰读一次。
10
+ *
11
+ * 夹具一律不放 CONTEXT.md:CONTEXT 侧的 `.ts` 读取属导出面采集(#142),不在本票范围,
12
+ * 放进来会把「扫描分支零内容读」这条闸洗成模糊口径。
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as os from 'os';
17
+ import * as path from 'path';
18
+ import { syncDocs } from '../sync-docs';
19
+ import { captureIO, type CapturingIO } from '../../command-contract';
20
+
21
+ type ReadCall = { file: string; via: 'readFile' | 'readFileSync' };
22
+
23
+ // 计数器挂在 globalThis:jest.mock 工厂会被提前调用,闭包外提会撞 TDZ
24
+ jest.mock('fs/promises', () => {
25
+ const actual = jest.requireActual<typeof import('fs/promises')>('fs/promises');
26
+ const g = globalThis as unknown as { __SD147_READS__?: ReadCall[] };
27
+ if (!g.__SD147_READS__) g.__SD147_READS__ = [];
28
+ const bucket = g.__SD147_READS__;
29
+ const name = (p: unknown): string => (Buffer.isBuffer(p) ? p.toString('utf8') : String(p));
30
+
31
+ return {
32
+ ...actual,
33
+ readFile: jest.fn((p: unknown, ...rest: unknown[]) => {
34
+ bucket.push({ file: name(p), via: 'readFile' });
35
+ return actual.readFile(p as fs.PathLike, ...(rest as [BufferEncoding])) as Promise<string>;
36
+ }),
37
+ };
38
+ });
39
+
40
+ jest.mock('fs', () => {
41
+ const actual = jest.requireActual<typeof import('fs')>('fs');
42
+ const g = globalThis as unknown as { __SD147_READS__?: ReadCall[] };
43
+ if (!g.__SD147_READS__) g.__SD147_READS__ = [];
44
+ const bucket = g.__SD147_READS__;
45
+ const name = (p: unknown): string => (Buffer.isBuffer(p) ? p.toString('utf8') : String(p));
46
+
47
+ return {
48
+ ...actual,
49
+ readFileSync: jest.fn((p: unknown, ...rest: unknown[]) => {
50
+ bucket.push({ file: name(p), via: 'readFileSync' });
51
+ return (actual.readFileSync as unknown as (path: unknown, options?: unknown) => unknown)(
52
+ p,
53
+ ...rest
54
+ );
55
+ }),
56
+ };
57
+ });
58
+
59
+ const reads = () =>
60
+ ((globalThis as unknown as { __SD147_READS__?: ReadCall[] }).__SD147_READS__ ||= []);
61
+
62
+ const SOURCE_FILES = [
63
+ 'src/alpha.ts',
64
+ 'src/beta.tsx',
65
+ 'src/nested/gamma.ts',
66
+ 'src/nested/deep/delta.ts',
67
+ ];
68
+
69
+ /** 清单格式与文件表格式共用的源码树(含三处必须被跳过口径排除的同名内容) */
70
+ function fixtureProject(capabilities: 'listing' | 'table' | 'absent'): string {
71
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sd147-read-count-'));
72
+ const write = (rel: string, content: string): void => {
73
+ const target = path.join(dir, rel);
74
+ fs.mkdirSync(path.dirname(target), { recursive: true });
75
+ fs.writeFileSync(target, content, 'utf-8');
76
+ };
77
+
78
+ write('src/alpha.ts', '// alpha 的描述\nexport const a = 1;\n');
79
+ write('src/beta.tsx', 'export const b = 1;\n');
80
+ write('src/nested/gamma.ts', '/**\n * gamma 的描述\n */\nexport const c = 1;\n');
81
+ write('src/nested/deep/delta.ts', 'export const d = 1;\n');
82
+ // 跳过口径的三份对照:barrel、.d.ts、__tests__(外加 node_modules 与 dist)
83
+ write('src/index.ts', 'export * from "./alpha";\n');
84
+ write('src/ambient.d.ts', 'export const t = 1;\n');
85
+ write('src/__tests__/alpha.test.ts', '// 不该被读\nexport const x = 1;\n');
86
+ write('src/node_modules/pkg/index.ts', '// 不该被读\n');
87
+ write('src/dist/built.ts', '// 不该被读\n');
88
+
89
+ if (capabilities === 'listing') {
90
+ write(
91
+ 'CAPABILITIES.md',
92
+ '# Harness Capabilities\n\n## CLI Commands (21)\ncheck, validate, sync-docs\n'
93
+ );
94
+ } else if (capabilities === 'table') {
95
+ write(
96
+ 'CAPABILITIES.md',
97
+ [
98
+ '# Harness Capabilities',
99
+ '',
100
+ '| 模块 | 文件 | 说明 |',
101
+ '|------|------|------|',
102
+ '| alpha | src/alpha.ts | alpha 的描述 |',
103
+ '',
104
+ ].join('\n')
105
+ );
106
+ }
107
+ return dir;
108
+ }
109
+
110
+ const isTsContent = (rel: string): boolean => /\.tsx?$/.test(rel) && !rel.endsWith('.d.ts');
111
+
112
+ describe('sync-docs 的 .ts 内容读取计数闸(#147)', () => {
113
+ let io: CapturingIO;
114
+ let dir: string;
115
+
116
+ /** 相对项目根的落点 → 内容读取次数(readFileSync 与异步 readFile 各算一次) */
117
+ function contentCounts(): Map<string, number> {
118
+ const counts = new Map<string, number>();
119
+ for (const call of reads()) {
120
+ const rel = path.relative(dir, call.file);
121
+ if (!rel || rel.startsWith('..')) continue; // 非本项目文件(模块加载等)不计
122
+ counts.set(rel, (counts.get(rel) ?? 0) + 1);
123
+ }
124
+ return counts;
125
+ }
126
+
127
+ function tsContentReads(): Array<[string, number]> {
128
+ return [...contentCounts().entries()].filter(([rel]) => isTsContent(rel)).sort();
129
+ }
130
+
131
+ beforeEach(() => {
132
+ // 原地清空:mock 工厂在装载时就 captured 了这个数组引用,换新数组计数会落到旧的去向(假绿)
133
+ reads().length = 0;
134
+ io = captureIO();
135
+ });
136
+
137
+ afterEach(() => {
138
+ fs.rmSync(dir, { recursive: true, force: true });
139
+ });
140
+
141
+ it('capability-listing 模式:模块清单无人消费 → 一份 .ts 内容都不读', async () => {
142
+ dir = fixtureProject('listing');
143
+
144
+ await syncDocs({ projectPath: dir, check: true, json: true }, io);
145
+
146
+ // 正对照:真走了 listing 分支(否则下面的 0 是空跑出来的)
147
+ expect(JSON.parse(io.outText()).format).toBe('capability-listing');
148
+ expect(tsContentReads()).toEqual([]);
149
+ });
150
+
151
+ it('反证:file-table 格式照旧逐文件取描述,且每份文件一次运行内恰读一次', async () => {
152
+ dir = fixtureProject('table');
153
+
154
+ await syncDocs({ projectPath: dir, check: true, json: true }, io);
155
+
156
+ // 正对照:走了 file-table 分支,描述确实进了对外 JSON(判定面未被这次改造削掉)
157
+ const json = JSON.parse(io.outText());
158
+ expect(json.format).toBe('file-table');
159
+ const described = json.added.map(
160
+ (a: { module: { file: string; description: string } }) => `${a.module.file}#${a.module.description}`
161
+ );
162
+ expect(described.sort()).toEqual([
163
+ 'src/beta.tsx#beta',
164
+ 'src/nested/deep/delta.ts#delta',
165
+ 'src/nested/gamma.ts#gamma 的描述',
166
+ ]);
167
+
168
+ expect(tsContentReads()).toEqual(
169
+ SOURCE_FILES.map((f) => [f, 1]).sort()
170
+ );
171
+ });
172
+
173
+ it('CAPABILITIES.md 缺席时同样走 file-table 分支:读取表只含源码清单那几份文件', async () => {
174
+ dir = fixtureProject('absent');
175
+
176
+ await syncDocs({ projectPath: dir, check: true, json: true }, io);
177
+
178
+ expect(JSON.parse(io.outText()).format).toBe('file-table');
179
+ expect(tsContentReads()).toEqual(SOURCE_FILES.map((f) => [f, 1]).sort());
180
+ });
181
+
182
+ it('listing 模式的整张读取表冻结:本次运行只碰 CAPABILITIES.md,源码树零读取', async () => {
183
+ dir = fixtureProject('listing');
184
+
185
+ await syncDocs({ projectPath: dir, check: true, json: true }, io);
186
+
187
+ expect([...contentCounts().entries()].sort()).toEqual([['CAPABILITIES.md', 5]]);
188
+ // CAPABILITIES.md = 5 是**本票范围外的既有重复读**:入口一次异步读 + 计数核对
189
+ // (checkCapabilityCounts → FreshnessRunner 每条计数规则各读一次)。此处如实冻结,
190
+ // 既不为它改动也不放行——数字变了就是扫描分支被改动的信号(登记在 #147 resolution)。
191
+ });
192
+ });
@@ -5,6 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import { captureIO, type CapturingIO } from '../../command-contract';
7
7
  import * as path from 'path';
8
+ import * as os from 'os';
8
9
  import { syncDocs } from '../sync-docs';
9
10
 
10
11
  let io: CapturingIO;
@@ -13,10 +14,10 @@ beforeEach(() => {
13
14
  });
14
15
 
15
16
  describe('sync-docs command', () => {
16
- const tempDir = path.join(process.cwd(), 'temp-test-sync-docs');
17
+ let tempDir: string;
17
18
 
18
19
  beforeAll(() => {
19
- fs.mkdirSync(tempDir, { recursive: true });
20
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'temp-test-sync-docs-'));
20
21
  });
21
22
 
22
23
  afterAll(() => {
@@ -2,7 +2,7 @@
2
2
  * validate 命令测试
3
3
  */
4
4
 
5
- import { validate, createExampleCheckpoint, createExampleResolutions } from '../validate';
5
+ import { validate } from '../validate';
6
6
  import { captureIO, type CapturingIO } from '../../command-contract';
7
7
  import * as fs from 'fs/promises';
8
8
  import { CheckpointValidator } from '../../../core/validators/checkpoint';
@@ -126,51 +126,4 @@ describe('validate command', () => {
126
126
  expect(result).toEqual({ kind: 'fail', reason: '1 个检查点未通过: test-1' });
127
127
  });
128
128
  });
129
-
130
- describe('createExampleCheckpoint', () => {
131
- it('应该创建示例检查点文件', async () => {
132
- mockFs.access.mockRejectedValue(new Error('ENOENT'));
133
- mockFs.mkdir.mockResolvedValue(undefined);
134
- mockFs.writeFile.mockResolvedValue(undefined);
135
- mockYaml.dump.mockReturnValue('yaml content');
136
-
137
- await createExampleCheckpoint('/project', io);
138
-
139
- expect(mockFs.mkdir).toHaveBeenCalled();
140
- expect(mockFs.writeFile).toHaveBeenCalled();
141
- expect(io.outText()).toContain('已创建示例检查点');
142
- });
143
-
144
- it('已存在时不覆盖', async () => {
145
- mockFs.access.mockResolvedValue(undefined);
146
-
147
- await createExampleCheckpoint('/project', io);
148
-
149
- expect(mockFs.writeFile).not.toHaveBeenCalled();
150
- expect(io.outText()).toContain('checkpoints.yml 已存在,跳过');
151
- });
152
- });
153
-
154
- describe('createExampleResolutions', () => {
155
- it('应该创建 Resolutions 文件', async () => {
156
- mockFs.access.mockRejectedValue(new Error('ENOENT'));
157
- mockFs.mkdir.mockResolvedValue(undefined);
158
- mockFs.writeFile.mockResolvedValue(undefined);
159
-
160
- await createExampleResolutions('/project', io);
161
-
162
- expect(mockFs.mkdir).toHaveBeenCalled();
163
- expect(mockFs.writeFile).toHaveBeenCalled();
164
- expect(io.outText()).toContain('已创建 Resolutions 文件');
165
- });
166
-
167
- it('已存在时不覆盖', async () => {
168
- mockFs.access.mockResolvedValue(undefined);
169
-
170
- await createExampleResolutions('/project', io);
171
-
172
- expect(mockFs.writeFile).not.toHaveBeenCalled();
173
- expect(io.outText()).toContain('resolutions.json 已存在,跳过');
174
- });
175
- });
176
129
  });