@dommaker/harness 0.16.8 → 0.17.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 (344) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +33 -32
  3. package/bin/harness.js +34 -20
  4. package/dist/cli/commands/check.d.ts +3 -2
  5. package/dist/cli/commands/check.d.ts.map +1 -1
  6. package/dist/cli/commands/check.js +69 -36
  7. package/dist/cli/commands/check.js.map +1 -1
  8. package/dist/cli/commands/constraints-report.d.ts +42 -0
  9. package/dist/cli/commands/constraints-report.d.ts.map +1 -0
  10. package/dist/cli/commands/constraints-report.js +233 -0
  11. package/dist/cli/commands/constraints-report.js.map +1 -0
  12. package/dist/cli/commands/constraints-retire.d.ts +71 -0
  13. package/dist/cli/commands/constraints-retire.d.ts.map +1 -0
  14. package/dist/cli/commands/constraints-retire.js +384 -0
  15. package/dist/cli/commands/constraints-retire.js.map +1 -0
  16. package/dist/cli/commands/constraints.d.ts +1 -1
  17. package/dist/cli/commands/constraints.d.ts.map +1 -1
  18. package/dist/cli/commands/constraints.js +4 -4
  19. package/dist/cli/commands/constraints.js.map +1 -1
  20. package/dist/cli/commands/index.d.ts +2 -1
  21. package/dist/cli/commands/index.d.ts.map +1 -1
  22. package/dist/cli/commands/index.js +10 -4
  23. package/dist/cli/commands/index.js.map +1 -1
  24. package/dist/cli/commands/init.d.ts +20 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +81 -59
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/report.js +4 -4
  29. package/dist/cli/commands/report.js.map +1 -1
  30. package/dist/cli/commands/status.d.ts.map +1 -1
  31. package/dist/cli/commands/status.js +3 -4
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +19 -2
  34. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  35. package/dist/cli/commands/sync-docs/capabilities-syncer.js +79 -19
  36. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  37. package/dist/cli/commands/sync-docs/index.d.ts +2 -0
  38. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  39. package/dist/cli/commands/sync-docs/index.js +83 -20
  40. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  41. package/dist/cli/commands/validate.d.ts.map +1 -1
  42. package/dist/cli/commands/validate.js +1 -5
  43. package/dist/cli/commands/validate.js.map +1 -1
  44. package/dist/core/constraints/capabilities-parser.d.ts +14 -0
  45. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  46. package/dist/core/constraints/capabilities-parser.js +30 -0
  47. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  48. package/dist/core/constraints/checker.d.ts +13 -5
  49. package/dist/core/constraints/checker.d.ts.map +1 -1
  50. package/dist/core/constraints/checker.js +62 -25
  51. package/dist/core/constraints/checker.js.map +1 -1
  52. package/dist/core/constraints/checkers/capability-sync.d.ts +10 -0
  53. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/capability-sync.js +73 -11
  55. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  56. package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
  57. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
  59. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  60. package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
  61. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/docs-freshness.js +39 -0
  63. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  64. package/dist/core/constraints/checkers/index.d.ts +8 -4
  65. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  66. package/dist/core/constraints/checkers/index.js +29 -46
  67. package/dist/core/constraints/checkers/index.js.map +1 -1
  68. package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
  69. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  70. package/dist/core/constraints/checkers/iron-flags.js +10 -17
  71. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  72. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
  73. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
  74. package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
  75. package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
  76. package/dist/core/constraints/checkers/types.d.ts +15 -4
  77. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  78. package/dist/core/constraints/checkers/types.js +16 -4
  79. package/dist/core/constraints/checkers/types.js.map +1 -1
  80. package/dist/core/constraints/context-builder.d.ts +6 -3
  81. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  82. package/dist/core/constraints/context-builder.js +29 -20
  83. package/dist/core/constraints/context-builder.js.map +1 -1
  84. package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
  85. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  86. package/dist/core/constraints/definitions/guidelines.js +33 -574
  87. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  88. package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
  89. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  90. package/dist/core/constraints/definitions/iron-laws.js +48 -231
  91. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  92. package/dist/core/constraints/definitions/prompts.d.ts +12 -0
  93. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
  94. package/dist/core/constraints/definitions/prompts.js +405 -0
  95. package/dist/core/constraints/definitions/prompts.js.map +1 -0
  96. package/dist/core/constraints/definitions.d.ts +15 -9
  97. package/dist/core/constraints/definitions.d.ts.map +1 -1
  98. package/dist/core/constraints/definitions.js +18 -12
  99. package/dist/core/constraints/definitions.js.map +1 -1
  100. package/dist/core/constraints/index.d.ts +3 -2
  101. package/dist/core/constraints/index.d.ts.map +1 -1
  102. package/dist/core/constraints/index.js +8 -2
  103. package/dist/core/constraints/index.js.map +1 -1
  104. package/dist/core/constraints/injection-drift.d.ts +48 -0
  105. package/dist/core/constraints/injection-drift.d.ts.map +1 -0
  106. package/dist/core/constraints/injection-drift.js +140 -0
  107. package/dist/core/constraints/injection-drift.js.map +1 -0
  108. package/dist/core/constraints/injection-renderer.d.ts +23 -0
  109. package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
  110. package/dist/core/constraints/injection-renderer.js +44 -0
  111. package/dist/core/constraints/injection-renderer.js.map +1 -0
  112. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  113. package/dist/core/constraints/interceptor.js +3 -2
  114. package/dist/core/constraints/interceptor.js.map +1 -1
  115. package/dist/core/constraints/usage-report.d.ts +95 -0
  116. package/dist/core/constraints/usage-report.d.ts.map +1 -0
  117. package/dist/core/constraints/usage-report.js +206 -0
  118. package/dist/core/constraints/usage-report.js.map +1 -0
  119. package/dist/core/effective-constraints.d.ts +47 -0
  120. package/dist/core/effective-constraints.d.ts.map +1 -0
  121. package/dist/core/effective-constraints.js +60 -0
  122. package/dist/core/effective-constraints.js.map +1 -0
  123. package/dist/core/index.d.ts +1 -0
  124. package/dist/core/index.d.ts.map +1 -1
  125. package/dist/core/index.js +2 -0
  126. package/dist/core/index.js.map +1 -1
  127. package/dist/core/project-config-loader.d.ts +28 -3
  128. package/dist/core/project-config-loader.d.ts.map +1 -1
  129. package/dist/core/project-config-loader.js +74 -8
  130. package/dist/core/project-config-loader.js.map +1 -1
  131. package/dist/dashboard/data.d.ts +1 -9
  132. package/dist/dashboard/data.d.ts.map +1 -1
  133. package/dist/dashboard/data.js +7 -15
  134. package/dist/dashboard/data.js.map +1 -1
  135. package/dist/dashboard/types.d.ts +13 -1
  136. package/dist/dashboard/types.d.ts.map +1 -1
  137. package/dist/index.d.ts +0 -4
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +1 -13
  140. package/dist/index.js.map +1 -1
  141. package/dist/monitoring/index.d.ts +0 -2
  142. package/dist/monitoring/index.d.ts.map +1 -1
  143. package/dist/monitoring/index.js +0 -2
  144. package/dist/monitoring/index.js.map +1 -1
  145. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  146. package/dist/monitoring/knowledge-evolver.js +1 -1
  147. package/dist/monitoring/trace-analyzer.d.ts +1 -1
  148. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  149. package/dist/monitoring/trace-analyzer.js +16 -10
  150. package/dist/monitoring/trace-analyzer.js.map +1 -1
  151. package/dist/monitoring/traces.d.ts +2 -1
  152. package/dist/monitoring/traces.d.ts.map +1 -1
  153. package/dist/monitoring/traces.js +5 -3
  154. package/dist/monitoring/traces.js.map +1 -1
  155. package/dist/presets/standard.d.ts +3 -1
  156. package/dist/presets/standard.d.ts.map +1 -1
  157. package/dist/presets/standard.js +18 -8
  158. package/dist/presets/standard.js.map +1 -1
  159. package/dist/types/constraint.d.ts +51 -11
  160. package/dist/types/constraint.d.ts.map +1 -1
  161. package/dist/types/constraint.js +8 -4
  162. package/dist/types/constraint.js.map +1 -1
  163. package/dist/types/index.d.ts +1 -1
  164. package/dist/types/index.d.ts.map +1 -1
  165. package/dist/types/index.js.map +1 -1
  166. package/dist/types/monitoring-types.d.ts +2 -63
  167. package/dist/types/monitoring-types.d.ts.map +1 -1
  168. package/dist/types/monitoring-types.js +2 -2
  169. package/dist/types/project-config.d.ts +32 -1
  170. package/dist/types/project-config.d.ts.map +1 -1
  171. package/dist/types/trace.d.ts +16 -7
  172. package/dist/types/trace.d.ts.map +1 -1
  173. package/dist/types/trace.js +8 -0
  174. package/dist/types/trace.js.map +1 -1
  175. package/package.json +1 -1
  176. package/src/CONTEXT.md +2 -2
  177. package/src/__tests__/checker-extra.test.ts +356 -331
  178. package/src/__tests__/checker.test.ts +87 -724
  179. package/src/__tests__/constraint-doctor.test.ts +5 -5
  180. package/src/__tests__/doctor-extra.test.ts +164 -0
  181. package/src/__tests__/effective-constraints.test.ts +147 -0
  182. package/src/__tests__/extend-exceptions.test.ts +3 -3
  183. package/src/__tests__/iron-laws.test.ts +115 -91
  184. package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
  185. package/src/__tests__/presets.test.ts +7 -7
  186. package/src/__tests__/project-config-loader-extra.test.ts +4 -4
  187. package/src/__tests__/project-config-loader.test.ts +40 -1
  188. package/src/__tests__/skip-semantics.test.ts +322 -0
  189. package/src/cli/commands/CONTEXT.md +5 -3
  190. package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
  191. package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
  192. package/src/cli/commands/__tests__/check.test.ts +14 -25
  193. package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
  194. package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
  195. package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
  196. package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
  197. package/src/cli/commands/__tests__/status.test.ts +1 -1
  198. package/src/cli/commands/__tests__/sync-docs.test.ts +163 -0
  199. package/src/cli/commands/check.ts +72 -38
  200. package/src/cli/commands/constraints-report.ts +234 -0
  201. package/src/cli/commands/constraints-retire.ts +443 -0
  202. package/src/cli/commands/constraints.ts +5 -5
  203. package/src/cli/commands/index.ts +2 -1
  204. package/src/cli/commands/init.ts +94 -66
  205. package/src/cli/commands/report.ts +6 -6
  206. package/src/cli/commands/status.ts +3 -4
  207. package/src/cli/commands/sync-docs/capabilities-syncer.ts +90 -15
  208. package/src/cli/commands/sync-docs/index.ts +87 -24
  209. package/src/cli/commands/validate.ts +1 -5
  210. package/src/core/CONTEXT.md +7 -4
  211. package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
  212. package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
  213. package/src/core/constraints/capabilities-parser.ts +28 -0
  214. package/src/core/constraints/checker.ts +73 -28
  215. package/src/core/constraints/checkers/capability-sync.ts +89 -18
  216. package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
  217. package/src/core/constraints/checkers/docs-freshness.ts +43 -0
  218. package/src/core/constraints/checkers/index.ts +38 -71
  219. package/src/core/constraints/checkers/iron-flags.ts +13 -49
  220. package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
  221. package/src/core/constraints/checkers/types.ts +27 -5
  222. package/src/core/constraints/context-builder.ts +28 -19
  223. package/src/core/constraints/definitions/guidelines.ts +33 -599
  224. package/src/core/constraints/definitions/iron-laws.ts +48 -239
  225. package/src/core/constraints/definitions/prompts.ts +419 -0
  226. package/src/core/constraints/definitions.ts +18 -11
  227. package/src/core/constraints/index.ts +10 -1
  228. package/src/core/constraints/injection-drift.ts +139 -0
  229. package/src/core/constraints/injection-renderer.ts +44 -0
  230. package/src/core/constraints/interceptor.ts +3 -2
  231. package/src/core/constraints/usage-report.ts +254 -0
  232. package/src/core/effective-constraints.ts +85 -0
  233. package/src/core/index.ts +4 -1
  234. package/src/core/project-config-loader.ts +82 -9
  235. package/src/dashboard/__tests__/data.test.ts +5 -12
  236. package/src/dashboard/data.ts +9 -19
  237. package/src/dashboard/types.ts +18 -1
  238. package/src/index.ts +0 -12
  239. package/src/knowledge/CONTEXT.md +2 -2
  240. package/src/monitoring/CONTEXT.md +3 -4
  241. package/src/monitoring/index.ts +0 -2
  242. package/src/monitoring/knowledge-evolver.ts +1 -1
  243. package/src/monitoring/trace-analyzer.ts +15 -10
  244. package/src/monitoring/traces.ts +10 -6
  245. package/src/presets/__tests__/standard.test.ts +11 -9
  246. package/src/presets/standard.ts +24 -15
  247. package/src/types/constraint.ts +59 -14
  248. package/src/types/index.ts +1 -3
  249. package/src/types/monitoring-types.ts +2 -84
  250. package/src/types/project-config.ts +37 -1
  251. package/src/types/trace.ts +18 -7
  252. package/src/verification/CONTEXT.md +1 -1
  253. package/dist/cli/commands/flow.d.ts +0 -22
  254. package/dist/cli/commands/flow.d.ts.map +0 -1
  255. package/dist/cli/commands/flow.js +0 -275
  256. package/dist/cli/commands/flow.js.map +0 -1
  257. package/dist/constraints/lifecycle-runner.d.ts +0 -52
  258. package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
  259. package/dist/constraints/lifecycle-runner.js +0 -151
  260. package/dist/constraints/lifecycle-runner.js.map +0 -1
  261. package/dist/constraints/registry.d.ts +0 -27
  262. package/dist/constraints/registry.d.ts.map +0 -1
  263. package/dist/constraints/registry.js +0 -128
  264. package/dist/constraints/registry.js.map +0 -1
  265. package/dist/constraints/types.d.ts +0 -53
  266. package/dist/constraints/types.d.ts.map +0 -1
  267. package/dist/constraints/types.js +0 -8
  268. package/dist/constraints/types.js.map +0 -1
  269. package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
  270. package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
  271. package/dist/core/constraints/checkers/always-pass.js +0 -23
  272. package/dist/core/constraints/checkers/always-pass.js.map +0 -1
  273. package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
  274. package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
  275. package/dist/core/constraints/checkers/guideline-flags.js +0 -13
  276. package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
  277. package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
  278. package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
  279. package/dist/core/constraints/checkers/no-any-type.js +0 -42
  280. package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
  281. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
  282. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
  283. package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
  284. package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
  285. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
  286. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
  287. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
  288. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
  289. package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
  290. package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
  291. package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
  292. package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
  293. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
  294. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
  295. package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
  296. package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
  297. package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
  298. package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
  299. package/dist/core/constraints/checkers/test-coverage.js +0 -33
  300. package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
  301. package/dist/core/constraints/checkers/yagni.d.ts +0 -8
  302. package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
  303. package/dist/core/constraints/checkers/yagni.js +0 -96
  304. package/dist/core/constraints/checkers/yagni.js.map +0 -1
  305. package/dist/core/constraints/definitions/tips.d.ts +0 -7
  306. package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
  307. package/dist/core/constraints/definitions/tips.js +0 -62
  308. package/dist/core/constraints/definitions/tips.js.map +0 -1
  309. package/dist/evolution/auto-evolve.d.ts +0 -35
  310. package/dist/evolution/auto-evolve.d.ts.map +0 -1
  311. package/dist/evolution/auto-evolve.js +0 -64
  312. package/dist/evolution/auto-evolve.js.map +0 -1
  313. package/dist/evolution/index.d.ts +0 -2
  314. package/dist/evolution/index.d.ts.map +0 -1
  315. package/dist/evolution/index.js +0 -18
  316. package/dist/evolution/index.js.map +0 -1
  317. package/dist/monitoring/constraint-evolver.d.ts +0 -120
  318. package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
  319. package/dist/monitoring/constraint-evolver.js +0 -410
  320. package/dist/monitoring/constraint-evolver.js.map +0 -1
  321. package/src/__tests__/constraint-evolver.test.ts +0 -405
  322. package/src/__tests__/doctor-evolver.test.ts +0 -381
  323. package/src/cli/commands/__tests__/flow.test.ts +0 -343
  324. package/src/cli/commands/flow.ts +0 -277
  325. package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
  326. package/src/constraints/__tests__/registry.test.ts +0 -196
  327. package/src/constraints/lifecycle-runner.ts +0 -184
  328. package/src/constraints/registry.ts +0 -134
  329. package/src/constraints/types.ts +0 -59
  330. package/src/core/constraints/checkers/always-pass.ts +0 -22
  331. package/src/core/constraints/checkers/guideline-flags.ts +0 -30
  332. package/src/core/constraints/checkers/no-any-type.ts +0 -42
  333. package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
  334. package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
  335. package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
  336. package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
  337. package/src/core/constraints/checkers/test-coverage.ts +0 -35
  338. package/src/core/constraints/checkers/yagni.ts +0 -65
  339. package/src/core/constraints/definitions/tips.ts +0 -63
  340. package/src/evolution/CONTEXT.md +0 -22
  341. package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
  342. package/src/evolution/auto-evolve.ts +0 -98
  343. package/src/evolution/index.ts +0 -1
  344. package/src/monitoring/constraint-evolver.ts +0 -501
