@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.
- package/CHANGELOG.md +94 -0
- package/LICENSE +661 -0
- package/README.md +676 -0
- package/dist/advisory/gate-advisory.d.ts +151 -0
- package/dist/advisory/gate-advisory.js +512 -0
- package/dist/ai-policy-map.d.ts +97 -0
- package/dist/ai-policy-map.js +377 -0
- package/dist/ams-policy-spec.d.ts +56 -0
- package/dist/ams-policy-spec.js +165 -0
- package/dist/calibration-dashboard.d.ts +23 -0
- package/dist/calibration-dashboard.js +65 -0
- package/dist/calibration-trend.d.ts +40 -0
- package/dist/calibration-trend.js +60 -0
- package/dist/contributor-fit.d.ts +24 -0
- package/dist/contributor-fit.js +50 -0
- package/dist/discovery-index-contract.d.ts +74 -0
- package/dist/discovery-index-contract.js +184 -0
- package/dist/discovery-soft-claim.d.ts +35 -0
- package/dist/discovery-soft-claim.js +50 -0
- package/dist/duplicate-winner.d.ts +57 -0
- package/dist/duplicate-winner.js +98 -0
- package/dist/feasibility.d.ts +27 -0
- package/dist/feasibility.js +86 -0
- package/dist/finding-severity-calibration.d.ts +116 -0
- package/dist/finding-severity-calibration.js +457 -0
- package/dist/fleet-run-manifest.d.ts +41 -0
- package/dist/fleet-run-manifest.js +151 -0
- package/dist/focus-manifest/guidance.d.ts +21 -0
- package/dist/focus-manifest/guidance.js +222 -0
- package/dist/focus-manifest.d.ts +826 -0
- package/dist/focus-manifest.js +2730 -0
- package/dist/gate-verdict-calibration.d.ts +111 -0
- package/dist/gate-verdict-calibration.js +411 -0
- package/dist/github/constants.d.ts +1 -0
- package/dist/github/constants.js +1 -0
- package/dist/github/sanitize-public-comment.d.ts +1 -0
- package/dist/github/sanitize-public-comment.js +26 -0
- package/dist/goal-model.d.ts +7 -0
- package/dist/goal-model.js +87 -0
- package/dist/governor/action-mode.d.ts +55 -0
- package/dist/governor/action-mode.js +74 -0
- package/dist/governor/budget-cap.d.ts +49 -0
- package/dist/governor/budget-cap.js +34 -0
- package/dist/governor/chokepoint.d.ts +58 -0
- package/dist/governor/chokepoint.js +269 -0
- package/dist/governor/kill-switch.d.ts +38 -0
- package/dist/governor/kill-switch.js +62 -0
- package/dist/governor/rate-limit.d.ts +40 -0
- package/dist/governor/rate-limit.js +63 -0
- package/dist/governor/reputation-throttle.d.ts +50 -0
- package/dist/governor/reputation-throttle.js +94 -0
- package/dist/governor/run-halt.d.ts +39 -0
- package/dist/governor/run-halt.js +83 -0
- package/dist/governor/self-plagiarism.d.ts +47 -0
- package/dist/governor/self-plagiarism.js +200 -0
- package/dist/governor/write-rate-limit.d.ts +52 -0
- package/dist/governor/write-rate-limit.js +132 -0
- package/dist/governor-ledger.d.ts +25 -0
- package/dist/governor-ledger.js +91 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +120 -0
- package/dist/issue-plan-decomposition.d.ts +18 -0
- package/dist/issue-plan-decomposition.js +56 -0
- package/dist/issue-rag-query.d.ts +12 -0
- package/dist/issue-rag-query.js +35 -0
- package/dist/local-scorer.d.ts +34 -0
- package/dist/local-scorer.js +36 -0
- package/dist/metadata-best-min-score.d.ts +9 -0
- package/dist/metadata-best-min-score.js +9 -0
- package/dist/metadata-best-pick.d.ts +9 -0
- package/dist/metadata-best-pick.js +9 -0
- package/dist/metadata-min-score.d.ts +9 -0
- package/dist/metadata-min-score.js +13 -0
- package/dist/metadata-top-min-score.d.ts +9 -0
- package/dist/metadata-top-min-score.js +13 -0
- package/dist/metadata-top-pick.d.ts +9 -0
- package/dist/metadata-top-pick.js +13 -0
- package/dist/miner/acceptance-criteria.d.ts +43 -0
- package/dist/miner/acceptance-criteria.js +77 -0
- package/dist/miner/agent-sdk-driver.d.ts +39 -0
- package/dist/miner/agent-sdk-driver.js +162 -0
- package/dist/miner/attempt-log.d.ts +29 -0
- package/dist/miner/attempt-log.js +100 -0
- package/dist/miner/attempt-metering.d.ts +39 -0
- package/dist/miner/attempt-metering.js +67 -0
- package/dist/miner/cli-subprocess-driver.d.ts +68 -0
- package/dist/miner/cli-subprocess-driver.js +272 -0
- package/dist/miner/coding-agent-driver.d.ts +33 -0
- package/dist/miner/coding-agent-driver.js +36 -0
- package/dist/miner/coding-agent-invoke.d.ts +11 -0
- package/dist/miner/coding-agent-invoke.js +80 -0
- package/dist/miner/coding-agent-mode.d.ts +25 -0
- package/dist/miner/coding-agent-mode.js +43 -0
- package/dist/miner/driver-factory.d.ts +82 -0
- package/dist/miner/driver-factory.js +169 -0
- package/dist/miner/harness-submission-trigger.d.ts +37 -0
- package/dist/miner/harness-submission-trigger.js +46 -0
- package/dist/miner/iterate-loop.d.ts +97 -0
- package/dist/miner/iterate-loop.js +278 -0
- package/dist/miner/iterate-policy.d.ts +98 -0
- package/dist/miner/iterate-policy.js +98 -0
- package/dist/miner/lint-guard.d.ts +50 -0
- package/dist/miner/lint-guard.js +68 -0
- package/dist/miner/local-write-tools.d.ts +81 -0
- package/dist/miner/local-write-tools.js +113 -0
- package/dist/miner/loop-reentry-policy.d.ts +36 -0
- package/dist/miner/loop-reentry-policy.js +53 -0
- package/dist/miner/repo-map.d.ts +51 -0
- package/dist/miner/repo-map.js +268 -0
- package/dist/miner/self-review-adapter.d.ts +106 -0
- package/dist/miner/self-review-adapter.js +83 -0
- package/dist/miner/submission-gate.d.ts +40 -0
- package/dist/miner/submission-gate.js +89 -0
- package/dist/miner/worktree-allocator.d.ts +59 -0
- package/dist/miner/worktree-allocator.js +60 -0
- package/dist/miner/worktree-pool.d.ts +55 -0
- package/dist/miner/worktree-pool.js +65 -0
- package/dist/miner-goal-lane-fit.d.ts +18 -0
- package/dist/miner-goal-lane-fit.js +76 -0
- package/dist/miner-goal-spec.d.ts +139 -0
- package/dist/miner-goal-spec.js +281 -0
- package/dist/miner-prediction-metrics.d.ts +16 -0
- package/dist/miner-prediction-metrics.js +61 -0
- package/dist/miner-telemetry.d.ts +32 -0
- package/dist/miner-telemetry.js +87 -0
- package/dist/objective-anchor.d.ts +125 -0
- package/dist/objective-anchor.js +401 -0
- package/dist/opportunity-competition.d.ts +6 -0
- package/dist/opportunity-competition.js +29 -0
- package/dist/opportunity-freshness.d.ts +18 -0
- package/dist/opportunity-freshness.js +55 -0
- package/dist/opportunity-metadata.d.ts +53 -0
- package/dist/opportunity-metadata.js +204 -0
- package/dist/opportunity-ranker.d.ts +46 -0
- package/dist/opportunity-ranker.js +74 -0
- package/dist/pairwise-calibration.d.ts +49 -0
- package/dist/pairwise-calibration.js +106 -0
- package/dist/phase7-calibration-loop.d.ts +136 -0
- package/dist/phase7-calibration-loop.js +437 -0
- package/dist/plan-blocked.d.ts +6 -0
- package/dist/plan-blocked.js +24 -0
- package/dist/plan-completed.d.ts +5 -0
- package/dist/plan-completed.js +6 -0
- package/dist/plan-completion.d.ts +6 -0
- package/dist/plan-completion.js +7 -0
- package/dist/plan-empty.d.ts +5 -0
- package/dist/plan-empty.js +6 -0
- package/dist/plan-export.d.ts +27 -0
- package/dist/plan-export.js +86 -0
- package/dist/plan-failure.d.ts +5 -0
- package/dist/plan-failure.js +6 -0
- package/dist/plan-overall-status.d.ts +6 -0
- package/dist/plan-overall-status.js +25 -0
- package/dist/plan-pending.d.ts +5 -0
- package/dist/plan-pending.js +6 -0
- package/dist/plan-progress-complete.d.ts +7 -0
- package/dist/plan-progress-complete.js +10 -0
- package/dist/plan-ready.d.ts +6 -0
- package/dist/plan-ready.js +12 -0
- package/dist/plan-running.d.ts +5 -0
- package/dist/plan-running.js +6 -0
- package/dist/plan-skipped.d.ts +5 -0
- package/dist/plan-skipped.js +6 -0
- package/dist/plan-step-count.d.ts +5 -0
- package/dist/plan-step-count.js +6 -0
- package/dist/plan-step-stats.d.ts +5 -0
- package/dist/plan-step-stats.js +6 -0
- package/dist/plan-templates.d.ts +25 -0
- package/dist/plan-templates.js +107 -0
- package/dist/plan-terminated.d.ts +6 -0
- package/dist/plan-terminated.js +9 -0
- package/dist/portfolio/non-convergence.d.ts +37 -0
- package/dist/portfolio/non-convergence.js +57 -0
- package/dist/portfolio/queue.d.ts +30 -0
- package/dist/portfolio/queue.js +150 -0
- package/dist/predicted-gate.d.ts +103 -0
- package/dist/predicted-gate.js +218 -0
- package/dist/prompt-packet.d.ts +11 -0
- package/dist/prompt-packet.js +34 -0
- package/dist/ranked-opportunity-best-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-best-min-score.js +9 -0
- package/dist/ranked-opportunity-best-pick.d.ts +8 -0
- package/dist/ranked-opportunity-best-pick.js +9 -0
- package/dist/ranked-opportunity-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-min-score.js +13 -0
- package/dist/ranked-opportunity-top-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-top-min-score.js +13 -0
- package/dist/review/advisory-ai-routing-config.d.ts +8 -0
- package/dist/review/advisory-ai-routing-config.js +40 -0
- package/dist/review/check-names.d.ts +1 -0
- package/dist/review/check-names.js +1 -0
- package/dist/review/cla-check.d.ts +49 -0
- package/dist/review/cla-check.js +71 -0
- package/dist/review/diff-file-priority.d.ts +1 -0
- package/dist/review/diff-file-priority.js +12 -0
- package/dist/review/enrichment-analyzer-names.d.ts +3 -0
- package/dist/review/enrichment-analyzer-names.js +63 -0
- package/dist/review/guardrail-config.d.ts +10 -0
- package/dist/review/guardrail-config.js +62 -0
- package/dist/review/linked-issue-hard-rules-config.d.ts +4 -0
- package/dist/review/linked-issue-hard-rules-config.js +79 -0
- package/dist/review/linked-issue-label-propagation.d.ts +9 -0
- package/dist/review/linked-issue-label-propagation.js +114 -0
- package/dist/review/pre-merge-checks.d.ts +28 -0
- package/dist/review/pre-merge-checks.js +66 -0
- package/dist/review/review-thread-findings.d.ts +1 -0
- package/dist/review/review-thread-findings.js +1 -0
- package/dist/review/safe-url.d.ts +5 -0
- package/dist/review/safe-url.js +137 -0
- package/dist/review/screenshot-table-gate.d.ts +87 -0
- package/dist/review/screenshot-table-gate.js +340 -0
- package/dist/review/unlinked-issue-guardrail-config.d.ts +9 -0
- package/dist/review/unlinked-issue-guardrail-config.js +44 -0
- package/dist/reviewer-consensus-calibration.d.ts +113 -0
- package/dist/reviewer-consensus-calibration.js +493 -0
- package/dist/reward-risk.d.ts +223 -0
- package/dist/reward-risk.js +618 -0
- package/dist/scoring/label-match.d.ts +4 -0
- package/dist/scoring/label-match.js +145 -0
- package/dist/scoring/model.d.ts +31 -0
- package/dist/scoring/model.js +157 -0
- package/dist/scoring/pending-pr-scenarios.d.ts +44 -0
- package/dist/scoring/pending-pr-scenarios.js +145 -0
- package/dist/scoring/preview.d.ts +246 -0
- package/dist/scoring/preview.js +1108 -0
- package/dist/scoring/types.d.ts +131 -0
- package/dist/scoring/types.js +7 -0
- package/dist/settings/auto-close-exempt.d.ts +10 -0
- package/dist/settings/auto-close-exempt.js +58 -0
- package/dist/settings/autonomy.d.ts +33 -0
- package/dist/settings/autonomy.js +80 -0
- package/dist/settings/command-authorization.d.ts +49 -0
- package/dist/settings/command-authorization.js +222 -0
- package/dist/settings/contributor-blacklist.d.ts +17 -0
- package/dist/settings/contributor-blacklist.js +86 -0
- package/dist/settings/moderation-rules.d.ts +51 -0
- package/dist/settings/moderation-rules.js +104 -0
- package/dist/settings/pr-type-label.d.ts +70 -0
- package/dist/settings/pr-type-label.js +149 -0
- package/dist/signals/change-guardrail.d.ts +49 -0
- package/dist/signals/change-guardrail.js +164 -0
- package/dist/signals/check-summary.d.ts +9 -0
- package/dist/signals/check-summary.js +12 -0
- package/dist/signals/contributor-calibration.d.ts +23 -0
- package/dist/signals/contributor-calibration.js +47 -0
- package/dist/signals/duplicate-winner.d.ts +9 -0
- package/dist/signals/duplicate-winner.js +9 -0
- package/dist/signals/path-matchers.d.ts +36 -0
- package/dist/signals/path-matchers.js +371 -0
- package/dist/signals/predicted-gate-engine.d.ts +62 -0
- package/dist/signals/predicted-gate-engine.js +895 -0
- package/dist/signals/preflight-limits.d.ts +14 -0
- package/dist/signals/preflight-limits.js +14 -0
- package/dist/signals/slop.d.ts +56 -0
- package/dist/signals/slop.js +324 -0
- package/dist/signals/test-evidence.d.ts +25 -0
- package/dist/signals/test-evidence.js +108 -0
- package/dist/subprocess-env.d.ts +23 -0
- package/dist/subprocess-env.js +74 -0
- package/dist/track-record-summary.d.ts +92 -0
- package/dist/track-record-summary.js +301 -0
- package/dist/types/manifest-deps-types.d.ts +514 -0
- package/dist/types/manifest-deps-types.js +4 -0
- package/dist/types/predicted-gate-types.d.ts +338 -0
- package/dist/types/predicted-gate-types.js +3 -0
- package/dist/types/reward-risk-types.d.ts +319 -0
- package/dist/types/reward-risk-types.js +11 -0
- package/dist/utils/json.d.ts +1 -0
- package/dist/utils/json.js +3 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- 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;
|