@query-doctor/core 0.10.10 → 0.12.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.
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ const require_taxonomy = require("./taxonomy.cjs");
3
+ //#region src/ci/policy.ts
4
+ /**
5
+ * Opinionated safe defaults (epic #3493). A captured new query already ran in a
6
+ * test (tested by definition), so it's informational; everything that marks a
7
+ * genuine, diff-introduced problem fails by default. The set is deliberately
8
+ * narrow so the gate under-fires.
9
+ */
10
+ const DEFAULT_CONDITION_POLICIES = {
11
+ "untested-data-access": "fail",
12
+ "schema-drift": "fail",
13
+ "new-query": "warn",
14
+ "regression-beyond-threshold": "fail",
15
+ "high-value-nudge": "fail"
16
+ };
17
+ /**
18
+ * Fallback for a condition with neither a repo setting nor a documented default:
19
+ * surface it, don't block. Under-firing on an unrecognised condition is the safe
20
+ * side of the line.
21
+ */
22
+ const FALLBACK_POLICY = "warn";
23
+ /** Resolve the effective policy for a condition: repo config, else default, else fallback. */
24
+ function policyFor(condition, config = {}) {
25
+ return config[condition] ?? DEFAULT_CONDITION_POLICIES[condition] ?? "warn";
26
+ }
27
+ /**
28
+ * Fold a policy over a condition's base (taxonomy) conclusion. `fail` respects
29
+ * the taxonomy; `warn` caps a failure at `neutral` (still surfaced, never
30
+ * blocking); `off` is handled by dropping the condition before this runs.
31
+ */
32
+ function applyPolicy(base, policy) {
33
+ if (policy === "warn") return base === "failure" ? "neutral" : base;
34
+ return base;
35
+ }
36
+ const SEVERITY = {
37
+ success: 0,
38
+ neutral: 1,
39
+ failure: 2
40
+ };
41
+ /**
42
+ * Evaluate a run: apply each condition's policy, then take the run conclusion as
43
+ * the worst surfaced conclusion. Pure — the same (results, config) always yields
44
+ * the same evaluation.
45
+ *
46
+ * Only diff-introduced conditions should reach here: detectors (e.g. the crude
47
+ * gate, #3496) evaluate the PR diff, so pre-existing untouched code never
48
+ * produces a result and can't trip a condition. With no results, the run passes.
49
+ */
50
+ function evaluateRun(results, config = {}) {
51
+ const conditions = [];
52
+ for (const result of results) {
53
+ const policy = policyFor(result.condition, config);
54
+ if (policy === "off") continue;
55
+ conditions.push({
56
+ condition: result.condition,
57
+ policy,
58
+ verdict: result.verdict,
59
+ conclusion: applyPolicy(require_taxonomy.conclusionForVerdict(result.verdict), policy)
60
+ });
61
+ }
62
+ return {
63
+ conclusion: conditions.reduce((worst, c) => SEVERITY[c.conclusion] > SEVERITY[worst] ? c.conclusion : worst, "success"),
64
+ conditions
65
+ };
66
+ }
67
+ //#endregion
68
+ exports.DEFAULT_CONDITION_POLICIES = DEFAULT_CONDITION_POLICIES;
69
+ exports.FALLBACK_POLICY = FALLBACK_POLICY;
70
+ exports.evaluateRun = evaluateRun;
71
+ exports.policyFor = policyFor;
72
+
73
+ //# sourceMappingURL=policy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.cjs","names":["conclusionForVerdict"],"sources":["../../src/ci/policy.ts"],"sourcesContent":["import { conclusionForVerdict, type CiConclusion } from \"./taxonomy.ts\";\nimport type { CiVerdict } from \"./verdict.ts\";\n\n// The policy engine (#3500). Turns the single hard-coded gate into a\n// configurable condition set: a per-repo config gives each condition\n// `fail | warn | off`, and this pure function folds (config, condition results)\n// into one run conclusion plus the per-condition verdicts that produced it.\n//\n// It sits one layer above the taxonomy (#3498): the taxonomy says what a\n// verdict *would* conclude; the policy decides whether this repo lets that\n// condition fail, soften to a warning, or be suppressed entirely.\n\n/** What a repo lets a condition do: fail the run, warn-only, or ignore it. */\nexport type ConditionPolicy = \"fail\" | \"warn\" | \"off\";\n\n/** One condition's evaluated outcome, before policy is applied. */\nexport interface ConditionResult {\n /** Stable condition key, matched against the repo config (e.g. \"untested-data-access\"). */\n condition: string;\n verdict: CiVerdict;\n}\n\n/** A condition after policy resolution — its effective conclusion and the policy that shaped it. */\nexport interface ResolvedCondition {\n condition: string;\n policy: ConditionPolicy;\n verdict: CiVerdict;\n conclusion: CiConclusion;\n}\n\nexport interface RunEvaluation {\n /** The run's overall conclusion — the worst surfaced per-condition conclusion. */\n conclusion: CiConclusion;\n /** Per-condition results that were surfaced (an `off` condition is dropped). */\n conditions: ResolvedCondition[];\n}\n\n/** Per-repo policy: condition key → its policy. Absent keys fall back to defaults. */\nexport type RepoPolicyConfig = Record<string, ConditionPolicy>;\n\n/**\n * Opinionated safe defaults (epic #3493). A captured new query already ran in a\n * test (tested by definition), so it's informational; everything that marks a\n * genuine, diff-introduced problem fails by default. The set is deliberately\n * narrow so the gate under-fires.\n */\nexport const DEFAULT_CONDITION_POLICIES: RepoPolicyConfig = {\n \"untested-data-access\": \"fail\",\n \"schema-drift\": \"fail\", // #3296 owns the signal; this just gates on it\n \"new-query\": \"warn\", // captured ⇒ tested — informational, surfaced, non-blocking\n \"regression-beyond-threshold\": \"fail\", // #3104\n \"high-value-nudge\": \"fail\", // narrow set only\n};\n\n/**\n * Fallback for a condition with neither a repo setting nor a documented default:\n * surface it, don't block. Under-firing on an unrecognised condition is the safe\n * side of the line.\n */\nexport const FALLBACK_POLICY: ConditionPolicy = \"warn\";\n\n/** Resolve the effective policy for a condition: repo config, else default, else fallback. */\nexport function policyFor(\n condition: string,\n config: RepoPolicyConfig = {},\n): ConditionPolicy {\n return (\n config[condition] ??\n DEFAULT_CONDITION_POLICIES[condition] ??\n FALLBACK_POLICY\n );\n}\n\n/**\n * Fold a policy over a condition's base (taxonomy) conclusion. `fail` respects\n * the taxonomy; `warn` caps a failure at `neutral` (still surfaced, never\n * blocking); `off` is handled by dropping the condition before this runs.\n */\nfunction applyPolicy(\n base: CiConclusion,\n policy: ConditionPolicy,\n): CiConclusion {\n if (policy === \"warn\") return base === \"failure\" ? \"neutral\" : base;\n return base;\n}\n\nconst SEVERITY: Record<CiConclusion, number> = {\n success: 0,\n neutral: 1,\n failure: 2,\n};\n\n/**\n * Evaluate a run: apply each condition's policy, then take the run conclusion as\n * the worst surfaced conclusion. Pure — the same (results, config) always yields\n * the same evaluation.\n *\n * Only diff-introduced conditions should reach here: detectors (e.g. the crude\n * gate, #3496) evaluate the PR diff, so pre-existing untouched code never\n * produces a result and can't trip a condition. With no results, the run passes.\n */\nexport function evaluateRun(\n results: ConditionResult[],\n config: RepoPolicyConfig = {},\n): RunEvaluation {\n const conditions: ResolvedCondition[] = [];\n for (const result of results) {\n const policy = policyFor(result.condition, config);\n if (policy === \"off\") continue; // suppressed entirely — not surfaced\n conditions.push({\n condition: result.condition,\n policy,\n verdict: result.verdict,\n conclusion: applyPolicy(conclusionForVerdict(result.verdict), policy),\n });\n }\n const conclusion = conditions.reduce<CiConclusion>(\n (worst, c) =>\n SEVERITY[c.conclusion] > SEVERITY[worst] ? c.conclusion : worst,\n \"success\",\n );\n return { conclusion, conditions };\n}\n"],"mappings":";;;;;;;;;AA8CA,MAAa,6BAA+C;CAC1D,wBAAwB;CACxB,gBAAgB;CAChB,aAAa;CACb,+BAA+B;CAC/B,oBAAoB;CACrB;;;;;;AAOD,MAAa,kBAAmC;;AAGhD,SAAgB,UACd,WACA,SAA2B,EAAE,EACZ;AACjB,QACE,OAAO,cACP,2BAA2B,cAAA;;;;;;;AAU/B,SAAS,YACP,MACA,QACc;AACd,KAAI,WAAW,OAAQ,QAAO,SAAS,YAAY,YAAY;AAC/D,QAAO;;AAGT,MAAM,WAAyC;CAC7C,SAAS;CACT,SAAS;CACT,SAAS;CACV;;;;;;;;;;AAWD,SAAgB,YACd,SACA,SAA2B,EAAE,EACd;CACf,MAAM,aAAkC,EAAE;AAC1C,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,SAAS,UAAU,OAAO,WAAW,OAAO;AAClD,MAAI,WAAW,MAAO;AACtB,aAAW,KAAK;GACd,WAAW,OAAO;GAClB;GACA,SAAS,OAAO;GAChB,YAAY,YAAYA,iBAAAA,qBAAqB,OAAO,QAAQ,EAAE,OAAO;GACtE,CAAC;;AAOJ,QAAO;EAAE,YALU,WAAW,QAC3B,OAAO,MACN,SAAS,EAAE,cAAc,SAAS,SAAS,EAAE,aAAa,OAC5D,UACD;EACoB;EAAY"}
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { CiVerdict } from "./verdict.cjs";
4
+ import { CiConclusion } from "./taxonomy.cjs";
5
+
6
+ //#region src/ci/policy.d.ts
7
+ /** What a repo lets a condition do: fail the run, warn-only, or ignore it. */
8
+ type ConditionPolicy = "fail" | "warn" | "off";
9
+ /** One condition's evaluated outcome, before policy is applied. */
10
+ interface ConditionResult {
11
+ /** Stable condition key, matched against the repo config (e.g. "untested-data-access"). */
12
+ condition: string;
13
+ verdict: CiVerdict;
14
+ }
15
+ /** A condition after policy resolution — its effective conclusion and the policy that shaped it. */
16
+ interface ResolvedCondition {
17
+ condition: string;
18
+ policy: ConditionPolicy;
19
+ verdict: CiVerdict;
20
+ conclusion: CiConclusion;
21
+ }
22
+ interface RunEvaluation {
23
+ /** The run's overall conclusion — the worst surfaced per-condition conclusion. */
24
+ conclusion: CiConclusion;
25
+ /** Per-condition results that were surfaced (an `off` condition is dropped). */
26
+ conditions: ResolvedCondition[];
27
+ }
28
+ /** Per-repo policy: condition key → its policy. Absent keys fall back to defaults. */
29
+ type RepoPolicyConfig = Record<string, ConditionPolicy>;
30
+ /**
31
+ * Opinionated safe defaults (epic #3493). A captured new query already ran in a
32
+ * test (tested by definition), so it's informational; everything that marks a
33
+ * genuine, diff-introduced problem fails by default. The set is deliberately
34
+ * narrow so the gate under-fires.
35
+ */
36
+ declare const DEFAULT_CONDITION_POLICIES: RepoPolicyConfig;
37
+ /**
38
+ * Fallback for a condition with neither a repo setting nor a documented default:
39
+ * surface it, don't block. Under-firing on an unrecognised condition is the safe
40
+ * side of the line.
41
+ */
42
+ declare const FALLBACK_POLICY: ConditionPolicy;
43
+ /** Resolve the effective policy for a condition: repo config, else default, else fallback. */
44
+ declare function policyFor(condition: string, config?: RepoPolicyConfig): ConditionPolicy;
45
+ /**
46
+ * Evaluate a run: apply each condition's policy, then take the run conclusion as
47
+ * the worst surfaced conclusion. Pure — the same (results, config) always yields
48
+ * the same evaluation.
49
+ *
50
+ * Only diff-introduced conditions should reach here: detectors (e.g. the crude
51
+ * gate, #3496) evaluate the PR diff, so pre-existing untouched code never
52
+ * produces a result and can't trip a condition. With no results, the run passes.
53
+ */
54
+ declare function evaluateRun(results: ConditionResult[], config?: RepoPolicyConfig): RunEvaluation;
55
+ //#endregion
56
+ export { ConditionPolicy, ConditionResult, DEFAULT_CONDITION_POLICIES, FALLBACK_POLICY, RepoPolicyConfig, ResolvedCondition, RunEvaluation, evaluateRun, policyFor };
57
+ //# sourceMappingURL=policy.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.cts","names":[],"sources":["../../src/ci/policy.ts"],"mappings":";;;;;;;KAaY,eAAA;;UAGK,eAAA;EAHU;EAKzB,SAAA;EACA,OAAA,EAAS,SAAA;AAAA;AAHX;AAAA,UAOiB,iBAAA;EACf,SAAA;EACA,MAAA,EAAQ,eAAA;EACR,OAAA,EAAS,SAAA;EACT,UAAA,EAAY,YAAA;AAAA;AAAA,UAGG,aAAA;EAXG;EAalB,UAAA,EAAY,YAAA;EAToB;EAWhC,UAAA,EAAY,iBAAA;AAAA;;KAIF,gBAAA,GAAmB,MAAA,SAAe,eAAA;;;;;;;cAQjC,0BAAA,EAA4B,gBAAA;;;;;AAhBzC;cA6Ba,eAAA,EAAiB,eAAA;;iBAGd,SAAA,CACd,SAAA,UACA,MAAA,GAAQ,gBAAA,GACP,eAAA;;;;;;;AA3BH;;;iBA+DgB,WAAA,CACd,OAAA,EAAS,eAAA,IACT,MAAA,GAAQ,gBAAA,GACP,aAAA"}
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { CiVerdict } from "./verdict.mjs";
4
+ import { CiConclusion } from "./taxonomy.mjs";
5
+
6
+ //#region src/ci/policy.d.ts
7
+ /** What a repo lets a condition do: fail the run, warn-only, or ignore it. */
8
+ type ConditionPolicy = "fail" | "warn" | "off";
9
+ /** One condition's evaluated outcome, before policy is applied. */
10
+ interface ConditionResult {
11
+ /** Stable condition key, matched against the repo config (e.g. "untested-data-access"). */
12
+ condition: string;
13
+ verdict: CiVerdict;
14
+ }
15
+ /** A condition after policy resolution — its effective conclusion and the policy that shaped it. */
16
+ interface ResolvedCondition {
17
+ condition: string;
18
+ policy: ConditionPolicy;
19
+ verdict: CiVerdict;
20
+ conclusion: CiConclusion;
21
+ }
22
+ interface RunEvaluation {
23
+ /** The run's overall conclusion — the worst surfaced per-condition conclusion. */
24
+ conclusion: CiConclusion;
25
+ /** Per-condition results that were surfaced (an `off` condition is dropped). */
26
+ conditions: ResolvedCondition[];
27
+ }
28
+ /** Per-repo policy: condition key → its policy. Absent keys fall back to defaults. */
29
+ type RepoPolicyConfig = Record<string, ConditionPolicy>;
30
+ /**
31
+ * Opinionated safe defaults (epic #3493). A captured new query already ran in a
32
+ * test (tested by definition), so it's informational; everything that marks a
33
+ * genuine, diff-introduced problem fails by default. The set is deliberately
34
+ * narrow so the gate under-fires.
35
+ */
36
+ declare const DEFAULT_CONDITION_POLICIES: RepoPolicyConfig;
37
+ /**
38
+ * Fallback for a condition with neither a repo setting nor a documented default:
39
+ * surface it, don't block. Under-firing on an unrecognised condition is the safe
40
+ * side of the line.
41
+ */
42
+ declare const FALLBACK_POLICY: ConditionPolicy;
43
+ /** Resolve the effective policy for a condition: repo config, else default, else fallback. */
44
+ declare function policyFor(condition: string, config?: RepoPolicyConfig): ConditionPolicy;
45
+ /**
46
+ * Evaluate a run: apply each condition's policy, then take the run conclusion as
47
+ * the worst surfaced conclusion. Pure — the same (results, config) always yields
48
+ * the same evaluation.
49
+ *
50
+ * Only diff-introduced conditions should reach here: detectors (e.g. the crude
51
+ * gate, #3496) evaluate the PR diff, so pre-existing untouched code never
52
+ * produces a result and can't trip a condition. With no results, the run passes.
53
+ */
54
+ declare function evaluateRun(results: ConditionResult[], config?: RepoPolicyConfig): RunEvaluation;
55
+ //#endregion
56
+ export { ConditionPolicy, ConditionResult, DEFAULT_CONDITION_POLICIES, FALLBACK_POLICY, RepoPolicyConfig, ResolvedCondition, RunEvaluation, evaluateRun, policyFor };
57
+ //# sourceMappingURL=policy.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.mts","names":[],"sources":["../../src/ci/policy.ts"],"mappings":";;;;;;;KAaY,eAAA;;UAGK,eAAA;EAHU;EAKzB,SAAA;EACA,OAAA,EAAS,SAAA;AAAA;AAHX;AAAA,UAOiB,iBAAA;EACf,SAAA;EACA,MAAA,EAAQ,eAAA;EACR,OAAA,EAAS,SAAA;EACT,UAAA,EAAY,YAAA;AAAA;AAAA,UAGG,aAAA;EAXG;EAalB,UAAA,EAAY,YAAA;EAToB;EAWhC,UAAA,EAAY,iBAAA;AAAA;;KAIF,gBAAA,GAAmB,MAAA,SAAe,eAAA;;;;;;;cAQjC,0BAAA,EAA4B,gBAAA;;;;;AAhBzC;cA6Ba,eAAA,EAAiB,eAAA;;iBAGd,SAAA,CACd,SAAA,UACA,MAAA,GAAQ,gBAAA,GACP,eAAA;;;;;;;AA3BH;;;iBA+DgB,WAAA,CACd,OAAA,EAAS,eAAA,IACT,MAAA,GAAQ,gBAAA,GACP,aAAA"}
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ import { conclusionForVerdict } from "./taxonomy.mjs";
3
+ //#region src/ci/policy.ts
4
+ /**
5
+ * Opinionated safe defaults (epic #3493). A captured new query already ran in a
6
+ * test (tested by definition), so it's informational; everything that marks a
7
+ * genuine, diff-introduced problem fails by default. The set is deliberately
8
+ * narrow so the gate under-fires.
9
+ */
10
+ const DEFAULT_CONDITION_POLICIES = {
11
+ "untested-data-access": "fail",
12
+ "schema-drift": "fail",
13
+ "new-query": "warn",
14
+ "regression-beyond-threshold": "fail",
15
+ "high-value-nudge": "fail"
16
+ };
17
+ /**
18
+ * Fallback for a condition with neither a repo setting nor a documented default:
19
+ * surface it, don't block. Under-firing on an unrecognised condition is the safe
20
+ * side of the line.
21
+ */
22
+ const FALLBACK_POLICY = "warn";
23
+ /** Resolve the effective policy for a condition: repo config, else default, else fallback. */
24
+ function policyFor(condition, config = {}) {
25
+ return config[condition] ?? DEFAULT_CONDITION_POLICIES[condition] ?? "warn";
26
+ }
27
+ /**
28
+ * Fold a policy over a condition's base (taxonomy) conclusion. `fail` respects
29
+ * the taxonomy; `warn` caps a failure at `neutral` (still surfaced, never
30
+ * blocking); `off` is handled by dropping the condition before this runs.
31
+ */
32
+ function applyPolicy(base, policy) {
33
+ if (policy === "warn") return base === "failure" ? "neutral" : base;
34
+ return base;
35
+ }
36
+ const SEVERITY = {
37
+ success: 0,
38
+ neutral: 1,
39
+ failure: 2
40
+ };
41
+ /**
42
+ * Evaluate a run: apply each condition's policy, then take the run conclusion as
43
+ * the worst surfaced conclusion. Pure — the same (results, config) always yields
44
+ * the same evaluation.
45
+ *
46
+ * Only diff-introduced conditions should reach here: detectors (e.g. the crude
47
+ * gate, #3496) evaluate the PR diff, so pre-existing untouched code never
48
+ * produces a result and can't trip a condition. With no results, the run passes.
49
+ */
50
+ function evaluateRun(results, config = {}) {
51
+ const conditions = [];
52
+ for (const result of results) {
53
+ const policy = policyFor(result.condition, config);
54
+ if (policy === "off") continue;
55
+ conditions.push({
56
+ condition: result.condition,
57
+ policy,
58
+ verdict: result.verdict,
59
+ conclusion: applyPolicy(conclusionForVerdict(result.verdict), policy)
60
+ });
61
+ }
62
+ return {
63
+ conclusion: conditions.reduce((worst, c) => SEVERITY[c.conclusion] > SEVERITY[worst] ? c.conclusion : worst, "success"),
64
+ conditions
65
+ };
66
+ }
67
+ //#endregion
68
+ export { DEFAULT_CONDITION_POLICIES, FALLBACK_POLICY, evaluateRun, policyFor };
69
+
70
+ //# sourceMappingURL=policy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.mjs","names":[],"sources":["../../src/ci/policy.ts"],"sourcesContent":["import { conclusionForVerdict, type CiConclusion } from \"./taxonomy.ts\";\nimport type { CiVerdict } from \"./verdict.ts\";\n\n// The policy engine (#3500). Turns the single hard-coded gate into a\n// configurable condition set: a per-repo config gives each condition\n// `fail | warn | off`, and this pure function folds (config, condition results)\n// into one run conclusion plus the per-condition verdicts that produced it.\n//\n// It sits one layer above the taxonomy (#3498): the taxonomy says what a\n// verdict *would* conclude; the policy decides whether this repo lets that\n// condition fail, soften to a warning, or be suppressed entirely.\n\n/** What a repo lets a condition do: fail the run, warn-only, or ignore it. */\nexport type ConditionPolicy = \"fail\" | \"warn\" | \"off\";\n\n/** One condition's evaluated outcome, before policy is applied. */\nexport interface ConditionResult {\n /** Stable condition key, matched against the repo config (e.g. \"untested-data-access\"). */\n condition: string;\n verdict: CiVerdict;\n}\n\n/** A condition after policy resolution — its effective conclusion and the policy that shaped it. */\nexport interface ResolvedCondition {\n condition: string;\n policy: ConditionPolicy;\n verdict: CiVerdict;\n conclusion: CiConclusion;\n}\n\nexport interface RunEvaluation {\n /** The run's overall conclusion — the worst surfaced per-condition conclusion. */\n conclusion: CiConclusion;\n /** Per-condition results that were surfaced (an `off` condition is dropped). */\n conditions: ResolvedCondition[];\n}\n\n/** Per-repo policy: condition key → its policy. Absent keys fall back to defaults. */\nexport type RepoPolicyConfig = Record<string, ConditionPolicy>;\n\n/**\n * Opinionated safe defaults (epic #3493). A captured new query already ran in a\n * test (tested by definition), so it's informational; everything that marks a\n * genuine, diff-introduced problem fails by default. The set is deliberately\n * narrow so the gate under-fires.\n */\nexport const DEFAULT_CONDITION_POLICIES: RepoPolicyConfig = {\n \"untested-data-access\": \"fail\",\n \"schema-drift\": \"fail\", // #3296 owns the signal; this just gates on it\n \"new-query\": \"warn\", // captured ⇒ tested — informational, surfaced, non-blocking\n \"regression-beyond-threshold\": \"fail\", // #3104\n \"high-value-nudge\": \"fail\", // narrow set only\n};\n\n/**\n * Fallback for a condition with neither a repo setting nor a documented default:\n * surface it, don't block. Under-firing on an unrecognised condition is the safe\n * side of the line.\n */\nexport const FALLBACK_POLICY: ConditionPolicy = \"warn\";\n\n/** Resolve the effective policy for a condition: repo config, else default, else fallback. */\nexport function policyFor(\n condition: string,\n config: RepoPolicyConfig = {},\n): ConditionPolicy {\n return (\n config[condition] ??\n DEFAULT_CONDITION_POLICIES[condition] ??\n FALLBACK_POLICY\n );\n}\n\n/**\n * Fold a policy over a condition's base (taxonomy) conclusion. `fail` respects\n * the taxonomy; `warn` caps a failure at `neutral` (still surfaced, never\n * blocking); `off` is handled by dropping the condition before this runs.\n */\nfunction applyPolicy(\n base: CiConclusion,\n policy: ConditionPolicy,\n): CiConclusion {\n if (policy === \"warn\") return base === \"failure\" ? \"neutral\" : base;\n return base;\n}\n\nconst SEVERITY: Record<CiConclusion, number> = {\n success: 0,\n neutral: 1,\n failure: 2,\n};\n\n/**\n * Evaluate a run: apply each condition's policy, then take the run conclusion as\n * the worst surfaced conclusion. Pure — the same (results, config) always yields\n * the same evaluation.\n *\n * Only diff-introduced conditions should reach here: detectors (e.g. the crude\n * gate, #3496) evaluate the PR diff, so pre-existing untouched code never\n * produces a result and can't trip a condition. With no results, the run passes.\n */\nexport function evaluateRun(\n results: ConditionResult[],\n config: RepoPolicyConfig = {},\n): RunEvaluation {\n const conditions: ResolvedCondition[] = [];\n for (const result of results) {\n const policy = policyFor(result.condition, config);\n if (policy === \"off\") continue; // suppressed entirely — not surfaced\n conditions.push({\n condition: result.condition,\n policy,\n verdict: result.verdict,\n conclusion: applyPolicy(conclusionForVerdict(result.verdict), policy),\n });\n }\n const conclusion = conditions.reduce<CiConclusion>(\n (worst, c) =>\n SEVERITY[c.conclusion] > SEVERITY[worst] ? c.conclusion : worst,\n \"success\",\n );\n return { conclusion, conditions };\n}\n"],"mappings":";;;;;;;;;AA8CA,MAAa,6BAA+C;CAC1D,wBAAwB;CACxB,gBAAgB;CAChB,aAAa;CACb,+BAA+B;CAC/B,oBAAoB;CACrB;;;;;;AAOD,MAAa,kBAAmC;;AAGhD,SAAgB,UACd,WACA,SAA2B,EAAE,EACZ;AACjB,QACE,OAAO,cACP,2BAA2B,cAAA;;;;;;;AAU/B,SAAS,YACP,MACA,QACc;AACd,KAAI,WAAW,OAAQ,QAAO,SAAS,YAAY,YAAY;AAC/D,QAAO;;AAGT,MAAM,WAAyC;CAC7C,SAAS;CACT,SAAS;CACT,SAAS;CACV;;;;;;;;;;AAWD,SAAgB,YACd,SACA,SAA2B,EAAE,EACd;CACf,MAAM,aAAkC,EAAE;AAC1C,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,SAAS,UAAU,OAAO,WAAW,OAAO;AAClD,MAAI,WAAW,MAAO;AACtB,aAAW,KAAK;GACd,WAAW,OAAO;GAClB;GACA,SAAS,OAAO;GAChB,YAAY,YAAY,qBAAqB,OAAO,QAAQ,EAAE,OAAO;GACtE,CAAC;;AAOJ,QAAO;EAAE,YALU,WAAW,QAC3B,OAAO,MACN,SAAS,EAAE,cAAc,SAAS,SAAS,EAAE,aAAa,OAC5D,UACD;EACoB;EAAY"}
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ //#region src/ci/taxonomy.ts
3
+ const CLASS_TO_CONCLUSION = {
4
+ pass: "success",
5
+ "coverage-gap": "failure",
6
+ finding: "failure",
7
+ "uncertain-conservative-flag": "failure",
8
+ "setup-broken": "failure",
9
+ "infra-error": "neutral"
10
+ };
11
+ /** Map one verdict class to its CI conclusion. Total and deterministic. */
12
+ function conclusionForVerdictClass(verdictClass) {
13
+ return CLASS_TO_CONCLUSION[verdictClass];
14
+ }
15
+ /** Convenience over {@link conclusionForVerdictClass} for a whole verdict. */
16
+ function conclusionForVerdict(verdict) {
17
+ return conclusionForVerdictClass(verdict.verdictClass);
18
+ }
19
+ /** True when a class blocks the PR (a red check), as opposed to passing or neutral. */
20
+ function isBlocking(verdictClass) {
21
+ return conclusionForVerdictClass(verdictClass) === "failure";
22
+ }
23
+ //#endregion
24
+ exports.conclusionForVerdict = conclusionForVerdict;
25
+ exports.conclusionForVerdictClass = conclusionForVerdictClass;
26
+ exports.isBlocking = isBlocking;
27
+
28
+ //# sourceMappingURL=taxonomy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taxonomy.cjs","names":[],"sources":["../../src/ci/taxonomy.ts"],"sourcesContent":["import type { VerdictClass } from \"./verdict.ts\";\n\n// The failure taxonomy (#3498). The single deterministic hinge between \"what the\n// tool honestly knows\" (the verdict class) and \"what the CI check concludes\".\n// Keeping it a pure function of the verdict class — not scattered `if` branches\n// at each call site — is what guarantees every condition gets the *right*\n// conclusion and the same one every time.\n\n/**\n * A GitHub check-run conclusion, narrowed to the three this loop uses.\n * `neutral` reports \"we didn't/couldn't decide\" without a red X; `failure`\n * blocks; `success` is the clean pass.\n */\nexport type CiConclusion = \"success\" | \"failure\" | \"neutral\";\n\nconst CLASS_TO_CONCLUSION: Record<VerdictClass, CiConclusion> = {\n pass: \"success\",\n // A known coverage gap, a real finding, and a conservative \"unverified\" flag\n // all block the PR. They differ in their *reason and framing* (carried by the\n // verdict), not in whether they gate — an uncertain result still fails, but\n // with honest \"we're not sure, flagging to be safe\" messaging rather than a\n // \"your query is broken\" claim.\n \"coverage-gap\": \"failure\",\n finding: \"failure\",\n \"uncertain-conservative-flag\": \"failure\",\n // Capture isn't emitting: a setup problem the user must fix, surfaced as a\n // failure with setup next-steps — never silently green, or it reads as safe.\n \"setup-broken\": \"failure\",\n // A transient infra/transport error (e.g. a dropped WebSocket to the API) is\n // not the user's query problem, so it resolves to neutral, never a red X\n // (adopts #3401).\n \"infra-error\": \"neutral\",\n};\n\n/** Map one verdict class to its CI conclusion. Total and deterministic. */\nexport function conclusionForVerdictClass(\n verdictClass: VerdictClass,\n): CiConclusion {\n return CLASS_TO_CONCLUSION[verdictClass];\n}\n\n/** Convenience over {@link conclusionForVerdictClass} for a whole verdict. */\nexport function conclusionForVerdict(verdict: {\n verdictClass: VerdictClass;\n}): CiConclusion {\n return conclusionForVerdictClass(verdict.verdictClass);\n}\n\n/** True when a class blocks the PR (a red check), as opposed to passing or neutral. */\nexport function isBlocking(verdictClass: VerdictClass): boolean {\n return conclusionForVerdictClass(verdictClass) === \"failure\";\n}\n"],"mappings":";;AAeA,MAAM,sBAA0D;CAC9D,MAAM;CAMN,gBAAgB;CAChB,SAAS;CACT,+BAA+B;CAG/B,gBAAgB;CAIhB,eAAe;CAChB;;AAGD,SAAgB,0BACd,cACc;AACd,QAAO,oBAAoB;;;AAI7B,SAAgB,qBAAqB,SAEpB;AACf,QAAO,0BAA0B,QAAQ,aAAa;;;AAIxD,SAAgB,WAAW,cAAqC;AAC9D,QAAO,0BAA0B,aAAa,KAAK"}
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { VerdictClass } from "./verdict.cjs";
4
+
5
+ //#region src/ci/taxonomy.d.ts
6
+ /**
7
+ * A GitHub check-run conclusion, narrowed to the three this loop uses.
8
+ * `neutral` reports "we didn't/couldn't decide" without a red X; `failure`
9
+ * blocks; `success` is the clean pass.
10
+ */
11
+ type CiConclusion = "success" | "failure" | "neutral";
12
+ /** Map one verdict class to its CI conclusion. Total and deterministic. */
13
+ declare function conclusionForVerdictClass(verdictClass: VerdictClass): CiConclusion;
14
+ /** Convenience over {@link conclusionForVerdictClass} for a whole verdict. */
15
+ declare function conclusionForVerdict(verdict: {
16
+ verdictClass: VerdictClass;
17
+ }): CiConclusion;
18
+ /** True when a class blocks the PR (a red check), as opposed to passing or neutral. */
19
+ declare function isBlocking(verdictClass: VerdictClass): boolean;
20
+ //#endregion
21
+ export { CiConclusion, conclusionForVerdict, conclusionForVerdictClass, isBlocking };
22
+ //# sourceMappingURL=taxonomy.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taxonomy.d.cts","names":[],"sources":["../../src/ci/taxonomy.ts"],"mappings":";;;;;;;;AAaA;;KAAY,YAAA;;iBAsBI,yBAAA,CACd,YAAA,EAAc,YAAA,GACb,YAAA;AAFH;AAAA,iBAOgB,oBAAA,CAAqB,OAAA;EACnC,YAAA,EAAc,YAAA;AAAA,IACZ,YAAA;;iBAKY,UAAA,CAAW,YAAA,EAAc,YAAA"}
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { VerdictClass } from "./verdict.mjs";
4
+
5
+ //#region src/ci/taxonomy.d.ts
6
+ /**
7
+ * A GitHub check-run conclusion, narrowed to the three this loop uses.
8
+ * `neutral` reports "we didn't/couldn't decide" without a red X; `failure`
9
+ * blocks; `success` is the clean pass.
10
+ */
11
+ type CiConclusion = "success" | "failure" | "neutral";
12
+ /** Map one verdict class to its CI conclusion. Total and deterministic. */
13
+ declare function conclusionForVerdictClass(verdictClass: VerdictClass): CiConclusion;
14
+ /** Convenience over {@link conclusionForVerdictClass} for a whole verdict. */
15
+ declare function conclusionForVerdict(verdict: {
16
+ verdictClass: VerdictClass;
17
+ }): CiConclusion;
18
+ /** True when a class blocks the PR (a red check), as opposed to passing or neutral. */
19
+ declare function isBlocking(verdictClass: VerdictClass): boolean;
20
+ //#endregion
21
+ export { CiConclusion, conclusionForVerdict, conclusionForVerdictClass, isBlocking };
22
+ //# sourceMappingURL=taxonomy.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taxonomy.d.mts","names":[],"sources":["../../src/ci/taxonomy.ts"],"mappings":";;;;;;;;AAaA;;KAAY,YAAA;;iBAsBI,yBAAA,CACd,YAAA,EAAc,YAAA,GACb,YAAA;AAFH;AAAA,iBAOgB,oBAAA,CAAqB,OAAA;EACnC,YAAA,EAAc,YAAA;AAAA,IACZ,YAAA;;iBAKY,UAAA,CAAW,YAAA,EAAc,YAAA"}
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ //#region src/ci/taxonomy.ts
3
+ const CLASS_TO_CONCLUSION = {
4
+ pass: "success",
5
+ "coverage-gap": "failure",
6
+ finding: "failure",
7
+ "uncertain-conservative-flag": "failure",
8
+ "setup-broken": "failure",
9
+ "infra-error": "neutral"
10
+ };
11
+ /** Map one verdict class to its CI conclusion. Total and deterministic. */
12
+ function conclusionForVerdictClass(verdictClass) {
13
+ return CLASS_TO_CONCLUSION[verdictClass];
14
+ }
15
+ /** Convenience over {@link conclusionForVerdictClass} for a whole verdict. */
16
+ function conclusionForVerdict(verdict) {
17
+ return conclusionForVerdictClass(verdict.verdictClass);
18
+ }
19
+ /** True when a class blocks the PR (a red check), as opposed to passing or neutral. */
20
+ function isBlocking(verdictClass) {
21
+ return conclusionForVerdictClass(verdictClass) === "failure";
22
+ }
23
+ //#endregion
24
+ export { conclusionForVerdict, conclusionForVerdictClass, isBlocking };
25
+
26
+ //# sourceMappingURL=taxonomy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taxonomy.mjs","names":[],"sources":["../../src/ci/taxonomy.ts"],"sourcesContent":["import type { VerdictClass } from \"./verdict.ts\";\n\n// The failure taxonomy (#3498). The single deterministic hinge between \"what the\n// tool honestly knows\" (the verdict class) and \"what the CI check concludes\".\n// Keeping it a pure function of the verdict class — not scattered `if` branches\n// at each call site — is what guarantees every condition gets the *right*\n// conclusion and the same one every time.\n\n/**\n * A GitHub check-run conclusion, narrowed to the three this loop uses.\n * `neutral` reports \"we didn't/couldn't decide\" without a red X; `failure`\n * blocks; `success` is the clean pass.\n */\nexport type CiConclusion = \"success\" | \"failure\" | \"neutral\";\n\nconst CLASS_TO_CONCLUSION: Record<VerdictClass, CiConclusion> = {\n pass: \"success\",\n // A known coverage gap, a real finding, and a conservative \"unverified\" flag\n // all block the PR. They differ in their *reason and framing* (carried by the\n // verdict), not in whether they gate — an uncertain result still fails, but\n // with honest \"we're not sure, flagging to be safe\" messaging rather than a\n // \"your query is broken\" claim.\n \"coverage-gap\": \"failure\",\n finding: \"failure\",\n \"uncertain-conservative-flag\": \"failure\",\n // Capture isn't emitting: a setup problem the user must fix, surfaced as a\n // failure with setup next-steps — never silently green, or it reads as safe.\n \"setup-broken\": \"failure\",\n // A transient infra/transport error (e.g. a dropped WebSocket to the API) is\n // not the user's query problem, so it resolves to neutral, never a red X\n // (adopts #3401).\n \"infra-error\": \"neutral\",\n};\n\n/** Map one verdict class to its CI conclusion. Total and deterministic. */\nexport function conclusionForVerdictClass(\n verdictClass: VerdictClass,\n): CiConclusion {\n return CLASS_TO_CONCLUSION[verdictClass];\n}\n\n/** Convenience over {@link conclusionForVerdictClass} for a whole verdict. */\nexport function conclusionForVerdict(verdict: {\n verdictClass: VerdictClass;\n}): CiConclusion {\n return conclusionForVerdictClass(verdict.verdictClass);\n}\n\n/** True when a class blocks the PR (a red check), as opposed to passing or neutral. */\nexport function isBlocking(verdictClass: VerdictClass): boolean {\n return conclusionForVerdictClass(verdictClass) === \"failure\";\n}\n"],"mappings":";;AAeA,MAAM,sBAA0D;CAC9D,MAAM;CAMN,gBAAgB;CAChB,SAAS;CACT,+BAA+B;CAG/B,gBAAgB;CAIhB,eAAe;CAChB;;AAGD,SAAgB,0BACd,cACc;AACd,QAAO,oBAAoB;;;AAI7B,SAAgB,qBAAqB,SAEpB;AACf,QAAO,0BAA0B,QAAQ,aAAa;;;AAIxD,SAAgB,WAAW,cAAqC;AAC9D,QAAO,0BAA0B,aAAa,KAAK"}
@@ -0,0 +1,130 @@
1
+ "use client";
2
+ require("../_virtual/_rolldown/runtime.cjs");
3
+ let zod = require("zod");
4
+ //#region src/ci/verdict.ts
5
+ /**
6
+ * The versioned, shared verdict contract (#3497) — the cohesion mechanism for
7
+ * the CI⇄MCP verdict loop (epic #3493). One condition's outcome on a CI run,
8
+ * structured so an agent can *route* it (fix / add a test / escalate) without
9
+ * scraping a build log. Every future condition — coverage gaps, regressions,
10
+ * schema drift, nudges — emits this same shape, so no condition grows its own
11
+ * red/green or its own remediation UX.
12
+ *
13
+ * The same payload is meant to render to the PR comment and to surface in the
14
+ * MCP run object; the surfacing itself is owned by #3106/#3105, so this module
15
+ * defines and validates the contract they consume, plus a reference renderer.
16
+ *
17
+ * Copy rule (ADR-0003): every string a verdict carries — `reason`, the action
18
+ * `detail`s — renders verbatim in the PR comment. Hold it to
19
+ * `.claude/rules/writing.md`: one point per field, no hedging, no
20
+ * self-narration, no restating the condition. The terse-voice test in
21
+ * `verdict.test.ts` is the reference copy the analyzer conforms to.
22
+ */
23
+ /**
24
+ * Bump only on a *breaking* change to the shape below, so a consumer can refuse
25
+ * a payload it doesn't understand. Additive, backward-compatible fields do not
26
+ * require a bump.
27
+ */
28
+ const VERDICT_CONTRACT_VERSION = 1;
29
+ /**
30
+ * How honest the run can be about a condition. `pass` is the only clean
31
+ * outcome; every other class is a non-pass the loop must route.
32
+ * `uncertain-conservative-flag` is the deliberate "we could not check this, so
33
+ * we're flagging to be safe" state — kept distinct so the tool's blindness is
34
+ * never dressed up as either a clean pass or a hard failure.
35
+ */
36
+ const VerdictClass = zod.z.enum([
37
+ "pass",
38
+ "coverage-gap",
39
+ "finding",
40
+ "infra-error",
41
+ "setup-broken",
42
+ "uncertain-conservative-flag"
43
+ ]);
44
+ /**
45
+ * The kind of action a verdict routes to. The ceiling is `auto-fix`; the floor
46
+ * is `notify-human` — escalate-to-human-with-context, a designed success state
47
+ * rather than a gap. `none` is only for a clean pass.
48
+ */
49
+ const VerdictActionKind = zod.z.enum([
50
+ "auto-fix",
51
+ "add-test",
52
+ "triage",
53
+ "notify-human",
54
+ "none"
55
+ ]);
56
+ /**
57
+ * A routable next action. `detail` always carries the human- and
58
+ * machine-readable specifics: the fix instruction, the test to add, or — for
59
+ * `notify-human` — the context handed to the person. Modeling "notify human
60
+ * (with context)" as a first-class value, not an absent field, is the point:
61
+ * the escalate-to-human floor is always well-formed, never an absence.
62
+ */
63
+ const VerdictAction = zod.z.object({
64
+ kind: VerdictActionKind,
65
+ detail: zod.z.string()
66
+ });
67
+ /** A next step / triage action that escalates to a human, carrying context. */
68
+ function notifyHuman(context) {
69
+ return {
70
+ kind: "notify-human",
71
+ detail: context
72
+ };
73
+ }
74
+ /** The "nothing to do" action, for a clean pass. */
75
+ const NO_ACTION = {
76
+ kind: "none",
77
+ detail: ""
78
+ };
79
+ const CiVerdict = zod.z.object({
80
+ version: zod.z.literal(1),
81
+ condition: zod.z.string().min(1),
82
+ verdictClass: VerdictClass,
83
+ reason: zod.z.string().min(1),
84
+ nextStep: VerdictAction,
85
+ triageAction: VerdictAction,
86
+ docsUrl: zod.z.string().url().optional(),
87
+ suggestedMcpTools: zod.z.array(zod.z.string()).default([])
88
+ });
89
+ const ACTION_LABEL = {
90
+ "auto-fix": "Fix",
91
+ "add-test": "Add a test",
92
+ triage: "Triage",
93
+ "notify-human": "Notify a human",
94
+ none: "No action"
95
+ };
96
+ /**
97
+ * Render a verdict to Markdown for a PR comment. Intentionally presentation
98
+ * only: it shows the fields, it does not decide pass/fail (that mapping is the
99
+ * failure taxonomy, #3498). Kept alongside the contract so "the comment renders
100
+ * from the contract" is demonstrably true and ready for the analyzer to import.
101
+ */
102
+ function renderVerdictMarkdown(verdict) {
103
+ const parts = [`**${verdict.condition}** — ${verdict.reason}`];
104
+ const hasNextStep = verdict.nextStep.kind !== "none";
105
+ const hasTriage = verdict.triageAction.kind !== "none";
106
+ if (hasNextStep && hasTriage) parts.push([
107
+ "**Next step — do one of:**",
108
+ `- **${ACTION_LABEL[verdict.nextStep.kind]}:** ${verdict.nextStep.detail}`,
109
+ `- **${ACTION_LABEL[verdict.triageAction.kind]}:** ${verdict.triageAction.detail}`
110
+ ].join("\n"));
111
+ else if (hasNextStep) parts.push(`**Next step (${ACTION_LABEL[verdict.nextStep.kind]}):** ${verdict.nextStep.detail}`);
112
+ else if (hasTriage) parts.push(`**Triage (${ACTION_LABEL[verdict.triageAction.kind]}):** ${verdict.triageAction.detail}`);
113
+ if (verdict.suggestedMcpTools.length > 0) {
114
+ const tools = verdict.suggestedMcpTools.map((t) => `\`${t}\``).join(", ");
115
+ parts.push(`**MCP tools:** ${tools}`);
116
+ }
117
+ if (verdict.docsUrl) parts.push(`[Learn more](${verdict.docsUrl})`);
118
+ return parts.join("\n\n");
119
+ }
120
+ //#endregion
121
+ exports.CiVerdict = CiVerdict;
122
+ exports.NO_ACTION = NO_ACTION;
123
+ exports.VERDICT_CONTRACT_VERSION = VERDICT_CONTRACT_VERSION;
124
+ exports.VerdictAction = VerdictAction;
125
+ exports.VerdictActionKind = VerdictActionKind;
126
+ exports.VerdictClass = VerdictClass;
127
+ exports.notifyHuman = notifyHuman;
128
+ exports.renderVerdictMarkdown = renderVerdictMarkdown;
129
+
130
+ //# sourceMappingURL=verdict.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.cjs","names":["z"],"sources":["../../src/ci/verdict.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * The versioned, shared verdict contract (#3497) — the cohesion mechanism for\n * the CI⇄MCP verdict loop (epic #3493). One condition's outcome on a CI run,\n * structured so an agent can *route* it (fix / add a test / escalate) without\n * scraping a build log. Every future condition — coverage gaps, regressions,\n * schema drift, nudges — emits this same shape, so no condition grows its own\n * red/green or its own remediation UX.\n *\n * The same payload is meant to render to the PR comment and to surface in the\n * MCP run object; the surfacing itself is owned by #3106/#3105, so this module\n * defines and validates the contract they consume, plus a reference renderer.\n *\n * Copy rule (ADR-0003): every string a verdict carries — `reason`, the action\n * `detail`s — renders verbatim in the PR comment. Hold it to\n * `.claude/rules/writing.md`: one point per field, no hedging, no\n * self-narration, no restating the condition. The terse-voice test in\n * `verdict.test.ts` is the reference copy the analyzer conforms to.\n */\n\n/**\n * Bump only on a *breaking* change to the shape below, so a consumer can refuse\n * a payload it doesn't understand. Additive, backward-compatible fields do not\n * require a bump.\n */\nexport const VERDICT_CONTRACT_VERSION = 1;\n\n/**\n * How honest the run can be about a condition. `pass` is the only clean\n * outcome; every other class is a non-pass the loop must route.\n * `uncertain-conservative-flag` is the deliberate \"we could not check this, so\n * we're flagging to be safe\" state — kept distinct so the tool's blindness is\n * never dressed up as either a clean pass or a hard failure.\n */\nexport const VerdictClass = z.enum([\n \"pass\",\n \"coverage-gap\",\n \"finding\",\n \"infra-error\",\n \"setup-broken\",\n \"uncertain-conservative-flag\",\n]);\nexport type VerdictClass = z.infer<typeof VerdictClass>;\n\n/**\n * The kind of action a verdict routes to. The ceiling is `auto-fix`; the floor\n * is `notify-human` — escalate-to-human-with-context, a designed success state\n * rather than a gap. `none` is only for a clean pass.\n */\nexport const VerdictActionKind = z.enum([\n \"auto-fix\",\n \"add-test\",\n \"triage\",\n \"notify-human\",\n \"none\",\n]);\nexport type VerdictActionKind = z.infer<typeof VerdictActionKind>;\n\n/**\n * A routable next action. `detail` always carries the human- and\n * machine-readable specifics: the fix instruction, the test to add, or — for\n * `notify-human` — the context handed to the person. Modeling \"notify human\n * (with context)\" as a first-class value, not an absent field, is the point:\n * the escalate-to-human floor is always well-formed, never an absence.\n */\nexport const VerdictAction = z.object({\n kind: VerdictActionKind,\n detail: z.string(),\n});\nexport type VerdictAction = z.infer<typeof VerdictAction>;\n\n/** A next step / triage action that escalates to a human, carrying context. */\nexport function notifyHuman(context: string): VerdictAction {\n return { kind: \"notify-human\", detail: context };\n}\n\n/** The \"nothing to do\" action, for a clean pass. */\nexport const NO_ACTION: VerdictAction = { kind: \"none\", detail: \"\" };\n\nexport const CiVerdict = z.object({\n version: z.literal(VERDICT_CONTRACT_VERSION),\n /** The condition evaluated, e.g. \"untested-data-access\", \"regression-beyond-threshold\". */\n condition: z.string().min(1),\n verdictClass: VerdictClass,\n /** Plain-language why, honest about the tool's epistemic state. */\n reason: z.string().min(1),\n /** The best-available next step; `notify-human` is a valid, well-formed value. */\n nextStep: VerdictAction,\n /** The non-fix exit — triage/acknowledge, or escalate to a human. */\n triageAction: VerdictAction,\n /** Deep link to the relevant guide, when one applies. */\n docsUrl: z.string().url().optional(),\n /** MCP tools an agent should reach for to act on this verdict. */\n suggestedMcpTools: z.array(z.string()).default([]),\n});\nexport type CiVerdict = z.infer<typeof CiVerdict>;\n\nconst ACTION_LABEL: Record<VerdictActionKind, string> = {\n \"auto-fix\": \"Fix\",\n \"add-test\": \"Add a test\",\n triage: \"Triage\",\n \"notify-human\": \"Notify a human\",\n none: \"No action\",\n};\n\n/**\n * Render a verdict to Markdown for a PR comment. Intentionally presentation\n * only: it shows the fields, it does not decide pass/fail (that mapping is the\n * failure taxonomy, #3498). Kept alongside the contract so \"the comment renders\n * from the contract\" is demonstrably true and ready for the analyzer to import.\n */\nexport function renderVerdictMarkdown(verdict: CiVerdict): string {\n const parts: string[] = [`**${verdict.condition}** — ${verdict.reason}`];\n const hasNextStep = verdict.nextStep.kind !== \"none\";\n const hasTriage = verdict.triageAction.kind !== \"none\";\n // The fix path and the triage exit are alternatives, not a sequence — when\n // both exist, render them as one choice.\n if (hasNextStep && hasTriage) {\n parts.push(\n [\n \"**Next step — do one of:**\",\n `- **${ACTION_LABEL[verdict.nextStep.kind]}:** ${verdict.nextStep.detail}`,\n `- **${ACTION_LABEL[verdict.triageAction.kind]}:** ${verdict.triageAction.detail}`,\n ].join(\"\\n\"),\n );\n } else if (hasNextStep) {\n parts.push(\n `**Next step (${ACTION_LABEL[verdict.nextStep.kind]}):** ${verdict.nextStep.detail}`,\n );\n } else if (hasTriage) {\n parts.push(\n `**Triage (${ACTION_LABEL[verdict.triageAction.kind]}):** ${verdict.triageAction.detail}`,\n );\n }\n if (verdict.suggestedMcpTools.length > 0) {\n const tools = verdict.suggestedMcpTools.map((t) => `\\`${t}\\``).join(\", \");\n parts.push(`**MCP tools:** ${tools}`);\n }\n if (verdict.docsUrl) {\n parts.push(`[Learn more](${verdict.docsUrl})`);\n }\n return parts.join(\"\\n\\n\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,2BAA2B;;;;;;;;AASxC,MAAa,eAAeA,IAAAA,EAAE,KAAK;CACjC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAQF,MAAa,oBAAoBA,IAAAA,EAAE,KAAK;CACtC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AAUF,MAAa,gBAAgBA,IAAAA,EAAE,OAAO;CACpC,MAAM;CACN,QAAQA,IAAAA,EAAE,QAAQ;CACnB,CAAC;;AAIF,SAAgB,YAAY,SAAgC;AAC1D,QAAO;EAAE,MAAM;EAAgB,QAAQ;EAAS;;;AAIlD,MAAa,YAA2B;CAAE,MAAM;CAAQ,QAAQ;CAAI;AAEpE,MAAa,YAAYA,IAAAA,EAAE,OAAO;CAChC,SAASA,IAAAA,EAAE,QAAA,EAAiC;CAE5C,WAAWA,IAAAA,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC5B,cAAc;CAEd,QAAQA,IAAAA,EAAE,QAAQ,CAAC,IAAI,EAAE;CAEzB,UAAU;CAEV,cAAc;CAEd,SAASA,IAAAA,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;CAEpC,mBAAmBA,IAAAA,EAAE,MAAMA,IAAAA,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;CACnD,CAAC;AAGF,MAAM,eAAkD;CACtD,YAAY;CACZ,YAAY;CACZ,QAAQ;CACR,gBAAgB;CAChB,MAAM;CACP;;;;;;;AAQD,SAAgB,sBAAsB,SAA4B;CAChE,MAAM,QAAkB,CAAC,KAAK,QAAQ,UAAU,OAAO,QAAQ,SAAS;CACxE,MAAM,cAAc,QAAQ,SAAS,SAAS;CAC9C,MAAM,YAAY,QAAQ,aAAa,SAAS;AAGhD,KAAI,eAAe,UACjB,OAAM,KACJ;EACE;EACA,OAAO,aAAa,QAAQ,SAAS,MAAM,MAAM,QAAQ,SAAS;EAClE,OAAO,aAAa,QAAQ,aAAa,MAAM,MAAM,QAAQ,aAAa;EAC3E,CAAC,KAAK,KAAK,CACb;UACQ,YACT,OAAM,KACJ,gBAAgB,aAAa,QAAQ,SAAS,MAAM,OAAO,QAAQ,SAAS,SAC7E;UACQ,UACT,OAAM,KACJ,aAAa,aAAa,QAAQ,aAAa,MAAM,OAAO,QAAQ,aAAa,SAClF;AAEH,KAAI,QAAQ,kBAAkB,SAAS,GAAG;EACxC,MAAM,QAAQ,QAAQ,kBAAkB,KAAK,MAAM,KAAK,EAAE,IAAI,CAAC,KAAK,KAAK;AACzE,QAAM,KAAK,kBAAkB,QAAQ;;AAEvC,KAAI,QAAQ,QACV,OAAM,KAAK,gBAAgB,QAAQ,QAAQ,GAAG;AAEhD,QAAO,MAAM,KAAK,OAAO"}
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { z } from "zod";
4
+
5
+ //#region src/ci/verdict.d.ts
6
+ /**
7
+ * The versioned, shared verdict contract (#3497) — the cohesion mechanism for
8
+ * the CI⇄MCP verdict loop (epic #3493). One condition's outcome on a CI run,
9
+ * structured so an agent can *route* it (fix / add a test / escalate) without
10
+ * scraping a build log. Every future condition — coverage gaps, regressions,
11
+ * schema drift, nudges — emits this same shape, so no condition grows its own
12
+ * red/green or its own remediation UX.
13
+ *
14
+ * The same payload is meant to render to the PR comment and to surface in the
15
+ * MCP run object; the surfacing itself is owned by #3106/#3105, so this module
16
+ * defines and validates the contract they consume, plus a reference renderer.
17
+ *
18
+ * Copy rule (ADR-0003): every string a verdict carries — `reason`, the action
19
+ * `detail`s — renders verbatim in the PR comment. Hold it to
20
+ * `.claude/rules/writing.md`: one point per field, no hedging, no
21
+ * self-narration, no restating the condition. The terse-voice test in
22
+ * `verdict.test.ts` is the reference copy the analyzer conforms to.
23
+ */
24
+ /**
25
+ * Bump only on a *breaking* change to the shape below, so a consumer can refuse
26
+ * a payload it doesn't understand. Additive, backward-compatible fields do not
27
+ * require a bump.
28
+ */
29
+ declare const VERDICT_CONTRACT_VERSION = 1;
30
+ /**
31
+ * How honest the run can be about a condition. `pass` is the only clean
32
+ * outcome; every other class is a non-pass the loop must route.
33
+ * `uncertain-conservative-flag` is the deliberate "we could not check this, so
34
+ * we're flagging to be safe" state — kept distinct so the tool's blindness is
35
+ * never dressed up as either a clean pass or a hard failure.
36
+ */
37
+ declare const VerdictClass: z.ZodEnum<{
38
+ pass: "pass";
39
+ "coverage-gap": "coverage-gap";
40
+ finding: "finding";
41
+ "infra-error": "infra-error";
42
+ "setup-broken": "setup-broken";
43
+ "uncertain-conservative-flag": "uncertain-conservative-flag";
44
+ }>;
45
+ type VerdictClass = z.infer<typeof VerdictClass>;
46
+ /**
47
+ * The kind of action a verdict routes to. The ceiling is `auto-fix`; the floor
48
+ * is `notify-human` — escalate-to-human-with-context, a designed success state
49
+ * rather than a gap. `none` is only for a clean pass.
50
+ */
51
+ declare const VerdictActionKind: z.ZodEnum<{
52
+ "auto-fix": "auto-fix";
53
+ "add-test": "add-test";
54
+ triage: "triage";
55
+ "notify-human": "notify-human";
56
+ none: "none";
57
+ }>;
58
+ type VerdictActionKind = z.infer<typeof VerdictActionKind>;
59
+ /**
60
+ * A routable next action. `detail` always carries the human- and
61
+ * machine-readable specifics: the fix instruction, the test to add, or — for
62
+ * `notify-human` — the context handed to the person. Modeling "notify human
63
+ * (with context)" as a first-class value, not an absent field, is the point:
64
+ * the escalate-to-human floor is always well-formed, never an absence.
65
+ */
66
+ declare const VerdictAction: z.ZodObject<{
67
+ kind: z.ZodEnum<{
68
+ "auto-fix": "auto-fix";
69
+ "add-test": "add-test";
70
+ triage: "triage";
71
+ "notify-human": "notify-human";
72
+ none: "none";
73
+ }>;
74
+ detail: z.ZodString;
75
+ }, z.core.$strip>;
76
+ type VerdictAction = z.infer<typeof VerdictAction>;
77
+ /** A next step / triage action that escalates to a human, carrying context. */
78
+ declare function notifyHuman(context: string): VerdictAction;
79
+ /** The "nothing to do" action, for a clean pass. */
80
+ declare const NO_ACTION: VerdictAction;
81
+ declare const CiVerdict: z.ZodObject<{
82
+ version: z.ZodLiteral<1>;
83
+ condition: z.ZodString;
84
+ verdictClass: z.ZodEnum<{
85
+ pass: "pass";
86
+ "coverage-gap": "coverage-gap";
87
+ finding: "finding";
88
+ "infra-error": "infra-error";
89
+ "setup-broken": "setup-broken";
90
+ "uncertain-conservative-flag": "uncertain-conservative-flag";
91
+ }>;
92
+ reason: z.ZodString;
93
+ nextStep: z.ZodObject<{
94
+ kind: z.ZodEnum<{
95
+ "auto-fix": "auto-fix";
96
+ "add-test": "add-test";
97
+ triage: "triage";
98
+ "notify-human": "notify-human";
99
+ none: "none";
100
+ }>;
101
+ detail: z.ZodString;
102
+ }, z.core.$strip>;
103
+ triageAction: z.ZodObject<{
104
+ kind: z.ZodEnum<{
105
+ "auto-fix": "auto-fix";
106
+ "add-test": "add-test";
107
+ triage: "triage";
108
+ "notify-human": "notify-human";
109
+ none: "none";
110
+ }>;
111
+ detail: z.ZodString;
112
+ }, z.core.$strip>;
113
+ docsUrl: z.ZodOptional<z.ZodString>;
114
+ suggestedMcpTools: z.ZodDefault<z.ZodArray<z.ZodString>>;
115
+ }, z.core.$strip>;
116
+ type CiVerdict = z.infer<typeof CiVerdict>;
117
+ /**
118
+ * Render a verdict to Markdown for a PR comment. Intentionally presentation
119
+ * only: it shows the fields, it does not decide pass/fail (that mapping is the
120
+ * failure taxonomy, #3498). Kept alongside the contract so "the comment renders
121
+ * from the contract" is demonstrably true and ready for the analyzer to import.
122
+ */
123
+ declare function renderVerdictMarkdown(verdict: CiVerdict): string;
124
+ //#endregion
125
+ export { CiVerdict, NO_ACTION, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, notifyHuman, renderVerdictMarkdown };
126
+ //# sourceMappingURL=verdict.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.d.cts","names":[],"sources":["../../src/ci/verdict.ts"],"mappings":";;;;;;;;AA0BA;;;;;AASA;;;;;;;;;;;;;;AAQA;cAjBa,wBAAA;;;;;;;;cASA,YAAA,EAAY,CAAA,CAAA,OAAA;;;;;;;;KAQb,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,YAAA;;;;;AAc1C;cAPa,iBAAA,EAAiB,CAAA,CAAA,OAAA;;;;;;;KAOlB,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,iBAAA;AAS/C;;;;;;;AAAA,cAAa,aAAA,EAAa,CAAA,CAAA,SAAA;;;;;;;;;;KAId,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,aAAA;;iBAG3B,WAAA,CAAY,OAAA,WAAkB,aAAA;;cAKjC,SAAA,EAAW,aAAA;AAAA,cAEX,SAAA,EAAS,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgBV,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,SAAA;;;;;;;iBAgBvB,qBAAA,CAAsB,OAAA,EAAS,SAAA"}
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { z } from "zod";
4
+
5
+ //#region src/ci/verdict.d.ts
6
+ /**
7
+ * The versioned, shared verdict contract (#3497) — the cohesion mechanism for
8
+ * the CI⇄MCP verdict loop (epic #3493). One condition's outcome on a CI run,
9
+ * structured so an agent can *route* it (fix / add a test / escalate) without
10
+ * scraping a build log. Every future condition — coverage gaps, regressions,
11
+ * schema drift, nudges — emits this same shape, so no condition grows its own
12
+ * red/green or its own remediation UX.
13
+ *
14
+ * The same payload is meant to render to the PR comment and to surface in the
15
+ * MCP run object; the surfacing itself is owned by #3106/#3105, so this module
16
+ * defines and validates the contract they consume, plus a reference renderer.
17
+ *
18
+ * Copy rule (ADR-0003): every string a verdict carries — `reason`, the action
19
+ * `detail`s — renders verbatim in the PR comment. Hold it to
20
+ * `.claude/rules/writing.md`: one point per field, no hedging, no
21
+ * self-narration, no restating the condition. The terse-voice test in
22
+ * `verdict.test.ts` is the reference copy the analyzer conforms to.
23
+ */
24
+ /**
25
+ * Bump only on a *breaking* change to the shape below, so a consumer can refuse
26
+ * a payload it doesn't understand. Additive, backward-compatible fields do not
27
+ * require a bump.
28
+ */
29
+ declare const VERDICT_CONTRACT_VERSION = 1;
30
+ /**
31
+ * How honest the run can be about a condition. `pass` is the only clean
32
+ * outcome; every other class is a non-pass the loop must route.
33
+ * `uncertain-conservative-flag` is the deliberate "we could not check this, so
34
+ * we're flagging to be safe" state — kept distinct so the tool's blindness is
35
+ * never dressed up as either a clean pass or a hard failure.
36
+ */
37
+ declare const VerdictClass: z.ZodEnum<{
38
+ pass: "pass";
39
+ "coverage-gap": "coverage-gap";
40
+ finding: "finding";
41
+ "infra-error": "infra-error";
42
+ "setup-broken": "setup-broken";
43
+ "uncertain-conservative-flag": "uncertain-conservative-flag";
44
+ }>;
45
+ type VerdictClass = z.infer<typeof VerdictClass>;
46
+ /**
47
+ * The kind of action a verdict routes to. The ceiling is `auto-fix`; the floor
48
+ * is `notify-human` — escalate-to-human-with-context, a designed success state
49
+ * rather than a gap. `none` is only for a clean pass.
50
+ */
51
+ declare const VerdictActionKind: z.ZodEnum<{
52
+ "auto-fix": "auto-fix";
53
+ "add-test": "add-test";
54
+ triage: "triage";
55
+ "notify-human": "notify-human";
56
+ none: "none";
57
+ }>;
58
+ type VerdictActionKind = z.infer<typeof VerdictActionKind>;
59
+ /**
60
+ * A routable next action. `detail` always carries the human- and
61
+ * machine-readable specifics: the fix instruction, the test to add, or — for
62
+ * `notify-human` — the context handed to the person. Modeling "notify human
63
+ * (with context)" as a first-class value, not an absent field, is the point:
64
+ * the escalate-to-human floor is always well-formed, never an absence.
65
+ */
66
+ declare const VerdictAction: z.ZodObject<{
67
+ kind: z.ZodEnum<{
68
+ "auto-fix": "auto-fix";
69
+ "add-test": "add-test";
70
+ triage: "triage";
71
+ "notify-human": "notify-human";
72
+ none: "none";
73
+ }>;
74
+ detail: z.ZodString;
75
+ }, z.core.$strip>;
76
+ type VerdictAction = z.infer<typeof VerdictAction>;
77
+ /** A next step / triage action that escalates to a human, carrying context. */
78
+ declare function notifyHuman(context: string): VerdictAction;
79
+ /** The "nothing to do" action, for a clean pass. */
80
+ declare const NO_ACTION: VerdictAction;
81
+ declare const CiVerdict: z.ZodObject<{
82
+ version: z.ZodLiteral<1>;
83
+ condition: z.ZodString;
84
+ verdictClass: z.ZodEnum<{
85
+ pass: "pass";
86
+ "coverage-gap": "coverage-gap";
87
+ finding: "finding";
88
+ "infra-error": "infra-error";
89
+ "setup-broken": "setup-broken";
90
+ "uncertain-conservative-flag": "uncertain-conservative-flag";
91
+ }>;
92
+ reason: z.ZodString;
93
+ nextStep: z.ZodObject<{
94
+ kind: z.ZodEnum<{
95
+ "auto-fix": "auto-fix";
96
+ "add-test": "add-test";
97
+ triage: "triage";
98
+ "notify-human": "notify-human";
99
+ none: "none";
100
+ }>;
101
+ detail: z.ZodString;
102
+ }, z.core.$strip>;
103
+ triageAction: z.ZodObject<{
104
+ kind: z.ZodEnum<{
105
+ "auto-fix": "auto-fix";
106
+ "add-test": "add-test";
107
+ triage: "triage";
108
+ "notify-human": "notify-human";
109
+ none: "none";
110
+ }>;
111
+ detail: z.ZodString;
112
+ }, z.core.$strip>;
113
+ docsUrl: z.ZodOptional<z.ZodString>;
114
+ suggestedMcpTools: z.ZodDefault<z.ZodArray<z.ZodString>>;
115
+ }, z.core.$strip>;
116
+ type CiVerdict = z.infer<typeof CiVerdict>;
117
+ /**
118
+ * Render a verdict to Markdown for a PR comment. Intentionally presentation
119
+ * only: it shows the fields, it does not decide pass/fail (that mapping is the
120
+ * failure taxonomy, #3498). Kept alongside the contract so "the comment renders
121
+ * from the contract" is demonstrably true and ready for the analyzer to import.
122
+ */
123
+ declare function renderVerdictMarkdown(verdict: CiVerdict): string;
124
+ //#endregion
125
+ export { CiVerdict, NO_ACTION, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, notifyHuman, renderVerdictMarkdown };
126
+ //# sourceMappingURL=verdict.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.d.mts","names":[],"sources":["../../src/ci/verdict.ts"],"mappings":";;;;;;;;AA0BA;;;;;AASA;;;;;;;;;;;;;;AAQA;cAjBa,wBAAA;;;;;;;;cASA,YAAA,EAAY,CAAA,CAAA,OAAA;;;;;;;;KAQb,YAAA,GAAe,CAAA,CAAE,KAAA,QAAa,YAAA;;;;;AAc1C;cAPa,iBAAA,EAAiB,CAAA,CAAA,OAAA;;;;;;;KAOlB,iBAAA,GAAoB,CAAA,CAAE,KAAA,QAAa,iBAAA;AAS/C;;;;;;;AAAA,cAAa,aAAA,EAAa,CAAA,CAAA,SAAA;;;;;;;;;;KAId,aAAA,GAAgB,CAAA,CAAE,KAAA,QAAa,aAAA;;iBAG3B,WAAA,CAAY,OAAA,WAAkB,aAAA;;cAKjC,SAAA,EAAW,aAAA;AAAA,cAEX,SAAA,EAAS,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgBV,SAAA,GAAY,CAAA,CAAE,KAAA,QAAa,SAAA;;;;;;;iBAgBvB,qBAAA,CAAsB,OAAA,EAAS,SAAA"}
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { z } from "zod";
3
+ //#region src/ci/verdict.ts
4
+ /**
5
+ * The versioned, shared verdict contract (#3497) — the cohesion mechanism for
6
+ * the CI⇄MCP verdict loop (epic #3493). One condition's outcome on a CI run,
7
+ * structured so an agent can *route* it (fix / add a test / escalate) without
8
+ * scraping a build log. Every future condition — coverage gaps, regressions,
9
+ * schema drift, nudges — emits this same shape, so no condition grows its own
10
+ * red/green or its own remediation UX.
11
+ *
12
+ * The same payload is meant to render to the PR comment and to surface in the
13
+ * MCP run object; the surfacing itself is owned by #3106/#3105, so this module
14
+ * defines and validates the contract they consume, plus a reference renderer.
15
+ *
16
+ * Copy rule (ADR-0003): every string a verdict carries — `reason`, the action
17
+ * `detail`s — renders verbatim in the PR comment. Hold it to
18
+ * `.claude/rules/writing.md`: one point per field, no hedging, no
19
+ * self-narration, no restating the condition. The terse-voice test in
20
+ * `verdict.test.ts` is the reference copy the analyzer conforms to.
21
+ */
22
+ /**
23
+ * Bump only on a *breaking* change to the shape below, so a consumer can refuse
24
+ * a payload it doesn't understand. Additive, backward-compatible fields do not
25
+ * require a bump.
26
+ */
27
+ const VERDICT_CONTRACT_VERSION = 1;
28
+ /**
29
+ * How honest the run can be about a condition. `pass` is the only clean
30
+ * outcome; every other class is a non-pass the loop must route.
31
+ * `uncertain-conservative-flag` is the deliberate "we could not check this, so
32
+ * we're flagging to be safe" state — kept distinct so the tool's blindness is
33
+ * never dressed up as either a clean pass or a hard failure.
34
+ */
35
+ const VerdictClass = z.enum([
36
+ "pass",
37
+ "coverage-gap",
38
+ "finding",
39
+ "infra-error",
40
+ "setup-broken",
41
+ "uncertain-conservative-flag"
42
+ ]);
43
+ /**
44
+ * The kind of action a verdict routes to. The ceiling is `auto-fix`; the floor
45
+ * is `notify-human` — escalate-to-human-with-context, a designed success state
46
+ * rather than a gap. `none` is only for a clean pass.
47
+ */
48
+ const VerdictActionKind = z.enum([
49
+ "auto-fix",
50
+ "add-test",
51
+ "triage",
52
+ "notify-human",
53
+ "none"
54
+ ]);
55
+ /**
56
+ * A routable next action. `detail` always carries the human- and
57
+ * machine-readable specifics: the fix instruction, the test to add, or — for
58
+ * `notify-human` — the context handed to the person. Modeling "notify human
59
+ * (with context)" as a first-class value, not an absent field, is the point:
60
+ * the escalate-to-human floor is always well-formed, never an absence.
61
+ */
62
+ const VerdictAction = z.object({
63
+ kind: VerdictActionKind,
64
+ detail: z.string()
65
+ });
66
+ /** A next step / triage action that escalates to a human, carrying context. */
67
+ function notifyHuman(context) {
68
+ return {
69
+ kind: "notify-human",
70
+ detail: context
71
+ };
72
+ }
73
+ /** The "nothing to do" action, for a clean pass. */
74
+ const NO_ACTION = {
75
+ kind: "none",
76
+ detail: ""
77
+ };
78
+ const CiVerdict = z.object({
79
+ version: z.literal(1),
80
+ condition: z.string().min(1),
81
+ verdictClass: VerdictClass,
82
+ reason: z.string().min(1),
83
+ nextStep: VerdictAction,
84
+ triageAction: VerdictAction,
85
+ docsUrl: z.string().url().optional(),
86
+ suggestedMcpTools: z.array(z.string()).default([])
87
+ });
88
+ const ACTION_LABEL = {
89
+ "auto-fix": "Fix",
90
+ "add-test": "Add a test",
91
+ triage: "Triage",
92
+ "notify-human": "Notify a human",
93
+ none: "No action"
94
+ };
95
+ /**
96
+ * Render a verdict to Markdown for a PR comment. Intentionally presentation
97
+ * only: it shows the fields, it does not decide pass/fail (that mapping is the
98
+ * failure taxonomy, #3498). Kept alongside the contract so "the comment renders
99
+ * from the contract" is demonstrably true and ready for the analyzer to import.
100
+ */
101
+ function renderVerdictMarkdown(verdict) {
102
+ const parts = [`**${verdict.condition}** — ${verdict.reason}`];
103
+ const hasNextStep = verdict.nextStep.kind !== "none";
104
+ const hasTriage = verdict.triageAction.kind !== "none";
105
+ if (hasNextStep && hasTriage) parts.push([
106
+ "**Next step — do one of:**",
107
+ `- **${ACTION_LABEL[verdict.nextStep.kind]}:** ${verdict.nextStep.detail}`,
108
+ `- **${ACTION_LABEL[verdict.triageAction.kind]}:** ${verdict.triageAction.detail}`
109
+ ].join("\n"));
110
+ else if (hasNextStep) parts.push(`**Next step (${ACTION_LABEL[verdict.nextStep.kind]}):** ${verdict.nextStep.detail}`);
111
+ else if (hasTriage) parts.push(`**Triage (${ACTION_LABEL[verdict.triageAction.kind]}):** ${verdict.triageAction.detail}`);
112
+ if (verdict.suggestedMcpTools.length > 0) {
113
+ const tools = verdict.suggestedMcpTools.map((t) => `\`${t}\``).join(", ");
114
+ parts.push(`**MCP tools:** ${tools}`);
115
+ }
116
+ if (verdict.docsUrl) parts.push(`[Learn more](${verdict.docsUrl})`);
117
+ return parts.join("\n\n");
118
+ }
119
+ //#endregion
120
+ export { CiVerdict, NO_ACTION, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, notifyHuman, renderVerdictMarkdown };
121
+
122
+ //# sourceMappingURL=verdict.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.mjs","names":[],"sources":["../../src/ci/verdict.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * The versioned, shared verdict contract (#3497) — the cohesion mechanism for\n * the CI⇄MCP verdict loop (epic #3493). One condition's outcome on a CI run,\n * structured so an agent can *route* it (fix / add a test / escalate) without\n * scraping a build log. Every future condition — coverage gaps, regressions,\n * schema drift, nudges — emits this same shape, so no condition grows its own\n * red/green or its own remediation UX.\n *\n * The same payload is meant to render to the PR comment and to surface in the\n * MCP run object; the surfacing itself is owned by #3106/#3105, so this module\n * defines and validates the contract they consume, plus a reference renderer.\n *\n * Copy rule (ADR-0003): every string a verdict carries — `reason`, the action\n * `detail`s — renders verbatim in the PR comment. Hold it to\n * `.claude/rules/writing.md`: one point per field, no hedging, no\n * self-narration, no restating the condition. The terse-voice test in\n * `verdict.test.ts` is the reference copy the analyzer conforms to.\n */\n\n/**\n * Bump only on a *breaking* change to the shape below, so a consumer can refuse\n * a payload it doesn't understand. Additive, backward-compatible fields do not\n * require a bump.\n */\nexport const VERDICT_CONTRACT_VERSION = 1;\n\n/**\n * How honest the run can be about a condition. `pass` is the only clean\n * outcome; every other class is a non-pass the loop must route.\n * `uncertain-conservative-flag` is the deliberate \"we could not check this, so\n * we're flagging to be safe\" state — kept distinct so the tool's blindness is\n * never dressed up as either a clean pass or a hard failure.\n */\nexport const VerdictClass = z.enum([\n \"pass\",\n \"coverage-gap\",\n \"finding\",\n \"infra-error\",\n \"setup-broken\",\n \"uncertain-conservative-flag\",\n]);\nexport type VerdictClass = z.infer<typeof VerdictClass>;\n\n/**\n * The kind of action a verdict routes to. The ceiling is `auto-fix`; the floor\n * is `notify-human` — escalate-to-human-with-context, a designed success state\n * rather than a gap. `none` is only for a clean pass.\n */\nexport const VerdictActionKind = z.enum([\n \"auto-fix\",\n \"add-test\",\n \"triage\",\n \"notify-human\",\n \"none\",\n]);\nexport type VerdictActionKind = z.infer<typeof VerdictActionKind>;\n\n/**\n * A routable next action. `detail` always carries the human- and\n * machine-readable specifics: the fix instruction, the test to add, or — for\n * `notify-human` — the context handed to the person. Modeling \"notify human\n * (with context)\" as a first-class value, not an absent field, is the point:\n * the escalate-to-human floor is always well-formed, never an absence.\n */\nexport const VerdictAction = z.object({\n kind: VerdictActionKind,\n detail: z.string(),\n});\nexport type VerdictAction = z.infer<typeof VerdictAction>;\n\n/** A next step / triage action that escalates to a human, carrying context. */\nexport function notifyHuman(context: string): VerdictAction {\n return { kind: \"notify-human\", detail: context };\n}\n\n/** The \"nothing to do\" action, for a clean pass. */\nexport const NO_ACTION: VerdictAction = { kind: \"none\", detail: \"\" };\n\nexport const CiVerdict = z.object({\n version: z.literal(VERDICT_CONTRACT_VERSION),\n /** The condition evaluated, e.g. \"untested-data-access\", \"regression-beyond-threshold\". */\n condition: z.string().min(1),\n verdictClass: VerdictClass,\n /** Plain-language why, honest about the tool's epistemic state. */\n reason: z.string().min(1),\n /** The best-available next step; `notify-human` is a valid, well-formed value. */\n nextStep: VerdictAction,\n /** The non-fix exit — triage/acknowledge, or escalate to a human. */\n triageAction: VerdictAction,\n /** Deep link to the relevant guide, when one applies. */\n docsUrl: z.string().url().optional(),\n /** MCP tools an agent should reach for to act on this verdict. */\n suggestedMcpTools: z.array(z.string()).default([]),\n});\nexport type CiVerdict = z.infer<typeof CiVerdict>;\n\nconst ACTION_LABEL: Record<VerdictActionKind, string> = {\n \"auto-fix\": \"Fix\",\n \"add-test\": \"Add a test\",\n triage: \"Triage\",\n \"notify-human\": \"Notify a human\",\n none: \"No action\",\n};\n\n/**\n * Render a verdict to Markdown for a PR comment. Intentionally presentation\n * only: it shows the fields, it does not decide pass/fail (that mapping is the\n * failure taxonomy, #3498). Kept alongside the contract so \"the comment renders\n * from the contract\" is demonstrably true and ready for the analyzer to import.\n */\nexport function renderVerdictMarkdown(verdict: CiVerdict): string {\n const parts: string[] = [`**${verdict.condition}** — ${verdict.reason}`];\n const hasNextStep = verdict.nextStep.kind !== \"none\";\n const hasTriage = verdict.triageAction.kind !== \"none\";\n // The fix path and the triage exit are alternatives, not a sequence — when\n // both exist, render them as one choice.\n if (hasNextStep && hasTriage) {\n parts.push(\n [\n \"**Next step — do one of:**\",\n `- **${ACTION_LABEL[verdict.nextStep.kind]}:** ${verdict.nextStep.detail}`,\n `- **${ACTION_LABEL[verdict.triageAction.kind]}:** ${verdict.triageAction.detail}`,\n ].join(\"\\n\"),\n );\n } else if (hasNextStep) {\n parts.push(\n `**Next step (${ACTION_LABEL[verdict.nextStep.kind]}):** ${verdict.nextStep.detail}`,\n );\n } else if (hasTriage) {\n parts.push(\n `**Triage (${ACTION_LABEL[verdict.triageAction.kind]}):** ${verdict.triageAction.detail}`,\n );\n }\n if (verdict.suggestedMcpTools.length > 0) {\n const tools = verdict.suggestedMcpTools.map((t) => `\\`${t}\\``).join(\", \");\n parts.push(`**MCP tools:** ${tools}`);\n }\n if (verdict.docsUrl) {\n parts.push(`[Learn more](${verdict.docsUrl})`);\n }\n return parts.join(\"\\n\\n\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,2BAA2B;;;;;;;;AASxC,MAAa,eAAe,EAAE,KAAK;CACjC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAQF,MAAa,oBAAoB,EAAE,KAAK;CACtC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AAUF,MAAa,gBAAgB,EAAE,OAAO;CACpC,MAAM;CACN,QAAQ,EAAE,QAAQ;CACnB,CAAC;;AAIF,SAAgB,YAAY,SAAgC;AAC1D,QAAO;EAAE,MAAM;EAAgB,QAAQ;EAAS;;;AAIlD,MAAa,YAA2B;CAAE,MAAM;CAAQ,QAAQ;CAAI;AAEpE,MAAa,YAAY,EAAE,OAAO;CAChC,SAAS,EAAE,QAAA,EAAiC;CAE5C,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC5B,cAAc;CAEd,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE;CAEzB,UAAU;CAEV,cAAc;CAEd,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;CAEpC,mBAAmB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;CACnD,CAAC;AAGF,MAAM,eAAkD;CACtD,YAAY;CACZ,YAAY;CACZ,QAAQ;CACR,gBAAgB;CAChB,MAAM;CACP;;;;;;;AAQD,SAAgB,sBAAsB,SAA4B;CAChE,MAAM,QAAkB,CAAC,KAAK,QAAQ,UAAU,OAAO,QAAQ,SAAS;CACxE,MAAM,cAAc,QAAQ,SAAS,SAAS;CAC9C,MAAM,YAAY,QAAQ,aAAa,SAAS;AAGhD,KAAI,eAAe,UACjB,OAAM,KACJ;EACE;EACA,OAAO,aAAa,QAAQ,SAAS,MAAM,MAAM,QAAQ,SAAS;EAClE,OAAO,aAAa,QAAQ,aAAa,MAAM,MAAM,QAAQ,aAAa;EAC3E,CAAC,KAAK,KAAK,CACb;UACQ,YACT,OAAM,KACJ,gBAAgB,aAAa,QAAQ,SAAS,MAAM,OAAO,QAAQ,SAAS,SAC7E;UACQ,UACT,OAAM,KACJ,aAAa,aAAa,QAAQ,aAAa,MAAM,OAAO,QAAQ,aAAa,SAClF;AAEH,KAAI,QAAQ,kBAAkB,SAAS,GAAG;EACxC,MAAM,QAAQ,QAAQ,kBAAkB,KAAK,MAAM,KAAK,EAAE,IAAI,CAAC,KAAK,KAAK;AACzE,QAAM,KAAK,kBAAkB,QAAQ;;AAEvC,KAAI,QAAQ,QACV,OAAM,KAAK,gBAAgB,QAAQ,QAAQ,GAAG;AAEhD,QAAO,MAAM,KAAK,OAAO"}
package/dist/index.cjs CHANGED
@@ -20,11 +20,16 @@ const require_build_action_plan = require("./action-plan/build-action-plan.cjs")
20
20
  const require_sentry = require("./sentry.cjs");
