@dommaker/harness 1.7.0 → 1.8.1

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 (563) hide show
  1. package/CHANGELOG.md +44 -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 +19 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +36 -22
  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/status.d.ts +8 -0
  76. package/dist/cli/commands/status.d.ts.map +1 -1
  77. package/dist/cli/commands/status.js +5 -7
  78. package/dist/cli/commands/status.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/context-syncer.d.ts +21 -2
  80. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  82. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  84. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/index.js +86 -27
  86. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  87. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  88. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  89. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  90. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  91. package/dist/cli/commands/validate.d.ts +0 -16
  92. package/dist/cli/commands/validate.d.ts.map +1 -1
  93. package/dist/cli/commands/validate.js +2 -97
  94. package/dist/cli/commands/validate.js.map +1 -1
  95. package/dist/cli/gate-command.d.ts +37 -0
  96. package/dist/cli/gate-command.d.ts.map +1 -0
  97. package/dist/cli/gate-command.js +64 -0
  98. package/dist/cli/gate-command.js.map +1 -0
  99. package/dist/cli/state-io.d.ts +34 -0
  100. package/dist/cli/state-io.d.ts.map +1 -0
  101. package/dist/cli/state-io.js +71 -0
  102. package/dist/cli/state-io.js.map +1 -0
  103. package/dist/completion-checkers/index.d.ts +1 -1
  104. package/dist/completion-checkers/index.d.ts.map +1 -1
  105. package/dist/completion-checkers/index.js +0 -15
  106. package/dist/completion-checkers/index.js.map +1 -1
  107. package/dist/context/knowledge-injector.d.ts +4 -2
  108. package/dist/context/knowledge-injector.d.ts.map +1 -1
  109. package/dist/context/knowledge-injector.js +13 -6
  110. package/dist/context/knowledge-injector.js.map +1 -1
  111. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  112. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  113. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  114. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  115. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  116. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  117. package/dist/core/constraints/check-cache.d.ts +1 -1
  118. package/dist/core/constraints/check-cache.js +1 -1
  119. package/dist/core/constraints/checker.d.ts +8 -3
  120. package/dist/core/constraints/checker.d.ts.map +1 -1
  121. package/dist/core/constraints/checker.js +30 -19
  122. package/dist/core/constraints/checker.js.map +1 -1
  123. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  124. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  125. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  126. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  127. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  128. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  129. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  130. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  131. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  132. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  133. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  134. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  135. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  136. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  137. package/dist/core/constraints/checkers/types.d.ts +10 -6
  138. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  139. package/dist/core/constraints/checkers/types.js +7 -3
  140. package/dist/core/constraints/checkers/types.js.map +1 -1
  141. package/dist/core/constraints/context-builder.d.ts +11 -10
  142. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  143. package/dist/core/constraints/context-builder.js +31 -27
  144. package/dist/core/constraints/context-builder.js.map +1 -1
  145. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  146. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  147. package/dist/core/constraints/context-reconcile.js +161 -0
  148. package/dist/core/constraints/context-reconcile.js.map +1 -0
  149. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  150. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  151. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  152. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  153. package/dist/core/constraints/git-evidence.js +1 -1
  154. package/dist/core/constraints/git-evidence.js.map +1 -1
  155. package/dist/core/constraints/injection-drift.d.ts +7 -2
  156. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  157. package/dist/core/constraints/injection-drift.js +13 -7
  158. package/dist/core/constraints/injection-drift.js.map +1 -1
  159. package/dist/core/constraints/run-env.d.ts +92 -0
  160. package/dist/core/constraints/run-env.d.ts.map +1 -0
  161. package/dist/core/constraints/run-env.js +175 -0
  162. package/dist/core/constraints/run-env.js.map +1 -0
  163. package/dist/core/constraints/triggers.d.ts +22 -0
  164. package/dist/core/constraints/triggers.d.ts.map +1 -0
  165. package/dist/core/constraints/triggers.js +31 -0
  166. package/dist/core/constraints/triggers.js.map +1 -0
  167. package/dist/core/effective-constraints.d.ts +13 -4
  168. package/dist/core/effective-constraints.d.ts.map +1 -1
  169. package/dist/core/effective-constraints.js +16 -6
  170. package/dist/core/effective-constraints.js.map +1 -1
  171. package/dist/core/effective-set.d.ts +8 -15
  172. package/dist/core/effective-set.d.ts.map +1 -1
  173. package/dist/core/effective-set.js +7 -12
  174. package/dist/core/effective-set.js.map +1 -1
  175. package/dist/core/index.d.ts +1 -0
  176. package/dist/core/index.d.ts.map +1 -1
  177. package/dist/core/index.js.map +1 -1
  178. package/dist/core/project-config-loader.d.ts +39 -17
  179. package/dist/core/project-config-loader.d.ts.map +1 -1
  180. package/dist/core/project-config-loader.js +51 -122
  181. package/dist/core/project-config-loader.js.map +1 -1
  182. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  183. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  184. package/dist/core/validators/check-handlers/http.js +12 -7
  185. package/dist/core/validators/check-handlers/http.js.map +1 -1
  186. package/dist/failure/classifier.d.ts +1 -1
  187. package/dist/failure/classifier.d.ts.map +1 -1
  188. package/dist/failure/classifier.js +8 -8
  189. package/dist/failure/classifier.js.map +1 -1
  190. package/dist/failure/index.d.ts +2 -1
  191. package/dist/failure/index.d.ts.map +1 -1
  192. package/dist/failure/index.js +9 -17
  193. package/dist/failure/index.js.map +1 -1
  194. package/dist/failure/recorder.d.ts +1 -1
  195. package/dist/failure/recorder.d.ts.map +1 -1
  196. package/dist/gates/checker-gate.d.ts +2 -1
  197. package/dist/gates/checker-gate.d.ts.map +1 -1
  198. package/dist/gates/checker-gate.js +3 -2
  199. package/dist/gates/checker-gate.js.map +1 -1
  200. package/dist/gates/command.d.ts +10 -4
  201. package/dist/gates/command.d.ts.map +1 -1
  202. package/dist/gates/command.js +36 -62
  203. package/dist/gates/command.js.map +1 -1
  204. package/dist/gates/contract.d.ts.map +1 -1
  205. package/dist/gates/contract.js +0 -4
  206. package/dist/gates/contract.js.map +1 -1
  207. package/dist/gates/definitions.d.ts +2 -1
  208. package/dist/gates/definitions.d.ts.map +1 -1
  209. package/dist/gates/definitions.js +6 -5
  210. package/dist/gates/definitions.js.map +1 -1
  211. package/dist/gates/index.d.ts +0 -4
  212. package/dist/gates/index.d.ts.map +1 -1
  213. package/dist/gates/index.js +1 -6
  214. package/dist/gates/index.js.map +1 -1
  215. package/dist/gates/performance.d.ts.map +1 -1
  216. package/dist/gates/performance.js +0 -4
  217. package/dist/gates/performance.js.map +1 -1
  218. package/dist/gates/runner.d.ts +1 -1
  219. package/dist/gates/runner.js +1 -1
  220. package/dist/gates/security.d.ts +1 -1
  221. package/dist/gates/security.d.ts.map +1 -1
  222. package/dist/gates/security.js +5 -6
  223. package/dist/gates/security.js.map +1 -1
  224. package/dist/gates/types.d.ts +14 -17
  225. package/dist/gates/types.d.ts.map +1 -1
  226. package/dist/gates/types.js.map +1 -1
  227. package/dist/hooks/bootstrap.d.ts +6 -4
  228. package/dist/hooks/bootstrap.d.ts.map +1 -1
  229. package/dist/hooks/bootstrap.js +25 -13
  230. package/dist/hooks/bootstrap.js.map +1 -1
  231. package/dist/hooks/config.d.ts +6 -2
  232. package/dist/hooks/config.d.ts.map +1 -1
  233. package/dist/hooks/config.js +5 -1
  234. package/dist/hooks/config.js.map +1 -1
  235. package/dist/hooks/index.d.ts +1 -1
  236. package/dist/hooks/index.d.ts.map +1 -1
  237. package/dist/hooks/index.js +0 -15
  238. package/dist/hooks/index.js.map +1 -1
  239. package/dist/hooks/pipeline.d.ts +19 -1
  240. package/dist/hooks/pipeline.d.ts.map +1 -1
  241. package/dist/hooks/pipeline.js +42 -4
  242. package/dist/hooks/pipeline.js.map +1 -1
  243. package/dist/hooks/registry.d.ts +11 -8
  244. package/dist/hooks/registry.d.ts.map +1 -1
  245. package/dist/hooks/registry.js +22 -7
  246. package/dist/hooks/registry.js.map +1 -1
  247. package/dist/hooks/types.d.ts +25 -7
  248. package/dist/hooks/types.d.ts.map +1 -1
  249. package/dist/index.d.ts +3 -3
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +3 -4
  252. package/dist/index.js.map +1 -1
  253. package/dist/knowledge/audit-scoring.d.ts +87 -0
  254. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  255. package/dist/knowledge/audit-scoring.js +470 -0
  256. package/dist/knowledge/audit-scoring.js.map +1 -0
  257. package/dist/knowledge/audit.d.ts +11 -59
  258. package/dist/knowledge/audit.d.ts.map +1 -1
  259. package/dist/knowledge/audit.js +47 -522
  260. package/dist/knowledge/audit.js.map +1 -1
  261. package/dist/knowledge/import.d.ts.map +1 -1
  262. package/dist/knowledge/import.js +2 -0
  263. package/dist/knowledge/import.js.map +1 -1
  264. package/dist/knowledge/index-generator.d.ts.map +1 -1
  265. package/dist/knowledge/index-generator.js +4 -5
  266. package/dist/knowledge/index-generator.js.map +1 -1
  267. package/dist/knowledge/index.d.ts +3 -2
  268. package/dist/knowledge/index.d.ts.map +1 -1
  269. package/dist/knowledge/index.js +3 -16
  270. package/dist/knowledge/index.js.map +1 -1
  271. package/dist/knowledge/ingest.js +1 -1
  272. package/dist/knowledge/ingest.js.map +1 -1
  273. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  274. package/dist/knowledge/lifecycle.js +4 -2
  275. package/dist/knowledge/lifecycle.js.map +1 -1
  276. package/dist/knowledge/migration.d.ts +3 -0
  277. package/dist/knowledge/migration.d.ts.map +1 -1
  278. package/dist/knowledge/migration.js +8 -2
  279. package/dist/knowledge/migration.js.map +1 -1
  280. package/dist/knowledge/query.d.ts +6 -0
  281. package/dist/knowledge/query.d.ts.map +1 -1
  282. package/dist/knowledge/query.js +8 -2
  283. package/dist/knowledge/query.js.map +1 -1
  284. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  285. package/dist/knowledge/reference-tracker.js +4 -6
  286. package/dist/knowledge/reference-tracker.js.map +1 -1
  287. package/dist/knowledge/store.d.ts +22 -16
  288. package/dist/knowledge/store.d.ts.map +1 -1
  289. package/dist/knowledge/store.js +53 -12
  290. package/dist/knowledge/store.js.map +1 -1
  291. package/dist/knowledge/tree-walker.d.ts +19 -0
  292. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  293. package/dist/knowledge/tree-walker.js +30 -0
  294. package/dist/knowledge/tree-walker.js.map +1 -0
  295. package/dist/knowledge/types.d.ts +16 -0
  296. package/dist/knowledge/types.d.ts.map +1 -1
  297. package/dist/monitoring/traces.d.ts +5 -3
  298. package/dist/monitoring/traces.d.ts.map +1 -1
  299. package/dist/monitoring/traces.js +19 -5
  300. package/dist/monitoring/traces.js.map +1 -1
  301. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  302. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  303. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  304. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  305. package/dist/tools/index.d.ts +4 -1
  306. package/dist/tools/index.d.ts.map +1 -1
  307. package/dist/tools/index.js +7 -15
  308. package/dist/tools/index.js.map +1 -1
  309. package/dist/types/checkpoint.d.ts +5 -0
  310. package/dist/types/checkpoint.d.ts.map +1 -1
  311. package/dist/types/project-config.d.ts +18 -0
  312. package/dist/types/project-config.d.ts.map +1 -1
  313. package/dist/types/trace.d.ts +13 -4
  314. package/dist/types/trace.d.ts.map +1 -1
  315. package/dist/types/trace.js +4 -3
  316. package/dist/types/trace.js.map +1 -1
  317. package/dist/utils/exec.d.ts +4 -27
  318. package/dist/utils/exec.d.ts.map +1 -1
  319. package/dist/utils/exec.js +4 -94
  320. package/dist/utils/exec.js.map +1 -1
  321. package/dist/utils/jsonl.d.ts +17 -7
  322. package/dist/utils/jsonl.d.ts.map +1 -1
  323. package/dist/utils/jsonl.js +144 -17
  324. package/dist/utils/jsonl.js.map +1 -1
  325. package/dist/utils/numeric-flag.d.ts +44 -0
  326. package/dist/utils/numeric-flag.d.ts.map +1 -0
  327. package/dist/utils/numeric-flag.js +62 -0
  328. package/dist/utils/numeric-flag.js.map +1 -0
  329. package/package.json +1 -1
  330. package/src/CONTEXT.md +5 -2
  331. package/src/__tests__/acceptance-gate.test.ts +7 -4
  332. package/src/__tests__/checker-extra.test.ts +3 -2
  333. package/src/__tests__/checker.test.ts +4 -3
  334. package/src/__tests__/checkpoint.test.ts +26 -9
  335. package/src/__tests__/context-files-resolution.test.ts +29 -15
  336. package/src/__tests__/contract-gate.test.ts +19 -40
  337. package/src/__tests__/error-classifier.test.ts +1 -1
  338. package/src/__tests__/exec.test.ts +45 -107
  339. package/src/__tests__/failure-recorder.test.ts +7 -5
  340. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  341. package/src/__tests__/layering.test.ts +217 -25
  342. package/src/__tests__/passes-gate.test.ts +9 -4
  343. package/src/__tests__/performance-gate.test.ts +3 -15
  344. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  345. package/src/__tests__/project-config-loader.test.ts +6 -3
  346. package/src/__tests__/public-exports.test.ts +0 -1
  347. package/src/__tests__/public-type-surface.test.ts +5 -4
  348. package/src/__tests__/public-value-surface.test.ts +182 -0
  349. package/src/__tests__/review-gate.test.ts +4 -6
  350. package/src/__tests__/security-gate.test.ts +5 -20
  351. package/src/__tests__/skip-semantics.test.ts +5 -3
  352. package/src/__tests__/spec-validator.test.ts +3 -2
  353. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  354. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  355. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  356. package/src/__tests__/trace-analyzer.test.ts +5 -3
  357. package/src/__tests__/trace-collector.test.ts +5 -3
  358. package/src/agents/index.ts +13 -2
  359. package/src/cli/__tests__/gate-command.test.ts +162 -0
  360. package/src/cli/__tests__/state-io.test.ts +50 -0
  361. package/src/cli/commands/CONTEXT.md +30 -5
  362. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  363. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  364. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  365. package/src/cli/commands/__tests__/check-read-count.test.ts +170 -0
  366. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  367. package/src/cli/commands/__tests__/check.test.ts +63 -44
  368. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  369. package/src/cli/commands/__tests__/command.test.ts +33 -17
  370. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  371. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  372. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  373. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  374. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  375. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  376. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  377. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  378. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  379. package/src/cli/commands/__tests__/init.test.ts +82 -6
  380. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  381. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  382. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  383. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  384. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  385. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  386. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  387. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  388. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  389. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  390. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  391. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  392. package/src/cli/commands/__tests__/report.test.ts +23 -1
  393. package/src/cli/commands/__tests__/review.test.ts +10 -17
  394. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  395. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  396. package/src/cli/commands/__tests__/security.test.ts +21 -24
  397. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  398. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  399. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  400. package/src/cli/commands/__tests__/status-extra.test.ts +25 -8
  401. package/src/cli/commands/__tests__/status.test.ts +46 -18
  402. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  403. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  404. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  405. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  406. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  407. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  408. package/src/cli/commands/acceptance.ts +24 -29
  409. package/src/cli/commands/check.ts +60 -35
  410. package/src/cli/commands/command.ts +9 -8
  411. package/src/cli/commands/constraints-retire.ts +57 -70
  412. package/src/cli/commands/contract.ts +44 -47
  413. package/src/cli/commands/definitions.ts +12 -6
  414. package/src/cli/commands/failure.ts +16 -3
  415. package/src/cli/commands/init.ts +242 -389
  416. package/src/cli/commands/knowledge-view.ts +224 -0
  417. package/src/cli/commands/knowledge.ts +643 -448
  418. package/src/cli/commands/passes-gate.ts +12 -3
  419. package/src/cli/commands/performance.ts +31 -38
  420. package/src/cli/commands/report.ts +3 -2
  421. package/src/cli/commands/review.ts +22 -28
  422. package/src/cli/commands/scaffold-templates.ts +406 -0
  423. package/src/cli/commands/scaffold.ts +302 -0
  424. package/src/cli/commands/security.ts +27 -23
  425. package/src/cli/commands/spec-baseline-check.ts +158 -33
  426. package/src/cli/commands/status.ts +12 -7
  427. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  428. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  429. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  430. package/src/cli/commands/sync-docs/index.ts +103 -28
  431. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  432. package/src/cli/commands/validate.ts +1 -104
  433. package/src/cli/gate-command.ts +87 -0
  434. package/src/cli/state-io.ts +53 -0
  435. package/src/completion-checkers/index.ts +10 -1
  436. package/src/context/CONTEXT.md +2 -1
  437. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  438. package/src/context/__tests__/session-manager.test.ts +19 -12
  439. package/src/context/knowledge-injector.ts +14 -7
  440. package/src/core/CONTEXT.md +10 -7
  441. package/src/core/__tests__/effective-set.test.ts +36 -60
  442. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  443. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  444. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  445. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  446. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  447. package/src/core/constraints/check-cache.ts +1 -1
  448. package/src/core/constraints/checker.ts +30 -14
  449. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  450. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  451. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  452. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  453. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  454. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  455. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  456. package/src/core/constraints/checkers/types.ts +14 -8
  457. package/src/core/constraints/context-builder.ts +35 -26
  458. package/src/core/constraints/context-reconcile.ts +200 -0
  459. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  460. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  461. package/src/core/constraints/git-evidence.ts +1 -1
  462. package/src/core/constraints/injection-drift.ts +19 -8
  463. package/src/core/constraints/run-env.ts +207 -0
  464. package/src/core/constraints/triggers.ts +33 -0
  465. package/src/core/effective-constraints.ts +17 -6
  466. package/src/core/effective-set.ts +8 -24
  467. package/src/core/index.ts +2 -0
  468. package/src/core/project-config-loader.ts +54 -102
  469. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  470. package/src/core/validators/check-handlers/http.ts +18 -7
  471. package/src/failure/CONTEXT.md +2 -1
  472. package/src/failure/classifier.ts +2 -2
  473. package/src/failure/index.ts +14 -2
  474. package/src/failure/recorder.ts +1 -1
  475. package/src/gates/CONTEXT.md +13 -7
  476. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  477. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  478. package/src/gates/__tests__/command.test.ts +8 -15
  479. package/src/gates/__tests__/contract.test.ts +5 -5
  480. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  481. package/src/gates/__tests__/performance.test.ts +0 -13
  482. package/src/gates/__tests__/review.test.ts +0 -1
  483. package/src/gates/__tests__/runner.test.ts +1 -1
  484. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  485. package/src/gates/__tests__/security.test.ts +0 -13
  486. package/src/gates/checker-gate.ts +3 -2
  487. package/src/gates/command.ts +51 -74
  488. package/src/gates/contract.ts +0 -5
  489. package/src/gates/definitions.ts +6 -5
  490. package/src/gates/index.ts +0 -5
  491. package/src/gates/performance.ts +1 -6
  492. package/src/gates/runner.ts +1 -1
  493. package/src/gates/security.ts +9 -11
  494. package/src/gates/types.ts +20 -23
  495. package/src/hooks/CONTEXT.md +10 -9
  496. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  497. package/src/hooks/__tests__/pipeline.test.ts +133 -17
  498. package/src/hooks/__tests__/registry.test.ts +51 -3
  499. package/src/hooks/bootstrap.ts +35 -15
  500. package/src/hooks/config.ts +6 -2
  501. package/src/hooks/index.ts +10 -1
  502. package/src/hooks/pipeline.ts +51 -6
  503. package/src/hooks/registry.ts +31 -12
  504. package/src/hooks/types.ts +26 -7
  505. package/src/index.ts +1 -2
  506. package/src/knowledge/CONTEXT.md +14 -7
  507. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  508. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  509. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  510. package/src/knowledge/__tests__/audit.test.ts +291 -208
  511. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  512. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  513. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  514. package/src/knowledge/__tests__/migration.test.ts +35 -6
  515. package/src/knowledge/__tests__/query.test.ts +3 -2
  516. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  517. package/src/knowledge/__tests__/store.test.ts +149 -4
  518. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  519. package/src/knowledge/audit-scoring.ts +589 -0
  520. package/src/knowledge/audit.ts +55 -599
  521. package/src/knowledge/import.ts +2 -0
  522. package/src/knowledge/index-generator.ts +4 -6
  523. package/src/knowledge/index.ts +27 -2
  524. package/src/knowledge/ingest.ts +1 -1
  525. package/src/knowledge/lifecycle.ts +5 -2
  526. package/src/knowledge/migration.ts +8 -2
  527. package/src/knowledge/query.ts +8 -1
  528. package/src/knowledge/reference-tracker.ts +5 -7
  529. package/src/knowledge/store.ts +75 -16
  530. package/src/knowledge/tree-walker.ts +29 -0
  531. package/src/knowledge/types.ts +19 -0
  532. package/src/monitoring/CONTEXT.md +5 -2
  533. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  534. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  535. package/src/monitoring/traces.ts +19 -5
  536. package/src/release/CONTEXT.md +1 -1
  537. package/src/release/__tests__/integrity.test.ts +21 -12
  538. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  539. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  540. package/src/tools/index.ts +5 -1
  541. package/src/types/checkpoint.ts +6 -0
  542. package/src/types/project-config.ts +21 -0
  543. package/src/types/trace.ts +14 -4
  544. package/src/utils/__tests__/file-walk.test.ts +3 -2
  545. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  546. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  547. package/src/utils/__tests__/jsonl.test.ts +61 -14
  548. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  549. package/src/utils/__tests__/package-version.test.ts +2 -2
  550. package/src/utils/exec.ts +4 -64
  551. package/src/utils/jsonl.ts +159 -20
  552. package/src/utils/numeric-flag.ts +64 -0
  553. package/dist/failure/types.d.ts +0 -9
  554. package/dist/failure/types.d.ts.map +0 -1
  555. package/dist/failure/types.js +0 -25
  556. package/dist/failure/types.js.map +0 -1
  557. package/dist/gates/effective-gates.d.ts +0 -30
  558. package/dist/gates/effective-gates.d.ts.map +0 -1
  559. package/dist/gates/effective-gates.js +0 -81
  560. package/dist/gates/effective-gates.js.map +0 -1
  561. package/src/failure/types.ts +0 -9
  562. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  563. package/src/gates/effective-gates.ts +0 -99
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * ADR-0020 起不再 mock TraceAnalyzer/TraceCollector:统计与异常由 trace-analyzer
5
5
  * 的模块级纯函数真算,用例喂合法 trace 行、断言真实输出。
