@loopover/engine 1.0.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 (272) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE +661 -0
  3. package/README.md +676 -0
  4. package/dist/advisory/gate-advisory.d.ts +151 -0
  5. package/dist/advisory/gate-advisory.js +512 -0
  6. package/dist/ai-policy-map.d.ts +97 -0
  7. package/dist/ai-policy-map.js +377 -0
  8. package/dist/ams-policy-spec.d.ts +56 -0
  9. package/dist/ams-policy-spec.js +165 -0
  10. package/dist/calibration-dashboard.d.ts +23 -0
  11. package/dist/calibration-dashboard.js +65 -0
  12. package/dist/calibration-trend.d.ts +40 -0
  13. package/dist/calibration-trend.js +60 -0
  14. package/dist/contributor-fit.d.ts +24 -0
  15. package/dist/contributor-fit.js +50 -0
  16. package/dist/discovery-index-contract.d.ts +74 -0
  17. package/dist/discovery-index-contract.js +184 -0
  18. package/dist/discovery-soft-claim.d.ts +35 -0
  19. package/dist/discovery-soft-claim.js +50 -0
  20. package/dist/duplicate-winner.d.ts +57 -0
  21. package/dist/duplicate-winner.js +98 -0
  22. package/dist/feasibility.d.ts +27 -0
  23. package/dist/feasibility.js +86 -0
  24. package/dist/finding-severity-calibration.d.ts +116 -0
  25. package/dist/finding-severity-calibration.js +457 -0
  26. package/dist/fleet-run-manifest.d.ts +41 -0
  27. package/dist/fleet-run-manifest.js +151 -0
  28. package/dist/focus-manifest/guidance.d.ts +21 -0
  29. package/dist/focus-manifest/guidance.js +222 -0
  30. package/dist/focus-manifest.d.ts +826 -0
  31. package/dist/focus-manifest.js +2730 -0
  32. package/dist/gate-verdict-calibration.d.ts +111 -0
  33. package/dist/gate-verdict-calibration.js +411 -0
  34. package/dist/github/constants.d.ts +1 -0
  35. package/dist/github/constants.js +1 -0
  36. package/dist/github/sanitize-public-comment.d.ts +1 -0
  37. package/dist/github/sanitize-public-comment.js +26 -0
  38. package/dist/goal-model.d.ts +7 -0
  39. package/dist/goal-model.js +87 -0
  40. package/dist/governor/action-mode.d.ts +55 -0
  41. package/dist/governor/action-mode.js +74 -0
  42. package/dist/governor/budget-cap.d.ts +49 -0
  43. package/dist/governor/budget-cap.js +34 -0
  44. package/dist/governor/chokepoint.d.ts +58 -0
  45. package/dist/governor/chokepoint.js +269 -0
  46. package/dist/governor/kill-switch.d.ts +38 -0
  47. package/dist/governor/kill-switch.js +62 -0
  48. package/dist/governor/rate-limit.d.ts +40 -0
  49. package/dist/governor/rate-limit.js +63 -0
  50. package/dist/governor/reputation-throttle.d.ts +50 -0
  51. package/dist/governor/reputation-throttle.js +94 -0
  52. package/dist/governor/run-halt.d.ts +39 -0
  53. package/dist/governor/run-halt.js +83 -0
  54. package/dist/governor/self-plagiarism.d.ts +47 -0
  55. package/dist/governor/self-plagiarism.js +200 -0
  56. package/dist/governor/write-rate-limit.d.ts +52 -0
  57. package/dist/governor/write-rate-limit.js +132 -0
  58. package/dist/governor-ledger.d.ts +25 -0
  59. package/dist/governor-ledger.js +91 -0
  60. package/dist/index.d.ts +99 -0
  61. package/dist/index.js +120 -0
  62. package/dist/issue-plan-decomposition.d.ts +18 -0
  63. package/dist/issue-plan-decomposition.js +56 -0
  64. package/dist/issue-rag-query.d.ts +12 -0
  65. package/dist/issue-rag-query.js +35 -0
  66. package/dist/local-scorer.d.ts +34 -0
  67. package/dist/local-scorer.js +36 -0
  68. package/dist/metadata-best-min-score.d.ts +9 -0
  69. package/dist/metadata-best-min-score.js +9 -0
  70. package/dist/metadata-best-pick.d.ts +9 -0
  71. package/dist/metadata-best-pick.js +9 -0
  72. package/dist/metadata-min-score.d.ts +9 -0
  73. package/dist/metadata-min-score.js +13 -0
  74. package/dist/metadata-top-min-score.d.ts +9 -0
  75. package/dist/metadata-top-min-score.js +13 -0
  76. package/dist/metadata-top-pick.d.ts +9 -0
  77. package/dist/metadata-top-pick.js +13 -0
  78. package/dist/miner/acceptance-criteria.d.ts +43 -0
  79. package/dist/miner/acceptance-criteria.js +77 -0
  80. package/dist/miner/agent-sdk-driver.d.ts +39 -0
  81. package/dist/miner/agent-sdk-driver.js +162 -0
  82. package/dist/miner/attempt-log.d.ts +29 -0
  83. package/dist/miner/attempt-log.js +100 -0
  84. package/dist/miner/attempt-metering.d.ts +39 -0
  85. package/dist/miner/attempt-metering.js +67 -0
  86. package/dist/miner/cli-subprocess-driver.d.ts +68 -0
  87. package/dist/miner/cli-subprocess-driver.js +272 -0
  88. package/dist/miner/coding-agent-driver.d.ts +33 -0
  89. package/dist/miner/coding-agent-driver.js +36 -0
  90. package/dist/miner/coding-agent-invoke.d.ts +11 -0
  91. package/dist/miner/coding-agent-invoke.js +80 -0
  92. package/dist/miner/coding-agent-mode.d.ts +25 -0
  93. package/dist/miner/coding-agent-mode.js +43 -0
  94. package/dist/miner/driver-factory.d.ts +82 -0
  95. package/dist/miner/driver-factory.js +169 -0
  96. package/dist/miner/harness-submission-trigger.d.ts +37 -0
  97. package/dist/miner/harness-submission-trigger.js +46 -0
  98. package/dist/miner/iterate-loop.d.ts +97 -0
  99. package/dist/miner/iterate-loop.js +278 -0
  100. package/dist/miner/iterate-policy.d.ts +98 -0
  101. package/dist/miner/iterate-policy.js +98 -0
  102. package/dist/miner/lint-guard.d.ts +50 -0
  103. package/dist/miner/lint-guard.js +68 -0
  104. package/dist/miner/local-write-tools.d.ts +81 -0
  105. package/dist/miner/local-write-tools.js +113 -0
  106. package/dist/miner/loop-reentry-policy.d.ts +36 -0
  107. package/dist/miner/loop-reentry-policy.js +53 -0
  108. package/dist/miner/repo-map.d.ts +51 -0
  109. package/dist/miner/repo-map.js +268 -0
  110. package/dist/miner/self-review-adapter.d.ts +106 -0
  111. package/dist/miner/self-review-adapter.js +83 -0
  112. package/dist/miner/submission-gate.d.ts +40 -0
  113. package/dist/miner/submission-gate.js +89 -0
  114. package/dist/miner/worktree-allocator.d.ts +59 -0
  115. package/dist/miner/worktree-allocator.js +60 -0
  116. package/dist/miner/worktree-pool.d.ts +55 -0
  117. package/dist/miner/worktree-pool.js +65 -0
  118. package/dist/miner-goal-lane-fit.d.ts +18 -0
  119. package/dist/miner-goal-lane-fit.js +76 -0
  120. package/dist/miner-goal-spec.d.ts +139 -0
  121. package/dist/miner-goal-spec.js +281 -0
  122. package/dist/miner-prediction-metrics.d.ts +16 -0
  123. package/dist/miner-prediction-metrics.js +61 -0
  124. package/dist/miner-telemetry.d.ts +32 -0
  125. package/dist/miner-telemetry.js +87 -0
  126. package/dist/objective-anchor.d.ts +125 -0
  127. package/dist/objective-anchor.js +401 -0
  128. package/dist/opportunity-competition.d.ts +6 -0
  129. package/dist/opportunity-competition.js +29 -0
  130. package/dist/opportunity-freshness.d.ts +18 -0
  131. package/dist/opportunity-freshness.js +55 -0
  132. package/dist/opportunity-metadata.d.ts +53 -0
  133. package/dist/opportunity-metadata.js +204 -0
  134. package/dist/opportunity-ranker.d.ts +46 -0
  135. package/dist/opportunity-ranker.js +74 -0
  136. package/dist/pairwise-calibration.d.ts +49 -0
  137. package/dist/pairwise-calibration.js +106 -0
  138. package/dist/phase7-calibration-loop.d.ts +136 -0
  139. package/dist/phase7-calibration-loop.js +437 -0
  140. package/dist/plan-blocked.d.ts +6 -0
  141. package/dist/plan-blocked.js +24 -0
  142. package/dist/plan-completed.d.ts +5 -0
  143. package/dist/plan-completed.js +6 -0
  144. package/dist/plan-completion.d.ts +6 -0
  145. package/dist/plan-completion.js +7 -0
  146. package/dist/plan-empty.d.ts +5 -0
  147. package/dist/plan-empty.js +6 -0
  148. package/dist/plan-export.d.ts +27 -0
  149. package/dist/plan-export.js +86 -0
  150. package/dist/plan-failure.d.ts +5 -0
  151. package/dist/plan-failure.js +6 -0
  152. package/dist/plan-overall-status.d.ts +6 -0
  153. package/dist/plan-overall-status.js +25 -0
  154. package/dist/plan-pending.d.ts +5 -0
  155. package/dist/plan-pending.js +6 -0
  156. package/dist/plan-progress-complete.d.ts +7 -0
  157. package/dist/plan-progress-complete.js +10 -0
  158. package/dist/plan-ready.d.ts +6 -0
  159. package/dist/plan-ready.js +12 -0
  160. package/dist/plan-running.d.ts +5 -0
  161. package/dist/plan-running.js +6 -0
  162. package/dist/plan-skipped.d.ts +5 -0
  163. package/dist/plan-skipped.js +6 -0
  164. package/dist/plan-step-count.d.ts +5 -0
  165. package/dist/plan-step-count.js +6 -0
  166. package/dist/plan-step-stats.d.ts +5 -0
  167. package/dist/plan-step-stats.js +6 -0
  168. package/dist/plan-templates.d.ts +25 -0
  169. package/dist/plan-templates.js +107 -0
  170. package/dist/plan-terminated.d.ts +6 -0
  171. package/dist/plan-terminated.js +9 -0
  172. package/dist/portfolio/non-convergence.d.ts +37 -0
  173. package/dist/portfolio/non-convergence.js +57 -0
  174. package/dist/portfolio/queue.d.ts +30 -0
  175. package/dist/portfolio/queue.js +150 -0
  176. package/dist/predicted-gate.d.ts +103 -0
  177. package/dist/predicted-gate.js +218 -0
  178. package/dist/prompt-packet.d.ts +11 -0
  179. package/dist/prompt-packet.js +34 -0
  180. package/dist/ranked-opportunity-best-min-score.d.ts +8 -0
  181. package/dist/ranked-opportunity-best-min-score.js +9 -0
  182. package/dist/ranked-opportunity-best-pick.d.ts +8 -0
  183. package/dist/ranked-opportunity-best-pick.js +9 -0
  184. package/dist/ranked-opportunity-min-score.d.ts +8 -0
  185. package/dist/ranked-opportunity-min-score.js +13 -0
  186. package/dist/ranked-opportunity-top-min-score.d.ts +8 -0
  187. package/dist/ranked-opportunity-top-min-score.js +13 -0
  188. package/dist/review/advisory-ai-routing-config.d.ts +8 -0
  189. package/dist/review/advisory-ai-routing-config.js +40 -0
  190. package/dist/review/check-names.d.ts +1 -0
  191. package/dist/review/check-names.js +1 -0
  192. package/dist/review/cla-check.d.ts +49 -0
  193. package/dist/review/cla-check.js +71 -0
  194. package/dist/review/diff-file-priority.d.ts +1 -0
  195. package/dist/review/diff-file-priority.js +12 -0
  196. package/dist/review/enrichment-analyzer-names.d.ts +3 -0
  197. package/dist/review/enrichment-analyzer-names.js +63 -0
  198. package/dist/review/guardrail-config.d.ts +10 -0
  199. package/dist/review/guardrail-config.js +62 -0
  200. package/dist/review/linked-issue-hard-rules-config.d.ts +4 -0
  201. package/dist/review/linked-issue-hard-rules-config.js +79 -0
  202. package/dist/review/linked-issue-label-propagation.d.ts +9 -0
  203. package/dist/review/linked-issue-label-propagation.js +114 -0
  204. package/dist/review/pre-merge-checks.d.ts +28 -0
  205. package/dist/review/pre-merge-checks.js +66 -0
  206. package/dist/review/review-thread-findings.d.ts +1 -0
  207. package/dist/review/review-thread-findings.js +1 -0
  208. package/dist/review/safe-url.d.ts +5 -0
  209. package/dist/review/safe-url.js +137 -0
  210. package/dist/review/screenshot-table-gate.d.ts +87 -0
  211. package/dist/review/screenshot-table-gate.js +340 -0
  212. package/dist/review/unlinked-issue-guardrail-config.d.ts +9 -0
  213. package/dist/review/unlinked-issue-guardrail-config.js +44 -0
  214. package/dist/reviewer-consensus-calibration.d.ts +113 -0
  215. package/dist/reviewer-consensus-calibration.js +493 -0
  216. package/dist/reward-risk.d.ts +223 -0
  217. package/dist/reward-risk.js +618 -0
  218. package/dist/scoring/label-match.d.ts +4 -0
  219. package/dist/scoring/label-match.js +145 -0
  220. package/dist/scoring/model.d.ts +31 -0
  221. package/dist/scoring/model.js +157 -0
  222. package/dist/scoring/pending-pr-scenarios.d.ts +44 -0
  223. package/dist/scoring/pending-pr-scenarios.js +145 -0
  224. package/dist/scoring/preview.d.ts +246 -0
  225. package/dist/scoring/preview.js +1108 -0
  226. package/dist/scoring/types.d.ts +131 -0
  227. package/dist/scoring/types.js +7 -0
  228. package/dist/settings/auto-close-exempt.d.ts +10 -0
  229. package/dist/settings/auto-close-exempt.js +58 -0
  230. package/dist/settings/autonomy.d.ts +33 -0
  231. package/dist/settings/autonomy.js +80 -0
  232. package/dist/settings/command-authorization.d.ts +49 -0
  233. package/dist/settings/command-authorization.js +222 -0
  234. package/dist/settings/contributor-blacklist.d.ts +17 -0
  235. package/dist/settings/contributor-blacklist.js +86 -0
  236. package/dist/settings/moderation-rules.d.ts +51 -0
  237. package/dist/settings/moderation-rules.js +104 -0
  238. package/dist/settings/pr-type-label.d.ts +70 -0
  239. package/dist/settings/pr-type-label.js +149 -0
  240. package/dist/signals/change-guardrail.d.ts +49 -0
  241. package/dist/signals/change-guardrail.js +164 -0
  242. package/dist/signals/check-summary.d.ts +9 -0
  243. package/dist/signals/check-summary.js +12 -0
  244. package/dist/signals/contributor-calibration.d.ts +23 -0
  245. package/dist/signals/contributor-calibration.js +47 -0
  246. package/dist/signals/duplicate-winner.d.ts +9 -0
  247. package/dist/signals/duplicate-winner.js +9 -0
  248. package/dist/signals/path-matchers.d.ts +36 -0
  249. package/dist/signals/path-matchers.js +371 -0
  250. package/dist/signals/predicted-gate-engine.d.ts +62 -0
  251. package/dist/signals/predicted-gate-engine.js +895 -0
  252. package/dist/signals/preflight-limits.d.ts +14 -0
  253. package/dist/signals/preflight-limits.js +14 -0
  254. package/dist/signals/slop.d.ts +56 -0
  255. package/dist/signals/slop.js +324 -0
  256. package/dist/signals/test-evidence.d.ts +25 -0
  257. package/dist/signals/test-evidence.js +108 -0
  258. package/dist/subprocess-env.d.ts +23 -0
  259. package/dist/subprocess-env.js +74 -0
  260. package/dist/track-record-summary.d.ts +92 -0
  261. package/dist/track-record-summary.js +301 -0
  262. package/dist/types/manifest-deps-types.d.ts +514 -0
  263. package/dist/types/manifest-deps-types.js +4 -0
  264. package/dist/types/predicted-gate-types.d.ts +338 -0
  265. package/dist/types/predicted-gate-types.js +3 -0
  266. package/dist/types/reward-risk-types.d.ts +319 -0
  267. package/dist/types/reward-risk-types.js +11 -0
  268. package/dist/utils/json.d.ts +1 -0
  269. package/dist/utils/json.js +3 -0
  270. package/dist/version.d.ts +2 -0
  271. package/dist/version.js +3 -0
  272. package/package.json +80 -0