21
21
  const require_query = require("./query.cjs");
22
22
  const require_query_findings = require("./findings/query-findings.cjs");
23
+ const require_verdict = require("./ci/verdict.cjs");
24
+ const require_taxonomy = require("./ci/taxonomy.cjs");
25
+ const require_policy = require("./ci/policy.cjs");
23
26
  exports.Analyzer = require_analyzer.Analyzer;
27
+ exports.CiVerdict = require_verdict.CiVerdict;
24
28
  exports.CombinedExport = require_dump.CombinedExport;
25
29
  exports.ComputedColumnStats = require_statistics.ComputedColumnStats;
26
30
  exports.ComputedReltuples = require_statistics.ComputedReltuples;
27
31
  exports.ComputedStats = require_statistics.ComputedStats;
32
+ exports.DEFAULT_CONDITION_POLICIES = require_policy.DEFAULT_CONDITION_POLICIES;
28
33
  exports.DUMP_STATS_SQL = require_statistics.DUMP_STATS_SQL;
29
34
  exports.ExportedQuery = require_dump.ExportedQuery;
30
35
  exports.ExportedStats = require_statistics.ExportedStats;
@@ -32,6 +37,7 @@ exports.ExportedStatsColumns = require_statistics.ExportedStatsColumns;
32
37
  exports.ExportedStatsIndex = require_statistics.ExportedStatsIndex;
