@miller-tech/uap 2.1.7 → 2.11.1

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 (143) hide show
  1. package/README.md +7 -6
  2. package/config/capacity-policy.json +32 -0
  3. package/config/classify-thresholds.json +9 -0
  4. package/config/supervise-policy.json +13 -0
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/bin/cli.js +87 -2
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/capacity/policy.d.ts +67 -0
  9. package/dist/capacity/policy.d.ts.map +1 -0
  10. package/dist/capacity/policy.js +133 -0
  11. package/dist/capacity/policy.js.map +1 -0
  12. package/dist/capacity/probe.d.ts +23 -0
  13. package/dist/capacity/probe.d.ts.map +1 -0
  14. package/dist/capacity/probe.js +93 -0
  15. package/dist/capacity/probe.js.map +1 -0
  16. package/dist/classify/baseline.d.ts +24 -0
  17. package/dist/classify/baseline.d.ts.map +1 -0
  18. package/dist/classify/baseline.js +140 -0
  19. package/dist/classify/baseline.js.map +1 -0
  20. package/dist/classify/index.d.ts +30 -0
  21. package/dist/classify/index.d.ts.map +1 -0
  22. package/dist/classify/index.js +84 -0
  23. package/dist/classify/index.js.map +1 -0
  24. package/dist/classify/shadow.d.ts +34 -0
  25. package/dist/classify/shadow.d.ts.map +1 -0
  26. package/dist/classify/shadow.js +33 -0
  27. package/dist/classify/shadow.js.map +1 -0
  28. package/dist/classify/types.d.ts +64 -0
  29. package/dist/classify/types.d.ts.map +1 -0
  30. package/dist/classify/types.js +37 -0
  31. package/dist/classify/types.js.map +1 -0
  32. package/dist/cli/classify.d.ts +24 -0
  33. package/dist/cli/classify.d.ts.map +1 -0
  34. package/dist/cli/classify.js +94 -0
  35. package/dist/cli/classify.js.map +1 -0
  36. package/dist/cli/deliver.d.ts.map +1 -1
  37. package/dist/cli/deliver.js +26 -0
  38. package/dist/cli/deliver.js.map +1 -1
  39. package/dist/cli/doctor.d.ts +9 -0
  40. package/dist/cli/doctor.d.ts.map +1 -0
  41. package/dist/cli/doctor.js +49 -0
  42. package/dist/cli/doctor.js.map +1 -0
  43. package/dist/cli/review.d.ts +12 -0
  44. package/dist/cli/review.d.ts.map +1 -0
  45. package/dist/cli/review.js +118 -0
  46. package/dist/cli/review.js.map +1 -0
  47. package/dist/cli/supervise.d.ts +8 -0
  48. package/dist/cli/supervise.d.ts.map +1 -0
  49. package/dist/cli/supervise.js +76 -0
  50. package/dist/cli/supervise.js.map +1 -0
  51. package/dist/delivery/convergence-loop.d.ts +8 -0
  52. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  53. package/dist/delivery/convergence-loop.js +1 -0
  54. package/dist/delivery/convergence-loop.js.map +1 -1
  55. package/dist/delivery/delivery-result.d.ts.map +1 -1
  56. package/dist/delivery/delivery-result.js +5 -0
  57. package/dist/delivery/delivery-result.js.map +1 -1
  58. package/dist/delivery/gate-evidence.d.ts +143 -0
  59. package/dist/delivery/gate-evidence.d.ts.map +1 -0
  60. package/dist/delivery/gate-evidence.js +239 -0
  61. package/dist/delivery/gate-evidence.js.map +1 -0
  62. package/dist/delivery/index.d.ts +1 -0
  63. package/dist/delivery/index.d.ts.map +1 -1
  64. package/dist/delivery/index.js +1 -0
  65. package/dist/delivery/index.js.map +1 -1
  66. package/dist/evals/routing-corpus.d.ts +35 -0
  67. package/dist/evals/routing-corpus.d.ts.map +1 -0
  68. package/dist/evals/routing-corpus.js +142 -0
  69. package/dist/evals/routing-corpus.js.map +1 -0
  70. package/dist/evals/routing-eval.d.ts +104 -0
  71. package/dist/evals/routing-eval.d.ts.map +1 -0
  72. package/dist/evals/routing-eval.js +231 -0
  73. package/dist/evals/routing-eval.js.map +1 -0
  74. package/dist/evals/tfidf.d.ts +35 -0
  75. package/dist/evals/tfidf.d.ts.map +1 -0
  76. package/dist/evals/tfidf.js +132 -0
  77. package/dist/evals/tfidf.js.map +1 -0
  78. package/dist/review/prepass.d.ts +88 -0
  79. package/dist/review/prepass.d.ts.map +1 -0
  80. package/dist/review/prepass.js +428 -0
  81. package/dist/review/prepass.js.map +1 -0
  82. package/dist/review/visual-captures.d.ts +60 -0
  83. package/dist/review/visual-captures.d.ts.map +1 -0
  84. package/dist/review/visual-captures.js +222 -0
  85. package/dist/review/visual-captures.js.map +1 -0
  86. package/dist/supervise/assess.d.ts +73 -0
  87. package/dist/supervise/assess.d.ts.map +1 -0
  88. package/dist/supervise/assess.js +215 -0
  89. package/dist/supervise/assess.js.map +1 -0
  90. package/dist/supervise/config.d.ts +13 -0
  91. package/dist/supervise/config.d.ts.map +1 -0
  92. package/dist/supervise/config.js +81 -0
  93. package/dist/supervise/config.js.map +1 -0
  94. package/dist/supervise/loop.d.ts +78 -0
  95. package/dist/supervise/loop.d.ts.map +1 -0
  96. package/dist/supervise/loop.js +311 -0
  97. package/dist/supervise/loop.js.map +1 -0
  98. package/dist/supervise/observe.d.ts +31 -0
  99. package/dist/supervise/observe.d.ts.map +1 -0
  100. package/dist/supervise/observe.js +172 -0
  101. package/dist/supervise/observe.js.map +1 -0
  102. package/dist/supervise/policy.d.ts +16 -0
  103. package/dist/supervise/policy.d.ts.map +1 -0
  104. package/dist/supervise/policy.js +147 -0
  105. package/dist/supervise/policy.js.map +1 -0
  106. package/dist/supervise/types.d.ts +109 -0
  107. package/dist/supervise/types.d.ts.map +1 -0
  108. package/dist/supervise/types.js +11 -0
  109. package/dist/supervise/types.js.map +1 -0
  110. package/docs/INDEX.md +15 -4
  111. package/docs/architecture/adr/0001-automode-risk-floor.md +75 -0
  112. package/docs/architecture/adr/0002-evidence-bound-ship.md +81 -0
  113. package/docs/guides/CAPACITY_POLICY.md +99 -0
  114. package/docs/performance/automode-risk-calibration.md +72 -0
  115. package/docs/performance/reliability-ladder.md +78 -0
  116. package/docs/plans/system1-uplift-2026-09-18.md +190 -0
  117. package/docs/reference/automode-risk.md +134 -0
  118. package/docs/reference/evidence-bound-ship.md +146 -0
  119. package/docs/reference/review-prepass.md +77 -0
  120. package/docs/reference/routing-evals.md +71 -0
  121. package/docs/reference/semantic-supervisor.md +140 -0
  122. package/docs/reference/system1-classifier.md +103 -0
  123. package/docs/reference/visual-captures.md +113 -0
  124. package/package.json +5 -3
  125. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  126. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
  127. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  128. package/src/policies/enforcers/enforcement_self_protect.py +25 -2
  129. package/src/policies/enforcers/evidence_bound_ship.py +257 -0
  130. package/src/policies/enforcers/expert_review_required.py +120 -0
  131. package/src/policies/schemas/policies/evidence-bound-ship.md +106 -0
  132. package/src/policies/schemas/policies/visual-verification.md +8 -0
  133. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  134. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  135. package/tools/agents/scripts/anthropic_proxy.py +782 -19
  136. package/tools/agents/scripts/project_telemetry.py +52 -0
  137. package/tools/agents/scripts/tool_risk.py +789 -0
  138. package/tools/agents/scripts/tool_risk_calibrate.py +255 -0
  139. package/tools/agents/tests/fixtures/tool_risk_cases.json +124 -0
  140. package/tools/agents/tests/test_enforcer_escape_hatches.py +13 -0
  141. package/tools/agents/tests/test_gate_evidence.py +56 -0
  142. package/tools/agents/tests/test_tool_risk.py +521 -0
  143. package/tools/agents/tests/test_verbatim_compact.py +629 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * ROUTING EVAL — measures rank-1 *description separability* of the pattern,
