@deftai/directive-core 0.66.2 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/agents-md-advisory/evaluate.d.ts +44 -0
  2. package/dist/agents-md-advisory/evaluate.js +115 -0
  3. package/dist/agents-md-advisory/index.d.ts +2 -0
  4. package/dist/agents-md-advisory/index.js +2 -0
  5. package/dist/agents-md-budget/evaluate.d.ts +38 -0
  6. package/dist/agents-md-budget/evaluate.js +152 -0
  7. package/dist/agents-md-budget/index.d.ts +2 -0
  8. package/dist/agents-md-budget/index.js +2 -0
  9. package/dist/check/index.d.ts +1 -1
  10. package/dist/check/index.js +1 -1
  11. package/dist/check/orchestrator.d.ts +14 -4
  12. package/dist/check/orchestrator.js +30 -6
  13. package/dist/codebase/map.d.ts +1 -0
  14. package/dist/codebase/map.js +5 -2
  15. package/dist/doctor/checks.js +11 -11
  16. package/dist/doctor/constants.d.ts +7 -2
  17. package/dist/doctor/constants.js +17 -1
  18. package/dist/doctor/flags.js +5 -1
  19. package/dist/doctor/main.js +99 -8
  20. package/dist/doctor/payload-staleness.js +15 -6
  21. package/dist/doctor/types.d.ts +7 -0
  22. package/dist/forward-coverage/evaluate.d.ts +55 -0
  23. package/dist/forward-coverage/evaluate.js +254 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/init-deposit/constants.d.ts +14 -0
  27. package/dist/init-deposit/constants.js +10 -0
  28. package/dist/init-deposit/hygiene.d.ts +50 -0
  29. package/dist/init-deposit/hygiene.js +176 -0
  30. package/dist/init-deposit/init-deposit.d.ts +1 -0
  31. package/dist/init-deposit/init-deposit.js +7 -1
  32. package/dist/init-deposit/refresh.d.ts +2 -0
  33. package/dist/init-deposit/refresh.js +48 -30
  34. package/dist/init-deposit/scaffold.d.ts +3 -4
  35. package/dist/init-deposit/scaffold.js +3 -24
  36. package/dist/intake/github-auth-modes.d.ts +1 -0
  37. package/dist/intake/github-auth-modes.js +1 -0
  38. package/dist/intake/issue-ingest.d.ts +6 -2
  39. package/dist/intake/issue-ingest.js +65 -9
  40. package/dist/integration-e2e/helpers.d.ts +0 -1
  41. package/dist/integration-e2e/helpers.js +3 -7
  42. package/dist/layout/lifecycle-ref.d.ts +15 -0
  43. package/dist/layout/lifecycle-ref.js +55 -0
  44. package/dist/platform/agents-consumer-header.d.ts +13 -0
  45. package/dist/platform/agents-consumer-header.js +57 -0
  46. package/dist/platform/agents-md.d.ts +28 -0
  47. package/dist/platform/agents-md.js +38 -1
  48. package/dist/platform/index.d.ts +1 -0
  49. package/dist/platform/index.js +1 -0
  50. package/dist/policy/agents-md-advisory.d.ts +52 -0
  51. package/dist/policy/agents-md-advisory.js +63 -0
  52. package/dist/policy/agents-md-budget.d.ts +24 -0
  53. package/dist/policy/agents-md-budget.js +89 -0
  54. package/dist/policy/index.d.ts +1 -0
  55. package/dist/policy/index.js +1 -0
  56. package/dist/policy/resolve.js +56 -46
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
  58. package/dist/pr-merge-readiness/ci-gate.js +79 -0
  59. package/dist/pr-merge-readiness/compute.d.ts +15 -1
  60. package/dist/pr-merge-readiness/compute.js +129 -9
  61. package/dist/pr-merge-readiness/gh.d.ts +8 -0
  62. package/dist/pr-merge-readiness/gh.js +30 -5
  63. package/dist/pr-merge-readiness/index.d.ts +5 -1
  64. package/dist/pr-merge-readiness/index.js +3 -0
  65. package/dist/pr-merge-readiness/main.d.ts +3 -0
  66. package/dist/pr-merge-readiness/main.js +73 -6
  67. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  68. package/dist/pr-merge-readiness/mergeability.js +105 -0
  69. package/dist/pr-merge-readiness/output.js +20 -0
  70. package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
  71. package/dist/pr-merge-readiness/slizard-gate.js +119 -0
  72. package/dist/pr-monitor/index.d.ts +1 -1
  73. package/dist/pr-monitor/index.js +1 -1
  74. package/dist/pr-monitor/monitor.d.ts +8 -2
  75. package/dist/pr-monitor/monitor.js +35 -6
  76. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  77. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  78. package/dist/pr-watch/constants.d.ts +33 -0
  79. package/dist/pr-watch/constants.js +33 -0
  80. package/dist/pr-watch/index.d.ts +6 -0
  81. package/dist/pr-watch/index.js +6 -0
  82. package/dist/pr-watch/main.d.ts +21 -0
  83. package/dist/pr-watch/main.js +206 -0
  84. package/dist/pr-watch/probe.d.ts +13 -0
  85. package/dist/pr-watch/probe.js +100 -0
  86. package/dist/pr-watch/types.d.ts +47 -0
  87. package/dist/pr-watch/types.js +2 -0
  88. package/dist/pr-watch/watch.d.ts +12 -0
  89. package/dist/pr-watch/watch.js +122 -0
  90. package/dist/preflight-cache/evaluate.d.ts +1 -0
  91. package/dist/preflight-cache/evaluate.js +17 -10
  92. package/dist/render/project-render.d.ts +16 -2
  93. package/dist/render/project-render.js +155 -97
  94. package/dist/scope/capacity-stamp.d.ts +24 -2
  95. package/dist/scope/capacity-stamp.js +80 -28
  96. package/dist/scope/decomposed-refs.js +2 -2
  97. package/dist/scope/project-definition-sync.js +3 -3
  98. package/dist/scope/vbrief-ref.d.ts +2 -1
  99. package/dist/scope/vbrief-ref.js +3 -2
  100. package/dist/spec-authority/constants.d.ts +6 -0
  101. package/dist/spec-authority/constants.js +11 -0
  102. package/dist/spec-authority/resolver.js +3 -3
  103. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  104. package/dist/swarm/finalize-cohort-cli.js +100 -0
  105. package/dist/swarm/finalize-cohort.d.ts +39 -0
  106. package/dist/swarm/finalize-cohort.js +589 -0
  107. package/dist/swarm/index.d.ts +2 -0
  108. package/dist/swarm/index.js +2 -0
  109. package/dist/swarm/launch.d.ts +13 -0
  110. package/dist/swarm/launch.js +19 -7
  111. package/dist/swarm/routing-verify.d.ts +1 -1
  112. package/dist/swarm/routing-verify.js +11 -10
  113. package/dist/swarm/routing.d.ts +9 -0
  114. package/dist/swarm/routing.js +46 -0
  115. package/dist/swarm/verify-review-clean-cli.js +25 -1
  116. package/dist/swarm/verify-review-clean.d.ts +9 -1
  117. package/dist/swarm/verify-review-clean.js +76 -3
  118. package/dist/triage/queue/index.d.ts +1 -0
  119. package/dist/triage/queue/index.js +1 -0
  120. package/dist/triage/queue/project.d.ts +6 -1
  121. package/dist/triage/queue/project.js +9 -4
  122. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  123. package/dist/triage/queue/reconcile-live-state.js +54 -0
  124. package/dist/triage/scope/mutations-core.js +91 -81
  125. package/dist/triage/scope/resolve.js +4 -3
  126. package/dist/triage/scope-drift/add-ignore.js +33 -32
  127. package/dist/triage/scope-drift/scope-rules.js +3 -3
  128. package/dist/triage/subscribe/index.js +51 -45
  129. package/dist/triage/welcome/writers.js +79 -64
  130. package/dist/vbrief-build/project-definition-io.js +8 -3
  131. package/dist/vbrief-validate/paths.js +8 -4
  132. package/dist/vbrief-validate/precutover.d.ts +1 -1
  133. package/dist/vbrief-validate/precutover.js +12 -10
  134. package/dist/verify-source/biome-config.d.ts +41 -0
  135. package/dist/verify-source/biome-config.js +97 -0
  136. package/dist/verify-source/index.d.ts +1 -0
  137. package/dist/verify-source/index.js +1 -0
  138. package/dist/xbrief-migrate/agents-header.d.ts +69 -0
  139. package/dist/xbrief-migrate/agents-header.js +179 -0
  140. package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
  141. package/dist/xbrief-migrate/fs-helpers.js +11 -0
  142. package/dist/xbrief-migrate/index.d.ts +1 -0
  143. package/dist/xbrief-migrate/index.js +1 -0
  144. package/dist/xbrief-migrate/migrate-project.js +29 -10
  145. package/package.json +15 -7
  146. package/dist/install-upgrade/index.d.ts +0 -13
  147. package/dist/install-upgrade/index.js +0 -251