@@ -11,7 +11,12 @@ import * as path from 'path';
11
11
  import * as yaml from 'js-yaml';
12
12
  import { createExampleCheckpoint, createExampleResolutions } from './validate';
13
13
  import { detectSourceRoots } from '../../utils/detect-source-roots';
14
- import { IRON_LAWS, GUIDELINES, TIPS } from '../../core/constraints/definitions';
14
+ import { getEffectiveConstraints } from '../../core/effective-constraints';
15
+ import {
16
+ CONSTRAINTS_START_MARKER,
17
+ CONSTRAINTS_END_MARKER,
18
+ renderConstraintsSection,
19
+ } from '../../core/constraints/injection-renderer';
15
20
 
16
21
  export interface InitOptions {
17
22
  /** 项目路径 */
@@ -507,18 +512,15 @@ custom_constraints:
507
512
  }
508
513
 
509
514
  /**
510
- * 在 CLAUDE.md 中写入/更新 Governance Rules 约束段
511
- *
512
- * - 如果 CLAUDE.md 不存在,创建并写入完整约束段
513
- * - 如果存在 HARNESS_CONSTRAINTS_START/END 标记,替换标记间内容
514
- * - 如果不存在标记,在文件末尾追加约束段
515
- *
516
- /**
517
- * 在 CLAUDE.md 顶部写入 Output Style 段(仅首次创建,后续不覆盖)
515
+ * Output Style 段标记(ADR-0001:init 只写标记区间内,标记外只读)
518
516
  */
