@dommaker/harness 1.8.1 → 1.10.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 (299) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +10 -13
  3. package/dist/cli/commands/check.d.ts +3 -3
  4. package/dist/cli/commands/check.d.ts.map +1 -1
  5. package/dist/cli/commands/check.js +41 -78
  6. package/dist/cli/commands/check.js.map +1 -1
  7. package/dist/cli/commands/constraints-report.d.ts +2 -10
  8. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  9. package/dist/cli/commands/constraints-report.js +4 -57
  10. package/dist/cli/commands/constraints-report.js.map +1 -1
  11. package/dist/cli/commands/constraints-retire.d.ts +9 -23
  12. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  13. package/dist/cli/commands/constraints-retire.js +39 -121
  14. package/dist/cli/commands/constraints-retire.js.map +1 -1
  15. package/dist/cli/commands/constraints.d.ts +3 -8
  16. package/dist/cli/commands/constraints.d.ts.map +1 -1
  17. package/dist/cli/commands/constraints.js +8 -23
  18. package/dist/cli/commands/constraints.js.map +1 -1
  19. package/dist/cli/commands/definitions.js +2 -2
  20. package/dist/cli/commands/init.d.ts +0 -32
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/init.js +7 -149
  23. package/dist/cli/commands/init.js.map +1 -1
  24. package/dist/cli/commands/report.d.ts.map +1 -1
  25. package/dist/cli/commands/report.js +14 -17
  26. package/dist/cli/commands/report.js.map +1 -1
  27. package/dist/cli/commands/scaffold-templates.d.ts +0 -2
  28. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
  29. package/dist/cli/commands/scaffold-templates.js +2 -37
  30. package/dist/cli/commands/scaffold-templates.js.map +1 -1
  31. package/dist/cli/commands/scaffold.d.ts +0 -2
  32. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  33. package/dist/cli/commands/scaffold.js +0 -10
  34. package/dist/cli/commands/scaffold.js.map +1 -1
  35. package/dist/cli/commands/status.js +10 -10
  36. package/dist/cli/commands/status.js.map +1 -1
  37. package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
  38. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  39. package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
  40. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  41. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +22 -0
  42. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  43. package/dist/cli/commands/sync-docs/capabilities-syncer.js +113 -8
  44. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  45. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  46. package/dist/cli/commands/sync-docs/index.js +36 -4
  47. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  48. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  49. package/dist/core/constraints/capabilities-parser.js +16 -4
  50. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  51. package/dist/core/constraints/checker.d.ts +15 -24
  52. package/dist/core/constraints/checker.d.ts.map +1 -1
  53. package/dist/core/constraints/checker.js +33 -72
  54. package/dist/core/constraints/checker.js.map +1 -1
  55. package/dist/core/constraints/checkers/docs-freshness.js +1 -1
  56. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  57. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
  58. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  59. package/dist/core/constraints/checkers/governance-presence.js +4 -3
  60. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  61. package/dist/core/constraints/checkers/index.d.ts +1 -1
  62. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  63. package/dist/core/constraints/checkers/index.js +4 -8
  64. package/dist/core/constraints/checkers/index.js.map +1 -1
  65. package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
  66. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  67. package/dist/core/constraints/checkers/iron-flags.js +4 -5
  68. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  69. package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
  70. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  71. package/dist/core/constraints/checkers/types.d.ts +1 -1
  72. package/dist/core/constraints/checkers/types.js +1 -1
  73. package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
  74. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  75. package/dist/core/constraints/definitions/guidelines.js +9 -25
  76. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  77. package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
  78. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  79. package/dist/core/constraints/definitions/iron-laws.js +10 -81
  80. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  81. package/dist/core/constraints/definitions.d.ts +7 -9
  82. package/dist/core/constraints/definitions.d.ts.map +1 -1
  83. package/dist/core/constraints/definitions.js +13 -16
  84. package/dist/core/constraints/definitions.js.map +1 -1
  85. package/dist/core/constraints/index.d.ts +2 -5
  86. package/dist/core/constraints/index.d.ts.map +1 -1
  87. package/dist/core/constraints/index.js +3 -13
  88. package/dist/core/constraints/index.js.map +1 -1
  89. package/dist/core/constraints/injection-writer.d.ts +10 -38
  90. package/dist/core/constraints/injection-writer.d.ts.map +1 -1
  91. package/dist/core/constraints/injection-writer.js +17 -54
  92. package/dist/core/constraints/injection-writer.js.map +1 -1
  93. package/dist/core/constraints/run-env.d.ts +1 -10
  94. package/dist/core/constraints/run-env.d.ts.map +1 -1
  95. package/dist/core/constraints/run-env.js +1 -22
  96. package/dist/core/constraints/run-env.js.map +1 -1
  97. package/dist/core/constraints/usage-report.d.ts +5 -7
  98. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  99. package/dist/core/constraints/usage-report.js +5 -10
  100. package/dist/core/constraints/usage-report.js.map +1 -1
  101. package/dist/core/effective-constraints.d.ts +12 -22
  102. package/dist/core/effective-constraints.d.ts.map +1 -1
  103. package/dist/core/effective-constraints.js +13 -30
  104. package/dist/core/effective-constraints.js.map +1 -1
  105. package/dist/core/index.d.ts +2 -2
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +3 -9
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/core/project-config-loader.d.ts +4 -42
  110. package/dist/core/project-config-loader.d.ts.map +1 -1
  111. package/dist/core/project-config-loader.js +16 -125
  112. package/dist/core/project-config-loader.js.map +1 -1
  113. package/dist/core/validators/passes-gate.d.ts +2 -2
  114. package/dist/core/validators/passes-gate.js +6 -6
  115. package/dist/core/validators/passes-gate.js.map +1 -1
  116. package/dist/hooks/bootstrap.d.ts +9 -12
  117. package/dist/hooks/bootstrap.d.ts.map +1 -1
  118. package/dist/hooks/bootstrap.js +9 -31
  119. package/dist/hooks/bootstrap.js.map +1 -1
  120. package/dist/hooks/index.d.ts +5 -7
  121. package/dist/hooks/index.d.ts.map +1 -1
  122. package/dist/hooks/index.js +4 -10
  123. package/dist/hooks/index.js.map +1 -1
  124. package/dist/index.d.ts +9 -12
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +10 -23
  127. package/dist/index.js.map +1 -1
  128. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  129. package/dist/monitoring/trace-analyzer.js +11 -12
  130. package/dist/monitoring/trace-analyzer.js.map +1 -1
  131. package/dist/monitoring/traces.d.ts +4 -4
  132. package/dist/monitoring/traces.d.ts.map +1 -1
  133. package/dist/monitoring/traces.js +8 -8
  134. package/dist/monitoring/traces.js.map +1 -1
  135. package/dist/presets/standard.d.ts +8 -10
  136. package/dist/presets/standard.d.ts.map +1 -1
  137. package/dist/presets/standard.js +10 -16
  138. package/dist/presets/standard.js.map +1 -1
  139. package/dist/types/constraint.d.ts +24 -35
  140. package/dist/types/constraint.d.ts.map +1 -1
  141. package/dist/types/constraint.js +8 -7
  142. package/dist/types/constraint.js.map +1 -1
  143. package/dist/types/passes-gate.d.ts +5 -5
  144. package/dist/types/passes-gate.d.ts.map +1 -1
  145. package/dist/types/project-config.d.ts +8 -66
  146. package/dist/types/project-config.d.ts.map +1 -1
  147. package/dist/types/project-config.js +3 -2
  148. package/dist/types/project-config.js.map +1 -1
  149. package/dist/types/trace.d.ts +8 -10
  150. package/dist/types/trace.d.ts.map +1 -1
  151. package/package.json +2 -2
  152. package/src/CONTEXT.md +3 -3
  153. package/src/__tests__/checker-extra.test.ts +37 -61
  154. package/src/__tests__/checker.test.ts +10 -115
  155. package/src/__tests__/effective-constraints.test.ts +18 -83
  156. package/src/__tests__/iron-laws.test.ts +71 -90
  157. package/src/__tests__/passes-gate-check.test.ts +2 -2
  158. package/src/__tests__/project-config-loader-extra.test.ts +22 -123
  159. package/src/__tests__/project-config-loader.test.ts +5 -33
  160. package/src/__tests__/public-exports.test.ts +31 -11
  161. package/src/__tests__/public-type-surface.test.ts +2 -13
  162. package/src/__tests__/public-value-surface.test.ts +1 -7
  163. package/src/__tests__/skip-semantics.test.ts +21 -37
  164. package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
  165. package/src/__tests__/trace-analyzer.test.ts +21 -21
  166. package/src/__tests__/trace-collector.test.ts +22 -22
  167. package/src/__tests__/traces.test.ts +27 -28
  168. package/src/cli/commands/CONTEXT.md +12 -9
  169. package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
  170. package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
  171. package/src/cli/commands/__tests__/check.test.ts +14 -31
  172. package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
  173. package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
  174. package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
  175. package/src/cli/commands/__tests__/init-ondisk.test.ts +12 -20
  176. package/src/cli/commands/__tests__/init.test.ts +0 -11
  177. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
  178. package/src/cli/commands/__tests__/registry.test.ts +39 -3
  179. package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
  180. package/src/cli/commands/__tests__/report.test.ts +1 -1
  181. package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
  182. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
  183. package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
  184. package/src/cli/commands/__tests__/status.test.ts +12 -12
  185. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
  186. package/src/cli/commands/__tests__/sync-docs-table-layout.test.ts +259 -0
  187. package/src/cli/commands/check.ts +43 -82
  188. package/src/cli/commands/constraints-report.ts +4 -59
  189. package/src/cli/commands/constraints-retire.ts +49 -154
  190. package/src/cli/commands/constraints.ts +11 -26
  191. package/src/cli/commands/definitions.ts +2 -2
  192. package/src/cli/commands/init.ts +8 -179
  193. package/src/cli/commands/report.ts +19 -23
  194. package/src/cli/commands/scaffold-templates.ts +1 -37
  195. package/src/cli/commands/scaffold.ts +0 -11
  196. package/src/cli/commands/status.ts +10 -10
  197. package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
  198. package/src/cli/commands/sync-docs/capabilities-syncer.ts +139 -9
  199. package/src/cli/commands/sync-docs/index.ts +41 -4
  200. package/src/core/CONTEXT.md +9 -9
  201. package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
  202. package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
  203. package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
  204. package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
  205. package/src/core/constraints/__tests__/run-env.test.ts +5 -34
  206. package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
  207. package/src/core/constraints/capabilities-parser.ts +17 -5
  208. package/src/core/constraints/checker.ts +39 -88
  209. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
  210. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
  211. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
  212. package/src/core/constraints/checkers/docs-freshness.ts +1 -1
  213. package/src/core/constraints/checkers/governance-presence.ts +4 -3
  214. package/src/core/constraints/checkers/index.ts +4 -10
  215. package/src/core/constraints/checkers/iron-flags.ts +3 -12
  216. package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
  217. package/src/core/constraints/checkers/types.ts +1 -1
  218. package/src/core/constraints/definitions/guidelines.ts +8 -25
  219. package/src/core/constraints/definitions/iron-laws.ts +9 -82
  220. package/src/core/constraints/definitions.ts +14 -15
  221. package/src/core/constraints/index.ts +4 -19
  222. package/src/core/constraints/injection-writer.ts +16 -61
  223. package/src/core/constraints/run-env.ts +1 -33
  224. package/src/core/constraints/usage-report.ts +8 -15
  225. package/src/core/effective-constraints.ts +14 -45
  226. package/src/core/index.ts +3 -10
  227. package/src/core/project-config-loader.ts +20 -145
  228. package/src/core/validators/passes-gate.ts +6 -6
  229. package/src/failure/CONTEXT.md +1 -1
  230. package/src/hooks/CONTEXT.md +12 -22
  231. package/src/hooks/__tests__/bootstrap.test.ts +24 -75
  232. package/src/hooks/bootstrap.ts +9 -48
  233. package/src/hooks/index.ts +5 -20
  234. package/src/index.ts +9 -31
  235. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
  236. package/src/monitoring/__tests__/traces.test.ts +22 -22
  237. package/src/monitoring/trace-analyzer.ts +12 -13
  238. package/src/monitoring/traces.ts +8 -8
  239. package/src/presets/__tests__/standard.test.ts +12 -16
  240. package/src/presets/standard.ts +14 -23
  241. package/src/types/__tests__/types.test.ts +3 -3
  242. package/src/types/constraint.ts +26 -40
  243. package/src/types/passes-gate.ts +5 -5
  244. package/src/types/project-config.ts +8 -77
  245. package/src/types/trace.ts +8 -11
  246. package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
  247. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
  248. package/dist/core/constraints/agent-prompt-renderer.js +0 -55
  249. package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
  250. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
  251. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
  252. package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
  253. package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
  254. package/dist/core/constraints/definitions/prompts.d.ts +0 -12
  255. package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
  256. package/dist/core/constraints/definitions/prompts.js +0 -400
  257. package/dist/core/constraints/definitions/prompts.js.map +0 -1
  258. package/dist/core/constraints/injection-drift.d.ts +0 -61
  259. package/dist/core/constraints/injection-drift.d.ts.map +0 -1
  260. package/dist/core/constraints/injection-drift.js +0 -131
  261. package/dist/core/constraints/injection-drift.js.map +0 -1
  262. package/dist/core/constraints/injection-renderer.d.ts +0 -23
  263. package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
  264. package/dist/core/constraints/injection-renderer.js +0 -44
  265. package/dist/core/constraints/injection-renderer.js.map +0 -1
  266. package/dist/hooks/config.d.ts +0 -30
  267. package/dist/hooks/config.d.ts.map +0 -1
  268. package/dist/hooks/config.js +0 -34
  269. package/dist/hooks/config.js.map +0 -1
  270. package/dist/hooks/pipeline.d.ts +0 -54
  271. package/dist/hooks/pipeline.d.ts.map +0 -1
  272. package/dist/hooks/pipeline.js +0 -173
  273. package/dist/hooks/pipeline.js.map +0 -1
  274. package/dist/hooks/registry.d.ts +0 -70
  275. package/dist/hooks/registry.d.ts.map +0 -1
  276. package/dist/hooks/registry.js +0 -141
  277. package/dist/hooks/registry.js.map +0 -1
  278. package/dist/hooks/types.d.ts +0 -113
  279. package/dist/hooks/types.d.ts.map +0 -1
  280. package/dist/hooks/types.js +0 -9
  281. package/dist/hooks/types.js.map +0 -1
  282. package/src/__tests__/hooks-pipeline.test.ts +0 -201
  283. package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
  284. package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
  285. package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
  286. package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
  287. package/src/core/constraints/agent-prompt-renderer.ts +0 -67
  288. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
  289. package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
  290. package/src/core/constraints/definitions/prompts.ts +0 -414
  291. package/src/core/constraints/injection-drift.ts +0 -142
  292. package/src/core/constraints/injection-renderer.ts +0 -44
  293. package/src/hooks/__tests__/config.test.ts +0 -19
  294. package/src/hooks/__tests__/pipeline.test.ts +0 -195
  295. package/src/hooks/__tests__/registry.test.ts +0 -115
  296. package/src/hooks/config.ts +0 -33
  297. package/src/hooks/pipeline.ts +0 -202
  298. package/src/hooks/registry.ts +0 -166
  299. package/src/hooks/types.ts +0 -120