6
+ * ADR-0026 起状态文件读写经注入的 StateIO 假件,不碰(被 mock 的)fs。
6
7
  */
7
8
 
8
- import { status } from '../status';
9
- import { captureIO, type CapturingIO } from '../../command-contract';
9
+ import { status, type StatusOptions } from '../status';
10
+ import { captureIO, type CapturingIO, type CommandResult } from '../../command-contract';
11
+ import type { HarnessState, StateIO } from '../../state-io';
10
12
  import * as fs from 'fs';
11
13
  import type { ExecutionTrace } from '../../../types/trace';
12
14
 
@@ -42,10 +44,27 @@ function repeats(constraintId: string, n: number, result: ExecutionTrace['result
42
44
  }
43
45
 
44
46
  let io: CapturingIO;
47
+ let stateIO: ReturnType<typeof memoryStateIO>;
45
48
  beforeEach(() => {
46
49
  io = captureIO();
50
+ stateIO = memoryStateIO();
47
51
  });
48
52
 
53
+ /** 内存 StateIO 假件(ADR-0026):状态读写走注入面,缺省 fileStateIO 在全 mock 的 fs 下会吃到 trace 假数据 */
54
+ function memoryStateIO(initial: HarnessState = {}): StateIO & { snapshot(): HarnessState } {
55
+ let state = initial;
56
+ return {
57
+ read: () => state,
58
+ write: (next: HarnessState) => { state = next; },
59
+ snapshot: () => state,
60
+ };
61
+ }
62
+
63
+ /** 全部用例经注入假件调用 status(stateIO 可被单例覆盖以预置状态) */
64
+ function runStatus(options: StatusOptions = {}): Promise<CommandResult> {
65
+ return status({ stateIO, ...options }, io);
66
+ }
67
+
49
68
  describe('status command', () => {
50
69
 
51
70
  beforeEach(() => {
@@ -59,7 +78,7 @@ describe('status command', () => {
59
78
  describe('未初始化情况', () => {
60
79
  it('应该显示未初始化提示', async () => {
61
80
  mockFs.existsSync.mockReturnValue(false);
62
- await status({}, io);
81
+ await runStatus();
63
82
  expect(io.outText()).toContain('未初始化');
64
83
  });
65
84
  });
@@ -71,7 +90,7 @@ describe('status command', () => {
71
90
  .mockReturnValueOnce(true) // .harness dir
72
91
  .mockReturnValueOnce(false); // traces file
73
92
 
74
- await status({}, io);
93
+ await runStatus();
75
94
  expect(io.outText()).toContain('暂无 Trace');
76
95
  });
77
96
  });
@@ -83,7 +102,7 @@ describe('status command', () => {
83
102
  { constraintId: 'test2', level: 'guideline' },
84
103
  ]));
85
104
 
86
- await status({}, io);
105
+ await runStatus();
87
106
  expect(io.outText()).toContain('记录数: 2 条');
88
107
  expect(io.outText()).toContain('📈 约束统计:');
89
108
  });
@@ -93,7 +112,7 @@ describe('status command', () => {
93
112
  { constraintId: 'no_bypass_checkpoint' },
94
113
  ]));
95
114
 
96
- await status({ detail: true }, io);
115
+ await runStatus({ detail: true });
97
116
  expect(io.outText()).toContain('🔴 Iron Laws:');
98
117
  expect(io.outText()).toContain('✅ no_bypass_checkpoint');
99
118
  expect(io.outText()).toContain('检查: 1 | 通过: 100% | 失败: 0%');
@@ -106,7 +125,7 @@ describe('status command', () => {
106
125
  { constraintId: 'test1', result: 'fail' },
107
126
  ]));
