@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.
- package/dist/agents-md-advisory/evaluate.d.ts +44 -0
- package/dist/agents-md-advisory/evaluate.js +115 -0
- package/dist/agents-md-advisory/index.d.ts +2 -0
- package/dist/agents-md-advisory/index.js +2 -0
- package/dist/agents-md-budget/evaluate.d.ts +38 -0
- package/dist/agents-md-budget/evaluate.js +152 -0
- package/dist/agents-md-budget/index.d.ts +2 -0
- package/dist/agents-md-budget/index.js +2 -0
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +14 -4
- package/dist/check/orchestrator.js +30 -6
- package/dist/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/checks.js +11 -11
- package/dist/doctor/constants.d.ts +7 -2
- package/dist/doctor/constants.js +17 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/payload-staleness.js +15 -6
- package/dist/doctor/types.d.ts +7 -0
- package/dist/forward-coverage/evaluate.d.ts +55 -0
- package/dist/forward-coverage/evaluate.js +254 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/constants.d.ts +14 -0
- package/dist/init-deposit/constants.js +10 -0
- package/dist/init-deposit/hygiene.d.ts +50 -0
- package/dist/init-deposit/hygiene.js +176 -0
- package/dist/init-deposit/init-deposit.d.ts +1 -0
- package/dist/init-deposit/init-deposit.js +7 -1
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +48 -30
- package/dist/init-deposit/scaffold.d.ts +3 -4
- package/dist/init-deposit/scaffold.js +3 -24
- package/dist/intake/github-auth-modes.d.ts +1 -0
- package/dist/intake/github-auth-modes.js +1 -0
- package/dist/intake/issue-ingest.d.ts +6 -2
- package/dist/intake/issue-ingest.js +65 -9
- package/dist/integration-e2e/helpers.d.ts +0 -1
- package/dist/integration-e2e/helpers.js +3 -7
- package/dist/layout/lifecycle-ref.d.ts +15 -0
- package/dist/layout/lifecycle-ref.js +55 -0
- package/dist/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.d.ts +28 -0
- package/dist/platform/agents-md.js +38 -1
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/agents-md-advisory.d.ts +52 -0
- package/dist/policy/agents-md-advisory.js +63 -0
- package/dist/policy/agents-md-budget.d.ts +24 -0
- package/dist/policy/agents-md-budget.js +89 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/resolve.js +56 -46
- package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
- package/dist/pr-merge-readiness/ci-gate.js +79 -0
- package/dist/pr-merge-readiness/compute.d.ts +15 -1
- package/dist/pr-merge-readiness/compute.js +129 -9
- package/dist/pr-merge-readiness/gh.d.ts +8 -0
- package/dist/pr-merge-readiness/gh.js +30 -5
- package/dist/pr-merge-readiness/index.d.ts +5 -1
- package/dist/pr-merge-readiness/index.js +3 -0
- package/dist/pr-merge-readiness/main.d.ts +3 -0
- package/dist/pr-merge-readiness/main.js +73 -6
- package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
- package/dist/pr-merge-readiness/mergeability.js +105 -0
- package/dist/pr-merge-readiness/output.js +20 -0
- package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
- package/dist/pr-merge-readiness/slizard-gate.js +119 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +8 -2
- package/dist/pr-monitor/monitor.js +35 -6
- package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
- package/dist/pr-wait-mergeable/wrappers.js +13 -5
- package/dist/pr-watch/constants.d.ts +33 -0
- package/dist/pr-watch/constants.js +33 -0
- package/dist/pr-watch/index.d.ts +6 -0
- package/dist/pr-watch/index.js +6 -0
- package/dist/pr-watch/main.d.ts +21 -0
- package/dist/pr-watch/main.js +206 -0
- package/dist/pr-watch/probe.d.ts +13 -0
- package/dist/pr-watch/probe.js +100 -0
- package/dist/pr-watch/types.d.ts +47 -0
- package/dist/pr-watch/types.js +2 -0
- package/dist/pr-watch/watch.d.ts +12 -0
- package/dist/pr-watch/watch.js +122 -0
- package/dist/preflight-cache/evaluate.d.ts +1 -0
- package/dist/preflight-cache/evaluate.js +17 -10
- package/dist/render/project-render.d.ts +16 -2
- package/dist/render/project-render.js +155 -97
- package/dist/scope/capacity-stamp.d.ts +24 -2
- package/dist/scope/capacity-stamp.js +80 -28
- package/dist/scope/decomposed-refs.js +2 -2
- package/dist/scope/project-definition-sync.js +3 -3
- package/dist/scope/vbrief-ref.d.ts +2 -1
- package/dist/scope/vbrief-ref.js +3 -2
- package/dist/spec-authority/constants.d.ts +6 -0
- package/dist/spec-authority/constants.js +11 -0
- package/dist/spec-authority/resolver.js +3 -3
- package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
- package/dist/swarm/finalize-cohort-cli.js +100 -0
- package/dist/swarm/finalize-cohort.d.ts +39 -0
- package/dist/swarm/finalize-cohort.js +589 -0
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/launch.d.ts +13 -0
- package/dist/swarm/launch.js +19 -7
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +11 -10
- package/dist/swarm/routing.d.ts +9 -0
- package/dist/swarm/routing.js +46 -0
- package/dist/swarm/verify-review-clean-cli.js +25 -1
- package/dist/swarm/verify-review-clean.d.ts +9 -1
- package/dist/swarm/verify-review-clean.js +76 -3
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/project.d.ts +6 -1
- package/dist/triage/queue/project.js +9 -4
- package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
- package/dist/triage/queue/reconcile-live-state.js +54 -0
- package/dist/triage/scope/mutations-core.js +91 -81
- package/dist/triage/scope/resolve.js +4 -3
- package/dist/triage/scope-drift/add-ignore.js +33 -32
- package/dist/triage/scope-drift/scope-rules.js +3 -3
- package/dist/triage/subscribe/index.js +51 -45
- package/dist/triage/welcome/writers.js +79 -64
- package/dist/vbrief-build/project-definition-io.js +8 -3
- package/dist/vbrief-validate/paths.js +8 -4
- package/dist/vbrief-validate/precutover.d.ts +1 -1
- package/dist/vbrief-validate/precutover.js +12 -10
- package/dist/verify-source/biome-config.d.ts +41 -0
- package/dist/verify-source/biome-config.js +97 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/xbrief-migrate/agents-header.d.ts +69 -0
- package/dist/xbrief-migrate/agents-header.js +179 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
- package/dist/xbrief-migrate/fs-helpers.js +11 -0
- package/dist/xbrief-migrate/index.d.ts +1 -0
- package/dist/xbrief-migrate/index.js +1 -0
- package/dist/xbrief-migrate/migrate-project.js +29 -10
- package/package.json +15 -7
- package/dist/install-upgrade/index.d.ts +0 -13
- 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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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;
|
package/dist/swarm/routing.d.ts
CHANGED
|
@@ -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
|
package/dist/swarm/routing.js
CHANGED
|
@@ -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({
|
|
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 {
|
|
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
|
-
/**
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
/**
|
|
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 =
|
|
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
|