@deftai/directive-core 0.99.0 → 0.101.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/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +241 -30
- package/dist/authz/decompose-apply.d.ts +81 -0
- package/dist/authz/decompose-apply.js +377 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +130 -79
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +36 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +96 -11
- package/dist/policy/ceremony-dial.js +250 -23
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/render/framework-commands.js +4 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +110 -0
- package/dist/scope/acceptance-evidence.js +422 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +134 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +136 -0
- package/dist/session/effort-budget.js +352 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +7 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +31 -0
- package/dist/session/session-start.js +228 -2
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +15 -3
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - CLEAN + bot merge allowed + --merge → optional wait-mergeable-and-merge
|
|
8
8
|
* - Missing grants → fail closed BLOCKED
|
|
9
9
|
*/
|
|
10
|
+
import { type EnforceMergeApprovalHeadInput, type MergeApprovalHeadResult } from "../policy/merge-approval-head.js";
|
|
10
11
|
import { evaluateAgentMerge } from "../policy/require-human-merge.js";
|
|
11
12
|
import type { WatchOptions, WatchResult } from "../pr-watch/types.js";
|
|
12
13
|
import { type PrFinishLoopResult } from "./types.js";
|
|
@@ -20,13 +21,23 @@ export interface PrFinishLoopOptions {
|
|
|
20
21
|
/** Attempt merge when CLEAN and policy allows (default false). */
|
|
21
22
|
readonly merge?: boolean;
|
|
22
23
|
readonly skipGrantGate?: boolean;
|
|
24
|
+
/** Skip #3235 head-bound plan:approved gate (tests). */
|
|
25
|
+
readonly skipMergeApprovalHeadGate?: boolean;
|
|
23
26
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
24
27
|
readonly now?: Date;
|
|
25
28
|
/** Inject watch for tests. */
|
|
26
29
|
readonly watchFn?: (prNumber: number, repo: string | null, options?: WatchOptions) => WatchResult;
|
|
27
|
-
/**
|
|
28
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Inject merge cascade for tests; returns exit code.
|
|
32
|
+
* Optional `matchHeadCommit` pins the merge to the gated head (#3235).
|
|
33
|
+
*/
|
|
34
|
+
readonly mergeFn?: (prNumber: number, repo: string | null, options?: {
|
|
35
|
+
readonly matchHeadCommit?: string | null;
|
|
36
|
+
}) => number;
|
|
29
37
|
readonly agentMergeFn?: typeof evaluateAgentMerge;
|
|
38
|
+
readonly mergeApprovalHeadFn?: (input: EnforceMergeApprovalHeadInput) => MergeApprovalHeadResult;
|
|
39
|
+
/** Inject live HEAD fetch (tests); defaults to REST pulls head.sha. */
|
|
40
|
+
readonly fetchPrHeadShaFn?: (prNumber: number, repo: string | null) => string | null;
|
|
30
41
|
readonly writeProgress?: boolean;
|
|
31
42
|
readonly iteration?: number;
|
|
32
43
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - CLEAN + bot merge allowed + --merge → optional wait-mergeable-and-merge
|
|
8
8
|
* - Missing grants → fail closed BLOCKED
|
|
9
9
|
*/
|
|
10
|
+
import { disablePullRequestAutoMerge, enforceMergeApprovalHead, fetchPrHeadShaRest, } from "../policy/merge-approval-head.js";
|
|
10
11
|
import { evaluateAgentMerge } from "../policy/require-human-merge.js";
|
|
11
12
|
import { EXIT_CLEAN, EXIT_NEW_P0_P1, EXIT_TERMINAL_ERROR, VERDICT_CLEAN, VERDICT_NEW_P0_P1, } from "../pr-watch/constants.js";
|
|
12
13
|
import { watch } from "../pr-watch/watch.js";
|
|
@@ -70,7 +71,9 @@ export function runPrFinishLoop(options) {
|
|
|
70
71
|
}
|
|
71
72
|
// --- pr:watch ---
|
|
72
73
|
const watchFn = options.watchFn ?? watch;
|
|
73
|
-
|
|
74
|
+
// Prefer explicit repo; GH_REPO next. Repo is required for head-bound approval
|
|
75
|
+
// scoping when plan:approved records exist (#3235 cross-repo collision).
|
|
76
|
+
const repo = options.repo ?? process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? null;
|
|
74
77
|
let watchResult;
|
|
75
78
|
try {
|
|
76
79
|
watchResult = watchFn(prNumber, repo, {
|
|
@@ -129,6 +132,61 @@ export function runPrFinishLoop(options) {
|
|
|
129
132
|
grantId: null,
|
|
130
133
|
};
|
|
131
134
|
}
|
|
135
|
+
// #3235: head-bound plan:approved vs LIVE PR HEAD (before merge path).
|
|
136
|
+
// Always re-fetch HEAD. Never fall back to the watch snapshot: a failed live
|
|
137
|
+
// read + snapshot A would retain auto-merge while GitHub head is B.
|
|
138
|
+
if (options.skipMergeApprovalHeadGate !== true) {
|
|
139
|
+
const fetchHead = options.fetchPrHeadShaFn ?? fetchPrHeadShaRest;
|
|
140
|
+
const liveHead = fetchHead(prNumber, repo);
|
|
141
|
+
if (liveHead === null || liveHead.trim() === "") {
|
|
142
|
+
disablePullRequestAutoMerge(prNumber, repo);
|
|
143
|
+
const message = `pr:finish-loop ACTION_REQUIRED on PR #${prNumber}: cannot read live HEAD ` +
|
|
144
|
+
"after CLEAN; auto-merge disabled (fail closed, #3235).";
|
|
145
|
+
log("merge", "stale-merge-approval", message);
|
|
146
|
+
return {
|
|
147
|
+
exitCode: EXIT_ACTION_REQUIRED,
|
|
148
|
+
haltReason: "stale-merge-approval",
|
|
149
|
+
message,
|
|
150
|
+
prNumber,
|
|
151
|
+
watchVerdict: VERDICT_CLEAN,
|
|
152
|
+
mergeAttempted: false,
|
|
153
|
+
mergeSkippedReason: "stale-merge-approval",
|
|
154
|
+
grantId: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const headGateFn = options.mergeApprovalHeadFn ?? enforceMergeApprovalHead;
|
|
158
|
+
const headGate = headGateFn({
|
|
159
|
+
prNumber,
|
|
160
|
+
repo,
|
|
161
|
+
projectRoot,
|
|
162
|
+
currentHeadSha: liveHead,
|
|
163
|
+
disableAutoMergeOnDeny: true,
|
|
164
|
+
});
|
|
165
|
+
if (!headGate.allowed) {
|
|
166
|
+
const message = [
|
|
167
|
+
`pr:finish-loop ACTION_REQUIRED on PR #${prNumber}: stale or unbound merge approval.`,
|
|
168
|
+
headGate.message,
|
|
169
|
+
headGate.recovery ?? "",
|
|
170
|
+
]
|
|
171
|
+
.filter((line) => line.length > 0)
|
|
172
|
+
.join("\n");
|
|
173
|
+
log("merge", "stale-merge-approval", message, {
|
|
174
|
+
approved_head_sha: headGate.approved_head_sha,
|
|
175
|
+
current_head_sha: headGate.current_head_sha,
|
|
176
|
+
auto_merge_disabled: headGate.auto_merge_disabled,
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
exitCode: EXIT_ACTION_REQUIRED,
|
|
180
|
+
haltReason: "stale-merge-approval",
|
|
181
|
+
message,
|
|
182
|
+
prNumber,
|
|
183
|
+
watchVerdict: VERDICT_CLEAN,
|
|
184
|
+
mergeAttempted: false,
|
|
185
|
+
mergeSkippedReason: "stale-merge-approval",
|
|
186
|
+
grantId: null,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
132
190
|
// CLEAN path
|
|
133
191
|
if (options.merge !== true) {
|
|
134
192
|
const message = `pr:finish-loop CLEAN on PR #${prNumber}. Merge not requested ` +
|
|
@@ -165,12 +223,62 @@ export function runPrFinishLoop(options) {
|
|
|
165
223
|
grantId: null,
|
|
166
224
|
};
|
|
167
225
|
}
|
|
168
|
-
// Optional merge when policy allows
|
|
226
|
+
// Optional merge when policy allows — re-fetch live HEAD (not watch snapshot)
|
|
227
|
+
// and pin matchHeadCommit (#3235 TOCTOU).
|
|
169
228
|
if (options.mergeFn !== undefined) {
|
|
170
|
-
const
|
|
229
|
+
const fetchHead = options.fetchPrHeadShaFn ?? fetchPrHeadShaRest;
|
|
230
|
+
const liveHead = fetchHead(prNumber, repo);
|
|
231
|
+
if (options.skipMergeApprovalHeadGate !== true) {
|
|
232
|
+
if (liveHead === null || liveHead.trim() === "") {
|
|
233
|
+
disablePullRequestAutoMerge(prNumber, repo);
|
|
234
|
+
const message = `pr:finish-loop ACTION_REQUIRED on PR #${prNumber}: cannot read live HEAD ` +
|
|
235
|
+
"before merge; auto-merge disabled (fail closed, #3235).";
|
|
236
|
+
log("merge", "stale-merge-approval", message);
|
|
237
|
+
return {
|
|
238
|
+
exitCode: EXIT_ACTION_REQUIRED,
|
|
239
|
+
haltReason: "stale-merge-approval",
|
|
240
|
+
message,
|
|
241
|
+
prNumber,
|
|
242
|
+
watchVerdict: VERDICT_CLEAN,
|
|
243
|
+
mergeAttempted: false,
|
|
244
|
+
mergeSkippedReason: "stale-merge-approval",
|
|
245
|
+
grantId: null,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const headGateFn = options.mergeApprovalHeadFn ?? enforceMergeApprovalHead;
|
|
249
|
+
const recheck = headGateFn({
|
|
250
|
+
prNumber,
|
|
251
|
+
repo,
|
|
252
|
+
projectRoot,
|
|
253
|
+
currentHeadSha: liveHead,
|
|
254
|
+
disableAutoMergeOnDeny: true,
|
|
255
|
+
});
|
|
256
|
+
if (!recheck.allowed) {
|
|
257
|
+
const message = [
|
|
258
|
+
`pr:finish-loop ACTION_REQUIRED on PR #${prNumber}: stale merge approval at merge time.`,
|
|
259
|
+
recheck.message,
|
|
260
|
+
recheck.recovery ?? "",
|
|
261
|
+
]
|
|
262
|
+
.filter((line) => line.length > 0)
|
|
263
|
+
.join("\n");
|
|
264
|
+
log("merge", "stale-merge-approval", message);
|
|
265
|
+
return {
|
|
266
|
+
exitCode: EXIT_ACTION_REQUIRED,
|
|
267
|
+
haltReason: "stale-merge-approval",
|
|
268
|
+
message,
|
|
269
|
+
prNumber,
|
|
270
|
+
watchVerdict: VERDICT_CLEAN,
|
|
271
|
+
mergeAttempted: false,
|
|
272
|
+
mergeSkippedReason: "stale-merge-approval",
|
|
273
|
+
grantId: null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const pinnedHead = liveHead;
|
|
278
|
+
const rc = options.mergeFn(prNumber, repo, { matchHeadCommit: pinnedHead });
|
|
171
279
|
if (rc === 0) {
|
|
172
280
|
const message = `pr:finish-loop MERGED PR #${prNumber}`;
|
|
173
|
-
log("merge", "merged", message);
|
|
281
|
+
log("merge", "merged", message, { matchHeadCommit: pinnedHead });
|
|
174
282
|
return {
|
|
175
283
|
exitCode: EXIT_OK,
|
|
176
284
|
haltReason: "merged",
|
|
@@ -182,7 +290,10 @@ export function runPrFinishLoop(options) {
|
|
|
182
290
|
grantId: null,
|
|
183
291
|
};
|
|
184
292
|
}
|
|
185
|
-
|
|
293
|
+
// Pin mismatch / merge fail: revoke auto-merge so unapproved head cannot land.
|
|
294
|
+
disablePullRequestAutoMerge(prNumber, repo);
|
|
295
|
+
const message = `pr:finish-loop merge attempt failed exit=${rc} for PR #${prNumber}; ` +
|
|
296
|
+
"disabled auto-merge after pinned-merge failure (#3235).";
|
|
186
297
|
log("merge", "error", message);
|
|
187
298
|
return {
|
|
188
299
|
exitCode: EXIT_BLOCKED,
|
|
@@ -8,7 +8,7 @@ export declare const EXIT_OK = 0;
|
|
|
8
8
|
export declare const EXIT_ACTION_REQUIRED = 1;
|
|
9
9
|
/** BLOCKED (grant/gate), ERRORED, TIMEOUT, config. */
|
|
10
10
|
export declare const EXIT_BLOCKED = 2;
|
|
11
|
-
export type FinishLoopHaltReason = "empty-queue" | "grant-missing" | "grant-expired" | "grant-deny" | "gate-deny" | "max-iterations" | "require-human-merge" | "address-findings" | "agent-implement" | "clean" | "merged" | "error";
|
|
11
|
+
export type FinishLoopHaltReason = "empty-queue" | "grant-missing" | "grant-expired" | "grant-deny" | "gate-deny" | "max-iterations" | "require-human-merge" | "stale-merge-approval" | "address-findings" | "agent-implement" | "clean" | "merged" | "error";
|
|
12
12
|
export type FinishLoopPhase = "gate" | "queue-scan" | "implement" | "pr-open" | "pr-watch" | "address" | "merge" | "halt";
|
|
13
13
|
export interface FinishLoopProgressLine {
|
|
14
14
|
readonly schemaVersion: 1;
|
|
@@ -142,11 +142,15 @@ export declare function decideHook(input: HookDispatchInput, seams?: HookPolicyS
|
|
|
142
142
|
/**
|
|
143
143
|
* Render host-facing hook output.
|
|
144
144
|
*
|
|
145
|
-
* Cursor deposits use `failClosed: true
|
|
146
|
-
*
|
|
147
|
-
* `
|
|
148
|
-
*
|
|
149
|
-
*
|
|
145
|
+
* Cursor deposits use `failClosed: true` with a tool.before timeout above the
|
|
146
|
+
* gated-ritual / live agent-hook readiness budget
|
|
147
|
+
* (`CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS` in init-deposit/agent-hooks; #3246).
|
|
148
|
+
* Cursor treats empty/null stdout (or a host timeout kill) as a hook failure
|
|
149
|
+
* and blocks the tool — so Cursor allows must emit explicit
|
|
150
|
+
* `{"permission":"allow"}` within the deposit timeout. Other hosts keep empty
|
|
151
|
+
* allow so the host permission flow is unchanged — except session.start /
|
|
152
|
+
* session.compact soft re-bind injection (#3171), which surfaces checklist
|
|
153
|
+
* text without a write tool.
|
|
150
154
|
*
|
|
151
155
|
* Cursor stdout always includes `code` (stable machine-readable decision code)
|
|
152
156
|
* so agents can distinguish policy denials from host-integration failures
|
package/dist/hooks/dispatcher.js
CHANGED
|
@@ -184,10 +184,10 @@ export function isAllowlistedAssistScratchPath(projectRoot, targetPath) {
|
|
|
184
184
|
export function isAssistScratchWrite(projectRoot, targetPath, payload, environ = process.env) {
|
|
185
185
|
if (!isAllowlistedAssistScratchPath(projectRoot, targetPath))
|
|
186
186
|
return false;
|
|
187
|
-
// Structural classification only — compose with #3080 ephemeral markers.
|
|
187
|
+
// Structural classification only — compose with #3080 / #3259 ephemeral markers.
|
|
188
188
|
if (isAssistPosture(payload, environ))
|
|
189
189
|
return true;
|
|
190
|
-
if (isEphemeralSpawn(payload))
|
|
190
|
+
if (isEphemeralSpawn(payload, environ))
|
|
191
191
|
return true;
|
|
192
192
|
return false;
|
|
193
193
|
}
|
|
@@ -656,14 +656,18 @@ function inspectMutationGates(input, toolName, seams, options) {
|
|
|
656
656
|
if (!outsideRoot || isSpawnTool(toolName)) {
|
|
657
657
|
let proposedPathHint;
|
|
658
658
|
if (isSpawnTool(toolName)) {
|
|
659
|
-
// Multi-path recovery for implement-class spawns (#3080 AC4).
|
|
659
|
+
// Multi-path recovery for implement-class spawns (#3080 AC4 / #3259 honesty).
|
|
660
|
+
// Structural markers only — free-text prompt brackets are not sufficient.
|
|
660
661
|
proposedPathHint =
|
|
661
662
|
" Recovery: (1) Product implementation — run `deft scope:activate -- <path>` " +
|
|
662
663
|
"for the approved xBRIEF, then re-run the pre-start_agent gate stack. " +
|
|
663
|
-
"(2) Read-only research — spawn with `subagent_type`/`worker_role` explore. " +
|
|
664
|
-
"(3) Ephemeral docs/
|
|
665
|
-
"
|
|
666
|
-
"
|
|
664
|
+
"(2) Read-only research — spawn with structural `subagent_type`/`worker_role` explore. " +
|
|
665
|
+
"(3) Ephemeral docs/local-dev — set structural tool fields " +
|
|
666
|
+
"`worker_role`/`subagent_type` ∈ {ephemeral, docs, assist} (hosts that support them), " +
|
|
667
|
+
"or set session assist (`DEFT_SESSION_POSTURE=assist` or `DEFT_HOOK_ASSIST=1`), " +
|
|
668
|
+
"or run local-dev Shell (`docker compose` / `pnpm dev`) in the parent without a " +
|
|
669
|
+
"lifecycle story. Free-text markers such as `[worker_role: ephemeral]` in the " +
|
|
670
|
+
"prompt are NOT sufficient. Do not invent a fake scope only to satisfy this gate.";
|
|
667
671
|
}
|
|
668
672
|
else if (options.proposedLifecycleExempt &&
|
|
669
673
|
relTarget !== null &&
|
|
@@ -895,9 +899,9 @@ export function decideHook(input, seams = {}) {
|
|
|
895
899
|
scopePath: null,
|
|
896
900
|
};
|
|
897
901
|
}
|
|
898
|
-
// Ephemeral/docs/assist:
|
|
899
|
-
// Does not authorize push/merge/deploy —
|
|
900
|
-
if (isEphemeralSpawn(input.payload)) {
|
|
902
|
+
// Ephemeral/docs/assist (+ session assist env #3259): non-lifecycle spawn;
|
|
903
|
+
// no active xBRIEF. Does not authorize push/merge/deploy — shell/MCP matchers.
|
|
904
|
+
if (isEphemeralSpawn(input.payload, environ)) {
|
|
901
905
|
return {
|
|
902
906
|
verdict: "allow",
|
|
903
907
|
code: "spawn-ephemeral-ready",
|
|
@@ -951,11 +955,15 @@ function softAgentsRebindWireText(decision) {
|
|
|
951
955
|
/**
|
|
952
956
|
* Render host-facing hook output.
|
|
953
957
|
*
|
|
954
|
-
* Cursor deposits use `failClosed: true
|
|
955
|
-
*
|
|
956
|
-
* `
|
|
957
|
-
*
|
|
958
|
-
*
|
|
958
|
+
* Cursor deposits use `failClosed: true` with a tool.before timeout above the
|
|
959
|
+
* gated-ritual / live agent-hook readiness budget
|
|
960
|
+
* (`CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS` in init-deposit/agent-hooks; #3246).
|
|
961
|
+
* Cursor treats empty/null stdout (or a host timeout kill) as a hook failure
|
|
962
|
+
* and blocks the tool — so Cursor allows must emit explicit
|
|
963
|
+
* `{"permission":"allow"}` within the deposit timeout. Other hosts keep empty
|
|
964
|
+
* allow so the host permission flow is unchanged — except session.start /
|
|
965
|
+
* session.compact soft re-bind injection (#3171), which surfaces checklist
|
|
966
|
+
* text without a write tool.
|
|
959
967
|
*
|
|
960
968
|
* Cursor stdout always includes `code` (stable machine-readable decision code)
|
|
961
969
|
* so agents can distinguish policy denials from host-integration failures
|
package/dist/hooks/readonly.d.ts
CHANGED
|
@@ -13,9 +13,12 @@ export declare function isExploreSpawn(payload: unknown): boolean;
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function isAssistPosture(payload: unknown, environ?: NodeJS.ProcessEnv): boolean;
|
|
15
15
|
/**
|
|
16
|
-
* Ephemeral / assist / docs spawns skip active-xBRIEF implementation gates (#3080).
|
|
16
|
+
* Ephemeral / assist / docs spawns skip active-xBRIEF implementation gates (#3080 / #3259).
|
|
17
17
|
* True only with an explicit allowlisted marker. Absent marker → false (fail closed).
|
|
18
|
-
*
|
|
18
|
+
* Markers: structural `worker_role`/`subagent_type` ∈ {ephemeral,docs,assist}, OR
|
|
19
|
+
* session assist env (`DEFT_SESSION_POSTURE` assist-set / `DEFT_HOOK_ASSIST=1`) for spawn.
|
|
20
|
+
* Free-text prompt strings are never classified. When a marker conflicts with implement
|
|
21
|
+
* envelope signals, implement wins.
|
|
19
22
|
*/
|
|
20
|
-
export declare function isEphemeralSpawn(payload: unknown): boolean;
|
|
23
|
+
export declare function isEphemeralSpawn(payload: unknown, environ?: NodeJS.ProcessEnv): boolean;
|
|
21
24
|
//# sourceMappingURL=readonly.d.ts.map
|
package/dist/hooks/readonly.js
CHANGED
|
@@ -123,8 +123,8 @@ export function isAssistPosture(payload, environ = process.env) {
|
|
|
123
123
|
if (posture !== null && ASSIST_POSTURE_MARKERS.has(normalizePostureToken(posture))) {
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
|
-
// Shared taxonomy with #3080 ephemeral spawn markers.
|
|
127
|
-
if (isEphemeralSpawn(payload))
|
|
126
|
+
// Shared taxonomy with #3080 / #3259 ephemeral spawn markers.
|
|
127
|
+
if (isEphemeralSpawn(payload, environ))
|
|
128
128
|
return true;
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
@@ -176,34 +176,53 @@ function hasImplementConflictSignal(toolInput, input) {
|
|
|
176
176
|
return false;
|
|
177
177
|
}
|
|
178
178
|
/**
|
|
179
|
-
*
|
|
179
|
+
* Session assist env tokens that count as structural ephemeral markers for spawn (#3259).
|
|
180
|
+
* Reuses #1802 ASSIST_POSTURE_MARKERS / DEFT_HOOK_ASSIST — not free-text NLP.
|
|
181
|
+
*/
|
|
182
|
+
function hasSessionAssistEphemeralEnv(environ) {
|
|
183
|
+
const envPosture = (environ[ASSIST_SESSION_POSTURE_ENV] ?? "").trim();
|
|
184
|
+
if (envPosture.length > 0 && ASSIST_POSTURE_MARKERS.has(normalizePostureToken(envPosture))) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return envTruthy(environ, "DEFT_HOOK_ASSIST");
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Ephemeral / assist / docs spawns skip active-xBRIEF implementation gates (#3080 / #3259).
|
|
180
191
|
* True only with an explicit allowlisted marker. Absent marker → false (fail closed).
|
|
181
|
-
*
|
|
192
|
+
* Markers: structural `worker_role`/`subagent_type` ∈ {ephemeral,docs,assist}, OR
|
|
193
|
+
* session assist env (`DEFT_SESSION_POSTURE` assist-set / `DEFT_HOOK_ASSIST=1`) for spawn.
|
|
194
|
+
* Free-text prompt strings are never classified. When a marker conflicts with implement
|
|
195
|
+
* envelope signals, implement wins.
|
|
182
196
|
*/
|
|
183
|
-
export function isEphemeralSpawn(payload) {
|
|
197
|
+
export function isEphemeralSpawn(payload, environ = process.env) {
|
|
184
198
|
const input = record(payload);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
const toolInput = input !== null ? (toolInputRecord(input) ?? input) : null;
|
|
200
|
+
let hasMarker = false;
|
|
201
|
+
if (input !== null && toolInput !== null) {
|
|
202
|
+
const subagentType = fieldString(toolInput, "subagent_type") ??
|
|
203
|
+
fieldString(toolInput, "subagentType") ??
|
|
204
|
+
fieldString(input, "subagent_type") ??
|
|
205
|
+
fieldString(input, "subagentType");
|
|
206
|
+
const workerRole = fieldString(toolInput, "worker_role") ??
|
|
207
|
+
fieldString(toolInput, "workerRole") ??
|
|
208
|
+
fieldString(input, "worker_role") ??
|
|
209
|
+
fieldString(input, "workerRole");
|
|
210
|
+
if ((workerRole !== null && EPHEMERAL_ROLE_MARKERS.has(workerRole.toLowerCase())) ||
|
|
211
|
+
(subagentType !== null && EPHEMERAL_ROLE_MARKERS.has(subagentType.toLowerCase()))) {
|
|
212
|
+
hasMarker = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// #3259 D1-B: session assist env is a structural ephemeral marker for spawn tools.
|
|
216
|
+
// Call sites gate "spawn only"; this helper stays pure classification.
|
|
217
|
+
if (!hasMarker && hasSessionAssistEphemeralEnv(environ)) {
|
|
218
|
+
hasMarker = true;
|
|
219
|
+
}
|
|
220
|
+
if (!hasMarker)
|
|
203
221
|
return false;
|
|
204
|
-
// Implement signals win over ephemeral markers (fail closed).
|
|
205
|
-
if (hasImplementConflictSignal(toolInput, input))
|
|
222
|
+
// Implement signals win over ephemeral markers including session assist env (fail closed).
|
|
223
|
+
if (input !== null && toolInput !== null && hasImplementConflictSignal(toolInput, input)) {
|
|
206
224
|
return false;
|
|
225
|
+
}
|
|
207
226
|
return true;
|
|
208
227
|
}
|
|
209
228
|
//# sourceMappingURL=readonly.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export * as intake from "./intake/index.js";
|
|
|
35
35
|
export * as layout from "./layout/index.js";
|
|
36
36
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
37
37
|
export * as lifecycle from "./lifecycle/index.js";
|
|
38
|
+
export * as literalAcceptance from "./literal-acceptance/index.js";
|
|
38
39
|
export * as metrics from "./metrics/index.js";
|
|
39
40
|
export * as orchestration from "./orchestration/index.js";
|
|
40
41
|
export * as packs from "./packs/index.js";
|
|
@@ -47,12 +48,14 @@ export * as prMonitor from "./pr-monitor/index.js";
|
|
|
47
48
|
export * as prProtectedIssues from "./pr-protected-issues/index.js";
|
|
48
49
|
export * as prWaitMergeable from "./pr-wait-mergeable/index.js";
|
|
49
50
|
export * as preflight from "./preflight/index.js";
|
|
51
|
+
export * as productFirstDoneGate from "./product-first-done-gate/index.js";
|
|
50
52
|
export * as release from "./release/index.js";
|
|
51
53
|
export * as releaseE2e from "./release-e2e/index.js";
|
|
52
54
|
export * as releasePublish from "./release-publish/index.js";
|
|
53
55
|
export * as releaseRollback from "./release-rollback/index.js";
|
|
54
56
|
export * as render from "./render/index.js";
|
|
55
57
|
export * as resolution from "./resolution/index.js";
|
|
58
|
+
export * as runSummary from "./run-summary/index.js";
|
|
56
59
|
export * as scm from "./scm/index.js";
|
|
57
60
|
export * as scope from "./scope/index.js";
|
|
58
61
|
export * as scopeProvenance from "./scope-provenance/index.js";
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ export * as intake from "./intake/index.js";
|
|
|
36
36
|
export * as layout from "./layout/index.js";
|
|
37
37
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
38
38
|
export * as lifecycle from "./lifecycle/index.js";
|
|
39
|
+
export * as literalAcceptance from "./literal-acceptance/index.js";
|
|
39
40
|
export * as metrics from "./metrics/index.js";
|
|
40
41
|
export * as orchestration from "./orchestration/index.js";
|
|
41
42
|
export * as packs from "./packs/index.js";
|
|
@@ -48,12 +49,14 @@ export * as prMonitor from "./pr-monitor/index.js";
|
|
|
48
49
|
export * as prProtectedIssues from "./pr-protected-issues/index.js";
|
|
49
50
|
export * as prWaitMergeable from "./pr-wait-mergeable/index.js";
|
|
50
51
|
export * as preflight from "./preflight/index.js";
|
|
52
|
+
export * as productFirstDoneGate from "./product-first-done-gate/index.js";
|
|
51
53
|
export * as release from "./release/index.js";
|
|
52
54
|
export * as releaseE2e from "./release-e2e/index.js";
|
|
53
55
|
export * as releasePublish from "./release-publish/index.js";
|
|
54
56
|
export * as releaseRollback from "./release-rollback/index.js";
|
|
55
57
|
export * as render from "./render/index.js";
|
|
56
58
|
export * as resolution from "./resolution/index.js";
|
|
59
|
+
export * as runSummary from "./run-summary/index.js";
|
|
57
60
|
export * as scm from "./scm/index.js";
|
|
58
61
|
export * as scope from "./scope/index.js";
|
|
59
62
|
export * as scopeProvenance from "./scope-provenance/index.js";
|
|
@@ -5,6 +5,26 @@ export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_
|
|
|
5
5
|
export declare const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
|
|
6
6
|
export declare const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
7
7
|
export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json", ".codex/hooks.json"];
|
|
8
|
+
/**
|
|
9
|
+
* Cursor session.start / session.compact deposit timeout (seconds).
|
|
10
|
+
* Lightweight ceremony paths — keep tight so stalled hooks fail fast.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
|
|
13
|
+
/**
|
|
14
|
+
* Cursor preToolUse (tool.before) deposit timeout (seconds).
|
|
15
|
+
*
|
|
16
|
+
* Mutation tool.before runs `inspectMutationGates` → gated
|
|
17
|
+
* `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
|
|
18
|
+
* every boundary. Live readiness alone has a multi-host fixture ceiling of
|
|
19
|
+
* ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
|
|
20
|
+
* default of 5s was below that budget. Under Cursor `failClosed: true`, a
|
|
21
|
+
* host timeout kill surfaces as opaque exit-1 with no Directive decision
|
|
22
|
+
* code (#3246 / related #2864). Keep this above the readiness ceiling plus
|
|
23
|
+
* dispatch overhead so allow/deny can render within the host budget.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS = 30;
|
|
26
|
+
/** Nested Claude/Grok/Codex command-hook default timeout (seconds). */
|
|
27
|
+
export declare const NESTED_HOOK_TIMEOUT_SECONDS = 5;
|
|
8
28
|
export type AgentHookPath = (typeof AGENT_HOOK_PATHS)[number];
|
|
9
29
|
export type AgentHookRegistrationStatus = "healthy" | "disabled" | "missing" | "drifted";
|
|
10
30
|
/** Whether the host receives a compact/resume hook deposit (#2113). */
|
|
@@ -13,6 +13,26 @@ export const AGENT_HOOK_PATHS = [
|
|
|
13
13
|
".cursor/hooks.json",
|
|
14
14
|
".codex/hooks.json",
|
|
15
15
|
];
|
|
16
|
+
/**
|
|
17
|
+
* Cursor session.start / session.compact deposit timeout (seconds).
|
|
18
|
+
* Lightweight ceremony paths — keep tight so stalled hooks fail fast.
|
|
19
|
+
*/
|
|
20
|
+
export const CURSOR_SESSION_HOOK_TIMEOUT_SECONDS = 5;
|
|
21
|
+
/**
|
|
22
|
+
* Cursor preToolUse (tool.before) deposit timeout (seconds).
|
|
23
|
+
*
|
|
24
|
+
* Mutation tool.before runs `inspectMutationGates` → gated
|
|
25
|
+
* `verifySessionRitual`, which re-runs non-cacheable agent-hook readiness on
|
|
26
|
+
* every boundary. Live readiness alone has a multi-host fixture ceiling of
|
|
27
|
+
* ~12s (`content/contracts/agent-hook-readiness.md`); the historical deposit
|
|
28
|
+
* default of 5s was below that budget. Under Cursor `failClosed: true`, a
|
|
29
|
+
* host timeout kill surfaces as opaque exit-1 with no Directive decision
|
|
30
|
+
* code (#3246 / related #2864). Keep this above the readiness ceiling plus
|
|
31
|
+
* dispatch overhead so allow/deny can render within the host budget.
|
|
32
|
+
*/
|
|
33
|
+
export const CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS = 30;
|
|
34
|
+
/** Nested Claude/Grok/Codex command-hook default timeout (seconds). */
|
|
35
|
+
export const NESTED_HOOK_TIMEOUT_SECONDS = 5;
|
|
16
36
|
function object(value) {
|
|
17
37
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
18
38
|
? value
|
|
@@ -90,7 +110,7 @@ function nestedGroup(host, event, matcher) {
|
|
|
90
110
|
{
|
|
91
111
|
type: "command",
|
|
92
112
|
command: command(host, event),
|
|
93
|
-
timeout:
|
|
113
|
+
timeout: NESTED_HOOK_TIMEOUT_SECONDS,
|
|
94
114
|
},
|
|
95
115
|
],
|
|
96
116
|
};
|
|
@@ -154,35 +174,47 @@ function mergeCursorConfig(config, path) {
|
|
|
154
174
|
const session = eventArray(hooks, "sessionStart", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
155
175
|
const preTool = eventArray(hooks, "preToolUse", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
156
176
|
const preCompact = eventArray(hooks, "preCompact", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
157
|
-
hooks.sessionStart = [
|
|
177
|
+
hooks.sessionStart = [
|
|
178
|
+
...session,
|
|
179
|
+
{
|
|
180
|
+
command: command("cursor", "session.start"),
|
|
181
|
+
timeout: CURSOR_SESSION_HOOK_TIMEOUT_SECONDS,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
158
184
|
hooks.preToolUse = [
|
|
159
185
|
...preTool,
|
|
160
186
|
{
|
|
161
187
|
command: command("cursor", "tool.before"),
|
|
162
188
|
matcher: DIRECT_WRITE_HOOK_MATCHER,
|
|
163
189
|
failClosed: true,
|
|
164
|
-
timeout:
|
|
190
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
165
191
|
},
|
|
166
192
|
{
|
|
167
193
|
command: command("cursor", "tool.before"),
|
|
168
194
|
matcher: SPAWN_HOOK_MATCHER,
|
|
169
195
|
failClosed: true,
|
|
170
|
-
timeout:
|
|
196
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
171
197
|
},
|
|
172
198
|
{
|
|
173
199
|
command: command("cursor", "tool.before"),
|
|
174
200
|
matcher: SHELL_HOOK_MATCHER,
|
|
175
201
|
failClosed: true,
|
|
176
|
-
timeout:
|
|
202
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
177
203
|
},
|
|
178
204
|
{
|
|
179
205
|
command: command("cursor", "tool.before"),
|
|
180
206
|
matcher: MCP_HOOK_MATCHER,
|
|
181
207
|
failClosed: true,
|
|
182
|
-
timeout:
|
|
208
|
+
timeout: CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS,
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
hooks.preCompact = [
|
|
212
|
+
...preCompact,
|
|
213
|
+
{
|
|
214
|
+
command: command("cursor", "session.compact"),
|
|
215
|
+
timeout: CURSOR_SESSION_HOOK_TIMEOUT_SECONDS,
|
|
183
216
|
},
|
|
184
217
|
];
|
|
185
|
-
hooks.preCompact = [...preCompact, { command: command("cursor", "session.compact"), timeout: 5 }];
|
|
186
218
|
return { ...config, version: 1, hooks };
|
|
187
219
|
}
|
|
188
220
|
function writeJsonIfChanged(projectRoot, path, payload) {
|
|
@@ -341,6 +373,14 @@ function hasNestedRegistration(config, host, options = {}) {
|
|
|
341
373
|
return (preCompact.some((entry) => nestedCommands(entry).includes(compactCommand)) &&
|
|
342
374
|
postCompact.some((entry) => nestedCommands(entry).includes(compactCommand)));
|
|
343
375
|
}
|
|
376
|
+
function isCursorToolBeforeEntry(value, matcher) {
|
|
377
|
+
const hook = object(value);
|
|
378
|
+
return (hook?.command === command("cursor", "tool.before") &&
|
|
379
|
+
hook.matcher === matcher &&
|
|
380
|
+
hook.failClosed === true &&
|
|
381
|
+
// #3246: budget must cover gated ritual + live agent-hook readiness.
|
|
382
|
+
hook.timeout === CURSOR_TOOL_BEFORE_TIMEOUT_SECONDS);
|
|
383
|
+
}
|
|
344
384
|
function hasCursorRegistration(config) {
|
|
345
385
|
const hooks = object(config.hooks);
|
|
346
386
|
if (hooks === null || config.version !== 1)
|
|
@@ -349,30 +389,10 @@ function hasCursorRegistration(config) {
|
|
|
349
389
|
const preTool = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
|
|
350
390
|
const preCompact = Array.isArray(hooks.preCompact) ? hooks.preCompact : [];
|
|
351
391
|
return (session.some((entry) => object(entry)?.command === command("cursor", "session.start")) &&
|
|
352
|
-
preTool.some((entry) =>
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
hook.failClosed === true);
|
|
357
|
-
}) &&
|
|
358
|
-
preTool.some((entry) => {
|
|
359
|
-
const hook = object(entry);
|
|
360
|
-
return (hook?.command === command("cursor", "tool.before") &&
|
|
361
|
-
hook.matcher === SPAWN_HOOK_MATCHER &&
|
|
362
|
-
hook.failClosed === true);
|
|
363
|
-
}) &&
|
|
364
|
-
preTool.some((entry) => {
|
|
365
|
-
const hook = object(entry);
|
|
366
|
-
return (hook?.command === command("cursor", "tool.before") &&
|
|
367
|
-
hook.matcher === SHELL_HOOK_MATCHER &&
|
|
368
|
-
hook.failClosed === true);
|
|
369
|
-
}) &&
|
|
370
|
-
preTool.some((entry) => {
|
|
371
|
-
const hook = object(entry);
|
|
372
|
-
return (hook?.command === command("cursor", "tool.before") &&
|
|
373
|
-
hook.matcher === MCP_HOOK_MATCHER &&
|
|
374
|
-
hook.failClosed === true);
|
|
375
|
-
}) &&
|
|
392
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, DIRECT_WRITE_HOOK_MATCHER)) &&
|
|
393
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, SPAWN_HOOK_MATCHER)) &&
|
|
394
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, SHELL_HOOK_MATCHER)) &&
|
|
395
|
+
preTool.some((entry) => isCursorToolBeforeEntry(entry, MCP_HOOK_MATCHER)) &&
|
|
376
396
|
preCompact.some((entry) => object(entry)?.command === command("cursor", "session.compact")));
|
|
377
397
|
}
|
|
378
398
|
/** Read-only registration probe shared by verify and doctor. */
|
|
@@ -30,6 +30,8 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
30
30
|
".deft/ritual-state.json",
|
|
31
31
|
".deft/last-session.json",
|
|
32
32
|
".deft/routing.local.json",
|
|
33
|
+
// #3282: opt-in default run-summary JSONL at repo root (collectible; must not dirty trees).
|
|
34
|
+
".deft-run-summary.json",
|
|
33
35
|
// Temporary test/local kill-switch — must stay untracked (#3039).
|
|
34
36
|
DEFT_DIRECTIVE_DISABLE_GITIGNORE_LINE,
|
|
35
37
|
"vbrief/.triage-cache/candidates.jsonl",
|