@deftai/directive-core 0.94.0 → 0.96.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/authz/classify.js +485 -0
- package/dist/cache/archive.d.ts +134 -0
- package/dist/cache/archive.js +630 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/cache/main.js +298 -1
- package/dist/content-contracts/skills/helpers.d.ts +1 -1
- package/dist/content-contracts/skills/helpers.js +1 -0
- package/dist/doctor/main.js +41 -9
- package/dist/doctor/types.d.ts +2 -2
- package/dist/freshness/bind.d.ts +67 -0
- package/dist/freshness/bind.js +201 -0
- package/dist/freshness/cli.d.ts +30 -0
- package/dist/freshness/cli.js +180 -0
- package/dist/freshness/compare.d.ts +14 -0
- package/dist/freshness/compare.js +134 -0
- package/dist/freshness/generation.d.ts +44 -0
- package/dist/freshness/generation.js +172 -0
- package/dist/freshness/index.d.ts +13 -0
- package/dist/freshness/index.js +13 -0
- package/dist/freshness/report.d.ts +43 -0
- package/dist/freshness/report.js +139 -0
- package/dist/freshness/types.d.ts +70 -0
- package/dist/freshness/types.js +30 -0
- package/dist/handoff-evidence/index.d.ts +8 -0
- package/dist/handoff-evidence/index.js +7 -0
- package/dist/handoff-evidence/validate.d.ts +105 -0
- package/dist/handoff-evidence/validate.js +423 -0
- package/dist/hooks/dispatcher.d.ts +3 -0
- package/dist/hooks/dispatcher.js +12 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +5 -2
- package/dist/init-deposit/hygiene.d.ts +1 -1
- package/dist/init-deposit/hygiene.js +16 -4
- package/dist/init-deposit/init-deposit.d.ts +7 -1
- package/dist/init-deposit/init-deposit.js +24 -4
- package/dist/init-deposit/refresh.d.ts +9 -1
- package/dist/init-deposit/refresh.js +48 -5
- package/dist/init-deposit/scaffold.js +6 -5
- package/dist/parent-turn-shape/evaluate.d.ts +84 -0
- package/dist/parent-turn-shape/evaluate.js +353 -0
- package/dist/parent-turn-shape/index.d.ts +8 -0
- package/dist/parent-turn-shape/index.js +8 -0
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +35 -0
- package/dist/release-e2e/greenfield-python-free-smoke.js +7 -3
- package/dist/review-monitor/constants.js +3 -2
- package/dist/review-monitor/tier-detection.d.ts +7 -3
- package/dist/review-monitor/tier-detection.js +18 -1
- package/dist/review-monitor/verify.js +18 -0
- package/dist/scope/index.d.ts +2 -0
- package/dist/scope/index.js +2 -0
- package/dist/scope/main.d.ts +10 -0
- package/dist/scope/main.js +109 -24
- package/dist/scope/promote-from-issue.d.ts +49 -0
- package/dist/scope/promote-from-issue.js +367 -0
- package/dist/scope/promote-path.d.ts +39 -0
- package/dist/scope/promote-path.js +105 -0
- package/dist/session/ritual-entrypoint.d.ts +6 -2
- package/dist/session/ritual-entrypoint.js +15 -2
- package/dist/session/session-ready.js +31 -14
- package/dist/session/session-start.d.ts +2 -1
- package/dist/session/session-start.js +66 -3
- package/dist/session/verify-session-ritual.d.ts +4 -2
- package/dist/session/verify-session-ritual.js +11 -11
- package/dist/slash/product-set.js +4 -1
- package/dist/swarm/routing.d.ts +4 -2
- package/dist/swarm/routing.js +26 -4
- package/dist/swarm/verify-review-clean.d.ts +4 -3
- package/dist/swarm/verify-review-clean.js +28 -73
- package/dist/triage/actions/index.js +62 -2
- package/dist/triage/actions/types.d.ts +8 -1
- package/dist/triage/author-filter.d.ts +51 -0
- package/dist/triage/author-filter.js +152 -0
- package/dist/triage/classify/index.d.ts +9 -0
- package/dist/triage/classify/index.js +34 -2
- package/dist/triage/classify/label-mirror.d.ts +206 -0
- package/dist/triage/classify/label-mirror.js +914 -0
- package/dist/triage/help/registry-data.d.ts +49 -38
- package/dist/triage/help/registry-data.js +134 -39
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/render.d.ts +2 -0
- package/dist/triage/queue/render.js +6 -0
- package/dist/triage/queue/show.d.ts +1 -1
- package/dist/triage/queue/show.js +4 -2
- package/dist/vbrief-validate/project-definition.js +1 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +42 -0
- package/dist/verify-env/agent-hook-readiness.js +150 -0
- package/dist/verify-env/agent-hooks-live-probe.d.ts +16 -4
- package/dist/verify-env/agent-hooks-live-probe.js +120 -107
- package/dist/verify-env/agent-hooks.js +11 -3
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/package.json +11 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { runningInsideDeftRepo } from "../doctor/paths.js";
|
|
3
3
|
import { emitSessionEvalReadback } from "../eval/readback.js";
|
|
4
|
+
import { bindSessionGeneration } from "../freshness/bind.js";
|
|
5
|
+
import { readLiveGeneration } from "../freshness/generation.js";
|
|
4
6
|
import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
|
|
5
7
|
import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
|
|
6
8
|
import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
|
|
@@ -20,6 +22,7 @@ import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js
|
|
|
20
22
|
import { emitSessionStartProcessCost } from "./process-cost.js";
|
|
21
23
|
import { probeSessionReleaseAvailability, } from "./release-availability.js";
|
|
22
24
|
import { newRitualStatePayload, readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
25
|
+
import { timestampIso } from "./time.js";
|
|
23
26
|
export const SESSION_POSTURES = ["read-only", "mutation"];
|
|
24
27
|
export const READ_ONLY_POSTURE = "read-only";
|
|
25
28
|
export const MUTATION_POSTURE = "mutation";
|
|
@@ -30,7 +33,7 @@ export const SESSION_CEREMONY_TIERS = ["cold", "rearm"];
|
|
|
30
33
|
export const COLD_CEREMONY_TIER = "cold";
|
|
31
34
|
export const REARM_CEREMONY_TIER = "rearm";
|
|
32
35
|
export const QUICK_STEPS = ["alignment", "branch_policy", "triage_welcome"];
|
|
33
|
-
export const GATED_STEPS = ["doctor", "cache_fresh"];
|
|
36
|
+
export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
|
|
34
37
|
/** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
|
|
35
38
|
export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
|
|
36
39
|
/** Human-readable skip notice when optional network is off the hot path (#2991). */
|
|
@@ -148,6 +151,10 @@ export function parseDeferrals(rawValues) {
|
|
|
148
151
|
const name = raw.slice(0, eq);
|
|
149
152
|
const reason = raw.slice(eq + 1);
|
|
150
153
|
const stepName = normaliseStepName(name.trim());
|
|
154
|
+
if (stepName === "agent_hooks") {
|
|
155
|
+
errors.push('ritual step "agent_hooks" is not deferrable');
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
151
158
|
if (!allowed.has(stepName)) {
|
|
152
159
|
errors.push(`unknown ritual step ${JSON.stringify(name)}; expected one of ${JSON.stringify([...allowed].sort())}`);
|
|
153
160
|
continue;
|
|
@@ -454,10 +461,11 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
454
461
|
};
|
|
455
462
|
const gatedSteps = restampSteps(eligibility.state.gatedSteps, instant);
|
|
456
463
|
const writeStarted = performance.now();
|
|
464
|
+
const rearmSessionId = (options.newSessionId ?? randomUUID)();
|
|
457
465
|
// Fresh payload (no rearm_needed / compact_resume_at) clears compact markers (#2992).
|
|
458
466
|
const writePayload = {
|
|
459
467
|
...newRitualStatePayload({
|
|
460
|
-
sessionId:
|
|
468
|
+
sessionId: rearmSessionId,
|
|
461
469
|
gitHead: eligibility.currentHead,
|
|
462
470
|
worktreePath: eligibility.currentWorktree,
|
|
463
471
|
startedAt: instant,
|
|
@@ -502,6 +510,32 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
502
510
|
.filter(([, step]) => !step.ok && !step.deferred_reason)
|
|
503
511
|
.map(([name]) => name);
|
|
504
512
|
const code = failed.length > 0 ? 1 : 0;
|
|
513
|
+
// #3117: bind live deposit generation into session context on re-arm (host-agnostic).
|
|
514
|
+
let freshnessBind = null;
|
|
515
|
+
try {
|
|
516
|
+
const bound = bindSessionGeneration(projectRoot, {
|
|
517
|
+
sessionId: rearmSessionId,
|
|
518
|
+
nowIso: timestampIso(instant),
|
|
519
|
+
payloadLoaded: true,
|
|
520
|
+
});
|
|
521
|
+
freshnessBind = {
|
|
522
|
+
bound_generation: bound.bound.boundGeneration,
|
|
523
|
+
live_generation: bound.live.generation,
|
|
524
|
+
content_version: bound.live.contentVersion,
|
|
525
|
+
path: bound.path,
|
|
526
|
+
};
|
|
527
|
+
lines.push(`[deft freshness] bound generation ${bound.bound.boundGeneration} ` +
|
|
528
|
+
`(live deposit v${bound.live.contentVersion})`);
|
|
529
|
+
lines.push(`[deft freshness] set DEFT_SESSION_ID=${rearmSessionId} for bare freshness:report/bind ` +
|
|
530
|
+
`(or pass --session-id; required for multi-agent trusted readiness)`);
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
const live = readLiveGeneration(projectRoot);
|
|
534
|
+
if (live !== null) {
|
|
535
|
+
lines.push(`[deft freshness] live generation ${live.generation} present; session bind deferred ` +
|
|
536
|
+
`(run \`deft freshness:bind\` after loading payload surfaces)`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
505
539
|
// #2994: local process-cost event (best-effort; never blocks ceremony).
|
|
506
540
|
emitSessionStartProcessCost({
|
|
507
541
|
ceremonyTier: REARM_CEREMONY_TIER,
|
|
@@ -519,6 +553,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
519
553
|
ceremony_tier: REARM_CEREMONY_TIER,
|
|
520
554
|
rearm_eligible: true,
|
|
521
555
|
state_path: statePath,
|
|
556
|
+
...(freshnessBind ? { freshness: freshnessBind } : {}),
|
|
522
557
|
quick_steps: quickSteps,
|
|
523
558
|
gated_steps: gatedSteps,
|
|
524
559
|
steps: stepTimings,
|
|
@@ -829,8 +864,9 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
829
864
|
lines.push(consentPrompt.trimEnd());
|
|
830
865
|
}
|
|
831
866
|
const writeStarted = performance.now();
|
|
867
|
+
const coldSessionId = (options.newSessionId ?? randomUUID)();
|
|
832
868
|
const payload = newRitualStatePayload({
|
|
833
|
-
sessionId:
|
|
869
|
+
sessionId: coldSessionId,
|
|
834
870
|
gitHead: gitHeadValue,
|
|
835
871
|
worktreePath: worktreePath(projectRoot, runGit),
|
|
836
872
|
startedAt: instant,
|
|
@@ -860,11 +896,38 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
860
896
|
.map(([name]) => name);
|
|
861
897
|
const code = failed.length > 0 ? 1 : 0;
|
|
862
898
|
const totalMs = elapsedMs(overallStarted);
|
|
899
|
+
// #3117: bind live deposit generation when payload surfaces load (cold path).
|
|
900
|
+
let freshnessBind = null;
|
|
901
|
+
try {
|
|
902
|
+
const bound = bindSessionGeneration(projectRoot, {
|
|
903
|
+
sessionId: coldSessionId,
|
|
904
|
+
nowIso: timestampIso(instant),
|
|
905
|
+
payloadLoaded: true,
|
|
906
|
+
});
|
|
907
|
+
freshnessBind = {
|
|
908
|
+
bound_generation: bound.bound.boundGeneration,
|
|
909
|
+
live_generation: bound.live.generation,
|
|
910
|
+
content_version: bound.live.contentVersion,
|
|
911
|
+
path: bound.path,
|
|
912
|
+
};
|
|
913
|
+
lines.push(`[deft freshness] bound generation ${bound.bound.boundGeneration} ` +
|
|
914
|
+
`(live deposit v${bound.live.contentVersion})`);
|
|
915
|
+
lines.push(`[deft freshness] set DEFT_SESSION_ID=${coldSessionId} for bare freshness:report/bind ` +
|
|
916
|
+
`(or pass --session-id; required for multi-agent trusted readiness)`);
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
const live = readLiveGeneration(projectRoot);
|
|
920
|
+
if (live !== null) {
|
|
921
|
+
lines.push(`[deft freshness] live generation ${live.generation} present; session bind deferred ` +
|
|
922
|
+
`(run \`deft freshness:bind\` after loading payload surfaces)`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
863
925
|
const resultPayload = {
|
|
864
926
|
ready: code === 0,
|
|
865
927
|
exit_code: code,
|
|
866
928
|
ceremony_tier: COLD_CEREMONY_TIER,
|
|
867
929
|
state_path: statePath,
|
|
930
|
+
...(freshnessBind ? { freshness: freshnessBind } : {}),
|
|
868
931
|
quick_steps: quickSteps,
|
|
869
932
|
gated_steps: gatedSteps,
|
|
870
933
|
steps: stepTimings,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type GitRunner } from "./git.js";
|
|
2
2
|
import { type DirectivePosture } from "./posture.js";
|
|
3
|
-
import { type SessionCeremonyTier } from "./session-start.js";
|
|
3
|
+
import { type GatedStepName, type SessionCeremonyTier } from "./session-start.js";
|
|
4
4
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
5
5
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
6
6
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
7
|
-
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<
|
|
7
|
+
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<GatedStepName, readonly string[]>>;
|
|
8
8
|
export interface VerifyResult {
|
|
9
9
|
readonly code: number;
|
|
10
10
|
readonly message: string;
|
|
@@ -37,6 +37,8 @@ export interface VerifySessionRitualOptions {
|
|
|
37
37
|
readonly posture?: DirectivePosture;
|
|
38
38
|
readonly envPosture?: string | undefined;
|
|
39
39
|
readonly handoffText?: string | null;
|
|
40
|
+
/** Re-run selected gated prerequisites even when their recorded step is green. */
|
|
41
|
+
readonly forceGatedSteps?: readonly GatedStepName[];
|
|
40
42
|
}
|
|
41
43
|
export interface InspectSessionRitualOptions {
|
|
42
44
|
readonly tier?: "quick" | "gated";
|
|
@@ -11,6 +11,7 @@ export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from
|
|
|
11
11
|
export const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
12
12
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
13
13
|
export const GATED_ENTRYPOINT_COMMANDS = {
|
|
14
|
+
agent_hooks: ["verify:hooks-installed", "--scope=agent", "--live"],
|
|
14
15
|
doctor: ["doctor"],
|
|
15
16
|
cache_fresh: ["verify:cache-fresh"],
|
|
16
17
|
};
|
|
@@ -26,16 +27,11 @@ function stepPasses(step) {
|
|
|
26
27
|
}
|
|
27
28
|
function failedStepMessage(tierName, stepName, step) {
|
|
28
29
|
const coldCmd = formatSessionStartRecoveryCommand("cold");
|
|
29
|
-
if (step ===
|
|
30
|
+
if (step === undefined) {
|
|
30
31
|
return (`session ritual ${tierName} step '${stepName}' is missing. ` +
|
|
31
32
|
`Run \`${coldCmd}\` before implementation dispatch.`);
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
step !== null &&
|
|
35
|
-
step.deferred_reason) {
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
const message = typeof step === "object" && step !== null ? step.message : null;
|
|
34
|
+
const message = step.message;
|
|
39
35
|
const suffix = typeof message === "string" && message.length > 0 ? `: ${message}` : "";
|
|
40
36
|
return `session ritual ${tierName} step '${stepName}' failed${suffix}`;
|
|
41
37
|
}
|
|
@@ -53,9 +49,9 @@ export function formatRitualRecoveryInstruction(tier = "cold") {
|
|
|
53
49
|
`(one-shot: session:start + gated ritual + cache recovery as needed).`);
|
|
54
50
|
}
|
|
55
51
|
function runGatedStep(projectRoot, payload, stepName, runner, now) {
|
|
56
|
-
const command = [...
|
|
52
|
+
const command = [...GATED_ENTRYPOINT_COMMANDS[stepName]];
|
|
57
53
|
const { code, stdout, stderr } = runner(command, projectRoot);
|
|
58
|
-
const message = stdout.trim() || stderr.trim() || `${command[0]
|
|
54
|
+
const message = stdout.trim() || stderr.trim() || `${command[0]} exited ${code}`;
|
|
59
55
|
const gated = payload.gated_steps ?? {};
|
|
60
56
|
gated[stepName] = ritualStep({
|
|
61
57
|
ok: code === 0,
|
|
@@ -354,11 +350,15 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
354
350
|
const gated = { ...payload.gated_steps };
|
|
355
351
|
payload.gated_steps = gated;
|
|
356
352
|
const runCmd = options.runner ?? defaultRitualRunner;
|
|
353
|
+
const forced = new Set(options.forceGatedSteps ?? []);
|
|
357
354
|
for (const stepName of GATED_STEPS) {
|
|
358
355
|
const step = gated[stepName];
|
|
359
|
-
if (step?.deferred_reason)
|
|
356
|
+
if (step?.deferred_reason && stepName !== "agent_hooks")
|
|
360
357
|
continue;
|
|
361
|
-
|
|
358
|
+
// Hook readiness is a live mutation prerequisite, not a cacheable doctor
|
|
359
|
+
// result. Re-run it at every gated boundary so registration/shim drift
|
|
360
|
+
// cannot remain hidden behind an earlier green ritual record.
|
|
361
|
+
if (stepName !== "agent_hooks" && stepPasses(step) && !forced.has(stepName))
|
|
362
362
|
continue;
|
|
363
363
|
const writeError = runGatedStep(projectRoot, payload, stepName, runCmd, instant);
|
|
364
364
|
if (writeError !== null) {
|
|
@@ -109,9 +109,12 @@ export const PRODUCT_COMMANDS = Object.freeze([
|
|
|
109
109
|
{
|
|
110
110
|
logicalId: "/deft:checkpoint",
|
|
111
111
|
filenameStem: "deft-checkpoint",
|
|
112
|
+
// description documents the save path; wrappers load the strategy doc (same as continue).
|
|
113
|
+
// ⊗ Point dispatchPath at xbrief/continue.xbrief.json — that file is the checkpoint *output*,
|
|
114
|
+
// not the instruction doc; first invoke fails when it does not exist yet (#3105).
|
|
112
115
|
description: "Save session state to xbrief/continue.xbrief.json",
|
|
113
116
|
dispatchKind: "session",
|
|
114
|
-
dispatchPath: "
|
|
117
|
+
dispatchPath: "resilience/continue-here.md",
|
|
115
118
|
},
|
|
116
119
|
]);
|
|
117
120
|
/**
|
package/dist/swarm/routing.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ 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 / #2875). */
|
|
12
|
+
/** Providers whose per-role model must be decided before sub-agent dispatch (#1739 / #1877 / #2875 / #3134). */
|
|
13
13
|
export declare const ROUTING_GATED_DISPATCH_PROVIDERS: Set<string>;
|
|
14
14
|
export interface RouteDecision {
|
|
15
15
|
model: string | null;
|
|
@@ -55,7 +55,9 @@ export declare function dispatchProviderFromRuntime(runtimeMode: string): string
|
|
|
55
55
|
* Separate from `runtime_mode` (#1557): Cursor sessions may carry
|
|
56
56
|
* `runtime_mode=cloud-headless` for gh-auth purposes but route under provider
|
|
57
57
|
* `cursor` for model selection (#1877). OpenClaw routes under `openclaw` when
|
|
58
|
-
* `sessions_spawn` / OPENCLAW signals are present (#2875).
|
|
58
|
+
* `sessions_spawn` / OPENCLAW signals are present (#2875). Claude Code routes
|
|
59
|
+
* under `claude` when Claude-unique signals are present (#3134) — never via bare
|
|
60
|
+
* Task (that would misclassify as cursor).
|
|
59
61
|
*/
|
|
60
62
|
export declare function resolveDispatchProvider(environ?: NodeJS.ProcessEnv): string;
|
|
61
63
|
/**
|
package/dist/swarm/routing.js
CHANGED
|
@@ -33,8 +33,13 @@ export const ROUTING_MODE_HARNESS_DEFAULT = "harness-default";
|
|
|
33
33
|
export const ROUTING_FILENAME = "routing.local.json";
|
|
34
34
|
/** Providers whose model is harness-bound -- deft cannot pin or verify a slug. */
|
|
35
35
|
export const HARNESS_BOUND_PROVIDERS = new Set(["grok"]);
|
|
36
|
-
/** Providers whose per-role model must be decided before sub-agent dispatch (#1739 / #1877 / #2875). */
|
|
37
|
-
export const ROUTING_GATED_DISPATCH_PROVIDERS = new Set([
|
|
36
|
+
/** Providers whose per-role model must be decided before sub-agent dispatch (#1739 / #1877 / #2875 / #3134). */
|
|
37
|
+
export const ROUTING_GATED_DISPATCH_PROVIDERS = new Set([
|
|
38
|
+
"cursor",
|
|
39
|
+
"grok",
|
|
40
|
+
"openclaw",
|
|
41
|
+
"claude",
|
|
42
|
+
]);
|
|
38
43
|
const TRUTHY_ENV = new Set(["1", "true", "yes", "on"]);
|
|
39
44
|
function envTruthy(environ, name) {
|
|
40
45
|
return TRUTHY_ENV.has((environ[name] ?? "").trim().toLowerCase());
|
|
@@ -153,6 +158,9 @@ export function dispatchProviderFromRuntime(runtimeMode) {
|
|
|
153
158
|
if (normalized.includes("openclaw")) {
|
|
154
159
|
return "openclaw";
|
|
155
160
|
}
|
|
161
|
+
if (normalized.includes("claude")) {
|
|
162
|
+
return "claude";
|
|
163
|
+
}
|
|
156
164
|
if (normalized.includes("grok")) {
|
|
157
165
|
return "grok";
|
|
158
166
|
}
|
|
@@ -166,13 +174,24 @@ export function dispatchProviderFromRuntime(runtimeMode) {
|
|
|
166
174
|
* Separate from `runtime_mode` (#1557): Cursor sessions may carry
|
|
167
175
|
* `runtime_mode=cloud-headless` for gh-auth purposes but route under provider
|
|
168
176
|
* `cursor` for model selection (#1877). OpenClaw routes under `openclaw` when
|
|
169
|
-
* `sessions_spawn` / OPENCLAW signals are present (#2875).
|
|
177
|
+
* `sessions_spawn` / OPENCLAW signals are present (#2875). Claude Code routes
|
|
178
|
+
* under `claude` when Claude-unique signals are present (#3134) — never via bare
|
|
179
|
+
* Task (that would misclassify as cursor).
|
|
170
180
|
*/
|
|
171
181
|
export function resolveDispatchProvider(environ = process.env) {
|
|
172
182
|
if (envTruthy(environ, "CURSOR_COMPOSER") || envTruthy(environ, "CURSOR_AGENT")) {
|
|
173
183
|
return "cursor";
|
|
174
184
|
}
|
|
175
185
|
const runtime = (environ.DEFT_AGENT_RUNTIME ?? "").trim().toLowerCase();
|
|
186
|
+
// Claude Code before OpenClaw/CI so CLAUDECODE / DEFT_PROBE_CLAUDE_CODE win (#3134).
|
|
187
|
+
if (envTruthy(environ, "DEFT_PROBE_CLAUDE_CODE") ||
|
|
188
|
+
envTruthy(environ, "DEFT_HAS_CLAUDE_AGENT") ||
|
|
189
|
+
envTruthy(environ, "CLAUDECODE") ||
|
|
190
|
+
envTruthy(environ, "CLAUDE_CODE") ||
|
|
191
|
+
runtime === "claude-code" ||
|
|
192
|
+
runtime === "claude") {
|
|
193
|
+
return "claude";
|
|
194
|
+
}
|
|
176
195
|
if (envTruthy(environ, "OPENCLAW") ||
|
|
177
196
|
envTruthy(environ, "DEFT_HAS_SESSIONS_SPAWN") ||
|
|
178
197
|
envTruthy(environ, "DEFT_PROBE_SESSIONS_SPAWN") ||
|
|
@@ -191,7 +210,10 @@ export function resolveDispatchProvider(environ = process.env) {
|
|
|
191
210
|
!envTruthy(environ, "CURSOR_COMPOSER") &&
|
|
192
211
|
!envTruthy(environ, "CURSOR_AGENT") &&
|
|
193
212
|
!envTruthy(environ, "OPENCLAW") &&
|
|
194
|
-
!envTruthy(environ, "DEFT_HAS_SESSIONS_SPAWN")
|
|
213
|
+
!envTruthy(environ, "DEFT_HAS_SESSIONS_SPAWN") &&
|
|
214
|
+
!envTruthy(environ, "CLAUDECODE") &&
|
|
215
|
+
!envTruthy(environ, "CLAUDE_CODE") &&
|
|
216
|
+
!envTruthy(environ, "DEFT_PROBE_CLAUDE_CODE"))) {
|
|
195
217
|
return "cloud-headless";
|
|
196
218
|
}
|
|
197
219
|
return "unknown";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { SlizardGateOptions } from "../pr-merge-readiness/slizard-gate.js";
|
|
1
|
+
import { type ComputeGateOptions } from "../pr-merge-readiness/compute.js";
|
|
3
2
|
import type { RunGhFn } from "../pr-merge-readiness/types.js";
|
|
4
|
-
type ReviewGateOptions =
|
|
3
|
+
type ReviewGateOptions = ComputeGateOptions;
|
|
5
4
|
import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
|
|
6
5
|
export interface CohortResolutionError {
|
|
7
6
|
vbrief_path: string;
|
|
@@ -15,6 +14,8 @@ export interface CohortPRResult {
|
|
|
15
14
|
clean: boolean;
|
|
16
15
|
ci_summary: Record<string, unknown> | null;
|
|
17
16
|
slizard_summary: Record<string, unknown> | null;
|
|
17
|
+
via?: string;
|
|
18
|
+
partial_data?: Record<string, unknown>;
|
|
18
19
|
}
|
|
19
20
|
export interface CohortResult {
|
|
20
21
|
repo: string | null;
|
|
@@ -1,10 +1,9 @@
|
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { evaluateSlizardGate, isSlizardCheck } from "../pr-merge-readiness/slizard-gate.js";
|
|
4
|
+
import { computeGateResult, isMergeReady, } from "../pr-merge-readiness/compute.js";
|
|
5
|
+
import { VIA_ERROR, VIA_FALLBACK2 } from "../pr-merge-readiness/constants.js";
|
|
6
|
+
import { defaultRunGh } from "../pr-merge-readiness/gh.js";
|
|
8
7
|
import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
|
|
9
8
|
function extractPrFromUri(uri) {
|
|
10
9
|
const pullIdx = uri.indexOf("/pull/");
|
|
@@ -104,83 +103,29 @@ export function resolveCohortFromVbriefs(vbriefGlobs) {
|
|
|
104
103
|
return { prNumbers: seenPrs, failures };
|
|
105
104
|
}
|
|
106
105
|
export function evaluatePr(prNumber, repo, runGh = defaultRunGh, options = {}) {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
106
|
+
const result = computeGateResult(prNumber, repo, runGh, options);
|
|
107
|
+
if (result.via === VIA_ERROR) {
|
|
109
108
|
return null;
|
|
110
109
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
const verdict = parseGreptileBody(body);
|
|
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
|
-
}
|
|
110
|
+
const ciSummary = asRecord(result.partialData.ci);
|
|
111
|
+
const slizardSummary = asRecord(result.partialData.slizard);
|
|
174
112
|
return {
|
|
175
113
|
pr_number: prNumber,
|
|
176
|
-
head_sha: headSha,
|
|
177
|
-
verdict: { ...verdict },
|
|
178
|
-
failures: [...failures],
|
|
179
|
-
clean:
|
|
114
|
+
head_sha: result.headSha,
|
|
115
|
+
verdict: { ...result.verdict },
|
|
116
|
+
failures: [...result.failures],
|
|
117
|
+
clean: result.via !== VIA_FALLBACK2 && isMergeReady(result.failures),
|
|
180
118
|
ci_summary: ciSummary,
|
|
181
119
|
slizard_summary: slizardSummary,
|
|
120
|
+
via: result.via,
|
|
121
|
+
partial_data: { ...result.partialData },
|
|
182
122
|
};
|
|
183
123
|
}
|
|
124
|
+
function asRecord(value) {
|
|
125
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
126
|
+
? { ...value }
|
|
127
|
+
: null;
|
|
128
|
+
}
|
|
184
129
|
export function cohortResultToDict(cohort) {
|
|
185
130
|
return {
|
|
186
131
|
repo: cohort.repo,
|
|
@@ -194,6 +139,8 @@ export function cohortResultToDict(cohort) {
|
|
|
194
139
|
failures: r.failures,
|
|
195
140
|
ci_summary: r.ci_summary,
|
|
196
141
|
slizard_summary: r.slizard_summary,
|
|
142
|
+
via: r.via ?? null,
|
|
143
|
+
partial_data: r.partial_data ?? {},
|
|
197
144
|
})),
|
|
198
145
|
resolution_errors: cohort.resolution_errors,
|
|
199
146
|
};
|
|
@@ -216,6 +163,7 @@ export function renderReviewCleanText(cohort) {
|
|
|
216
163
|
lines.push("");
|
|
217
164
|
lines.push(` PR #${r.pr_number} -- ${status}`);
|
|
218
165
|
lines.push(` HEAD SHA: ${r.head_sha ?? "<unknown>"}`);
|
|
166
|
+
lines.push(` Gate via: ${r.via ?? "<unknown>"}`);
|
|
219
167
|
lines.push(` Greptile reviewed: ${String(v.lastReviewedSha ?? "<not parsed>")}`);
|
|
220
168
|
const conf = v.confidence;
|
|
221
169
|
lines.push(` Confidence: ${conf === null || conf === undefined ? "<not parsed>" : String(conf)}/5`);
|
|
@@ -228,6 +176,13 @@ export function renderReviewCleanText(cohort) {
|
|
|
228
176
|
if (r.slizard_summary && typeof r.slizard_summary.summary_line === "string") {
|
|
229
177
|
lines.push(` ${r.slizard_summary.summary_line}`);
|
|
230
178
|
}
|
|
179
|
+
const override = asRecord(r.partial_data?.verdict_override);
|
|
180
|
+
if (override !== null) {
|
|
181
|
+
lines.push(` Verdict override: ${String(override.reason ?? "<unknown>")}`);
|
|
182
|
+
if (typeof override.basis === "string") {
|
|
183
|
+
lines.push(` Override basis: ${override.basis}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
231
186
|
r.failures.forEach((fail, index) => {
|
|
232
187
|
lines.push(` [${index + 1}] ${fail}`);
|
|
233
188
|
});
|
|
@@ -4,6 +4,8 @@ import { cacheGet } from "../../cache/operations.js";
|
|
|
4
4
|
import { ingestSingleForAccept as ingestSingleForAcceptTs } from "../../intake/issue-ingest.js";
|
|
5
5
|
import { call } from "../../scm/call.js";
|
|
6
6
|
import { ScmStubError } from "../../scm/errors.js";
|
|
7
|
+
import { findLifecycleArtifactsForIssue, findProposedArtifactsForIssue, promoteFromIssue, } from "../../scope/promote-from-issue.js";
|
|
8
|
+
import { promotePath } from "../../scope/promote-path.js";
|
|
7
9
|
import { createCandidatesLog, findByIssue, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
8
10
|
import { TriageError, UpstreamCloseError } from "./errors.js";
|
|
9
11
|
import { parseResumeOn } from "./resume-on.js";
|
|
@@ -62,7 +64,8 @@ function defaultIssueIngest() {
|
|
|
62
64
|
// Delegate to the native TS intake path (#2350). The legacy Python
|
|
63
65
|
// `scripts/issue_ingest.py` shell-out was orphaned when #1933 removed the
|
|
64
66
|
// Python surface, leaving `triage:accept` raising ModuleNotFoundError.
|
|
65
|
-
ingestSingleForAcceptTs(issueNumber, repo, { projectRoot });
|
|
67
|
+
const [, path] = ingestSingleForAcceptTs(issueNumber, repo, { projectRoot });
|
|
68
|
+
return path;
|
|
66
69
|
},
|
|
67
70
|
};
|
|
68
71
|
}
|
|
@@ -207,24 +210,81 @@ function ensureRejectedLabelApplied(deps, issueNumber, repo) {
|
|
|
207
210
|
writeErr(`triage_actions: reject #${issueNumber} (${repo}) closed successfully but the '${REJECTED_LABEL}' label is missing and auto-create/re-add failed: ${healExc instanceof Error ? healExc.message : String(healExc)}`);
|
|
208
211
|
}
|
|
209
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* After a successful accept (or idempotent re-accept), promote proposed → pending (#1136).
|
|
215
|
+
* Surfaces WIP-cap refusals with the same messaging as scope:promote.
|
|
216
|
+
*/
|
|
217
|
+
function runAutoPromote(issueNumber, repo, projectRoot, decisionId, ingestedPath, force) {
|
|
218
|
+
// Already pending for this issue → idempotent success (repeat accept --auto-promote).
|
|
219
|
+
const pending = findLifecycleArtifactsForIssue(projectRoot, issueNumber, {
|
|
220
|
+
folder: "pending",
|
|
221
|
+
repo,
|
|
222
|
+
});
|
|
223
|
+
if (pending.length >= 1) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let path = typeof ingestedPath === "string" && ingestedPath.length > 0 ? ingestedPath : null;
|
|
227
|
+
if (path === null) {
|
|
228
|
+
const matches = findProposedArtifactsForIssue(projectRoot, issueNumber, repo);
|
|
229
|
+
if (matches.length === 1) {
|
|
230
|
+
path = matches[0] ?? null;
|
|
231
|
+
}
|
|
232
|
+
else if (matches.length > 1) {
|
|
233
|
+
throw new TriageError(`accept #${issueNumber} (${repo}): --auto-promote found multiple proposed artifacts:\n` +
|
|
234
|
+
matches.map((p) => ` - ${p}`).join("\n") +
|
|
235
|
+
`\nPromote one with: task scope:promote -- <path>`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (path === null) {
|
|
239
|
+
// Fall back to full from-issue resolution (may still fail on missing artifact).
|
|
240
|
+
const fromIssue = promoteFromIssue({
|
|
241
|
+
issueNumber,
|
|
242
|
+
repo,
|
|
243
|
+
projectRoot,
|
|
244
|
+
force: force === true,
|
|
245
|
+
});
|
|
246
|
+
if (!fromIssue.ok) {
|
|
247
|
+
throw new TriageError(`accept #${issueNumber} (${repo}): accept recorded but --auto-promote failed: ${fromIssue.message}`);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const result = promotePath(path, {
|
|
252
|
+
projectRoot,
|
|
253
|
+
force: force === true,
|
|
254
|
+
fromIssue: issueNumber,
|
|
255
|
+
cacheDecisionId: decisionId,
|
|
256
|
+
cacheStateAtPromote: "accept",
|
|
257
|
+
requireAudit: true,
|
|
258
|
+
});
|
|
259
|
+
if (!result.ok) {
|
|
260
|
+
throw new TriageError(`accept #${issueNumber} (${repo}): accept recorded but --auto-promote failed: ${result.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
210
263
|
/** Record an accept audit entry and delegate vBRIEF authoring to issue_ingest. */
|
|
211
264
|
export function accept(issueNumber, repo, deps, options = {}) {
|
|
212
265
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
213
266
|
const actor = resolveActor(options.actor);
|
|
214
267
|
const prior = isIdempotentRepeat(deps, issueNumber, repo, "accept", projectRoot);
|
|
215
268
|
if (prior !== null) {
|
|
269
|
+
if (options.autoPromote === true) {
|
|
270
|
+
runAutoPromote(issueNumber, repo, projectRoot, prior.decision_id, null, options.force);
|
|
271
|
+
}
|
|
216
272
|
return prior.decision_id;
|
|
217
273
|
}
|
|
218
274
|
const entry = buildEntry(deps, "accept", issueNumber, repo, actor);
|
|
219
275
|
const logPath = logPathFor(projectRoot);
|
|
220
276
|
const decisionId = deps.candidatesLog.append(entry, { path: logPath });
|
|
277
|
+
let ingestedPath = null;
|
|
221
278
|
try {
|
|
222
|
-
deps.issueIngest.ingestSingleForAccept(issueNumber, repo, { projectRoot });
|
|
279
|
+
ingestedPath = deps.issueIngest.ingestSingleForAccept(issueNumber, repo, { projectRoot });
|
|
223
280
|
}
|
|
224
281
|
catch (exc) {
|
|
225
282
|
rollbackAuditEntry(decisionId, projectRoot, logPath);
|
|
226
283
|
throw new TriageError(`accept #${issueNumber} (${repo}): issue:ingest delegation failed; audit entry rolled back. Cause: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
227
284
|
}
|
|
285
|
+
if (options.autoPromote === true) {
|
|
286
|
+
runAutoPromote(issueNumber, repo, projectRoot, decisionId, typeof ingestedPath === "string" ? ingestedPath : null, options.force);
|
|
287
|
+
}
|
|
228
288
|
return decisionId;
|
|
229
289
|
}
|
|
230
290
|
/** Close upstream, best-effort label, record reject audit entry. */
|
|
@@ -21,9 +21,12 @@ export interface CandidatesLog {
|
|
|
21
21
|
newDecisionId(): string;
|
|
22
22
|
}
|
|
23
23
|
export interface IssueIngest {
|
|
24
|
+
/**
|
|
25
|
+
* Ingest issue into proposed/; return absolute path when known (#1136 auto-promote).
|
|
26
|
+
*/
|
|
24
27
|
ingestSingleForAccept(issueNumber: number, repo: string, options?: {
|
|
25
28
|
projectRoot?: string;
|
|
26
|
-
}):
|
|
29
|
+
}): string | null | undefined;
|
|
27
30
|
}
|
|
28
31
|
export interface ScmRunner {
|
|
29
32
|
call(source: string, verb: string, args: readonly string[], options?: {
|
|
@@ -44,6 +47,10 @@ export interface TriageActionsDeps {
|
|
|
44
47
|
export interface AcceptOptions {
|
|
45
48
|
actor?: string | null;
|
|
46
49
|
projectRoot?: string;
|
|
50
|
+
/** After accept+ingest, promote proposed → pending (#1136). */
|
|
51
|
+
autoPromote?: boolean;
|
|
52
|
+
/** WIP-cap override for the auto-promote leg. */
|
|
53
|
+
force?: boolean;
|
|
47
54
|
}
|
|
48
55
|
export interface RejectOptions {
|
|
49
56
|
actor?: string | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Injectable authenticated-login resolver (tests inject; production uses gh). */
|
|
2
|
+
export type ResolveAuthenticatedLogin = () => string | null;
|
|
3
|
+
export interface AuthorFilter {
|
|
4
|
+
/** Raw CLI value before resolution (e.g. `@me` or `alice,bob`). */
|
|
5
|
+
readonly raw: string;
|
|
6
|
+
/** Resolved allow-list logins (exact match; case-sensitive like bulk). */
|
|
7
|
+
readonly allowLogins: readonly string[];
|
|
8
|
+
/** True when any token was `@me` / `--author-mine`. */
|
|
9
|
+
readonly usedMe: boolean;
|
|
10
|
+
/** Header/digest display string. */
|
|
11
|
+
readonly display: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthorFilterResolveResult {
|
|
14
|
+
readonly filter?: AuthorFilter;
|
|
15
|
+
readonly error?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface AuthorPartitionResult<T> {
|
|
18
|
+
readonly matched: readonly T[];
|
|
19
|
+
readonly unknownCount: number;
|
|
20
|
+
readonly nonMatchingCount: number;
|
|
21
|
+
}
|
|
22
|
+
/** Split comma allow-list; trim; drop empties. */
|
|
23
|
+
export declare function parseAuthorTokens(raw: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `@me` (and bare tokens) into an AuthorFilter.
|
|
26
|
+
* Returns error when raw is empty/whitespace-only or `@me` cannot be resolved.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveAuthorFilter(raw: string, resolveMe?: ResolveAuthenticatedLogin | null): AuthorFilterResolveResult;
|
|
29
|
+
/**
|
|
30
|
+
* Default `@me` resolution via live `gh` (not ghx — multi-arg api --jq; #2275 / #954).
|
|
31
|
+
*/
|
|
32
|
+
export declare function defaultResolveAuthenticatedLogin(): string | null;
|
|
33
|
+
/** Login from a CachedIssue.author string (empty = unknown). */
|
|
34
|
+
export declare function normalizeAuthorLogin(login: string | null | undefined): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Login from a raw cache payload (author.login / user.login / string author).
|
|
37
|
+
* Empty / missing → null (unknown).
|
|
38
|
+
*/
|
|
39
|
+
export declare function authorLoginFromRawIssue(issue: Record<string, unknown> | null | undefined): string | null;
|
|
40
|
+
/** Exact allow-list match (bulk parity). Unknown/missing never matches. */
|
|
41
|
+
export declare function matchesAuthorFilter(login: string | null | undefined, filter: AuthorFilter): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Partition items by author filter. Unknown (missing login) counted separately
|
|
44
|
+
* and excluded from matched — callers disclose unknownCount in headers.
|
|
45
|
+
*/
|
|
46
|
+
export declare function partitionByAuthorFilter<T>(items: readonly T[], getLogin: (item: T) => string | null | undefined, filter: AuthorFilter): AuthorPartitionResult<T>;
|
|
47
|
+
/** Single-line header fragment for queue / classify digest. */
|
|
48
|
+
export declare function formatAuthorFilterLine(filter: AuthorFilter, options?: {
|
|
49
|
+
readonly unknownCount?: number;
|
|
50
|
+
}): string;
|
|
51
|
+
//# sourceMappingURL=author-filter.d.ts.map
|