108
127
 
109
- await status({ anomalies: true }, io);
128
+ await runStatus({ anomalies: true });
110
129
  expect(io.outText()).toContain('发现 1 个异常');
111
130
  expect(io.outText()).toContain(' test1');
112
131
  expect(io.outText()).toContain('类型: low_pass_rate');
@@ -120,17 +139,26 @@ describe('status command', () => {
120
139
  { constraintId: 'test1', result: 'fail' },
121
140
  ]));
122
141
 
123
- await status({ detail: true }, io);
142
+ await runStatus({ detail: true });
124
143
  expect(io.outText()).toContain('检查: 5 | 通过: 80% | 失败: 20%');
125
144
  });
126
145
  });
127
146
 
128
147
  describe('状态文件更新', () => {
129
- it('应该更新 .state.json', async () => {
148
+ it('应该经 StateIO 写入 lastStatusRun', async () => {
149
+ mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
150
+
151
+ await runStatus();
152
+ expect(stateIO.snapshot().lastStatusRun).toBeTruthy();
153
+ });
154
+
155
+ it('读-改-写:已有的 shownHints 不被 status 抹掉(ADR-0026 决策 2 的行为修复)', async () => {
130
156
  mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
157
+ stateIO = memoryStateIO({ shownHints: ['trace_50'] });
131
158
 
132
- await status({}, io);
133
- expect(mockFs.writeFileSync).toHaveBeenCalled();
159
+ await runStatus();
160
+ expect(stateIO.snapshot().shownHints).toEqual(['trace_50']);
161
+ expect(stateIO.snapshot().lastStatusRun).toBeTruthy();
134
162
  });
135
163
  });
136
164
 
@@ -140,7 +168,7 @@ describe('status command', () => {
140
168
  { constraintId: 'test_guide', level: 'guideline' },
141
169
  ]));
142
170
 
143
- await status({}, io);
171
+ await runStatus();
144
172
  expect(io.outText()).toContain('🟡 Guidelines:');
145
173
  expect(io.outText()).toContain('✅ test_guide');
146
174
  });
