@deftai/directive-core 0.106.0 → 0.108.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/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -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 +232 -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/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -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 +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +69 -2
- package/dist/hooks/dispatcher.js +609 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- 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/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- 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/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -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/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/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +9 -18
- package/dist/policy/write-fence.js +3 -0
- 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-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- 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 +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/preflight-cache/evaluate.js +9 -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 +53 -12
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -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 +19 -0
- package/dist/release/types.d.ts +14 -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 +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +38 -0
- package/dist/scm/design-critique-chip.js +187 -0
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -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 +4 -1
- package/dist/scm/readiness.js +35 -5
- 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/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +42 -0
- package/dist/session/git.js +219 -3
- package/dist/session/occupancy.d.ts +239 -12
- package/dist/session/occupancy.js +820 -27
- 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 +17 -1
- package/dist/session/session-start.js +49 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +33 -3
- package/dist/session/verify-session-ritual.js +89 -19
- 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 +77 -0
- package/dist/swarm/launch.js +248 -38
- 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/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/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/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- 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-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -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/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- 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/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
* Cases are pure data: raw stdin or structured payload → expected classification.
|
|
5
5
|
* Core tests and CLI hook-dispatch tests import this module (shared corpus collapse).
|
|
6
6
|
*/
|
|
7
|
+
import type { ExactLifecycleVerb, HookHostIdentityStatus } from "../classify/host-session-identity.js";
|
|
7
8
|
import type { ClassifyHookHost, HookWriteIntent } from "../classify/types.js";
|
|
8
9
|
export type FixtureOs = "win32" | "posix";
|
|
9
|
-
export type FixtureToolFamily = "Write" | "ApplyPatch" | "Task" | "StrReplace" | "other";
|
|
10
|
+
export type FixtureToolFamily = "Write" | "ApplyPatch" | "Task" | "StrReplace" | "Shell" | "other";
|
|
10
11
|
export interface HookFixtureCase {
|
|
11
12
|
readonly id: string;
|
|
12
13
|
readonly host: ClassifyHookHost;
|
|
13
14
|
readonly os: FixtureOs;
|
|
14
15
|
readonly tool: FixtureToolFamily;
|
|
15
|
-
/**
|
|
16
|
+
/** Issue/regression tags for the behavior class this case freezes. */
|
|
16
17
|
readonly regression: readonly string[];
|
|
17
18
|
/** Structured payload when the host sends JSON. */
|
|
18
19
|
readonly payload?: unknown;
|
|
@@ -25,6 +26,19 @@ export interface HookFixtureCase {
|
|
|
25
26
|
/** When raw is set: after parseHookStdin. */
|
|
26
27
|
readonly stdinEmpty?: boolean;
|
|
27
28
|
readonly parseFailed?: boolean;
|
|
29
|
+
/** Cooperative payload owner resolution expected for #3611 cases. */
|
|
30
|
+
readonly hostIdentity?: {
|
|
31
|
+
readonly status: HookHostIdentityStatus;
|
|
32
|
+
readonly sessionId: string | null;
|
|
33
|
+
};
|
|
34
|
+
/** Exact lifecycle classification/rewrite expected for #3611 shell cases. */
|
|
35
|
+
readonly lifecycle?: {
|
|
36
|
+
readonly verb: ExactLifecycleVerb | null;
|
|
37
|
+
readonly requestedSessionId: string;
|
|
38
|
+
readonly resultKind: "rewrite" | "conflict" | null;
|
|
39
|
+
readonly rewrittenCommand?: string;
|
|
40
|
+
readonly updatedInput?: Readonly<Record<string, unknown>>;
|
|
41
|
+
};
|
|
28
42
|
};
|
|
29
43
|
readonly notes?: string;
|
|
30
44
|
}
|
|
@@ -527,6 +527,181 @@ export const HOOK_FIXTURE_CASES = [
|
|
|
527
527
|
writeTargetPath: "C:\\Repos\\proj\\src\\edit-me.ts",
|
|
528
528
|
},
|
|
529
529
|
},
|
|
530
|
+
// --- Cooperative host-session identity and exact lifecycle rewrite (#3611) ---
|
|
531
|
+
{
|
|
532
|
+
id: "codex-posix-shell-lifecycle-rewrite",
|
|
533
|
+
host: "codex",
|
|
534
|
+
os: "posix",
|
|
535
|
+
tool: "Shell",
|
|
536
|
+
regression: ["#3611"],
|
|
537
|
+
payload: {
|
|
538
|
+
tool_name: "Bash",
|
|
539
|
+
session_id: "session-a",
|
|
540
|
+
tool_input: {
|
|
541
|
+
command: "deft session:start --rearm",
|
|
542
|
+
description: "Re-arm the mutation session",
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
expected: {
|
|
546
|
+
toolName: "Bash",
|
|
547
|
+
writeIntent: "shell",
|
|
548
|
+
writeTargetPath: null,
|
|
549
|
+
hostIdentity: {
|
|
550
|
+
status: "ok",
|
|
551
|
+
sessionId: "host:codex:v1:c2Vzc2lvbi1h",
|
|
552
|
+
},
|
|
553
|
+
lifecycle: {
|
|
554
|
+
verb: "session:start",
|
|
555
|
+
requestedSessionId: "host:codex:v1:c2Vzc2lvbi1h",
|
|
556
|
+
resultKind: "rewrite",
|
|
557
|
+
rewrittenCommand: "deft session:start --rearm --session-id=host:codex:v1:c2Vzc2lvbi1h",
|
|
558
|
+
updatedInput: {
|
|
559
|
+
command: "deft session:start --rearm --session-id=host:codex:v1:c2Vzc2lvbi1h",
|
|
560
|
+
description: "Re-arm the mutation session",
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: "codex-posix-shell-lifecycle-owner-conflict",
|
|
567
|
+
host: "codex",
|
|
568
|
+
os: "posix",
|
|
569
|
+
tool: "Shell",
|
|
570
|
+
regression: ["#3611"],
|
|
571
|
+
payload: {
|
|
572
|
+
tool_name: "Bash",
|
|
573
|
+
session_id: "session-a",
|
|
574
|
+
tool_input: {
|
|
575
|
+
command: "deft session:start --session-id=host:codex:v1:b3RoZXI",
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
expected: {
|
|
579
|
+
toolName: "Bash",
|
|
580
|
+
writeIntent: "shell",
|
|
581
|
+
writeTargetPath: null,
|
|
582
|
+
hostIdentity: {
|
|
583
|
+
status: "ok",
|
|
584
|
+
sessionId: "host:codex:v1:c2Vzc2lvbi1h",
|
|
585
|
+
},
|
|
586
|
+
lifecycle: {
|
|
587
|
+
verb: "session:start",
|
|
588
|
+
requestedSessionId: "host:codex:v1:c2Vzc2lvbi1h",
|
|
589
|
+
resultKind: "conflict",
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: "codex-posix-shell-lifecycle-ambiguous",
|
|
595
|
+
host: "codex",
|
|
596
|
+
os: "posix",
|
|
597
|
+
tool: "Shell",
|
|
598
|
+
regression: ["#3611"],
|
|
599
|
+
payload: {
|
|
600
|
+
tool_name: "Bash",
|
|
601
|
+
session_id: "session-a",
|
|
602
|
+
tool_input: { command: "deft session:start && echo unsafe" },
|
|
603
|
+
},
|
|
604
|
+
expected: {
|
|
605
|
+
toolName: "Bash",
|
|
606
|
+
writeIntent: "shell",
|
|
607
|
+
writeTargetPath: null,
|
|
608
|
+
hostIdentity: {
|
|
609
|
+
status: "ok",
|
|
610
|
+
sessionId: "host:codex:v1:c2Vzc2lvbi1h",
|
|
611
|
+
},
|
|
612
|
+
lifecycle: {
|
|
613
|
+
verb: null,
|
|
614
|
+
requestedSessionId: "host:codex:v1:c2Vzc2lvbi1h",
|
|
615
|
+
resultKind: null,
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
id: "cursor-posix-shell-identity-conflict",
|
|
621
|
+
host: "cursor",
|
|
622
|
+
os: "posix",
|
|
623
|
+
tool: "Shell",
|
|
624
|
+
regression: ["#3611"],
|
|
625
|
+
payload: {
|
|
626
|
+
tool_name: "Shell",
|
|
627
|
+
conversation_id: "conversation-a",
|
|
628
|
+
session_id: "conversation-b",
|
|
629
|
+
tool_input: { command: "task session:ready" },
|
|
630
|
+
},
|
|
631
|
+
expected: {
|
|
632
|
+
toolName: "Shell",
|
|
633
|
+
writeIntent: "shell",
|
|
634
|
+
writeTargetPath: null,
|
|
635
|
+
hostIdentity: { status: "conflict", sessionId: null },
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
// #3599 item 5: the refresh verb is only useful if the id it renews is the
|
|
640
|
+
// one later hook processes compare against. #3611's bridge supplies that.
|
|
641
|
+
id: "cursor-posix-shell-occupancy-heartbeat-rewrite",
|
|
642
|
+
host: "cursor",
|
|
643
|
+
os: "posix",
|
|
644
|
+
tool: "Shell",
|
|
645
|
+
regression: ["#3599", "#3611"],
|
|
646
|
+
payload: {
|
|
647
|
+
tool_name: "Bash",
|
|
648
|
+
conversation_id: "session-a",
|
|
649
|
+
tool_input: { command: "task occupancy:heartbeat" },
|
|
650
|
+
},
|
|
651
|
+
expected: {
|
|
652
|
+
toolName: "Bash",
|
|
653
|
+
writeIntent: "shell",
|
|
654
|
+
writeTargetPath: null,
|
|
655
|
+
hostIdentity: {
|
|
656
|
+
status: "ok",
|
|
657
|
+
sessionId: "host:cursor:v1:c2Vzc2lvbi1h",
|
|
658
|
+
},
|
|
659
|
+
lifecycle: {
|
|
660
|
+
verb: "occupancy:heartbeat",
|
|
661
|
+
requestedSessionId: "host:cursor:v1:c2Vzc2lvbi1h",
|
|
662
|
+
resultKind: "rewrite",
|
|
663
|
+
rewrittenCommand: "task occupancy:heartbeat -- --session-id=host:cursor:v1:c2Vzc2lvbi1h",
|
|
664
|
+
updatedInput: {
|
|
665
|
+
command: "task occupancy:heartbeat -- --session-id=host:cursor:v1:c2Vzc2lvbi1h",
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: "claude-posix-shell-identity-missing",
|
|
672
|
+
host: "claude",
|
|
673
|
+
os: "posix",
|
|
674
|
+
tool: "Shell",
|
|
675
|
+
regression: ["#3611"],
|
|
676
|
+
payload: {
|
|
677
|
+
tool_name: "Bash",
|
|
678
|
+
tool_input: { command: "deft occupancy:release" },
|
|
679
|
+
},
|
|
680
|
+
expected: {
|
|
681
|
+
toolName: "Bash",
|
|
682
|
+
writeIntent: "shell",
|
|
683
|
+
writeTargetPath: null,
|
|
684
|
+
hostIdentity: { status: "missing", sessionId: null },
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
id: "claude-posix-shell-identity-malformed",
|
|
689
|
+
host: "claude",
|
|
690
|
+
os: "posix",
|
|
691
|
+
tool: "Shell",
|
|
692
|
+
regression: ["#3611"],
|
|
693
|
+
payload: {
|
|
694
|
+
tool_name: "Bash",
|
|
695
|
+
session_id: " malformed ",
|
|
696
|
+
tool_input: { command: "deft occupancy:release" },
|
|
697
|
+
},
|
|
698
|
+
expected: {
|
|
699
|
+
toolName: "Bash",
|
|
700
|
+
writeIntent: "shell",
|
|
701
|
+
writeTargetPath: null,
|
|
702
|
+
hostIdentity: { status: "invalid", sessionId: null },
|
|
703
|
+
},
|
|
704
|
+
},
|
|
530
705
|
// --- Regression: empty / missing tool name ---
|
|
531
706
|
{
|
|
532
707
|
id: "cursor-posix-stdin-empty",
|
package/dist/hooks/index.d.ts
CHANGED
package/dist/hooks/index.js
CHANGED
package/dist/hooks/scope.js
CHANGED
|
@@ -27,7 +27,9 @@ export function inspectActiveScope(projectRoot) {
|
|
|
27
27
|
candidates.sort();
|
|
28
28
|
let firstRejection = null;
|
|
29
29
|
for (const candidate of candidates) {
|
|
30
|
-
|
|
30
|
+
// #3736: origin freshness remains fail-closed at explicit xbrief:preflight.
|
|
31
|
+
// The host mutation path must stay local and render before its effective timeout.
|
|
32
|
+
const result = evaluate(candidate, { skipOriginFreshness: true });
|
|
31
33
|
if (result.exitCode === 0) {
|
|
32
34
|
return { ready: true, path: candidate, message: result.message };
|
|
33
35
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HookHost } from "../hooks/dispatcher.js";
|
|
2
2
|
import { type HostHooksPolicy } from "../policy/host-hooks.js";
|
|
3
3
|
import type { InitDepositIo } from "./constants.js";
|
|
4
|
+
import { type HookRuntimeTravelSeams } from "./hook-runtime-travel.js";
|
|
4
5
|
export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
|
|
5
6
|
export declare const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
|
|
6
7
|
export declare const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
@@ -41,7 +42,7 @@ export interface AgentHookDepositResult {
|
|
|
41
42
|
readonly changedPaths: AgentHookPath[];
|
|
42
43
|
}
|
|
43
44
|
/** Merge Directive-owned project hook entries without replacing user configuration. */
|
|
44
|
-
export declare function writeAgentHookDeposit(projectRoot: string, io?: InitDepositIo, hostHooksPolicy?: HostHooksPolicy): AgentHookDepositResult;
|
|
45
|
+
export declare function writeAgentHookDeposit(projectRoot: string, io?: InitDepositIo, hostHooksPolicy?: HostHooksPolicy, travelSeams?: HookRuntimeTravelSeams): AgentHookDepositResult;
|
|
45
46
|
/** Read-only registration probe shared by verify and doctor. */
|
|
46
47
|
export declare function inspectAgentHookDeposit(projectRoot: string, hostHooksPolicy?: HostHooksPolicy): AgentHookInspection[];
|
|
47
48
|
//# sourceMappingURL=agent-hooks.d.ts.map
|
|
@@ -4,6 +4,7 @@ import { assertDepositContained } from "../deposit/contain.js";
|
|
|
4
4
|
import { containedRemove, containedRename, containedWrite } from "../fs/contained-write.js";
|
|
5
5
|
import { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
|
|
6
6
|
import { isHostHookDepositEnabled, loadHostHooksPolicyFromProject, } from "../policy/host-hooks.js";
|
|
7
|
+
import { inspectHookRuntimeTravel } from "./hook-runtime-travel.js";
|
|
7
8
|
export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
|
|
8
9
|
export const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
|
|
9
10
|
export const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
@@ -252,7 +253,7 @@ function writeJsonIfChanged(projectRoot, path, payload, kind) {
|
|
|
252
253
|
return true;
|
|
253
254
|
}
|
|
254
255
|
/** Merge Directive-owned project hook entries without replacing user configuration. */
|
|
255
|
-
export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefined }, hostHooksPolicy = loadHostHooksPolicyFromProject(projectRoot)) {
|
|
256
|
+
export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefined }, hostHooksPolicy = loadHostHooksPolicyFromProject(projectRoot), travelSeams = {}) {
|
|
256
257
|
const changedPaths = [];
|
|
257
258
|
const strippedPaths = [];
|
|
258
259
|
const definitions = [
|
|
@@ -332,6 +333,12 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
|
|
|
332
333
|
if (changedPaths.length === 0 && strippedPaths.length === 0 && adaptersRemoved === 0) {
|
|
333
334
|
io.printf("Directive agent hooks already current.\n");
|
|
334
335
|
}
|
|
336
|
+
// #3785: the registration is trackable and the deposit that implements it is
|
|
337
|
+
// born-ignored, so warn when a clone would inherit the fence without a way to
|
|
338
|
+
// obtain the runtime it names.
|
|
339
|
+
const travel = inspectHookRuntimeTravel(projectRoot, definitions.map((definition) => ({ host: definition.host, path: definition.path })), hostHooksPolicy, travelSeams);
|
|
340
|
+
if (travel.warning !== null)
|
|
341
|
+
io.printf(`${travel.warning}\n`);
|
|
335
342
|
return {
|
|
336
343
|
changed: changedPaths.length + strippedPaths.length + adaptersRemoved > 0,
|
|
337
344
|
changedPaths: [...changedPaths, ...strippedPaths],
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook-runtime travel warning (#3785).
|
|
3
|
+
*
|
|
4
|
+
* Directive keeps the agent-hook registration files trackable by design and
|
|
5
|
+
* born-ignores the deposit that implements them. When a registration travels
|
|
6
|
+
* with the tree and nothing in that tree lets a clone obtain `deft-hook`, the
|
|
7
|
+
* registration arrives and the runtime does not. A fresh clone, CI runner, or
|
|
8
|
+
* container without a global install then fail-closes every mutation on an
|
|
9
|
+
* opaque exit 127, and no Directive code runs to explain it.
|
|
10
|
+
*
|
|
11
|
+
* "Travels" means tracked OR trackable-and-not-ignored. A first `deft init`
|
|
12
|
+
* writes the registration unstaged, so a tracked-only probe would go quiet on
|
|
13
|
+
* exactly the run that precedes the commit carrying the fence into every clone.
|
|
14
|
+
*
|
|
15
|
+
* The committed `package.json` dependency on `@deftai/directive` (#2264) is the
|
|
16
|
+
* existing reconstitution anchor. With it a clone can install the runtime the
|
|
17
|
+
* registration names; without it the registration names a command no clone can
|
|
18
|
+
* host. The anchor is read from the commit, not from the working tree or the
|
|
19
|
+
* index: a consumer can commit the generated registration and leave the
|
|
20
|
+
* manifest merely present, staged, or edited, and that is the case that strands
|
|
21
|
+
* the clone.
|
|
22
|
+
*
|
|
23
|
+
* Warn-only. Refusing `deft init` / `deft update` on this condition would be
|
|
24
|
+
* the same lockout from the other side, so the deposit still writes and
|
|
25
|
+
* reports. Absence of the runtime stays a deny — fail-open-on-absence was
|
|
26
|
+
* refuted as unimplementable and as a bypass primitive (#3156).
|
|
27
|
+
*
|
|
28
|
+
* Refs #3785, #3736, #3571, #2752, #2264, #3156.
|
|
29
|
+
*/
|
|
30
|
+
import { type HookHost, type HostHooksPolicy } from "../policy/host-hooks.js";
|
|
31
|
+
import { type PinReadResult } from "../resolution/pin.js";
|
|
32
|
+
/**
|
|
33
|
+
* Hosts whose registration is deposited `failClosed: true`. Only these turn a
|
|
34
|
+
* missing runtime into a denial; the rest fail open and merely lose the gate.
|
|
35
|
+
*/
|
|
36
|
+
export declare const FAIL_CLOSED_HOOK_HOSTS: readonly HookHost[];
|
|
37
|
+
/** Manifest carrying the committed runtime anchor. */
|
|
38
|
+
export declare const RUNTIME_ANCHOR_MANIFEST = "package.json";
|
|
39
|
+
/**
|
|
40
|
+
* Spec prefixes that name a location instead of a release. Each resolves
|
|
41
|
+
* against something outside the tree -- a path, a symlink target, a workspace
|
|
42
|
+
* member -- so the clone that receives the registration cannot install from it.
|
|
43
|
+
*/
|
|
44
|
+
export declare const LOCATION_SPEC_PREFIXES: readonly ["file:", "link:", "portal:", "workspace:", "git+file:"];
|
|
45
|
+
export type GitLsFilesProbe = (projectDir: string, paths: readonly string[]) => string | null;
|
|
46
|
+
/** Contents of a path as committed at `HEAD`, or null when there is none. */
|
|
47
|
+
export type CommittedFileProbe = (projectDir: string, path: string) => string | null;
|
|
48
|
+
export interface HookRuntimeTravelSeams {
|
|
49
|
+
readonly gitLsFiles?: GitLsFilesProbe;
|
|
50
|
+
readonly readCommittedFile?: CommittedFileProbe;
|
|
51
|
+
readonly readPin?: (projectRoot: string) => PinReadResult;
|
|
52
|
+
}
|
|
53
|
+
/** One deposited registration considered by the probe. */
|
|
54
|
+
export interface HookRegistrationRef {
|
|
55
|
+
readonly host: HookHost;
|
|
56
|
+
readonly path: string;
|
|
57
|
+
}
|
|
58
|
+
export interface HookRuntimeTravelResult {
|
|
59
|
+
/** Enabled registrations that travel with this repository. */
|
|
60
|
+
readonly travelingRegistrations: readonly string[];
|
|
61
|
+
/** Subset of {@link travelingRegistrations} deposited fail-closed. */
|
|
62
|
+
readonly failClosedRegistrations: readonly string[];
|
|
63
|
+
/** A committed manifest declares `@deftai/directive`, so a clone can install it. */
|
|
64
|
+
readonly runtimeTravels: boolean;
|
|
65
|
+
/** Null when the pairing is safe or git could not answer. */
|
|
66
|
+
readonly warning: string | null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Report whether any enabled agent-hook registration travels with this tree
|
|
70
|
+
* while no manifest travelling alongside it lets a clone install the runtime it
|
|
71
|
+
* names.
|
|
72
|
+
*
|
|
73
|
+
* Disabled hosts are excluded: their registration is stripped, so a leftover
|
|
74
|
+
* copy cannot deny anything.
|
|
75
|
+
*/
|
|
76
|
+
export declare function inspectHookRuntimeTravel(projectRoot: string, registrations: readonly HookRegistrationRef[], hostHooksPolicy: HostHooksPolicy, seams?: HookRuntimeTravelSeams): HookRuntimeTravelResult;
|
|
77
|
+
//# sourceMappingURL=hook-runtime-travel.d.ts.map
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook-runtime travel warning (#3785).
|
|
3
|
+
*
|
|
4
|
+
* Directive keeps the agent-hook registration files trackable by design and
|
|
5
|
+
* born-ignores the deposit that implements them. When a registration travels
|
|
6
|
+
* with the tree and nothing in that tree lets a clone obtain `deft-hook`, the
|
|
7
|
+
* registration arrives and the runtime does not. A fresh clone, CI runner, or
|
|
8
|
+
* container without a global install then fail-closes every mutation on an
|
|
9
|
+
* opaque exit 127, and no Directive code runs to explain it.
|
|
10
|
+
*
|
|
11
|
+
* "Travels" means tracked OR trackable-and-not-ignored. A first `deft init`
|
|
12
|
+
* writes the registration unstaged, so a tracked-only probe would go quiet on
|
|
13
|
+
* exactly the run that precedes the commit carrying the fence into every clone.
|
|
14
|
+
*
|
|
15
|
+
* The committed `package.json` dependency on `@deftai/directive` (#2264) is the
|
|
16
|
+
* existing reconstitution anchor. With it a clone can install the runtime the
|
|
17
|
+
* registration names; without it the registration names a command no clone can
|
|
18
|
+
* host. The anchor is read from the commit, not from the working tree or the
|
|
19
|
+
* index: a consumer can commit the generated registration and leave the
|
|
20
|
+
* manifest merely present, staged, or edited, and that is the case that strands
|
|
21
|
+
* the clone.
|
|
22
|
+
*
|
|
23
|
+
* Warn-only. Refusing `deft init` / `deft update` on this condition would be
|
|
24
|
+
* the same lockout from the other side, so the deposit still writes and
|
|
25
|
+
* reports. Absence of the runtime stays a deny — fail-open-on-absence was
|
|
26
|
+
* refuted as unimplementable and as a bypass primitive (#3156).
|
|
27
|
+
*
|
|
28
|
+
* Refs #3785, #3736, #3571, #2752, #2264, #3156.
|
|
29
|
+
*/
|
|
30
|
+
import { execFileSync } from "node:child_process";
|
|
31
|
+
import { disableHostHooksInvocation, isHostHookDepositEnabled, } from "../policy/host-hooks.js";
|
|
32
|
+
import { PIN_DEPENDENCY_NAME, readPin } from "../resolution/pin.js";
|
|
33
|
+
/**
|
|
34
|
+
* Hosts whose registration is deposited `failClosed: true`. Only these turn a
|
|
35
|
+
* missing runtime into a denial; the rest fail open and merely lose the gate.
|
|
36
|
+
*/
|
|
37
|
+
export const FAIL_CLOSED_HOOK_HOSTS = ["cursor"];
|
|
38
|
+
/** Manifest carrying the committed runtime anchor. */
|
|
39
|
+
export const RUNTIME_ANCHOR_MANIFEST = "package.json";
|
|
40
|
+
/**
|
|
41
|
+
* Spec prefixes that name a location instead of a release. Each resolves
|
|
42
|
+
* against something outside the tree -- a path, a symlink target, a workspace
|
|
43
|
+
* member -- so the clone that receives the registration cannot install from it.
|
|
44
|
+
*/
|
|
45
|
+
export const LOCATION_SPEC_PREFIXES = [
|
|
46
|
+
"file:",
|
|
47
|
+
"link:",
|
|
48
|
+
"portal:",
|
|
49
|
+
"workspace:",
|
|
50
|
+
"git+file:",
|
|
51
|
+
];
|
|
52
|
+
/** Prefixes any clone can fetch: a registry alias or a remote repository. */
|
|
53
|
+
const REMOTE_SPEC_PREFIXES = [
|
|
54
|
+
"npm:",
|
|
55
|
+
"http:",
|
|
56
|
+
"https:",
|
|
57
|
+
"git:",
|
|
58
|
+
"git+http:",
|
|
59
|
+
"git+https:",
|
|
60
|
+
"git+ssh:",
|
|
61
|
+
"github:",
|
|
62
|
+
"gitlab:",
|
|
63
|
+
"bitbucket:",
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Whether a clone could install `deft-hook` from this spec.
|
|
67
|
+
*
|
|
68
|
+
* Allowlist, not denylist. Location forms are open-ended -- `file:`,
|
|
69
|
+
* `git+file:`, `../directive`, `/opt/directive`, `C:\src\directive`, `~/dev` --
|
|
70
|
+
* so enumerating them leaves the next shape uncovered, and an uncovered shape
|
|
71
|
+
* silences the warning. Anything that is not recognisably a registry release or
|
|
72
|
+
* a fetchable remote is therefore treated as not travelling. A bare
|
|
73
|
+
* `owner/repo` shorthand is refused on the same rule: it warns where the
|
|
74
|
+
* runtime might in fact arrive, which is the harmless direction for a
|
|
75
|
+
* warn-only probe.
|
|
76
|
+
*/
|
|
77
|
+
function reconstitutesForAClone(rawSpec) {
|
|
78
|
+
const spec = rawSpec.trim();
|
|
79
|
+
if (spec.length === 0)
|
|
80
|
+
return false;
|
|
81
|
+
const lower = spec.toLowerCase();
|
|
82
|
+
if (LOCATION_SPEC_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
83
|
+
return false;
|
|
84
|
+
if (REMOTE_SPEC_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
85
|
+
return true;
|
|
86
|
+
// Bare paths: relative, home-anchored, POSIX-absolute, or a Windows drive.
|
|
87
|
+
if (/^[.~/\\]/.test(spec) || /^[a-z]:[\\/]/i.test(spec))
|
|
88
|
+
return false;
|
|
89
|
+
// Anything else with a separator is a path or a repository shorthand, not a
|
|
90
|
+
// version, a range, or a dist-tag.
|
|
91
|
+
return !(spec.includes("/") || spec.includes("\\"));
|
|
92
|
+
}
|
|
93
|
+
function defaultGitLsFiles(projectDir, paths) {
|
|
94
|
+
try {
|
|
95
|
+
// Ask git rather than looking for `.git`: a project deposited into a
|
|
96
|
+
// subdirectory of a repository has no `.git` of its own, yet its
|
|
97
|
+
// registration is trackable by the parent. `ls-files` answers from any
|
|
98
|
+
// depth, relative to this directory, and declines outside a repository.
|
|
99
|
+
//
|
|
100
|
+
// `--cached` reports what is in the index, `--others --exclude-standard`
|
|
101
|
+
// what a first `deft init` just wrote and the next `git add` would sweep
|
|
102
|
+
// in; ignored paths are omitted, since they cannot reach a clone.
|
|
103
|
+
return execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "--", ...paths], {
|
|
104
|
+
cwd: projectDir,
|
|
105
|
+
encoding: "utf8",
|
|
106
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
107
|
+
windowsHide: true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// git absent, or not a repository: whether these paths travel has no answer
|
|
112
|
+
// here, so the caller stays silent rather than guessing.
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Paths that travel, as reported by git, or null when git could not answer. */
|
|
117
|
+
function travelingPaths(output) {
|
|
118
|
+
if (output === null)
|
|
119
|
+
return null;
|
|
120
|
+
return new Set(output
|
|
121
|
+
.split("\n")
|
|
122
|
+
.map((line) => line.trim())
|
|
123
|
+
.filter((line) => line.length > 0));
|
|
124
|
+
}
|
|
125
|
+
function defaultReadCommittedFile(projectDir, path) {
|
|
126
|
+
try {
|
|
127
|
+
// `HEAD:./<path>` resolves relative to this directory, so a project nested
|
|
128
|
+
// in a repository reads its own manifest rather than the parent's.
|
|
129
|
+
return execFileSync("git", ["show", `HEAD:./${path}`], {
|
|
130
|
+
cwd: projectDir,
|
|
131
|
+
encoding: "utf8",
|
|
132
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
133
|
+
windowsHide: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// No commit, no such path in it, or no repository: nothing is committed
|
|
138
|
+
// here that a clone would receive.
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** The `@deftai/directive` spec as committed, or null when none is. */
|
|
143
|
+
function committedRuntimeSpec(manifest) {
|
|
144
|
+
if (manifest === null)
|
|
145
|
+
return null;
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = JSON.parse(manifest);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
154
|
+
return null;
|
|
155
|
+
const pkg = parsed;
|
|
156
|
+
for (const block of ["devDependencies", "dependencies"]) {
|
|
157
|
+
const deps = pkg[block];
|
|
158
|
+
if (typeof deps !== "object" || deps === null || Array.isArray(deps))
|
|
159
|
+
continue;
|
|
160
|
+
const spec = deps[PIN_DEPENDENCY_NAME];
|
|
161
|
+
if (typeof spec === "string" && spec.trim().length > 0)
|
|
162
|
+
return spec.trim();
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
function isFailClosed(host) {
|
|
167
|
+
return FAIL_CLOSED_HOOK_HOSTS.includes(host);
|
|
168
|
+
}
|
|
169
|
+
function recoveryHosts(failClosed) {
|
|
170
|
+
const hosts = [...new Set(failClosed.map((entry) => entry.host))];
|
|
171
|
+
return hosts
|
|
172
|
+
.map((host) => `\`${disableHostHooksInvocation(` --host ${host} --confirm`)}\``)
|
|
173
|
+
.join(" / ");
|
|
174
|
+
}
|
|
175
|
+
function causeLine(anchor) {
|
|
176
|
+
if (anchor.kind === "uncommitted") {
|
|
177
|
+
return ` ${RUNTIME_ANCHOR_MANIFEST} declares ${PIN_DEPENDENCY_NAME} but is not committed, so a fresh`;
|
|
178
|
+
}
|
|
179
|
+
if (anchor.kind === "non-portable") {
|
|
180
|
+
return ` ${RUNTIME_ANCHOR_MANIFEST} pins ${PIN_DEPENDENCY_NAME} to \`${anchor.spec}\`, which no clone can install, so a fresh`;
|
|
181
|
+
}
|
|
182
|
+
return ` No ${RUNTIME_ANCHOR_MANIFEST} dependency on ${PIN_DEPENDENCY_NAME} travels with this tree, so a fresh`;
|
|
183
|
+
}
|
|
184
|
+
function fixLine(anchor) {
|
|
185
|
+
if (anchor.kind === "uncommitted") {
|
|
186
|
+
return ` Fix: commit ${RUNTIME_ANCHOR_MANIFEST} so the runtime travels with the registration.`;
|
|
187
|
+
}
|
|
188
|
+
if (anchor.kind === "non-portable") {
|
|
189
|
+
return ` Fix: pin ${PIN_DEPENDENCY_NAME} to a published version so a clone can install it.`;
|
|
190
|
+
}
|
|
191
|
+
return ` Fix: commit a ${RUNTIME_ANCHOR_MANIFEST} dependency on ${PIN_DEPENDENCY_NAME} so the runtime travels too.`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Judge the anchor from the commit, not the working tree or the index.
|
|
195
|
+
*
|
|
196
|
+
* A clone receives commits. A manifest that is merely present, merely staged,
|
|
197
|
+
* or edited-but-uncommitted declares the runtime to this machine only, and the
|
|
198
|
+
* consumer is free to commit the generated registration without it.
|
|
199
|
+
*/
|
|
200
|
+
function resolveAnchorState(committedSpec, workingSpec) {
|
|
201
|
+
if (committedSpec === null) {
|
|
202
|
+
return workingSpec === null ? { kind: "absent" } : { kind: "uncommitted" };
|
|
203
|
+
}
|
|
204
|
+
if (!reconstitutesForAClone(committedSpec)) {
|
|
205
|
+
return { kind: "non-portable", spec: committedSpec };
|
|
206
|
+
}
|
|
207
|
+
return { kind: "travels" };
|
|
208
|
+
}
|
|
209
|
+
function buildWarning(traveling, failClosed, anchor) {
|
|
210
|
+
const listed = traveling
|
|
211
|
+
.map((entry) => `${entry.path}${isFailClosed(entry.host) ? " (fail-closed)" : ""}`)
|
|
212
|
+
.join(", ");
|
|
213
|
+
const cause = causeLine(anchor);
|
|
214
|
+
const lines = [
|
|
215
|
+
`\u26a0 Hook registration travels without its runtime (#3785): ${listed}`,
|
|
216
|
+
cause,
|
|
217
|
+
" clone, CI runner, or container cannot obtain the `deft-hook` command these files name.",
|
|
218
|
+
];
|
|
219
|
+
if (failClosed.length > 0) {
|
|
220
|
+
lines.push(" Fail-closed hosts then deny every mutation on an opaque exit 127, and no Directive", " code runs to say why. There is no in-session recovery on a host without Node.");
|
|
221
|
+
}
|
|
222
|
+
lines.push(fixLine(anchor));
|
|
223
|
+
if (failClosed.length > 0) {
|
|
224
|
+
lines.push(` Or, accepting the capability cost: ${recoveryHosts(failClosed)}.`);
|
|
225
|
+
}
|
|
226
|
+
lines.push(" \u2297 Do not hand-edit `failClosed` in the deposited file -- the next `deft update` restores it.", " Detail: .deft/core/docs/hook-runtime-unavailable.md");
|
|
227
|
+
return lines.join("\n");
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Report whether any enabled agent-hook registration travels with this tree
|
|
231
|
+
* while no manifest travelling alongside it lets a clone install the runtime it
|
|
232
|
+
* names.
|
|
233
|
+
*
|
|
234
|
+
* Disabled hosts are excluded: their registration is stripped, so a leftover
|
|
235
|
+
* copy cannot deny anything.
|
|
236
|
+
*/
|
|
237
|
+
export function inspectHookRuntimeTravel(projectRoot, registrations, hostHooksPolicy, seams = {}) {
|
|
238
|
+
const enabled = registrations.filter((entry) => isHostHookDepositEnabled(entry.host, hostHooksPolicy));
|
|
239
|
+
const probePaths = [...enabled.map((entry) => entry.path), RUNTIME_ANCHOR_MANIFEST];
|
|
240
|
+
const gitLsFiles = seams.gitLsFiles ?? defaultGitLsFiles;
|
|
241
|
+
const traveling = travelingPaths(gitLsFiles(projectRoot, probePaths));
|
|
242
|
+
if (traveling === null) {
|
|
243
|
+
return {
|
|
244
|
+
travelingRegistrations: [],
|
|
245
|
+
failClosedRegistrations: [],
|
|
246
|
+
runtimeTravels: false,
|
|
247
|
+
warning: null,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// The two sides are judged asymmetrically, deliberately. A registration is
|
|
251
|
+
// dangerous as soon as it is trackable: one `git add` sends it to every
|
|
252
|
+
// clone. An anchor counts only once a commit carries it, because a clone
|
|
253
|
+
// receives commits -- present, staged, and edited-but-uncommitted manifests
|
|
254
|
+
// all declare the runtime to this machine alone.
|
|
255
|
+
const readCommittedFile = seams.readCommittedFile ?? defaultReadCommittedFile;
|
|
256
|
+
const committedSpec = committedRuntimeSpec(readCommittedFile(projectRoot, RUNTIME_ANCHOR_MANIFEST));
|
|
257
|
+
const anchor = resolveAnchorState(committedSpec, (seams.readPin ?? readPin)(projectRoot).rawSpec);
|
|
258
|
+
const runtimeTravels = anchor.kind === "travels";
|
|
259
|
+
const travelingRegistrations = enabled.filter((entry) => traveling.has(entry.path));
|
|
260
|
+
const failClosedRegistrations = travelingRegistrations.filter((entry) => isFailClosed(entry.host));
|
|
261
|
+
const unsafe = travelingRegistrations.length > 0 && !runtimeTravels;
|
|
262
|
+
return {
|
|
263
|
+
travelingRegistrations: travelingRegistrations.map((entry) => entry.path),
|
|
264
|
+
failClosedRegistrations: failClosedRegistrations.map((entry) => entry.path),
|
|
265
|
+
runtimeTravels,
|
|
266
|
+
warning: unsafe ? buildWarning(travelingRegistrations, failClosedRegistrations, anchor) : null,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=hook-runtime-travel.js.map
|