@deftai/directive-core 0.107.0 → 0.109.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/architecture/sor-preflight.js +2 -0
- package/dist/cache/operations.js +1 -1
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.d.ts +14 -0
- package/dist/check/gate-lists.js +26 -3
- package/dist/check/named-cause.js +18 -3
- package/dist/check/session-completed-ac.d.ts +1 -1
- package/dist/check/session-completed-ac.js +1 -1
- package/dist/codebase/provider.js +7 -1
- package/dist/consumer-check-contract/evaluate.d.ts +47 -0
- package/dist/consumer-check-contract/evaluate.js +181 -15
- package/dist/delivery-attempt/evaluate.d.ts +9 -1
- package/dist/delivery-attempt/evaluate.js +69 -0
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/delivery-attempt/index.d.ts +1 -1
- package/dist/delivery-attempt/index.js +1 -1
- package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
- package/dist/deposit/live-procedure-exclusions.js +110 -0
- package/dist/deposit/live-procedure-targets.d.ts +45 -0
- package/dist/deposit/live-procedure-targets.js +274 -0
- package/dist/deposit/python-free.d.ts +6 -0
- package/dist/deposit/python-free.js +15 -0
- package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
- package/dist/deposit/rewrite-deposit-links.js +148 -0
- package/dist/deposit/run-stage-content-pack.d.ts +2 -0
- package/dist/deposit/run-stage-content-pack.js +3 -0
- package/dist/deposit/stage-content-pack.d.ts +17 -0
- package/dist/deposit/stage-content-pack.js +91 -0
- package/dist/design-critique/citation-grammar.d.ts +60 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +277 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +89 -0
- package/dist/hooks/classify/host-session-identity.js +467 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +9 -0
- package/dist/hooks/classify/paths.js +39 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +94 -1
- package/dist/hooks/dest-form.d.ts +20 -1
- package/dist/hooks/dest-form.js +158 -21
- package/dist/hooks/dispatcher.d.ts +58 -3
- package/dist/hooks/dispatcher.js +762 -73
- package/dist/hooks/fixtures/cases.d.ts +22 -2
- package/dist/hooks/fixtures/cases.js +228 -0
- package/dist/hooks/git-destructive-log.d.ts +32 -0
- package/dist/hooks/git-destructive-log.js +46 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/hooks/shell-write-targets.d.ts +10 -0
- package/dist/hooks/shell-write-targets.js +274 -0
- package/dist/hooks/tools.d.ts +20 -2
- package/dist/hooks/tools.js +30 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/init-deposit/init-deposit.js +3 -0
- package/dist/init-deposit/refresh.js +7 -0
- package/dist/init-deposit/runtime-writers.d.ts +14 -0
- package/dist/init-deposit/runtime-writers.js +33 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/issue-ingest.d.ts +6 -1
- package/dist/intake/issue-ingest.js +83 -4
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/intake/reconcile-issues.js +13 -13
- package/dist/lifecycle/brief-envelope.d.ts +25 -0
- package/dist/lifecycle/brief-envelope.js +42 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/evaluate.js +14 -5
- package/dist/literal-acceptance/index.d.ts +1 -1
- package/dist/literal-acceptance/index.js +1 -1
- package/dist/literal-acceptance/run.d.ts +2 -0
- package/dist/literal-acceptance/run.js +19 -1
- package/dist/orphan-active/candidate-scope.d.ts +53 -0
- package/dist/orphan-active/candidate-scope.js +157 -0
- package/dist/orphan-active/evaluate.d.ts +52 -0
- package/dist/orphan-active/evaluate.js +250 -128
- package/dist/orphan-active/index.d.ts +2 -0
- package/dist/orphan-active/index.js +2 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/merge-approval-head.js +7 -6
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/value-feedback.js +9 -18
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-closing-keywords/gh.js +32 -9
- package/dist/pr-closing-keywords/main.d.ts +1 -0
- package/dist/pr-closing-keywords/main.js +68 -3
- package/dist/pr-closing-keywords/types.d.ts +2 -0
- package/dist/pr-merge-readiness/gh.js +32 -9
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +42 -11
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +14 -3
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +23 -9
- package/dist/product-first-done-gate/types.js +2 -0
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/consumer-hard-stops.d.ts +48 -0
- package/dist/release/consumer-hard-stops.js +140 -0
- package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
- package/dist/release/consumer-readiness-disclosure.js +51 -0
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.js +3 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +74 -0
- package/dist/release/run-consumer-readiness.d.ts +15 -0
- package/dist/release/run-consumer-readiness.js +24 -0
- package/dist/release/types.d.ts +20 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +186 -4
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/run-summary/types.d.ts +2 -2
- package/dist/scm/build-command.d.ts +2 -2
- package/dist/scm/build-command.js +2 -2
- package/dist/scm/call-shape.d.ts +25 -0
- package/dist/scm/call-shape.js +59 -0
- package/dist/scm/call.d.ts +7 -4
- package/dist/scm/call.js +48 -9
- package/dist/scm/design-critique-chip.d.ts +1 -0
- package/dist/scm/design-critique-chip.js +22 -6
- package/dist/scm/gh-rest.d.ts +34 -13
- package/dist/scm/gh-rest.js +168 -15
- package/dist/scm/index.d.ts +2 -0
- package/dist/scm/index.js +2 -0
- package/dist/scm/readiness.d.ts +2 -0
- package/dist/scm/readiness.js +28 -4
- package/dist/scm/spawn-status.d.ts +33 -0
- package/dist/scm/spawn-status.js +53 -0
- package/dist/scope/acceptance-evidence.d.ts +1 -1
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +4 -14
- package/dist/session/ac-pass-banking.d.ts +2 -2
- package/dist/session/ac-pass-banking.js +2 -2
- package/dist/session/git.d.ts +24 -0
- package/dist/session/git.js +138 -2
- package/dist/session/host-session-owner.d.ts +53 -0
- package/dist/session/host-session-owner.js +89 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/occupancy.d.ts +259 -13
- package/dist/session/occupancy.js +877 -33
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +5 -1
- package/dist/session/session-start.js +32 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/session/verify-session-ritual.d.ts +11 -0
- package/dist/session/verify-session-ritual.js +60 -15
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +8 -0
- package/dist/swarm/launch.js +63 -43
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/evaluate/worktrees.js +153 -6
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/umbrella-current-shape/index.d.ts +51 -3
- package/dist/umbrella-current-shape/index.js +106 -18
- package/dist/validate-content/deposit-required.d.ts +39 -0
- package/dist/validate-content/deposit-required.js +147 -0
- package/dist/validate-content/index.d.ts +1 -0
- package/dist/validate-content/index.js +1 -0
- package/dist/validate-content/validate-links.d.ts +2 -3
- package/dist/validate-content/validate-links.js +29 -2
- package/dist/vbrief-activate/activate.d.ts +7 -2
- package/dist/vbrief-activate/activate.js +26 -13
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/evaluate.d.ts +9 -0
- package/dist/verify-ac/evaluate.js +32 -9
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/deposit-closure.d.ts +23 -0
- package/dist/verify-source/deposit-closure.js +162 -0
- package/dist/verify-source/index.d.ts +2 -0
- package/dist/verify-source/index.js +2 -0
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/verify-source/semantic-single-source.d.ts +36 -0
- package/dist/verify-source/semantic-single-source.js +349 -0
- package/package.json +7 -3
|
@@ -2,13 +2,42 @@
|
|
|
2
2
|
* Pure stdin → payload parse for hook-dispatch (#2734 / #2738 / #2950).
|
|
3
3
|
* No process I/O — operates on an already-read string.
|
|
4
4
|
*/
|
|
5
|
+
import { firstString, record, toolInputRecord } from "./payload.js";
|
|
5
6
|
const UTF8_BOM = "\uFEFF";
|
|
6
7
|
const APPLY_PATCH_BEGIN_MARKER = "*** Begin Patch";
|
|
7
8
|
/** Single-file Add/Update only — other *** … File: ops must fail closed (#2738 Greptile). */
|
|
8
9
|
const APPLY_PATCH_MUTATION_LINE_RE = /^\*\*\* (Add File|Update File|Delete File|Move File|Rename File): (.+)$/gm;
|
|
10
|
+
/**
|
|
11
|
+
* Canonical apply_patch spells a rename as `*** Update File:` followed by
|
|
12
|
+
* `*** Move to:`. The destination is a mutation target in its own right, so it
|
|
13
|
+
* must reach root admission. Synthesis refuses when applyPatchMutationPaths
|
|
14
|
+
* reports more than one unique path, so a Move-to destination is not dropped
|
|
15
|
+
* from the single-target contract (#3614 / #3794).
|
|
16
|
+
*/
|
|
17
|
+
const APPLY_PATCH_MOVE_DESTINATION_RE = /^\*\*\* Move to: (.+)$/gm;
|
|
9
18
|
export function stripUtf8Bom(raw) {
|
|
10
19
|
return raw.startsWith(UTF8_BOM) ? raw.slice(UTF8_BOM.length) : raw;
|
|
11
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Paths named by ApplyPatch mutation headers, plus `*** Move to:` rename
|
|
23
|
+
* destinations. Order preserved, duplicates dropped.
|
|
24
|
+
*/
|
|
25
|
+
export function applyPatchMutationPaths(text) {
|
|
26
|
+
const paths = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const push = (raw) => {
|
|
29
|
+
const path = raw?.trim();
|
|
30
|
+
if (!path || seen.has(path))
|
|
31
|
+
return;
|
|
32
|
+
seen.add(path);
|
|
33
|
+
paths.push(path);
|
|
34
|
+
};
|
|
35
|
+
for (const match of text.matchAll(APPLY_PATCH_MUTATION_LINE_RE))
|
|
36
|
+
push(match[2]);
|
|
37
|
+
for (const match of text.matchAll(APPLY_PATCH_MOVE_DESTINATION_RE))
|
|
38
|
+
push(match[1]);
|
|
39
|
+
return paths;
|
|
40
|
+
}
|
|
12
41
|
function trySynthesizeFreeFormApplyPatch(normalized) {
|
|
13
42
|
if (!normalized.includes(APPLY_PATCH_BEGIN_MARKER))
|
|
14
43
|
return null;
|
|
@@ -22,6 +51,10 @@ function trySynthesizeFreeFormApplyPatch(normalized) {
|
|
|
22
51
|
}
|
|
23
52
|
if (mutations.length !== 1)
|
|
24
53
|
return null;
|
|
54
|
+
// Count headers plus Move-to destinations so a rename cannot collapse to one
|
|
55
|
+
// checked path (#3614 / #3794). Reuses applyPatchMutationPaths; no second parser.
|
|
56
|
+
if (applyPatchMutationPaths(normalized).length !== 1)
|
|
57
|
+
return null;
|
|
25
58
|
const sole = mutations[0];
|
|
26
59
|
if (sole === undefined || (sole.op !== "Add File" && sole.op !== "Update File"))
|
|
27
60
|
return null;
|
|
@@ -36,6 +69,65 @@ function trySynthesizeFreeFormApplyPatch(normalized) {
|
|
|
36
69
|
context: {},
|
|
37
70
|
};
|
|
38
71
|
}
|
|
72
|
+
function declaredWritePathFromParsed(payload) {
|
|
73
|
+
const input = record(payload);
|
|
74
|
+
if (input === null)
|
|
75
|
+
return null;
|
|
76
|
+
const toolInput = toolInputRecord(input);
|
|
77
|
+
return firstString([
|
|
78
|
+
toolInput?.file_path,
|
|
79
|
+
toolInput?.filePath,
|
|
80
|
+
toolInput?.path,
|
|
81
|
+
input.file_path,
|
|
82
|
+
input.filePath,
|
|
83
|
+
input.path,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
function applyPatchBodyTextFromParsed(payload) {
|
|
87
|
+
const input = record(payload);
|
|
88
|
+
if (input === null)
|
|
89
|
+
return null;
|
|
90
|
+
const toolInput = toolInputRecord(input);
|
|
91
|
+
return firstString([
|
|
92
|
+
toolInput?.patch,
|
|
93
|
+
toolInput?.unified_diff,
|
|
94
|
+
toolInput?.diff,
|
|
95
|
+
input.patch,
|
|
96
|
+
input.unified_diff,
|
|
97
|
+
input.diff,
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
function withToolInputPath(parsed, path) {
|
|
101
|
+
const input = record(parsed);
|
|
102
|
+
if (input === null)
|
|
103
|
+
return parsed;
|
|
104
|
+
for (const key of ["tool_input", "toolInput", "input", "arguments"]) {
|
|
105
|
+
const nested = record(input[key]);
|
|
106
|
+
if (nested !== null) {
|
|
107
|
+
return { ...input, [key]: { ...nested, path } };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { ...input, tool_input: { path } };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Valid-JSON hosts (Codex) never hit the JSON.parse catch arm, so the free-form
|
|
114
|
+
* extractor was unreached. Call it on the patch body when no path was declared.
|
|
115
|
+
* Does not replace the parsed payload; only fills tool_input.path.
|
|
116
|
+
*/
|
|
117
|
+
function attachSynthesizedApplyPatchPath(parsed) {
|
|
118
|
+
if (declaredWritePathFromParsed(parsed) !== null)
|
|
119
|
+
return parsed;
|
|
120
|
+
const patch = applyPatchBodyTextFromParsed(parsed);
|
|
121
|
+
if (patch === null)
|
|
122
|
+
return parsed;
|
|
123
|
+
const synthesized = trySynthesizeFreeFormApplyPatch(patch);
|
|
124
|
+
if (synthesized === null)
|
|
125
|
+
return parsed;
|
|
126
|
+
const synthesizedPath = declaredWritePathFromParsed(synthesized.payload);
|
|
127
|
+
if (synthesizedPath === null)
|
|
128
|
+
return parsed;
|
|
129
|
+
return withToolInputPath(parsed, synthesizedPath);
|
|
130
|
+
}
|
|
39
131
|
/**
|
|
40
132
|
* Parse host hook stdin text into a payload + parse context.
|
|
41
133
|
* Empty → stdinEmpty; invalid JSON without free-form ApplyPatch → parseFailed.
|
|
@@ -49,7 +141,8 @@ export function parseHookStdin(raw) {
|
|
|
49
141
|
return { payload: {}, context: { stdinEmpty: true } };
|
|
50
142
|
}
|
|
51
143
|
try {
|
|
52
|
-
|
|
144
|
+
const parsed = JSON.parse(normalized);
|
|
145
|
+
return { payload: attachSynthesizedApplyPatchPath(parsed), context: {} };
|
|
53
146
|
}
|
|
54
147
|
catch {
|
|
55
148
|
const synthesized = trySynthesizeFreeFormApplyPatch(normalized);
|
|
@@ -12,13 +12,27 @@
|
|
|
12
12
|
* *forms* instead, and see `content/contracts/path-write-fence.md`.
|
|
13
13
|
*
|
|
14
14
|
* Residual known-open (recognition, not resolution): `python -c`,
|
|
15
|
-
* `cmd /c copy`, obfuscated `bash -c`.
|
|
15
|
+
* `cmd /c copy`, obfuscated `bash -c`. Tree-wide destructive git
|
|
16
|
+
* (`reset --hard`, `clean -f`, `checkout -f`/`switch -f`, `stash drop`/`clear`)
|
|
17
|
+
* is classified separately by {@link classifyGitDestructive} and is always
|
|
18
|
+
* fail-closed at the dispatcher, independent of `shellDestForms`.
|
|
16
19
|
*
|
|
17
20
|
* Commit-time active-scope is out of this slice (#3438), and ⊗ do not weaken
|
|
18
21
|
* Edit to match Shell. Keep this sentence on ONE line: acceptance clause 4 is
|
|
19
22
|
* verified by a literal token search, so wrapping it silently fails the gate.
|
|
20
23
|
*/
|
|
21
24
|
export type ProductDestFormKind = "git-checkout" | "git-restore" | "rm" | "rmdir";
|
|
25
|
+
export type GitDestructiveKind = "git-reset-hard" | "git-clean" | "git-checkout-force" | "git-stash-drop";
|
|
26
|
+
export interface GitDestructiveForm {
|
|
27
|
+
readonly kind: GitDestructiveKind;
|
|
28
|
+
/** Relocating context paths from `-C` / `--git-dir` / `--work-tree` / `GIT_DIR` / `GIT_WORK_TREE`. */
|
|
29
|
+
readonly relocators: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* True when the command is compound or the relocator is opaque (`GIT_CONFIG*`,
|
|
32
|
+
* `-c core.workTree`). The dispatcher denies these; it cannot prove a fixture.
|
|
33
|
+
*/
|
|
34
|
+
readonly unprovable: boolean;
|
|
35
|
+
}
|
|
22
36
|
export interface ProductDestForm {
|
|
23
37
|
readonly kind: ProductDestFormKind;
|
|
24
38
|
readonly path: string;
|
|
@@ -43,4 +57,9 @@ export declare function payloadWithInjectedWriteTarget(payload: unknown, destPat
|
|
|
43
57
|
*/
|
|
44
58
|
export declare function destFormHasExpansion(path: string): boolean;
|
|
45
59
|
export declare function classifyProductDestForms(command: string): ProductDestForm[];
|
|
60
|
+
/**
|
|
61
|
+
* Tree-wide destructive git — always-on at the dispatcher, not behind
|
|
62
|
+
* `shellDestForms`. Empty when the command is not one of those forms.
|
|
63
|
+
*/
|
|
64
|
+
export declare function classifyGitDestructive(command: string): GitDestructiveForm[];
|
|
46
65
|
//# sourceMappingURL=dest-form.d.ts.map
|
package/dist/hooks/dest-form.js
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
* *forms* instead, and see `content/contracts/path-write-fence.md`.
|
|
13
13
|
*
|
|
14
14
|
* Residual known-open (recognition, not resolution): `python -c`,
|
|
15
|
-
* `cmd /c copy`, obfuscated `bash -c`.
|
|
15
|
+
* `cmd /c copy`, obfuscated `bash -c`. Tree-wide destructive git
|
|
16
|
+
* (`reset --hard`, `clean -f`, `checkout -f`/`switch -f`, `stash drop`/`clear`)
|
|
17
|
+
* is classified separately by {@link classifyGitDestructive} and is always
|
|
18
|
+
* fail-closed at the dispatcher, independent of `shellDestForms`.
|
|
16
19
|
*
|
|
17
20
|
* Commit-time active-scope is out of this slice (#3438), and ⊗ do not weaken
|
|
18
21
|
* Edit to match Shell. Keep this sentence on ONE line: acceptance clause 4 is
|
|
@@ -113,13 +116,14 @@ export function classifyProductDestForms(command) {
|
|
|
113
116
|
// bypass.
|
|
114
117
|
//
|
|
115
118
|
// ⊗ Recognition is NOT total either, and this is not a security boundary.
|
|
116
|
-
// Only four verbs are recognized, and a non-literal verb (`\rm`,
|
|
117
|
-
// defeats the tokenizer, so the fail-closed branch below only
|
|
118
|
-
// verb is still legible.
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
119
|
+
// Only four dest-form verbs are recognized, and a non-literal verb (`\rm`,
|
|
120
|
+
// `rm${IFS}x`) defeats the tokenizer, so the fail-closed branch below only
|
|
121
|
+
// fires when the verb is still legible. Tree-wide destructive git is handled
|
|
122
|
+
// by classifyGitDestructive (always-on). Remaining fail-OPEN mutators:
|
|
123
|
+
// `git checkout` without `--` or `-f`, `mv`, `sed -i`, `>` redirection, and
|
|
124
|
+
// interpreters (`bash -c`, `python -c`). See the threat model in
|
|
125
|
+
// content/contracts/path-write-fence.md: this is a guardrail for careless
|
|
126
|
+
// agents, not a boundary against adversarial ones.
|
|
123
127
|
if (segments.length === 1 && !hasUnsupportedSyntax(cmd)) {
|
|
124
128
|
return classifySimpleDestForms(segments[0] ?? "");
|
|
125
129
|
}
|
|
@@ -136,6 +140,29 @@ export function classifyProductDestForms(command) {
|
|
|
136
140
|
expansion: true,
|
|
137
141
|
}));
|
|
138
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Tree-wide destructive git — always-on at the dispatcher, not behind
|
|
145
|
+
* `shellDestForms`. Empty when the command is not one of those forms.
|
|
146
|
+
*/
|
|
147
|
+
export function classifyGitDestructive(command) {
|
|
148
|
+
const cmd = command.trim();
|
|
149
|
+
if (cmd.length === 0)
|
|
150
|
+
return [];
|
|
151
|
+
const segments = splitCommandSegments(cmd);
|
|
152
|
+
const compound = segments.length > 1 || hasUnsupportedSyntax(cmd);
|
|
153
|
+
const found = [];
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
for (const seg of segments) {
|
|
156
|
+
const form = classifyGitDestructiveSegment(seg);
|
|
157
|
+
if (form === null)
|
|
158
|
+
continue;
|
|
159
|
+
if (seen.has(form.kind))
|
|
160
|
+
continue;
|
|
161
|
+
seen.add(form.kind);
|
|
162
|
+
found.push(compound ? { ...form, unprovable: true } : form);
|
|
163
|
+
}
|
|
164
|
+
return found;
|
|
165
|
+
}
|
|
139
166
|
/** Classify one command with no operators, no grouping, and no substitution. */
|
|
140
167
|
function classifySimpleDestForms(segment) {
|
|
141
168
|
const found = [];
|
|
@@ -334,9 +361,33 @@ function isGitWorkTreeAssign(token) {
|
|
|
334
361
|
// A config FILE override can set core.worktree out of sight.
|
|
335
362
|
return key === "GIT_CONFIG" || key === "GIT_CONFIG_GLOBAL" || key === "GIT_CONFIG_SYSTEM";
|
|
336
363
|
}
|
|
364
|
+
function gitDirOrWorkTreeValue(token) {
|
|
365
|
+
const eq = token.indexOf("=");
|
|
366
|
+
if (eq <= 0)
|
|
367
|
+
return null;
|
|
368
|
+
const key = token.slice(0, eq);
|
|
369
|
+
const value = token.slice(eq + 1);
|
|
370
|
+
if ((key === "GIT_WORK_TREE" || key === "GIT_DIR") && value.length > 0)
|
|
371
|
+
return value;
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
337
374
|
function skipPrefix(tokens) {
|
|
338
375
|
let i = 0;
|
|
339
376
|
let gitContext = false;
|
|
377
|
+
let opaqueContext = false;
|
|
378
|
+
const relocators = [];
|
|
379
|
+
const noteAssign = (tok) => {
|
|
380
|
+
const reloc = gitDirOrWorkTreeValue(tok);
|
|
381
|
+
if (reloc !== null) {
|
|
382
|
+
gitContext = true;
|
|
383
|
+
relocators.push(reloc);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (isGitWorkTreeAssign(tok)) {
|
|
387
|
+
gitContext = true;
|
|
388
|
+
opaqueContext = true;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
340
391
|
const consumeAssigns = () => {
|
|
341
392
|
while (i < tokens.length) {
|
|
342
393
|
const tok = tokens[i];
|
|
@@ -351,8 +402,7 @@ function skipPrefix(tokens) {
|
|
|
351
402
|
}
|
|
352
403
|
if (!isEnvAssign(tok))
|
|
353
404
|
break;
|
|
354
|
-
|
|
355
|
-
gitContext = true;
|
|
405
|
+
noteAssign(tok);
|
|
356
406
|
i++;
|
|
357
407
|
}
|
|
358
408
|
};
|
|
@@ -380,15 +430,14 @@ function skipPrefix(tokens) {
|
|
|
380
430
|
continue;
|
|
381
431
|
}
|
|
382
432
|
if (isEnvAssign(tok)) {
|
|
383
|
-
|
|
384
|
-
gitContext = true;
|
|
433
|
+
noteAssign(tok);
|
|
385
434
|
i++;
|
|
386
435
|
continue;
|
|
387
436
|
}
|
|
388
437
|
break;
|
|
389
438
|
}
|
|
390
439
|
}
|
|
391
|
-
return { i, gitContext };
|
|
440
|
+
return { i, gitContext, relocators, opaqueContext };
|
|
392
441
|
}
|
|
393
442
|
/**
|
|
394
443
|
* Skip git's global options, reporting whether any of them RELOCATES the work
|
|
@@ -400,31 +449,51 @@ function skipPrefix(tokens) {
|
|
|
400
449
|
function skipGitGlobals(tokens, start) {
|
|
401
450
|
let i = start;
|
|
402
451
|
let gitContext = false;
|
|
452
|
+
let opaqueContext = false;
|
|
453
|
+
const relocators = [];
|
|
403
454
|
while (i < tokens.length) {
|
|
404
455
|
const t = tokens[i];
|
|
405
456
|
if (t === undefined || !t.startsWith("-"))
|
|
406
|
-
return { i, gitContext };
|
|
457
|
+
return { i, gitContext, relocators, opaqueContext };
|
|
407
458
|
if (t === "-c" || t === "--config-env") {
|
|
408
|
-
if (isCoreWorkTreeConfig(tokens[i + 1]))
|
|
459
|
+
if (isCoreWorkTreeConfig(tokens[i + 1])) {
|
|
409
460
|
gitContext = true;
|
|
461
|
+
opaqueContext = true;
|
|
462
|
+
}
|
|
410
463
|
i += 2;
|
|
411
464
|
continue;
|
|
412
465
|
}
|
|
413
466
|
if (t.startsWith("--config-env=")) {
|
|
414
|
-
if (isCoreWorkTreeConfig(t.slice("--config-env=".length)))
|
|
467
|
+
if (isCoreWorkTreeConfig(t.slice("--config-env=".length))) {
|
|
415
468
|
gitContext = true;
|
|
469
|
+
opaqueContext = true;
|
|
470
|
+
}
|
|
416
471
|
i++;
|
|
417
472
|
continue;
|
|
418
473
|
}
|
|
419
474
|
if (t === "-C" || t === "--work-tree" || t === "--git-dir") {
|
|
420
475
|
gitContext = true;
|
|
476
|
+
const val = tokens[i + 1];
|
|
477
|
+
if (val !== undefined)
|
|
478
|
+
relocators.push(val);
|
|
421
479
|
i += 2;
|
|
422
480
|
continue;
|
|
423
481
|
}
|
|
424
|
-
if (t.startsWith("--work-tree=")
|
|
425
|
-
|
|
426
|
-
(t.
|
|
482
|
+
if (t.startsWith("--work-tree=")) {
|
|
483
|
+
gitContext = true;
|
|
484
|
+
relocators.push(t.slice("--work-tree=".length));
|
|
485
|
+
i++;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (t.startsWith("--git-dir=")) {
|
|
427
489
|
gitContext = true;
|
|
490
|
+
relocators.push(t.slice("--git-dir=".length));
|
|
491
|
+
i++;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (t.startsWith("-C") && t.length > 2) {
|
|
495
|
+
gitContext = true;
|
|
496
|
+
relocators.push(t.slice(2));
|
|
428
497
|
i++;
|
|
429
498
|
continue;
|
|
430
499
|
}
|
|
@@ -437,14 +506,16 @@ function skipGitGlobals(tokens, start) {
|
|
|
437
506
|
continue;
|
|
438
507
|
}
|
|
439
508
|
if (t.startsWith("-c")) {
|
|
440
|
-
if (isCoreWorkTreeConfig(t.slice(2)))
|
|
509
|
+
if (isCoreWorkTreeConfig(t.slice(2))) {
|
|
441
510
|
gitContext = true;
|
|
511
|
+
opaqueContext = true;
|
|
512
|
+
}
|
|
442
513
|
i++;
|
|
443
514
|
continue;
|
|
444
515
|
}
|
|
445
516
|
i++;
|
|
446
517
|
}
|
|
447
|
-
return { i, gitContext };
|
|
518
|
+
return { i, gitContext, relocators, opaqueContext };
|
|
448
519
|
}
|
|
449
520
|
function pathsAfterDashDash(tokens, start) {
|
|
450
521
|
const dash = tokens.indexOf("--", start);
|
|
@@ -469,6 +540,72 @@ function dests(kind, paths, unsafe = false) {
|
|
|
469
540
|
.filter((path) => path.length > 0)
|
|
470
541
|
.map((path) => (unsafe ? { kind, path, expansion: true } : { kind, path }));
|
|
471
542
|
}
|
|
543
|
+
function tokensHaveFlag(tokens, start, names) {
|
|
544
|
+
for (let i = start; i < tokens.length; i++) {
|
|
545
|
+
const t = tokens[i];
|
|
546
|
+
if (t === undefined)
|
|
547
|
+
break;
|
|
548
|
+
if (names.has(t))
|
|
549
|
+
return true;
|
|
550
|
+
for (const name of names) {
|
|
551
|
+
if (name.startsWith("--") && t.startsWith(`${name}=`))
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
function tokensHaveShortForce(tokens, start) {
|
|
558
|
+
for (let i = start; i < tokens.length; i++) {
|
|
559
|
+
const t = tokens[i];
|
|
560
|
+
if (t === undefined || t === "--")
|
|
561
|
+
break;
|
|
562
|
+
if (t === "-f" || t === "-B")
|
|
563
|
+
return true;
|
|
564
|
+
if (t.startsWith("-") && !t.startsWith("--") && t.includes("f"))
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
function classifyGitDestructiveSegment(segment) {
|
|
570
|
+
const tokens = tokenizeSegment(segment.trim());
|
|
571
|
+
const prefixSkip = skipPrefix(tokens);
|
|
572
|
+
let i = prefixSkip.i;
|
|
573
|
+
const binRaw = tokens[i];
|
|
574
|
+
if (binRaw === undefined)
|
|
575
|
+
return null;
|
|
576
|
+
const bin = binRaw.replace(/^[({!]+/, "").toLowerCase();
|
|
577
|
+
if (bin !== "git" && bin !== "git.exe")
|
|
578
|
+
return null;
|
|
579
|
+
const skipped = skipGitGlobals(tokens, i + 1);
|
|
580
|
+
i = skipped.i;
|
|
581
|
+
const sub = tokens[i]?.toLowerCase();
|
|
582
|
+
if (sub === undefined)
|
|
583
|
+
return null;
|
|
584
|
+
let kind = null;
|
|
585
|
+
if (sub === "reset" && tokensHaveFlag(tokens, i + 1, new Set(["--hard"]))) {
|
|
586
|
+
kind = "git-reset-hard";
|
|
587
|
+
}
|
|
588
|
+
else if (sub === "clean" &&
|
|
589
|
+
(tokensHaveFlag(tokens, i + 1, new Set(["--force", "-f"])) ||
|
|
590
|
+
tokensHaveShortForce(tokens, i + 1))) {
|
|
591
|
+
kind = "git-clean";
|
|
592
|
+
}
|
|
593
|
+
else if ((sub === "checkout" || sub === "switch") &&
|
|
594
|
+
(tokensHaveFlag(tokens, i + 1, new Set(["--force", "-f", "-B"])) ||
|
|
595
|
+
tokensHaveShortForce(tokens, i + 1))) {
|
|
596
|
+
kind = "git-checkout-force";
|
|
597
|
+
}
|
|
598
|
+
else if (sub === "stash") {
|
|
599
|
+
const action = tokens[i + 1]?.toLowerCase();
|
|
600
|
+
if (action === "drop" || action === "clear")
|
|
601
|
+
kind = "git-stash-drop";
|
|
602
|
+
}
|
|
603
|
+
if (kind === null)
|
|
604
|
+
return null;
|
|
605
|
+
const relocators = [...prefixSkip.relocators, ...skipped.relocators];
|
|
606
|
+
const opaque = prefixSkip.opaqueContext || skipped.opaqueContext;
|
|
607
|
+
return { kind, relocators, unprovable: opaque };
|
|
608
|
+
}
|
|
472
609
|
/**
|
|
473
610
|
* `-c core.workTree=...` / `--config-env` relocates the work tree through git
|
|
474
611
|
* config. Resolution depends on the git dir, which this classifier does not
|
|
@@ -7,7 +7,7 @@ import { type DetectWorkSelection, type RitualRunner, type VerifyResult } from "
|
|
|
7
7
|
import { type HookPayloadContext } from "./classify/index.js";
|
|
8
8
|
import { type ActiveScopeInspection } from "./scope.js";
|
|
9
9
|
export { ASSIST_SESSION_POSTURE_ENV, hookReadOnlyFromPayload, isAssistPosture, isEphemeralSpawn, isExploreSpawn, isReadOnlyHookContext, } from "./readonly.js";
|
|
10
|
-
export { DIRECT_WRITE_HOOK_MATCHER, DIRECT_WRITE_TOOL_NAMES, isDirectWriteTool, isMcpTool, isShellTool, isSpawnTool, MCP_HOOK_MATCHER, MCP_PUSH_MERGE_BARE_NAMES, READ_ONLY_HOOK_ENV, SHELL_HOOK_MATCHER, SHELL_TOOL_NAMES, SPAWN_HOOK_MATCHER, SPAWN_TOOL_NAMES, } from "./tools.js";
|
|
10
|
+
export { DIRECT_WRITE_HOOK_MATCHER, DIRECT_WRITE_TOOL_NAMES, GROK_MUTATION_TOOL_CATALOG, GROK_NON_MUTATION_TOOLS, isDirectWriteTool, isMcpTool, isShellTool, isSpawnTool, MCP_HOOK_MATCHER, MCP_PUSH_MERGE_BARE_NAMES, matcherHasLiteralToken, READ_ONLY_HOOK_ENV, SHELL_HOOK_MATCHER, SHELL_TOOL_NAMES, SPAWN_HOOK_MATCHER, SPAWN_TOOL_NAMES, } from "./tools.js";
|
|
11
11
|
export declare const HOOK_HOSTS: readonly ["claude", "grok", "cursor", "codex"];
|
|
12
12
|
export type HookHost = (typeof HOOK_HOSTS)[number];
|
|
13
13
|
export declare const HOOK_EVENTS: readonly ["session.start", "session.compact", "tool.before"];
|
|
@@ -24,7 +24,15 @@ export type HookDecisionCode = "session-start" | "session-start-disabled" | "ses
|
|
|
24
24
|
/** Host closed stdin with zero bytes — integration failure, not a policy gate (#2864). */
|
|
25
25
|
| "stdin-empty" | "ritual-not-ready"
|
|
26
26
|
/** Product-path write while another live session occupies this worktree (#3433). */
|
|
27
|
-
| "occupancy-occupied"
|
|
27
|
+
| "occupancy-occupied"
|
|
28
|
+
/** A supported host omitted or malformed its cooperative payload owner (#3611). */
|
|
29
|
+
| "occupancy-identity-unavailable"
|
|
30
|
+
/** Host payload identity conflicts with explicit/environment identity (#3611). */
|
|
31
|
+
| "occupancy-identity-conflict"
|
|
32
|
+
/** Live lease and exact verified ritual state name different owners (#3611). */
|
|
33
|
+
| "occupancy-ritual-mismatch"
|
|
34
|
+
/** Write target resolved to a Git repository that does not share git-common-dir (#3794). */
|
|
35
|
+
| "foreign-repository-deny" | "scope-not-ready" | "write-propose-ready"
|
|
28
36
|
/** Allowlisted assist/scratch write without active xBRIEF (#1802). */
|
|
29
37
|
| "write-assist-scratch-ready" | "write-ready" | "read-only-deny" | "spawn-explore-ready"
|
|
30
38
|
/** Non-lifecycle assist/docs spawn allowed without active xBRIEF (#3080). */
|
|
@@ -36,7 +44,11 @@ export type HookDecisionCode = "session-start" | "session-start-disabled" | "ses
|
|
|
36
44
|
/** UAT lease / human-origin grant denial (#2944). */
|
|
37
45
|
| "authz-uat-deny" | "authz-grant-missing" | "authz-grant-origin-reject" | "authz-grant-scope-deny" | "authz-grant-expired" | "authz-grant-revoked" | "authz-grant-single-use-spent"
|
|
38
46
|
/** Slash-command intent ceiling denial (#1193). */
|
|
39
|
-
| "intent-ceiling-deny"
|
|
47
|
+
| "intent-ceiling-deny"
|
|
48
|
+
/** Tree-wide destructive git aimed at this checkout (#3917). */
|
|
49
|
+
| "git-destructive-deny"
|
|
50
|
+
/** Tree-wide destructive git aimed at an absolute out-of-root fixture (#3917). */
|
|
51
|
+
| "git-destructive-fixture";
|
|
40
52
|
export interface HookDecision {
|
|
41
53
|
readonly verdict: HookVerdict;
|
|
42
54
|
readonly code: HookDecisionCode;
|
|
@@ -46,6 +58,8 @@ export interface HookDecision {
|
|
|
46
58
|
readonly projectRoot: string;
|
|
47
59
|
readonly message: string;
|
|
48
60
|
readonly scopePath: string | null;
|
|
61
|
+
/** Complete host tool input replacement for an exact lifecycle command (#3611). */
|
|
62
|
+
readonly updatedInput?: Readonly<Record<string, unknown>>;
|
|
49
63
|
}
|
|
50
64
|
export interface HookDispatchInput {
|
|
51
65
|
readonly host: HookHost;
|
|
@@ -106,6 +120,12 @@ export interface HookPolicySeams {
|
|
|
106
120
|
readonly loadAuthzGrants?: (projectRoot: string, state: AuthzState) => readonly HumanOriginGrant[];
|
|
107
121
|
/** When false, skip writing `.deft/authz/audit.jsonl` (tests). Default true. */
|
|
108
122
|
readonly authzAudit?: boolean;
|
|
123
|
+
/** Test seam for restricting Task lifecycle rewrites to the Directive source repo. */
|
|
124
|
+
readonly runningInsideDeftRepo?: (projectRoot: string) => boolean;
|
|
125
|
+
/** Test seam for execution-root realpath binding in lifecycle rewrites. */
|
|
126
|
+
readonly realpathLifecycleExecutionRoot?: (path: string) => string;
|
|
127
|
+
/** Test seam for Windows drive-only execution-root payloads (#2787). */
|
|
128
|
+
readonly lifecycleExecutionPlatform?: NodeJS.Platform;
|
|
109
129
|
}
|
|
110
130
|
/** POSIX-ish project-relative path for lifecycle matching. */
|
|
111
131
|
export declare function toProjectRelativePosix(projectRoot: string, targetPath: string): string;
|
|
@@ -153,6 +173,41 @@ export declare function isAssistScratchWrite(projectRoot: string, targetPath: st
|
|
|
153
173
|
/** Normalize hook project-root resolution on Windows (doubled drive + MSYS `/c/...`). */
|
|
154
174
|
export declare function normalizeHookProjectRoot(path: string): string;
|
|
155
175
|
export declare function projectRootFromHookPayload(payload: unknown, fallback: string): string;
|
|
176
|
+
/** Interpolate both roots so a deny names the tree that was judged (#3794). */
|
|
177
|
+
export declare function formatHookRootNote(payloadRoot: string, effectiveRoot: string): string;
|
|
178
|
+
/** Why a mutation target was refused, so the deny message can be specific (#3794). */
|
|
179
|
+
export type EffectiveHookRootRefusal =
|
|
180
|
+
/** Both common-dirs resolved and differ: a proven separate repository. */
|
|
181
|
+
"foreign-repository"
|
|
182
|
+
/** `payloadRoot` is a repository but the target's identity could not be read. */
|
|
183
|
+
| "unproven-identity"
|
|
184
|
+
/** Members of one mutation resolved to two admitted worktrees. */
|
|
185
|
+
| "worktree-span";
|
|
186
|
+
export interface EffectiveHookRootAdmission {
|
|
187
|
+
readonly root: string;
|
|
188
|
+
/** True when the target was refused and the dispatcher must deny (#3794). */
|
|
189
|
+
readonly foreign: boolean;
|
|
190
|
+
readonly candidate: string | null;
|
|
191
|
+
readonly refusal: EffectiveHookRootRefusal | null;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Admit the Git toplevel of the write target only when it shares
|
|
195
|
+
* `--git-common-dir` with `payloadRoot`.
|
|
196
|
+
*
|
|
197
|
+
* Identity resolves to one of three outcomes, not two. A `payloadRoot` that is
|
|
198
|
+
* not a Git repository never posed a containment question, so it keeps today's
|
|
199
|
+
* payloadRoot gating. A `payloadRoot` that *is* a repository, paired with a
|
|
200
|
+
* target that resolved to some other toplevel whose identity cannot be read, is
|
|
201
|
+
* a question asked and left unanswered — that fails closed rather than
|
|
202
|
+
* inheriting payloadRoot's occupancy and ritual state (#3794).
|
|
203
|
+
*/
|
|
204
|
+
export declare function admitEffectiveHookRoot(payloadRoot: string, writeTarget: string | null, runGit: GitRunner): EffectiveHookRootAdmission;
|
|
205
|
+
/**
|
|
206
|
+
* Admit every mutation target and require one effectiveRoot. A refused member
|
|
207
|
+
* short-circuits; a span of two admitted worktrees is also refused so
|
|
208
|
+
* occupancy/ritual cannot follow only the declared ApplyPatch path (#3794).
|
|
209
|
+
*/
|
|
210
|
+
export declare function admitMutationTargetSet(payloadRoot: string, targets: readonly string[], runGit: GitRunner): EffectiveHookRootAdmission;
|
|
156
211
|
export declare function isHookHost(value: string): value is HookHost;
|
|
157
212
|
export declare function isHookEvent(value: string): value is HookEvent;
|
|
158
213
|
/** Decide a normalized event using only the P0 direct-write policy. */
|