33
38
  exports.ExportedStatsStatistics = require_statistics.ExportedStatsStatistics;
34
39
  exports.ExportedStatsV1 = require_statistics.ExportedStatsV1;
40
+ exports.FALLBACK_POLICY = require_policy.FALLBACK_POLICY;
35
41
  exports.FullSchema = require_schema.FullSchema;
36
42
  exports.FullSchemaColumn = require_schema.FullSchemaColumn;
37
43
  exports.FullSchemaCompositeAttribute = require_schema.FullSchemaCompositeAttribute;
@@ -47,6 +53,7 @@ exports.FullSchemaType = require_schema.FullSchemaType;
47
53
  exports.FullSchemaTypeConstraint = require_schema.FullSchemaTypeConstraint;
48
54
  exports.FullSchemaView = require_schema.FullSchemaView;
49
55
  exports.IndexOptimizer = require_genalgo.IndexOptimizer;
56
+ exports.NO_ACTION = require_verdict.NO_ACTION;
50
57
  exports.OptimizedQuery = require_query.OptimizedQuery;
51
58
  exports.PROCEED = require_genalgo.PROCEED;
52
59
  exports.PgIdentifier = require_pg_identifier.PgIdentifier;
@@ -59,22 +66,33 @@ exports.SKIP = require_genalgo.SKIP;
59
66
  exports.Statistics = require_statistics.Statistics;