519
- const OUTPUT_STYLE_SECTION = [
520
- '## Output Style',
521
- '',
517
+ const OUTPUT_STYLE_START = '<!-- HARNESS_OUTPUT_STYLE_START -->';
518
+ const OUTPUT_STYLE_END = '<!-- HARNESS_OUTPUT_STYLE_END -->';
519
+
520
+ /** 旧版 harness 无标记 Output Style 段的特征串(用于区分 harness 写入 vs 用户自写) */
521
+ const LEGACY_OUTPUT_STYLE_FINGERPRINT = 'Terse like caveman';
522
+
523
+ const OUTPUT_STYLE_BODY = [
522
524
  'Terse like caveman. Technical substance exact. Only fluff die.',
523
525
  'Drop: articles, filler (just/really/basically), pleasantries (sure/certainly/happy to), hedging.',
524
526
  'Fragments OK. Short synonyms. Code blocks unchanged. Error messages quoted exact.',
@@ -527,25 +529,87 @@ const OUTPUT_STYLE_SECTION = [
527
529
  'Read existing files before writing. Don\'t re-read unless changed.',
528
530
  'Skip files over 100KB unless required.',
529
531
  'Don\'t guess APIs, versions, flags, commit SHAs, or package names. Verify before asserting.',
530
- '',
531
532
  ].join('\n');
532
533
 
533
- async function setupClaudeMdOutputStyle(projectPath: string): Promise<void> {
534
+ /** 渲染带标记的 Output Style 段(含 `## Output Style` 标题,以换行结尾) */
535
+ function renderOutputStyleSection(): string {
536
+ return `${OUTPUT_STYLE_START}\n## Output Style\n\n${OUTPUT_STYLE_BODY}\n${OUTPUT_STYLE_END}\n`;
537
+ }
538
+
539
+ /**
540
+ * 在 CLAUDE.md 顶部写入 Output Style 段(标记化、幂等)
541
+ *
542
+ * - 已有 HARNESS_OUTPUT_STYLE 标记:替换标记间内容
543
+ * - 无标记但存在旧版 harness 写入的无标记段(特征串匹配):原位置换为标记版
544
+ * - 无标记且 `## Output Style` 段为用户自写(特征串不匹配):跳过并提示,不重复追加
545
+ * - 完全没有该段:在文件顶部插入标记版
546
+ */
547
+ export async function setupClaudeMdOutputStyle(projectPath: string): Promise<void> {
534
548
  const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
549
+ let content: string;
535
550
  try {
536
- const content = await fs.readFile(claudeMdPath, 'utf-8');
537
- if (content.includes('## Output Style')) return; // 已存在,不覆盖
538
- const newContent = OUTPUT_STYLE_SECTION + '\n' + content;
539
- await fs.writeFile(claudeMdPath, newContent, 'utf-8');
551
+ content = await fs.readFile(claudeMdPath, 'utf-8');
540
552
  } catch {
541
553
  // CLAUDE.md 不存在——setupClaudeMdConstraints 会创建它
554
+ return;
555
+ }
556
+
557
+ const section = renderOutputStyleSection();
558
+ const startIdx = content.indexOf(OUTPUT_STYLE_START);
559
+ const endIdx = content.indexOf(OUTPUT_STYLE_END);
560
+
561
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
562
+ // 替换标记间内容(标记本身也替换,保持最新文本)。
563
+ // 尾部换行规范化:标记后恰好保留一个换行分隔,保证重复运行幂等。
564
+ const after = content.slice(endIdx + OUTPUT_STYLE_END.length).replace(/^\n+/, '');
565
+ const newContent = content.slice(0, startIdx) + section + (after ? '\n' + after : '');
566
+ if (newContent !== content) {
567
+ await fs.writeFile(claudeMdPath, newContent, 'utf-8');
568
+ console.log(chalk.green('✅ 已更新 CLAUDE.md Output Style 段'));
569
+ }
570
+ return;
571
+ }
572
+
573
+ // 检测旧版无标记 `## Output Style` 段
574
+ const legacyMatch = /^## Output Style[ \t]*$/m.exec(content);
575
+ if (legacyMatch) {
576
+ const sectionStart = legacyMatch.index;
577
+ const afterHeading = sectionStart + legacyMatch[0].length;
578
+ const nextHeadingOffset = content.slice(afterHeading).search(/^#{1,6} /m);
579
+ const sectionEnd = nextHeadingOffset === -1 ? content.length : afterHeading + nextHeadingOffset;
580
+ const legacySection = content.slice(sectionStart, sectionEnd);
581
+
582
+ if (!legacySection.includes(LEGACY_OUTPUT_STYLE_FINGERPRINT)) {
583
+ // 用户自写的同名 section:不动,也不追加(避免重复)
584
+ console.log(chalk.yellow('⚠️ CLAUDE.md 已存在自定义 "## Output Style" 段,跳过 harness Output Style 注入'));
585
+ return;
586
+ }
587
+
588
+ // 旧版 harness 写入:原位置换为标记版
589
+ const before = content.slice(0, sectionStart);
590
+ const after = content.slice(sectionEnd);
591
+ const newContent = before + section + (after.length > 0 ? '\n' + after : '');
592
+ await fs.writeFile(claudeMdPath, newContent, 'utf-8');
593
+ console.log(chalk.green('✅ 已将 CLAUDE.md Output Style 段迁移为标记化管理'));
594
+ return;
542
595
  }
596
+
597
+ // 完全没有该段:插入文件顶部
598
+ await fs.writeFile(claudeMdPath, section + '\n' + content, 'utf-8');
599
+ console.log(chalk.green('✅ 已在 CLAUDE.md 顶部写入 Output Style 段'));
543
600
  }
544
601
 
545
602
  /**
546
- * 只写入包含 promptInjection 文本的约束
603
+ * 在 CLAUDE.md 中写入/更新 Governance Rules 约束段
604
+ *
605
+ * - 约束集来自 getEffectiveConstraints(ADR-0001):preset 裁剪、config.yml
606
+ * 禁用、custom 追加、scenes 过滤全部反映在注入文本里
607
+ * - 期望段文本由纯函数 renderConstraintsSection 渲染(P6 漂移校验复用)
608
+ * - 如果 CLAUDE.md 不存在,创建并写入完整约束段
609
+ * - 如果存在 HARNESS_CONSTRAINTS_START/END 标记,替换标记间内容
610
+ * - 如果不存在标记,在文件末尾追加约束段
547
611
  */
548
- async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
612
+ export async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
549
613
  const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
550
614
 
551
615
  // 读取 harness 版本
@@ -558,44 +622,10 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
558
622
  // fallback to 'unknown'
559
623
  }
560
624
 
561
- // 构建约束段正文(不含 ## Governance Rules 标题,因为替换时标题已在外部)
562
- const bodyLines: string[] = [];
563
- bodyLines.push('<!-- HARNESS_CONSTRAINTS_START -->');
564
- bodyLines.push(`<!-- version: ${version} -->`);
565
-
566
- // Iron Laws
567
- const ironLawItems = Object.entries(IRON_LAWS).filter(([, c]) => c.promptInjection);
568
- if (ironLawItems.length > 0) {
569
- bodyLines.push('### Iron Laws (违反将阻断)');
570
- for (const [id, c] of ironLawItems) {
571
- bodyLines.push(`- **${id}**: ${c.promptInjection}`);
572
- }
573
- }
574
-
575
- // Guidelines
576
- const guidelineItems = Object.entries(GUIDELINES).filter(([, c]) => c.promptInjection);
577
- if (guidelineItems.length > 0) {
578
- bodyLines.push('');
579
- bodyLines.push('### Guidelines (应遵循)');
580
- for (const [id, c] of guidelineItems) {
581
- bodyLines.push(`- **${id}**: ${c.promptInjection}`);
582
- }
583
- }
584
-
585
- // Tips
586
- const tipItems = Object.entries(TIPS).filter(([, c]) => c.promptInjection);
587
- if (tipItems.length > 0) {
588
- bodyLines.push('');
589
- bodyLines.push('### Tips');
590
- for (const [id, c] of tipItems) {
591
- bodyLines.push(`- **${id}**: ${c.promptInjection}`);
592
- }
593
- }
594
-
595
- bodyLines.push('<!-- HARNESS_CONSTRAINTS_END -->');
596
-
597
- const fullSection = '## Governance Rules\n' + bodyLines.join('\n') + '\n';
598
- const bodyOnly = bodyLines.join('\n') + '\n';
625
+ // 生效约束集 → 渲染期望段(纯函数,与写文件分离)
626
+ const constraints = getEffectiveConstraints(projectPath);
627
+ const bodyOnly = renderConstraintsSection(constraints, version);
628
+ const fullSection = '## Governance Rules\n' + bodyOnly;
599
629
 
600
630
  // 检查 CLAUDE.md 是否存在
601
631
  let existingContent: string;
@@ -607,9 +637,6 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
607
637
  existingContent = '';
608
638
  }
609
639
 
610
- const startMarker = '<!-- HARNESS_CONSTRAINTS_START -->';
611
- const endMarker = '<!-- HARNESS_CONSTRAINTS_END -->';
612
-
613
640
  if (!fileExists) {
614
641
  // 创建新文件
615
642
  await fs.writeFile(claudeMdPath, fullSection, 'utf-8');
@@ -617,14 +644,15 @@ async function setupClaudeMdConstraints(projectPath: string): Promise<void> {
617
644
  return;
618
645
  }
619
646
 
620
- const startIdx = existingContent.indexOf(startMarker);
621
- const endIdx = existingContent.indexOf(endMarker);
647
+ const startIdx = existingContent.indexOf(CONSTRAINTS_START_MARKER);
648
+ const endIdx = existingContent.indexOf(CONSTRAINTS_END_MARKER);
622
649
 
623
650
  if (startIdx !== -1 && endIdx !== -1) {
624
- // 替换标记间内容(标记本身也替换,保持包括最新版本号)
651
+ // 替换标记间内容(标记本身也替换,保持包括最新版本号)。
652
+ // 尾部换行规范化:标记后恰好保留一个换行分隔,保证重复运行幂等。
625
653
  const before = existingContent.slice(0, startIdx);
626
- const after = existingContent.slice(endIdx + endMarker.length);
627
- const newContent = before + bodyOnly + after;
654
+ const after = existingContent.slice(endIdx + CONSTRAINTS_END_MARKER.length).replace(/^\n+/, '');
655
+ const newContent = before + bodyOnly + (after ? '\n' + after : '');
628
656
  await fs.writeFile(claudeMdPath, newContent, 'utf-8');
629
657
  console.log(chalk.green(`✅ 已更新 CLAUDE.md 治理约束 (v${version})`));
630
658
  } else {
@@ -8,7 +8,7 @@ import chalk from 'chalk';
8
8
  import * as fs from 'fs/promises';
9
9
  import { ConstraintChecker } from '../../core/constraints/checker';
10
10
  import { getTraceCollector } from '../../monitoring/traces';
11
- import { IRON_LAWS, GUIDELINES, TIPS } from '../../core/constraints/definitions';
11
+ import { IRON_LAWS, GUIDELINES, PROMPTS } from '../../core/constraints/definitions';
12
12
  import { executeWithCollect } from '../../failure/constraint-handler';
13
13
  import type { ConstraintContext } from '../../types/constraint';
14
14
 
@@ -28,7 +28,7 @@ interface ReportData {
28
28
  total: number;
29
29
  ironLaws: number;
30
30
  guidelines: number;
31
- tips: number;
31
+ prompts: number;
32
32
  passed: number;
33
33
  failed: number;
34
34
  warnings: number;
@@ -50,7 +50,7 @@ export async function report(options: ReportOptions): Promise<void> {
50
50
  const checker = ConstraintChecker.getInstance();
51
51
  checker.setTraceRecorder(getTraceCollector());
52
52
 
53
- const allConstraints = { ...IRON_LAWS, ...GUIDELINES, ...TIPS };
53
+ const allConstraints = { ...IRON_LAWS, ...GUIDELINES, ...PROMPTS };
54
54
  const totalConstraints = Object.keys(allConstraints).length;
55
55
 
56
56
  const context: ConstraintContext = {
@@ -81,7 +81,7 @@ export async function report(options: ReportOptions): Promise<void> {
81
81
  total: totalConstraints,
82
82
  ironLaws: Object.keys(IRON_LAWS).length,
83
83
  guidelines: Object.keys(GUIDELINES).length,
84
- tips: Object.keys(TIPS).length,
84
+ prompts: Object.keys(PROMPTS).length,
85
85
  passed: result.passed ? totalConstraints : totalConstraints - violations.length,
86
86
  failed: failedIronLaws.length,
87
87
  warnings: failedGuidelines.length,
@@ -129,7 +129,7 @@ function generateMarkdownReport(data: ReportData): string {
129
129
  `| 总约束 | ${data.constraints.total} |`,
130
130
  `| Iron Laws | ${data.constraints.ironLaws} |`,
131
131
  `| Guidelines | ${data.constraints.guidelines} |`,
132
- `| Tips | ${data.constraints.tips} |`,
132
+ `| Prompts | ${data.constraints.prompts} |`,
133
133
  `| 通过 | ${data.constraints.passed} |`,
134
134
  `| 失败 (error) | ${data.constraints.failed} |`,
135
135
  `| 警告 (warning) | ${data.constraints.warnings} |`,
@@ -184,7 +184,7 @@ function generateHtmlReport(data: ReportData): string {
184
184
  <tr><td>总约束</td><td>${data.constraints.total}</td></tr>
185
185
  <tr><td>Iron Laws</td><td>${data.constraints.ironLaws}</td></tr>
186
186
  <tr><td>Guidelines</td><td>${data.constraints.guidelines}</td></tr>
187
- <tr><td>Tips</td><td>${data.constraints.tips}</td></tr>
187
+ <tr><td>Prompts</td><td>${data.constraints.prompts}</td></tr>
188
188
  <tr><td>通过</td><td class="passed">${data.constraints.passed}</td></tr>
189
189
  <tr><td>失败</td><td class="failed">${data.constraints.failed}</td></tr>
190
190
  <tr><td>警告</td><td>${data.constraints.warnings}</td></tr>
@@ -9,6 +9,7 @@ import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import { TraceCollector } from '../../monitoring/traces';
11
11
  import { TraceAnalyzer } from '../../monitoring/trace-analyzer';
12
+ import { DEFAULT_TRACE_FILE } from '../../types/trace';
12
13
  import type { TraceSummary, TraceAnomaly } from '../../types/trace';
13
14
 
14
15
  export interface StatusOptions {
@@ -28,7 +29,7 @@ export interface StatusOptions {
28
29
  export async function status(options: StatusOptions): Promise<void> {
29
30
  const projectPath = options.projectPath || process.cwd();
30
31
  const harnessDir = path.join(projectPath, '.harness');
31
- const tracesPath = path.join(harnessDir, 'traces', 'execution.log');
32
+ const tracesPath = path.join(projectPath, DEFAULT_TRACE_FILE);
32
33
  const statePath = path.join(harnessDir, '.state.json');
33
34
 
34
35
  console.log(chalk.blue('📊 Harness 状态'));
@@ -91,8 +92,7 @@ export async function status(options: StatusOptions): Promise<void> {
91
92
 
92
93
  // 下一步建议
93
94
  console.log(chalk.blue('💡 下一步建议:'));
94
- console.log(chalk.gray(' 1. 运行 harness flow 一键诊断 + 提案'));
95
- console.log(chalk.gray(' 2. 运行 harness status --detail 查看详情'));
95
+ console.log(chalk.gray(' 运行 harness status --detail 查看详情'));
96
96
  }
97
97
  return;
98
98
  }
@@ -159,7 +159,6 @@ export async function status(options: StatusOptions): Promise<void> {
159
159
  // 下一步建议
160
160
  console.log(chalk.blue('💡 下一步建议:'));
161
161
  if (anomalies.length > 0) {
162
- console.log(chalk.gray(' harness flow # 一键执行诊断+提案'));
163
162
  console.log(chalk.gray(' harness status --detail # 查看详细状态'));
164
163
  } else if (traceCount >= 100) {
165
164
  console.log(chalk.gray(' • 状态良好,继续保持!'));
@@ -7,9 +7,11 @@
7
7
  */
8
8
 
9
9
  import * as fs from 'fs/promises';
10
+ import * as path from 'path';
10
11
  import { IRON_LAWS, GUIDELINES, TIPS } from '../../../core/constraints/definitions';
11
- import { readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
12
+ import { isCapabilityListingFormat, readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
12
13
  import { FreshnessRunner } from '../../../core/constraints/doc-freshness/runner';
14
+ import type { CapabilitiesMode } from '../../../core/project-config-loader';
13
15
  import type { DocFreshnessCheck } from '../../../types/project-config';
14
16
  import type { ModuleInfo, SyncResult } from './project-reader';
15
17
 
@@ -33,13 +35,11 @@ export async function parseCapabilitiesFiles(capabilitiesPath: string): Promise<
33
35
 
34
36
  /**
35
37
  * 检测 CAPABILITIES.md 是否使用能力清单格式(计数行),而非文件表格格式
38
+ *
39
+ * @deprecated 实现已收敛到 core/constraints/capabilities-parser(与工单 19-B 同方向),
40
+ * 此处保留 re-export 兼容现有调用方
36
41
  */
37
- export function isCapabilityListingFormat(content: string): boolean {
38
- // 能力清单格式特征:包含 "CLI Commands (N)" / "Iron Laws (N)" 等计数行
39
- return /CLI Commands\s*\(\d+\)/.test(content) ||
40
- /Iron Laws?\s*\(\d+\)/.test(content) ||
41
- /Guidelines?\s*\(\d+\)/.test(content);
42
- }
42
+ export { isCapabilityListingFormat };
43
43
 
44
44
  /**
45
45
  * 构建 CAPABILITIES.md 能力清单格式的检查配置
@@ -137,19 +137,23 @@ export function updateCapabilityCounts(content: string, projectPath: string): st
137
137
 
138
138
  /**
139
139
  * 更新 CAPABILITIES.md 文件
140
+ *
141
+ * module 模式(governance.capabilities.mode=module)下不再为新文件自动加表格行
142
+ * (目录条目需人工策划登记),幽灵行剔除与「最后更新」刷新保留。
140
143
  */
141
144
  export async function updateCapabilitiesFile(
142
145
  capabilitiesPath: string,
143
146
  currentModules: ModuleInfo[],
144
147
  existingFiles: string[],
145
148
  result: SyncResult,
149
+ mode: CapabilitiesMode = 'file',
146
150
  ): Promise<void> {
147
151
  let content: string;
148
152
  try {
149
153
  content = await fs.readFile(capabilitiesPath, 'utf-8');
150
154
  } catch {
151
- // 文件不存在,创建新的
152
- content = generateCapabilitiesContent(currentModules);
155
+ // 文件不存在,创建新的(module 模式生成按目录聚合的模板)
156
+ content = generateCapabilitiesContent(currentModules, mode);
153
157
  await fs.writeFile(capabilitiesPath, content, 'utf-8');
154
158
  return;
155
159
  }
@@ -164,8 +168,8 @@ export async function updateCapabilitiesFile(
164
168
  content = content.replace(rowRegex, '');
165
169
  }
166
170
 
167
- // 添加新文件的行(在最后一个表格行之后)
168
- if (result.added.length > 0) {
171
+ // 添加新文件的行(在最后一个表格行之后);module 模式跳过
172
+ if (mode !== 'module' && result.added.length > 0) {
169
173
  const getBasenameLocal = (f: string) => f.split('/').pop()!;
170
174
  const addedModules = currentModules.filter(m => result.added.includes(getBasenameLocal(m.file)));
171
175
  const tableEndRegex = /(^\|[^|]+\|[^|]+\|[^|]+\|\s*$)/gm;
@@ -186,8 +190,8 @@ export async function updateCapabilitiesFile(
186
190
  // 清理多余空行
187
191
  content = content.replace(/\n{3,}/g, '\n\n');
188
192
  } else {
189
- // 没有表格,追加模块表格
190
- content += '\n\n' + generateModuleTable(currentModules);
193
+ // 没有表格,追加模块表格(module 模式按目录聚合)
194
+ content += '\n\n' + (mode === 'module' ? generateDirTable(currentModules) : generateModuleTable(currentModules));
191
195
  }
192
196
 
193
197
  // 更新最后更新时间
@@ -203,7 +207,7 @@ export async function updateCapabilitiesFile(
203
207
  /**
204
208
  * 生成 CAPABILITIES.md 内容
205
209
  */
206
- function generateCapabilitiesContent(modules: ModuleInfo[]): string {
210
+ function generateCapabilitiesContent(modules: ModuleInfo[], mode: CapabilitiesMode = 'file'): string {
207
211
  const now = new Date().toISOString().split('T')[0];
208
212
  return `# CAPABILITIES.md
209
213
 
@@ -211,7 +215,7 @@ function generateCapabilitiesContent(modules: ModuleInfo[]): string {
211
215
 
212
216
  ---
213
217
 
214
- ${generateModuleTable(modules)}
218
+ ${mode === 'module' ? generateDirTable(modules) : generateModuleTable(modules)}
215
219
  `;
216
220
  }
217
221
 
@@ -227,3 +231,74 @@ function generateModuleTable(modules: ModuleInfo[]): string {
227
231
 
228
232
  return `| 模块 | 文件 | 说明 |\n|------|------|------|\n${rows}`;
229
233
  }
234
+
235
+ /**
236
+ * 生成按目录聚合的模块表格(module 模式模板)
237
+ *
238
+ * 每个源码子目录一行目录条目(| core | src/core/ | core |),
239
+ * 文件直接位于源码根时聚到根目录一行。
240
+ */
241
+ function generateDirTable(modules: ModuleInfo[]): string {
242
+ if (modules.length === 0) return '';
243
+
244
+ const dirs: string[] = [];
245
+ for (const m of modules) {
246
+ const dir = path.posix.dirname(m.file);
247
+ const key = dir === '.' ? '' : dir + '/';
248
+ if (key && !dirs.includes(key)) dirs.push(key);
249
+ }
250
+
251
+ const rows = dirs.map(d => {
252
+ const name = d.replace(/\/$/, '').split('/').pop()!;
253
+ return `| ${name} | ${d} | ${name} |`;
254
+ }).join('\n');
255
+
256
+ return `| 模块 | 文件 | 说明 |\n|------|------|------|\n${rows}`;
257
+ }
258
+
259
+ /**
260
+ * 将 CAPABILITIES.md 文件表格折叠为目录条目(--compact 一次性迁移)
261
+ *
262
+ * 按文件条目(第二列)的 dirname 分组:同组 ≥2 个文件条目折叠为一行目录条目
263
+ * (替换组内第一行、删除其余行,说明取组内第一行,为空则用目录名);
264
+ * 单独成组的文件行、表头、非表格文本、PRESERVE 块原样保留。
265
+ * 已折叠的目录条目行不再匹配文件条目,故幂等。
266
+ */
267
+ export function compactCapabilitiesContent(content: string): string {
268
+ const lines = content.split('\n');
269
+ const fileEntryRegex = /\.(?:ts|tsx|js|jsx)$/;
270
+ const rowRegex = /^\|[^|]+\|[^|]+\|[^|]+\|\s*$/;
271
+
272
+ interface TableRow {
273
+ lineIndex: number;
274
+ file: string;
275
+ desc: string;
276
+ }
277
+
278
+ // 收集文件条目行,按 dirname 分组(无目录前缀的条目不参与折叠)
279
+ const groups = new Map<string, TableRow[]>();
280
+ for (let i = 0; i < lines.length; i++) {
281
+ if (!rowRegex.test(lines[i])) continue;
282
+ const cells = lines[i].split('|').map(c => c.trim());
283
+ const file = cells[2] ?? '';
284
+ if (!fileEntryRegex.test(file)) continue;
285
+ const dir = path.posix.dirname(file);
286
+ if (dir === '.') continue;
287
+ const group = groups.get(dir) ?? [];
288
+ group.push({ lineIndex: i, file, desc: cells[3] ?? '' });
289
+ groups.set(dir, group);
290
+ }
291
+
292
+ const deleteLines = new Set<number>();
293
+ for (const [dir, rows] of groups) {
294
+ if (rows.length < 2) continue;
295
+ const name = dir.split('/').pop()!;
296
+ const desc = rows[0].desc || name;
297
+ lines[rows[0].lineIndex] = `| ${name} | ${dir}/ | ${desc} |`;
298
+ for (const row of rows.slice(1)) {
299
+ deleteLines.add(row.lineIndex);
300
+ }
301
+ }
302
+
303
+ return lines.filter((_, i) => !deleteLines.has(i)).join('\n');
304
+ }
@@ -16,8 +16,9 @@ import chalk from 'chalk';
16
16
  import * as fs from 'fs/promises';
17
17
  import { existsSync } from 'fs';
18
18
  import * as path from 'path';
19
- import { readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
19
+ import { aggregateToSourceSubdir, readCapabilitiesEntries } from '../../../core/constraints/capabilities-parser';
20
20
  import { detectSourceRoots } from '../../../utils/detect-source-roots';
21
+ import { getCapabilitiesMode } from '../../../core/project-config-loader';
21
22
  import { getSourceDirs, scanSourceModules, getRequiredContextDirs } from './project-reader';
22
23
  import type { ModuleInfo, SyncResult } from './project-reader';
23
24
  import {
@@ -26,6 +27,7 @@ import {
26
27
  checkCapabilityCounts,
27
28
  updateCapabilityCounts,
28
29
  updateCapabilitiesFile,
30
+ compactCapabilitiesContent,
29
31
  } from './capabilities-syncer';
30
32
  import { createContextMd, findExistingContextFiles, getLatestTsMtime } from './context-syncer';
31
33
  import { buildAgentsMd } from './agents-syncer';
@@ -42,6 +44,8 @@ export interface SyncDocsOptions {
42
44
  json?: boolean;
43
45
  /** 同步 AGENTS.md(agent 导读);PRESERVE 标记段在重新生成时原样保留 */
44
46
  agents?: boolean;
47
+ /** 一次性迁移:将 CAPABILITIES.md 文件表格折叠为目录条目 */
48
+ compact?: boolean;
45
49
  }
46
50
 
47
51
  /**
@@ -51,6 +55,26 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
51
55
  const projectPath = options.projectPath || process.cwd();
52
56
  const isCheck = options.check === true;
53
57
  const isJson = options.json === true;
58
+ const capsMode = getCapabilitiesMode(projectPath);
59
+
60
+ // --compact:一次性迁移,将文件表格折叠为目录条目后直接返回
61
+ if (options.compact) {
62
+ const capsPath = path.join(projectPath, 'CAPABILITIES.md');
63
+ try {
64
+ const content = await fs.readFile(capsPath, 'utf-8');
65
+ const compacted = compactCapabilitiesContent(content);
66
+ if (compacted !== content) {
67
+ await fs.writeFile(capsPath, compacted, 'utf-8');
68
+ if (!isJson) console.log(chalk.green('✅ 已将 CAPABILITIES.md 文件表格折叠为目录条目'));
69
+ } else {
70
+ if (!isJson) console.log(chalk.green('✅ CAPABILITIES.md 无需折叠'));
71
+ }
72
+ return true;
73
+ } catch {
74
+ if (!isJson) console.log(chalk.red('❌ CAPABILITIES.md 不存在,无法折叠'));
75
+ return false;
76
+ }
77
+ }
54
78
 
55
79
  if (!isJson) {
56
80
  if (isCheck) {
@@ -88,7 +112,8 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
88
112
  let capsIsCapabilityListing = false;
89
113
  try {
90
114
  capsContent = await fs.readFile(capabilitiesPath, 'utf-8');
91
- if (isCapabilityListingFormat(capsContent)) {
115
+ // mode=listing 是清单格式的显式声明,与嗅探结果等价
116
+ if (capsMode === 'listing' || isCapabilityListingFormat(capsContent)) {
92
117
  capsIsCapabilityListing = true;
93
118
  } else {
94
119
  existingFiles = await parseCapabilitiesFiles(capabilitiesPath);
@@ -116,13 +141,31 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
116
141
  // (basename 列表里永远不会有目录,直接对比会把目录行误报为「已删除的模块」)
117
142
  const existingDirs = existingFiles.filter(f => f.endsWith('/'));
118
143
  const existingFileNames = existingFiles.filter(f => !f.endsWith('/'));
119
- result.added = currentModules
120
- .filter(
121
- m =>
122
- !existingDirs.some(d => m.file.startsWith(d)) &&
123
- !existingFileNames.includes(getBasename(m.file))
124
- )
125
- .map(m => getBasename(m.file));
144
+ if (capsMode === 'module') {
145
+ // module 模式:文件条目精确匹配或目录条目前缀覆盖;
146
+ // added 不再是逐文件列表,聚合为「未覆盖目录」(与 capability_sync checker 同规则)
147
+ const entries = readCapabilitiesEntries(capabilitiesPath, { includeDirs: true });
148
+ const fileEntries = entries.filter(e => !e.endsWith('/'));
149
+ const dirEntries = entries.filter(e => e.endsWith('/'));
150
+ const uncoveredDirs = new Set<string>();
151
+ for (const m of currentModules) {
152
+ const covered =
153
+ fileEntries.includes(m.file) || dirEntries.some(d => m.file.startsWith(d));
154
+ if (!covered) {
155
+ const root = srcDirs.find(d => m.file.startsWith(d + '/')) || srcDirs[0] || '';
156
+ uncoveredDirs.add(aggregateToSourceSubdir(root, m.file));
157
+ }
158
+ }
159
+ result.added = [...uncoveredDirs];
160
+ } else {
161
+ result.added = currentModules
162
+ .filter(
163
+ m =>
164
+ !existingDirs.some(d => m.file.startsWith(d)) &&
165
+ !existingFileNames.includes(getBasename(m.file))
166
+ )
167
+ .map(m => getBasename(m.file));
168
+ }
126
169
  result.removed = existingFileNames.filter(f => !currentBasenames.includes(f));
127
170
 
128
171
  // 幽灵条目清扫(2026-08-08 studio CI 4 连红事故):上方按 basename 对比,
@@ -235,18 +278,30 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
235
278
  }
236
279
 
237
280
  if (!capsIsCapabilityListing && hasTableIssues) {
238
- jsonOutput.added = result.added.map(f => {
239
- const getBasenameLocal = (s: string) => s.split('/').pop()!;
240
- return {
241
- file: f,
242
- module: currentModules.find(m => getBasenameLocal(m.file) === f),
243
- };
244
- });
245
- jsonOutput.removed = result.removed.map(f => ({ file: f }));
246
- (jsonOutput.resolution as Array<Record<string, unknown>>).push({
247
- action: 'sync-capabilities',
248
- command: 'harness sync-docs',
249
- });
281
+ if (capsMode === 'module') {
282
+ // module 模式:added 为聚合后的未覆盖目录,需人工登记目录条目
283
+ jsonOutput.added = result.added.map(d => ({ dir: d }));
284
+ jsonOutput.removed = result.removed.map(f => ({ file: f }));
285
+ (jsonOutput.resolution as Array<Record<string, unknown>>).push({
286
+ action: 'register-capability-dirs',
287
+ details:
288
+ '在 CAPABILITIES.md 中为这些目录登记一行目录条目(如 `| 模块名 | src/xxx/ | 说明 |`)',
289
+ dirs: result.added,
290
+ });
291
+ } else {
292
+ jsonOutput.added = result.added.map(f => {
293
+ const getBasenameLocal = (s: string) => s.split('/').pop()!;
294
+ return {
295
+ file: f,
296
+ module: currentModules.find(m => getBasenameLocal(m.file) === f),
297
+ };
298
+ });
299
+ jsonOutput.removed = result.removed.map(f => ({ file: f }));
300
+ (jsonOutput.resolution as Array<Record<string, unknown>>).push({
301
+ action: 'sync-capabilities',
302
+ command: 'harness sync-docs',
303
+ });
304
+ }
250
305
  }
251
306
 
252
307
  if (hasContextIssues) {
@@ -282,8 +337,16 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
282
337
  }
283
338
 
284
339
  if (result.added.length > 0) {
285
- console.log(chalk.yellow(`\n📄 CAPABILITIES.md 缺少以下模块:`));
286
- result.added.forEach(f => console.log(chalk.gray(` + ${f}`)));
340
+ if (capsMode === 'module') {
341
+ console.log(chalk.yellow(`\n📄 CAPABILITIES.md 未登记以下模块(目录):`));
342
+ result.added.forEach(d => console.log(chalk.gray(` + ${d}`)));
343
+ console.log(
344
+ chalk.gray(' 请在 CAPABILITIES.md 中为这些目录登记一行目录条目(如 `| 模块名 | src/xxx/ | 说明 |`)')
345
+ );
346
+ } else {
347
+ console.log(chalk.yellow(`\n📄 CAPABILITIES.md 缺少以下模块:`));
348
+ result.added.forEach(f => console.log(chalk.gray(` + ${f}`)));
349
+ }
287
350
  }
288
351
 
289
352
  if (result.removed.length > 0) {
@@ -337,7 +400,7 @@ export async function syncDocs(options: SyncDocsOptions): Promise<boolean> {
337
400
  }
338
401
 
339
402
  if (!capsIsCapabilityListing && hasTableIssues) {
340
- await updateCapabilitiesFile(capabilitiesPath, currentModules, existingFiles, result);
403
+ await updateCapabilitiesFile(capabilitiesPath, currentModules, existingFiles, result, capsMode);
341
404
  console.log(chalk.green(`\n✅ 已更新 CAPABILITIES.md`));
342
405
  }
343
406
 
@@ -163,7 +163,7 @@ export async function createExampleCheckpoint(projectPath: string): Promise<void
163
163
  * 默认 Resolutions(RKB — 约束 → 已知解法映射)
164
164
  */
165
165
  const DEFAULT_RESOLUTIONS = {
166
- no_claim_without_evidence: {
166
+ no_fuzzy_completion_claim: {
167
167
  title: 'commit message 缺少验证证据',
168
168
  fix: '在 commit message body 中附上验证输出:\n`npx harness check --staged` | `npx harness validate` | `npm test -- --coverage`\n确认全部通过后重新 commit。',
169
169
  },
@@ -175,10 +175,6 @@ const DEFAULT_RESOLUTIONS = {
175
175
  title: '关键目录缺少 CONTEXT.md',
176
176
  fix: '在 required_dirs 目录下创建 CONTEXT.md。运行 `npx harness sync-docs` 可自动生成模板。',
177
177
  },
178
- no_coverage_decrease: {
179
- title: '测试覆盖率下降',
180
- fix: '运行 `npm test -- --coverage` 查看当前覆盖率。新增文件必须有对应的测试文件。',
181
- },
182
178
  };
183
179
 
184
180
  /**