@@ -9,7 +9,7 @@ export interface VerifyRoutingOptions {
9
9
  advise?: boolean;
10
10
  /** Override the resolved provider (else derived from the runtime). */
11
11
  provider?: string | null;
12
- /** Inject the runtime descriptor (else getPlatformCapabilities().runtimeMode). */
12
+ /** Inject the runtime descriptor (legacy test seam; else resolveDispatchProvider). */
13
13
  runtimeProbe?: () => string;
14
14
  }
15
15
  export interface VerifyRoutingResult {
@@ -8,9 +8,8 @@
8
8
  * 1 = at least one in-scope role is undecided / not dispatchable
9
9
  * 2 = config error (unreadable / malformed route file)
10
10
  */
11
- import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
12
11
  import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK } from "./constants.js";
13
- import { dispatchProviderFromRuntime, HARNESS_BOUND_PROVIDERS, loadRoutingFile, ROUTING_MODE_HARNESS_DEFAULT, resolveModelRoute, resolveRoutingPath, } from "./routing.js";
12
+ import { dispatchProviderFromRuntime, HARNESS_BOUND_PROVIDERS, loadRoutingFile, ROUTING_MODE_HARNESS_DEFAULT, resolveDispatchProvider, resolveModelRoute, resolveRoutingPath, } from "./routing.js";
14
13
  export const ROUTING_SET_CMD = "task swarm:routing-set -- --role <role> --model <slug> (or --harness-default)";