@@ -150,7 +178,7 @@ describe('status command', () => {
150
178
  it('应该显示未发现异常当无异常', async () => {
151
179
  mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
152
180
 
153
- await status({ anomalies: true }, io);
181
+ await runStatus({ anomalies: true });
154
182
  expect(io.outText()).toContain('✅ 未发现异常');
155
183
  });
156
184
 
@@ -159,7 +187,7 @@ describe('status command', () => {
159
187
  { constraintId: 'test', result: 'fail' },
160
188
  ]));
161
189
 
162
- await status({ anomalies: true }, io);
190
+ await runStatus({ anomalies: true });
163
191
  expect(io.outText()).toContain('当前值: 0');
164
192
  expect(io.outText()).toContain('阈值: 0.3');
165
193
  expect(io.outText()).toContain('下一步建议');
@@ -170,7 +198,7 @@ describe('status command', () => {
170
198
  it('应该显示良好建议当 trace >= 100', async () => {
171
199
  mockFs.readFileSync.mockReturnValue(traceFile(repeats('test', 100, 'pass')));
172
200
 
173
- await status({}, io);
201
+ await runStatus();
174
202
  expect(io.outText()).toContain('记录数: 100 条');
175
203
  expect(io.outText()).toContain('状态良好');
176
204
  });
@@ -178,7 +206,7 @@ describe('status command', () => {
178
206
  it('应该显示积累数据建议当 trace < 100', async () => {
179
207
  mockFs.readFileSync.mockReturnValue(traceFile([{ constraintId: 'test' }]));
180
208
 
181
- await status({}, io);
209
+ await runStatus();
182
210
  expect(io.outText()).toContain('继续积累数据');
183
211
  });
184
212
 
@@ -187,7 +215,7 @@ describe('status command', () => {
187
215
  { constraintId: 'test', result: 'fail' },
188
216
  ]));
189
217
 
190
- await status({ anomalies: true }, io);
218
+ await runStatus({ anomalies: true });
191
219
  expect(io.outText()).toContain('harness status --detail');
192
220
  });
193
221
  });
