clinkx 1.0.0 → 1.1.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 (331) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +72 -0
  3. package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
  4. package/clinkx-workflows/dist/chairman-knobs.js +27 -0
  5. package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
  6. package/clinkx-workflows/dist/clink-client.d.ts +10 -0
  7. package/clinkx-workflows/dist/clink-client.js +39 -1
  8. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  9. package/clinkx-workflows/dist/config.d.ts +29 -1
  10. package/clinkx-workflows/dist/config.js +35 -2
  11. package/clinkx-workflows/dist/config.js.map +1 -1
  12. package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
  13. package/clinkx-workflows/dist/context-preflight.js.map +1 -1
  14. package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
  15. package/clinkx-workflows/dist/council-execution-policy.js +110 -0
  16. package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
  17. package/clinkx-workflows/dist/council-expander.d.ts +9 -8
  18. package/clinkx-workflows/dist/council-expander.js +18 -9
  19. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  20. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
  21. package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
  22. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
  23. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
  24. package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
  25. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
  26. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
  27. package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
  28. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
  29. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
  30. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
  31. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
  32. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
  33. package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
  34. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
  35. package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
  36. package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
  37. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
  38. package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
  39. package/clinkx-workflows/dist/dashboard/index.js +4 -0
  40. package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
  41. package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
  42. package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
  43. package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
  44. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
  45. package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
  46. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
  47. package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
  48. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
  49. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
  51. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
  52. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
  54. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
  55. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
  56. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
  57. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
  58. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
  59. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
  60. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
  61. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
  62. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
  63. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
  64. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
  65. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
  66. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
  67. package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
  68. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
  69. package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
  70. package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
  71. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
  72. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
  73. package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
  74. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
  75. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
  76. package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
  77. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
  78. package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
  79. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
  80. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
  81. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
  82. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
  83. package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
  84. package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
  85. package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
  86. package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
  87. package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
  88. package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
  89. package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
  90. package/clinkx-workflows/dist/delegate-signature.js +14 -0
  91. package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
  92. package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
  93. package/clinkx-workflows/dist/delegate-validate.js +51 -0
  94. package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
  95. package/clinkx-workflows/dist/engine.d.ts +113 -3
  96. package/clinkx-workflows/dist/engine.js +476 -80
  97. package/clinkx-workflows/dist/engine.js.map +1 -1
  98. package/clinkx-workflows/dist/errors.d.ts +22 -2
  99. package/clinkx-workflows/dist/errors.js +30 -2
  100. package/clinkx-workflows/dist/errors.js.map +1 -1
  101. package/clinkx-workflows/dist/fidelity.d.ts +47 -1
  102. package/clinkx-workflows/dist/fidelity.js +54 -1
  103. package/clinkx-workflows/dist/fidelity.js.map +1 -1
  104. package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
  105. package/clinkx-workflows/dist/fingerprint.js +32 -9
  106. package/clinkx-workflows/dist/fingerprint.js.map +1 -1
  107. package/clinkx-workflows/dist/index.js +53 -1
  108. package/clinkx-workflows/dist/index.js.map +1 -1
  109. package/clinkx-workflows/dist/logger.d.ts +1 -1
  110. package/clinkx-workflows/dist/logger.js +17 -3
  111. package/clinkx-workflows/dist/logger.js.map +1 -1
  112. package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
  113. package/clinkx-workflows/dist/monotonic-clock.js +22 -0
  114. package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
  115. package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
  116. package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
  117. package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
  118. package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
  119. package/clinkx-workflows/dist/roster-contract.js +180 -0
  120. package/clinkx-workflows/dist/roster-contract.js.map +1 -0
  121. package/clinkx-workflows/dist/rosters.d.ts +14 -0
  122. package/clinkx-workflows/dist/rosters.js +169 -0
  123. package/clinkx-workflows/dist/rosters.js.map +1 -0
  124. package/clinkx-workflows/dist/run-health.d.ts +7 -0
  125. package/clinkx-workflows/dist/run-health.js +25 -0
  126. package/clinkx-workflows/dist/run-health.js.map +1 -0
  127. package/clinkx-workflows/dist/schema.d.ts +64 -0
  128. package/clinkx-workflows/dist/schema.js +24 -0
  129. package/clinkx-workflows/dist/schema.js.map +1 -1
  130. package/clinkx-workflows/dist/server.js +2 -0
  131. package/clinkx-workflows/dist/server.js.map +1 -1
  132. package/clinkx-workflows/dist/shutdown.d.ts +15 -2
  133. package/clinkx-workflows/dist/shutdown.js +60 -16
  134. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  135. package/clinkx-workflows/dist/state-schema.d.ts +23 -0
  136. package/clinkx-workflows/dist/state.d.ts +5 -0
  137. package/clinkx-workflows/dist/state.js +128 -21
  138. package/clinkx-workflows/dist/state.js.map +1 -1
  139. package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
  140. package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
  141. package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
  142. package/clinkx-workflows/dist/types.d.ts +6 -0
  143. package/clinkx-workflows/dist/validators/council.d.ts +50 -50
  144. package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
  145. package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
  146. package/clinkx-workflows/dist/workflow-tools.js +760 -168
  147. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  148. package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
  149. package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
  150. package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
  151. package/clinkx-workflows/templates/council/chairman.txt +3 -0
  152. package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
  153. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
  154. package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
  155. package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
  156. package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
  157. package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
  158. package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
  159. package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
  160. package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
  161. package/clinkx-workflows/templates/council/member.txt +3 -0
  162. package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
  163. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
  164. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
  165. package/clinkx-workflows/templates/council/reviewer.txt +3 -0
  166. package/clinkx-workflows/workflows/council-answer.yaml +3 -0
  167. package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
  168. package/clinkx-workflows/workflows/council-debug.yaml +3 -0
  169. package/clinkx-workflows/workflows/council-default.yaml +7 -0
  170. package/clinkx-workflows/workflows/council-discover.yaml +3 -0
  171. package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
  172. package/conf/adapters/agy.json +9 -2
  173. package/conf/adapters/claude.json +7 -0
  174. package/conf/adapters/codex.json +7 -0
  175. package/conf/adapters/glm.json +19 -3
  176. package/conf/adapters/grok.json +9 -2
  177. package/conf/adapters/hapi/agy.json +9 -2
  178. package/conf/adapters/hapi/claude.json +7 -0
  179. package/conf/adapters/hapi/codex.json +7 -0
  180. package/conf/adapters/hapi/glm.json +20 -4
  181. package/conf/rosters/default-council.json +21 -0
  182. package/dist/activity-journal.d.ts +17 -3
  183. package/dist/activity-journal.js +404 -79
  184. package/dist/activity-journal.js.map +1 -1
  185. package/dist/codex/agent-tools.js +36 -0
  186. package/dist/codex/agent-tools.js.map +1 -1
  187. package/dist/codex/app-server-client.d.ts +3 -0
  188. package/dist/codex/app-server-client.js +11 -0
  189. package/dist/codex/app-server-client.js.map +1 -1
  190. package/dist/codex/app-server-registry.d.ts +18 -0
  191. package/dist/codex/app-server-registry.js +88 -2
  192. package/dist/codex/app-server-registry.js.map +1 -1
  193. package/dist/codex/client-types.d.ts +7 -1
  194. package/dist/codex/protocol.d.ts +4 -4
  195. package/dist/config.d.ts +220 -23
  196. package/dist/config.js +104 -1
  197. package/dist/config.js.map +1 -1
  198. package/dist/context-builder/bundle.d.ts +13 -0
  199. package/dist/context-builder/bundle.js +46 -0
  200. package/dist/context-builder/bundle.js.map +1 -1
  201. package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
  202. package/dist/context-builder/delegate-finalizer-effective.js +82 -0
  203. package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
  204. package/dist/context-builder/delegate-packet.d.ts +18 -0
  205. package/dist/context-builder/delegate-packet.js +73 -9
  206. package/dist/context-builder/delegate-packet.js.map +1 -1
  207. package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
  208. package/dist/context-builder/delegate-reconstruction.js +110 -41
  209. package/dist/context-builder/delegate-reconstruction.js.map +1 -1
  210. package/dist/context-builder/delegate-validation.d.ts +31 -0
  211. package/dist/context-builder/delegate-validation.js +87 -10
  212. package/dist/context-builder/delegate-validation.js.map +1 -1
  213. package/dist/context-builder/delegate.d.ts +9 -0
  214. package/dist/context-builder/delegate.js +210 -59
  215. package/dist/context-builder/delegate.js.map +1 -1
  216. package/dist/context-builder/diagnostics.d.ts +1 -1
  217. package/dist/context-builder/diagnostics.js.map +1 -1
  218. package/dist/context-builder/discovery-progress.d.ts +25 -0
  219. package/dist/context-builder/discovery-progress.js +64 -0
  220. package/dist/context-builder/discovery-progress.js.map +1 -0
  221. package/dist/context-builder/discovery-watchdog.d.ts +15 -0
  222. package/dist/context-builder/discovery-watchdog.js +39 -0
  223. package/dist/context-builder/discovery-watchdog.js.map +1 -0
  224. package/dist/context-builder/discovery.d.ts +7 -0
  225. package/dist/context-builder/discovery.js +119 -7
  226. package/dist/context-builder/discovery.js.map +1 -1
  227. package/dist/context-builder/external-gaps.d.ts +73 -0
  228. package/dist/context-builder/external-gaps.js +248 -0
  229. package/dist/context-builder/external-gaps.js.map +1 -0
  230. package/dist/context-builder/globs.d.ts +1 -0
  231. package/dist/context-builder/globs.js +15 -0
  232. package/dist/context-builder/globs.js.map +1 -1
  233. package/dist/context-builder/handler.d.ts +42 -1
  234. package/dist/context-builder/handler.js +690 -62
  235. package/dist/context-builder/handler.js.map +1 -1
  236. package/dist/context-builder/invocation-sidecar.d.ts +41 -0
  237. package/dist/context-builder/invocation-sidecar.js +189 -0
  238. package/dist/context-builder/invocation-sidecar.js.map +1 -0
  239. package/dist/context-builder/lifecycle-contract.d.ts +51 -51
  240. package/dist/context-builder/mutation-lock.d.ts +32 -0
  241. package/dist/context-builder/mutation-lock.js +321 -0
  242. package/dist/context-builder/mutation-lock.js.map +1 -0
  243. package/dist/context-builder/review-coverage.d.ts +32 -0
  244. package/dist/context-builder/review-coverage.js +130 -23
  245. package/dist/context-builder/review-coverage.js.map +1 -1
  246. package/dist/context-builder/schema.d.ts +76 -1
  247. package/dist/context-builder/schema.js +21 -1
  248. package/dist/context-builder/schema.js.map +1 -1
  249. package/dist/context-builder/secrets.d.ts +31 -0
  250. package/dist/context-builder/secrets.js +865 -83
  251. package/dist/context-builder/secrets.js.map +1 -1
  252. package/dist/context-builder/status.d.ts +1 -1
  253. package/dist/context-builder/status.js +1 -0
  254. package/dist/context-builder/status.js.map +1 -1
  255. package/dist/context-builder/storage.d.ts +29 -5
  256. package/dist/context-builder/storage.js +311 -236
  257. package/dist/context-builder/storage.js.map +1 -1
  258. package/dist/context-builder/versions.d.ts +19 -4
  259. package/dist/context-builder/versions.js +19 -4
  260. package/dist/context-builder/versions.js.map +1 -1
  261. package/dist/context-builder/workflow-runner.d.ts +6 -0
  262. package/dist/context-builder/workflow-runner.js +363 -173
  263. package/dist/context-builder/workflow-runner.js.map +1 -1
  264. package/dist/dashboard-adapter-catalog.js +7 -0
  265. package/dist/dashboard-adapter-catalog.js.map +1 -1
  266. package/dist/dashboard-cli.d.ts +2 -0
  267. package/dist/dashboard-cli.js +40 -2
  268. package/dist/dashboard-cli.js.map +1 -1
  269. package/dist/dashboard-companion/client.d.ts +1 -0
  270. package/dist/dashboard-companion/client.js +22 -3
  271. package/dist/dashboard-companion/client.js.map +1 -1
  272. package/dist/dashboard-companion/protocol.js +4 -1
  273. package/dist/dashboard-companion/protocol.js.map +1 -1
  274. package/dist/dashboard-gateway/index.d.ts +1 -0
  275. package/dist/dashboard-gateway/index.js +33 -2
  276. package/dist/dashboard-gateway/index.js.map +1 -1
  277. package/dist/handler.js +5 -1
  278. package/dist/handler.js.map +1 -1
  279. package/dist/index.d.ts +18 -1
  280. package/dist/index.js +145 -7
  281. package/dist/index.js.map +1 -1
  282. package/dist/logger.d.ts +1 -1
  283. package/dist/logger.js +18 -3
  284. package/dist/logger.js.map +1 -1
  285. package/dist/parsers/agy-stream-json.d.ts +14 -0
  286. package/dist/parsers/agy-stream-json.js +172 -0
  287. package/dist/parsers/agy-stream-json.js.map +1 -0
  288. package/dist/parsers/extract.js +5 -1
  289. package/dist/parsers/extract.js.map +1 -1
  290. package/dist/parsers/observed-invocation.d.ts +20 -0
  291. package/dist/parsers/observed-invocation.js +42 -0
  292. package/dist/parsers/observed-invocation.js.map +1 -0
  293. package/dist/pipeline.d.ts +13 -0
  294. package/dist/pipeline.js +60 -10
  295. package/dist/pipeline.js.map +1 -1
  296. package/dist/progress-report/lock.d.ts +74 -0
  297. package/dist/progress-report/lock.js +343 -0
  298. package/dist/progress-report/lock.js.map +1 -0
  299. package/dist/progress-report/schema.d.ts +808 -0
  300. package/dist/progress-report/schema.js +119 -0
  301. package/dist/progress-report/schema.js.map +1 -0
  302. package/dist/progress-report/store.d.ts +66 -0
  303. package/dist/progress-report/store.js +766 -0
  304. package/dist/progress-report/store.js.map +1 -0
  305. package/dist/progress-report/tools.d.ts +20 -0
  306. package/dist/progress-report/tools.js +141 -0
  307. package/dist/progress-report/tools.js.map +1 -0
  308. package/dist/prompt-mode.d.ts +1 -1
  309. package/dist/prompt-mode.js +7 -2
  310. package/dist/prompt-mode.js.map +1 -1
  311. package/dist/runner.d.ts +7 -0
  312. package/dist/runner.js +10 -3
  313. package/dist/runner.js.map +1 -1
  314. package/dist/safety.d.ts +50 -0
  315. package/dist/safety.js +182 -1
  316. package/dist/safety.js.map +1 -1
  317. package/dist/schema.d.ts +8 -0
  318. package/dist/schema.js +20 -0
  319. package/dist/schema.js.map +1 -1
  320. package/dist/server.d.ts +6 -1
  321. package/dist/server.js +94 -8
  322. package/dist/server.js.map +1 -1
  323. package/dist/session-log-stream.d.ts +1 -1
  324. package/dist/session-log-stream.js +280 -33
  325. package/dist/session-log-stream.js.map +1 -1
  326. package/dist/stdio-activity.d.ts +28 -0
  327. package/dist/stdio-activity.js +72 -0
  328. package/dist/stdio-activity.js.map +1 -0
  329. package/package.json +2 -1
  330. package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
  331. package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