@@ -54,12 +54,22 @@ export async function updateCapabilitiesFile(
54
54
 
55
55
  // 如果有表格行,更新表格
56
56
  if (existingFiles.length > 0) {
57
- // 移除已删除文件的行
58
- for (const removed of result.removed) {
59
- const escapedFile = removed.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
60
- // 第二列存完整路径,basename 只在末尾出现,用 [^|]* 匹配路径前缀
61
- const rowRegex = new RegExp(`^\\|[^|]*\\|[^|]*\\b${escapedFile}\\s*\\|.*$`, 'gm');
62
- content = content.replace(rowRegex, '');
57
+ // 移除已删除文件的行(整行连行尾一起删——只清行内容会留一个空行,
58
+ // CommonMark 据此把一张表切成若干小表,harness#171)。
59
+ // 这里**不**豁免围栏代码块,与下方排版收拢刻意不同口径:登记条目由
60
+ // capabilities-parser 全文扫描得出(ADR-0009),围栏内的行同样算登记项;
61
+ // 只让删除认围栏而条目不认,块内示例行会一直被解析成幽灵条目,`--check`
62
+ // 从此每轮都报同一个已删文件且永远修不掉。豁免要生效得连登记面一起改。
63
+ if (result.removed.length > 0) {
64
+ const deadRowRegexes = result.removed.map((removed) => {
65
+ const escapedFile = removed.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
66
+ // 第二列存完整路径,basename 只在末尾出现,用 [^|]* 匹配路径前缀
67
+ return new RegExp(`^\\|[^|]*\\|[^|]*\\b${escapedFile}\\s*\\|.*\\r?$`);
68
+ });
69
+ content = content
70
+ .split('\n')
71
+ .filter((line) => !deadRowRegexes.some((rowRegex) => rowRegex.test(line)))
72
+ .join('\n');
63
73
  }
64
74
 
65
75
  // 添加新文件的行(在最后一个表格行之后);module 模式跳过
@@ -80,14 +90,17 @@ export async function updateCapabilitiesFile(
80
90
  content = content.replace(lastTableRow, lastTableRow + '\n' + newRows);
81
91
  }
82
92
  }
83
-
84
- // 清理多余空行
85
- content = content.replace(/\n{3,}/g, '\n\n');
86
93
  } else {
87
94
  // 没有表格,追加模块表格(module 模式按目录聚合)
88
95
  content += '\n\n' + (mode === 'module' ? generateDirTable(currentModules) : generateModuleTable(currentModules));
89
96
  }
90
97
 
98
+ // 表格排版收拢放在增删之后:有新行的表不会被误判为空表(#171)
99
+ content = normalizeCapabilitiesTableLayout(content).content;
100
+ // 多余空行的清理收在收拢之后一处:收拢本身会新产出连续空行
101
+ // (收掉一张夹在两段散文之间的空表就留下 `\n\n\n`,#171)
102
+ content = content.replace(/\n{3,}/g, '\n\n');
103
+
91
104
  // 更新最后更新时间
92
105
  const now = new Date().toISOString().split('T')[0];
93
106
  content = content.replace(
@@ -98,6 +111,123 @@ export async function updateCapabilitiesFile(
98
111
  await fs.writeFile(capabilitiesPath, content, 'utf-8');
99
112
  }
100
113
 
114
+ /** 表格行:以 `|` 起始(表头、分隔行、数据行都算) */
115
+ const TABLE_ROW_REGEX = /^\s*\|/;
116
+
117
+ /** 表格分隔行(|------|------|) */
118
+ const TABLE_SEPARATOR_REGEX = /^\s*\|[\s:|-]+\|\s*$/;
119
+
120
+ /** 围栏代码块的起止行(``` / ~~~,允许缩进与信息串) */
121
+ const FENCE_LINE_REGEX = /^\s*(?:```|~~~)/;
122
+
123
+ export interface TableLayoutNormalization {
124
+ /** 收拢后的内容 */
125
+ content: string;
126
+ /** 被删掉的「表格内空行」行数 */
127
+ blankLines: number;
128
+ /** 被收掉的「空表」(表头+分隔行且无数据行)张数 */
129
+ emptyTables: number;
130
+ }
131
+
132
+ /** 参与排版判定的行:正文 + 是否落在围栏代码块内 */
133
+ interface LayoutLine {
134
+ text: string;
135
+ fenced: boolean;
136
+ }
137
+
138
+ /**
139
+ * 按行切开并标出围栏代码块(``` / ~~~)内的行
140
+ *
141
+ * 只服务排版收拢:CAPABILITIES.md 是手写文档,块内长得像表格的示例行不是排版脏行,
142
+ * 收它就是删用户正文(harness#171)。登记条目面不在此列——见 `updateCapabilitiesFile`
143
+ * 里「幽灵行删除不豁免围栏」的取舍注释。
144
+ */
145
+ function parseLayoutLines(content: string): LayoutLine[] {
146
+ const parsed: LayoutLine[] = [];
147
+ let inFence = false;
148
+ for (const text of content.split('\n')) {
149
+ if (FENCE_LINE_REGEX.test(text)) {
150
+ parsed.push({ text, fenced: true });
151
+ inFence = !inFence;
152
+ continue;
153
+ }
154
+ parsed.push({ text, fenced: inFence });
155
+ }
156
+ return parsed;
157
+ }
158
+
159
+ const isTableRow = (line: LayoutLine | undefined): line is LayoutLine =>
160
+ line !== undefined && !line.fenced && TABLE_ROW_REGEX.test(line.text);
161
+
162
+ const isTableSeparator = (line: LayoutLine | undefined): boolean =>
163
+ line !== undefined && !line.fenced && TABLE_SEPARATOR_REGEX.test(line.text);
164
+
165
+ /** 走一遍两条收拢规则(不迭代) */
166
+ function collapseTableLayoutOnce(content: string): TableLayoutNormalization {
167
+ const parsed = parseLayoutLines(content);
168
+
169
+ // ① 收拢表格内空行:一段连续空行,两侧最近非空行都是表格行 → 整段丢弃
170
+ const kept: LayoutLine[] = [];
171
+ let blankLines = 0;
172
+ for (let i = 0; i < parsed.length; ) {
173
+ if (parsed[i].text.trim() !== '') {
174
+ kept.push(parsed[i]);
175
+ i++;
176
+ continue;
177
+ }
178
+ let end = i;
179
+ while (end < parsed.length && parsed[end].text.trim() === '') end++;
180
+ if (isTableRow(kept[kept.length - 1]) && isTableRow(parsed[end])) {
181
+ blankLines += end - i;
182
+ } else {
183
+ kept.push(...parsed.slice(i, end));
184
+ }
185
+ i = end;
186
+ }
187
+
188
+ // ② 收掉空表:表头 + 分隔行后面没有数据行
189
+ const out: string[] = [];
190
+ let emptyTables = 0;
191
+ for (let i = 0; i < kept.length; i++) {
192
+ if (isTableRow(kept[i]) && isTableSeparator(kept[i + 1]) && !isTableRow(kept[i + 2])) {
193
+ emptyTables++;
194
+ i++;
195
+ continue;
196
+ }
197
+ out.push(kept[i].text);
198
+ }
199
+
200
+ return { content: out.join('\n'), blankLines, emptyTables };
201
+ }
202
+
203
+ /**
204
+ * 收拢 CAPABILITIES.md 的表格排版(harness#171)
205
+ *
206
+ * 两条规则:① 删掉夹在两个表格行之间的空行(CommonMark 会在此切断表格);
207
+ * ② 收掉没有数据行的表头+分隔行。表格外的空行(段落分隔)不动;围栏代码块内的行整体豁免。
208
+ *
209
+ * 规则互相制造对方的触发点(①把两张空表之间的那个空行吃掉后,②一轮只收得掉后一张),
210
+ * 所以跑到不动点:**一次 `sync-docs` 必须把 `--check` 报出来的东西全清掉**,
211
+ * 否则下游 CI 修完还是红的。计数 = 各轮合计(每轮只删当轮存在的行,不会重计)。
212
+ *
213
+ * 幂等,且 `--check` 与写模式共用此正本——判定面就是「返回内容与入参是否不同」,
214
+ * 因此不存在「check 报了 fix 修不掉」的不收敛(ADR-0009 口径)。
215
+ */
216
+ export function normalizeCapabilitiesTableLayout(content: string): TableLayoutNormalization {
217
+ let current = content;
218
+ let blankLines = 0;
219
+ let emptyTables = 0;
220
+ for (;;) {
221
+ const pass = collapseTableLayoutOnce(current);
222
+ if (pass.blankLines === 0 && pass.emptyTables === 0) {
223
+ return { content: current, blankLines, emptyTables };
224
+ }
225
+ blankLines += pass.blankLines;
226
+ emptyTables += pass.emptyTables;
227
+ current = pass.content;
228
+ }
229
+ }
230
+
101
231
  /**
102
232
  * 生成 CAPABILITIES.md 内容
103
233
  */
@@ -34,6 +34,7 @@ import {
34
34
  parseCapabilitiesFiles,
35
35
  updateCapabilitiesFile,
36
36
  compactCapabilitiesContent,
37
+ normalizeCapabilitiesTableLayout,
37
38
  } from './capabilities-syncer';
38
39
  import {
39
40
  createContextMd,
@@ -264,7 +265,15 @@ export async function syncDocs(
264
265
  }
265
266
  const hasAgentsIssues = options.agents === true && agentsMdStale;
266
267
 
267
- const hasTableIssues = result.added.length > 0 || result.removed.length > 0;
268
+ // 表格排版脏行(#171):撤登记时残留的空行会把 CAPABILITIES.md 的表格切断。
269
+ // 判定与修复共用 normalizeCapabilitiesTableLayout 正本——同一份规则,check 报的 fix 必清得掉。
270
+ const tableLayout = capsIsCapabilityListing
271
+ ? { blankLines: 0, emptyTables: 0 }
272
+ : normalizeCapabilitiesTableLayout(capsContent);
273
+ const hasTableLayoutIssues = tableLayout.blankLines > 0 || tableLayout.emptyTables > 0;
274
+
275
+ const hasTableEntryIssues = result.added.length > 0 || result.removed.length > 0;
276
+ const hasTableIssues = hasTableEntryIssues || hasTableLayoutIssues;
268
277
  const hasCapIssues = capCountMismatches.length > 0;
269
278
  // mtime 只作提示,不参与判定(harness#142):判定面是内容漂移与缺失
270
279
  const hasContextIssues = result.contextMissing.length > 0 || result.contextContentDrift.length > 0;
@@ -287,6 +296,7 @@ export async function syncDocs(
287
296
  contextMissing: result.contextMissing.length,
288
297
  contextStale: result.contextStale.length,
289
298
  contextContentDrift: result.contextContentDrift.length,
299
+ tableLayoutDirty: hasTableLayoutIssues,
290
300
  },
291
301
  contextMissing: result.contextMissing.map(d => ({
292
302
  dir: d,
@@ -314,7 +324,7 @@ export async function syncDocs(
314
324
  });
315
325
  }
316
326
 
317
- if (!capsIsCapabilityListing && hasTableIssues) {
327
+ if (!capsIsCapabilityListing && hasTableEntryIssues) {
318
328
  if (capsMode === 'module') {
319
329
  // module 模式:added 为聚合后的未覆盖目录,需人工登记目录条目
320
330
  jsonOutput.added = result.added.map(d => ({ dir: d }));
@@ -341,6 +351,19 @@ export async function syncDocs(
341
351
  }
342
352
  }
343
353
 
354
+ if (hasTableLayoutIssues) {
355
+ jsonOutput.tableLayout = {
356
+ blankLines: tableLayout.blankLines,
357
+ emptyTables: tableLayout.emptyTables,
358
+ };
359
+ (jsonOutput.resolution as Array<Record<string, unknown>>).push({
360
+ action: 'sync-capabilities-table-layout',
361
+ command: 'harness sync-docs',
362
+ details:
363
+ 'CAPABILITIES.md 的表格被空行切断、或残留无数据行的空表头,运行 harness sync-docs 收拢',
364
+ });
365
+ }
366
+
344
367
  if (hasContextIssues) {
345
368
  (jsonOutput.resolution as Array<Record<string, unknown>>).push(
346
369
  ...(result.contextMissing.length > 0
@@ -400,6 +423,16 @@ export async function syncDocs(
400
423
  result.removed.forEach(f => log(io, chalk.gray(` - ${f}`)));
401
424
  }
402
425
 
426
+ if (hasTableLayoutIssues) {
427
+ log(io, chalk.yellow(`\n🧹 CAPABILITIES.md 表格排版待收拢:`));
428
+ if (tableLayout.blankLines > 0) {
429
+ log(io, chalk.gray(` - 表格内空行 ${tableLayout.blankLines} 处(CommonMark 会在此把表格切断)`));
430
+ }
431
+ if (tableLayout.emptyTables > 0) {
432
+ log(io, chalk.gray(` - 无数据行的空表 ${tableLayout.emptyTables} 张(连表头/分隔行一起收掉)`));
433
+ }
434
+ }
435
+
403
436
  if (result.contextMissing.length > 0) {
404
437
  log(io, chalk.yellow(`\n📋 缺少 CONTEXT.md:`));
405
438
  result.contextMissing.forEach(d => log(io, chalk.gray(` - ${d}/CONTEXT.md`)));
@@ -450,10 +483,14 @@ export async function syncDocs(
450
483
  if (isCheck) {
451
484
  log(io, chalk.red('\n❌ 文档不是最新的,请运行 harness sync-docs 更新'));
452
485
  // reason 必须可定位(harness#142):CI 判红时要直接拿到文件与符号,不靠翻 stdout
486
+ const tableLayoutReason = hasTableLayoutIssues
487
+ ? `;CAPABILITIES.md 表格排版待收拢(表格内空行 ${tableLayout.blankLines} 处、`
488
+ + `空表 ${tableLayout.emptyTables} 张)`
489
+ : '';
453
490
  return drift(
454
491
  contextDriftReason
455
- ? `文档不是最新的(CONTEXT.md 与实现漂移:${contextDriftReason})`
456
- : '文档不是最新的,请运行 harness sync-docs 更新'
492
+ ? `文档不是最新的(CONTEXT.md 与实现漂移:${contextDriftReason})${tableLayoutReason}`
493
+ : `文档不是最新的,请运行 harness sync-docs 更新${tableLayoutReason}`
457
494
  );
458
495
  }
459
496
 
@@ -1,20 +1,20 @@
1
1
  # core/
2
2
 
3
3
  ## 职责
4
- 约束引擎核心:check/prompt 二元约束系统(ADR-0001)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)、Spec 验证器、项目配置加载。
4
+ 约束引擎核心:severity 显式约束系统(ADR-0029,三层命名与 kind 二元已由 ADR-0001 迁至本模型)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)、Spec 验证器、项目配置加载。
5
5
 
6
6
  ## 核心导出
7
- - `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS) + 检查引擎(ConstraintChecker,拦截统一由 checkBeforeExecution 承担,ADR-0004) + 缓存(CheckCache:TTL 缓存 + 计数采样,H6/G5 起公开导出) + 注入渲染(injection-renderer) + 落点写入器(injection-writer:「声明→对照→幂等替换」中 marker-range 替换的唯一 writer——replaceStandaloneRange/replaceEnclosedRange/cutMarkerBlock + 半标记守护;落点路由读写两侧同住此模块 resolveInjectionTarget(CLAUDE.md 有标记段优先、否则 AGENTS.md PRESERVE:governance,studio #307)/resolveGovernanceLanding(init 写侧,含旧版无标记 Governance Rules 块豁免),ADR-0011) + Agent prompt 渲染(agent-prompt-renderer:trigger 参数化分组渲染,role 路由留 studio,H6/G6;其 trigger 归一与匹配谓词单点在 `constraints/triggers.ts`——harness#105 定语义,harness#137 自 `utils/exec.ts` 归位,约束域谓词不住 utils 层)/漂移校验(injection-drift:纯检测只读,路由消费 injection-writer;期望段可吃调用方传入的本 run 生效集,缺省自取)/使用统计(usage-report:constraints report·retire 的只读数据层,读 trace 两个入口——`readProjectTracesReport()` 带 `skippedLines`、`readProjectTraces()` 兼容包装丢计数;报告模型 `ConstraintsUsageReport` 的降级位 = `traceFileExists` + `skippedLines`,harness#100)/清单对照(capabilities-reconcile:CAPABILITIES.md 与代码实况的覆盖/幽灵判定唯一实现,capability_sync·docs_freshness·sync-docs 共消费,ADR-0009;一次 check run 内该判定只算一次并由 `RunEnv.capabilities()` 供给两 checker,ADR-0023 决策 4——增量那一维由 capability_sync 用导出的唯一覆盖规则 `isCoveredByEntries` 自算,代码实况清单的唯一取法是 `collectPopulationFiles`);CheckEnv 生产侧唯一构造点 `buildCheckEnv(context, providers|'none', runEnv?)`(checkers/types.ts:CheckEnv `extends RunEnv`,runEnv 注入即与 context-builder 共用同一份上行数据读取、不传则自造一枚;providers = 证据接线,git 两项绑 GitEvidence 实例,'none' = 显式不接 → evidence flag checker 按契约 skip);判定接缝 `CheckOutcome = boolean | 'skip' | CheckDetail{pass, evidence}`(harness#119/ADR-0016)——编排层与 `gates/checker-gate` 一律经唯一归一点 `normalizeCheckOutcome()` 取三态 + 证据,不各自 `=== false`(对新形状会误判);证据行由 checker 自行措辞(自描述、不带缩进),组装与截断策略单点在 `formatEvidence()`/`MAX_EVIDENCE_ITEMS`(同住 checkers/types.ts);证据四出口 = `ConstraintResult.evidence` → CLI 结论块/提示块、`ExecutionTrace.evidence`、铁律 `ConstraintViolationError.message`(铁律违规直接 throw,异常文案是其证据唯一外溢面);检查引擎双出口——`checkConstraints`(block:首个铁律违规即抛,throw 契约不动)与 `collectConstraints`(collect:共享检查体、不抛、全量收集,report 的不抛出口,架构评审候选1)
7
+ - `constraints/` — 约束定义(CONSTRAINTS 单桶,severity 在条目上,ADR-0029) + 检查引擎(ConstraintChecker,拦截统一由 checkBeforeExecution 承担,ADR-0004) + 缓存(CheckCache:TTL 缓存 + 计数采样,H6/G5 起公开导出) + 标记区间写入器(injection-writer:「声明→对照→幂等替换」中 marker-range 替换的唯一 writer——replaceStandaloneRange/replaceEnclosedRange/cutMarkerBlock + 半标记守护;治理正本探测 GOVERNANCE_HEADING/hasGovernanceContract/countGovernanceHeadings/hasPreserveBlock 同住此模块,ADR-0011/#83。ADR-0029:注入渲染(injection-renderer)、漂移校验(injection-drift)、Agent prompt 渲染(agent-prompt-renderer) 与注入段落点路由已随文本注入层关停整体删除)/trigger 谓词(triggers.ts——harness#105 定语义,约束域谓词不住 utils 层)/使用统计(usage-report:constraints report·retire 的只读数据层,读 trace 两个入口——`readProjectTracesReport()` 带 `skippedLines`、`readProjectTraces()` 兼容包装丢计数;报告模型 `ConstraintsUsageReport` 的降级位 = `traceFileExists` + `skippedLines`,harness#100)/清单对照(capabilities-reconcile:CAPABILITIES.md 与代码实况的覆盖/幽灵判定唯一实现,capability_sync·docs_freshness·sync-docs 共消费,ADR-0009;一次 check run 内该判定只算一次并由 `RunEnv.capabilities()` 供给两 checker,ADR-0023 决策 4——增量那一维由 capability_sync 用导出的唯一覆盖规则 `isCoveredByEntries` 自算,代码实况清单的唯一取法是 `collectPopulationFiles`);CheckEnv 生产侧唯一构造点 `buildCheckEnv(context, providers|'none', runEnv?)`(checkers/types.ts:CheckEnv `extends RunEnv`,runEnv 注入即与 context-builder 共用同一份上行数据读取、不传则自造一枚;providers = 证据接线,git 两项绑 GitEvidence 实例,'none' = 显式不接 → evidence flag checker 按契约 skip);判定接缝 `CheckOutcome = boolean | 'skip' | CheckDetail{pass, evidence}`(harness#119/ADR-0016)——编排层与 `gates/checker-gate` 一律经唯一归一点 `normalizeCheckOutcome()` 取三态 + 证据,不各自 `=== false`(对新形状会误判);证据行由 checker 自行措辞(自描述、不带缩进),组装与截断策略单点在 `formatEvidence()`/`MAX_EVIDENCE_ITEMS`(同住 checkers/types.ts);证据四出口 = `ConstraintResult.evidence` → CLI 结论块/提示块、`ExecutionTrace.evidence`、error `ConstraintViolationError.message`(error 级违规直接 throw,异常文案是其证据唯一外溢面);检查引擎双出口——`checkConstraints`(block:首个 error 级违规即抛,throw 契约不动)与 `collectConstraints`(collect:共享检查体、不抛、全量收集,report 的不抛出口,架构评审候选1)
8
8
  - `constraints/context-reconcile.ts` — CONTEXT.md「核心导出」节与目录导出面的**幽灵/未登记**判定唯一实现(harness#142 / ADR-0025,与 `capabilities-reconcile` 同一套覆盖/幽灵语义,不另造第二套对照逻辑):`reconcileContext({ contextMdContent, exportSurface, barrelExports? })` → `{ declaredSymbols, ghosts, unlistedBarrelExports, hasCoreExportsSection }`;配套两个口径正本也是纯函数——`parseDeclaredExportSymbols`(只认 bullet 行首反引号标识符,`/` 连接视为多声明,形参与 `(type)`注解剥掉,非标识符的模块名 bullet 整条跳过)与 `parseExportStatements`(声明形 / `export { a, b as c }` / 逐项与块级 `type` / `export default` / `export *` 的待解析 spec)。零 fs IO:文件遍历与 `export *` 目标解析在调用方(`cli/commands/sync-docs/context-syncer.ts` 的 `collectContextExportSurface`);覆盖方向只判 barrel 的**值**符号(类型面豁免),mtime 判定就此降级为本地提示。本层 checker 暂无消费方
9
9
  - `git-evidence.ts` — check 链路的 git 取证唯一接缝(#87,范围按 ADR-0021 收窄;`spec/validator` 的 staged 列表同型复用):`createGitEvidence(projectPath, run?)` 返回 `GitEvidence{stagedDiff, changedFileNames(staged), headDirs}`,seam 上两个真 adapter(缺省 `realGitCommandRunner` 走真 git / 注入 runner 供计数与替身)。命令级 memo 落在此实例(含失败结果),实例生命周期 = 一次 check run,故 context-builder 与 ConstraintChecker 传同一实例即共用证据;判定语义与迁移前一致(diff 取证失败 → 空串,`git ls-tree` 失败 → null = 所有目录视为新);`parseHeadDirs`/`splitFileNames` 是配套纯函数
10
10
  - `constraints/context-builder.ts` — 只装配不取证:`buildConstraintContext(options.evidence?, options.runEnv?)` 与 `detectTrigger(…, options.runEnv?)` 经 GitEvidence 取 git 事实(不传 = 自建真 adapter),本模块无 child_process import;上行数据(源码根、trace 记录)经 `RunEnv` 取(ADR-0023),其余证据标志(ROOT_CAUSE/reuse/需求文档标记)仍为本地探测;`detectFailingTest`/`detectVerificationEvidence` 是模块私有的布尔投影(除本模块外零消费者,收私依 ADR-0022 同判据)
11
- - `constraints/run-env.ts` — 一次 check 的运行级观察面(ADR-0023):`createRunEnv(projectPath)` 返回 `RunEnv{projectPath, traceTail(limit), sourceRoots(), rawConfig(), customConstraints(fileName), capabilities(populationFiles)}`,懒建 + run 内 memo,同一份文件一次运行至多读一次。与 git-evidence 同一形状:一次 run 一份、沿调用链显式传递(CLI check 入口 → 生效集 / buildConstraintContext / checkConstraints / 漂移检测),不做模块级全局。为何是两个类型而不是一个:`CheckEnv` 含 `context`,而 `context` 正是本对象读文件算出来的产物——同一对象不能既是构造者的输入又是其产物;故这里只放「不需要 context 就能造」的那半,`CheckEnv` 以 `extends RunEnv` 从它派生(`buildCheckEnv(context, evidence, runEnv?)` 第三参注入,不传 = 自造一枚一次性观察面)。配置两口的口径:`rawConfig()` 缺失 → undefined、空文件 → `{}`、YAML 解析失败**照抛**(兜与否属消费方判定);`customConstraints(file)` 取 `custom_constraints` 段、缺失 → `{}`,「哪个文件名算数」仍由 ProjectConfigLoader 决定,读面不掺判定。`capabilities(population)` 一次读文档 + 一次 `reconcileCapabilities`(带存在性 oracle;不收 git 增量,`verdict.uncoveredChanges` 在此恒空),**memo 只记第一次传入的清单**——两消费方一律经 `collectPopulationFiles` 取数以由构造保证同形;源根 memo 与它共用。`traceTail(limit)` 底层是 `jsonl.readJsonlWindow`(一次分块读尾部行文本、按不同 limit 反复截窗),**先截行文本再 parse** 才与「各读一遍」逐字同口径(坏行占槽位不占 records 名额);请求超出窗口上限直接抛,不静默少给。`RunTarget = RunEnv | 项目根路径` 与 `resolveRunEnv()` 是这套入参形状的归一点:传路径 = 一次性读取语义(每次调用读当下内容)
12
- - `effective-constraints.ts` — `getEffectiveConstraints(target, {preset?})`:全仓唯一生效集来源(内置 → preset → config.yml 禁用(内置与 custom 同效)→ custom 追加(禁用/已退役的不追加)→ scenes 过滤);`constraintsFromMerged(merged)`:从已算好的合并配置取清单(推导单点,`getEffectiveConstraints` 也走它);`getMergedConstraintsConfig(target, {preset?})`:同链路的完整 MergedConstraintsConfig 形状(含 disabled/custom/unknownIds),内置工单 23 优先级规则(**只有显式传 preset 才覆盖 config.yml**,CLI 不给缺省值,ADR-0023 步骤 4.5),check 命令经此入口并把算好的一份清单转交漂移检测 → 一次运行只算一遍;三个入口的 `target` = `RunTarget`(传 RunEnv 即与 checker、漂移检测共用同一份配置读取,ADR-0023 决策 2);`lintEffectiveConfig` 配置诊断
11
+ - `constraints/run-env.ts` — 一次 check 的运行级观察面(ADR-0023):`createRunEnv(projectPath)` 返回 `RunEnv{projectPath, traceTail(limit), sourceRoots(), rawConfig(), capabilities(populationFiles)}`(ADR-0029:customConstraints 观察口随 custom 约束面退役移除),懒建 + run 内 memo,同一份文件一次运行至多读一次。与 git-evidence 同一形状:一次 run 一份、沿调用链显式传递(CLI check 入口 → 生效集 / buildConstraintContext / checkConstraints),不做模块级全局。为何是两个类型而不是一个:`CheckEnv` 含 `context`,而 `context` 正是本对象读文件算出来的产物——同一对象不能既是构造者的输入又是其产物;故这里只放「不需要 context 就能造」的那半,`CheckEnv` 以 `extends RunEnv` 从它派生(`buildCheckEnv(context, evidence, runEnv?)` 第三参注入,不传 = 自造一枚一次性观察面)。配置口口径:`rawConfig()` 缺失 → undefined、空文件 → `{}`、YAML 解析失败**照抛**(兜与否属消费方判定)。`capabilities(population)` 一次读文档 + 一次 `reconcileCapabilities`(带存在性 oracle;不收 git 增量,`verdict.uncoveredChanges` 在此恒空),**memo 只记第一次传入的清单**——两消费方一律经 `collectPopulationFiles` 取数以由构造保证同形;源根 memo 与它共用。`traceTail(limit)` 底层是 `jsonl.readJsonlWindow`(一次分块读尾部行文本、按不同 limit 反复截窗),**先截行文本再 parse** 才与「各读一遍」逐字同口径(坏行占槽位不占 records 名额);请求超出窗口上限直接抛,不静默少给。`RunTarget = RunEnv | 项目根路径` 与 `resolveRunEnv()` 是这套入参形状的归一点:传路径 = 一次性读取语义(每次调用读当下内容)
12
+ - `effective-constraints.ts` — `getEffectiveConstraints(target, {preset?})`:全仓唯一生效集来源(内置 → preset → config.yml 禁用;ADR-0029:custom 追加与 scenes 过滤随文本注入层关停退役);`constraintsFromMerged(merged)`:从已算好的合并配置取清单(推导单点,`getEffectiveConstraints` 也走它);`getMergedConstraintsConfig(target, {preset?})`:同链路的完整 MergedConstraintsConfig 形状(含 constraints/disabled/unknownIds 单桶),内置工单 23 优先级规则(**只有显式传 preset 才覆盖 config.yml**,CLI 不给缺省值,ADR-0023 步骤 4.5),check 命令经此入口 → 一次运行只算一遍;三个入口的 `target` = `RunTarget`(传 RunEnv 即与 checker 共用同一份配置读取,ADR-0023 决策 2);`lintEffectiveConfig` 配置诊断(unknownIds)
13
13
  - `effective-set.ts` — `filterEnabledEntries(knownIds, entries)`:config 条目筛选器唯一实现,语义单一——未知 id 静默忽略并收集进 `unknownIds` 供诊断(历史配置可能残留已移除条目的禁用键,故不抛错)。唯一生产调用方是约束侧 `mergeConstraints`;曾按语义分 `'collect'`/`'throw'` 两模式,throw 侧唯一调用方 `getEffectiveGates` 随 ADR-0002「后续变更」收缩后,该模式按 ADR-0022 判据删除(harness#141)
14
14
  - `validators/` — checkpoints、passes-gate、CSO 验证器;`test-output.ts` —「测试产物解读」唯一实现:`extractFailures` 提取函数 + `judgeTestRun` 判定入口(退出码为主 + 行首锚定的文本失败信号交叉否决;文本救不回非零退出,`allowPartialPass` 只作用于文本否决那一维),passes-gate 与 `gates/acceptance` 的结论一律从它取、各自体内不写判定 `if`(解析收口 ADR-0012,判定依据 ADR-0014);**不读覆盖率**——harness 核心判定对覆盖率不取数不执法,判覆盖率的两处(CLI `passes-gate --coverage`、`gates/performance`)各自读 json-summary,`extractCoverage` 与 `TaskTestResult.coverage` 已随 ADR-0015 删除;`PassesGate.runTests(workDir)` 的执行与证据落盘根是**必传形参**,本层不再取 cwd(harness#95,约定正本见 `src/cli/commands/CONTEXT.md`);`detectTestCommand` 是测试命令探测唯一正本(模块级导出:test:ci → test 排 echo 占位 → test:e2e → test:coverage → Python 标记 pyproject.toml/pytest.ini → go.mod,探不到返回 undefined,兜底归调用方——CLI 映射 skip、PassesGate 内部 fail-closed,不再兜底 'npm test';CLI 本地版已删,架构评审 A2);PassesGate 扩展注册表(registerExtension/unregisterExtension/getExtensionNames/runAllTests/checkAllPasses + PassesGateExtension/ExtensionTestResult 两类型与包根导出)已整段删除——零 adapter 假想 seam 且扩展结果本就不参与 allowed 判定,需要扩展测试类型时按 ADR-0014 判例在 `judgeTestRun` 层加,不恢复旧 seam(harness#104,架构评审 B1);PassesGate 公开面 = `check()` + `runTests()` 两个真实消费点,`setPasses`/`getTestResult`/`checkTestFileChanges` 三方法与死数据 `PROTECTED_TEST_PATTERNS`、私有 `testResults` 缓存已随 ADR-0022(双仓零消费者)删除,历史 AC-007「保留 setPasses 向后兼容」同案推翻;`runTest` 的 `_task` 形参与其类型 `DynamicTask` 是同批漏收的第三处死面(二次复审 B1)——setPasses 删除后只剩这个从不读取的私有形参在供养它,而包根 `DynamicTask` 取自 `./types/passes-gate`,故它是**公开的**死类型;随 A1/A2 同判据删除,`runTest` 签名收为 `(workDir, command?)`,类型面钉子同 `PassesGateResult` 手法(`src/__tests__/public-exports.test.ts`);包内模块,不进 `src/index.ts` 导出面;`check-handlers/` 各族处理器统一签名 `(check, context)`,context 即既有注入车辆(`customHandlers` 同形)——`http_*` 族的重试退避等待经 `CheckpointContext.sleep` 注入(工单 #136),缺省 = 真定时器且退避公式与重试次数逐字不变,测试据此断言尝试次数与退避序列而不必真等;新增等待类副作用按同形收进 context,不再内联进处理器
15
15
  - `spec/validator` — SpecValidator
16
- - `project-config-loader` — 项目配置解读 + 约束合并(mergeConstraints 内含 preset 裁剪:presets/ 纯数据 + 未知名回落 standard,禁用到未知 id 经 effective-set 共享筛选器收集(未知 id 不抛错、进 `unknownIds` 供诊断));**退役判定单点 `isConstraintRetired(customDef, disabled)`**(harness#137)= 「禁用 或 custom 条目带 `retired` 元数据」(studio#82 D6 两个落点合成一条规则),`mergeConstraints` 的 custom 追加跳过与 `cli/commands/constraints-retire` 的 already_retired 共用它——后者此前是这条规则的手写复印;`disabled` 由调用方给(merge 侧含 preset 裁剪,retire 侧只看 config.yml),`retired` 按 truthiness 判;同批删掉 `isConstraintEnabled()`/`getConstraintSource()` 两个平行复印(双仓零消费者,生效集判定唯一来源是 mergeConstraints → getEffectiveConstraints);**本模块不碰文件系统**:config.yml 与自定义约束文件的读取在 `constraints/run-env.ts`(ADR-0023 决策 2 撤销了工单 16 的进程级 `rawConfigCache` + mtime 指纹,改为一次运行一份快照);访问器族(`loadRawProjectConfig`/`getGovernanceConfig`/`resolveContextFiles`/`getCapabilitiesMode`)与 `ProjectConfigLoader` 构造器的入参形状 = `RunTarget`(传项目根路径 = 一次性读取,传 RunEnv = 与本 run 其余消费方共用同一份);`getGovernanceConfig` 是全仓唯一允许手写钻取 `config.governance` 的点(工单 84),消费方一律经它或经 `resolveContextFiles`(三态:unconfigured / enabled-empty / enabled,dirs 元素类型在此收口)取数据,不得再自己 cast `Record<string, unknown>`;checker 对前两态一律 skip,自动探测回落只属于 init/扫描类工具流调用方
17
- - `index.ts` barrel 公开面(子路径 `@dommaker/harness/core`,ADR-0003 显式清单、禁 `export *`)——定义取数 `getAllConstraints()` / `getConstraint(id)` / `findConstraintsByTrigger(trigger)`(正本 `constraints/definitions.ts`);判定出口 `checkConstraints()` / `checkConstraint()` / `checkBeforeExecution()` 与未接线默认实例 `constraintChecker`(= `ConstraintChecker.getInstance()`,不写 trace,正本 `constraints/checker.ts`);治理注入段标记常量 `CONSTRAINTS_START_MARKER` / `CONSTRAINTS_END_MARKER` 与 `renderConstraintsSection(constraints, version)`(正本 `constraints/injection-renderer.ts`)、按 trigger 分组渲染 `renderConstraintsByTrigger()`(正本 `constraints/agent-prompt-renderer.ts`);验证器 `CheckpointValidator` / `CSOValidator`(两者私有构造 + `static getInstance()` 单例出口)与 `PassesGate` + 工厂 `createPassesGate(config?)`(`Partial<PassesGateConfig>`,正本 `validators/`)
16
+ - `project-config-loader` — 项目配置解读 + 约束合并(mergeConstraints 内含 preset 裁剪:presets/ 纯数据(键按 severity 命名 errors/warnings,ADR-0029)+ 未知名回落 standard,禁用到未知 id 经 effective-set 共享筛选器收集(未知 id 不抛错、进 `unknownIds` 供诊断));ADR-0029:custom 约束装载(loadCustomConstraints/getCustomConstraints/toConstraint)与退役判定单点 `isConstraintRetired` 已随 custom 面退役移除,retire 的 already_retired 判定只剩 config.yml `enabled:false` 一处;**本模块不碰文件系统**:config.yml 的读取在 `constraints/run-env.ts`(ADR-0023 决策 2 撤销了工单 16 的进程级 `rawConfigCache` + mtime 指纹,改为一次运行一份快照);访问器族(`loadRawProjectConfig`/`getGovernanceConfig`/`resolveContextFiles`/`getCapabilitiesMode`)与 `ProjectConfigLoader` 构造器的入参形状 = `RunTarget`(传项目根路径 = 一次性读取,传 RunEnv = 与本 run 其余消费方共用同一份);`getGovernanceConfig` 是全仓唯一允许手写钻取 `config.governance` 的点(工单 84),消费方一律经它或经 `resolveContextFiles`(三态:unconfigured / enabled-empty / enabled,dirs 元素类型在此收口)取数据,不得再自己 cast `Record<string, unknown>`;checker 对前两态一律 skip,自动探测回落只属于 init/扫描类工具流调用方
17
+ - `index.ts` barrel 公开面(子路径 `@dommaker/harness/core`,ADR-0003 显式清单、禁 `export *`)——定义取数 `getAllConstraints()` / `getConstraint(id)` / `findConstraintsByTrigger(trigger)`(正本 `constraints/definitions.ts`);判定出口 `checkConstraints()` / `checkConstraint()` / `checkBeforeExecution()` 与未接线默认实例 `constraintChecker`(= `ConstraintChecker.getInstance()`,不写 trace,正本 `constraints/checker.ts`);约束数据 `CONSTRAINTS` 单桶(正本 `constraints/definitions.ts`;ADR-0029:注入渲染三件套导出已随文本注入层关停移除);验证器 `CheckpointValidator` / `CSOValidator`(两者私有构造 + `static getInstance()` 单例出口)与 `PassesGate` + 工厂 `createPassesGate(config?)`(`Partial<PassesGateConfig>`,正本 `validators/`)
18
18
 
19
19
  ## 依赖关系
20
20
  - 被所有模块依赖(基础层,无上层依赖)
@@ -23,9 +23,9 @@
23
23
  - 对 `cli/`、`gates/`、`monitoring/` **零值导入**(type-only 允许):方向由 eslint.config.mjs 的 `@typescript-eslint/no-restricted-imports`(error 级)锁死,守卫测试见 `src/__tests__/layering.test.ts`(harness#88;harness#137 起该守卫已参数化成「每个顶层单元声明允许的下行集合」,core 的这条边只是其中一个条目)
24
24
 
25
25
  ## 约定
26
- - 约束定义在 `constraints/definitions/{iron-laws,guidelines,prompts}.ts`,不在运行时代码中定义
27
- - check 层必须带真实 checker(注册表闭环,引用未注册 checker 构建报错)
28
- - Iron Law 违规必须 throw ConstraintViolationError
26
+ - 约束定义在 `constraints/definitions/{iron-laws,guidelines}.ts`(按 severity 分组),不在运行时代码中定义
27
+ - 全部约束 kind='check',必须带真实 checker(注册表闭环,引用未注册 checker 构建报错)
28
+ - severity='error' 违规必须 throw ConstraintViolationError
29
29
  - 上行数据一律注入,不在 core 内 require 上层(harness#88):trace 记录器经 `ConstraintChecker` 构造参数注入(缺省 no-op,`getInstance()`/`constraintChecker` 即未接线的默认实例,只评估不写 trace),真实收集器由组合根接线(CLI check/report、`bootstrapHarness`);CAPABILITIES.md 能力清单计数经 `capabilities-parser` 的 `CapabilityDefinitionSource` 由 cli 侧注入定义表
30
30
  - git **取证**(读判定证据)在 check 链路内只能经 `constraints/git-evidence.ts` 的 adapter 取(#87,适用范围按 ADR-0021 收窄为「check 链路唯一」,不再声称全仓唯一):`context-builder` / `checker` / `CheckEnv` / `validators` 这条判定链内不得出现 `execSync`/`execAsync`/`execFileAsync` 跑 git;`spec/validator` 的 staged 文件列表与 `changedFileNames(staged:true)` 同型,已收口(就地 `createGitEvidence(cwd)`,测试注入 runner);一次 run 一份实例、沿调用链显式传递(CLI check → buildConstraintContext → checkConstraints),memo 既不做成模块级全局也不做成单例字段。**链外站点记名豁免**(语义异质:不要 memo、不要 once 语义、失败处理各异,硬并入会造出宽 interface)——`cli/commands/release.ts`(发布流程编排,非判定证据)、`gates/review.ts` 与 `cli/commands/review.ts`(PR 审查流,gh/日志语义)、`knowledge/import.ts`(导入启发式,失败记 ImportError 降级)。豁免清单外新增 git 站点先回答「在不在判定链内」;`validators/passes-gate.ts` 的 `git diff --name-only` 站点已随 ADR-0022 删 `checkTestFileChanges` 消失,判定链内当下无站外 git 调用点
31
31
 
@@ -11,7 +11,7 @@
11
11
  import * as fs from 'fs';
12
12
  import * as os from 'os';
13
13
  import * as path from 'path';
14
- import { IRON_LAWS, GUIDELINES } from '../definitions';
14
+ import { CONSTRAINTS } from '../definitions';
15
15
  import {
16
16
  checkCapabilityCounts,
17
17
  updateCapabilityCounts,
@@ -56,10 +56,10 @@ describe('能力清单计数(注入定义源,harness#88)', () => {
56
56
  expect(out).toContain('Quality Gates (4)');
57
57
  });
58
58
 
59
- it('Iron Laws / Guidelines 计数仍来自 core 自身定义表(同层,不注入)', () => {
60
- const out = updateCapabilityCounts('Iron Laws (999)\nGuidelines (999)\n', SOURCE);
61
- expect(out).toContain(`Iron Laws (${Object.keys(IRON_LAWS).length})`);
62
- expect(out).toContain(`Guidelines (${Object.keys(GUIDELINES).length})`);
59
+ it('errors / warnings 计数仍来自 core 自身定义表(同层,不注入)', () => {
60
+ const out = updateCapabilityCounts('errors (999)\nwarnings (999)\n', SOURCE);
61
+ expect(out).toContain(`errors (${Object.values(CONSTRAINTS).filter(c => c.severity === 'error').length})`);
62
+ expect(out).toContain(`warnings (${Object.values(CONSTRAINTS).filter(c => c.severity === 'warning').length})`);
63
63
  });
64
64
 
65
65
  it('不触碰文档中不存在的计数行', () => {
@@ -39,28 +39,33 @@ describe('collectConstraints(收集模式,不抛)', () => {
39
39
 
40
40
  it('违规铁律如实进 ironLaws:satisfied=false 且 passed=false', async () => {
41
41
  const result = await checker.collectConstraints(VIOLATING);
42
- const violated = result.ironLaws.find((r: ConstraintResult) => r.id === 'no_completion_without_verification');
42
+ const violated = result.errors.find((r: ConstraintResult) => r.id === 'no_completion_without_verification');
43
43
  expect(violated).toBeDefined();
44
44
  expect(violated!.satisfied).toBe(false);
45
45
  expect(result.passed).toBe(false);
46
46
  });
47
47
 
48
48
  it('首个违规不截断:触发域内后续铁律照常评估', async () => {
49
- const result = await checker.collectConstraints(VIOLATING);
50
- const ids = result.ironLaws.map((r: ConstraintResult) => r.id);
51
- expect(ids).toContain('incremental_progress');
52
- expect(ids).toContain('no_implementation_without_requirement');
49
+ // 首条铁律(no_completion_without_verification)违规后,extraTriggers 带入的
50
+ // no_test_simplification 仍须出现在收集结果里
51
+ const result = await checker.collectConstraints({
52
+ ...VIOLATING,
53
+ extraTriggers: ['test_creation'],
54
+ });
55
+ const ids = result.errors.map((r: ConstraintResult) => r.id);
56
+ expect(ids).toContain('no_completion_without_verification');
57
+ expect(ids).toContain('no_test_simplification');
53
58
  });
54
59
 
55
60
  it('guidelines 照常执行,warningCount 与不满意条目同口径', async () => {
56
61
  const result = await checker.collectConstraints(VIOLATING);
57
- expect(result.guidelines.length).toBeGreaterThan(0);
58
- expect(result.warningCount).toBe(result.guidelines.filter((g: ConstraintResult) => !g.satisfied).length);
62
+ expect(result.warnings.length).toBeGreaterThan(0);
63
+ expect(result.warningCount).toBe(result.warnings.filter((g: ConstraintResult) => !g.satisfied).length);
59
64
  });
60
65
 
61
66
  it('干净上下文对照:passed=true、ironLaws 全满意', async () => {
62
67
  const result = await checker.collectConstraints(CLEAN);
63
68
  expect(result.passed).toBe(true);
64
- expect(result.ironLaws.every((r: ConstraintResult) => r.satisfied)).toBe(true);
69
+ expect(result.errors.every((r: ConstraintResult) => r.satisfied)).toBe(true);
65
70
  });
66
71
  });
@@ -22,7 +22,7 @@ import {
22
22
  } from '../git-evidence';
23
23
  import { buildConstraintContext } from '../context-builder';
24
24
  import { ConstraintChecker } from '../checker';
25
- import { GUIDELINES } from '../definitions';
25
+ import { CONSTRAINTS } from '../definitions';
26
26
  import type { MergedConstraintsConfig } from '../../../types/project-config';
27
27
 
28
28
  /** 建一个真 git fixture:committed src/existing.ts + 一个 staged 修改 */
@@ -181,10 +181,7 @@ describe('git 证据适配器(#87)', () => {
181
181
  const context = await buildConstraintContext({ projectPath: dir, staged: true, evidence });
182
182
  // capability_sync 经 CheckEnv.stagedDiffNames() 取同一份 git 证据
183
183
  const merged: MergedConstraintsConfig = {
184
- ironLaws: {},
185
- guidelines: { capability_sync: GUIDELINES['capability_sync'] },
186
- prompts: {},
187
- custom: [],
184
+ constraints: { capability_sync: CONSTRAINTS['capability_sync'] },
188
185
  disabled: [],
189
186
  unknownIds: [],
190
187
  };
@@ -193,8 +190,8 @@ describe('git 证据适配器(#87)', () => {
193
190
  const runResult = await checker.checkConstraints(context, merged, evidence);
194
191
 
195
192
  // capability_sync 确实被评估(非 skip)→ 它经 env.stagedDiffNames() 取过证据
196
- expect(runResult.guidelines.map(r => r.id)).toEqual(['capability_sync']);
197
- expect(runResult.guidelines[0].skipped).toBeUndefined();
193
+ expect(runResult.warnings.map(r => r.id)).toEqual(['capability_sync']);
194
+ expect(runResult.warnings[0].skipped).toBeUndefined();
198
195
  // 两个消费者都从注入的这一份实例取证据(checker 没有另起炉灶)
199
196
  expect(calls).toContain('context:changedFileNames');
200
197
  expect(calls).toContain('checker:changedFileNames');
@@ -1,32 +1,27 @@
1
1
  /**
2
- * injection-writer 旁测(架构评审候选1,ADR-0011
2
+ * injection-writer 旁测(架构评审候选1,ADR-0011;ADR-0029 收窄)
3
3
  *
4
- * 测试面 = 纯函数(字符串进、字符串出)+ 落点路由(真实 fs 临时目录)。
5
- * 幂等/半标记守护/两种尾部形状/读写路由优先级全部在此测一次;
6
- * 消费方(init/retire/drift)只测各自语义(init-injection.test 行为面)。
4
+ * 测试面 = 纯函数(字符串进、字符串出)+ 治理正本探测。
5
+ * 幂等/半标记守护/两种尾部形状在此测一次。
6
+ * ADR-0029:注入段落点路由(resolveInjectionTarget/resolveGovernanceLanding)
7
+ * 已随文本注入层关停删除,相关用例移除。
7
8
  */
8
9
 
9
- import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
10
- import * as fs from 'fs';
11
- import * as path from 'path';
10
+ import { describe, it, expect } from '@jest/globals';
12
11
  import {
13
12
  replaceStandaloneRange,
14
13
  replaceEnclosedRange,
15
14
  cutMarkerBlock,
16
- resolveInjectionTarget,
17
- resolveGovernanceLanding,
18
15
  GOVERNANCE_HEADING,
19
16
  hasGovernanceContract,
20
17
  countGovernanceHeadings,
21
18
  hasPreserveBlock,
22
19
  } from '../injection-writer';
23
- import {
24
- CONSTRAINTS_START_MARKER as CS,
25
- CONSTRAINTS_END_MARKER as CE,
26
- } from '../injection-renderer';
27
20
 
28
21
  const B = '<!-- B -->';
29
22
  const E = '<!-- E -->';
23
+ const CS = '<!-- CS -->';
24
+ const CE = '<!-- CE -->';
30
25
 
31
26
  describe('replaceStandaloneRange — 独立成节形状', () => {
32
27
  it('替换含标记区间;尾文多换行折叠为恰好一个空行', () => {
@@ -97,54 +92,6 @@ describe('cutMarkerBlock', () => {
97
92
  });
98
93
  });
99
94
 
100
- describe('落点路由(读写两侧,studio #307/#302)', () => {
101
- let dir: string;
102
-
103
- beforeEach(() => {
104
- dir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-inj-writer-'));
105
- });
106
- afterEach(() => fs.rmSync(dir, { recursive: true, force: true }));
107
-
108
- const write = (name: string, content: string) =>
109
- fs.writeFileSync(path.join(dir, name), content, 'utf-8');
110
-
111
- it('resolveInjectionTarget:CLAUDE.md 完整标记段优先于 AGENTS.md', () => {
112
- write('CLAUDE.md', `x\n${CS}\nbody\n${CE}\n`);
113
- write('AGENTS.md', `<!-- PRESERVE:governance -->\n${CS}\nbody2\n${CE}\n`);
114
- expect(resolveInjectionTarget(dir)?.file).toBe('CLAUDE.md');
115
- });
116
-
117
- it('CLAUDE.md 标记残缺(单边)不算落点 → 落 AGENTS.md', () => {
118
- write('CLAUDE.md', `x\n${CS}\nbroken\n`);
119
- write('AGENTS.md', `${CS}\nbody\n${CE}\n`);
120
- expect(resolveInjectionTarget(dir)?.file).toBe('AGENTS.md');
121
- });
122
-
123
- it('两处均无完整标记段 → null(未注入)', () => {
124
- write('CLAUDE.md', '# 无约束标记\n');
125
- expect(resolveInjectionTarget(dir)).toBeNull();
126
- });
127
-
128
- it('resolveGovernanceLanding:CLAUDE.md 有标记或有旧版 Governance Rules 块 → claude-md', () => {
129
- write('CLAUDE.md', `${CS}\nbody\n${CE}\n`);
130
- expect(resolveGovernanceLanding(dir).target).toBe('claude-md');
131
- write('CLAUDE.md', '# CLAUDE.md\n\n## Governance Rules\n\n手写\n');
132
- expect(resolveGovernanceLanding(dir).target).toBe('claude-md');
133
- });
134
-
135
- it('CLAUDE.md 与治理无关 / 不存在 → agents-md', () => {
136
- write('CLAUDE.md', '# CLAUDE.md\n\n@AGENTS.md\n');
137
- expect(resolveGovernanceLanding(dir).target).toBe('agents-md');
138
- fs.rmSync(path.join(dir, 'CLAUDE.md'));
139
- expect(resolveGovernanceLanding(dir).target).toBe('agents-md');
140
- });
141
-
142
- it('落点路由消费共享宽松谓词:双空格标题变体也路由 claude-md(fail-open)', () => {
143
- write('CLAUDE.md', '# CLAUDE.md\n\n## Governance Rules\n\n手写\n');
144
- expect(resolveGovernanceLanding(dir).target).toBe('claude-md');
145
- });
146
- });
147
-
148
95
  describe('治理契约探测共享符号(#83,ADR-0011 补注:收口范围含探测器)', () => {
149
96
  it('GOVERNANCE_HEADING 是标题字面量单一来源', () => {
150
97
  expect(GOVERNANCE_HEADING).toBe('## Governance Rules');
@@ -47,7 +47,7 @@ function writeTraces(projectPath: string, lines: string[]): void {
47
47
  }
48
48
 
49
49
  const traceLine = (id: string, result: 'pass' | 'fail'): string =>
50
- JSON.stringify({ constraintId: id, level: 'iron_law', timestamp: 1, result });
50
+ JSON.stringify({ constraintId: id, severity: 'error', timestamp: 1, result });
51
51
 
52
52
  const CORRUPT = '{"constraintId":"broken"';
53
53
 
@@ -125,18 +125,11 @@ describe('createRunEnv', () => {
125
125
  });
126
126
  });
127
127
 
128
- describe('配置一次装载 — rawConfig / customConstraints(ADR-0023 决策 2)', () => {
128
+ describe('配置一次装载 — rawConfig(ADR-0023 决策 2)', () => {
129
129
  let dir: string;
130
130
 
131
131
  const writeConfig = (content: string) => writeProjectConfig(dir, content);
132
132
 
133
- /** 落 `.harness/<fileName>`(夹具可能还没有 .harness 目录,先补上) */
134
- const writeHarnessFile = (fileName: string, body: string) => {
135
- const target = path.join(dir, '.harness', fileName);
136
- fs.mkdirSync(path.dirname(target), { recursive: true });
137
- fs.writeFileSync(target, body, 'utf-8');
138
- };
139
-
140
133
  beforeEach(() => {
141
134
  dir = createProjectFixture({ name: 'run-env-config' });
142
135
  });
@@ -191,27 +184,6 @@ describe('配置一次装载 — rawConfig / customConstraints(ADR-0023 决策
191
184
  expect(resolveRunEnv().projectPath).toBe(process.cwd());
192
185
  });
193
186
 
194
- it('customConstraints 取 custom_constraints 段,run 内同名文件至多读一次', () => {
195
- const write = (body: string) => writeHarnessFile('custom-constraints.yml', body);
196
- write('custom_constraints:\n a:\n level: iron_law\n rule: 只此一份\n');
197
- const env = createRunEnv(dir);
198
- expect(Object.keys(env.customConstraints('custom-constraints.yml'))).toEqual(['a']);
199
-
200
- write('custom_constraints:\n b:\n level: guideline\n rule: 后来的\n');
201
- expect(Object.keys(env.customConstraints('custom-constraints.yml'))).toEqual(['a']);
202
- });
203
-
204
- it('customConstraints:文件缺失 = {};不同文件名各自 memo', () => {
205
- const env = createRunEnv(dir);
206
- expect(env.customConstraints('missing.yml')).toEqual({});
207
-
208
- writeHarnessFile(
209
- 'other.yml',
210
- 'custom_constraints:\n c:\n level: guideline\n rule: r\n'
211
- );
212
- expect(Object.keys(env.customConstraints('other.yml'))).toEqual(['c']);
213
- expect(env.customConstraints('missing.yml')).toEqual({});
214
- });
215
187
  });
216
188
 
217
189
  describe('context-builder 经注入的观察面取证据', () => {
@@ -235,7 +207,6 @@ describe('context-builder 经注入的观察面取证据', () => {
235
207
  },
236
208
  sourceRoots: () => ['src'],
237
209
  rawConfig: () => undefined,
238
- customConstraints: () => ({}),
239
210
  capabilities: () => undefined,
240
211
  };
241
212
  return { env, limits };
@@ -251,8 +222,8 @@ describe('context-builder 经注入的观察面取证据', () => {
251
222
 
252
223
  it('hasFailingTest / hasVerificationEvidence 各按自己的窗口向 env 取数,不另开读', async () => {
253
224
  const { env, limits } = countingEnv([
254
- { constraintId: 'p', level: 'iron_law', timestamp: 1, result: 'pass' },
255
- { constraintId: 'f', level: 'iron_law', timestamp: 2, result: 'fail' },
225
+ { constraintId: 'p', severity: 'error', timestamp: 1, result: 'pass' },
226
+ { constraintId: 'f', severity: 'error', timestamp: 2, result: 'fail' },
256
227
  ]);
257
228
  const ctx = await buildConstraintContext({
258
229
  projectPath: dir,
@@ -268,7 +239,7 @@ describe('context-builder 经注入的观察面取证据', () => {
268
239
 
269
240
  it('窗口里没有 pass 时验证证据为 false(合并读取不改判定)', async () => {
270
241
  const { env } = countingEnv([
271
- { constraintId: 'f', level: 'iron_law', timestamp: 2, result: 'fail' },
242
+ { constraintId: 'f', severity: 'error', timestamp: 2, result: 'fail' },
272
243
  ]);
273
244
  const ctx = await buildConstraintContext({
274
245
  projectPath: dir,
@@ -51,7 +51,7 @@ describe('trace 记录器构造注入(harness#88)', () => {
51
51
  await checker.checkConstraints(context());
52
52
 
53
53
  expect(records.map(r => r.constraintId)).toEqual(
54
- expect.arrayContaining(['incremental_progress', 'no_implementation_without_requirement'])
54
+ expect.arrayContaining(['no_completion_without_verification', 'no_hardcoded_credentials'])
55
55
  );
56
56
  expect(records.every(r => ['pass', 'fail', 'skip'].includes(r.result))).toBe(true);
57
57
  });