@deftai/directive-core 0.106.0 → 0.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/check/gate-lists.js +2 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +17 -1
- package/dist/hooks/dispatcher.js +82 -11
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/write-fence.js +3 -0
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/evaluate.js +36 -5
- package/dist/scm/design-critique-chip.d.ts +37 -0
- package/dist/scm/design-critique-chip.js +171 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +2 -1
- package/dist/scm/readiness.js +7 -1
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +18 -0
- package/dist/session/git.js +81 -1
- package/dist/session/occupancy.d.ts +2 -1
- package/dist/session/occupancy.js +11 -4
- package/dist/session/session-start.d.ts +12 -0
- package/dist/session/session-start.js +17 -0
- package/dist/session/verify-session-ritual.d.ts +22 -3
- package/dist/session/verify-session-ritual.js +29 -4
- package/dist/swarm/launch.d.ts +69 -0
- package/dist/swarm/launch.js +191 -1
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +3 -3
package/dist/scope/decompose.js
CHANGED
|
@@ -17,6 +17,7 @@ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveL
|
|
|
17
17
|
import { resolveRepo } from "../triage/queue/repo.js";
|
|
18
18
|
import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
|
|
19
19
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
20
|
+
import { READY_REQUIRES_PARALLEL_SAFE } from "../vbrief-validation/story-quality.js";
|
|
20
21
|
import { formatCoverageReportLine, validateCoverageMap } from "./coverage-map.js";
|
|
21
22
|
import { buildParentLineageArtifact } from "./parent-lineage.js";
|
|
22
23
|
import { formatBriefJson } from "./vbrief-json.js";
|
|
@@ -407,7 +408,7 @@ export function storyQualityIssues(opts) {
|
|
|
407
408
|
issues.push(...fileScopeIssues(swarm));
|
|
408
409
|
issues.push(...verifyCommandIssues(swarm));
|
|
409
410
|
if (swarm.parallel_safe === false) {
|
|
410
|
-
issues.push(
|
|
411
|
+
issues.push(READY_REQUIRES_PARALLEL_SAFE);
|
|
411
412
|
}
|
|
412
413
|
if (swarm.file_scope_confidence === "low") {
|
|
413
414
|
issues.push("readiness=ready requires file_scope_confidence above low");
|
|
@@ -16,6 +16,29 @@ export declare const NON_DELIVERY_DISPOSITIONS: readonly ["cancelled", "supersed
|
|
|
16
16
|
export type NonDeliveryDisposition = (typeof NON_DELIVERY_DISPOSITIONS)[number];
|
|
17
17
|
/** Delivery verification outcome for completion provenance. */
|
|
18
18
|
export type DeliveryDisposition = "delivered" | "merged_to_integration" | "not_delivered" | NonDeliveryDisposition | "unknown" | "unverified";
|
|
19
|
+
/**
|
|
20
|
+
* This is a historical audit snapshot stamped onto
|
|
21
|
+
* `plan.metadata.completionProvenance` at `scope:complete` (#3041 / #3690).
|
|
22
|
+
*
|
|
23
|
+
* Completed xBRIEFs have full standing as a record of *what is* and zero
|
|
24
|
+
* authority over *what to build next* (#3383). Fields stay because they
|
|
25
|
+
* answer a later reconstruction question after branches are deleted and
|
|
26
|
+
* refs move. A field does not need a current production-code reader.
|
|
27
|
+
*
|
|
28
|
+
* Reconstruction groups:
|
|
29
|
+
* - Identity and location: `repository`, `implementationCommit`, `prNumber`,
|
|
30
|
+
* `prBase`, `deliveryBranch`
|
|
31
|
+
* - Delivery evidence: `mergeCommit`, `deliveryCommit`, `disposition`,
|
|
32
|
+
* `handoffState`
|
|
33
|
+
* - Verification attribution: `verifiedAt`, `verifier`
|
|
34
|
+
* - Explicit operator-supplied facts: `deployed`, `uatVerified` (never
|
|
35
|
+
* inferred from Git)
|
|
36
|
+
*
|
|
37
|
+
* `completedSessionId` is duplicated. This nested copy is stamped so the
|
|
38
|
+
* snapshot is self-contained. Consumers (`verify:ac` /
|
|
39
|
+
* `session-completed-ac`) read the sibling
|
|
40
|
+
* `plan.metadata.completedSessionId`, not this field.
|
|
41
|
+
*/
|
|
19
42
|
export interface CompletionProvenance {
|
|
20
43
|
readonly repository: string | null;
|
|
21
44
|
readonly implementationCommit: string | null;
|
|
@@ -32,7 +55,10 @@ export interface CompletionProvenance {
|
|
|
32
55
|
readonly deployed: boolean | null;
|
|
33
56
|
/** Always null unless explicitly supplied — never inferred from Git (#3041). */
|
|
34
57
|
readonly uatVerified: boolean | null;
|
|
35
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Nested session correlation for a self-contained snapshot.
|
|
60
|
+
* Consumers read sibling `plan.metadata.completedSessionId`, not this field.
|
|
61
|
+
*/
|
|
36
62
|
readonly completedSessionId?: string | null;
|
|
37
63
|
}
|
|
38
64
|
export interface DeliveryEvidenceInput {
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./decomposed-refs.js";
|
|
|
8
8
|
export * from "./delivery-evidence.js";
|
|
9
9
|
export * from "./demote.js";
|
|
10
10
|
export * from "./effort-activate-gate.js";
|
|
11
|
+
export * from "./lifecycle-write.js";
|
|
11
12
|
export * from "./main.js";
|
|
12
13
|
export * from "./open-umbrella-warning.js";
|
|
13
14
|
export * from "./parent-lineage.js";
|
package/dist/scope/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./decomposed-refs.js";
|
|
|
8
8
|
export * from "./delivery-evidence.js";
|
|
9
9
|
export * from "./demote.js";
|
|
10
10
|
export * from "./effort-activate-gate.js";
|
|
11
|
+
export * from "./lifecycle-write.js";
|
|
11
12
|
export * from "./main.js";
|
|
12
13
|
export * from "./open-umbrella-warning.js";
|
|
13
14
|
export * from "./parent-lineage.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition-write fingerprint for completed/ artifacts (#3679).
|
|
3
|
+
*
|
|
4
|
+
* Distinct from completionProvenance (#3686): this only records that
|
|
5
|
+
* runTransition wrote the blob. Non-code-bearing completes and scope:fail
|
|
6
|
+
* stamp this and still omit delivery provenance.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LIFECYCLE_WRITE_KEY: "lifecycleWrite";
|
|
9
|
+
export type LifecycleWriteAction = "complete" | "fail";
|
|
10
|
+
export interface LifecycleWriteStamp {
|
|
11
|
+
readonly action: LifecycleWriteAction;
|
|
12
|
+
readonly writtenAt: string;
|
|
13
|
+
}
|
|
14
|
+
/** Worker-facing leftover-land path after a scope-provenance strip (#3476 / #3679). */
|
|
15
|
+
export declare const LEFTOVER_LAND_PR_REMEDIATION: string;
|
|
16
|
+
/** Stamp that runTransition wrote this plan into completed/. */
|
|
17
|
+
export declare function stampLifecycleWrite(plan: Record<string, unknown>, action: LifecycleWriteAction, writtenAt: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* True when a completed/ blob shows verb evidence.
|
|
20
|
+
* Accepts the new stamp, legacy completedAt (pre-#3679 complete), or failed status
|
|
21
|
+
* (scope:fail never stamped provenance and must keep passing).
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasTransitionWrite(plan: Record<string, unknown>): boolean;
|
|
24
|
+
//# sourceMappingURL=lifecycle-write.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition-write fingerprint for completed/ artifacts (#3679).
|
|
3
|
+
*
|
|
4
|
+
* Distinct from completionProvenance (#3686): this only records that
|
|
5
|
+
* runTransition wrote the blob. Non-code-bearing completes and scope:fail
|
|
6
|
+
* stamp this and still omit delivery provenance.
|
|
7
|
+
*/
|
|
8
|
+
export const LIFECYCLE_WRITE_KEY = "lifecycleWrite";
|
|
9
|
+
/** Worker-facing leftover-land path after a scope-provenance strip (#3476 / #3679). */
|
|
10
|
+
export const LEFTOVER_LAND_PR_REMEDIATION = "After a provenance-gated product PR strips active/ from the change set, " +
|
|
11
|
+
"the designed remainder is a leftover land PR (#3476): run `task scope:complete` " +
|
|
12
|
+
"after merge (it can stamp a brief already in completed/), then land that artifact. " +
|
|
13
|
+
"Do not git-add a completed/ husk to skip the verb.";
|
|
14
|
+
function asRecord(value) {
|
|
15
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function metadataRecord(plan) {
|
|
21
|
+
const existing = asRecord(plan.metadata);
|
|
22
|
+
if (existing !== null) {
|
|
23
|
+
return existing;
|
|
24
|
+
}
|
|
25
|
+
const created = {};
|
|
26
|
+
plan.metadata = created;
|
|
27
|
+
return created;
|
|
28
|
+
}
|
|
29
|
+
/** Stamp that runTransition wrote this plan into completed/. */
|
|
30
|
+
export function stampLifecycleWrite(plan, action, writtenAt) {
|
|
31
|
+
const meta = metadataRecord(plan);
|
|
32
|
+
meta[LIFECYCLE_WRITE_KEY] = { action, writtenAt };
|
|
33
|
+
}
|
|
34
|
+
function hasLifecycleWriteStamp(plan) {
|
|
35
|
+
const meta = asRecord(plan.metadata);
|
|
36
|
+
if (meta === null) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const stamp = asRecord(meta[LIFECYCLE_WRITE_KEY]);
|
|
40
|
+
if (stamp === null) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const action = stamp.action;
|
|
44
|
+
const writtenAt = stamp.writtenAt;
|
|
45
|
+
return ((action === "complete" || action === "fail") &&
|
|
46
|
+
typeof writtenAt === "string" &&
|
|
47
|
+
writtenAt.trim().length > 0);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* True when a completed/ blob shows verb evidence.
|
|
51
|
+
* Accepts the new stamp, legacy completedAt (pre-#3679 complete), or failed status
|
|
52
|
+
* (scope:fail never stamped provenance and must keep passing).
|
|
53
|
+
*/
|
|
54
|
+
export function hasTransitionWrite(plan) {
|
|
55
|
+
if (hasLifecycleWriteStamp(plan)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const meta = asRecord(plan.metadata);
|
|
59
|
+
if (meta !== null && typeof meta.completedAt === "string" && meta.completedAt.trim().length > 0) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return String(plan.status ?? "") === "failed";
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=lifecycle-write.js.map
|
package/dist/scope/main.js
CHANGED
|
@@ -17,10 +17,12 @@ import { findByDecisionId, isAlreadyUndone, REVERSIBLE_ACTIONS, undoBatch, undoO
|
|
|
17
17
|
const LIFECYCLE_USAGE_STDERR = "usage: deft scope:<action> [file] [--batch] [--from-issue N] [--repo OWNER/NAME]\n" +
|
|
18
18
|
" [--force] [--project-root PATH] [--strict] [--force-no-cache]\n" +
|
|
19
19
|
" [--path PATH]\n" +
|
|
20
|
+
" complete also accepts: --merge-commit SHA --pr N [--pr-base BRANCH] [--delivery-branch BRANCH]\n" +
|
|
20
21
|
" actions: activate, block, cancel, complete, fail, promote, restore, unblock\n" +
|
|
21
22
|
"The verb already encodes the action (e.g. deft scope:promote <file>). Do not pass the action again.\n" +
|
|
22
23
|
"(promote --batch may omit file and promotes all proposed/ scopes; #3011)\n" +
|
|
23
|
-
"(promote --from-issue=N may omit file; #1136)\n"
|
|
24
|
+
"(promote --from-issue=N may omit file; #1136)\n" +
|
|
25
|
+
"(complete --merge-commit / --pr are required for code-bearing delivery evidence; #3041 / #3721)\n";
|
|
24
26
|
function parseLifecycleArgv(argv) {
|
|
25
27
|
if (argv.length < 1) {
|
|
26
28
|
return { args: null, error: "usage" };
|
package/dist/scope/transition.js
CHANGED
|
@@ -16,6 +16,7 @@ import { LIFECYCLE_FOLDERS, MOVE_LABELS, STATUS_PRECONDITIONS, STAY_LABELS, TRAN
|
|
|
16
16
|
import { detectLifecycleFolder, updateDecomposedChildBackReferences, updateDecomposedParentBackReferences, } from "./decomposed-refs.js";
|
|
17
17
|
import { classifyStoredDeliveryDisposition, evaluateDeliveryGate, resolveCompletionSessionId, stampDeliveryProvenance, } from "./delivery-evidence.js";
|
|
18
18
|
import { evaluateEffortActivateGate } from "./effort-activate-gate.js";
|
|
19
|
+
import { stampLifecycleWrite } from "./lifecycle-write.js";
|
|
19
20
|
import { syncProjectDefinitionAfterScopeMove } from "./project-definition-sync.js";
|
|
20
21
|
import { syncSpecificationAfterScopeMove } from "./specification-sync.js";
|
|
21
22
|
import { utcNowIso } from "./vbrief-json.js";
|
|
@@ -81,7 +82,9 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
84
|
const { allowedSources, targetFolder, targetStatus } = TRANSITIONS[act];
|
|
84
|
-
|
|
85
|
+
const restampCompleted = act === "complete" && currentFolder === "completed";
|
|
86
|
+
if (!restampCompleted &&
|
|
87
|
+
!allowedSources.includes(currentFolder)) {
|
|
85
88
|
const allowedStr = allowedSources.map((s) => `${s}/`).join(", ");
|
|
86
89
|
return {
|
|
87
90
|
ok: false,
|
|
@@ -103,15 +106,9 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
103
106
|
const requiredStatus = STATUS_PRECONDITIONS[act];
|
|
104
107
|
if (requiredStatus !== undefined) {
|
|
105
108
|
if (currentStatus === targetStatus) {
|
|
106
|
-
// Surface legacy delivery disposition on already-completed briefs (#3041).
|
|
107
|
-
let dispositionSuffix = "";
|
|
108
|
-
if (act === "complete" && currentFolder === "completed") {
|
|
109
|
-
const disposition = classifyStoredDeliveryDisposition(planObj);
|
|
110
|
-
dispositionSuffix = ` (deliveryDisposition=${disposition})`;
|
|
111
|
-
}
|
|
112
109
|
return {
|
|
113
110
|
ok: true,
|
|
114
|
-
message: `No-op: ${basename} is already ${targetStatus} in ${currentFolder}
|
|
111
|
+
message: `No-op: ${basename} is already ${targetStatus} in ${currentFolder}/`,
|
|
115
112
|
};
|
|
116
113
|
}
|
|
117
114
|
if (currentStatus !== requiredStatus) {
|
|
@@ -122,14 +119,30 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
122
119
|
};
|
|
123
120
|
}
|
|
124
121
|
}
|
|
122
|
+
const nowIso = utcNowIso(now);
|
|
123
|
+
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
124
|
+
const projectRoot = dirname(vbriefRoot);
|
|
125
|
+
// #3679: lift #3041 already-completed handling out of STATUS_PRECONDITIONS
|
|
126
|
+
// so complete can stamp a brief already in completed/ without a folder trip.
|
|
127
|
+
if (restampCompleted) {
|
|
128
|
+
return restampCompletedBrief({
|
|
129
|
+
resolvedPath,
|
|
130
|
+
basename,
|
|
131
|
+
data,
|
|
132
|
+
planObj,
|
|
133
|
+
projectRoot,
|
|
134
|
+
vbriefRoot,
|
|
135
|
+
now,
|
|
136
|
+
nowIso,
|
|
137
|
+
options,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
125
140
|
if (targetFolder !== null && targetFolder === currentFolder) {
|
|
126
141
|
return {
|
|
127
142
|
ok: true,
|
|
128
143
|
message: `No-op: ${basename} is already in ${currentFolder}/ (status: ${currentStatus})`,
|
|
129
144
|
};
|
|
130
145
|
}
|
|
131
|
-
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
132
|
-
const projectRoot = dirname(vbriefRoot);
|
|
133
146
|
if (targetFolder !== null) {
|
|
134
147
|
const destDir = join(vbriefRoot, targetFolder);
|
|
135
148
|
try {
|
|
@@ -144,7 +157,6 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
144
157
|
throw err;
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
|
-
const nowIso = utcNowIso(now);
|
|
148
160
|
// #3360: hand-authored briefs run #3323 clause derivation on activate/promote.
|
|
149
161
|
// #3355: stamp is state-observed (before vs after), not derivation-notice-bound.
|
|
150
162
|
const previousAcceptance = planObj.acceptance;
|
|
@@ -190,6 +202,7 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
190
202
|
? { ...gate.provenance, completedSessionId: sessionId }
|
|
191
203
|
: gate.provenance);
|
|
192
204
|
}
|
|
205
|
+
stampLifecycleWrite(planObj, "complete", nowIso);
|
|
193
206
|
}
|
|
194
207
|
// #3240: per-criterion typed evidence or human-origin disposition before auto-advance.
|
|
195
208
|
let acceptanceReports;
|
|
@@ -266,6 +279,9 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
266
279
|
};
|
|
267
280
|
}
|
|
268
281
|
}
|
|
282
|
+
if (act === "fail") {
|
|
283
|
+
stampLifecycleWrite(planObj, "fail", nowIso);
|
|
284
|
+
}
|
|
269
285
|
const formatted = formatBriefJson(data);
|
|
270
286
|
const crud = new InstrumentedVbriefCrud({ now: () => now });
|
|
271
287
|
if (targetFolder !== null) {
|
|
@@ -370,6 +386,69 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
370
386
|
acceptanceReports,
|
|
371
387
|
};
|
|
372
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Stamp a brief already in completed/ (#3679). Runs the #3041 delivery gate
|
|
391
|
+
* and writes in place so recovery does not need a folder round-trip.
|
|
392
|
+
* The #3041 deliveryDisposition suffix now actually executes for complete.
|
|
393
|
+
*/
|
|
394
|
+
function restampCompletedBrief(args) {
|
|
395
|
+
const { resolvedPath, basename, data, planObj, projectRoot, vbriefRoot, now, nowIso, options } = args;
|
|
396
|
+
const gate = evaluateDeliveryGate({
|
|
397
|
+
projectRoot,
|
|
398
|
+
plan: planObj,
|
|
399
|
+
nowIso,
|
|
400
|
+
evidence: options.deliveryEvidence,
|
|
401
|
+
nonDeliveryDisposition: options.nonDeliveryDisposition,
|
|
402
|
+
runGit: options.runGit,
|
|
403
|
+
verifier: options.verifier ?? "scope:complete",
|
|
404
|
+
assumeEvidenceValidated: options.assumeEvidenceValidated,
|
|
405
|
+
});
|
|
406
|
+
if (!gate.ok) {
|
|
407
|
+
return { ok: false, message: gate.message };
|
|
408
|
+
}
|
|
409
|
+
if (gate.provenance !== null) {
|
|
410
|
+
const sessionId = resolveCompletionSessionId(projectRoot);
|
|
411
|
+
stampDeliveryProvenance(planObj, sessionId !== null ? { ...gate.provenance, completedSessionId: sessionId } : gate.provenance);
|
|
412
|
+
}
|
|
413
|
+
planObj.status = "completed";
|
|
414
|
+
planObj.updated = nowIso;
|
|
415
|
+
stampEnvelopeUpdated(data, nowIso);
|
|
416
|
+
stampCompletionMetadata(planObj, projectRoot, nowIso, {
|
|
417
|
+
completedSessionId: resolveCompletionSessionId(projectRoot),
|
|
418
|
+
});
|
|
419
|
+
stampLifecycleWrite(planObj, "complete", nowIso);
|
|
420
|
+
const writeResult = atomicWriteBrief(resolvedPath, data, vbriefRoot, { projectRoot });
|
|
421
|
+
if (!writeResult.ok) {
|
|
422
|
+
return { ok: false, message: writeResult.message };
|
|
423
|
+
}
|
|
424
|
+
const crud = new InstrumentedVbriefCrud({ now: () => now });
|
|
425
|
+
crud.recordTrustedUpdate(resolvedPath, formatBriefJson(data));
|
|
426
|
+
try {
|
|
427
|
+
persistCrudMetrics(projectRoot, crud.getMetrics());
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
/* best-effort telemetry persistence */
|
|
431
|
+
}
|
|
432
|
+
const sessionId = resolveCompletionSessionId(projectRoot);
|
|
433
|
+
if (sessionId !== null) {
|
|
434
|
+
try {
|
|
435
|
+
writeSessionCompletedMarker(projectRoot, {
|
|
436
|
+
path: resolvedPath,
|
|
437
|
+
sessionId,
|
|
438
|
+
completedAt: nowIso,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return { ok: false, message: SESSION_COMPLETED_AC_REMEDIATION };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const disposition = classifyStoredDeliveryDisposition(planObj);
|
|
446
|
+
return {
|
|
447
|
+
ok: true,
|
|
448
|
+
message: `Restamped ${basename} in completed/ (status: completed, ` +
|
|
449
|
+
`deliveryDisposition=${disposition})`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
373
452
|
export function recordWipCapOverride(filePath, projectRoot, check, now = new Date()) {
|
|
374
453
|
try {
|
|
375
454
|
const rel = resolve(filePath).startsWith(resolve(projectRoot))
|
|
@@ -209,7 +209,11 @@ function remediationForExpansion() {
|
|
|
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 " +
|
|
212
|
-
"does not authorize new paths (#3145 / #3205). See content/docs/scope-provenance.md."
|
|
212
|
+
"does not authorize new paths (#3145 / #3205). See content/docs/scope-provenance.md. " +
|
|
213
|
+
"If you cannot mint a digest (human TTY required), drop active/ from this product " +
|
|
214
|
+
"change set and land the completed artifact in a leftover land PR after merge (#3476). " +
|
|
215
|
+
"Run `task scope:complete` on the brief (it can stamp a file already in completed/). " +
|
|
216
|
+
"Do not git-add a completed/ husk to skip that PR (#3679).");
|
|
213
217
|
}
|
|
214
218
|
/**
|
|
215
219
|
* Parse + lightly validate an approved-scope JSON blob (base-ref `git show` or disk).
|
package/dist/session/git.d.ts
CHANGED
|
@@ -12,5 +12,23 @@ export declare function gitHead(projectRoot: string, runGit?: GitRunner): {
|
|
|
12
12
|
export declare function worktreePath(projectRoot: string, runGit?: GitRunner): string;
|
|
13
13
|
/** True when `ancestor` is reachable from `descendant` (same commit counts). */
|
|
14
14
|
export declare function gitIsAncestor(projectRoot: string, ancestor: string, descendant: string, runGit?: GitRunner): boolean | null;
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling for one `git cat-file --batch` payload. The directive terminal
|
|
17
|
+
* corpus is ~8 MiB today; this leaves headroom without reintroducing
|
|
18
|
+
* per-blob `git show` on a truncated read.
|
|
19
|
+
*/
|
|
20
|
+
export declare const GIT_CAT_FILE_BATCH_MAX_BUFFER: number;
|
|
21
|
+
/**
|
|
22
|
+
* Parse `git cat-file --batch` stdout for `paths.length` objects, in the
|
|
23
|
+
* same order the names were written to stdin. Returns null if the stream
|
|
24
|
+
* is truncated or a header is malformed so callers can fall back.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseGitCatFileBatch(stdout: Buffer, paths: readonly string[]): Map<string, string | null> | null;
|
|
27
|
+
/**
|
|
28
|
+
* Read many `tip:path` blobs in one `git cat-file --batch` process.
|
|
29
|
+
* Falls back to per-path `git show` only when the batch stream cannot be
|
|
30
|
+
* parsed, so verdicts stay content-authoritative.
|
|
31
|
+
*/
|
|
32
|
+
export declare function showBlobsBatch(projectRoot: string, tip: string, paths: readonly string[], runGit?: GitRunner): Map<string, string | null>;
|
|
15
33
|
export declare function detectBranch(projectRoot: string, runGit?: GitRunner): string | null;
|
|
16
34
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/session/git.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
function coerceGitBytes(value) {
|
|
4
4
|
if (Buffer.isBuffer(value))
|
|
@@ -64,6 +64,86 @@ export function gitIsAncestor(projectRoot, ancestor, descendant, runGit = defaul
|
|
|
64
64
|
}
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Ceiling for one `git cat-file --batch` payload. The directive terminal
|
|
69
|
+
* corpus is ~8 MiB today; this leaves headroom without reintroducing
|
|
70
|
+
* per-blob `git show` on a truncated read.
|
|
71
|
+
*/
|
|
72
|
+
export const GIT_CAT_FILE_BATCH_MAX_BUFFER = 64 * 1024 * 1024;
|
|
73
|
+
/**
|
|
74
|
+
* Parse `git cat-file --batch` stdout for `paths.length` objects, in the
|
|
75
|
+
* same order the names were written to stdin. Returns null if the stream
|
|
76
|
+
* is truncated or a header is malformed so callers can fall back.
|
|
77
|
+
*/
|
|
78
|
+
export function parseGitCatFileBatch(stdout, paths) {
|
|
79
|
+
const out = new Map();
|
|
80
|
+
let offset = 0;
|
|
81
|
+
for (const path of paths) {
|
|
82
|
+
const nl = stdout.indexOf(0x0a, offset);
|
|
83
|
+
if (nl < 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const header = stdout.subarray(offset, nl).toString("utf8");
|
|
87
|
+
offset = nl + 1;
|
|
88
|
+
if (header.endsWith(" missing") || header.endsWith(" ambiguous")) {
|
|
89
|
+
out.set(path, null);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const match = /^[0-9a-fA-F]+ \S+ (\d+)$/.exec(header);
|
|
93
|
+
if (match === null) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const size = Number(match[1]);
|
|
97
|
+
if (!Number.isInteger(size) || size < 0 || offset + size > stdout.length) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const content = stdout.subarray(offset, offset + size);
|
|
101
|
+
offset += size;
|
|
102
|
+
if (offset < stdout.length && stdout[offset] === 0x0a) {
|
|
103
|
+
offset += 1;
|
|
104
|
+
}
|
|
105
|
+
else if (offset !== stdout.length) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
out.set(path, content.toString("utf8"));
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function showBlobViaRunner(projectRoot, tip, path, runGit) {
|
|
113
|
+
const result = runGit(projectRoot, ["show", `${tip}:${path}`]);
|
|
114
|
+
if (result.code !== 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return result.stdout;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Read many `tip:path` blobs in one `git cat-file --batch` process.
|
|
121
|
+
* Falls back to per-path `git show` only when the batch stream cannot be
|
|
122
|
+
* parsed, so verdicts stay content-authoritative.
|
|
123
|
+
*/
|
|
124
|
+
export function showBlobsBatch(projectRoot, tip, paths, runGit = defaultGitRunner) {
|
|
125
|
+
const out = new Map();
|
|
126
|
+
if (paths.length === 0) {
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
const input = Buffer.from(`${paths.map((path) => `${tip}:${path}`).join("\n")}\n`, "utf8");
|
|
130
|
+
const result = spawnSync("git", ["cat-file", "--batch"], {
|
|
131
|
+
cwd: projectRoot,
|
|
132
|
+
input,
|
|
133
|
+
maxBuffer: GIT_CAT_FILE_BATCH_MAX_BUFFER,
|
|
134
|
+
windowsHide: true,
|
|
135
|
+
});
|
|
136
|
+
if (result.error === undefined && result.status === 0 && result.stdout !== undefined) {
|
|
137
|
+
const parsed = parseGitCatFileBatch(coerceGitBytes(result.stdout), paths);
|
|
138
|
+
if (parsed !== null) {
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const path of paths) {
|
|
143
|
+
out.set(path, showBlobViaRunner(projectRoot, tip, path, runGit));
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
67
147
|
export function detectBranch(projectRoot, runGit = defaultGitRunner) {
|
|
68
148
|
const sym = runGit(projectRoot, ["symbolic-ref", "--short", "HEAD"]);
|
|
69
149
|
if (sym.code === 0 && sym.stdout.trim()) {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Ritual-state is "this session completed ceremony." Occupancy is "who may
|
|
5
5
|
* mutate this tree right now." Those lifetimes differ; do not overload
|
|
6
|
-
* ritual-state.json.
|
|
6
|
+
* ritual-state.json. Ordinary end is occupancy:release / session:end (#3604).
|
|
7
|
+
* Join negotiation (`occupancy:request`) is out of scope.
|
|
7
8
|
*
|
|
8
9
|
* Concurrency model:
|
|
9
10
|
* - Assumptions: local filesystem; cooperating processes on one machine.
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Ritual-state is "this session completed ceremony." Occupancy is "who may
|
|
5
5
|
* mutate this tree right now." Those lifetimes differ; do not overload
|
|
6
|
-
* ritual-state.json.
|
|
6
|
+
* ritual-state.json. Ordinary end is occupancy:release / session:end (#3604).
|
|
7
|
+
* Join negotiation (`occupancy:request`) is out of scope.
|
|
7
8
|
*
|
|
8
9
|
* Concurrency model:
|
|
9
10
|
* - Assumptions: local filesystem; cooperating processes on one machine.
|
|
@@ -35,11 +36,16 @@ export function heartbeatAgeSeconds(record, now = new Date()) {
|
|
|
35
36
|
export function isOccupancyExpired(record, now = new Date(), ttlMs = OCCUPANCY_TTL_MS) {
|
|
36
37
|
return now.getTime() - record.heartbeatAt.getTime() > ttlMs;
|
|
37
38
|
}
|
|
39
|
+
function occupancyClockLine(record) {
|
|
40
|
+
return `claimed_at=${timestampIso(record.claimedAt)} heartbeat_at=${timestampIso(record.heartbeatAt)}`;
|
|
41
|
+
}
|
|
38
42
|
export function formatOccupancyRemediation(record, now = new Date()) {
|
|
39
43
|
const age = heartbeatAgeSeconds(record, now);
|
|
40
|
-
return (`Worktree occupied by session ${record.sessionId} (intent=${record.intent}, heartbeat ${age}s ago
|
|
44
|
+
return (`Worktree occupied by session ${record.sessionId} (intent=${record.intent}, heartbeat ${age}s ago, ` +
|
|
45
|
+
`${occupancyClockLine(record)}).\n` +
|
|
41
46
|
"Stay read-only (`session:start --read-only`), use another worktree,\n" +
|
|
42
|
-
"queue a join (`occupancy:request`), or steal (`occupancy:steal --confirm`)
|
|
47
|
+
"queue a join (`occupancy:request`), or steal (`occupancy:steal --confirm`).\n" +
|
|
48
|
+
"The occupant may release (`occupancy:release` / `session:end`).");
|
|
43
49
|
}
|
|
44
50
|
export function resolveOccupancySessionId(input = {}) {
|
|
45
51
|
const explicit = input.sessionId?.trim();
|
|
@@ -194,6 +200,7 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
194
200
|
};
|
|
195
201
|
}
|
|
196
202
|
const incoming = resolveOccupancySessionId(input);
|
|
203
|
+
const priorClock = existingLocked !== null ? ` (${occupancyClockLine(existingLocked)})` : "";
|
|
197
204
|
const record = writeOccupancyRecord(projectRoot, {
|
|
198
205
|
sessionId: incoming,
|
|
199
206
|
worktreePath: resolve(projectRoot),
|
|
@@ -210,7 +217,7 @@ export function stealOccupancy(projectRoot, input = {}) {
|
|
|
210
217
|
sessionId: record.sessionId,
|
|
211
218
|
record,
|
|
212
219
|
path,
|
|
213
|
-
message: `occupancy stolen from ${named}; writer is now session ${record.sessionId}`,
|
|
220
|
+
message: `occupancy stolen from ${named}${priorClock}; writer is now session ${record.sessionId}`,
|
|
214
221
|
code: 0,
|
|
215
222
|
};
|
|
216
223
|
}, input.lockDeps);
|
|
@@ -24,8 +24,20 @@ export type SessionCeremonyTier = (typeof SESSION_CEREMONY_TIERS)[number];
|
|
|
24
24
|
export declare const COLD_CEREMONY_TIER: SessionCeremonyTier;
|
|
25
25
|
export declare const REARM_CEREMONY_TIER: SessionCeremonyTier;
|
|
26
26
|
export declare const QUICK_STEPS: readonly ["alignment", "branch_policy", "triage_welcome", "verify_tools"];
|
|
27
|
+
/** Session / full gated ritual set. Write dispatch uses a per-surface subset (#3738). */
|
|
27
28
|
export declare const GATED_STEPS: readonly ["agent_hooks", "doctor", "cache_fresh"];
|
|
28
29
|
export type GatedStepName = (typeof GATED_STEPS)[number];
|
|
30
|
+
/**
|
|
31
|
+
* Gated steps a write/spawn mutation must prove from recorded ritual state (#3738).
|
|
32
|
+
* `cache_fresh` is a work-selection precondition, not a write-authorization one.
|
|
33
|
+
*/
|
|
34
|
+
export declare const WRITE_GATED_REQUIRED_STEPS: readonly ["agent_hooks", "doctor"];
|
|
35
|
+
/**
|
|
36
|
+
* Gated steps the write path may execute (#3738).
|
|
37
|
+
* Hook readiness is deliberately non-cacheable; doctor and cache_fresh stay
|
|
38
|
+
* session-surface only.
|
|
39
|
+
*/
|
|
40
|
+
export declare const WRITE_GATED_EXECUTE_STEPS: readonly ["agent_hooks"];
|
|
29
41
|
/** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
|
|
30
42
|
export declare const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
|
|
31
43
|
/** Human-readable skip notice when optional network is off the hot path (#2991). */
|
|
@@ -50,7 +50,24 @@ export const QUICK_STEPS = [
|
|
|
50
50
|
"triage_welcome",
|
|
51
51
|
"verify_tools",
|
|
52
52
|
];
|
|
53
|
+
/** Session / full gated ritual set. Write dispatch uses a per-surface subset (#3738). */
|
|
53
54
|
export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
|
|
55
|
+
/**
|
|
56
|
+
* Gated steps a write/spawn mutation must prove from recorded ritual state (#3738).
|
|
57
|
+
* `cache_fresh` is a work-selection precondition, not a write-authorization one.
|
|
58
|
+
*/
|
|
59
|
+
export const WRITE_GATED_REQUIRED_STEPS = [
|
|
60
|
+
"agent_hooks",
|
|
61
|
+
"doctor",
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Gated steps the write path may execute (#3738).
|
|
65
|
+
* Hook readiness is deliberately non-cacheable; doctor and cache_fresh stay
|
|
66
|
+
* session-surface only.
|
|
67
|
+
*/
|
|
68
|
+
export const WRITE_GATED_EXECUTE_STEPS = [
|
|
69
|
+
"agent_hooks",
|
|
70
|
+
];
|
|
54
71
|
/** Per-clone ignore/index hide of lifecycle roots — warn-only (#3505). */
|
|
55
72
|
function pushLifecycleVisibleAdvisory(lines, projectRoot, options, runGit) {
|
|
56
73
|
try {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ActiveCliCheckResult, type ActiveCliCheckSeams } from "./active-cli.js";
|
|
2
2
|
import { type GitRunner } from "./git.js";
|
|
3
3
|
import { type DirectivePosture } from "./posture.js";
|
|
4
|
-
import { type GatedStepName, type SessionCeremonyTier } from "./session-start.js";
|
|
4
|
+
import { type GatedStepName, type SessionCeremonyTier, WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS } from "./session-start.js";
|
|
5
5
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
6
|
+
export { WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS };
|
|
6
7
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
7
8
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
8
9
|
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<GatedStepName, readonly string[]>>;
|
|
@@ -81,7 +82,24 @@ export interface VerifySessionRitualOptions {
|
|
|
81
82
|
readonly checkActiveCli?: (targetVersion: string | null, seams?: ActiveCliCheckSeams) => ActiveCliCheckResult;
|
|
82
83
|
/** Injectable work-selection detector for gated `cache_fresh` (#3507). */
|
|
83
84
|
readonly detectWorkSelection?: DetectWorkSelection;
|
|
85
|
+
/**
|
|
86
|
+
* Required gated steps for this surface. Defaults to all {@link GATED_STEPS}
|
|
87
|
+
* (session / full gated verify). Write dispatch uses
|
|
88
|
+
* {@link WRITE_GATED_REQUIRED_STEPS} (#3738).
|
|
89
|
+
*/
|
|
90
|
+
readonly requiredGatedSteps?: readonly GatedStepName[];
|
|
91
|
+
/**
|
|
92
|
+
* Gated steps this surface may execute. Defaults to all {@link GATED_STEPS}.
|
|
93
|
+
* Write dispatch uses {@link WRITE_GATED_EXECUTE_STEPS} so `cache_fresh` and
|
|
94
|
+
* `doctor` cannot run as write-path repair (#3738).
|
|
95
|
+
*/
|
|
96
|
+
readonly executeGatedSteps?: readonly GatedStepName[];
|
|
84
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Options for the write/spawn mutation verifier (#3738).
|
|
100
|
+
* Required set omits `cache_fresh`; execution authority is `agent_hooks` only.
|
|
101
|
+
*/
|
|
102
|
+
export declare function writeGateRitualOptions(extras?: Omit<VerifySessionRitualOptions, "requiredGatedSteps" | "executeGatedSteps" | "tier">): VerifySessionRitualOptions;
|
|
85
103
|
export interface InspectSessionRitualOptions {
|
|
86
104
|
readonly tier?: "quick" | "gated";
|
|
87
105
|
readonly now?: Date;
|
|
@@ -94,8 +112,9 @@ export interface InspectSessionRitualOptions {
|
|
|
94
112
|
* Read-only ritual-state inspection for host hooks.
|
|
95
113
|
*
|
|
96
114
|
* Unlike {@link verifySessionRitual}, this never runs missing gated entrypoints
|
|
97
|
-
* and never rewrites `.deft/ritual-state.json`.
|
|
98
|
-
*
|
|
115
|
+
* and never rewrites `.deft/ritual-state.json`. Write dispatch does **not** use
|
|
116
|
+
* this function: it must still execute `agent_hooks` (#3738). A PreToolUse
|
|
117
|
+
* decision must not become a hidden `doctor` / cache-refresh mutation boundary.
|
|
99
118
|
*/
|
|
100
119
|
export declare function inspectSessionRitual(projectRoot: string, options?: InspectSessionRitualOptions): VerifyResult;
|
|
101
120
|
export declare function verifySessionRitual(projectRoot: string, options?: VerifySessionRitualOptions): VerifyResult;
|