60
67
  exports.StatisticsMode = require_statistics.StatisticsMode;
61
68
  exports.StatisticsSource = require_statistics.StatisticsSource;
69
+ exports.VERDICT_CONTRACT_VERSION = require_verdict.VERDICT_CONTRACT_VERSION;
70
+ exports.VerdictAction = require_verdict.VerdictAction;
71
+ exports.VerdictActionKind = require_verdict.VerdictActionKind;
72
+ exports.VerdictClass = require_verdict.VerdictClass;
62
73
  exports.aggregateIndexRecommendations = require_aggregate_index_recommendations.aggregateIndexRecommendations;
63
74
  exports.analyzeQueryFindings = require_query_findings.analyzeQueryFindings;
64
75
  exports.buildActionPlan = require_build_action_plan.buildActionPlan;
65
76
  exports.combinedDumpSql = require_dump.combinedDumpSql;
66
77
  exports.compactSelectList = require_display_query.compactSelectList;
78
+ exports.conclusionForVerdict = require_taxonomy.conclusionForVerdict;
79
+ exports.conclusionForVerdictClass = require_taxonomy.conclusionForVerdictClass;
67
80
  exports.deriveSentryEnvironment = require_sentry.deriveSentryEnvironment;
68
81
  exports.dropIndex = require_database.dropIndex;