15
14
  /** Roles the pre-dispatch gate checks by default: the actual model lever. */
16
15
  export const DEFAULT_GATED_ROLES = ["leaf-implementation"];
@@ -18,15 +17,17 @@ function resolveProvider(options) {
18
17
  if (options.provider !== undefined && options.provider !== null && options.provider.length > 0) {
19
18
  return options.provider;
20
19
  }
21
- const probe = options.runtimeProbe ?? (() => getPlatformCapabilities().runtimeMode);
22
- let runtimeMode = "";
23
- try {
24
- runtimeMode = probe();
25
- }
26
- catch {
27
- runtimeMode = "";
20
+ if (options.runtimeProbe !== undefined) {
21
+ let runtimeMode = "";
22
+ try {
23
+ runtimeMode = options.runtimeProbe();
24
+ }
25
+ catch {
26
+ runtimeMode = "";
27
+ }
28
+ return dispatchProviderFromRuntime(runtimeMode);
28
29
  }
29
- return dispatchProviderFromRuntime(runtimeMode);
30
+ return resolveDispatchProvider(options.environ ?? process.env);
30
31
  }
31
32
  export function verifyRouting(options) {
32
33
  const roles = options.roles && options.roles.length > 0 ? options.roles : DEFAULT_GATED_ROLES;
@@ -9,6 +9,8 @@ export declare const ROUTING_MODE_HARNESS_DEFAULT = "harness-default";
9
9
  export declare const ROUTING_FILENAME = "routing.local.json";
10
10
  /** Providers whose model is harness-bound -- deft cannot pin or verify a slug. */
11
11
  export declare const HARNESS_BOUND_PROVIDERS: Set<string>;
12
+ /** Providers whose per-role model must be decided before sub-agent dispatch (#1739 / #1877). */
13
+ export declare const ROUTING_GATED_DISPATCH_PROVIDERS: Set<string>;
12
14
  export interface RouteDecision {
13
15
  model: string | null;
14
16
  mode?: string;
@@ -48,6 +50,13 @@ export declare function loadRoutingFile(path: string): {
48
50
  export declare function resolveModelRoute(file: RoutingFile | null, provider: string, role: string): RouteResolution;
49
51
  /** Map a runtime descriptor (platform-capabilities.runtimeMode) to a route key. */
50
52
  export declare function dispatchProviderFromRuntime(runtimeMode: string): string;
53
+ /**
54
+ * Resolve the `dispatch_provider` routing key from the active runtime envelope.
55
+ * Separate from `runtime_mode` (#1557): Cursor sessions may carry
56
+ * `runtime_mode=cloud-headless` for gh-auth purposes but route under provider
57
+ * `cursor` for model selection (#1877).
58
+ */
59
+ export declare function resolveDispatchProvider(environ?: NodeJS.ProcessEnv): string;
51
60
  /**
52
61
  * Write a decision back to the route file (create-if-missing). Stamps
53
62
  * `decidedAt` when the caller did not supply one. Used by the interactive
@@ -31,6 +31,12 @@ export const ROUTING_MODE_HARNESS_DEFAULT = "harness-default";
31
31
  export const ROUTING_FILENAME = "routing.local.json";
32
32
  /** Providers whose model is harness-bound -- deft cannot pin or verify a slug. */
33
33
  export const HARNESS_BOUND_PROVIDERS = new Set(["grok"]);
34
+ /** Providers whose per-role model must be decided before sub-agent dispatch (#1739 / #1877). */
35
+ export const ROUTING_GATED_DISPATCH_PROVIDERS = new Set(["cursor", "grok"]);
36
+ const TRUTHY_ENV = new Set(["1", "true", "yes", "on"]);
37
+ function envTruthy(environ, name) {
38
+ return TRUTHY_ENV.has((environ[name] ?? "").trim().toLowerCase());
39
+ }
34
40
  /**
35
41
  * Resolve the route-file path. Honors the `DEFT_ROUTING_PATH` override first
36
42
  * (keeps both maintainer and consumer testable), then reads from the MAIN
@@ -150,12 +156,52 @@ export function dispatchProviderFromRuntime(runtimeMode) {
150
156
  }
151
157
  return normalized;
152
158
  }
159
+ /**
160
+ * Resolve the `dispatch_provider` routing key from the active runtime envelope.
161
+ * Separate from `runtime_mode` (#1557): Cursor sessions may carry
162
+ * `runtime_mode=cloud-headless` for gh-auth purposes but route under provider
163
+ * `cursor` for model selection (#1877).
164
+ */
165
+ export function resolveDispatchProvider(environ = process.env) {
166
+ if (envTruthy(environ, "CURSOR_COMPOSER") || envTruthy(environ, "CURSOR_AGENT")) {
167
+ return "cursor";
168
+ }
169
+ const runtime = (environ.DEFT_AGENT_RUNTIME ?? "").trim().toLowerCase();
170
+ if (envTruthy(environ, "GROK_BUILD") || runtime === "grok-build") {
171
+ return "grok";
172
+ }
173
+ if (runtime === "cloud" || runtime === "headless") {
174
+ return "cloud-headless";
175
+ }
176
+ if (envTruthy(environ, "GITHUB_ACTIONS") ||
177
+ envTruthy(environ, "BUILDKITE") ||
178
+ (envTruthy(environ, "CI") &&
179
+ !envTruthy(environ, "CURSOR_COMPOSER") &&
180
+ !envTruthy(environ, "CURSOR_AGENT"))) {
181
+ return "cloud-headless";
182
+ }
183
+ return "unknown";
184
+ }
185
+ /**
186
+ * Keys that would mutate the prototype chain rather than set an own property
187
+ * if used as a computed object key. Rejected for provider/role names so a
188
+ * malicious routing input cannot pollute `Object.prototype` (CodeQL
189
+ * js/prototype-polluting-assignment).
190
+ */
191
+ const FORBIDDEN_ROUTING_KEYS = new Set(["__proto__", "constructor", "prototype"]);
192
+ function assertSafeRoutingKey(kind, key) {
193
+ if (FORBIDDEN_ROUTING_KEYS.has(key)) {
194
+ throw new Error(`routing ${kind} name "${key}" is not allowed`);
195
+ }
196
+ }
153
197
  /**
154
198
  * Write a decision back to the route file (create-if-missing). Stamps
155
199
  * `decidedAt` when the caller did not supply one. Used by the interactive
156
200
  * resolver path (resolver step 5) and the `swarm:routing-set` task.
157
201
  */
158
202
  export function writeModelDecision(path, provider, role, decision) {
203
+ assertSafeRoutingKey("provider", provider);
204
+ assertSafeRoutingKey("role", role);
159
205
  const { data } = loadRoutingFile(path);
160
206
  const file = data ?? {};
161
207
  const existing = providerBlockOf(file, provider);
@@ -4,8 +4,11 @@ import { verifyReviewClean } from "./verify-review-clean.js";
4
4
  export function verifyReviewCleanMain(argv = process.argv.slice(2)) {
5
5
  const prNumbers = [];
6
6
  const cohortGlobs = [];
7
+ const ciIgnoreChecks = [];
7
8
  let repo = null;
8
9
  let emitJson = false;
10
+ let skipCi = false;
11
+ let skipSlizard = false;
9
12
  for (let i = 0; i < argv.length; i += 1) {
10
13
  const arg = argv[i];
11
14
  if (arg === "--cohort" && argv[i + 1] !== undefined) {
@@ -19,11 +22,32 @@ export function verifyReviewCleanMain(argv = process.argv.slice(2)) {
19
22
  else if (arg === "--json") {
20
23
  emitJson = true;
21
24
  }
25
+ else if (arg === "--skip-ci") {
26
+ skipCi = true;
27
+ }
28
+ else if (arg === "--skip-slizard") {
29
+ skipSlizard = true;
30
+ }
31
+ else if (arg === "--ci-ignore-check" && argv[i + 1] !== undefined) {
32
+ ciIgnoreChecks.push(argv[i + 1] ?? "");
33
+ i += 1;
34
+ }
35
+ else if (arg?.startsWith("--ci-ignore-check=")) {
36
+ ciIgnoreChecks.push(arg.slice("--ci-ignore-check=".length));
37
+ }
22
38
  else if (arg !== undefined && /^\d+$/.test(arg)) {
23
39
  prNumbers.push(Number.parseInt(arg, 10));
24
40
  }
25
41
  }
26
- const result = verifyReviewClean({ prNumbers, cohortGlobs, repo, emitJson });
42
+ const result = verifyReviewClean({
43
+ prNumbers,
44
+ cohortGlobs,
45
+ repo,
46
+ emitJson,
47
+ skipCi,
48
+ skipSlizard,
49
+ ciIgnoreChecks,
50
+ });
27
51
  if (result.stdout.length > 0) {
28
52
  process.stdout.write(result.stdout);
29
53
  }
@@ -1,4 +1,7 @@
1
+ import type { CiGateOptions } from "../pr-merge-readiness/ci-gate.js";
2
+ import type { SlizardGateOptions } from "../pr-merge-readiness/slizard-gate.js";
1
3
  import type { RunGhFn } from "../pr-merge-readiness/types.js";
4
+ type ReviewGateOptions = CiGateOptions & SlizardGateOptions;
2
5
  import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
3
6
  export interface CohortResolutionError {
4
7
  vbrief_path: string;
@@ -10,6 +13,8 @@ export interface CohortPRResult {
10
13
  verdict: Record<string, unknown>;
11
14
  failures: string[];
12
15
  clean: boolean;
16
+ ci_summary: Record<string, unknown> | null;
17
+ slizard_summary: Record<string, unknown> | null;
13
18
  }
14
19
  export interface CohortResult {
15
20
  repo: string | null;
@@ -21,7 +26,7 @@ export declare function resolveCohortFromVbriefs(vbriefGlobs: readonly string[])
21
26
  prNumbers: number[];
22
27
  failures: CohortResolutionError[];
23
28
  };
24
- export declare function evaluatePr(prNumber: number, repo: string | null, runGh?: RunGhFn): CohortPRResult | null;
29
+ export declare function evaluatePr(prNumber: number, repo: string | null, runGh?: RunGhFn, options?: ReviewGateOptions): CohortPRResult | null;
25
30
  export declare function cohortResultToDict(cohort: CohortResult): Record<string, unknown>;
26
31
  export declare function renderReviewCleanText(cohort: CohortResult): string;
27
32
  export interface VerifyReviewCleanArgs {
@@ -29,6 +34,9 @@ export interface VerifyReviewCleanArgs {
29
34
  cohortGlobs?: readonly string[];
30
35
  repo?: string | null;
31
36
  emitJson?: boolean;
37
+ skipCi?: boolean;
38
+ skipSlizard?: boolean;
39
+ ciIgnoreChecks?: readonly string[];
32
40
  runGh?: RunGhFn;
33
41
  }
34
42
  export declare function verifyReviewClean(args: VerifyReviewCleanArgs): {
@@ -1,8 +1,10 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { hasArtifactSuffix } from "../layout/resolve.js";
4
- import { defaultRunGh, fetchGreptileCommentBody, fetchPrHeadSha, } from "../pr-merge-readiness/gh.js";
4
+ import { buildCiSummaryLine, evaluateCiGate } from "../pr-merge-readiness/ci-gate.js";
5
+ import { defaultRunGh, fetchCheckRunsRest, fetchGreptileCommentBody, fetchPrHeadSha, } from "../pr-merge-readiness/gh.js";
5
6
  import { evaluateGates, parseGreptileBody } from "../pr-merge-readiness/index.js";
7
+ import { evaluateSlizardGate, isSlizardCheck } from "../pr-merge-readiness/slizard-gate.js";
6
8
  import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
7
9
  function extractPrFromUri(uri) {
8
10
  const pullIdx = uri.indexOf("/pull/");
@@ -101,7 +103,7 @@ export function resolveCohortFromVbriefs(vbriefGlobs) {
101
103
  }
102
104
  return { prNumbers: seenPrs, failures };
103
105
  }
104
- export function evaluatePr(prNumber, repo, runGh = defaultRunGh) {
106
+ export function evaluatePr(prNumber, repo, runGh = defaultRunGh, options = {}) {
105
107
  const headSha = fetchPrHeadSha(prNumber, repo, runGh);
106
108
  if (headSha === null) {
107
109
  return null;
@@ -112,12 +114,71 @@ export function evaluatePr(prNumber, repo, runGh = defaultRunGh) {
112
114
  }
113
115
  const verdict = parseGreptileBody(body);
114
116
  const failures = evaluateGates(prNumber, headSha, verdict);
117
+ let ciSummary = null;
118
+ let slizardSummary = null;
119
+ if (failures.length === 0) {
120
+ if (repo === null) {
121
+ failures.push("Could not resolve repo for required CI check-run gate. " +
122
+ "Use --repo OWNER/REPO or run from a checked-out repository.");
123
+ ciSummary = {
124
+ ready_state: "blocked",
125
+ error: "repo unresolved for check-runs lookup",
126
+ };
127
+ slizardSummary = { ready_state: "skipped", present: false };
128
+ }
129
+ else if (options.skipCi === true) {
130
+ const ci = evaluateCiGate([], { skipCi: true });
131
+ ciSummary = {
132
+ ...ci.summary,
133
+ summary_line: "CI check-runs: skipped (--skip-ci)",
134
+ };
135
+ const slizard = evaluateSlizardGate([], options);
136
+ failures.push(...slizard.failures);
137
+ slizardSummary = { ...slizard.summary };
138
+ }
139
+ else {
140
+ const checks = fetchCheckRunsRest(headSha, repo, runGh);
141
+ if (checks.summary === null) {
142
+ failures.push("Required CI check-runs could not be fetched; fail closed by default (#2169). " +
143
+ `Root cause: ${checks.error}`);
144
+ ciSummary = {
145
+ ready_state: "blocked",
146
+ error: checks.error,
147
+ checked_count: 0,
148
+ ignored_checks: [...(options.ignoreCheckNames ?? [])],
149
+ failed_required: [],
150
+ pending_required: [],
151
+ conclusions: [],
152
+ };
153
+ slizardSummary = { ready_state: "skipped", present: false };
154
+ }
155
+ else {
156
+ const slizard = evaluateSlizardGate(checks.checkRuns, options);
157
+ // SLizard is scored by the structured gate; exclude it from the generic CI set.
158
+ const slizardNames = checks.checkRuns
159
+ .filter((r) => isSlizardCheck(r.name))
160
+ .map((r) => r.name);
161
+ const ci = evaluateCiGate(checks.checkRuns, {
162
+ skipCi: options.skipCi,
163
+ ignoreCheckNames: [...(options.ignoreCheckNames ?? []), ...slizardNames],
164
+ });
165
+ failures.push(...ci.failures, ...slizard.failures);
166
+ ciSummary = {
167
+ ...ci.summary,
168
+ summary_line: buildCiSummaryLine(ci.summary),
169
+ };
170
+ slizardSummary = { ...slizard.summary };
171
+ }
172
+ }
173
+ }
115
174
  return {
116
175
  pr_number: prNumber,
117
176
  head_sha: headSha,
118
177
  verdict: { ...verdict },
119
178
  failures: [...failures],
120
179
  clean: failures.length === 0,
180
+ ci_summary: ciSummary,
181
+ slizard_summary: slizardSummary,
121
182
  };
122
183
  }
123
184
  export function cohortResultToDict(cohort) {
@@ -131,6 +192,8 @@ export function cohortResultToDict(cohort) {
131
192
  clean: r.clean,
132
193
  verdict: r.verdict,
133
194
  failures: r.failures,
195
+ ci_summary: r.ci_summary,
196
+ slizard_summary: r.slizard_summary,
134
197
  })),
135
198
  resolution_errors: cohort.resolution_errors,
136
199
  };
@@ -159,6 +222,12 @@ export function renderReviewCleanText(cohort) {
159
222
  lines.push(` Findings: P0=${String(v.p0Count ?? 0)} ` +
160
223
  `P1=${String(v.p1Count ?? 0)} P2=${String(v.p2Count ?? 0)}`);
161
224
  lines.push(` Errored sentinel: ${v.errored === true ? "True" : "False"}`);
225
+ if (r.ci_summary && typeof r.ci_summary.summary_line === "string") {
226
+ lines.push(` ${r.ci_summary.summary_line}`);
227
+ }
228
+ if (r.slizard_summary && typeof r.slizard_summary.summary_line === "string") {
229
+ lines.push(` ${r.slizard_summary.summary_line}`);
230
+ }
162
231
  r.failures.forEach((fail, index) => {
163
232
  lines.push(` [${index + 1}] ${fail}`);
164
233
  });
@@ -212,7 +281,11 @@ export function verifyReviewClean(args) {
212
281
  const runGh = args.runGh ?? defaultRunGh;
213
282
  const prResults = [];
214
283
  for (const prNum of prNumbers) {
215
- const perPr = evaluatePr(prNum, args.repo ?? null, runGh);
284
+ const perPr = evaluatePr(prNum, args.repo ?? null, runGh, {
285
+ skipCi: args.skipCi,
286
+ skipSlizard: args.skipSlizard,
287
+ ignoreCheckNames: args.ciIgnoreChecks,
288
+ });
216
289
  if (perPr === null) {
217
290
  return { exitCode: EXIT_EXTERNAL_ERROR, stdout: "", stderr: "" };
218
291
  }
@@ -4,6 +4,7 @@ export * from "./cache.js";
4
4
  export * from "./constants.js";
5
5
  export * from "./derive-group.js";
6
6
  export * from "./ranking-labels.js";
7
+ export * from "./reconcile-live-state.js";
7
8
  export * from "./render.js";
8
9
  export * from "./repo.js";
9
10
  export * from "./scope-ignores-filter.js";
@@ -4,6 +4,7 @@ export * from "./cache.js";
4
4
  export * from "./constants.js";
5
5
  export * from "./derive-group.js";
6
6
  export * from "./ranking-labels.js";
7
+ export * from "./reconcile-live-state.js";
7
8
  export * from "./render.js";
8
9
  export * from "./repo.js";
9
10
  export * from "./scope-ignores-filter.js";
@@ -1,6 +1,11 @@
1
1
  export interface ProjectDefinition {
2
2
  readonly plan?: unknown;
3
3
  }
4
- /** Read vbrief/PROJECT-DEFINITION.vbrief.json. Returns null if absent/invalid. */
4
+ /**
5
+ * Read the layout-resolved PROJECT-DEFINITION artifact (#2207). Resolves the
6
+ * `xbrief/` layout on migrated trees and falls back to `vbrief/` otherwise, so
7
+ * `plan.policy.triageRankingLabels` load correctly after the #2109 rename.
8
+ * Returns null if absent/invalid.
9
+ */
5
10
  export declare function loadProjectDefinition(projectRoot: string): ProjectDefinition | null;
6
11
  //# sourceMappingURL=project.d.ts.map
@@ -1,9 +1,14 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { PROJECT_DEFINITION_REL_PATH } from "./constants.js";
4
- /** Read vbrief/PROJECT-DEFINITION.vbrief.json. Returns null if absent/invalid. */
2
+ import { resolve } from "node:path";
3
+ import { resolveProjectDefinitionPath } from "../../layout/resolve.js";
4
+ /**
5
+ * Read the layout-resolved PROJECT-DEFINITION artifact (#2207). Resolves the
6
+ * `xbrief/` layout on migrated trees and falls back to `vbrief/` otherwise, so
7
+ * `plan.policy.triageRankingLabels` load correctly after the #2109 rename.
8
+ * Returns null if absent/invalid.
9
+ */
5
10
  export function loadProjectDefinition(projectRoot) {
6
- const path = join(resolve(projectRoot), PROJECT_DEFINITION_REL_PATH);
11
+ const path = resolveProjectDefinitionPath(resolve(projectRoot));
7
12
  if (!existsSync(path)) {
8
13
  return null;
9
14
  }
@@ -0,0 +1,42 @@
1
+ import type { CachedIssue } from "./types.js";
2
+ /**
3
+ * Resolve the set of issue numbers that are currently OPEN on the live SCM.
4
+ *
5
+ * Returns `null` when the live state could not be determined (offline, gh
6
+ * missing / unauthenticated, REST error). Callers MUST treat `null` as "do not
7
+ * reconcile" and fall back to the cached view -- a transient read failure must
8
+ * never empty the queue.
9
+ */
10
+ export type LiveOpenIssuesReader = (repo: string) => ReadonlySet<number> | null;
11
+ /**
12
+ * Default reader: one batched, paginated REST list (`state=open`, PRs excluded)
13
+ * routed through the scm gh-rest shim. Prefers REST over GraphQL and issues a
14
+ * single logical lookup for the whole open set rather than N per-issue calls.
15
+ * Any failure is swallowed to `null` so the queue degrades to cached rendering.
16
+ *
17
+ * Latency ceiling: no explicit `limit` is passed ON PURPOSE. The reconcile drops
18
+ * any candidate absent from this set, so a truncated page would incorrectly drop
19
+ * genuinely-open issues (a false-negative is worse than the latency). The set is
20
+ * therefore the COMPLETE live-open list, bounded only by `restIssueListPaginated`'s
21
+ * pagination cap (`REST_PAGINATION_MAX_PAGES` * per-page = 100 * 100 = 10,000 open
22
+ * issues); a repo with more open issues than that raises rather than silently
23
+ * truncating. In practice open-issue counts are 10^1-10^2, so this is 1-2 REST
24
+ * pages. Batch here, never per-candidate.
25
+ */
26
+ export declare function defaultLiveOpenIssuesReader(repo: string): ReadonlySet<number> | null;
27
+ /**
28
+ * Reconcile cached candidate issues against live open/closed state before the
29
+ * queue renders (#2238). The cached candidate set records `state`, but that
30
+ * value goes stale as soon as an issue is closed/merged and the cache has not
31
+ * yet refreshed -- so honoring the cached flag alone is insufficient.
32
+ *
33
+ * Drops any candidate whose number is NOT in the live-open set. When the reader
34
+ * returns `null` (state undeterminable) the candidates pass through unchanged so
35
+ * a network / auth failure never silently empties the queue.
36
+ *
37
+ * `reader` defaults to {@link defaultLiveOpenIssuesReader}; tests (and the CLI)
38
+ * inject a stub via this optional parameter -- there is no module-level mutable
39
+ * seam, so nothing test-only leaks into the package's public API.
40
+ */
41
+ export declare function reconcileLiveOpenState(issues: readonly CachedIssue[], repo: string, reader?: LiveOpenIssuesReader): readonly CachedIssue[];
42
+ //# sourceMappingURL=reconcile-live-state.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { restIssueListPaginated } from "../../scm/gh-rest.js";
2
+ /**
3
+ * Default reader: one batched, paginated REST list (`state=open`, PRs excluded)
4
+ * routed through the scm gh-rest shim. Prefers REST over GraphQL and issues a
5
+ * single logical lookup for the whole open set rather than N per-issue calls.
6
+ * Any failure is swallowed to `null` so the queue degrades to cached rendering.
7
+ *
8
+ * Latency ceiling: no explicit `limit` is passed ON PURPOSE. The reconcile drops
9
+ * any candidate absent from this set, so a truncated page would incorrectly drop
10
+ * genuinely-open issues (a false-negative is worse than the latency). The set is
11
+ * therefore the COMPLETE live-open list, bounded only by `restIssueListPaginated`'s
12
+ * pagination cap (`REST_PAGINATION_MAX_PAGES` * per-page = 100 * 100 = 10,000 open
13
+ * issues); a repo with more open issues than that raises rather than silently
14
+ * truncating. In practice open-issue counts are 10^1-10^2, so this is 1-2 REST
15
+ * pages. Batch here, never per-candidate.
16
+ */
17
+ export function defaultLiveOpenIssuesReader(repo) {
18
+ try {
19
+ const rows = restIssueListPaginated(repo, { state: "open", excludePulls: true });
20
+ const open = new Set();
21
+ for (const row of rows) {
22
+ const n = row.number;
23
+ if (typeof n === "number" && Number.isInteger(n)) {
24
+ open.add(n);
25
+ }
26
+ }
27
+ return open;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ /**
34
+ * Reconcile cached candidate issues against live open/closed state before the
35
+ * queue renders (#2238). The cached candidate set records `state`, but that
36
+ * value goes stale as soon as an issue is closed/merged and the cache has not
37
+ * yet refreshed -- so honoring the cached flag alone is insufficient.
38
+ *
39
+ * Drops any candidate whose number is NOT in the live-open set. When the reader
40
+ * returns `null` (state undeterminable) the candidates pass through unchanged so
41
+ * a network / auth failure never silently empties the queue.
42
+ *
43
+ * `reader` defaults to {@link defaultLiveOpenIssuesReader}; tests (and the CLI)
44
+ * inject a stub via this optional parameter -- there is no module-level mutable
45
+ * seam, so nothing test-only leaks into the package's public API.
46
+ */
47
+ export function reconcileLiveOpenState(issues, repo, reader = defaultLiveOpenIssuesReader) {
48
+ const liveOpen = reader(repo);
49
+ if (liveOpen === null) {
50
+ return issues;
51
+ }
52
+ return issues.filter((issue) => liveOpen.has(issue.number));
53
+ }
54
+ //# sourceMappingURL=reconcile-live-state.js.map