@@ -0,0 +1,164 @@
1
+ // Convergence safety: the hard-guardrail path check for the auto-maintain layer (#778). Changed paths that
2
+ // match a repo's configured hardGuardrailGlobs force MANUAL review — gittensory must never auto-merge OR
3
+ // auto-close a PR that touches a guarded path. Ported verbatim from
4
+ // reviewbot core/change-classifier.ts — the mechanism that prevents the awesome-claude #4196 incident class
5
+ // (a weakened policy script auto-merging because its path wasn't guarded). Pure + dependency-free.
6
+ // Canonicalize a path or glob so matching is case- and separator-insensitive: backslashes → `/`, drop a
7
+ // leading `./` or `/`, and case-fold. Mirrors signals/focus-manifest `normalizePathForMatch` — without it a
8
+ // guarded path is evaded with `.github/Workflows/` (capital W), a `./`-prefix, or a `\` separator, turning a
9
+ // mandatory human hold on CI/policy files into an auto-merge.
10
+ export function canonicalize(value) {
11
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").toLowerCase();
12
+ }
13
+ // globToRegExp's COMPILATION is linear-time, but the COMPILED pattern's .test() can be polynomial-to-exponential
14
+ // time on an adversarial near-miss input when MULTIPLE wildcard GROUPS chain in one glob (a "group" is one `*`
15
+ // OR one `**` — a `**` pair compiles to a SINGLE `.*`, not two independent wildcards, so it must be counted as
16
+ // ONE group, not two characters; see countWildcardGroups below). Both group TYPES contribute to the same danger
17
+ // once chained — `[^/]*` groups separated by a literal that class doesn't exclude (e.g. "-", not "/") back-
18
+ // track ambiguously, and `.*` groups back-track ambiguously EVEN when "/"-separated, since `.*` crosses `/`
19
+ // freely. Re-benchmarked against `path` lengths GitHub can plausibly deliver via a deeply nested file path in a
20
+ // malicious PR (both `path` and, via `.gittensory.yml`'s contentLane.*Glob fields, the glob itself can be
21
+ // attacker-influenced in the same PR):
22
+ // 2 wildcard groups (any mix of `*`/`**`, any arrangement): sub-second even at a wildly implausible 32,000-
23
+ // char adversarial path (worst case observed: ~400ms) — quadratic, bounded, never a
24
+ // realistic hang.
25
+ // 3 wildcard groups: OVER 2 SECONDS at just ~4,000 chars for one chained-`*` shape, over 100ms at ~1,600
26
+ // chars for a chained-`**` shape — already dangerous well within a plausible path length.
27
+ // 4+ wildcard groups: confirmed catastrophic — 35 SECONDS at just 1,614 chars for 4 chained `**` groups.
28
+ // hardGuardrailGlobs are maintainer-configured, and this compiler is also exported for reuse by other
29
+ // maintainer-config-driven consumers (content-lane/spec-resolver.ts, whose real globs like "public/**/*.json" are
30
+ // exactly 2 groups: this cap must stay inclusive of that legitimate shape, not just "safer than before"), so the
31
+ // cap lives INSIDE globToRegExp itself (not just in a wrapper like matchesAny below) — every caller is
32
+ // protected automatically rather than needing to separately remember the risk. The boundary is set at the
33
+ // highest GROUP count proven safe by the benchmark above (2) — a boundary that itself sits inside the
34
+ // empirically dangerous range would defeat the point of a cap.
35
+ const MAX_GLOB_WILDCARD_GROUPS = 2;
36
+ /** Count `*` GROUPS in `glob` — a `**` pair is ONE group (it compiles to a single `.*`, see globToRegExp), not
37
+ * two. Mirrors globToRegExp's own tokenization exactly (including consuming a `**`'s trailing `/`) so the count
38
+ * reflects the actual number of backtracking-capable groups the compiled RegExp will contain, not raw `*`
39
+ * character count (which would double-count every globstar and reject legitimate globs like
40
+ * "public/**\/*.json" — 2 real groups — as if they were 3-groups-dangerous). */
41
+ function countWildcardGroups(glob) {
42
+ let count = 0;
43
+ for (let i = 0; i < glob.length; i += 1) {
44
+ if (glob.charAt(i) !== "*")
45
+ continue;
46
+ count += 1;
47
+ if (glob.charAt(i + 1) === "*") {
48
+ i += 1; // consume the second star of the "**" pair — one group, not two
49
+ if (glob.charAt(i + 1) === "/")
50
+ i += 1; // `**/` also matches zero segments, mirroring globToRegExp
51
+ }
52
+ }
53
+ return count;
54
+ }
55
+ /** True if `glob` has more wildcard GROUPS than can be safely compiled to a RegExp without risking catastrophic
56
+ * backtracking (see the MAX_GLOB_WILDCARD_GROUPS rationale above). Exported so any OTHER glob-accepting config
57
+ * surface (e.g. focus-manifest.ts's contentLane.*Glob parsing) can reject an over-complex glob using the SAME
58
+ * predicate globToRegExp itself enforces — a caller with its own, independently-counted threshold could accept
59
+ * a glob globToRegExp then silently compiles to NEVER_MATCHES, configuring a lane that can never activate. */
60
+ export function hasUnsafeWildcardCount(glob) {
61
+ return countWildcardGroups(glob) > MAX_GLOB_WILDCARD_GROUPS;
62
+ }
63
+ // A RegExp that never matches any input, at any position — the safe, conservative compiled form of an
64
+ // over-complex glob. "Never matches" (not "matches everything") is the correct default HERE because
65
+ // globToRegExp has no context on caller intent, and a false "matches everything" would be actively wrong for a
66
+ // non-guardrail caller (e.g. content-lane file-scope matching, where "matches everything" would misclassify
67
+ // every changed file as a registry submission). A caller whose OWN semantics want the opposite fail direction
68
+ // (a security guardrail, where under-protection is worse than an unnecessary hold) checks hasUnsafeWildcardCount
69
+ // itself and overrides — see matchesAny below.
70
+ const NEVER_MATCHES = /^(?!)$/;
71
+ /** Convert a path glob (`*` matches within a segment, `**` matches across `/`) to an anchored RegExp. The
72
+ * glob is canonicalized first, so matching is case-insensitive against a canonicalized path. Exported for
73
+ * reuse anywhere a maintainer-supplied path pattern needs compiling — never compile a raw regex string from
74
+ * config (ReDoS risk); this linear-time glob compiler is the one safe path pattern this codebase uses.
75
+ *
76
+ * An over-complex glob (see MAX_GLOB_WILDCARD_GROUPS) short-circuits to NEVER_MATCHES instead of being compiled —
77
+ * this function never returns a RegExp that risks catastrophic backtracking on .test(), for any input. */
78
+ export function globToRegExp(glob) {
79
+ if (hasUnsafeWildcardCount(glob))
80
+ return NEVER_MATCHES;
81
+ const canonical = canonicalize(glob);
82
+ let re = "";
83
+ for (let i = 0; i < canonical.length; i += 1) {
84
+ const c = canonical.charAt(i);
85
+ if (c === "*") {
86
+ if (canonical.charAt(i + 1) === "*") {
87
+ re += ".*";
88
+ i += 1;
89
+ if (canonical.charAt(i + 1) === "/")
90
+ i += 1; // `**/` also matches zero segments
91
+ }
92
+ else {
93
+ re += "[^/]*";
94
+ }
95
+ }
96
+ else if (/[.+?^${}()|[\]\\]/.test(c)) {
97
+ re += `\\${c}`;
98
+ }
99
+ else {
100
+ re += c;
101
+ }
102
+ }
103
+ return new RegExp(`^${re}$`);
104
+ }
105
+ /**
106
+ * True if `path` matches any of the globs (`*` within a segment, `**` across `/`), case-insensitively. A glob
107
+ * with more wildcards than can be safely compiled (see hasUnsafeWildcardCount) is treated as matching EVERY
108
+ * path — fail SAFE TOWARD GUARDING, mirroring isGuardrailHit's own "unknown ⇒ treat as a hit" philosophy (an
109
+ * over-complex guardrail glob still forces manual review) rather than the NEVER_MATCHES default globToRegExp
110
+ * itself falls back to, which would silently disable the maintainer's intended protection — the worse failure
111
+ * mode for a safety guardrail specifically (see globToRegExp's own docstring for why NEVER_MATCHES is still the
112
+ * right default for globToRegExp as a general-purpose compiler).
113
+ */
114
+ export function matchesAny(path, globs) {
115
+ const canonicalPath = canonicalize(path);
116
+ return globs.some((g) => hasUnsafeWildcardCount(g) || globToRegExp(g).test(canonicalPath));
117
+ }
118
+ /**
119
+ * The changed paths (if any) that trip a hard guardrail. A non-empty result means the PR touches a guarded
120
+ * path and MUST fall through to a human — gittensory may neither auto-merge nor auto-close it. Pure.
121
+ */
122
+ export function changedPathsHittingGuardrail(changedPaths, hardGuardrailGlobs) {
123
+ if (hardGuardrailGlobs.length === 0)
124
+ return [];
125
+ return changedPaths.filter((path) => path.length > 0 && matchesAny(path, hardGuardrailGlobs));
126
+ }
127
+ /**
128
+ * Structured guardrail match details for public review output + audit logs. Over-complex globs preserve the
129
+ * same fail-safe direction as {@link matchesAny}: they match every non-empty path rather than silently disabling
130
+ * a maintainer's guardrail. Unknown changed paths are represented by {@link isGuardrailHit}'s boolean path only,
131
+ * so callers can say "paths unavailable" without inventing a fake path.
132
+ */
133
+ export function guardrailPathMatches(changedPaths, hardGuardrailGlobs) {
134
+ if (hardGuardrailGlobs.length === 0 || changedPaths.length === 0)
135
+ return [];
136
+ const matches = [];
137
+ for (const path of changedPaths) {
138
+ if (path.length === 0)
139
+ continue;
140
+ const canonicalPath = canonicalize(path);
141
+ for (const glob of hardGuardrailGlobs) {
142
+ if (hasUnsafeWildcardCount(glob)) {
143
+ matches.push({ path, glob });
144
+ continue;
145
+ }
146
+ if (globToRegExp(glob).test(canonicalPath)) {
147
+ matches.push({ path, glob });
148
+ }
149
+ }
150
+ }
151
+ return matches;
152
+ }
153
+ /**
154
+ * Whether a PR's diff trips a hard guardrail — the BOOLEAN form shared by the disposition (held for owner
155
+ * review) and the public comment (so the headline reads "held", not "safe to merge"). FAIL-SAFE on unknown
156
+ * paths (#1062): when guardrails ARE configured but the changed-file set is empty (the cache is not yet / no
157
+ * longer populated), we cannot prove the PR avoids a guarded path, so treat it as a hit. No guardrails
158
+ * configured ⇒ never a hit. Pure.
159
+ */
160
+ export function isGuardrailHit(changedPaths, hardGuardrailGlobs) {
161
+ if (hardGuardrailGlobs.length === 0)
162
+ return false;
163
+ return changedPaths.length === 0 || changedPathsHittingGuardrail(changedPaths, hardGuardrailGlobs).length > 0;
164
+ }
@@ -0,0 +1,9 @@
1
+ import type { CheckSummaryRecord } from "../scoring/types.js";
2
+ /**
3
+ * Canonical "is this ONE cached check failing?" predicate, shared so every surface (readiness, the maintainer
4
+ * queue digest, reward-risk reviewability) classifies a check identically. A cached check may carry its outcome
5
+ * on `conclusion` (check runs) OR only on `status` (commit-status rows and runs that errored before concluding),
6
+ * so fall back to `status` when `conclusion` is absent, and case-fold both — GitHub conclusions are lowercase,
7
+ * but cached/commit statuses are not guaranteed to be.
8
+ */
9
+ export declare function isFailingCheckSummary(check: CheckSummaryRecord): boolean;
@@ -0,0 +1,12 @@
1
+ /** Conclusion/status values that mark a single cached check as failing or attention-needing. */
2
+ const FAILING_CHECK_STATES = ["failure", "failed", "timed_out", "cancelled", "action_required", "startup_failure"];
3
+ /**
4
+ * Canonical "is this ONE cached check failing?" predicate, shared so every surface (readiness, the maintainer
5
+ * queue digest, reward-risk reviewability) classifies a check identically. A cached check may carry its outcome
6
+ * on `conclusion` (check runs) OR only on `status` (commit-status rows and runs that errored before concluding),
7
+ * so fall back to `status` when `conclusion` is absent, and case-fold both — GitHub conclusions are lowercase,
8
+ * but cached/commit statuses are not guaranteed to be.
9
+ */
10
+ export function isFailingCheckSummary(check) {
11
+ return FAILING_CHECK_STATES.includes((check.conclusion ?? check.status).toLowerCase());
12
+ }
@@ -0,0 +1,23 @@
1
+ /** Below this many historical (prediction, real-decision) pairings, a login's track record is treated as
2
+ * cold-start (unweighted baseline) -- too few samples to distinguish signal from noise. */
3
+ export declare const MIN_CALIBRATION_SAMPLES = 5;
4
+ /** Maximum points (out of the 0-100 readinessScore scale) personalization may add or subtract. Deliberately
5
+ * small relative to the 100-point scale: this is a confidence nudge, not a re-scoring. */
6
+ export declare const MAX_READINESS_ADJUSTMENT = 10;
7
+ export type ContributorCalibrationSignal = {
8
+ /** How many (prediction, real-decision) pairings this login has in predicted_gate_calibration_ledger. */
9
+ sampleSize: number;
10
+ /** Fraction of those pairings where the predicted action matched the real decision. Clamped to [0, 1]
11
+ * before use, so a malformed upstream aggregate can never push the adjustment past its own clamp. */
12
+ agreementRate: number;
13
+ };
14
+ /**
15
+ * Adjusts a baseline readinessScore by a login's own predict-vs-real calibration history, clamped to
16
+ * +/-{@link MAX_READINESS_ADJUSTMENT} points and to the score's own [0, 100] range.
17
+ *
18
+ * Cold start -- no calibration signal, or fewer than {@link MIN_CALIBRATION_SAMPLES} pairings -- returns the
19
+ * baseline completely UNCHANGED: a never-seen (or barely-seen) actor gets no penalty and no bonus. A `null`
20
+ * baseline (no readiness score to begin with) stays `null` -- personalization never manufactures a score out
21
+ * of nothing.
22
+ */
23
+ export declare function applyContributorCalibration(baselineReadinessScore: number | null, calibration: ContributorCalibrationSignal | null | undefined): number | null;
@@ -0,0 +1,47 @@
1
+ // #2349: personalizes buildPredictedGateVerdict's readiness/confidence output using a contributor/miner's OWN
2
+ // historical predict-vs-real agreement (predicted_gate_calibration_ledger, written by
3
+ // src/review/predicted-gate-calibration-ledger.ts). Pure and D1-free by design -- this package never touches a
4
+ // database; the caller (src/mcp/server.ts, src/api/routes.ts) reads and aggregates the ledger for one login,
5
+ // then hands this module a plain, already-computed signal.
6
+ //
7
+ // SAFETY BOUNDARY (mirrors the design note in both src/review/contributor-calibration.ts and
8
+ // src/review/predicted-gate-calibration-ledger.ts): buildPredictedGateVerdict calls applyContributorCalibration
9
+ // strictly AFTER evaluateGateCheck has already finalized conclusion/blockers/warnings, and threads through
10
+ // ONLY the numeric readinessScore. This function never receives blockers or conclusion, so it is structurally
11
+ // incapable of flipping a hard blocker off -- not just clamped by convention, but by construction.
12
+ //
13
+ // PRIVACY: the calibration signal (sampleSize, agreementRate) is consumed here and never echoed back in
14
+ // PredictedGateVerdict -- only its clamped, bounded DOWNSTREAM EFFECT (a shifted readinessScore, itself
15
+ // already a public-facing concept) is returned. src/signals/redaction.ts's "no raw per-actor trust signal,
16
+ // ever public" boundary is preserved because the raw numbers never reach the output at all.
17
+ function clamp(value, min, max) {
18
+ return Math.min(max, Math.max(min, value));
19
+ }
20
+ /** Below this many historical (prediction, real-decision) pairings, a login's track record is treated as
21
+ * cold-start (unweighted baseline) -- too few samples to distinguish signal from noise. */
22
+ export const MIN_CALIBRATION_SAMPLES = 5;
23
+ /** Maximum points (out of the 0-100 readinessScore scale) personalization may add or subtract. Deliberately
24
+ * small relative to the 100-point scale: this is a confidence nudge, not a re-scoring. */
25
+ export const MAX_READINESS_ADJUSTMENT = 10;
26
+ /** Agreement rate that maps to a zero adjustment -- a coin-flip predict-vs-real track record earns neither a
27
+ * bonus nor a penalty. */
28
+ const NEUTRAL_AGREEMENT_RATE = 0.5;
29
+ /**
30
+ * Adjusts a baseline readinessScore by a login's own predict-vs-real calibration history, clamped to
31
+ * +/-{@link MAX_READINESS_ADJUSTMENT} points and to the score's own [0, 100] range.
32
+ *
33
+ * Cold start -- no calibration signal, or fewer than {@link MIN_CALIBRATION_SAMPLES} pairings -- returns the
34
+ * baseline completely UNCHANGED: a never-seen (or barely-seen) actor gets no penalty and no bonus. A `null`
35
+ * baseline (no readiness score to begin with) stays `null` -- personalization never manufactures a score out
36
+ * of nothing.
37
+ */
38
+ export function applyContributorCalibration(baselineReadinessScore, calibration) {
39
+ if (baselineReadinessScore === null)
40
+ return null;
41
+ if (!calibration || calibration.sampleSize < MIN_CALIBRATION_SAMPLES)
42
+ return baselineReadinessScore;
43
+ const agreementRate = clamp(calibration.agreementRate, 0, 1);
44
+ const rawAdjustment = (agreementRate - NEUTRAL_AGREEMENT_RATE) * 2 * MAX_READINESS_ADJUSTMENT;
45
+ const adjustment = clamp(rawAdjustment, -MAX_READINESS_ADJUSTMENT, MAX_READINESS_ADJUSTMENT);
46
+ return clamp(baselineReadinessScore + adjustment, 0, 100);
47
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Duplicate-winner adjudication (#dup-winner) — thin re-export shim (#4251). This file and
3
+ * `../duplicate-winner.ts` were accidental byte-identical forks living inside the same package (not a
4
+ * `src/`-vs-engine extraction pair; both copies were already inside `gittensory-engine`). The top-level module
5
+ * is the source of truth; see its doc comment for the full election-order rationale (claim-time election,
6
+ * anti-backdating semantics). Kept as a re-export (not deleted) so `advisory/gate-advisory.ts`'s existing
7
+ * `../signals/duplicate-winner.js` import keeps resolving without a call-site change.
8
+ */
9
+ export { isDuplicateClusterWinner, isDuplicateClusterWinnerByClaim, resolveDuplicateClusterWinnerNumber, type DuplicateClaimMember, } from "../duplicate-winner.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Duplicate-winner adjudication (#dup-winner) — thin re-export shim (#4251). This file and
3
+ * `../duplicate-winner.ts` were accidental byte-identical forks living inside the same package (not a
4
+ * `src/`-vs-engine extraction pair; both copies were already inside `gittensory-engine`). The top-level module
5
+ * is the source of truth; see its doc comment for the full election-order rationale (claim-time election,
6
+ * anti-backdating semantics). Kept as a re-export (not deleted) so `advisory/gate-advisory.ts`'s existing
7
+ * `../signals/duplicate-winner.js` import keeps resolving without a call-site change.
8
+ */
9
+ export { isDuplicateClusterWinner, isDuplicateClusterWinnerByClaim, resolveDuplicateClusterWinnerNumber, } from "../duplicate-winner.js";
@@ -0,0 +1,36 @@
1
+ import { isCodeFile } from "./test-evidence.js";
2
+ /** Keep local scoring aligned with slop/test-evidence matchers (#561 / #1046). */
3
+ export declare function isTestFile(file: string): boolean;
4
+ export { isCodeFile };
5
+ /** Machine-generated output (codegen, protobuf, source maps, typegen). */
6
+ export declare function isGeneratedFile(path: string): boolean;
7
+ /** Third-party / imported code that lives in the repo but is not the contributor's work. */
8
+ export declare function isVendoredFile(path: string): boolean;
9
+ /** Dependency lockfiles (resolved trees), e.g. `package-lock.json`, `go.sum`, `Cargo.lock`. */
10
+ export declare function isLockfile(path: string): boolean;
11
+ /** Minified bundles, e.g. `app.min.js`, `styles.min.css`. */
12
+ export declare function isMinifiedFile(path: string): boolean;
13
+ /** Documentation files (by extension or a top-level `docs/` directory). */
14
+ export declare function isDocsFile(path: string): boolean;
15
+ /** Dependency manifests (declare dependencies), e.g. `package.json`, `go.mod`, `pyproject.toml`. */
16
+ export declare function isDependencyManifestFile(path: string): boolean;
17
+ /**
18
+ * Build, lint, test-runner, monorepo, deploy, and environment configuration files. Distinct from
19
+ * dependency manifests (which declare external dependencies) and source code. Config-only diffs are
20
+ * lower-effort than genuine source changes, so slop signals can weight them differently (#561).
21
+ */
22
+ export declare function isConfigFile(path: string): boolean;
23
+ /**
24
+ * Files that masquerade as substantive source/work but are machine-produced or imported — the set a
25
+ * padded diff inflates its size with. Lockfiles, dependency manifests, and docs are legitimate change
26
+ * categories and are deliberately excluded here (they have their own matchers for reuse).
27
+ */
28
+ export declare function isNonSubstantivePaddingFile(path: string): boolean;
29
+ export type ChangedFileCategory = "minified" | "generated" | "vendored" | "lockfile" | "dependency_manifest" | "config" | "test" | "docs" | "source" | "other";
30
+ /**
31
+ * Classify a changed file into a single category. Non-substantive padding categories
32
+ * (minified/generated/vendored) take precedence so they are never miscounted as substantive source
33
+ * or test effort; lockfiles and dependency manifests are recognized before generic docs/source.
34
+ * Normalises the path once and threads the pre-computed parts through every `isX` matcher.
35
+ */
36
+ export declare function classifyChangedFile(path: string): ChangedFileCategory;