@@ -199,7 +227,7 @@ describe('status command', () => {
199
227
  ...repeats('test_guide', 2, 'fail'),
200
228
  ].map(t => ({ ...t, level: 'guideline' as const }))));
201
229
 
202
- await status({ detail: true }, io);
230
+ await runStatus({ detail: true });
203
231
  expect(io.outText()).toContain('⚠️ test_guide');
204
232
  expect(io.outText()).toContain('检查: 5 | 通过: 60% | 失败: 40%');
205
233
  });
@@ -5,18 +5,33 @@
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
 
11
+ /**
12
+ * 环境固定:CONTEXT.md 的 mtime 过时判定在 CI 下被有意关掉(#142——全新 checkout 的 mtime
13
+ * 由 clone 顺序决定,比出来的「过时」是假灯)。本文件的夹具都是现场写的、断言的是本地行为,
14
+ * 所以环境必须由用例自己声明,不能跟着跑测试的机器走(否则同一份代码在开发机绿、在 CI 红)。
15
+ * 「CI 下关掉 mtime 判定」这条规则本身由 sync-docs-context-drift.test.ts 负责验证。
16
+ */
17
+ const SAVED_CI = process.env.CI;
18
+
10
19
  let io: CapturingIO;
11
20
  beforeEach(() => {
12
21
  io = captureIO();
22
+ delete process.env.CI;
23
+ });
24
+
25
+ afterEach(() => {
26
+ if (SAVED_CI === undefined) delete process.env.CI;
27
+ else process.env.CI = SAVED_CI;
13
28
  });