@@ -0,0 +1,321 @@
1
+ import { lstat as fsLstat, open as fsOpen, rm as fsRm, } from "node:fs/promises";
2
+ import { hostname } from "node:os";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
+ import { ExecutionError } from "../errors.js";
5
+ import { validateCallerRunId } from "./run-id.js";
6
+ const CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX = ".mutation.lock";
7
+ const CONTEXT_BUILDER_MUTATION_LOCK_RECOVERY_SUFFIX = ".recovery";
8
+ const CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS = 1_000;
9
+ const CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS = 10;
10
+ const CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS = 10 * 60 * 1_000;
11
+ /**
12
+ * Serialize cooperative mutations of one context_builder run. The lock lives
13
+ * beside the run directory so it also survives removal of that directory.
14
+ */
15
+ export async function withContextBuilderMutationLock(target, ops, callback, options = {}) {
16
+ const lockPath = mutationLockPathForTarget(target);
17
+ const acquired = await acquireContextBuilderMutationLock(lockPath, ops, options);
18
+ try {
19
+ return await callback();
20
+ }
21
+ finally {
22
+ await removeMutationLockIfCurrent(lockPath, acquired.fingerprint, ops);
23
+ await acquired.handle.close().catch(() => { });
24
+ }
25
+ }
26
+ async function acquireContextBuilderMutationLock(lockPath, ops, options) {
27
+ const open = ops.open ?? fsOpen;
28
+ const maxAttempts = resolveMutationLockAttempts(options.maxAttempts);
29
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
30
+ try {
31
+ const handle = await open(lockPath, "wx", 0o600);
32
+ try {
33
+ await writeMutationLockMetadata(handle, options.ownerNonce);
34
+ return {
35
+ handle,
36
+ fingerprint: await fingerprintOpenHandle(handle),
37
+ };
38
+ }
39
+ catch (error) {
40
+ const fingerprint = await fingerprintOpenHandle(handle).catch(() => undefined);
41
+ if (fingerprint !== undefined) {
42
+ await removeMutationLockIfCurrent(lockPath, fingerprint, ops);
43
+ }
44
+ await handle.close().catch(() => { });
45
+ throw error;
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (isNodeError(error) && error.code === "EEXIST") {
50
+ await options.onContention?.();
51
+ if (options.canRecoverStale !== undefined
52
+ && await breakStaleMutationLock(lockPath, ops, options.canRecoverStale, options.beforeStaleRemoval)) {
53
+ continue;
54
+ }
55
+ await delay(CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS);
56
+ continue;
57
+ }
58
+ throw error;
59
+ }
60
+ }
61
+ throw new ExecutionError("context_builder timed out waiting for the run mutation lock");
62
+ }
63
+ function resolveMutationLockAttempts(value) {
64
+ if (value === undefined) {
65
+ return CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS;
66
+ }
67
+ if (!Number.isSafeInteger(value) || value <= 0) {
68
+ throw new ExecutionError("context_builder mutation lock attempts must be a positive integer");
69
+ }
70
+ return value;
71
+ }
72
+ function mutationLockPathForTarget(target) {
73
+ validateCallerRunId(target.runId);
74
+ const resolvedRunsDir = resolve(target.runsDir);
75
+ const resolvedRunDir = resolve(target.runDir);
76
+ if (!isPathInsideDirectory(resolvedRunDir, resolvedRunsDir)) {
77
+ throw new ExecutionError("context_builder refused mutation lock outside runs directory");
78
+ }
79
+ return join(resolvedRunsDir, `.${target.runId}${CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX}`);
80
+ }
81
+ async function writeMutationLockMetadata(handle, ownerNonce) {
82
+ const metadata = {
83
+ pid: process.pid,
84
+ hostname: hostname(),
85
+ created_at: new Date().toISOString(),
86
+ ...(ownerNonce == null ? {} : { owner_nonce: ownerNonce }),
87
+ };
88
+ await handle.writeFile(`${JSON.stringify(metadata)}\n`, { encoding: "utf8" });
89
+ }
90
+ async function breakStaleMutationLock(lockPath, ops, canRecoverStale, beforeStaleRemoval) {
91
+ const candidate = await observeMutationLock(lockPath, ops);
92
+ if (candidate === undefined) {
93
+ return false;
94
+ }
95
+ try {
96
+ if (!isRecoverableStaleMutationLock(candidate)) {
97
+ return false;
98
+ }
99
+ }
100
+ finally {
101
+ await candidate.handle.close().catch(() => { });
102
+ }
103
+ const recoveryGate = await tryAcquireMutationLockRecoveryGate(lockPath, ops);
104
+ if (recoveryGate === undefined) {
105
+ return false;
106
+ }
107
+ try {
108
+ const observed = await observeMutationLock(lockPath, ops);
109
+ if (observed === undefined) {
110
+ return false;
111
+ }
112
+ try {
113
+ if (!isRecoverableStaleMutationLock(observed)) {
114
+ return false;
115
+ }
116
+ await beforeStaleRemoval?.();
117
+ if (!await canRecoverStale()) {
118
+ return false;
119
+ }
120
+ return await removeMutationLockIfCurrent(lockPath, observed.fingerprint, ops);
121
+ }
122
+ finally {
123
+ await observed.handle.close().catch(() => { });
124
+ }
125
+ }
126
+ finally {
127
+ await removeMutationLockIfCurrent(`${lockPath}${CONTEXT_BUILDER_MUTATION_LOCK_RECOVERY_SUFFIX}`, recoveryGate.fingerprint, ops);
128
+ await recoveryGate.handle.close().catch(() => { });
129
+ }
130
+ }
131
+ async function tryAcquireMutationLockRecoveryGate(lockPath, ops) {
132
+ const gatePath = `${lockPath}${CONTEXT_BUILDER_MUTATION_LOCK_RECOVERY_SUFFIX}`;
133
+ const open = ops.open ?? fsOpen;
134
+ let handle;
135
+ try {
136
+ handle = await open(gatePath, "wx", 0o600);
137
+ }
138
+ catch (error) {
139
+ if (isNodeError(error) && error.code === "EEXIST") {
140
+ return undefined;
141
+ }
142
+ throw error;
143
+ }
144
+ try {
145
+ await writeMutationLockMetadata(handle, undefined);
146
+ return {
147
+ handle,
148
+ fingerprint: await fingerprintOpenHandle(handle),
149
+ };
150
+ }
151
+ catch (error) {
152
+ const fingerprint = await fingerprintOpenHandle(handle).catch(() => undefined);
153
+ if (fingerprint !== undefined) {
154
+ await removeMutationLockIfCurrent(gatePath, fingerprint, ops);
155
+ }
156
+ await handle.close().catch(() => { });
157
+ throw error;
158
+ }
159
+ }
160
+ async function observeMutationLock(lockPath, ops) {
161
+ const lstat = ops.lstat ?? fsLstat;
162
+ const open = ops.open ?? fsOpen;
163
+ let handle;
164
+ try {
165
+ const pathStats = await lstat(lockPath, { bigint: true });
166
+ if (!pathStats.isFile()) {
167
+ return undefined;
168
+ }
169
+ handle = await open(lockPath, "r");
170
+ const before = await handle.stat({ bigint: true });
171
+ if (!before.isFile()
172
+ || before.nlink === 0n
173
+ || !sameMutationLockIncarnation(fingerprintFromStats(pathStats), fingerprintFromStats(before))) {
174
+ return undefined;
175
+ }
176
+ const raw = await handle.readFile({ encoding: "utf8" });
177
+ const after = await handle.stat({ bigint: true });
178
+ const beforeFingerprint = fingerprintFromStats(before);
179
+ const afterFingerprint = fingerprintFromStats(after);
180
+ if (after.nlink === 0n
181
+ || !mutationLockFingerprintsEqual(beforeFingerprint, afterFingerprint)) {
182
+ return undefined;
183
+ }
184
+ const observed = {
185
+ handle,
186
+ fingerprint: afterFingerprint,
187
+ metadata: parseMutationLockMetadata(raw),
188
+ };
189
+ handle = undefined;
190
+ return observed;
191
+ }
192
+ catch {
193
+ return undefined;
194
+ }
195
+ finally {
196
+ await handle?.close().catch(() => { });
197
+ }
198
+ }
199
+ function isRecoverableStaleMutationLock(observed) {
200
+ const mtimeMs = Number(observed.fingerprint.mtimeNs / 1000000n);
201
+ if (Date.now() - mtimeMs < CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS) {
202
+ return false;
203
+ }
204
+ return observed.metadata === "invalid"
205
+ || isStaleMutationLockMetadata(observed.metadata);
206
+ }
207
+ async function fingerprintOpenHandle(handle) {
208
+ return fingerprintFromStats(await handle.stat({ bigint: true }));
209
+ }
210
+ async function removeMutationLockIfCurrent(lockPath, expected, ops) {
211
+ // Node has no portable conditional-unlink primitive. Keeping the admitted
212
+ // descriptor open prevents inode reuse while this fingerprint recheck rejects
213
+ // replacements visible before unlink. Ordinary owner and recovery-gate release
214
+ // are safe under cooperative ownership. Stale main-lock removal holds the
215
+ // per-run recovery gate across this boundary; that gate is never stale-recovered.
216
+ const lstat = ops.lstat ?? fsLstat;
217
+ const rm = ops.rm ?? fsRm;
218
+ let current;
219
+ try {
220
+ current = await lstat(lockPath, { bigint: true });
221
+ }
222
+ catch {
223
+ return false;
224
+ }
225
+ if (!current.isFile()
226
+ || !mutationLockFingerprintsEqual(fingerprintFromStats(current), expected)) {
227
+ return false;
228
+ }
229
+ try {
230
+ await rm(lockPath, { force: true });
231
+ return true;
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ }
237
+ function parseMutationLockMetadata(raw) {
238
+ let parsed;
239
+ try {
240
+ parsed = JSON.parse(raw);
241
+ }
242
+ catch {
243
+ return "invalid";
244
+ }
245
+ if (parsed === null || typeof parsed !== "object") {
246
+ return "invalid";
247
+ }
248
+ const metadata = parsed;
249
+ if (typeof metadata.pid !== "number"
250
+ || !Number.isInteger(metadata.pid)
251
+ || typeof metadata.hostname !== "string"
252
+ || typeof metadata.created_at !== "string") {
253
+ return "invalid";
254
+ }
255
+ if (metadata.owner_nonce !== undefined
256
+ && typeof metadata.owner_nonce !== "string") {
257
+ return "invalid";
258
+ }
259
+ return {
260
+ pid: metadata.pid,
261
+ hostname: metadata.hostname,
262
+ created_at: metadata.created_at,
263
+ ...(metadata.owner_nonce === undefined
264
+ ? {}
265
+ : { owner_nonce: metadata.owner_nonce }),
266
+ };
267
+ }
268
+ function fingerprintFromStats(stats) {
269
+ return {
270
+ dev: stats.dev,
271
+ ino: stats.ino,
272
+ mtimeNs: stats.mtimeNs,
273
+ ctimeNs: stats.ctimeNs,
274
+ size: stats.size,
275
+ nlink: stats.nlink,
276
+ };
277
+ }
278
+ function sameMutationLockIncarnation(left, right) {
279
+ return left.dev === right.dev && left.ino === right.ino;
280
+ }
281
+ function mutationLockFingerprintsEqual(left, right) {
282
+ return sameMutationLockIncarnation(left, right)
283
+ && left.mtimeNs === right.mtimeNs
284
+ && left.ctimeNs === right.ctimeNs
285
+ && left.size === right.size
286
+ && left.nlink === right.nlink;
287
+ }
288
+ function isStaleMutationLockMetadata(metadata) {
289
+ const createdAtMs = Date.parse(metadata.created_at);
290
+ if (!Number.isFinite(createdAtMs)
291
+ || Date.now() - createdAtMs < CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS) {
292
+ return false;
293
+ }
294
+ if (metadata.hostname !== hostname()) {
295
+ return false;
296
+ }
297
+ return !isProcessAlive(metadata.pid);
298
+ }
299
+ function isProcessAlive(pid) {
300
+ if (pid <= 0) {
301
+ return false;
302
+ }
303
+ try {
304
+ process.kill(pid, 0);
305
+ return true;
306
+ }
307
+ catch (error) {
308
+ return !(isNodeError(error) && error.code === "ESRCH");
309
+ }
310
+ }
311
+ function isNodeError(error) {
312
+ return error instanceof Error && "code" in error;
313
+ }
314
+ async function delay(ms) {
315
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
316
+ }
317
+ function isPathInsideDirectory(candidate, directory) {
318
+ const relation = relative(directory, candidate);
319
+ return relation !== "" && !relation.startsWith("..") && !isAbsolute(relation);
320
+ }
321
+ //# sourceMappingURL=mutation-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-lock.js","sourceRoot":"","sources":["../../src/context-builder/mutation-lock.ts"],"names":[],"mappings":"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"}
@@ -58,6 +58,7 @@ type ReviewSliceInput = ReviewCandidateInput & ContextReviewRange & {
58
58
  source: "focus" | "search";
59
59
  search_term_index?: number;
60
60
  };
61
+ export declare const MAX_REGRESSION_HEAD_SLICE_TARGETS = 24;
61
62
  export declare function contextReviewCandidateId(rootIndex: number, path: string): string;
62
63
  export declare function buildContextReviewObligations(input: {
63
64
  candidates: readonly ReviewCandidateInput[];
@@ -66,6 +67,37 @@ export declare function buildContextReviewObligations(input: {
66
67
  slices: readonly ReviewSliceInput[];
67
68
  policy: ContextCoveragePolicy | undefined;
68
69
  }): ContextReviewObligationModel | undefined;
70
+ /**
71
+ * Picks exact-stem regression tests that would link confidently but cannot,
72
+ * only because discovery produced no usable slice for them (typically a capped
73
+ * search on generic terms, or a search hit whose slice was pruned). Linkage
74
+ * needs a range, so without one the target reports a false "no confident
75
+ * regression link". Discovery synthesizes one bounded head slice per returned
76
+ * test and rebuilds the obligations.
77
+ *
78
+ * Deliberately narrower than linkage itself: exact stem only (no proximity or
79
+ * search-token links), the same ambiguity/generic-stem confidence rules, never
80
+ * for non-source targets (docs/config have no regression obligation), and
81
+ * skipped whenever the test is already linked to ANY target, because then
82
+ * linkage is already decided by real evidence. `searchRecords`/`slices` are
83
+ * kept in the input for call-site stability; eligibility no longer reads them.
84
+ */
85
+ export declare function planRegressionHeadSliceCandidates(input: {
86
+ candidates: readonly (ReviewCandidateInput & {
87
+ line_count: number;
88
+ })[];
89
+ searchRecords: readonly ReviewSearchInput[];
90
+ slices: readonly ReviewCandidateInput[];
91
+ obligations: ContextReviewObligationModel;
92
+ }): ReviewCandidateInput[];
93
+ /**
94
+ * Docs, data, and config-format files carry no regression-test obligation, so
95
+ * an unlinked one reports regression "unavailable" instead of degrading the
96
+ * review. A denylist on purpose: unknown extensions stay source, so a new
97
+ * language is never silently exempted. Not `isGenericConfigPath`, which also
98
+ * matches source modules such as `config.ts`.
99
+ */
100
+ export declare function isNonSourceReviewPath(path: string): boolean;
69
101
  export declare function resolveContextCoveragePolicy(profile: ContextBuilderIntentProfile | undefined): ContextCoveragePolicy;
70
102
  export declare function evaluateContextCoverage(input: {
71
103
  policy: ContextCoveragePolicy;
@@ -9,6 +9,10 @@ export const CONTEXT_EVIDENCE_CLASS_ORDER = [
9
9
  ];
10
10
  const REVIEW_LINK_MAX_DIRECTORY_DISTANCE = 2;
11
11
  const GENERIC_REVIEW_STEMS = new Set(["index", "config", "types", "utils"]);
12
+ // Bounds the synthesized regression head slices discovery may add: at most one
13
+ // per unlinked target and this many overall, so a wide focus directory cannot
14
+ // turn stem linkage into bulk test ingestion.
15
+ export const MAX_REGRESSION_HEAD_SLICE_TARGETS = 24;
12
16
  export function contextReviewCandidateId(rootIndex, path) {
13
17
  return `c:${String(rootIndex)}:${path}`;
14
18
  }
@@ -132,13 +136,14 @@ export function buildContextReviewObligations(input) {
132
136
  : [];
133
137
  const hasAnyDistinguishingSearch = ambiguousStem &&
134
138
  sameStemTargets.some((target) => targetDistinguishingSearchRecords(testSearch, target, sameStemTargets).length > 0);
135
- const linkingSearchIndexes = requiresDistinguishingSearch
136
- ? new Set(distinguishingSearchRecords.map((record) => record.search_term_index))
137
- : new Set(testSearch
138
- .filter((record) => targetStem.length > 0 &&
139
+ const stemLinkingRecords = requiresDistinguishingSearch
140
+ ? []
141
+ : testSearch.filter((record) => targetStem.length > 0 &&
139
142
  (record.search_term.toLowerCase().includes(targetStem) ||
140
- record.preview.toLowerCase().includes(targetStem)))
141
- .map((record) => record.search_term_index));
143
+ record.preview.toLowerCase().includes(targetStem)));
144
+ const linkingSearchIndexes = new Set((requiresDistinguishingSearch
145
+ ? distinguishingSearchRecords
146
+ : stemLinkingRecords).map((record) => record.search_term_index));
142
147
  const confidentProximity = nearby &&
143
148
  !hasAnyDistinguishingSearch &&
144
149
  (!ambiguousStem ||
@@ -149,20 +154,31 @@ export function buildContextReviewObligations(input) {
149
154
  continue;
150
155
  }
151
156
  const testSlices = slicesByCandidateForLinking.get(testId) ?? [];
152
- const candidateSlices = linkingSearchIndexes.size > 0
153
- ? testSlices.filter((slice) => {
154
- if (slice.search_term_index == null ||
155
- !linkingSearchIndexes.has(slice.search_term_index)) {
156
- return false;
157
- }
158
- return (!requiresDistinguishingSearch ||
159
- distinguishingSearchRecords.some((record) => record.search_term_index === slice.search_term_index &&
160
- record.line >= slice.start_line &&
161
- record.line <= slice.end_line));
162
- })
163
- : confidentStem || confidentProximity
164
- ? [...testSlices].sort(compareReviewSlices).slice(0, 1)
165
- : [];
157
+ const firstSlice = () => [...testSlices].sort(compareReviewSlices).slice(0, 1);
158
+ let candidateSlices;
159
+ if (linkingSearchIndexes.size === 0) {
160
+ candidateSlices = confidentStem || confidentProximity ? firstSlice() : [];
161
+ }
162
+ else if (requiresDistinguishingSearch) {
163
+ candidateSlices = testSlices.filter((slice) => slice.search_term_index != null &&
164
+ linkingSearchIndexes.has(slice.search_term_index) &&
165
+ distinguishingSearchRecords.some((record) => record.search_term_index === slice.search_term_index &&
166
+ record.line >= slice.start_line &&
167
+ record.line <= slice.end_line));
168
+ }
169
+ else {
170
+ // Overlapping search slices are merged under one retained
171
+ // search_term_index, so a slice that physically contains a stem hit
172
+ // links even when it carries a different term index. A confident
173
+ // exact-stem test with no containing slice still links through its
174
+ // first slice rather than reporting a false "no link".
175
+ candidateSlices = testSlices.filter((slice) => (slice.search_term_index != null &&
176
+ linkingSearchIndexes.has(slice.search_term_index)) ||
177
+ stemLinkingRecords.some((record) => record.line >= slice.start_line && record.line <= slice.end_line));
178
+ if (candidateSlices.length === 0 && confidentStem) {
179
+ candidateSlices = firstSlice();
180
+ }
181
+ }
166
182
  const ranges = mergeReviewRanges(candidateSlices);
167
183
  if (ranges.length > 0) {
168
184
  regressionLinks.push({
@@ -183,15 +199,106 @@ export function buildContextReviewObligations(input) {
183
199
  classification_reason: classificationReason,
184
200
  regression_availability: regressionLinks.length > 0
185
201
  ? "available"
186
- : rootsWithTests.has(candidate.root_index)
187
- ? "unclassifiable"
188
- : "unavailable",
202
+ : isNonSourceReviewPath(candidate.path)
203
+ ? "unavailable"
204
+ : rootsWithTests.has(candidate.root_index)
205
+ ? "unclassifiable"
206
+ : "unavailable",
189
207
  regression_links: regressionLinks.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id)),
190
208
  };
191
209
  targetById.set(target.target_id, target);
192
210
  }
193
211
  return { targets: [...targetById.values()] };
194
212
  }
213
+ /**
214
+ * Picks exact-stem regression tests that would link confidently but cannot,
215
+ * only because discovery produced no usable slice for them (typically a capped
216
+ * search on generic terms, or a search hit whose slice was pruned). Linkage
217
+ * needs a range, so without one the target reports a false "no confident
218
+ * regression link". Discovery synthesizes one bounded head slice per returned
219
+ * test and rebuilds the obligations.
220
+ *
221
+ * Deliberately narrower than linkage itself: exact stem only (no proximity or
222
+ * search-token links), the same ambiguity/generic-stem confidence rules, never
223
+ * for non-source targets (docs/config have no regression obligation), and
224
+ * skipped whenever the test is already linked to ANY target, because then
225
+ * linkage is already decided by real evidence. `searchRecords`/`slices` are
226
+ * kept in the input for call-site stability; eligibility no longer reads them.
227
+ */
228
+ export function planRegressionHeadSliceCandidates(input) {
229
+ const linkedTestIds = new Set(input.obligations.targets.flatMap((target) => target.regression_links.map((link) => link.candidate_id)));
230
+ const testsByRootAndStem = new Map();
231
+ for (const candidate of input.candidates) {
232
+ if (!isTestPath(candidate.path) || candidate.line_count <= 0) {
233
+ continue;
234
+ }
235
+ appendIndexedCandidate(testsByRootAndStem, rootAndStemKey(candidate), candidate);
236
+ }
237
+ const targetCountByRootAndStem = new Map();
238
+ for (const target of input.obligations.targets) {
239
+ const key = rootAndStemKey(target);
240
+ targetCountByRootAndStem.set(key, (targetCountByRootAndStem.get(key) ?? 0) + 1);
241
+ }
242
+ const planned = [];
243
+ const plannedIds = new Set();
244
+ for (const target of [...input.obligations.targets].sort((left, right) => left.target_id.localeCompare(right.target_id))) {
245
+ if (planned.length >= MAX_REGRESSION_HEAD_SLICE_TARGETS) {
246
+ break;
247
+ }
248
+ if (target.regression_links.length > 0 || isNonSourceReviewPath(target.path)) {
249
+ continue;
250
+ }
251
+ const key = rootAndStemKey(target);
252
+ if ((targetCountByRootAndStem.get(key) ?? 0) > 1) {
253
+ continue;
254
+ }
255
+ const targetStem = normalizedPathStem(target.path).toLowerCase();
256
+ const genericStem = GENERIC_REVIEW_STEMS.has(targetStem);
257
+ const eligible = (testsByRootAndStem.get(key) ?? []).filter((test) => {
258
+ const testId = contextReviewCandidateId(test.root_index, test.path);
259
+ const nearby = directoryDistance(target.path, test.path) <=
260
+ REVIEW_LINK_MAX_DIRECTORY_DISTANCE;
261
+ return (!genericStem || nearby) && !linkedTestIds.has(testId);
262
+ });
263
+ const chosen = eligible.sort((left, right) => directoryDistance(target.path, left.path) -
264
+ directoryDistance(target.path, right.path) ||
265
+ left.path.localeCompare(right.path))[0];
266
+ if (chosen == null) {
267
+ continue;
268
+ }
269
+ const chosenId = contextReviewCandidateId(chosen.root_index, chosen.path);
270
+ if (plannedIds.has(chosenId)) {
271
+ continue;
272
+ }
273
+ plannedIds.add(chosenId);
274
+ planned.push({ root_index: chosen.root_index, path: chosen.path });
275
+ }
276
+ return planned;
277
+ }
278
+ const NON_SOURCE_REVIEW_EXTENSIONS = new Set([
279
+ ".md", ".mdx", ".markdown", ".txt", ".rst", ".adoc",
280
+ ".json", ".jsonc", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf",
281
+ ".lock", ".csv", ".tsv", ".xml", ".po", ".pot", ".mo",
282
+ ]);
283
+ // Exact extensionless names only: `README.md`/`LICENSE.txt` are already caught
284
+ // by the extension denylist, and a prefix match would exempt source files such
285
+ // as `readme_helper.ts`.
286
+ const NON_SOURCE_REVIEW_BASENAME = /^(?:license|licence|copying|changelog|readme)$/i;
287
+ /**
288
+ * Docs, data, and config-format files carry no regression-test obligation, so
289
+ * an unlinked one reports regression "unavailable" instead of degrading the
290
+ * review. A denylist on purpose: unknown extensions stay source, so a new
291
+ * language is never silently exempted. Not `isGenericConfigPath`, which also
292
+ * matches source modules such as `config.ts`.
293
+ */
294
+ export function isNonSourceReviewPath(path) {
295
+ const basename = path.slice(path.lastIndexOf("/") + 1);
296
+ if (NON_SOURCE_REVIEW_BASENAME.test(basename)) {
297
+ return true;
298
+ }
299
+ const dot = basename.lastIndexOf(".");
300
+ return dot > 0 && NON_SOURCE_REVIEW_EXTENSIONS.has(basename.slice(dot).toLowerCase());
301
+ }
195
302
  export function resolveContextCoveragePolicy(profile) {
196
303
  if (profile !== "review_deep") {
197
304
  return {