3
+ * droid, and skill registries against curated fixtures with planted traps.
4
+ *
5
+ * This is a separability proxy, not a replica of production routing: the
6
+ * production pattern router is keyword-substring multi-match, and droids and
7
+ * skills are routed by the LLM reading descriptions. What this eval guards
8
+ * is the shared failure mode — descriptions that stop being distinguishable
9
+ * as the registry grows. For patterns, a parity baseline against the real
10
+ * PatternRouter is reported alongside (informational, not gated).
11
+ *
12
+ * Kind-scoped ranking: a fixture case declares its surface (pattern, droid,
13
+ * or skill) and is ranked only against that surface, matching how the
14
+ * surfaces are actually consumed (pattern router vs Task subagent_type vs
15
+ * Skill invocation). Threshold failures exit nonzero in CI.
16
+ */
17
+ import { type RoutingEntry, type RoutingKind } from './routing-corpus.js';
18
+ /** All routable surfaces — add a fourth kind here, not in two places. */
19
+ export declare const ROUTING_KINDS: RoutingKind[];
20
+ export interface RoutingEvalCase {
21
+ kind: RoutingKind;
22
+ query: string;
23
+ /** Expected entry id within the kind, or null for "should match nothing". */
24
+ expect: string | null;
25
+ /** Planted trap: deliberately confusable with a near-neighbor entry. */
26
+ trap?: boolean;
27
+ note?: string;
28
+ }
29
+ export interface CaseResult {
30
+ kind: RoutingKind;
31
+ query: string;
32
+ expect: string | null;
33
+ actual: string | null;
34
+ score: number;
35
+ /** rank1 minus rank2 score for positive cases — near-tie fragility gauge. */
36
+ margin: number;
37
+ /** Distinct content tokens shared between query and top-ranked entry. */
38
+ overlap: number;
39
+ pass: boolean;
40
+ trap: boolean;
41
+ }
42
+ export interface KindReport {
43
+ total: number;
44
+ passed: number;
45
+ rank1Accuracy: number;
46
+ }
47
+ export interface RoutingEvalReport {
48
+ total: number;
49
+ passed: number;
50
+ failed: number;
51
+ /** Rank-1 accuracy over positive (non-null expect) cases. */
52
+ rank1Accuracy: number;
53
+ trapTotal: number;
54
+ trapPassed: number;
55
+ negativeTotal: number;
56
+ negativePassed: number;
57
+ perKind: Record<string, KindReport>;
58
+ /** Corpus entries embedded from a body fallback (weak routing surface). */
59
+ weakEntries: string[];
60
+ failures: CaseResult[];
61
+ /** Smallest rank1–rank2 margin among passing positive cases. */
62
+ minMargin: number;
63
+ /** Entries whose bare name exists under more than one kind (cross-kind collision). */
64
+ crossKindNames: string[];
65
+ /** PatternRouter (keyword multi-match) inclusion rate on pattern positives. */
66
+ patternRouterBaseline: {
67
+ total: number;
68
+ included: number;
69
+ } | null;
70
+ threshold: number;
71
+ meetsThreshold: boolean;
72
+ }
73
+ export declare const DEFAULT_CASES_PATH = "evals/routing/cases.json";
74
+ export declare const DEFAULT_THRESHOLD = 0.9;
75
+ /**
76
+ * Backstop ceiling on a negative case's top cosine score. The real negative
77
+ * gate is token overlap (below) — calibration showed cosine alone cannot
78
+ * separate near-domain negatives (single high-idf shared token, score up to
79
+ * ~0.31) from weak positives (down to ~0.23), so this only catches
80
+ * pathological full-coverage matches.
81
+ */
82
+ export declare const DEFAULT_NEGATIVE_MAX_SCORE = 0.5;
83
+ /**
84
+ * Real negative gate: a route claim resting on at most one shared content
85
+ * token is not a route. Negatives with 2+ distinct shared tokens fail.
86
+ * Ranker-agnostic, unlike a cosine ceiling.
87
+ */
88
+ export declare const DEFAULT_NEGATIVE_MAX_OVERLAP = 1;
89
+ /** Load and validate eval cases from JSON. Throws on malformed fixtures. */
90
+ export declare function loadEvalCases(path: string): RoutingEvalCase[];
91
+ export interface RunRoutingEvalOptions {
92
+ casesPath?: string;
93
+ threshold?: number;
94
+ negativeMaxScore?: number;
95
+ negativeMaxOverlap?: number;
96
+ /** Pre-loaded cases (unit tests inject synthetic fixtures). */
97
+ cases?: RoutingEvalCase[];
98
+ /** Pre-loaded corpus (unit tests inject synthetic corpora). */
99
+ corpus?: RoutingEntry[];
100
+ }
101
+ export declare function runRoutingEval(projectDir: string, options?: RunRoutingEvalOptions): RoutingEvalReport;
102
+ /** Human-readable summary for CLI output. */
103
+ export declare function formatReport(report: RoutingEvalReport): string;
104
+ //# sourceMappingURL=routing-eval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-eval.d.ts","sourceRoot":"","sources":["../../src/evals/routing-eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAG7B,yEAAyE;AACzE,eAAO,MAAM,aAAa,EAAE,WAAW,EAAkC,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,2EAA2E;IAC3E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,+EAA+E;IAC/E,qBAAqB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CA4B7D;AA8CD,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CAgHnB;AAED,6CAA6C;AAC7C,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA6B9D"}
@@ -0,0 +1,231 @@
1
+ /**
2
+ * ROUTING EVAL — measures rank-1 *description separability* of the pattern,
3
+ * droid, and skill registries against curated fixtures with planted traps.
4
+ *
5
+ * This is a separability proxy, not a replica of production routing: the
6
+ * production pattern router is keyword-substring multi-match, and droids and
7
+ * skills are routed by the LLM reading descriptions. What this eval guards
8
+ * is the shared failure mode — descriptions that stop being distinguishable
9
+ * as the registry grows. For patterns, a parity baseline against the real
10
+ * PatternRouter is reported alongside (informational, not gated).
11
+ *
12
+ * Kind-scoped ranking: a fixture case declares its surface (pattern, droid,
13
+ * or skill) and is ranked only against that surface, matching how the
14
+ * surfaces are actually consumed (pattern router vs Task subagent_type vs
15
+ * Skill invocation). Threshold failures exit nonzero in CI.
16
+ */
17
+ import { existsSync, readFileSync } from 'fs';
18
+ import { join } from 'path';
19
+ import { TfIdfIndex } from './tfidf.js';
20
+ import { loadRoutingCorpus, } from './routing-corpus.js';
21
+ import { PatternRouter } from '../coordination/pattern-router.js';
22
+ /** All routable surfaces — add a fourth kind here, not in two places. */
23
+ export const ROUTING_KINDS = ['pattern', 'droid', 'skill'];
24
+ export const DEFAULT_CASES_PATH = 'evals/routing/cases.json';
25
+ export const DEFAULT_THRESHOLD = 0.9;
26
+ /**
27
+ * Backstop ceiling on a negative case's top cosine score. The real negative
28
+ * gate is token overlap (below) — calibration showed cosine alone cannot
29
+ * separate near-domain negatives (single high-idf shared token, score up to
30
+ * ~0.31) from weak positives (down to ~0.23), so this only catches
31
+ * pathological full-coverage matches.
32
+ */
33
+ export const DEFAULT_NEGATIVE_MAX_SCORE = 0.5;
34
+ /**
35
+ * Real negative gate: a route claim resting on at most one shared content
36
+ * token is not a route. Negatives with 2+ distinct shared tokens fail.
37
+ * Ranker-agnostic, unlike a cosine ceiling.
38
+ */
39
+ export const DEFAULT_NEGATIVE_MAX_OVERLAP = 1;
40
+ /** Load and validate eval cases from JSON. Throws on malformed fixtures. */
41
+ export function loadEvalCases(path) {
42
+ if (!existsSync(path)) {
43
+ throw new Error(`routing eval cases not found: ${path}`);
44
+ }
45
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
46
+ // Honor the fixture format version: a ranker swap changes score semantics,
47
+ // so unknown versions must force recalibration instead of silently reusing
48
+ // fixtures calibrated for a different scorer.
49
+ if (raw.version !== 1) {
50
+ throw new Error(`routing eval cases malformed: unsupported version "${String(raw.version)}" in ${path}`);
51
+ }
52
+ if (!Array.isArray(raw.cases)) {
53
+ throw new Error(`routing eval cases malformed: missing "cases" array in ${path}`);
54
+ }
55
+ const kinds = new Set(['pattern', 'droid', 'skill']);
56
+ return raw.cases.map((c, i) => {
57
+ const kase = c;
58
+ if (!kinds.has(String(kase.kind))) {
59
+ throw new Error(`case ${i}: invalid kind "${String(kase.kind)}"`);
60
+ }
61
+ if (typeof kase.query !== 'string' || kase.query.length < 5) {
62
+ throw new Error(`case ${i}: query missing or too short`);
63
+ }
64
+ if (kase.expect !== null && typeof kase.expect !== 'string') {
65
+ throw new Error(`case ${i}: expect must be a string id or null`);
66
+ }
67
+ return kase;
68
+ });
69
+ }
70
+ function evaluateCase(kase, entries, index, negativeMaxScore, negativeMaxOverlap) {
71
+ const scores = index.score(kase.query);
72
+ let topIdx = -1;
73
+ let top = 0;
74
+ let second = 0;
75
+ for (let i = 0; i < scores.length; i++) {
76
+ if (scores[i] > top) {
77
+ second = top;
78
+ top = scores[i];
79
+ topIdx = i;
80
+ }
81
+ else if (scores[i] > second) {
82
+ second = scores[i];
83
+ }
84
+ }
85
+ const actual = topIdx >= 0 ? entries[topIdx].id : null;
86
+ const overlap = topIdx >= 0 ? index.sharedTokens(kase.query, topIdx).length : 0;
87
+ const pass = kase.expect === null
88
+ ? overlap <= negativeMaxOverlap && top <= negativeMaxScore
89
+ : actual === kase.expect;
90
+ const round = (n) => Math.round(n * 1000) / 1000;
91
+ return {
92
+ kind: kase.kind,
93
+ query: kase.query,
94
+ expect: kase.expect,
95
+ actual,
96
+ score: round(top),
97
+ margin: round(top - second),
98
+ overlap,
99
+ pass,
100
+ trap: kase.trap === true,
101
+ };
102
+ }
103
+ function emptyKindReport() {
104
+ return { total: 0, passed: 0, rank1Accuracy: 0 };
105
+ }
106
+ export function runRoutingEval(projectDir, options = {}) {
107
+ const threshold = options.threshold ?? DEFAULT_THRESHOLD;
108
+ const negativeMaxScore = options.negativeMaxScore ?? DEFAULT_NEGATIVE_MAX_SCORE;
109
+ const negativeMaxOverlap = options.negativeMaxOverlap ?? DEFAULT_NEGATIVE_MAX_OVERLAP;
110
+ const cases = options.cases ?? loadEvalCases(join(projectDir, options.casesPath ?? DEFAULT_CASES_PATH));
111
+ const corpus = options.corpus ?? loadRoutingCorpus(projectDir);
112
+ // Fixture integrity: every expected id must exist in its kind's corpus.
113
+ for (const kase of cases) {
114
+ if (kase.expect === null)
115
+ continue;
116
+ if (!corpus.some((e) => e.kind === kase.kind && e.id === kase.expect)) {
117
+ throw new Error(`fixture integrity: case "${kase.query.slice(0, 60)}…" expects unknown ${kase.kind} "${kase.expect}"`);
118
+ }
119
+ }
120
+ // One TF-IDF index per kind — kind-scoped ranking matches how the
121
+ // surfaces are consumed (pattern router vs droid registry vs skills).
122
+ const indexes = new Map();
123
+ for (const kind of ROUTING_KINDS) {
124
+ const entries = corpus.filter((e) => e.kind === kind);
125
+ if (entries.length > 0) {
126
+ indexes.set(kind, { entries, index: new TfIdfIndex(entries.map((e) => e.text)) });
127
+ }
128
+ }
129
+ const results = [];
130
+ for (const kase of cases) {
131
+ const surface = indexes.get(kase.kind);
132
+ if (!surface) {
133
+ throw new Error(`routing corpus is empty for kind "${kase.kind}"`);
134
+ }
135
+ results.push(evaluateCase(kase, surface.entries, surface.index, negativeMaxScore, negativeMaxOverlap));
136
+ }
137
+ const positives = results.filter((r) => r.expect !== null);
138
+ if (positives.length === 0) {
139
+ // Guard against a silently vacuous pass: with no positive cases the
140
+ // accuracy gate is meaningless, so this is an error, not a 100% run.
141
+ throw new Error('routing eval has no positive cases — refusing to pass vacuously');
142
+ }
143
+ const traps = results.filter((r) => r.trap);
144
+ const negatives = results.filter((r) => r.expect === null);
145
+ const failures = results.filter((r) => !r.pass);
146
+ const rank1Accuracy = positives.filter((r) => r.pass).length / positives.length;
147
+ const perKind = {};
148
+ for (const kind of ROUTING_KINDS) {
149
+ const kindPositives = positives.filter((r) => r.kind === kind);
150
+ const report = emptyKindReport();
151
+ report.total = kindPositives.length;
152
+ report.passed = kindPositives.filter((r) => r.pass).length;
153
+ report.rank1Accuracy = report.total === 0 ? 1 : report.passed / report.total;
154
+ perKind[kind] = report;
155
+ }
156
+ const passingPositives = positives.filter((r) => r.pass);
157
+ const minMargin = Math.min(...passingPositives.map((r) => r.margin));
158
+ // Cross-kind name collisions: the production LLM sees all registries at
159
+ // once, so a name that exists as both droid and skill is a real routing
160
+ // hazard even though the eval ranks kind-scoped. Surfaced as a warning.
161
+ const kindsByName = new Map();
162
+ for (const e of corpus) {
163
+ if (!kindsByName.has(e.id))
164
+ kindsByName.set(e.id, new Set());
165
+ kindsByName.get(e.id).add(e.kind);
166
+ }
167
+ const crossKindNames = [...kindsByName.entries()]
168
+ .filter(([, kinds]) => kinds.size > 1)
169
+ .map(([name]) => name);
170
+ // Parity baseline: run pattern positives through the production keyword
171
+ // router and measure inclusion rate (multi-match, so rank-1 n/a).
172
+ // Skipped for injected synthetic corpora (unit tests).
173
+ let patternRouterBaseline = null;
174
+ if (options.corpus === undefined) {
175
+ const router = new PatternRouter();
176
+ if (router.loadPatterns(projectDir)) {
177
+ const patternCases = cases.filter((c) => c.kind === 'pattern' && c.expect !== null);
178
+ const included = patternCases.filter((c) => router.matchPatterns(c.query).some((p) => String(p.id) === c.expect)).length;
179
+ patternRouterBaseline = { total: patternCases.length, included };
180
+ }
181
+ }
182
+ return {
183
+ total: results.length,
184
+ passed: results.filter((r) => r.pass).length,
185
+ failed: failures.length,
186
+ rank1Accuracy,
187
+ trapTotal: traps.length,
188
+ trapPassed: traps.filter((r) => r.pass).length,
189
+ negativeTotal: negatives.length,
190
+ negativePassed: negatives.filter((r) => r.pass).length,
191
+ perKind,
192
+ weakEntries: corpus.filter((e) => !e.hasDescription).map((e) => e.key),
193
+ failures,
194
+ minMargin,
195
+ crossKindNames,
196
+ patternRouterBaseline,
197
+ threshold,
198
+ // Gate: rank-1 accuracy over positives must clear the threshold, and
199
+ // every negative case must stay below the no-match score ceiling —
200
+ // false-routing garbage input is a hard fail, not a percentage point.
201
+ meetsThreshold: rank1Accuracy >= threshold && negatives.every((r) => r.pass),
202
+ };
203
+ }
204
+ /** Human-readable summary for CLI output. */
205
+ export function formatReport(report) {
206
+ const pct = (n) => `${(n * 100).toFixed(1)}%`;
207
+ const lines = [
208
+ `rank-1 accuracy: ${pct(report.rank1Accuracy)} (threshold ${pct(report.threshold)})`,
209
+ `cases: ${report.passed}/${report.total} passed` +
210
+ ` | traps: ${report.trapPassed}/${report.trapTotal}` +
211
+ ` | negatives: ${report.negativePassed}/${report.negativeTotal}`,
212
+ ...Object.entries(report.perKind).map(([kind, r]) => ` ${kind}: ${pct(r.rank1Accuracy)} (${r.passed}/${r.total})`),
213
+ `min rank1-rank2 margin (passing positives): ${report.minMargin.toFixed(3)}`,
214
+ ];
215
+ if (report.patternRouterBaseline) {
216
+ const b = report.patternRouterBaseline;
217
+ lines.push(`production pattern-router inclusion baseline: ${b.included}/${b.total}`);
218
+ }
219
+ if (report.crossKindNames.length > 0) {
220
+ lines.push(`cross-kind name collisions: ${report.crossKindNames.join(', ')}`);
221
+ }
222
+ if (report.weakEntries.length > 0) {
223
+ lines.push(`weak entries (no description, body fallback): ${report.weakEntries.join(', ')}`);
224
+ }
225
+ for (const f of report.failures) {
226
+ lines.push(`FAIL [${f.kind}] "${f.query.slice(0, 70)}" → got ${f.actual ?? '∅'} (${f.score}), expected ${f.expect ?? 'no match'}`);
227
+ }
228
+ lines.push(report.meetsThreshold ? 'PASS' : 'FAIL — below threshold');
229
+ return lines.join('\n');
230
+ }
231
+ //# sourceMappingURL=routing-eval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-eval.js","sourceRoot":"","sources":["../../src/evals/routing-eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EACL,iBAAiB,GAGlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,yEAAyE;AACzE,MAAM,CAAC,MAAM,aAAa,GAAkB,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAwD1E,MAAM,CAAC,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAC9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAE9C,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAA2C,CAAC;IAC9F,2EAA2E;IAC3E,2EAA2E;IAC3E,8CAA8C;IAC9C,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,sDAAsD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC3G,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,0DAA0D,IAAI,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,CAA6B,CAAC;QAC3C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,8BAA8B,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,IAAuB,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CACnB,IAAqB,EACrB,OAAuB,EACvB,KAAiB,EACjB,gBAAwB,EACxB,kBAA0B;IAE1B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;IAChB,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC;YACpB,MAAM,GAAG,GAAG,CAAC;YACb,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAChB,MAAM,GAAG,CAAC,CAAC;QACb,CAAC;aAAM,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;YAC9B,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,MAAM,IAAI,GACR,IAAI,CAAC,MAAM,KAAK,IAAI;QAClB,CAAC,CAAC,OAAO,IAAI,kBAAkB,IAAI,GAAG,IAAI,gBAAgB;QAC1D,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC;IAC7B,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM;QACN,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC;QACjB,MAAM,EAAE,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC;QAC3B,OAAO;QACP,IAAI;QACJ,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;AACnD,CAAC;AAaD,MAAM,UAAU,cAAc,CAC5B,UAAkB,EAClB,UAAiC,EAAE;IAEnC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iBAAiB,CAAC;IACzD,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IAChF,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,4BAA4B,CAAC;IACtF,MAAM,KAAK,GACT,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAC,CAAC;IAC5F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAE/D,wEAAwE;IACxE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;YAAE,SAAS;QACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,GAAG,CACtG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,sEAAsE;IACtE,MAAM,OAAO,GAAG,IAAI,GAAG,EAA+D,CAAC;IACvF,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACtD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,CAAC,IAAI,CACV,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAC3D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,oEAAoE;QACpE,qEAAqE;QACrE,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAEhF,MAAM,OAAO,GAA+B,EAAE,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;QACjC,MAAM,CAAC,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC;QACpC,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC3D,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;IACzB,CAAC;IAED,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAErE,wEAAwE;IACxE,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA4B,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC7D,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,cAAc,GAAG,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;SAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;SACrC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IAEzB,wEAAwE;IACxE,kEAAkE;IAClE,uDAAuD;IACvD,IAAI,qBAAqB,GAA+C,IAAI,CAAC;IAC7E,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;YACpF,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACzC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CACrE,CAAC,MAAM,CAAC;YACT,qBAAqB,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;QACnE,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM;QAC5C,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,aAAa;QACb,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM;QAC9C,aAAa,EAAE,SAAS,CAAC,MAAM;QAC/B,cAAc,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM;QACtD,OAAO;QACP,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACtE,QAAQ;QACR,SAAS;QACT,cAAc;QACd,qBAAqB;QACrB,SAAS;QACT,qEAAqE;QACrE,mEAAmE;QACnE,sEAAsE;QACtE,cAAc,EACZ,aAAa,IAAI,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KAC/D,CAAC;AACJ,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,YAAY,CAAC,MAAyB;IACpD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACtD,MAAM,KAAK,GAAG;QACZ,oBAAoB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG;QACpF,UAAU,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,SAAS;YAC9C,aAAa,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,SAAS,EAAE;YACpD,iBAAiB,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,aAAa,EAAE;QAClE,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CACnC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,GAAG,CAC7E;QACD,+CAA+C,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;KAC7E,CAAC;IACF,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,MAAM,CAAC,qBAAqB,CAAC;QACvC,KAAK,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,+BAA+B,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,iDAAiD,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,MAAM,IAAI,UAAU,EAAE,CACvH,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC;IACtE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * TF-IDF RANKER — pure-TypeScript retrieval scorer for routing evals.
3
+ *
4
+ * Airgap-pure by design: no embedding model, no network, deterministic output.
5
+ * Used by the routing eval harness (0.4) to measure whether corpus entries
6
+ * (patterns, droids, skills) stay separable as the registry grows.
7
+ */
8
+ /**
9
+ * Naive suffix stemmer — good enough for routing separability without a
10
+ * dependency. Handles the dominant mismatch classes seen in calibration:
11
+ * plurals ("interfaces"/"interface") and -ing forms ("tweaking"/"tweak").
12
+ */
13
+ export declare function stem(token: string): string;
14
+ /** Tokenize: lowercase, split on non-alphanumerics, stem, drop short + stopwords. */
15
+ export declare function tokenize(text: string): string[];
16
+ export declare class TfIdfIndex {
17
+ private readonly idf;
18
+ private vectors;
19
+ private norms;
20
+ /** Raw tokenized documents, kept for shared-token overlap analysis. */
21
+ private docTokens;
22
+ constructor(documents: string[]);
23
+ /** Distinct content tokens shared between the query and a document. */
24
+ sharedTokens(query: string, docIndex: number): string[];
25
+ private build;
26
+ private vectorize;
27
+ /** Cosine score of the query against every document, in document order. */
28
+ score(query: string): number[];
29
+ /** Index of the highest-scoring document, or -1 when all scores are 0. */
30
+ topIndex(query: string): {
31
+ index: number;
32
+ score: number;
33
+ };
34
+ }
35
+ //# sourceMappingURL=tfidf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tfidf.d.ts","sourceRoot":"","sources":["../../src/evals/tfidf.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAU1C;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/C;AAyBD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IACjD,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,KAAK,CAAgB;IAC7B,uEAAuE;IACvE,OAAO,CAAC,SAAS,CAAkB;gBAEvB,SAAS,EAAE,MAAM,EAAE;IAI/B,uEAAuE;IACvE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAOvD,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,SAAS;IAYjB,2EAA2E;IAC3E,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAM9B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAY1D"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * TF-IDF RANKER — pure-TypeScript retrieval scorer for routing evals.
3
+ *
4
+ * Airgap-pure by design: no embedding model, no network, deterministic output.
5
+ * Used by the routing eval harness (0.4) to measure whether corpus entries
6
+ * (patterns, droids, skills) stay separable as the registry grows.
7
+ */
8
+ /** Minimal stopword list — keeps domain tokens, drops glue words. */
9
+ const STOPWORDS = new Set([
10
+ 'the', 'a', 'an', 'and', 'or', 'but', 'if', 'then', 'else', 'for', 'to',
11
+ 'of', 'in', 'on', 'at', 'by', 'with', 'from', 'as', 'is', 'are', 'was',
12
+ 'were', 'be', 'been', 'being', 'it', 'its', 'this', 'that', 'these',
13
+ 'those', 'i', 'we', 'you', 'they', 'he', 'she', 'do', 'does', 'did',
14
+ 'use', 'using', 'used', 'when', 'what', 'which', 'who', 'how', 'why',
15
+ 'our', 'your', 'their', 'my', 'his', 'her', 'its', 'not', 'no', 'so',
16
+ 'can', 'could', 'should', 'would', 'will', 'shall', 'may', 'might',
17
+ 'into', 'onto', 'upon', 'via', 'per', 'about', 'over', 'under', 'again',
18
+ ]);
19
+ /**
20
+ * Naive suffix stemmer — good enough for routing separability without a
21
+ * dependency. Handles the dominant mismatch classes seen in calibration:
22
+ * plurals ("interfaces"/"interface") and -ing forms ("tweaking"/"tweak").
23
+ */
24
+ export function stem(token) {
25
+ if (token.length <= 4)
26
+ return token;
27
+ if (token.endsWith('ing') && token.length > 6)
28
+ return token.slice(0, -3);
29
+ if (token.endsWith('ed') && token.length > 5)
30
+ return token.slice(0, -2);
31
+ // "-es" after a sibilant: processes→process, classes→class (before plain -s)
32
+ if (/(s|x|ch|sh)es$/.test(token) && token.length > 5)
33
+ return token.slice(0, -2);
34
+ if (token.endsWith('s') && !token.endsWith('ss') && !token.endsWith('us')) {
35
+ return token.slice(0, -1);
36
+ }
37
+ return token;
38
+ }
39
+ /** Tokenize: lowercase, split on non-alphanumerics, stem, drop short + stopwords. */
40
+ export function tokenize(text) {
41
+ return text
42
+ .toLowerCase()
43
+ .split(/[^a-z0-9]+/)
44
+ .map(stem)
45
+ .filter((t) => t.length >= 2 && !STOPWORDS.has(t));
46
+ }
47
+ function l2Norm(vec) {
48
+ let sum = 0;
49
+ for (const v of vec.values())
50
+ sum += v * v;
51
+ return Math.sqrt(sum);
52
+ }
53
+ function cosine(a, aNorm, b, bNorm) {
54
+ if (aNorm === 0 || bNorm === 0)
55
+ return 0;
56
+ // Iterate the smaller vector for speed.
57
+ const [small, big] = a.size <= b.size ? [a, b] : [b, a];
58
+ let dot = 0;
59
+ for (const [term, w] of small) {
60
+ const other = big.get(term);
61
+ if (other !== undefined)
62
+ dot += w * other;
63
+ }
64
+ return dot / (aNorm * bNorm);
65
+ }
66
+ export class TfIdfIndex {
67
+ idf = new Map();
68
+ vectors = [];
69
+ norms = [];
70
+ /** Raw tokenized documents, kept for shared-token overlap analysis. */
71
+ docTokens = [];
72
+ constructor(documents) {
73
+ this.build(documents);
74
+ }
75
+ /** Distinct content tokens shared between the query and a document. */
76
+ sharedTokens(query, docIndex) {
77
+ const doc = this.docTokens[docIndex];
78
+ if (!doc)
79
+ return [];
80
+ const docSet = new Set(doc);
81
+ return [...new Set(tokenize(query))].filter((t) => docSet.has(t));
82
+ }
83
+ build(documents) {
84
+ const tokenized = documents.map((d) => tokenize(d));
85
+ this.docTokens = tokenized;
86
+ const docFreq = new Map();
87
+ for (const tokens of tokenized) {
88
+ for (const term of new Set(tokens)) {
89
+ docFreq.set(term, (docFreq.get(term) ?? 0) + 1);
90
+ }
91
+ }
92
+ const total = Math.max(documents.length, 1);
93
+ for (const [term, df] of docFreq) {
94
+ this.idf.set(term, Math.log(1 + total / df));
95
+ }
96
+ this.vectors = tokenized.map((tokens) => this.vectorize(tokens));
97
+ this.norms = this.vectors.map((v) => l2Norm(v));
98
+ }
99
+ vectorize(tokens) {
100
+ const termFreq = new Map();
101
+ for (const t of tokens)
102
+ termFreq.set(t, (termFreq.get(t) ?? 0) + 1);
103
+ const vec = new Map();
104
+ for (const [term, count] of termFreq) {
105
+ const idf = this.idf.get(term);
106
+ if (idf === undefined)
107
+ continue; // unseen term (query side)
108
+ vec.set(term, (1 + Math.log(count)) * idf);
109
+ }
110
+ return vec;
111
+ }
112
+ /** Cosine score of the query against every document, in document order. */
113
+ score(query) {
114
+ const qv = this.vectorize(tokenize(query));
115
+ const qn = l2Norm(qv);
116
+ return this.vectors.map((dv, i) => cosine(qv, qn, dv, this.norms[i]));
117
+ }
118
+ /** Index of the highest-scoring document, or -1 when all scores are 0. */
119
+ topIndex(query) {
120
+ const scores = this.score(query);
121
+ let best = -1;
122
+ let bestScore = 0;
123
+ for (let i = 0; i < scores.length; i++) {
124
+ if (scores[i] > bestScore) {
125
+ bestScore = scores[i];
126
+ best = i;
127
+ }
128
+ }
129
+ return { index: best, score: bestScore };
130
+ }
131
+ }
132
+ //# sourceMappingURL=tfidf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tfidf.js","sourceRoot":"","sources":["../../src/evals/tfidf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * DETERMINISTIC REVIEW PRE-PASS (uplift 0.2)
3
+ *
4
+ * A ruleset scanner that runs BEFORE the LLM reviewers in the parallel
5
+ * review protocol (open-code-review precedent: deterministic pipelines first,
6
+ * LLM second). Reviewers receive pre-filtered, line-anchored findings so
7
+ * their tokens go to judgment, not pattern-matching.
8
+ *
9
+ * Design stance: precision over recall. Every rule here fires on a pattern
10
+ * that is almost always worth a human/reviewer glance; ambiguous heuristics
11
+ * belong to the LLM reviewers, not to this pass. Findings are advisory input
12
+ * to review — the blocking behavior stays in the expert-review enforcer,
13
+ * which is unchanged.
14
+ */
15
+ export type PrePassCategory = 'secrets' | 'injection' | 'error-handling' | 'concurrency' | 'sql';
16
+ export type PrePassSeverity = 'high' | 'medium';
17
+ export interface PrePassFinding {
18
+ rule: string;
19
+ category: PrePassCategory;
20
+ severity: PrePassSeverity;
21
+ file: string;
22
+ line: number;
23
+ snippet: string;
24
+ message: string;
25
+ }
26
+ interface LineRule {
27
+ name: string;
28
+ category: PrePassCategory;
29
+ severity: PrePassSeverity;
30
+ pattern: RegExp;
31
+ message: string;
32
+ /** When set, only files with one of these extensions are scanned. */
33
+ codeOnly?: boolean;
34
+ }
35
+ /**
36
+ * Line-anchored rules. Keep each one boringly precise — a finding that cries
37
+ * wolf trains reviewers to ignore the whole pass.
38
+ */
39
+ export declare const LINE_RULES: LineRule[];
40
+ /**
41
+ * Redact secret material from a finding snippet. The artifact's purpose is to
42
+ * be pasted into LLM reviewer prompts — a secrets scanner must not multiply
43
+ * copies of what it detects. The file:line anchor is enough for a reviewer
44
+ * to inspect the secret in place.
45
+ */
46
+ export declare function redactSecrets(snippet: string): string;
47
+ /** Scan one file's content. `file` is the repo-relative path used in findings. */
48
+ export declare function scanFileContent(file: string, content: string): PrePassFinding[];
49
+ export interface PrePassOptions {
50
+ /** Explicit repo-relative file list (default: changed files vs upstream). */
51
+ files?: string[];
52
+ projectDir?: string;
53
+ }
54
+ /** Resolve the target file list for a pre-pass run. */
55
+ export declare function collectPrePassTargets(options: PrePassOptions): string[];
56
+ /** Scan a resolved target list. */
57
+ export declare function runPrePass(options: PrePassOptions): {
58
+ findings: PrePassFinding[];
59
+ filesScanned: string[];
60
+ };
61
+ /** Percent-encode branch to a filename slug — mirrors slug_for() in the
62
+ * expert_review_required enforcer exactly (the artifact must land where the
63
+ * enforcer looks). */
64
+ export declare function branchSlug(branch: string): string;
65
+ export interface PrePassArtifact {
66
+ ran_at: string;
67
+ head: string | null;
68
+ files_scanned: number;
69
+ finding_count: number;
70
+ findings: PrePassFinding[];
71
+ }
72
+ /**
73
+ * Write the pre-pass artifact to .uap/reviews/<branch-slug>.pre-pass.json.
74
+ *
75
+ * Deliberately a SIBLING of the review artifact (<slug>.json), not a block
76
+ * inside it: the expert-review enforcer treats the existence of <slug>.json
77
+ * as "a review happened", and every substantive check in it is conditional on
78
+ * keys the pre-pass never writes. A pre-pass-only <slug>.json would satisfy
79
+ * the blocking gate with zero reviewers — so this advisory pass must never
80
+ * create that file. Consolidation reads this sibling and merges its findings
81
+ * into the real artifact along with the verdict.
82
+ */
83
+ export declare function writePrePassArtifact(projectDir: string, findings: PrePassFinding[], filesScanned: number): {
84
+ path: string;
85
+ artifact: PrePassArtifact;
86
+ } | null;
87
+ export {};
88
+ //# sourceMappingURL=prepass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepass.d.ts","sourceRoot":"","sources":["../../src/review/prepass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,GAAG,aAAa,GAAG,KAAK,CAAC;AACjG,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,EAqKhC,CAAC;AAIF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOrD;AAQD,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA6B/E;AAwFD,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,uDAAuD;AACvD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,EAAE,CA2BvE;AAED,mCAAmC;AACnC,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG;IACnD,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAgBA;AAED;;sBAEsB;AACtB,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,cAAc,EAAE,EAC1B,YAAY,EAAE,MAAM,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,GAAG,IAAI,CAgCpD"}