@deftai/directive-core 0.105.0 → 0.106.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 +591 -34
- package/dist/check/named-cause.js +1 -1
- package/dist/forward-coverage/diff-coverage.d.ts +39 -0
- package/dist/forward-coverage/diff-coverage.js +112 -0
- package/dist/forward-coverage/diff-lines.d.ts +19 -0
- package/dist/forward-coverage/diff-lines.js +173 -0
- package/dist/forward-coverage/evaluate.d.ts +24 -9
- package/dist/forward-coverage/evaluate.js +125 -19
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +1 -1
- package/dist/intake/clause-derivation.d.ts +9 -0
- package/dist/intake/clause-derivation.js +30 -3
- package/dist/lifecycle-visible/evaluate.d.ts +98 -0
- package/dist/lifecycle-visible/evaluate.js +710 -0
- package/dist/lifecycle-visible/index.d.ts +2 -0
- package/dist/lifecycle-visible/index.js +2 -0
- package/dist/literal-acceptance/capture.js +3 -3
- package/dist/orchestration/judgment-policy.d.ts +13 -0
- package/dist/orchestration/judgment-policy.js +15 -0
- package/dist/orchestration/probe-session.js +13 -8
- package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
- package/dist/orchestration/verify-judgment-gates.js +43 -11
- package/dist/policy/ceremony-dial.js +7 -7
- package/dist/policy/host-hooks.d.ts +20 -0
- package/dist/policy/host-hooks.js +121 -2
- package/dist/policy/org-force-on-migration.js +2 -1
- package/dist/policy/plan-extensions.d.ts +1 -1
- package/dist/policy/plan-extensions.js +2 -0
- package/dist/policy/product-signal.js +6 -7
- package/dist/policy/require-human-merge.js +8 -6
- package/dist/policy/resolve.d.ts +7 -2
- package/dist/policy/resolve.js +25 -8
- package/dist/policy/value-feedback.js +7 -8
- package/dist/preflight-cache/evaluate.d.ts +12 -0
- package/dist/preflight-cache/evaluate.js +15 -1
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/product-first-done-gate/evaluate.d.ts +4 -0
- package/dist/product-first-done-gate/evaluate.js +46 -5
- package/dist/release/closed-verb-gate.d.ts +12 -0
- package/dist/release/closed-verb-gate.js +51 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/pipeline.js +7 -0
- package/dist/release/types.d.ts +12 -0
- package/dist/render/constants.d.ts +2 -1
- package/dist/render/constants.js +2 -1
- package/dist/render/framework-commands.js +4 -0
- package/dist/render/spec-validate.js +23 -12
- package/dist/run-summary/types.d.ts +4 -0
- package/dist/scope/main.js +15 -5
- package/dist/scope/promote-from-issue.js +5 -5
- package/dist/scope-provenance/evaluate.js +3 -3
- package/dist/session/ac-pass-banking.d.ts +5 -0
- package/dist/session/ac-pass-banking.js +3 -2
- package/dist/session/process-cost.d.ts +32 -10
- package/dist/session/process-cost.js +189 -1
- package/dist/session/product-state-hash.js +41 -24
- package/dist/session/ritual-entrypoint.js +16 -1
- package/dist/session/session-start.d.ts +6 -0
- package/dist/session/session-start.js +32 -2
- package/dist/session/verify-session-ritual.d.ts +24 -1
- package/dist/session/verify-session-ritual.js +89 -6
- package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
- package/dist/swarm/finalize-cohort-cli.js +165 -35
- package/dist/swarm/finalize-cohort.d.ts +1 -0
- package/dist/swarm/finalize-cohort.js +14 -2
- package/dist/swarm/routing-set-cli.js +2 -0
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/triage/bootstrap/index.js +6 -6
- package/dist/triage/help/registry-data.d.ts +1 -1
- package/dist/triage/help/registry-data.js +1 -1
- package/dist/triage/welcome/writers.d.ts +1 -1
- package/dist/triage/welcome/writers.js +10 -5
- package/dist/value/readback.d.ts +4 -1
- package/dist/value/readback.js +51 -12
- package/dist/vbrief-validate/schema.d.ts +4 -0
- package/dist/vbrief-validate/schema.js +1 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
- package/dist/verify-env/agent-hook-readiness.js +12 -8
- package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
- package/dist/verify-env/agent-hooks-live-probe.js +26 -5
- package/dist/verify-env/agent-hooks.js +3 -4
- package/package.json +7 -3
|
@@ -205,7 +205,7 @@ function listActiveXbriefPaths(projectRoot) {
|
|
|
205
205
|
}
|
|
206
206
|
function remediationForExpansion() {
|
|
207
207
|
return ("Renew human approval: re-record the approved-scope digest after operator review " +
|
|
208
|
-
"(`task scope:record-approved-scope -- <xbrief-path> --actor <you
|
|
208
|
+
"(`task scope:record-approved-scope -- <xbrief-path> --actor <you> --confirm` writes " +
|
|
209
209
|
"`.deft/approved-scope/<plan-id>.json` with a humanApproval stamp). Commit that " +
|
|
210
210
|
"approval on the merge base (or a prior PR) before expanding or activating the " +
|
|
211
211
|
"scoped xBRIEF in the implementation change set. Editing the active xBRIEF alone " +
|
|
@@ -344,7 +344,7 @@ export function evaluateOneScopeProvenance(input) {
|
|
|
344
344
|
detail: "active xBRIEF modified with a non-human (agent/missing) approved-scope stamp; " +
|
|
345
345
|
"only humanApproval stamps authorize non-empty file_scope",
|
|
346
346
|
remediation: "Record a human-origin approval via `task scope:record-approved-scope -- " +
|
|
347
|
-
"<xbrief-path> --actor <you
|
|
347
|
+
"<xbrief-path> --actor <you> --confirm` (#3145 / #3205).",
|
|
348
348
|
};
|
|
349
349
|
}
|
|
350
350
|
}
|
|
@@ -361,7 +361,7 @@ export function evaluateOneScopeProvenance(input) {
|
|
|
361
361
|
detail: "active xBRIEF modified with a non-human approved-scope stamp (scope shrink/noise path); " +
|
|
362
362
|
"only humanApproval stamps authorize non-empty file_scope",
|
|
363
363
|
remediation: "Record a human-origin approval via `task scope:record-approved-scope -- " +
|
|
364
|
-
"<xbrief-path> --actor <you
|
|
364
|
+
"<xbrief-path> --actor <you> --confirm` (#3145 / #3205).",
|
|
365
365
|
};
|
|
366
366
|
}
|
|
367
367
|
return null;
|
|
@@ -173,6 +173,11 @@ export interface MaybeBankOnAcPassInput {
|
|
|
173
173
|
readonly executableRuns: number;
|
|
174
174
|
readonly quiet?: boolean;
|
|
175
175
|
readonly productStateHash?: string | null;
|
|
176
|
+
/**
|
|
177
|
+
* Bank a verified-pass walk even when no shell runs were recorded (#3558).
|
|
178
|
+
* Soft/advisory empty passes still skip via executableRuns === 0.
|
|
179
|
+
*/
|
|
180
|
+
readonly verifiedPass?: boolean;
|
|
176
181
|
}
|
|
177
182
|
export interface MaybeBankOnAcPassResult {
|
|
178
183
|
readonly banked: boolean;
|
|
@@ -17,6 +17,7 @@ import { containedWrite } from "../fs/contained-write.js";
|
|
|
17
17
|
import { DEFAULT_SURPLUS_THRESHOLD, resolveAcPassBanking, } from "../policy/ac-pass-banking.js";
|
|
18
18
|
import { RunSummaryEmitter } from "../run-summary/emit.js";
|
|
19
19
|
import { detectHardEffortBudget, formatDeepeningSkippedNote, recommendVerificationDepth, } from "./effort-budget.js";
|
|
20
|
+
import { gitHead } from "./git.js";
|
|
20
21
|
/** Durable bank ledger directory (gitignored under `.deft/`). */
|
|
21
22
|
export const AC_PASS_BANK_DIR = ".deft/ac-pass-banks";
|
|
22
23
|
export const AC_PASS_BANK_SCHEMA_VERSION = 1;
|
|
@@ -682,7 +683,7 @@ export function resolveBankingConfigForRoot(projectRoot, environ) {
|
|
|
682
683
|
* Throws on I/O failure so verify:ac can fail closed (checkpoint mandatory).
|
|
683
684
|
*/
|
|
684
685
|
export function maybeBankOnAcPass(input) {
|
|
685
|
-
if (input.executableRuns <= 0) {
|
|
686
|
+
if (input.executableRuns <= 0 && input.verifiedPass !== true) {
|
|
686
687
|
return { banked: false, decision: null, bank: null, notes: [] };
|
|
687
688
|
}
|
|
688
689
|
const budget = input.budget ?? detectHardEffortBudget({ environ: input.environ ?? process.env });
|
|
@@ -699,7 +700,7 @@ export function maybeBankOnAcPass(input) {
|
|
|
699
700
|
budget,
|
|
700
701
|
surplus: decision.surplus,
|
|
701
702
|
nextAction,
|
|
702
|
-
headSha: input.headSha
|
|
703
|
+
headSha: input.headSha !== undefined ? input.headSha : gitHead(input.projectRoot).head,
|
|
703
704
|
productStateHash: input.productStateHash ?? null,
|
|
704
705
|
now: input.now,
|
|
705
706
|
environ: input.environ,
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local process-cost ceremony events (#2994).
|
|
3
|
-
*
|
|
4
|
-
* Always-on, best-effort appends to `.deft-cache/events.jsonl`.
|
|
5
|
-
* Not gated on valueFeedback (distinct from #1709 attribution).
|
|
6
|
-
* No remote / Product Insights upload (#2603 not required).
|
|
7
|
-
*
|
|
8
|
-
* Types are declared locally (not imported from session-start) so this module
|
|
9
|
-
* does not form an import cycle with session-start call sites.
|
|
10
|
-
*/
|
|
11
1
|
import { type BehavioralEventRecord } from "../lifecycle/events.js";
|
|
12
2
|
export { PROCESS_COST_EVENT_NAMES, PROCESS_COST_REQUIRED_PAYLOAD, type ProcessCostEventName, } from "./process-cost-constants.js";
|
|
13
3
|
/** Ceremony tier labels mirror session-start cold|rearm (#2992 / #2994). */
|
|
@@ -50,4 +40,36 @@ export declare function emitSessionStartProcessCost(input: SessionStartProcessCo
|
|
|
50
40
|
* Returns null on any failure (telemetry must not change deny verdict).
|
|
51
41
|
*/
|
|
52
42
|
export declare function emitSessionRitualBlockedProcessCost(input: SessionRitualBlockedProcessCostInput, options: EmitProcessCostOptions): BehavioralEventRecord | null;
|
|
43
|
+
export interface CeremonyCostRollup {
|
|
44
|
+
/** Discriminator: CLI process time, not agent-turn wall clock (#3500 / #3508). */
|
|
45
|
+
readonly kind: "cli_process_time";
|
|
46
|
+
readonly windowLabel: string;
|
|
47
|
+
readonly lastColdDurationMs: number | null;
|
|
48
|
+
readonly lastRearmDurationMs: number | null;
|
|
49
|
+
readonly lastColdSteps: readonly ProcessCostStepTiming[];
|
|
50
|
+
readonly lastRearmSteps: readonly ProcessCostStepTiming[];
|
|
51
|
+
readonly blockedRitualCount: number;
|
|
52
|
+
readonly recoveryTierDistribution: Readonly<Record<string, number>>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Roll up ceremony cost from `.deft-cache/events.jsonl` (#3508).
|
|
56
|
+
* Measures CLI process time, not agent-turn wall clock (#3500).
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeCeremonyCostRollup(options: {
|
|
59
|
+
projectRoot: string;
|
|
60
|
+
logPath?: string | null;
|
|
61
|
+
windowMs?: number;
|
|
62
|
+
now?: Date;
|
|
63
|
+
windowLabel?: string;
|
|
64
|
+
}): CeremonyCostRollup;
|
|
65
|
+
/**
|
|
66
|
+
* Human rollup for the composed `value:show` reader (#3508).
|
|
67
|
+
* CLI process time, not agent-turn wall clock (#3500).
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatCeremonyCostReport(rollup: CeremonyCostRollup): string;
|
|
70
|
+
/**
|
|
71
|
+
* Operator-visible mutation `session:start` line (#3508).
|
|
72
|
+
* CLI process time only. ⊗ not #3286 Later graduation input.
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatSessionStartCeremonyCostLine(ceremonyTier: ProcessCostCeremonyTier, durationMs: number): string;
|
|
53
75
|
//# sourceMappingURL=process-cost.d.ts.map
|
|
@@ -7,8 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Types are declared locally (not imported from session-start) so this module
|
|
9
9
|
* does not form an import cycle with session-start call sites.
|
|
10
|
+
*
|
|
11
|
+
* #3508: composed readers live on `task value:show` (`value/readback.ts`).
|
|
12
|
+
* This module measures CLI process time, not agent-turn wall clock (#3500).
|
|
13
|
+
* ⊗ Do not use a printed CLI duration as #3286 Later graduation input.
|
|
10
14
|
*/
|
|
11
|
-
import {
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import { DEFAULT_EVENT_LOG, emit, readEvents, } from "../lifecycle/events.js";
|
|
12
17
|
import { PROCESS_COST_EVENT_NAMES } from "./process-cost-constants.js";
|
|
13
18
|
export { PROCESS_COST_EVENT_NAMES, PROCESS_COST_REQUIRED_PAYLOAD, } from "./process-cost-constants.js";
|
|
14
19
|
/**
|
|
@@ -79,4 +84,187 @@ export function emitSessionRitualBlockedProcessCost(input, options) {
|
|
|
79
84
|
return null;
|
|
80
85
|
}
|
|
81
86
|
}
|
|
87
|
+
function isRecordPayload(payload) {
|
|
88
|
+
return payload !== null && typeof payload === "object" && !Array.isArray(payload);
|
|
89
|
+
}
|
|
90
|
+
function parseDetectedAt(record) {
|
|
91
|
+
const raw = record.detected_at;
|
|
92
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
let text = raw.trim();
|
|
96
|
+
if (text.endsWith("Z")) {
|
|
97
|
+
text = `${text.slice(0, -1)}+00:00`;
|
|
98
|
+
}
|
|
99
|
+
const parsed = new Date(text);
|
|
100
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
101
|
+
}
|
|
102
|
+
function payloadNumber(payload, key) {
|
|
103
|
+
const raw = payload[key];
|
|
104
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
105
|
+
}
|
|
106
|
+
function resolveProcessCostLogPath(projectRoot, logPath) {
|
|
107
|
+
if (logPath !== undefined && logPath !== null) {
|
|
108
|
+
return resolve(logPath);
|
|
109
|
+
}
|
|
110
|
+
return resolve(projectRoot, DEFAULT_EVENT_LOG);
|
|
111
|
+
}
|
|
112
|
+
function parseSteps(payload) {
|
|
113
|
+
const raw = payload?.steps;
|
|
114
|
+
if (!Array.isArray(raw)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const item of raw) {
|
|
119
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const rec = item;
|
|
123
|
+
if (typeof rec.name !== "string" || typeof rec.duration_ms !== "number") {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!Number.isFinite(rec.duration_ms)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const step = {
|
|
130
|
+
name: rec.name,
|
|
131
|
+
duration_ms: rec.duration_ms,
|
|
132
|
+
};
|
|
133
|
+
if (rec.skipped === true) {
|
|
134
|
+
out.push({ ...step, skipped: true });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
out.push(step);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
function lastSessionStartForTier(events, tier) {
|
|
143
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
144
|
+
const record = events[i];
|
|
145
|
+
if (record === undefined || record.event !== PROCESS_COST_EVENT_NAMES.sessionStart) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const payload = isRecordPayload(record.payload) ? record.payload : null;
|
|
149
|
+
if (payload?.ceremony_tier === tier) {
|
|
150
|
+
return record;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function durationFrom(record) {
|
|
156
|
+
if (record === null || !isRecordPayload(record.payload)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return payloadNumber(record.payload, "duration_ms");
|
|
160
|
+
}
|
|
161
|
+
function formatWindowLabel(windowMs) {
|
|
162
|
+
const days = Math.round(windowMs / 86_400_000);
|
|
163
|
+
if (days >= 1 && days * 86_400_000 === windowMs) {
|
|
164
|
+
return `${days}d`;
|
|
165
|
+
}
|
|
166
|
+
const hours = Math.round(windowMs / 3_600_000);
|
|
167
|
+
if (hours >= 1 && hours * 3_600_000 === windowMs) {
|
|
168
|
+
return `${hours}h`;
|
|
169
|
+
}
|
|
170
|
+
return `${windowMs}ms`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Roll up ceremony cost from `.deft-cache/events.jsonl` (#3508).
|
|
174
|
+
* Measures CLI process time, not agent-turn wall clock (#3500).
|
|
175
|
+
*/
|
|
176
|
+
export function computeCeremonyCostRollup(options) {
|
|
177
|
+
const windowMs = options.windowMs ?? 7 * 86_400_000;
|
|
178
|
+
const now = options.now ?? new Date();
|
|
179
|
+
const sinceMs = now.getTime() - windowMs;
|
|
180
|
+
const logPath = resolveProcessCostLogPath(options.projectRoot, options.logPath);
|
|
181
|
+
let records = [];
|
|
182
|
+
try {
|
|
183
|
+
records = readEvents(logPath).filter((record) => {
|
|
184
|
+
if (record.event !== PROCESS_COST_EVENT_NAMES.sessionStart &&
|
|
185
|
+
record.event !== PROCESS_COST_EVENT_NAMES.sessionRitualBlocked) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const at = parseDetectedAt(record);
|
|
189
|
+
if (at === null) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return at.getTime() >= sinceMs;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
records = [];
|
|
197
|
+
}
|
|
198
|
+
const lastCold = lastSessionStartForTier(records, "cold");
|
|
199
|
+
const lastRearm = lastSessionStartForTier(records, "rearm");
|
|
200
|
+
const recoveryTierDistribution = {};
|
|
201
|
+
let blockedRitualCount = 0;
|
|
202
|
+
for (const record of records) {
|
|
203
|
+
if (record.event !== PROCESS_COST_EVENT_NAMES.sessionRitualBlocked) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
blockedRitualCount += 1;
|
|
207
|
+
const payload = isRecordPayload(record.payload) ? record.payload : null;
|
|
208
|
+
const rawTier = payload?.recovery_tier;
|
|
209
|
+
const tier = typeof rawTier === "string" && rawTier.trim().length > 0 ? rawTier.trim() : "unspecified";
|
|
210
|
+
recoveryTierDistribution[tier] = (recoveryTierDistribution[tier] ?? 0) + 1;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
kind: "cli_process_time",
|
|
214
|
+
windowLabel: options.windowLabel ?? formatWindowLabel(windowMs),
|
|
215
|
+
lastColdDurationMs: durationFrom(lastCold),
|
|
216
|
+
lastRearmDurationMs: durationFrom(lastRearm),
|
|
217
|
+
lastColdSteps: parseSteps(lastCold !== null && isRecordPayload(lastCold.payload) ? lastCold.payload : null),
|
|
218
|
+
lastRearmSteps: parseSteps(lastRearm !== null && isRecordPayload(lastRearm.payload) ? lastRearm.payload : null),
|
|
219
|
+
blockedRitualCount,
|
|
220
|
+
recoveryTierDistribution,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function formatMs(value) {
|
|
224
|
+
return value === null ? "none" : `${value}ms`;
|
|
225
|
+
}
|
|
226
|
+
function formatSteps(steps) {
|
|
227
|
+
if (steps.length === 0) {
|
|
228
|
+
return "none";
|
|
229
|
+
}
|
|
230
|
+
return steps
|
|
231
|
+
.map((step) => {
|
|
232
|
+
const skip = step.skipped === true ? " skipped" : "";
|
|
233
|
+
return `${step.name}=${step.duration_ms}ms${skip}`;
|
|
234
|
+
})
|
|
235
|
+
.join(", ");
|
|
236
|
+
}
|
|
237
|
+
function formatRecovery(distribution) {
|
|
238
|
+
const parts = Object.entries(distribution)
|
|
239
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
240
|
+
.map(([tier, count]) => `${tier}=${count}`);
|
|
241
|
+
return parts.length === 0 ? "none" : parts.join(", ");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Human rollup for the composed `value:show` reader (#3508).
|
|
245
|
+
* CLI process time, not agent-turn wall clock (#3500).
|
|
246
|
+
*/
|
|
247
|
+
export function formatCeremonyCostReport(rollup) {
|
|
248
|
+
const lines = [
|
|
249
|
+
`[ceremony] CLI process time (not agent-turn wall clock; #3508 / #2994) ` +
|
|
250
|
+
`(${rollup.windowLabel}):`,
|
|
251
|
+
` last cold: ${formatMs(rollup.lastColdDurationMs)}`,
|
|
252
|
+
` last re-arm: ${formatMs(rollup.lastRearmDurationMs)}`,
|
|
253
|
+
` steps (last cold ${PROCESS_COST_EVENT_NAMES.sessionStart}): ` +
|
|
254
|
+
formatSteps(rollup.lastColdSteps),
|
|
255
|
+
` steps (last re-arm ${PROCESS_COST_EVENT_NAMES.sessionStart}): ` +
|
|
256
|
+
formatSteps(rollup.lastRearmSteps),
|
|
257
|
+
` blocked-ritual (${PROCESS_COST_EVENT_NAMES.sessionRitualBlocked}): ` +
|
|
258
|
+
String(rollup.blockedRitualCount),
|
|
259
|
+
` recovery-tier: ${formatRecovery(rollup.recoveryTierDistribution)}`,
|
|
260
|
+
];
|
|
261
|
+
return `${lines.join("\n")}\n`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Operator-visible mutation `session:start` line (#3508).
|
|
265
|
+
* CLI process time only. ⊗ not #3286 Later graduation input.
|
|
266
|
+
*/
|
|
267
|
+
export function formatSessionStartCeremonyCostLine(ceremonyTier, durationMs) {
|
|
268
|
+
return `[deft session] ceremony ${ceremonyTier} ${durationMs}ms`;
|
|
269
|
+
}
|
|
82
270
|
//# sourceMappingURL=process-cost.js.map
|
|
@@ -19,6 +19,8 @@ const EXCLUDED_DIR_NAMES = new Set([
|
|
|
19
19
|
".planning",
|
|
20
20
|
]);
|
|
21
21
|
const EXCLUDED_PATH_PREFIXES = ["xbrief/", "vbrief/", ".deft/", ".git/"];
|
|
22
|
+
/** Repo-root run-summary telemetry must not invalidate a green bank (#3558). */
|
|
23
|
+
const ROOT_RUN_SUMMARY_REL = ".deft-run-summary.json";
|
|
22
24
|
function asRecord(value) {
|
|
23
25
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
24
26
|
return value;
|
|
@@ -39,13 +41,15 @@ function stableJson(value) {
|
|
|
39
41
|
function toPosix(rel) {
|
|
40
42
|
return rel.replace(/\\/g, "/");
|
|
41
43
|
}
|
|
42
|
-
function isExcludedRel(rel) {
|
|
44
|
+
function isExcludedRel(rel, options) {
|
|
43
45
|
const posix = toPosix(rel);
|
|
44
46
|
if (posix === "." || posix.length === 0)
|
|
45
47
|
return false;
|
|
46
48
|
const first = posix.split("/")[0] ?? "";
|
|
47
49
|
if (EXCLUDED_DIR_NAMES.has(first))
|
|
48
50
|
return true;
|
|
51
|
+
if (!options?.keepTelemetryFiles && posix === ROOT_RUN_SUMMARY_REL)
|
|
52
|
+
return true;
|
|
49
53
|
return EXCLUDED_PATH_PREFIXES.some((prefix) => posix === prefix.slice(0, -1) || posix.startsWith(prefix));
|
|
50
54
|
}
|
|
51
55
|
function hashBytes(buf) {
|
|
@@ -130,7 +134,7 @@ function relFromRootBytes(root, abs) {
|
|
|
130
134
|
}
|
|
131
135
|
return toPosix(relative(root, abs.toString("utf8")));
|
|
132
136
|
}
|
|
133
|
-
function walkFiles(root, dir, out, seen = new Set()) {
|
|
137
|
+
function walkFiles(root, dir, out, seen = new Set(), keepTelemetryFiles = false) {
|
|
134
138
|
const dirBuf = asPathBuf(dir);
|
|
135
139
|
let real;
|
|
136
140
|
try {
|
|
@@ -153,7 +157,7 @@ function walkFiles(root, dir, out, seen = new Set()) {
|
|
|
153
157
|
const nameLatin1 = nameBuf.toString("latin1");
|
|
154
158
|
const abs = joinNameBytes(dirBuf, nameBuf);
|
|
155
159
|
const rel = relFromRootBytes(root, abs);
|
|
156
|
-
if (isExcludedRel(rel))
|
|
160
|
+
if (isExcludedRel(rel, { keepTelemetryFiles }))
|
|
157
161
|
continue;
|
|
158
162
|
let st;
|
|
159
163
|
try {
|
|
@@ -165,7 +169,7 @@ function walkFiles(root, dir, out, seen = new Set()) {
|
|
|
165
169
|
if (st.isDirectory()) {
|
|
166
170
|
if (EXCLUDED_DIR_NAMES.has(nameLatin1))
|
|
167
171
|
continue;
|
|
168
|
-
walkFiles(root, abs, out, seen);
|
|
172
|
+
walkFiles(root, abs, out, seen, keepTelemetryFiles);
|
|
169
173
|
continue;
|
|
170
174
|
}
|
|
171
175
|
if (st.isFile())
|
|
@@ -409,7 +413,7 @@ function literalDirPrefix(rel) {
|
|
|
409
413
|
}
|
|
410
414
|
return literal.join("/");
|
|
411
415
|
}
|
|
412
|
-
function expandPath(root, relOrGlob) {
|
|
416
|
+
function expandPath(root, relOrGlob, keepTelemetryFiles = false) {
|
|
413
417
|
const rel = toPosix(relOrGlob).replace(/^\.\//, "");
|
|
414
418
|
if (looksLikeGlob(rel)) {
|
|
415
419
|
try {
|
|
@@ -418,8 +422,11 @@ function expandPath(root, relOrGlob) {
|
|
|
418
422
|
for (const pattern of globPatternsIncludingDotfiles(rel)) {
|
|
419
423
|
for (const match of globSync(pattern, { cwd: root })) {
|
|
420
424
|
const posix = toPosix(relative(root, resolve(root, match)));
|
|
421
|
-
if (posix.length === 0 ||
|
|
425
|
+
if (posix.length === 0 ||
|
|
426
|
+
isExcludedRel(posix, { keepTelemetryFiles }) ||
|
|
427
|
+
seen.has(posix)) {
|
|
422
428
|
continue;
|
|
429
|
+
}
|
|
423
430
|
seen.add(posix);
|
|
424
431
|
const absMatch = resolve(root, posix);
|
|
425
432
|
let st;
|
|
@@ -432,7 +439,7 @@ function expandPath(root, relOrGlob) {
|
|
|
432
439
|
if (st.isFile())
|
|
433
440
|
out.push(posix);
|
|
434
441
|
else if (st.isDirectory())
|
|
435
|
-
walkFiles(root, absMatch, out);
|
|
442
|
+
walkFiles(root, absMatch, out, new Set(), keepTelemetryFiles);
|
|
436
443
|
}
|
|
437
444
|
}
|
|
438
445
|
{
|
|
@@ -448,7 +455,7 @@ function expandPath(root, relOrGlob) {
|
|
|
448
455
|
}
|
|
449
456
|
if (st?.isDirectory()) {
|
|
450
457
|
const walked = [];
|
|
451
|
-
walkFiles(root, startDir, walked);
|
|
458
|
+
walkFiles(root, startDir, walked, new Set(), keepTelemetryFiles);
|
|
452
459
|
const re = globToRegExp(rel);
|
|
453
460
|
for (const file of walked) {
|
|
454
461
|
if (seen.has(file) || !re.test(file))
|
|
@@ -472,7 +479,7 @@ function expandPath(root, relOrGlob) {
|
|
|
472
479
|
const st = statSync(abs);
|
|
473
480
|
if (st.isDirectory()) {
|
|
474
481
|
const out = [];
|
|
475
|
-
walkFiles(root, abs, out);
|
|
482
|
+
walkFiles(root, abs, out, new Set(), keepTelemetryFiles);
|
|
476
483
|
return out;
|
|
477
484
|
}
|
|
478
485
|
if (st.isFile())
|
|
@@ -629,45 +636,55 @@ export function hashProductState(input) {
|
|
|
629
636
|
const runGit = input.runGit ?? defaultGitRunner;
|
|
630
637
|
const files = new Set();
|
|
631
638
|
let statusOk = true;
|
|
639
|
+
const scope = fileScopePaths(input.plan);
|
|
640
|
+
const specifiedSurface = input.productPaths !== undefined || scope.length > 0;
|
|
641
|
+
const keepTelemetryFiles = specifiedSurface;
|
|
632
642
|
if (input.productPaths !== undefined) {
|
|
633
643
|
for (const rel of input.productPaths) {
|
|
634
|
-
for (const expanded of expandPath(root, rel))
|
|
644
|
+
for (const expanded of expandPath(root, rel, true))
|
|
635
645
|
files.add(expanded);
|
|
636
646
|
}
|
|
637
647
|
}
|
|
638
|
-
else {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
for (const expanded of expandPath(root, entry))
|
|
643
|
-
files.add(expanded);
|
|
644
|
-
}
|
|
648
|
+
else if (scope.length > 0) {
|
|
649
|
+
for (const entry of scope) {
|
|
650
|
+
for (const expanded of expandPath(root, entry, keepTelemetryFiles))
|
|
651
|
+
files.add(expanded);
|
|
645
652
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
const git = existsSync(join(root, ".git"));
|
|
656
|
+
if (git) {
|
|
657
|
+
const dirty = dirtyProductFiles(root, runGit);
|
|
658
|
+
if (dirty.ok) {
|
|
659
|
+
statusOk = true;
|
|
651
660
|
for (const rel of dirty.files) {
|
|
652
661
|
for (const expanded of expandIfDirectory(root, rel))
|
|
653
662
|
files.add(expanded);
|
|
654
663
|
}
|
|
655
664
|
}
|
|
656
665
|
else {
|
|
666
|
+
// git status failed (containers, missing git, safe.directory).
|
|
667
|
+
// Walk product files instead of miss-forever (#3558 / #3549).
|
|
657
668
|
const walked = [];
|
|
658
669
|
walkFiles(root, root, walked);
|
|
659
670
|
for (const rel of walked)
|
|
660
671
|
files.add(rel);
|
|
672
|
+
statusOk = true;
|
|
661
673
|
}
|
|
662
674
|
}
|
|
675
|
+
else {
|
|
676
|
+
const walked = [];
|
|
677
|
+
walkFiles(root, root, walked);
|
|
678
|
+
for (const rel of walked)
|
|
679
|
+
files.add(rel);
|
|
680
|
+
}
|
|
663
681
|
}
|
|
664
|
-
const sorted = [...files].sort();
|
|
682
|
+
const sorted = [...files].filter((rel) => !isExcludedRel(rel, { keepTelemetryFiles })).sort();
|
|
665
683
|
const fileHashes = {};
|
|
666
684
|
for (const rel of sorted) {
|
|
667
685
|
fileHashes[rel] = hashProductRel(root, rel, runGit);
|
|
668
686
|
}
|
|
669
687
|
const head = existsSync(join(root, ".git")) ? gitHead(root, runGit).head : null;
|
|
670
|
-
const specifiedSurface = input.productPaths !== undefined || fileScopePaths(input.plan).length > 0;
|
|
671
688
|
const hasSurface = specifiedSurface
|
|
672
689
|
? sorted.length > 0
|
|
673
690
|
: statusOk && (sorted.length > 0 || head !== null);
|
|
@@ -46,12 +46,27 @@ function parseProjectRoot(argv) {
|
|
|
46
46
|
}
|
|
47
47
|
return ".";
|
|
48
48
|
}
|
|
49
|
+
function parseOptionalForIssue(argv) {
|
|
50
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
51
|
+
if (argv[i] !== "--for-issue")
|
|
52
|
+
continue;
|
|
53
|
+
const next = argv[i + 1];
|
|
54
|
+
if (next === undefined || next.startsWith("-"))
|
|
55
|
+
return null;
|
|
56
|
+
const parsed = Number.parseInt(next, 10);
|
|
57
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
49
61
|
/** CLI-shaped cache-fresh entrypoint (mirrors scripts/preflight_cache.py main). */
|
|
50
62
|
export function runCacheFreshMain(argv) {
|
|
51
63
|
const projectRoot = parseProjectRoot(argv);
|
|
52
64
|
const allowMissingBootstrap = argv.includes("--allow-missing-bootstrap");
|
|
53
65
|
const quiet = argv.includes("--quiet");
|
|
54
|
-
const
|
|
66
|
+
const workSelection = argv.includes("--work-selection");
|
|
67
|
+
const skipDriftProbe = argv.includes("--skip-drift-probe") && !workSelection;
|
|
68
|
+
const forIssue = parseOptionalForIssue(argv);
|
|
69
|
+
const result = evaluate(projectRoot, { allowMissingBootstrap, skipDriftProbe, forIssue });
|
|
55
70
|
if (result.code === 0) {
|
|
56
71
|
if (!quiet) {
|
|
57
72
|
if (result.message.startsWith("⚠")) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LifecycleVisibleResult } from "../lifecycle-visible/evaluate.js";
|
|
1
2
|
import { type HostContentSurfaceSeams } from "../platform/host-content-surface.js";
|
|
2
3
|
import { type EnvironmentContext } from "../platform/shell-context.js";
|
|
3
4
|
import { type CeremonyDialInputs, type CeremonyDialSelection, type ProvisionalCeremonyEstimateHints } from "../policy/ceremony-dial.js";
|
|
@@ -117,6 +118,11 @@ export interface SessionStartOptions {
|
|
|
117
118
|
* network is enabled. Inject in tests.
|
|
118
119
|
*/
|
|
119
120
|
readonly probeScm?: (options: ProbeScmReadinessOptions) => ScmReadinessReport;
|
|
121
|
+
/**
|
|
122
|
+
* #3505: per-clone lifecycle-root visibility (ignore / skip-worktree).
|
|
123
|
+
* Warn-only; never blocks session:start. Inject in tests.
|
|
124
|
+
*/
|
|
125
|
+
readonly probeLifecycleVisible?: (projectRoot: string, runGit: GitRunner) => LifecycleVisibleResult;
|
|
120
126
|
/**
|
|
121
127
|
* #3214: ceremony dial inputs (task size × model tier × project shape).
|
|
122
128
|
* Missing fields are filled by the headless provisional classifier
|
|
@@ -3,6 +3,7 @@ import { emitSessionEvalReadback } from "../eval/readback.js";
|
|
|
3
3
|
import { bindSessionGeneration } from "../freshness/bind.js";
|
|
4
4
|
import { readLiveGeneration } from "../freshness/generation.js";
|
|
5
5
|
import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
|
|
6
|
+
import { evaluateLifecycleVisible, formatLifecycleVisibleSessionLines, } from "../lifecycle-visible/evaluate.js";
|
|
6
7
|
import { hostContentSurfaceToDict, maybeFormatHostContentSurfaceLines, } from "../platform/host-content-surface.js";
|
|
7
8
|
import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
|
|
8
9
|
import { ceremonyDialToDict, formatCeremonyDialStatusLine, mergeCeremonyDialDeferrals, resolveCeremonyDial, resolveSessionCeremonyDialInputs, } from "../policy/ceremony-dial.js";
|
|
@@ -26,8 +27,8 @@ import { ceremonyDialEvidenceToDict, collectCeremonyDialConsumerEvidence, format
|
|
|
26
27
|
import { effortBudgetToDict, maybeFormatEffortBudgetLines, resolveProductionHostEffortDescriptor, } from "./effort-budget.js";
|
|
27
28
|
import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
|
|
28
29
|
import { applyWorktreeOccupancy, } from "./occupancy.js";
|
|
29
|
-
import { runOrientationCompression, } from "./orientation-compression.js";
|
|
30
|
-
import { emitSessionStartProcessCost } from "./process-cost.js";
|
|
30
|
+
import { resolveSessionCompact, runOrientationCompression, } from "./orientation-compression.js";
|
|
31
|
+
import { emitSessionStartProcessCost, formatSessionStartCeremonyCostLine } from "./process-cost.js";
|
|
31
32
|
import { probeSessionReleaseAvailability, } from "./release-availability.js";
|
|
32
33
|
import { newRitualStatePayload, readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
33
34
|
import { timestampIso } from "./time.js";
|
|
@@ -50,6 +51,18 @@ export const QUICK_STEPS = [
|
|
|
50
51
|
"verify_tools",
|
|
51
52
|
];
|
|
52
53
|
export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
|
|
54
|
+
/** Per-clone ignore/index hide of lifecycle roots — warn-only (#3505). */
|
|
55
|
+
function pushLifecycleVisibleAdvisory(lines, projectRoot, options, runGit) {
|
|
56
|
+
try {
|
|
57
|
+
const probe = options.probeLifecycleVisible ??
|
|
58
|
+
((root, git) => evaluateLifecycleVisible({ projectRoot: root, runGit: git }));
|
|
59
|
+
const result = probe(projectRoot, runGit);
|
|
60
|
+
lines.push(...formatLifecycleVisibleSessionLines(result));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// best-effort — session:start must not abort on a clone-local advisory
|
|
64
|
+
}
|
|
65
|
+
}
|
|
53
66
|
/** Standing one-liner when coverageDebt/checkResume is non-default (#3314). */
|
|
54
67
|
function pushCoverageCheckResumeDisclosure(lines, projectRoot) {
|
|
55
68
|
try {
|
|
@@ -494,6 +507,8 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
494
507
|
lines.push(...formatScmReadinessLines(scm));
|
|
495
508
|
lines.push(...hostSurface.lines);
|
|
496
509
|
lines.push(...effortBudget.lines);
|
|
510
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
511
|
+
pushLifecycleVisibleAdvisory(lines, projectRoot, options, runGit);
|
|
497
512
|
const resultPayload = {
|
|
498
513
|
ready: true,
|
|
499
514
|
exit_code: 0,
|
|
@@ -576,6 +591,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
576
591
|
...effortBudget.lines,
|
|
577
592
|
REARM_SKIPPED_FAT_PATH_MESSAGE,
|
|
578
593
|
];
|
|
594
|
+
pushLifecycleVisibleAdvisory(lines, projectRoot, options, runGit);
|
|
579
595
|
// Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
|
|
580
596
|
const policyResult = resolvePolicy(projectRoot);
|
|
581
597
|
const policyMessage = disclosureLine(policyResult);
|
|
@@ -739,6 +755,10 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
739
755
|
optionalNetwork: false,
|
|
740
756
|
steps: stepTimings,
|
|
741
757
|
}, { projectRoot });
|
|
758
|
+
// #3508: operator-visible CLI process time. ⊗ not #3286 Later graduation input.
|
|
759
|
+
if (!resolveSessionCompact({ compact: options.compact, env: options.env })) {
|
|
760
|
+
lines.push(formatSessionStartCeremonyCostLine(REARM_CEREMONY_TIER, totalMs));
|
|
761
|
+
}
|
|
742
762
|
return {
|
|
743
763
|
code,
|
|
744
764
|
payload: {
|
|
@@ -976,6 +996,12 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
976
996
|
name: "effort_budget",
|
|
977
997
|
duration_ms: elapsedMs(effortBudgetStepStarted),
|
|
978
998
|
});
|
|
999
|
+
const lifecycleVisibleStarted = performance.now();
|
|
1000
|
+
pushLifecycleVisibleAdvisory(lines, projectRoot, options, runGit);
|
|
1001
|
+
stepTimings.push({
|
|
1002
|
+
name: "lifecycle_visible",
|
|
1003
|
+
duration_ms: elapsedMs(lifecycleVisibleStarted),
|
|
1004
|
+
});
|
|
979
1005
|
if (!quickSteps.branch_policy) {
|
|
980
1006
|
const stepStarted = performance.now();
|
|
981
1007
|
const result = resolvePolicy(projectRoot);
|
|
@@ -1461,6 +1487,10 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
1461
1487
|
optionalNetwork: allowOptionalNetwork,
|
|
1462
1488
|
steps: stepTimings,
|
|
1463
1489
|
}, { projectRoot });
|
|
1490
|
+
// #3508: operator-visible CLI process time. ⊗ not #3286 Later graduation input.
|
|
1491
|
+
if (!resolveSessionCompact({ compact: options.compact, env: options.env })) {
|
|
1492
|
+
lines.push(formatSessionStartCeremonyCostLine(COLD_CEREMONY_TIER, totalMs));
|
|
1493
|
+
}
|
|
1464
1494
|
return { code, payload: resultPayload, lines };
|
|
1465
1495
|
}
|
|
1466
1496
|
export { ritualStatePath };
|
|
@@ -6,6 +6,26 @@ export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from
|
|
|
6
6
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
7
7
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
8
8
|
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<GatedStepName, readonly string[]>>;
|
|
9
|
+
/** Explicit skip flag threaded through the gated `cache_fresh` argv (#3507). */
|
|
10
|
+
export declare const SKIP_DRIFT_PROBE_FLAG = "--skip-drift-probe";
|
|
11
|
+
/** Explicit work-selection flag threaded through the gated `cache_fresh` argv (#3507). */
|
|
12
|
+
export declare const WORK_SELECTION_FLAG = "--work-selection";
|
|
13
|
+
/** Distinct ritual-state field — never `deferred_reason` (#3507). */
|
|
14
|
+
export declare const DRIFT_PROBE_SKIPPED_NO_WORK_SELECTION = "skipped-no-work-selection";
|
|
15
|
+
export type WorkSelectionKind = "none" | "active-story" | "triage-queue" | "ordered-plan" | "for-issue" | "explicit-flag";
|
|
16
|
+
export interface WorkSelectionSignal {
|
|
17
|
+
readonly inPlay: boolean;
|
|
18
|
+
readonly kind: WorkSelectionKind;
|
|
19
|
+
}
|
|
20
|
+
export type DetectWorkSelection = (projectRoot: string) => WorkSelectionSignal;
|
|
21
|
+
/**
|
|
22
|
+
* Default work-selection detector for the gated `cache_fresh` seam (#3507).
|
|
23
|
+
* Active story xBRIEF (scope:activate residue), unexhausted ordered-plan /
|
|
24
|
+
* triage sequence. `--for-issue` is a positive evaluate override, not this
|
|
25
|
+
* detector — the gated argv never carries it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectWorkSelection(projectRoot: string): WorkSelectionSignal;
|
|
28
|
+
export declare function resolveGatedEntrypointCommand(stepName: GatedStepName, projectRoot: string, detect?: DetectWorkSelection): string[];
|
|
9
29
|
export interface VerifyResult {
|
|
10
30
|
readonly code: number;
|
|
11
31
|
readonly message: string;
|
|
@@ -30,7 +50,8 @@ export type RitualRunner = (command: readonly string[], projectRoot: string) =>
|
|
|
30
50
|
export declare function formatRitualRecoveryInstruction(tier?: SessionCeremonyTier): string;
|
|
31
51
|
/**
|
|
32
52
|
* Sanctioned audited defer copy for gated `cache_fresh` failure (#3506 / #3507).
|
|
33
|
-
*
|
|
53
|
+
* Independent of the #3507 work-selection argv seam — defer is still the
|
|
54
|
+
* operator-audited escape, not the drift-probe skip.
|
|
34
55
|
*/
|
|
35
56
|
export declare function formatCacheFreshDeferSoftPath(): string;
|
|
36
57
|
export interface VerifySessionRitualOptions {
|
|
@@ -58,6 +79,8 @@ export interface VerifySessionRitualOptions {
|
|
|
58
79
|
* {@link checkActiveCliAgainstTarget}.
|
|
59
80
|
*/
|
|
60
81
|
readonly checkActiveCli?: (targetVersion: string | null, seams?: ActiveCliCheckSeams) => ActiveCliCheckResult;
|
|
82
|
+
/** Injectable work-selection detector for gated `cache_fresh` (#3507). */
|
|
83
|
+
readonly detectWorkSelection?: DetectWorkSelection;
|
|
61
84
|
}
|
|
62
85
|
export interface InspectSessionRitualOptions {
|
|
63
86
|
readonly tier?: "quick" | "gated";
|