69
82
  exports.dumpQueriesSql = require_dump.dumpQueriesSql;
70
83
  exports.dumpSchema = require_schema.dumpSchema;
84
+ exports.evaluateRun = require_policy.evaluateRun;
71
85
  exports.groupDuplicateIndexes = require_indexes.groupDuplicateIndexes;
72
86
  exports.groupIndexesByCoverage = require_index_coverage.groupIndexesByCoverage;
73
87
  exports.ignoredIdentifier = require_analyzer.ignoredIdentifier;
74
88
  exports.indexCovers = require_index_coverage.indexCovers;
89
+ exports.isBlocking = require_taxonomy.isBlocking;
75
90
  exports.isIndexProbablyDroppable = require_indexes.isIndexProbablyDroppable;
76
91
  exports.isIndexSupported = require_indexes.isIndexSupported;
77
92
  exports.isTestFilePath = require_test_origin.isTestFilePath;
78
93
  exports.isTestOriginQuery = require_test_origin.isTestOriginQuery;
79
94
  exports.normalizedFingerprint = require_normalized_fingerprint.normalizedFingerprint;
95
+ exports.notifyHuman = require_verdict.notifyHuman;
80
96
  exports.parseNudges = require_nudges.parseNudges;
97
+ exports.policyFor = require_policy.policyFor;
98
+ exports.renderVerdictMarkdown = require_verdict.renderVerdictMarkdown;
package/dist/index.d.cts CHANGED
@@ -21,4 +21,7 @@ import { ActionPlanNudgeQuery, ActionableStep, AffectedQueryCost, CostReduction,
21
21
  import { deriveSentryEnvironment } from "./sentry.cjs";
22
22
  import { ClientApi, ConnectionMode, ExtensionPresence, IndexDefinition, RepoConfig, ServerApi, UnauthenticatedServerApi } from "./websocket-server.cjs";
23
23
  import { QueryFinding, QueryFindingCode, QueryFindingSeverity, analyzeQueryFindings } from "./findings/query-findings.cjs";
24
- export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, CostReduction, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FingerprintFn, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, isTestFilePath, isTestOriginQuery, normalizedFingerprint, parseNudges };
24
+ import { CiVerdict, NO_ACTION, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, notifyHuman, renderVerdictMarkdown } from "./ci/verdict.cjs";
25
+ import { CiConclusion, conclusionForVerdict, conclusionForVerdictClass, isBlocking } from "./ci/taxonomy.cjs";
26
+ import { ConditionPolicy, ConditionResult, DEFAULT_CONDITION_POLICIES, FALLBACK_POLICY, RepoPolicyConfig, ResolvedCondition, RunEvaluation, evaluateRun, policyFor } from "./ci/policy.cjs";
27
+ export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, CiConclusion, CiVerdict, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConditionPolicy, ConditionResult, ConnectionMode, CostReduction, DEFAULT_CONDITION_POLICIES, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FALLBACK_POLICY, FingerprintFn, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, NO_ACTION, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RepoPolicyConfig, ResolvedCondition, RootIndexCandidate, RunEvaluation, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, conclusionForVerdict, conclusionForVerdictClass, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, evaluateRun, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isBlocking, isIndexProbablyDroppable, isIndexSupported, isTestFilePath, isTestOriginQuery, normalizedFingerprint, notifyHuman, parseNudges, policyFor, renderVerdictMarkdown };
package/dist/index.d.mts CHANGED
@@ -21,4 +21,7 @@ import { ActionPlanNudgeQuery, ActionableStep, AffectedQueryCost, CostReduction,
21
21
  import { deriveSentryEnvironment } from "./sentry.mjs";
22
22
  import { ClientApi, ConnectionMode, ExtensionPresence, IndexDefinition, RepoConfig, ServerApi, UnauthenticatedServerApi } from "./websocket-server.mjs";
23
23
  import { QueryFinding, QueryFindingCode, QueryFindingSeverity, analyzeQueryFindings } from "./findings/query-findings.mjs";
24
- export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConnectionMode, CostReduction, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FingerprintFn, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RootIndexCandidate, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, isTestFilePath, isTestOriginQuery, normalizedFingerprint, parseNudges };
24
+ import { CiVerdict, NO_ACTION, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, notifyHuman, renderVerdictMarkdown } from "./ci/verdict.mjs";
25
+ import { CiConclusion, conclusionForVerdict, conclusionForVerdictClass, isBlocking } from "./ci/taxonomy.mjs";
26
+ import { ConditionPolicy, ConditionResult, DEFAULT_CONDITION_POLICIES, FALLBACK_POLICY, RepoPolicyConfig, ResolvedCondition, RunEvaluation, evaluateRun, policyFor } from "./ci/policy.mjs";
27
+ export { ActionPlanNudgeQuery, ActionPlanQuery, ActionableStep, AffectedQueryCost, AggregatedIndexRecommendation, AnalysisResult, Analyzer, CiConclusion, CiVerdict, ClientApi, ColumnMetadata, CombinedExport, CompactSelectListOptions, ComputedColumnStats, ComputedReltuples, ComputedStats, ConditionPolicy, ConditionResult, ConnectionMode, CostReduction, DEFAULT_CONDITION_POLICIES, DUMP_STATS_SQL, DatabaseDriver, DiscoveredColumnReference, DomainLabel, DuplicateIndexGroup, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, ExtensionPresence, FALLBACK_POLICY, FingerprintFn, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexAction, IndexDefinition, IndexGroup, IndexIdentifier, IndexOptimizer, IndexRecommendation, IndexToCreate, JsonbOperator, LiveQueryOptimization, NO_ACTION, Nudge, NudgeFinding, OptimizationResult, OptimizeResult, OptimizedQuery, PROCEED, Parameter, Parser, Path, PermutedIndexCandidate, PgIdentifier, Postgres, PostgresConnectionInput, PostgresExplainResult, PostgresExplainStage, PostgresExplainStageCommon, PostgresExplainStageSchema, PostgresFactory, PostgresQueryBuilder, PostgresQueryBuilderCommand, PostgresStage, PostgresStageId, PostgresTransaction, PostgresVersion, PssRewriter, QueryFinding, QueryFindingCode, QueryFindingSeverity, QueryHash, QuerySource, RecentQuery, RegressionBreach, RepoConfig, RepoPolicyConfig, ResolvedCondition, RootIndexCandidate, RunEvaluation, SKIP, SQLCommenterExtraction, SQLCommenterTag, SerializeResult, ServerApi, SortContext, StatementType, Statistics, StatisticsMode, StatisticsSource, TableMetadata, TableReference, TableStats, UnauthenticatedServerApi, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, conclusionForVerdict, conclusionForVerdictClass, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, evaluateRun, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isBlocking, isIndexProbablyDroppable, isIndexSupported, isTestFilePath, isTestOriginQuery, normalizedFingerprint, notifyHuman, parseNudges, policyFor, renderVerdictMarkdown };
package/dist/index.mjs CHANGED
@@ -19,4 +19,7 @@ import { buildActionPlan } from "./action-plan/build-action-plan.mjs";
19
19
  import { deriveSentryEnvironment } from "./sentry.mjs";
