@deftai/directive-core 0.106.0 → 0.107.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/check/gate-lists.js +2 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +17 -1
- package/dist/hooks/dispatcher.js +82 -11
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/write-fence.js +3 -0
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/evaluate.js +36 -5
- package/dist/scm/design-critique-chip.d.ts +37 -0
- package/dist/scm/design-critique-chip.js +171 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +2 -1
- package/dist/scm/readiness.js +7 -1
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +18 -0
- package/dist/session/git.js +81 -1
- package/dist/session/occupancy.d.ts +2 -1
- package/dist/session/occupancy.js +11 -4
- package/dist/session/session-start.d.ts +12 -0
- package/dist/session/session-start.js +17 -0
- package/dist/session/verify-session-ritual.d.ts +22 -3
- package/dist/session/verify-session-ritual.js +29 -4
- package/dist/swarm/launch.d.ts +69 -0
- package/dist/swarm/launch.js +191 -1
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +3 -3
|
@@ -10,9 +10,10 @@ import { pythonJsonDump } from "./json.js";
|
|
|
10
10
|
import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
|
|
11
11
|
import { defaultRitualRunner } from "./ritual-entrypoint.js";
|
|
12
12
|
import { readRitualState, ritualStateMarksRearmNeeded, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
13
|
-
import { formatSessionStartRecoveryCommand, GATED_STEPS, QUICK_STEPS, } from "./session-start.js";
|
|
13
|
+
import { formatSessionStartRecoveryCommand, GATED_STEPS, QUICK_STEPS, WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS, } from "./session-start.js";
|
|
14
14
|
import { resolveSessionRitualStalenessHours } from "./staleness.js";
|
|
15
15
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
16
|
+
export { WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS };
|
|
16
17
|
export const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
17
18
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
18
19
|
export const GATED_ENTRYPOINT_COMMANDS = {
|
|
@@ -244,7 +245,8 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
if (input.tier === "gated") {
|
|
247
|
-
|
|
248
|
+
const requiredGated = input.requiredGatedSteps ?? GATED_STEPS;
|
|
249
|
+
for (const stepName of requiredGated) {
|
|
248
250
|
const step = state.gatedSteps[stepName];
|
|
249
251
|
if (!stepPasses(step)) {
|
|
250
252
|
return {
|
|
@@ -261,12 +263,31 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
261
263
|
recoveryTier: null,
|
|
262
264
|
};
|
|
263
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Options for the write/spawn mutation verifier (#3738).
|
|
268
|
+
* Required set omits `cache_fresh`; execution authority is `agent_hooks` only.
|
|
269
|
+
*/
|
|
270
|
+
export function writeGateRitualOptions(extras = {}) {
|
|
271
|
+
return {
|
|
272
|
+
...extras,
|
|
273
|
+
tier: "gated",
|
|
274
|
+
posture: extras.posture ?? "mutation",
|
|
275
|
+
requiredGatedSteps: WRITE_GATED_REQUIRED_STEPS,
|
|
276
|
+
executeGatedSteps: WRITE_GATED_EXECUTE_STEPS,
|
|
277
|
+
// Write dispatch already refuses skip (dispatcher passes bypass: false).
|
|
278
|
+
// Default the same here so CI DEFT_SESSION_RITUAL_SKIP=1 cannot empty
|
|
279
|
+
// the execute loop in tests that use this helper (#3738).
|
|
280
|
+
bypass: extras.bypass ?? false,
|
|
281
|
+
envSkip: extras.envSkip ?? "",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
264
284
|
/**
|
|
265
285
|
* Read-only ritual-state inspection for host hooks.
|
|
266
286
|
*
|
|
267
287
|
* Unlike {@link verifySessionRitual}, this never runs missing gated entrypoints
|
|
268
|
-
* and never rewrites `.deft/ritual-state.json`.
|
|
269
|
-
*
|
|
288
|
+
* and never rewrites `.deft/ritual-state.json`. Write dispatch does **not** use
|
|
289
|
+
* this function: it must still execute `agent_hooks` (#3738). A PreToolUse
|
|
290
|
+
* decision must not become a hidden `doctor` / cache-refresh mutation boundary.
|
|
270
291
|
*/
|
|
271
292
|
export function inspectSessionRitual(projectRoot, options = {}) {
|
|
272
293
|
const tier = options.tier ?? "quick";
|
|
@@ -442,7 +463,10 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
442
463
|
const runCmd = options.runner ?? defaultRitualRunner;
|
|
443
464
|
const forced = new Set(options.forceGatedSteps ?? []);
|
|
444
465
|
const detect = options.detectWorkSelection ?? detectWorkSelection;
|
|
466
|
+
const executable = new Set(options.executeGatedSteps ?? [...GATED_STEPS]);
|
|
445
467
|
for (const stepName of GATED_STEPS) {
|
|
468
|
+
if (!executable.has(stepName))
|
|
469
|
+
continue;
|
|
446
470
|
const step = gated[stepName];
|
|
447
471
|
if (step?.deferred_reason && stepName !== "agent_hooks")
|
|
448
472
|
continue;
|
|
@@ -519,6 +543,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
519
543
|
now: instant,
|
|
520
544
|
runGit: options.runGit,
|
|
521
545
|
rebindForwardHead: true,
|
|
546
|
+
requiredGatedSteps: options.requiredGatedSteps,
|
|
522
547
|
});
|
|
523
548
|
if (isBypassed) {
|
|
524
549
|
return {
|
package/dist/swarm/launch.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ExpectedGithubWorkerPrincipal, type GhRunner, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN } from "../intake/github-auth-modes.js";
|
|
1
2
|
import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK } from "./constants.js";
|
|
2
3
|
import { type WorktreeRecord } from "./worktrees.js";
|
|
3
4
|
export interface ResolvedStory {
|
|
@@ -21,6 +22,70 @@ export type RuntimeAuthProbeFn = () => [string, string];
|
|
|
21
22
|
export declare const defaultPreflightGate: PreflightGateFn;
|
|
22
23
|
export declare const defaultReadinessGate: ReadinessGateFn;
|
|
23
24
|
export declare const defaultRuntimeAuthProbe: RuntimeAuthProbeFn;
|
|
25
|
+
export type WorkerCredentialDispatchPath = "grok-build" | "local-hybrid";
|
|
26
|
+
/**
|
|
27
|
+
* Dispatch-envelope half of the runtime / GitHub auth-mode contract (#1557 / #1351).
|
|
28
|
+
* Policy labels only — never a token value.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatDispatchAuthEnvelope(fields: {
|
|
31
|
+
runtimeMode: string;
|
|
32
|
+
githubAuthMode: string;
|
|
33
|
+
expectedGithubLogin?: string | null;
|
|
34
|
+
}): string;
|
|
35
|
+
export interface WorkerCredentialInjectionRequest {
|
|
36
|
+
environ?: NodeJS.ProcessEnv;
|
|
37
|
+
githubAuthMode: string;
|
|
38
|
+
runtimeMode?: string | null;
|
|
39
|
+
dispatchPath?: WorkerCredentialDispatchPath;
|
|
40
|
+
expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
|
|
41
|
+
repo?: string;
|
|
42
|
+
runGh?: GhRunner;
|
|
43
|
+
cwd?: string;
|
|
44
|
+
}
|
|
45
|
+
export type WorkerCredentialInjectionResult = {
|
|
46
|
+
ok: true;
|
|
47
|
+
injected: true;
|
|
48
|
+
githubAuthMode: typeof GITHUB_AUTH_MODE_INJECTED_TOKEN;
|
|
49
|
+
runtimeMode: string | null;
|
|
50
|
+
expectedLogin: string;
|
|
51
|
+
spawnEnv: {
|
|
52
|
+
GH_TOKEN: string;
|
|
53
|
+
GITHUB_TOKEN: string;
|
|
54
|
+
GH_ENTERPRISE_TOKEN: string;
|
|
55
|
+
DEFT_EXPECTED_GITHUB_LOGIN: string;
|
|
56
|
+
};
|
|
57
|
+
envelopeSection: string;
|
|
58
|
+
} | {
|
|
59
|
+
ok: true;
|
|
60
|
+
injected: false;
|
|
61
|
+
githubAuthMode: typeof GITHUB_AUTH_MODE_HOST_GH;
|
|
62
|
+
runtimeMode: string | null;
|
|
63
|
+
expectedLogin: string | null;
|
|
64
|
+
spawnEnv: Record<string, never>;
|
|
65
|
+
envelopeSection: string;
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
blocked: true;
|
|
69
|
+
githubAuthMode: string;
|
|
70
|
+
runtimeMode: string | null;
|
|
71
|
+
missingCredential: string;
|
|
72
|
+
remedy: string;
|
|
73
|
+
detail: string;
|
|
74
|
+
failureKind: string | null;
|
|
75
|
+
envelopeSection: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Validate a held user-bearing worker credential and prepare spawn-time injection (#1351).
|
|
79
|
+
*
|
|
80
|
+
* Comparison is opt-in on the validator. This path always stamps
|
|
81
|
+
* `DEFT_EXPECTED_GITHUB_LOGIN` after a successful user-principal check so the
|
|
82
|
+
* worker envelope does not inherit "any user token is approved."
|
|
83
|
+
*
|
|
84
|
+
* Installation credentials fail closed via the existing validator (#3693).
|
|
85
|
+
* Token values belong only in the returned `spawnEnv` — never in prompts,
|
|
86
|
+
* transcripts, or manifest entries.
|
|
87
|
+
*/
|
|
88
|
+
export declare function prepareWorkerCredentialInjection(request: WorkerCredentialInjectionRequest): WorkerCredentialInjectionResult;
|
|
24
89
|
/**
|
|
25
90
|
* True when a brief in `xbrief/completed/` (or the legacy `vbrief/completed/`)
|
|
26
91
|
* references the given issue number in its `references[]` / `Traces`. Used by
|
|
@@ -74,6 +139,7 @@ export declare function buildManifest(resolved: readonly ResolvedStory[], option
|
|
|
74
139
|
modelSource?: string | null;
|
|
75
140
|
runtimeMode?: string | null;
|
|
76
141
|
githubAuthMode?: string | null;
|
|
142
|
+
expectedGithubLogin?: string | null;
|
|
77
143
|
occupancySessionId?: string | null;
|
|
78
144
|
}): Record<string, unknown>[];
|
|
79
145
|
export declare function orderCohort(resolved: readonly ResolvedStory[], _projectRoot: string): ResolvedStory[];
|
|
@@ -103,6 +169,9 @@ export interface LaunchArgs {
|
|
|
103
169
|
* Defaults to `process.env` when unset.
|
|
104
170
|
*/
|
|
105
171
|
environ?: NodeJS.ProcessEnv;
|
|
172
|
+
/** Optional gh runner for identity-bound credential injection (#1351). */
|
|
173
|
+
runGh?: GhRunner;
|
|
174
|
+
expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
|
|
106
175
|
}
|
|
107
176
|
export declare function swarmLaunch(args: LaunchArgs): {
|
|
108
177
|
exitCode: number;
|
package/dist/swarm/launch.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { containedWrite } from "../fs/contained-write.js";
|
|
4
|
-
import { inferGithubAuthMode } from "../intake/github-auth-modes.js";
|
|
4
|
+
import { ENV_EXPECTED_GITHUB_LOGIN, FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE, FAILURE_MISSING_INJECTED_TOKEN, findInjectedToken, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN, inferGithubAuthMode, validateGithubAuthForWorker, } from "../intake/github-auth-modes.js";
|
|
5
5
|
import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
|
|
6
6
|
import { hasArtifactSuffix, resolveLifecycleFolder, stripArtifactSuffix, } from "../layout/resolve.js";
|
|
7
7
|
import { evaluate as preflightEvaluate } from "../preflight/evaluate.js";
|
|
@@ -25,6 +25,164 @@ export const defaultRuntimeAuthProbe = () => {
|
|
|
25
25
|
const report = getPlatformCapabilities();
|
|
26
26
|
return [report.runtimeMode, inferGithubAuthMode(report)];
|
|
27
27
|
};
|
|
28
|
+
const CREDENTIAL_VALUE_PATTERN = /^(ghp_|gho_|ghu_|ghs_|github_pat_)/i;
|
|
29
|
+
const INJECTION_MISSING_CREDENTIAL = "GH_TOKEN (or GITHUB_TOKEN / GH_ENTERPRISE_TOKEN)";
|
|
30
|
+
const INJECTION_DISPATCHER_REMEDY = "Dispatcher-side remedy: load an approved user-bearing worker credential into the dispatcher environment, then call prepareWorkerCredentialInjection before spawn. Do not fall back to the host gh token or continue under the maintainer identity. Installation credentials are not injectable; see #3693.";
|
|
31
|
+
const HELD_WORKER_TOKEN_VARS = [
|
|
32
|
+
"GH_TOKEN",
|
|
33
|
+
"GITHUB_TOKEN",
|
|
34
|
+
"GH_ENTERPRISE_TOKEN",
|
|
35
|
+
"GITHUB_ENTERPRISE_TOKEN",
|
|
36
|
+
];
|
|
37
|
+
function looksLikeCredentialValue(value) {
|
|
38
|
+
return CREDENTIAL_VALUE_PATTERN.test(value.trim());
|
|
39
|
+
}
|
|
40
|
+
function assertNoCredentialValue(value, label) {
|
|
41
|
+
if (looksLikeCredentialValue(value)) {
|
|
42
|
+
throw new Error(`${label} must not contain a credential value`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function sanitizeEnvelopeField(value, label) {
|
|
46
|
+
const cleaned = value.replace(/\r?\n/g, " ").trim();
|
|
47
|
+
assertNoCredentialValue(cleaned, label);
|
|
48
|
+
return cleaned;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Bind validation to the selected token. Stamp it into every gh token slot and
|
|
52
|
+
* drop GH_CONFIG_DIR so a host/enterprise config cannot authenticate as someone
|
|
53
|
+
* else. GH_HOST stays: it selects the API host, not the secret.
|
|
54
|
+
*/
|
|
55
|
+
function isolateHeldWorkerToken(environ, token) {
|
|
56
|
+
const isolated = { ...environ };
|
|
57
|
+
for (const name of HELD_WORKER_TOKEN_VARS) {
|
|
58
|
+
isolated[name] = token;
|
|
59
|
+
}
|
|
60
|
+
delete isolated.GH_CONFIG_DIR;
|
|
61
|
+
return isolated;
|
|
62
|
+
}
|
|
63
|
+
function boundWorkerSpawnEnv(token, expectedLogin) {
|
|
64
|
+
return {
|
|
65
|
+
GH_TOKEN: token,
|
|
66
|
+
GITHUB_TOKEN: token,
|
|
67
|
+
GH_ENTERPRISE_TOKEN: token,
|
|
68
|
+
[ENV_EXPECTED_GITHUB_LOGIN]: expectedLogin,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Dispatch-envelope half of the runtime / GitHub auth-mode contract (#1557 / #1351).
|
|
73
|
+
* Policy labels only — never a token value.
|
|
74
|
+
*/
|
|
75
|
+
export function formatDispatchAuthEnvelope(fields) {
|
|
76
|
+
const runtimeMode = sanitizeEnvelopeField(fields.runtimeMode, "runtime_mode");
|
|
77
|
+
const githubAuthMode = sanitizeEnvelopeField(fields.githubAuthMode, "github_auth_mode");
|
|
78
|
+
const lines = [
|
|
79
|
+
"## Runtime and GitHub auth mode",
|
|
80
|
+
"",
|
|
81
|
+
`- runtime_mode: ${runtimeMode}`,
|
|
82
|
+
`- github_auth_mode: ${githubAuthMode}`,
|
|
83
|
+
];
|
|
84
|
+
if (fields.expectedGithubLogin !== undefined && fields.expectedGithubLogin !== null) {
|
|
85
|
+
const login = sanitizeEnvelopeField(fields.expectedGithubLogin, "expected_github_login");
|
|
86
|
+
if (login.length > 0) {
|
|
87
|
+
lines.push(`- expected_github_login: ${login}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return `${lines.join("\n")}\n`;
|
|
91
|
+
}
|
|
92
|
+
function blockedInjection(fields) {
|
|
93
|
+
const detail = fields.detail.startsWith("BLOCKED") ? fields.detail : `BLOCKED: ${fields.detail}`;
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
blocked: true,
|
|
97
|
+
githubAuthMode: fields.githubAuthMode,
|
|
98
|
+
runtimeMode: fields.runtimeMode,
|
|
99
|
+
missingCredential: fields.missingCredential ?? INJECTION_MISSING_CREDENTIAL,
|
|
100
|
+
remedy: INJECTION_DISPATCHER_REMEDY,
|
|
101
|
+
detail,
|
|
102
|
+
failureKind: fields.failureKind,
|
|
103
|
+
envelopeSection: formatDispatchAuthEnvelope({
|
|
104
|
+
runtimeMode: fields.runtimeMode ?? "unknown",
|
|
105
|
+
githubAuthMode: fields.githubAuthMode,
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate a held user-bearing worker credential and prepare spawn-time injection (#1351).
|
|
111
|
+
*
|
|
112
|
+
* Comparison is opt-in on the validator. This path always stamps
|
|
113
|
+
* `DEFT_EXPECTED_GITHUB_LOGIN` after a successful user-principal check so the
|
|
114
|
+
* worker envelope does not inherit "any user token is approved."
|
|
115
|
+
*
|
|
116
|
+
* Installation credentials fail closed via the existing validator (#3693).
|
|
117
|
+
* Token values belong only in the returned `spawnEnv` — never in prompts,
|
|
118
|
+
* transcripts, or manifest entries.
|
|
119
|
+
*/
|
|
120
|
+
export function prepareWorkerCredentialInjection(request) {
|
|
121
|
+
const environ = request.environ ?? process.env;
|
|
122
|
+
const runtimeMode = request.runtimeMode ?? null;
|
|
123
|
+
const token = findInjectedToken(environ);
|
|
124
|
+
const grokBuild = request.dispatchPath === "grok-build";
|
|
125
|
+
const effectiveMode = grokBuild || token !== null ? GITHUB_AUTH_MODE_INJECTED_TOKEN : request.githubAuthMode;
|
|
126
|
+
if (effectiveMode === GITHUB_AUTH_MODE_HOST_GH && token === null) {
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
injected: false,
|
|
130
|
+
githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
|
|
131
|
+
runtimeMode,
|
|
132
|
+
expectedLogin: null,
|
|
133
|
+
spawnEnv: {},
|
|
134
|
+
envelopeSection: formatDispatchAuthEnvelope({
|
|
135
|
+
runtimeMode: runtimeMode ?? "local-unsandboxed",
|
|
136
|
+
githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (token === null) {
|
|
141
|
+
return blockedInjection({
|
|
142
|
+
githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
|
|
143
|
+
runtimeMode,
|
|
144
|
+
failureKind: FAILURE_MISSING_INJECTED_TOKEN,
|
|
145
|
+
missingCredential: INJECTION_MISSING_CREDENTIAL,
|
|
146
|
+
detail: "missing worker credential GH_TOKEN (or GITHUB_TOKEN / GH_ENTERPRISE_TOKEN) for a write-requiring injected-token dispatch",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const isolatedEnviron = isolateHeldWorkerToken(environ, token);
|
|
150
|
+
const validated = validateGithubAuthForWorker(GITHUB_AUTH_MODE_INJECTED_TOKEN, {
|
|
151
|
+
environ: isolatedEnviron,
|
|
152
|
+
runtimeReport: { runtimeMode: runtimeMode ?? "cloud-headless" },
|
|
153
|
+
repo: request.repo,
|
|
154
|
+
runGh: request.runGh,
|
|
155
|
+
expectedPrincipal: request.expectedPrincipal,
|
|
156
|
+
cwd: request.cwd,
|
|
157
|
+
});
|
|
158
|
+
if (!validated.ok || validated.login === null || validated.login.trim().length === 0) {
|
|
159
|
+
const installation = validated.failureKind === FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE;
|
|
160
|
+
return blockedInjection({
|
|
161
|
+
githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
|
|
162
|
+
runtimeMode,
|
|
163
|
+
failureKind: validated.failureKind,
|
|
164
|
+
detail: installation
|
|
165
|
+
? `${validated.detail} Installation credentials are not injectable (#3693).`
|
|
166
|
+
: validated.detail,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const expectedLogin = validated.login.trim();
|
|
170
|
+
assertNoCredentialValue(expectedLogin, "expected_github_login");
|
|
171
|
+
const spawnEnv = boundWorkerSpawnEnv(token, expectedLogin);
|
|
172
|
+
return {
|
|
173
|
+
ok: true,
|
|
174
|
+
injected: true,
|
|
175
|
+
githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
|
|
176
|
+
runtimeMode,
|
|
177
|
+
expectedLogin,
|
|
178
|
+
spawnEnv,
|
|
179
|
+
envelopeSection: formatDispatchAuthEnvelope({
|
|
180
|
+
runtimeMode: runtimeMode ?? "cloud-headless",
|
|
181
|
+
githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
|
|
182
|
+
expectedGithubLogin: expectedLogin,
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
28
186
|
function loadJson(path) {
|
|
29
187
|
try {
|
|
30
188
|
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -498,6 +656,10 @@ export function buildManifest(resolved, options) {
|
|
|
498
656
|
if (options.githubAuthMode !== undefined && options.githubAuthMode !== null) {
|
|
499
657
|
entry.github_auth_mode = options.githubAuthMode;
|
|
500
658
|
}
|
|
659
|
+
if (options.expectedGithubLogin !== undefined && options.expectedGithubLogin !== null) {
|
|
660
|
+
assertNoCredentialValue(options.expectedGithubLogin, "expected_github_login");
|
|
661
|
+
entry.expected_github_login = options.expectedGithubLogin;
|
|
662
|
+
}
|
|
501
663
|
manifest.push(entry);
|
|
502
664
|
}
|
|
503
665
|
return manifest;
|
|
@@ -692,6 +854,33 @@ export function swarmLaunch(args) {
|
|
|
692
854
|
stderr: `${occupancy.message}\n`,
|
|
693
855
|
};
|
|
694
856
|
}
|
|
857
|
+
const launchEnviron = args.environ ?? process.env;
|
|
858
|
+
let expectedGithubLogin = null;
|
|
859
|
+
// Only the injected-token launch path auto-validates (and fail-closes on a
|
|
860
|
+
// missing token). An ambient GH_TOKEN on a host-gh probe is often the
|
|
861
|
+
// maintainer workaround, not a worker credential; local-hybrid injection is
|
|
862
|
+
// the explicit prepareWorkerCredentialInjection call.
|
|
863
|
+
if (githubAuthMode === GITHUB_AUTH_MODE_INJECTED_TOKEN) {
|
|
864
|
+
const injection = prepareWorkerCredentialInjection({
|
|
865
|
+
environ: launchEnviron,
|
|
866
|
+
githubAuthMode,
|
|
867
|
+
runtimeMode,
|
|
868
|
+
dispatchPath: "grok-build",
|
|
869
|
+
runGh: args.runGh,
|
|
870
|
+
expectedPrincipal: args.expectedPrincipal,
|
|
871
|
+
cwd: projectRoot,
|
|
872
|
+
});
|
|
873
|
+
if (!injection.ok) {
|
|
874
|
+
return {
|
|
875
|
+
exitCode: EXIT_GATE_FAILED,
|
|
876
|
+
stdout: "",
|
|
877
|
+
stderr: `${injection.detail}\n${injection.remedy}\n`,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
if (injection.injected) {
|
|
881
|
+
expectedGithubLogin = injection.expectedLogin;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
695
884
|
const manifest = buildManifest(ordered, {
|
|
696
885
|
projectRoot,
|
|
697
886
|
group: args.group ?? null,
|
|
@@ -708,6 +897,7 @@ export function swarmLaunch(args) {
|
|
|
708
897
|
modelSource,
|
|
709
898
|
runtimeMode,
|
|
710
899
|
githubAuthMode,
|
|
900
|
+
expectedGithubLogin,
|
|
711
901
|
occupancySessionId: occupancy.sessionId,
|
|
712
902
|
});
|
|
713
903
|
const rendered = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
@@ -38,6 +38,10 @@ export declare function reset(issueNumber: number, repo: string, deps: TriageAct
|
|
|
38
38
|
actor?: string | null;
|
|
39
39
|
projectRoot?: string;
|
|
40
40
|
}): string;
|
|
41
|
+
/** Success line for ``triage:accept``, plus the optional design-critique affordance (#3708). */
|
|
42
|
+
export declare function formatAcceptOutput(issueNumber: number, repo: string, decisionId: string, options?: {
|
|
43
|
+
autoPromote?: boolean;
|
|
44
|
+
}): string;
|
|
41
45
|
/** Format a decision entry for CLI ``status`` / ``history`` output. */
|
|
42
46
|
export declare function formatDecision(entry: AuditEntry | null): string;
|
|
43
47
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -407,6 +407,12 @@ export function reset(issueNumber, repo, deps, options = {}) {
|
|
|
407
407
|
});
|
|
408
408
|
return deps.candidatesLog.append(entry, { path: logPath });
|
|
409
409
|
}
|
|
410
|
+
/** Success line for ``triage:accept``, plus the optional design-critique affordance (#3708). */
|
|
411
|
+
export function formatAcceptOutput(issueNumber, repo, decisionId, options = {}) {
|
|
412
|
+
const promoteNote = options.autoPromote === true ? " + auto-promote" : "";
|
|
413
|
+
return (`accept #${issueNumber} (${repo}) -> ${decisionId}${promoteNote}\n` +
|
|
414
|
+
"optional: deft-directive-design-critique (declining writes nothing)");
|
|
415
|
+
}
|
|
410
416
|
/** Format a decision entry for CLI ``status`` / ``history`` output. */
|
|
411
417
|
export function formatDecision(entry) {
|
|
412
418
|
if (entry === null) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type EvaluateOptions, type EvaluateResult } from "./types.js";
|
|
2
|
+
export declare class EvaluateError extends Error {
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Stage A parent: WIP census + GitHub REST + worktree fan-out + sink write.
|
|
7
|
+
* Evaluators never receive the WIP census.
|
|
8
|
+
*/
|
|
9
|
+
export declare function evaluateIssues(options: EvaluateOptions): Promise<EvaluateResult>;
|
|
10
|
+
export declare function renderEvaluateText(result: EvaluateResult): string;
|
|
11
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { runText } from "../../swarm/subprocess.js";
|
|
3
|
+
import { defaultGitRunner } from "../../swarm/worktrees.js";
|
|
4
|
+
import { defaultGithubReader, pullsMentioning } from "./github.js";
|
|
5
|
+
import { sha12Of, sinkDir } from "./paths.js";
|
|
6
|
+
import { gcStaleSha12Dirs, writeInvocationSink } from "./sink.js";
|
|
7
|
+
import { DEFAULT_CONCURRENCY, ORIGIN_MASTER, } from "./types.js";
|
|
8
|
+
import { evaluateValidity, joinValidityWithGithub } from "./validity.js";
|
|
9
|
+
import { buildValueAdvice } from "./value.js";
|
|
10
|
+
import { collectWipCensus, wipHitsForIssue } from "./wip-census.js";
|
|
11
|
+
import { addEvaluatorWorktree, removeEvaluatorWorktree } from "./worktrees.js";
|
|
12
|
+
export class EvaluateError extends Error {
|
|
13
|
+
name = "EvaluateError";
|
|
14
|
+
}
|
|
15
|
+
function resolveOriginSha(projectRoot, git) {
|
|
16
|
+
const proc = git(["rev-parse", ORIGIN_MASTER], projectRoot);
|
|
17
|
+
if (proc.returncode !== 0) {
|
|
18
|
+
throw new EvaluateError(`could not resolve ${ORIGIN_MASTER}: ${proc.stderr.trim() || "<no stderr>"}`);
|
|
19
|
+
}
|
|
20
|
+
return proc.stdout.trim();
|
|
21
|
+
}
|
|
22
|
+
function defaultSessionStart(worktreePath) {
|
|
23
|
+
const proc = runText(["deft", "session:start", "--read-only"], { cwd: worktreePath });
|
|
24
|
+
if (proc.returncode !== 0) {
|
|
25
|
+
throw new EvaluateError(`session:start --read-only failed in ${worktreePath}: ${proc.stderr.trim() || "<no stderr>"}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function mapPool(items, limit, fn) {
|
|
29
|
+
const results = new Array(items.length);
|
|
30
|
+
let next = 0;
|
|
31
|
+
const worker = async () => {
|
|
32
|
+
while (true) {
|
|
33
|
+
const index = next;
|
|
34
|
+
next += 1;
|
|
35
|
+
if (index >= items.length) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
results[index] = await fn(items[index], index);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const n = Math.max(1, Math.min(limit, Math.max(items.length, 1)));
|
|
42
|
+
await Promise.all(Array.from({ length: n }, () => worker()));
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
function collectGithubForRepo(repo, issues, reader) {
|
|
46
|
+
const views = issues.map((n) => reader.viewIssue(repo, n));
|
|
47
|
+
const openPulls = reader.listOpenPulls(repo);
|
|
48
|
+
reader.listOpenIssues(repo);
|
|
49
|
+
return { issues: views, openPulls };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stage A parent: WIP census + GitHub REST + worktree fan-out + sink write.
|
|
53
|
+
* Evaluators never receive the WIP census.
|
|
54
|
+
*/
|
|
55
|
+
export async function evaluateIssues(options) {
|
|
56
|
+
const issues = [...new Set(options.issues)].filter((n) => Number.isInteger(n) && n > 0);
|
|
57
|
+
if (issues.length === 0) {
|
|
58
|
+
throw new EvaluateError("triage:evaluate requires one or more issue numbers");
|
|
59
|
+
}
|
|
60
|
+
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
61
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
62
|
+
throw new EvaluateError("--concurrency must be a positive integer");
|
|
63
|
+
}
|
|
64
|
+
const git = options.git ?? defaultGitRunner;
|
|
65
|
+
const sessionStart = options.sessionStart ?? defaultSessionStart;
|
|
66
|
+
const github = options.github ?? defaultGithubReader();
|
|
67
|
+
const originSha = resolveOriginSha(options.projectRoot, git);
|
|
68
|
+
const sha12 = sha12Of(originSha);
|
|
69
|
+
const invocationId = options.invocationId ?? randomUUID();
|
|
70
|
+
const wip = collectWipCensus(options.projectRoot, issues);
|
|
71
|
+
const githubViews = collectGithubForRepo(options.repo, issues, github);
|
|
72
|
+
const viewByIssue = new Map(githubViews.issues.map((snap) => [snap.number, snap]));
|
|
73
|
+
const runOne = async (issue) => {
|
|
74
|
+
let worktreePath = null;
|
|
75
|
+
let teardownError = null;
|
|
76
|
+
const snap = viewByIssue.get(issue) ?? null;
|
|
77
|
+
const pulls = pullsMentioning(githubViews.openPulls, issue);
|
|
78
|
+
let verdict;
|
|
79
|
+
try {
|
|
80
|
+
worktreePath = addEvaluatorWorktree(options.projectRoot, issue, invocationId, originSha, git);
|
|
81
|
+
sessionStart(worktreePath);
|
|
82
|
+
const validity = evaluateValidity(worktreePath, issue);
|
|
83
|
+
const joined = joinValidityWithGithub(validity, snap?.state ?? null);
|
|
84
|
+
const duplicates = snap?.duplicateOf !== null && snap?.duplicateOf !== undefined ? [snap.duplicateOf] : [];
|
|
85
|
+
verdict = {
|
|
86
|
+
issue,
|
|
87
|
+
sha12,
|
|
88
|
+
invocationId,
|
|
89
|
+
validity: joined,
|
|
90
|
+
wip: wipHitsForIssue(wip, issue),
|
|
91
|
+
github: snap,
|
|
92
|
+
openPulls: pulls,
|
|
93
|
+
duplicates,
|
|
94
|
+
value: buildValueAdvice(snap),
|
|
95
|
+
error: null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
100
|
+
verdict = {
|
|
101
|
+
issue,
|
|
102
|
+
sha12,
|
|
103
|
+
invocationId,
|
|
104
|
+
validity: null,
|
|
105
|
+
wip: wipHitsForIssue(wip, issue),
|
|
106
|
+
github: snap,
|
|
107
|
+
openPulls: pulls,
|
|
108
|
+
duplicates: [],
|
|
109
|
+
value: buildValueAdvice(snap),
|
|
110
|
+
error: message,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
if (worktreePath !== null) {
|
|
115
|
+
try {
|
|
116
|
+
removeEvaluatorWorktree(options.projectRoot, worktreePath, git);
|
|
117
|
+
}
|
|
118
|
+
catch (teardownErr) {
|
|
119
|
+
teardownError = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (teardownError !== null) {
|
|
124
|
+
verdict = {
|
|
125
|
+
...verdict,
|
|
126
|
+
error: verdict.error !== null ? `${verdict.error}; teardown: ${teardownError}` : teardownError,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return verdict;
|
|
130
|
+
};
|
|
131
|
+
try {
|
|
132
|
+
const verdicts = await mapPool(issues, concurrency, (issue) => runOne(issue));
|
|
133
|
+
const result = {
|
|
134
|
+
sha12,
|
|
135
|
+
invocationId,
|
|
136
|
+
originSha,
|
|
137
|
+
sinkDir: sinkDir(options.projectRoot, sha12, invocationId),
|
|
138
|
+
concurrency,
|
|
139
|
+
verdicts,
|
|
140
|
+
};
|
|
141
|
+
writeInvocationSink(options.projectRoot, result);
|
|
142
|
+
gcStaleSha12Dirs(options.projectRoot, sha12);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
gcStaleSha12Dirs(options.projectRoot, sha12);
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function renderEvaluateText(result) {
|
|
151
|
+
const lines = [
|
|
152
|
+
`triage:evaluate sha12=${result.sha12} invocation=${result.invocationId} concurrency=${result.concurrency}`,
|
|
153
|
+
`sink: ${result.sinkDir}`,
|
|
154
|
+
];
|
|
155
|
+
for (const verdict of result.verdicts) {
|
|
156
|
+
const state = verdict.validity?.state ?? "error";
|
|
157
|
+
const evidence = verdict.validity?.evidence ?? verdict.error ?? "";
|
|
158
|
+
const wip = verdict.wip.length > 0 ? ` wip=${verdict.wip.length}` : " wip=0";
|
|
159
|
+
lines.push(`#${verdict.issue} validity=${state}${wip} critique-recommend: ${verdict.value["critique-recommend"]}`);
|
|
160
|
+
if (evidence.length > 0) {
|
|
161
|
+
lines.push(` ${evidence}`);
|
|
162
|
+
}
|
|
163
|
+
if (verdict.error !== null) {
|
|
164
|
+
lines.push(` error: ${verdict.error}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return `${lines.join("\n")}\n`;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RunGhApiFn } from "../../scm/gh-rest.js";
|
|
2
|
+
import type { GithubCensus, GithubIssueSnapshot, GithubPullSnapshot } from "./types.js";
|
|
3
|
+
export declare function snapshotFromIssuePayload(raw: Record<string, unknown>): GithubIssueSnapshot;
|
|
4
|
+
export declare function snapshotFromPullPayload(raw: Record<string, unknown>): GithubPullSnapshot;
|
|
5
|
+
export interface GithubSeams {
|
|
6
|
+
readonly runGhApiFn?: RunGhApiFn;
|
|
7
|
+
}
|
|
8
|
+
/** GET-only GitHub census. Parent-owned. Never POST/PATCH/PUT/DELETE. */
|
|
9
|
+
export declare function collectGithubCensus(repo: string, issues: readonly number[], seams?: GithubSeams): GithubCensus;
|
|
10
|
+
export declare function defaultGithubReader(seams?: GithubSeams): {
|
|
11
|
+
viewIssue: (repo: string, n: number) => GithubIssueSnapshot;
|
|
12
|
+
listOpenIssues: (repo: string) => GithubIssueSnapshot[];
|
|
13
|
+
listOpenPulls: (repo: string) => GithubPullSnapshot[];
|
|
14
|
+
};
|
|
15
|
+
export declare function pullsMentioning(pulls: readonly GithubPullSnapshot[], issue: number): GithubPullSnapshot[];
|
|
16
|
+
//# sourceMappingURL=github.d.ts.map
|