@deftai/directive-core 0.66.2 → 0.67.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/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/constants.d.ts +5 -2
- package/dist/doctor/constants.js +15 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/types.d.ts +7 -0
- 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/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.js +4 -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/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 +5 -1
- package/dist/pr-merge-readiness/compute.js +90 -6
- 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 +3 -0
- package/dist/pr-merge-readiness/index.js +2 -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/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/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 +55 -22
- package/dist/swarm/launch.d.ts +6 -0
- package/dist/swarm/launch.js +2 -2
- 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/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 +11 -3
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
1
2
|
type JsonObject = Record<string, unknown>;
|
|
3
|
+
/** PROJECT-DEFINITION artifact shape (filename + envelope key + emitted version). */
|
|
4
|
+
interface ProjectDefinitionLayout {
|
|
5
|
+
readonly filename: string;
|
|
6
|
+
readonly infoRootKey: typeof MIGRATED_INFO_ROOT_KEY | typeof LEGACY_INFO_ROOT_KEY;
|
|
7
|
+
readonly infoVersion: string;
|
|
8
|
+
}
|
|
2
9
|
/** Durable review state for PROJECT-DEFINITION narrative staleness (#640). */
|
|
3
10
|
export interface StalenessReviewMetadata {
|
|
4
11
|
/** ISO-8601 UTC when narratives were last reviewed/acknowledged. */
|
|
@@ -24,12 +31,19 @@ export declare function buildStalenessAcknowledgement(completedItems: readonly L
|
|
|
24
31
|
existing?: StalenessReviewMetadata | null;
|
|
25
32
|
}): StalenessReviewMetadata;
|
|
26
33
|
export declare function computeStalenessFlags(narratives: Record<string, string>, completedItems: readonly LifecycleItem[], review?: StalenessReviewMetadata | null): string[];
|
|
27
|
-
export declare function createSkeleton(items: LifecycleItem[], now: string): JsonObject;
|
|
34
|
+
export declare function createSkeleton(items: LifecycleItem[], now: string, layout: ProjectDefinitionLayout): JsonObject;
|
|
28
35
|
export interface RenderProjectOptions {
|
|
29
36
|
readonly now?: Date;
|
|
30
37
|
}
|
|
31
38
|
export type RenderProjectResult = readonly [boolean, string];
|
|
32
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Regenerate the PROJECT-DEFINITION artifact for `vbriefDir`.
|
|
41
|
+
*
|
|
42
|
+
* Layout-aware (#2149): on a migrated `xbrief/` root it targets
|
|
43
|
+
* `PROJECT-DEFINITION.xbrief.json` with an `xBRIEFInfo` envelope; on a legacy `vbrief/`
|
|
44
|
+
* root it keeps `PROJECT-DEFINITION.vbrief.json` + `vBRIEFInfo`.
|
|
45
|
+
* (Mirrors ``scripts/project_render.render_project_definition``.)
|
|
46
|
+
*/
|
|
33
47
|
export declare function renderProjectDefinition(vbriefDir: string, options?: RenderProjectOptions): RenderProjectResult;
|
|
34
48
|
/**
|
|
35
49
|
* Mark current completed scopes as reviewed for PROJECT-DEFINITION narratives.
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
|
|
4
4
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
5
5
|
import { deriveRegistryItemStatus, registryMetadataReferencesFromScope, } from "../vbrief-validate/registry-status.js";
|
|
6
|
+
import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
|
|
6
7
|
import { PROJECT_LIFECYCLE_FOLDERS, SKELETON_NARRATIVES } from "./constants.js";
|
|
7
8
|
import { splitCamel, splitWords } from "./text-utils.js";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the PROJECT-DEFINITION artifact shape for a lifecycle root directory.
|
|
11
|
+
*
|
|
12
|
+
* The decision is STRUCTURAL and keyed on the lifecycle root directory name -- the
|
|
13
|
+
* same signal `resolveLifecycleLayout` / `resolveLifecycleRoot` produce, so ingest and
|
|
14
|
+
* render never diverge (#2149). A migrated `xbrief/` root gets `PROJECT-DEFINITION.xbrief.json`
|
|
15
|
+
* + `xBRIEFInfo`; a legacy `vbrief/` root keeps `PROJECT-DEFINITION.vbrief.json` + `vBRIEFInfo`.
|
|
16
|
+
* This prevents render from writing a legacy-named/enveloped artifact into a migrated tree.
|
|
17
|
+
*/
|
|
18
|
+
function resolveProjectDefinitionLayout(vbriefDir) {
|
|
19
|
+
const migrated = basename(vbriefDir) === MIGRATED_ARTIFACT_DIR;
|
|
20
|
+
return migrated
|
|
21
|
+
? {
|
|
22
|
+
filename: `PROJECT-DEFINITION${MIGRATED_ARTIFACT_SUFFIX}`,
|
|
23
|
+
infoRootKey: MIGRATED_INFO_ROOT_KEY,
|
|
24
|
+
infoVersion: VBRIEF_VERSION,
|
|
25
|
+
}
|
|
26
|
+
: {
|
|
27
|
+
filename: `PROJECT-DEFINITION${LEGACY_ARTIFACT_SUFFIX}`,
|
|
28
|
+
infoRootKey: LEGACY_INFO_ROOT_KEY,
|
|
29
|
+
infoVersion: EMITTED_VBRIEF_VERSION,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
8
32
|
export function scanLifecycleFolders(vbriefDir) {
|
|
9
33
|
const items = [];
|
|
10
34
|
for (const folderName of PROJECT_LIFECYCLE_FOLDERS) {
|
|
@@ -25,11 +49,11 @@ export function scanLifecycleFolders(vbriefDir) {
|
|
|
25
49
|
try {
|
|
26
50
|
const data = JSON.parse(readFileSync(full, "utf8"));
|
|
27
51
|
const plan = (data.plan ?? {});
|
|
28
|
-
const title = String(plan.title ?? vbriefFile
|
|
52
|
+
const title = String(plan.title ?? stripArtifactSuffix(vbriefFile));
|
|
29
53
|
const status = deriveRegistryItemStatus(plan.status, folderName);
|
|
30
54
|
const references = registryMetadataReferencesFromScope(plan.references);
|
|
31
55
|
const item = {
|
|
32
|
-
id: vbriefFile
|
|
56
|
+
id: stripArtifactSuffix(vbriefFile),
|
|
33
57
|
title,
|
|
34
58
|
status,
|
|
35
59
|
metadata: {
|
|
@@ -44,7 +68,7 @@ export function scanLifecycleFolders(vbriefDir) {
|
|
|
44
68
|
}
|
|
45
69
|
catch {
|
|
46
70
|
items.push({
|
|
47
|
-
id: vbriefFile
|
|
71
|
+
id: stripArtifactSuffix(vbriefFile),
|
|
48
72
|
title: `[unreadable] ${vbriefFile}`,
|
|
49
73
|
status: "draft",
|
|
50
74
|
metadata: {
|
|
@@ -136,12 +160,12 @@ export function computeStalenessFlags(narratives, completedItems, review = null)
|
|
|
136
160
|
const pending = unacknowledgedCompletedItems(completedItems, review);
|
|
137
161
|
return flagStaleNarratives(narratives, pending);
|
|
138
162
|
}
|
|
139
|
-
export function createSkeleton(items, now) {
|
|
163
|
+
export function createSkeleton(items, now, layout) {
|
|
140
164
|
const completedItems = items.filter((i) => i.status === "completed");
|
|
141
165
|
const stalenessFlags = computeStalenessFlags({ ...SKELETON_NARRATIVES }, completedItems);
|
|
142
166
|
return {
|
|
143
|
-
|
|
144
|
-
version:
|
|
167
|
+
[layout.infoRootKey]: {
|
|
168
|
+
version: layout.infoVersion,
|
|
145
169
|
description: "Project definition -- synthesized gestalt of the project",
|
|
146
170
|
created: now,
|
|
147
171
|
updated: now,
|
|
@@ -155,11 +179,19 @@ export function createSkeleton(items, now) {
|
|
|
155
179
|
},
|
|
156
180
|
};
|
|
157
181
|
}
|
|
158
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* Regenerate the PROJECT-DEFINITION artifact for `vbriefDir`.
|
|
184
|
+
*
|
|
185
|
+
* Layout-aware (#2149): on a migrated `xbrief/` root it targets
|
|
186
|
+
* `PROJECT-DEFINITION.xbrief.json` with an `xBRIEFInfo` envelope; on a legacy `vbrief/`
|
|
187
|
+
* root it keeps `PROJECT-DEFINITION.vbrief.json` + `vBRIEFInfo`.
|
|
188
|
+
* (Mirrors ``scripts/project_render.render_project_definition``.)
|
|
189
|
+
*/
|
|
159
190
|
export function renderProjectDefinition(vbriefDir, options = {}) {
|
|
160
191
|
const nowDate = options.now ?? new Date();
|
|
161
192
|
const now = nowDate.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
162
|
-
const
|
|
193
|
+
const layout = resolveProjectDefinitionLayout(vbriefDir);
|
|
194
|
+
const projectDefPath = join(vbriefDir, layout.filename);
|
|
163
195
|
const items = scanLifecycleFolders(vbriefDir);
|
|
164
196
|
const createdNew = !existsSync(projectDefPath);
|
|
165
197
|
let projectDef;
|
|
@@ -172,10 +204,11 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
|
|
|
172
204
|
}
|
|
173
205
|
const plan = (projectDef.plan ?? {});
|
|
174
206
|
plan.items = items;
|
|
175
|
-
if (typeof projectDef.
|
|
176
|
-
projectDef.
|
|
207
|
+
if (typeof projectDef[layout.infoRootKey] !== "object" ||
|
|
208
|
+
projectDef[layout.infoRootKey] === null) {
|
|
209
|
+
projectDef[layout.infoRootKey] = {};
|
|
177
210
|
}
|
|
178
|
-
projectDef.
|
|
211
|
+
projectDef[layout.infoRootKey].updated = now;
|
|
179
212
|
const narratives = typeof plan.narratives === "object" &&
|
|
180
213
|
plan.narratives !== null &&
|
|
181
214
|
!Array.isArray(plan.narratives)
|
|
@@ -194,7 +227,7 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
|
|
|
194
227
|
projectDef.plan = plan;
|
|
195
228
|
}
|
|
196
229
|
else {
|
|
197
|
-
projectDef = createSkeleton(items, now);
|
|
230
|
+
projectDef = createSkeleton(items, now, layout);
|
|
198
231
|
}
|
|
199
232
|
mkdirSync(vbriefDir, { recursive: true });
|
|
200
233
|
writeFileSync(projectDefPath, `${JSON.stringify(projectDef, null, 2)}\n`, "utf8");
|
|
@@ -202,7 +235,7 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
|
|
|
202
235
|
const planMeta = (projectDef.plan?.metadata ?? {});
|
|
203
236
|
const flagCount = Array.isArray(planMeta.staleness_flags) ? planMeta.staleness_flags.length : 0;
|
|
204
237
|
const action = createdNew ? "created" : "updated";
|
|
205
|
-
const parts = [`✓
|
|
238
|
+
const parts = [`✓ ${layout.filename} ${action} (${itemCount} scope items)`];
|
|
206
239
|
if (flagCount > 0)
|
|
207
240
|
parts.push(`⚠ ${flagCount} staleness flag(s) -- agent review recommended`);
|
|
208
241
|
return [true, parts.join("\n")];
|
|
@@ -216,7 +249,8 @@ export function renderProjectDefinition(vbriefDir, options = {}) {
|
|
|
216
249
|
export function acknowledgeProjectDefinitionStaleness(vbriefDir, options = {}) {
|
|
217
250
|
const nowDate = options.now ?? new Date();
|
|
218
251
|
const now = isoTimestamp(nowDate);
|
|
219
|
-
const
|
|
252
|
+
const layout = resolveProjectDefinitionLayout(vbriefDir);
|
|
253
|
+
const projectDefPath = join(vbriefDir, layout.filename);
|
|
220
254
|
if (!existsSync(projectDefPath)) {
|
|
221
255
|
return [false, `✗ ${projectDefPath} not found — run project:render first`];
|
|
222
256
|
}
|
|
@@ -245,10 +279,11 @@ export function acknowledgeProjectDefinitionStaleness(vbriefDir, options = {}) {
|
|
|
245
279
|
? plan.narratives
|
|
246
280
|
: {};
|
|
247
281
|
planMetadata.staleness_flags = computeStalenessFlags(narratives, completedItems, parseStalenessReview(planMetadata));
|
|
248
|
-
if (typeof projectDef.
|
|
249
|
-
projectDef.
|
|
282
|
+
if (typeof projectDef[layout.infoRootKey] !== "object" ||
|
|
283
|
+
projectDef[layout.infoRootKey] === null) {
|
|
284
|
+
projectDef[layout.infoRootKey] = {};
|
|
250
285
|
}
|
|
251
|
-
projectDef.
|
|
286
|
+
projectDef[layout.infoRootKey].updated = now;
|
|
252
287
|
projectDef.plan = plan;
|
|
253
288
|
writeFileSync(projectDefPath, `${JSON.stringify(projectDef, null, 2)}\n`, "utf8");
|
|
254
289
|
const ackCount = completedItems.length;
|
|
@@ -280,9 +315,7 @@ export function main(argv) {
|
|
|
280
315
|
process.stderr.write("Usage: project-render [--acknowledge-staleness] [--project-root <dir>] [vbrief_dir]\n");
|
|
281
316
|
return 2;
|
|
282
317
|
}
|
|
283
|
-
const vbriefDir = projectRoot !== undefined
|
|
284
|
-
? resolveLifecycleRoot(resolve(projectRoot))
|
|
285
|
-
: (positional[0] ?? "vbrief");
|
|
318
|
+
const vbriefDir = positional[0] ?? resolveLifecycleRoot(resolve(projectRoot !== undefined ? projectRoot : "."));
|
|
286
319
|
const [ok, message] = acknowledge
|
|
287
320
|
? acknowledgeProjectDefinitionStaleness(vbriefDir)
|
|
288
321
|
: renderProjectDefinition(vbriefDir);
|
package/dist/swarm/launch.d.ts
CHANGED
|
@@ -68,6 +68,12 @@ export interface LaunchArgs {
|
|
|
68
68
|
readinessGate?: ReadinessGateFn;
|
|
69
69
|
worktreeResolver?: WorktreeResolverFn;
|
|
70
70
|
runtimeAuthProbe?: RuntimeAuthProbeFn;
|
|
71
|
+
/**
|
|
72
|
+
* Injection seam for the routing-provider environment lookup, mirroring
|
|
73
|
+
* `resolveRoutingPath`'s `environ` parameter (#1877 Greptile follow-up).
|
|
74
|
+
* Defaults to `process.env` when unset.
|
|
75
|
+
*/
|
|
76
|
+
environ?: NodeJS.ProcessEnv;
|
|
71
77
|
}
|
|
72
78
|
export declare function swarmLaunch(args: LaunchArgs): {
|
|
73
79
|
exitCode: number;
|
package/dist/swarm/launch.js
CHANGED
|
@@ -8,7 +8,7 @@ import { issueNumbersFromPlan, scopeMetadataRank } from "../triage/queue/scope-w
|
|
|
8
8
|
import { selectionOrderingKey } from "../triage/queue/selection.js";
|
|
9
9
|
import { DEFAULT_BASE_BRANCH, EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK, GATE_ADVISE, GATE_ENFORCE, LEAF_CODING_WORKER_ROLE, } from "./constants.js";
|
|
10
10
|
import { readinessReport } from "./readiness.js";
|
|
11
|
-
import {
|
|
11
|
+
import { loadRoutingFile, resolveDispatchProvider, resolveModelRoute, resolveRoutingPath, } from "./routing.js";
|
|
12
12
|
import { dispatchProviderFor, enforceSubagentBackendPolicy } from "./subagent-backend.js";
|
|
13
13
|
import { resolveWorktreeMap } from "./worktrees.js";
|
|
14
14
|
export const defaultPreflightGate = (vbriefPath) => {
|
|
@@ -525,7 +525,7 @@ export function swarmLaunch(args) {
|
|
|
525
525
|
let modelSource = null;
|
|
526
526
|
let routingProvider = null;
|
|
527
527
|
if (routingFile !== null) {
|
|
528
|
-
routingProvider =
|
|
528
|
+
routingProvider = resolveDispatchProvider(args.environ ?? process.env);
|
|
529
529
|
const route = resolveModelRoute(routingFile, routingProvider, LEAF_CODING_WORKER_ROLE);
|
|
530
530
|
// A malformed decision object must fail loud here: the legacy backend gate
|
|
531
531
|
// was already bypassed above (routingFile !== null), so silently continuing
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* biome-config.ts -- guard against #2190 (biome check . reports
|
|
3
|
+
* recommended-preset diagnostics as errors vs warnings non-deterministically
|
|
4
|
+
* on a pinned biome version).
|
|
5
|
+
*
|
|
6
|
+
* A preset-inherited rule severity can silently change tier across a biome
|
|
7
|
+
* version bump (or, per #2190, apparently even across runs on the identical
|
|
8
|
+
* pinned version). Pinning `noUnusedVariables` / `noNonNullAssertion` to an
|
|
9
|
+
* explicit non-"error" severity in biome.json makes the tier config-owned so
|
|
10
|
+
* it cannot flip a required CI check to failing on pre-existing diagnostics.
|
|
11
|
+
*/
|
|
12
|
+
/** The two rules #2190 observed flipping severity in CI (see docs/analysis/2026-07-02-2190-biome-determinism.md). */
|
|
13
|
+
export declare const GUARDED_RULES: ReadonlyArray<{
|
|
14
|
+
readonly group: string;
|
|
15
|
+
readonly rule: string;
|
|
16
|
+
}>;
|
|
17
|
+
export interface RuleSeverityFinding {
|
|
18
|
+
readonly group: string;
|
|
19
|
+
readonly rule: string;
|
|
20
|
+
/** The literal severity value found, or null when the rule has no explicit entry. */
|
|
21
|
+
readonly severity: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface BiomeConfigGuardResult {
|
|
24
|
+
readonly code: 0 | 1 | 2;
|
|
25
|
+
readonly findings: readonly RuleSeverityFinding[];
|
|
26
|
+
readonly message: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse a biome.json document (already JSON.parse'd) and report the explicit
|
|
30
|
+
* severity declared for each guarded rule.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findRuleSeverities(biomeConfig: unknown, rules?: ReadonlyArray<{
|
|
33
|
+
readonly group: string;
|
|
34
|
+
readonly rule: string;
|
|
35
|
+
}>): RuleSeverityFinding[];
|
|
36
|
+
/**
|
|
37
|
+
* Evaluate biome.json at `projectRoot` and fail closed when a guarded rule
|
|
38
|
+
* has no explicit severity, or an explicit severity of "error".
|
|
39
|
+
*/
|
|
40
|
+
export declare function evaluateBiomeConfigGuard(projectRoot?: string): BiomeConfigGuardResult;
|
|
41
|
+
//# sourceMappingURL=biome-config.d.ts.map
|