14
29
 
15
30
  describe('sync-docs --agents', () => {
16
- const tempDir = path.join(process.cwd(), 'temp-test-sync-docs-agents');
31
+ let tempDir: string;
17
32
 
18
33
  beforeAll(() => {
19
- fs.mkdirSync(tempDir, { recursive: true });
34
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'temp-test-sync-docs-agents-'));
20
35
  });
21
36
 
22
37
  afterAll(() => {
@@ -489,11 +504,11 @@ describe('sync-docs --agents', () => {
489
504
  expect(parsed.resolution.some((r: { action: string }) => r.action === 'update-context-md')).toBe(true);
490
505
  expect(parsed.agentsMd).toEqual({ file: 'AGENTS.md', exists: false, stale: true });
491
506
 
492
- // 人读模式:输出过时提示
507
+ // 人读模式:mtime 差异只给提示(harness#142 降级),判失败的是缺失
493
508
  io = captureIO();
494
509
  const humanResult = await syncDocs({ projectPath: testDir, agents: true, check: true }, io);
495
510
  expect(humanResult).toEqual({ kind: 'fail', reason: expect.stringContaining('文档不是最新') });
496
- expect(io.outText()).toContain('可能过时');
511
+ expect(io.outText()).toContain('源码比文档新');
497
512
  expect(io.outText()).toContain('缺少 CONTEXT.md');
498
513
 
499
514
  fs.rmSync(testDir, { recursive: true, force: true });
@@ -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
+ });