20
20
  import { OptimizedQuery, RecentQuery } from "./query.mjs";
21
21
  import { analyzeQueryFindings } from "./findings/query-findings.mjs";
22
- export { Analyzer, CombinedExport, ComputedColumnStats, ComputedReltuples, ComputedStats, DUMP_STATS_SQL, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexOptimizer, OptimizedQuery, PROCEED, PgIdentifier, PostgresExplainStageSchema, PostgresQueryBuilder, PostgresVersion, PssRewriter, RecentQuery, SKIP, Statistics, StatisticsMode, StatisticsSource, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isIndexProbablyDroppable, isIndexSupported, isTestFilePath, isTestOriginQuery, normalizedFingerprint, parseNudges };
22
+ import { CiVerdict, NO_ACTION, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, notifyHuman, renderVerdictMarkdown } from "./ci/verdict.mjs";
23
+ import { conclusionForVerdict, conclusionForVerdictClass, isBlocking } from "./ci/taxonomy.mjs";
24
+ import { DEFAULT_CONDITION_POLICIES, FALLBACK_POLICY, evaluateRun, policyFor } from "./ci/policy.mjs";
25
+ export { Analyzer, CiVerdict, CombinedExport, ComputedColumnStats, ComputedReltuples, ComputedStats, DEFAULT_CONDITION_POLICIES, DUMP_STATS_SQL, ExportedQuery, ExportedStats, ExportedStatsColumns, ExportedStatsIndex, ExportedStatsStatistics, ExportedStatsV1, FALLBACK_POLICY, FullSchema, FullSchemaColumn, FullSchemaCompositeAttribute, FullSchemaConstraint, FullSchemaExtension, FullSchemaFunction, FullSchemaIncludedColumn, FullSchemaIndex, FullSchemaKeyColumn, FullSchemaTable, FullSchemaTrigger, FullSchemaType, FullSchemaTypeConstraint, FullSchemaView, IndexOptimizer, NO_ACTION, OptimizedQuery, PROCEED, PgIdentifier, PostgresExplainStageSchema, PostgresQueryBuilder, PostgresVersion, PssRewriter, RecentQuery, SKIP, Statistics, StatisticsMode, StatisticsSource, VERDICT_CONTRACT_VERSION, VerdictAction, VerdictActionKind, VerdictClass, aggregateIndexRecommendations, analyzeQueryFindings, buildActionPlan, combinedDumpSql, compactSelectList, conclusionForVerdict, conclusionForVerdictClass, deriveSentryEnvironment, dropIndex, dumpQueriesSql, dumpSchema, evaluateRun, groupDuplicateIndexes, groupIndexesByCoverage, ignoredIdentifier, indexCovers, isBlocking, isIndexProbablyDroppable, isIndexSupported, isTestFilePath, isTestOriginQuery, normalizedFingerprint, notifyHuman, parseNudges, policyFor, renderVerdictMarkdown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@query-doctor/core",
3
- "version": "0.10.10",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "Core logic for Query Doctor",
6
6
  "license": "",