@deftai/directive-core 0.94.0 → 0.96.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authz/classify.js +485 -0
- package/dist/cache/archive.d.ts +134 -0
- package/dist/cache/archive.js +630 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/cache/main.js +298 -1
- package/dist/content-contracts/skills/helpers.d.ts +1 -1
- package/dist/content-contracts/skills/helpers.js +1 -0
- package/dist/doctor/main.js +41 -9
- package/dist/doctor/types.d.ts +2 -2
- package/dist/freshness/bind.d.ts +67 -0
- package/dist/freshness/bind.js +201 -0
- package/dist/freshness/cli.d.ts +30 -0
- package/dist/freshness/cli.js +180 -0
- package/dist/freshness/compare.d.ts +14 -0
- package/dist/freshness/compare.js +134 -0
- package/dist/freshness/generation.d.ts +44 -0
- package/dist/freshness/generation.js +172 -0
- package/dist/freshness/index.d.ts +13 -0
- package/dist/freshness/index.js +13 -0
- package/dist/freshness/report.d.ts +43 -0
- package/dist/freshness/report.js +139 -0
- package/dist/freshness/types.d.ts +70 -0
- package/dist/freshness/types.js +30 -0
- package/dist/handoff-evidence/index.d.ts +8 -0
- package/dist/handoff-evidence/index.js +7 -0
- package/dist/handoff-evidence/validate.d.ts +105 -0
- package/dist/handoff-evidence/validate.js +423 -0
- package/dist/hooks/dispatcher.d.ts +3 -0
- package/dist/hooks/dispatcher.js +12 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +5 -2
- package/dist/init-deposit/hygiene.d.ts +1 -1
- package/dist/init-deposit/hygiene.js +16 -4
- package/dist/init-deposit/init-deposit.d.ts +7 -1
- package/dist/init-deposit/init-deposit.js +24 -4
- package/dist/init-deposit/refresh.d.ts +9 -1
- package/dist/init-deposit/refresh.js +48 -5
- package/dist/init-deposit/scaffold.js +6 -5
- package/dist/parent-turn-shape/evaluate.d.ts +84 -0
- package/dist/parent-turn-shape/evaluate.js +353 -0
- package/dist/parent-turn-shape/index.d.ts +8 -0
- package/dist/parent-turn-shape/index.js +8 -0
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +35 -0
- package/dist/release-e2e/greenfield-python-free-smoke.js +7 -3
- package/dist/review-monitor/constants.js +3 -2
- package/dist/review-monitor/tier-detection.d.ts +7 -3
- package/dist/review-monitor/tier-detection.js +18 -1
- package/dist/review-monitor/verify.js +18 -0
- package/dist/scope/index.d.ts +2 -0
- package/dist/scope/index.js +2 -0
- package/dist/scope/main.d.ts +10 -0
- package/dist/scope/main.js +109 -24
- package/dist/scope/promote-from-issue.d.ts +49 -0
- package/dist/scope/promote-from-issue.js +367 -0
- package/dist/scope/promote-path.d.ts +39 -0
- package/dist/scope/promote-path.js +105 -0
- package/dist/session/ritual-entrypoint.d.ts +6 -2
- package/dist/session/ritual-entrypoint.js +15 -2
- package/dist/session/session-ready.js +31 -14
- package/dist/session/session-start.d.ts +2 -1
- package/dist/session/session-start.js +66 -3
- package/dist/session/verify-session-ritual.d.ts +4 -2
- package/dist/session/verify-session-ritual.js +11 -11
- package/dist/slash/product-set.js +4 -1
- package/dist/swarm/routing.d.ts +4 -2
- package/dist/swarm/routing.js +26 -4
- package/dist/swarm/verify-review-clean.d.ts +4 -3
- package/dist/swarm/verify-review-clean.js +28 -73
- package/dist/triage/actions/index.js +62 -2
- package/dist/triage/actions/types.d.ts +8 -1
- package/dist/triage/author-filter.d.ts +51 -0
- package/dist/triage/author-filter.js +152 -0
- package/dist/triage/classify/index.d.ts +9 -0
- package/dist/triage/classify/index.js +34 -2
- package/dist/triage/classify/label-mirror.d.ts +206 -0
- package/dist/triage/classify/label-mirror.js +914 -0
- package/dist/triage/help/registry-data.d.ts +49 -38
- package/dist/triage/help/registry-data.js +134 -39
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/render.d.ts +2 -0
- package/dist/triage/queue/render.js +6 -0
- package/dist/triage/queue/show.d.ts +1 -1
- package/dist/triage/queue/show.js +4 -2
- package/dist/vbrief-validate/project-definition.js +1 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +42 -0
- package/dist/verify-env/agent-hook-readiness.js +150 -0
- package/dist/verify-env/agent-hooks-live-probe.d.ts +16 -4
- package/dist/verify-env/agent-hooks-live-probe.js +120 -107
- package/dist/verify-env/agent-hooks.js +11 -3
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/package.json +11 -3
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { resolveFrameworkRootForProject } from "../doctor/paths.js";
|
|
3
|
+
import { loadHostHooksPolicyFromProject } from "../policy/host-hooks.js";
|
|
4
|
+
import { evaluateAgentHooks } from "./agent-hooks.js";
|
|
5
|
+
import { probeAgentHooksLive, } from "./agent-hooks-live-probe.js";
|
|
6
|
+
/** Stable CLI JSON projection; keep the four readiness dimensions orthogonal. */
|
|
7
|
+
export function agentHookReadinessJson(result) {
|
|
8
|
+
return {
|
|
9
|
+
ready: result.code === 0,
|
|
10
|
+
exit_code: result.code,
|
|
11
|
+
skipped: result.skipped,
|
|
12
|
+
live_status: result.liveStatus,
|
|
13
|
+
hosts: result.hosts.map((entry) => ({ ...entry })),
|
|
14
|
+
message: result.message,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Preserve fail-closed readiness semantics when an unexpected evaluator dependency throws. */
|
|
18
|
+
export function evaluateAgentHookReadinessSafely(projectRoot, evaluate = evaluateAgentHookReadiness) {
|
|
19
|
+
try {
|
|
20
|
+
return evaluate(projectRoot);
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
24
|
+
return {
|
|
25
|
+
code: 2,
|
|
26
|
+
message: `❌ deft agent hook readiness unavailable: ${detail}`,
|
|
27
|
+
stream: "stderr",
|
|
28
|
+
skipped: false,
|
|
29
|
+
liveStatus: "unavailable",
|
|
30
|
+
hosts: [],
|
|
31
|
+
registrations: [],
|
|
32
|
+
liveProbe: null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isConsumerContext(projectRoot) {
|
|
37
|
+
return resolve(projectRoot) !== resolve(resolveFrameworkRootForProject(projectRoot));
|
|
38
|
+
}
|
|
39
|
+
function registrationState(entry) {
|
|
40
|
+
if (entry.status === "healthy")
|
|
41
|
+
return "registered";
|
|
42
|
+
return entry.status;
|
|
43
|
+
}
|
|
44
|
+
function renderHosts(hosts) {
|
|
45
|
+
return hosts
|
|
46
|
+
.map((entry) => ` - ${entry.host}: registration=${entry.registration}; functionality=${entry.functionality}; ` +
|
|
47
|
+
`trust=${entry.trust}; interception=${entry.interception}`)
|
|
48
|
+
.join("\n");
|
|
49
|
+
}
|
|
50
|
+
function baseHostResult(entry, policy) {
|
|
51
|
+
if (!policy[entry.host]) {
|
|
52
|
+
return {
|
|
53
|
+
host: entry.host,
|
|
54
|
+
registration: "disabled",
|
|
55
|
+
functionality: "disabled",
|
|
56
|
+
trust: "disabled",
|
|
57
|
+
interception: "disabled",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
host: entry.host,
|
|
62
|
+
registration: registrationState(entry),
|
|
63
|
+
functionality: "not-run",
|
|
64
|
+
trust: entry.host === "codex" ? "manual-review-required" : "not-applicable",
|
|
65
|
+
interception: "not-directly-verified",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Fail-closed consumer readiness: structural registration first, installed-shim live probe second. */
|
|
69
|
+
export function evaluateAgentHookReadiness(projectRoot, options = {}) {
|
|
70
|
+
const root = resolve(projectRoot);
|
|
71
|
+
const consumerContext = options.consumerContext ?? isConsumerContext;
|
|
72
|
+
if (!consumerContext(root)) {
|
|
73
|
+
return {
|
|
74
|
+
code: 0,
|
|
75
|
+
message: "✓ deft agent hook readiness skipped: maintainer source checkout; consumer hook deposit is not applicable.",
|
|
76
|
+
stream: "stdout",
|
|
77
|
+
skipped: true,
|
|
78
|
+
liveStatus: "skipped",
|
|
79
|
+
hosts: [],
|
|
80
|
+
registrations: [],
|
|
81
|
+
liveProbe: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const policy = options.hostHooksPolicy ?? loadHostHooksPolicyFromProject(root);
|
|
85
|
+
const structural = (options.evaluateStructural ?? evaluateAgentHooks)(root, policy);
|
|
86
|
+
const hosts = structural.registrations.map((entry) => baseHostResult(entry, policy));
|
|
87
|
+
if (structural.code !== 0) {
|
|
88
|
+
return {
|
|
89
|
+
code: structural.code,
|
|
90
|
+
message: `${structural.message}\n${renderHosts(hosts)}`,
|
|
91
|
+
stream: "stderr",
|
|
92
|
+
skipped: false,
|
|
93
|
+
liveStatus: "not-run",
|
|
94
|
+
hosts,
|
|
95
|
+
registrations: structural.registrations,
|
|
96
|
+
liveProbe: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const enabledHosts = structural.registrations
|
|
100
|
+
.filter((entry) => policy[entry.host])
|
|
101
|
+
.map((entry) => entry.host);
|
|
102
|
+
const liveProbe = (options.probeLive ?? probeAgentHooksLive)(root, {
|
|
103
|
+
...options.liveProbeSeams,
|
|
104
|
+
hosts: enabledHosts,
|
|
105
|
+
});
|
|
106
|
+
const liveByHost = new Map(liveProbe.hosts.map((entry) => [entry.host, entry.status]));
|
|
107
|
+
const evaluatedHosts = hosts.map((entry) => {
|
|
108
|
+
if (entry.functionality === "disabled")
|
|
109
|
+
return entry;
|
|
110
|
+
const liveStatus = liveByHost.get(entry.host);
|
|
111
|
+
return {
|
|
112
|
+
...entry,
|
|
113
|
+
functionality: liveStatus === "functional"
|
|
114
|
+
? "functional"
|
|
115
|
+
: liveStatus === "unavailable"
|
|
116
|
+
? "unavailable"
|
|
117
|
+
: liveStatus === "non-functional"
|
|
118
|
+
? "non-functional"
|
|
119
|
+
: "not-run",
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
const allDisabled = enabledHosts.length === 0;
|
|
123
|
+
const liveStatus = allDisabled
|
|
124
|
+
? "disabled"
|
|
125
|
+
: liveProbe.code === 0
|
|
126
|
+
? "functional"
|
|
127
|
+
: liveProbe.code === 2
|
|
128
|
+
? "unavailable"
|
|
129
|
+
: "non-functional";
|
|
130
|
+
const trustReview = policy.codex
|
|
131
|
+
? "\n Codex trust: manual-review-required. Open `/hooks` and approve the exact project hook commands. " +
|
|
132
|
+
"The live probe validates the shim and codec, not host interception."
|
|
133
|
+
: "";
|
|
134
|
+
return {
|
|
135
|
+
code: liveProbe.code,
|
|
136
|
+
message: `${liveProbe.code === 0 ? "✓" : "❌"} deft agent hook readiness: ${liveProbe.message}\n` +
|
|
137
|
+
renderHosts(evaluatedHosts) +
|
|
138
|
+
trustReview +
|
|
139
|
+
(liveProbe.code === 0
|
|
140
|
+
? ""
|
|
141
|
+
: "\n If an affected host is unused, inspect `deft policy:show --field=hostHooks`, set `plan.policy.hostHooks.<host> = false`, and run `deft update`."),
|
|
142
|
+
stream: liveProbe.code === 0 ? "stdout" : "stderr",
|
|
143
|
+
skipped: false,
|
|
144
|
+
liveStatus,
|
|
145
|
+
hosts: evaluatedHosts,
|
|
146
|
+
registrations: structural.registrations,
|
|
147
|
+
liveProbe,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=agent-hook-readiness.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export
|
|
1
|
+
import { type HookEvent, type HookHost } from "../hooks/dispatcher.js";
|
|
2
|
+
export declare const LIVE_PROBE_CASE_TIMEOUT_MS = 1500;
|
|
3
|
+
export type AgentHookLiveProbeIssue = "hook-command-missing" | "spawn-failed" | "timed-out" | "empty-stdout" | "unparseable-json" | "missing-allow" | "missing-deny";
|
|
3
4
|
export interface AgentHookLiveProbeCase {
|
|
4
5
|
readonly host: HookHost;
|
|
5
6
|
readonly event: HookEvent;
|
|
@@ -7,26 +8,37 @@ export interface AgentHookLiveProbeCase {
|
|
|
7
8
|
readonly issue: AgentHookLiveProbeIssue;
|
|
8
9
|
readonly detail: string;
|
|
9
10
|
}
|
|
11
|
+
export type AgentHookLiveProbeHostStatus = "functional" | "non-functional" | "unavailable";
|
|
12
|
+
export interface AgentHookLiveProbeHostResult {
|
|
13
|
+
readonly host: HookHost;
|
|
14
|
+
readonly status: AgentHookLiveProbeHostStatus;
|
|
15
|
+
}
|
|
10
16
|
export interface AgentHookLiveProbeResult {
|
|
11
17
|
readonly code: 0 | 1 | 2;
|
|
12
18
|
readonly message: string;
|
|
13
19
|
readonly cases: readonly AgentHookLiveProbeCase[];
|
|
20
|
+
readonly hosts: readonly AgentHookLiveProbeHostResult[];
|
|
21
|
+
readonly durationMs: number;
|
|
14
22
|
}
|
|
15
23
|
export interface AgentHookLiveProbeSeams {
|
|
24
|
+
/** Enabled hosts to probe. Defaults to every deposited host. */
|
|
25
|
+
readonly hosts?: readonly HookHost[];
|
|
16
26
|
readonly resolveCommand?: (name: string) => string | null;
|
|
17
27
|
readonly spawnHook?: (input: {
|
|
18
28
|
readonly command: string;
|
|
19
29
|
readonly args: readonly string[];
|
|
20
30
|
readonly stdin: string;
|
|
21
31
|
readonly cwd: string;
|
|
22
|
-
readonly env
|
|
32
|
+
readonly env: NodeJS.ProcessEnv;
|
|
23
33
|
}) => {
|
|
24
34
|
readonly status: number;
|
|
25
35
|
readonly stdout: string;
|
|
26
36
|
readonly stderr: string;
|
|
37
|
+
readonly timedOut?: boolean;
|
|
27
38
|
};
|
|
28
39
|
}
|
|
40
|
+
/** Quote one Windows cmd.exe argument used by the installed hook shim. */
|
|
29
41
|
export declare function quoteWindowsCmdArg(value: string): string;
|
|
30
|
-
/**
|
|
42
|
+
/** Invoke the installed deft-hook shim and validate enabled host allow/deny codecs. */
|
|
31
43
|
export declare function probeAgentHooksLive(projectRoot: string, seams?: AgentHookLiveProbeSeams): AgentHookLiveProbeResult;
|
|
32
44
|
//# sourceMappingURL=agent-hooks-live-probe.d.ts.map
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { HOOK_HOSTS } from "../hooks/dispatcher.js";
|
|
3
4
|
import { READ_ONLY_HOOK_ENV } from "../hooks/tools.js";
|
|
4
5
|
import { DEFT_HOOK_COMMAND_MARKER } from "../init-deposit/agent-hooks.js";
|
|
5
6
|
import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
|
|
6
7
|
import { quoteWin32CommandForShell, resolveCommandOnPath, shouldUseShellForCommand, } from "./command-spawn.js";
|
|
7
|
-
const
|
|
8
|
+
export const LIVE_PROBE_CASE_TIMEOUT_MS = 1_500;
|
|
8
9
|
const LIVE_PROBE_EVENT = "tool.before";
|
|
10
|
+
function elapsedMs(started) {
|
|
11
|
+
return Math.max(0, Math.round(performance.now() - started));
|
|
12
|
+
}
|
|
9
13
|
function resolveHookCommand(seams) {
|
|
10
|
-
|
|
11
|
-
const deftHook = resolveCommand(DEFT_HOOK_COMMAND_MARKER);
|
|
12
|
-
if (deftHook !== null) {
|
|
13
|
-
return { command: deftHook, argsPrefix: [] };
|
|
14
|
-
}
|
|
15
|
-
const deft = resolveCommand("deft");
|
|
16
|
-
if (deft !== null) {
|
|
17
|
-
return { command: deft, argsPrefix: ["hook:dispatch"] };
|
|
18
|
-
}
|
|
19
|
-
const directive = resolveCommand("directive");
|
|
20
|
-
if (directive !== null) {
|
|
21
|
-
return { command: directive, argsPrefix: ["hook:dispatch"] };
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
14
|
+
return (seams.resolveCommand ?? resolveCommandOnPath)(DEFT_HOOK_COMMAND_MARKER);
|
|
24
15
|
}
|
|
16
|
+
/** Quote one Windows cmd.exe argument used by the installed hook shim. */
|
|
25
17
|
export function quoteWindowsCmdArg(value) {
|
|
26
18
|
const escaped = value.replace(/%/g, "%%");
|
|
27
19
|
if (!/[\s"&|<>^()]/.test(escaped)) {
|
|
@@ -31,27 +23,10 @@ export function quoteWindowsCmdArg(value) {
|
|
|
31
23
|
}
|
|
32
24
|
function spawnHookWithStdin(command, args, stdin, cwd, env = process.env) {
|
|
33
25
|
const shell = shouldUseShellForCommand(command);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cwd,
|
|
39
|
-
env,
|
|
40
|
-
encoding: "utf8",
|
|
41
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
42
|
-
shell: true,
|
|
43
|
-
windowsHide: true,
|
|
44
|
-
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
45
|
-
});
|
|
46
|
-
const status = proc.status ?? (proc.error ? 2 : proc.signal ? 128 : 0);
|
|
47
|
-
return {
|
|
48
|
-
status,
|
|
49
|
-
stdout: typeof proc.stdout === "string" ? proc.stdout : "",
|
|
50
|
-
stderr: typeof proc.stderr === "string" ? proc.stderr : "",
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const spawnCmd = shell && process.platform === "win32" ? quoteWin32CommandForShell(command) : command;
|
|
54
|
-
const proc = spawnSync(spawnCmd, [...args], {
|
|
26
|
+
const commandArgs = shell && process.platform === "win32"
|
|
27
|
+
? [[quoteWin32CommandForShell(command), ...args.map(quoteWindowsCmdArg)].join(" "), []]
|
|
28
|
+
: [command, [...args]];
|
|
29
|
+
const proc = spawnSync(commandArgs[0], commandArgs[1], {
|
|
55
30
|
input: stdin,
|
|
56
31
|
cwd,
|
|
57
32
|
env,
|
|
@@ -60,12 +35,14 @@ function spawnHookWithStdin(command, args, stdin, cwd, env = process.env) {
|
|
|
60
35
|
shell,
|
|
61
36
|
windowsHide: true,
|
|
62
37
|
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
38
|
+
timeout: LIVE_PROBE_CASE_TIMEOUT_MS,
|
|
63
39
|
});
|
|
64
|
-
const
|
|
40
|
+
const timedOut = proc.error?.code === "ETIMEDOUT";
|
|
65
41
|
return {
|
|
66
|
-
status,
|
|
42
|
+
status: proc.status ?? (proc.error ? 2 : proc.signal ? 128 : 0),
|
|
67
43
|
stdout: typeof proc.stdout === "string" ? proc.stdout : "",
|
|
68
44
|
stderr: typeof proc.stderr === "string" ? proc.stderr : "",
|
|
45
|
+
...(timedOut ? { timedOut: true } : {}),
|
|
69
46
|
};
|
|
70
47
|
}
|
|
71
48
|
function allowFixture(projectRoot) {
|
|
@@ -86,131 +63,167 @@ function denyFixture(projectRoot) {
|
|
|
86
63
|
function denyProbeEnv() {
|
|
87
64
|
return { ...process.env, [READ_ONLY_HOOK_ENV]: "1" };
|
|
88
65
|
}
|
|
89
|
-
function
|
|
66
|
+
function parseJsonObject(stdout) {
|
|
90
67
|
const trimmed = stdout.trim();
|
|
91
68
|
if (trimmed.length === 0) {
|
|
92
|
-
return {
|
|
69
|
+
return { value: null, issue: "empty-stdout", detail: "empty stdout" };
|
|
93
70
|
}
|
|
94
71
|
try {
|
|
95
72
|
const parsed = JSON.parse(trimmed);
|
|
96
|
-
if (parsed !== null &&
|
|
97
|
-
|
|
98
|
-
!Array.isArray(parsed) &&
|
|
99
|
-
parsed.permission === "allow") {
|
|
100
|
-
return { ok: true, permission: "allow", detail: "permission allow" };
|
|
101
|
-
}
|
|
102
|
-
if (parsed !== null &&
|
|
103
|
-
typeof parsed === "object" &&
|
|
104
|
-
!Array.isArray(parsed) &&
|
|
105
|
-
parsed.permission === "deny") {
|
|
106
|
-
return { ok: true, permission: "deny", detail: "permission deny" };
|
|
73
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
74
|
+
return { value: parsed, issue: null, detail: "valid JSON object" };
|
|
107
75
|
}
|
|
108
|
-
return {
|
|
76
|
+
return { value: null, issue: "unparseable-json", detail: "stdout JSON is not an object" };
|
|
109
77
|
}
|
|
110
78
|
catch {
|
|
111
|
-
return {
|
|
79
|
+
return { value: null, issue: "unparseable-json", detail: "stdout is not valid JSON" };
|
|
112
80
|
}
|
|
113
81
|
}
|
|
114
|
-
function
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
cwd: projectRoot,
|
|
129
|
-
env,
|
|
130
|
-
});
|
|
131
|
-
if (spawned.status !== 0) {
|
|
82
|
+
function validatesNestedDeny(value) {
|
|
83
|
+
const output = value.hookSpecificOutput;
|
|
84
|
+
if (output === null || typeof output !== "object" || Array.isArray(output))
|
|
85
|
+
return false;
|
|
86
|
+
const nested = output;
|
|
87
|
+
return (nested.hookEventName === "PreToolUse" &&
|
|
88
|
+
nested.permissionDecision === "deny" &&
|
|
89
|
+
typeof nested.permissionDecisionReason === "string");
|
|
90
|
+
}
|
|
91
|
+
function validateFixtureOutput(host, fixture, stdout) {
|
|
92
|
+
if (fixture === "allow" && host !== "cursor") {
|
|
93
|
+
if (stdout.trim().length === 0)
|
|
94
|
+
return { ok: true };
|
|
95
|
+
const parsed = parseJsonObject(stdout);
|
|
132
96
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
issue: "spawn-failed",
|
|
137
|
-
detail: `hook command exited ${spawned.status}${spawned.stderr.trim() ? `: ${spawned.stderr.trim()}` : ""}`,
|
|
97
|
+
ok: false,
|
|
98
|
+
issue: parsed.issue ?? "missing-allow",
|
|
99
|
+
detail: "expected empty stdout so the host permission flow remains unchanged",
|
|
138
100
|
};
|
|
139
101
|
}
|
|
140
|
-
const
|
|
141
|
-
if (
|
|
102
|
+
const parsed = parseJsonObject(stdout);
|
|
103
|
+
if (parsed.value === null) {
|
|
142
104
|
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
issue: decision.detail.includes("JSON") ? "unparseable-json" : "empty-stdout",
|
|
147
|
-
detail: decision.detail,
|
|
105
|
+
ok: false,
|
|
106
|
+
issue: parsed.issue,
|
|
107
|
+
detail: parsed.detail,
|
|
148
108
|
};
|
|
149
109
|
}
|
|
150
|
-
|
|
110
|
+
const value = parsed.value;
|
|
111
|
+
if (fixture === "allow") {
|
|
112
|
+
return value.permission === "allow"
|
|
113
|
+
? { ok: true }
|
|
114
|
+
: { ok: false, issue: "missing-allow", detail: "expected Cursor permission allow" };
|
|
115
|
+
}
|
|
116
|
+
const denied = host === "cursor"
|
|
117
|
+
? value.permission === "deny"
|
|
118
|
+
: host === "grok"
|
|
119
|
+
? value.decision === "deny" && typeof value.reason === "string"
|
|
120
|
+
: validatesNestedDeny(value);
|
|
121
|
+
return denied
|
|
122
|
+
? { ok: true }
|
|
123
|
+
: { ok: false, issue: "missing-deny", detail: `expected ${host} deny envelope` };
|
|
124
|
+
}
|
|
125
|
+
function runFixtureProbe(command, host, projectRoot, fixture, spawnHook) {
|
|
126
|
+
const spawned = spawnHook({
|
|
127
|
+
command,
|
|
128
|
+
args: ["--host", host, "--event", LIVE_PROBE_EVENT, "--project-root", projectRoot],
|
|
129
|
+
stdin: fixture === "allow" ? allowFixture(projectRoot) : denyFixture(projectRoot),
|
|
130
|
+
cwd: projectRoot,
|
|
131
|
+
env: fixture === "allow" ? process.env : denyProbeEnv(),
|
|
132
|
+
});
|
|
133
|
+
if (spawned.timedOut === true) {
|
|
151
134
|
return {
|
|
152
|
-
host
|
|
135
|
+
host,
|
|
153
136
|
event: LIVE_PROBE_EVENT,
|
|
154
137
|
fixture,
|
|
155
|
-
issue: "
|
|
156
|
-
detail: `
|
|
138
|
+
issue: "timed-out",
|
|
139
|
+
detail: `hook command exceeded ${LIVE_PROBE_CASE_TIMEOUT_MS}ms`,
|
|
157
140
|
};
|
|
158
141
|
}
|
|
159
|
-
if (
|
|
142
|
+
if (spawned.status !== 0) {
|
|
160
143
|
return {
|
|
161
|
-
host
|
|
144
|
+
host,
|
|
162
145
|
event: LIVE_PROBE_EVENT,
|
|
163
146
|
fixture,
|
|
164
|
-
issue: "
|
|
165
|
-
detail: `
|
|
147
|
+
issue: "spawn-failed",
|
|
148
|
+
detail: `hook command exited ${spawned.status}${spawned.stderr.trim() ? `: ${spawned.stderr.trim()}` : ""}`,
|
|
166
149
|
};
|
|
167
150
|
}
|
|
168
|
-
|
|
151
|
+
const validation = validateFixtureOutput(host, fixture, spawned.stdout);
|
|
152
|
+
return validation.ok
|
|
153
|
+
? null
|
|
154
|
+
: {
|
|
155
|
+
host,
|
|
156
|
+
event: LIVE_PROBE_EVENT,
|
|
157
|
+
fixture,
|
|
158
|
+
issue: validation.issue,
|
|
159
|
+
detail: validation.detail,
|
|
160
|
+
};
|
|
169
161
|
}
|
|
170
|
-
/**
|
|
162
|
+
/** Invoke the installed deft-hook shim and validate enabled host allow/deny codecs. */
|
|
171
163
|
export function probeAgentHooksLive(projectRoot, seams = {}) {
|
|
164
|
+
const started = performance.now();
|
|
172
165
|
const root = resolve(projectRoot);
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
166
|
+
const hosts = [...(seams.hosts ?? HOOK_HOSTS)];
|
|
167
|
+
if (hosts.length === 0) {
|
|
168
|
+
return {
|
|
169
|
+
code: 0,
|
|
170
|
+
message: "deft agent hooks live probe skipped: every host is intentionally disabled.",
|
|
171
|
+
cases: [],
|
|
172
|
+
hosts: [],
|
|
173
|
+
durationMs: elapsedMs(started),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const command = resolveHookCommand(seams);
|
|
177
|
+
if (command === null) {
|
|
175
178
|
return {
|
|
176
179
|
code: 2,
|
|
177
|
-
message:
|
|
180
|
+
message: `deft agent hooks live probe unavailable: installed ${DEFT_HOOK_COMMAND_MARKER} is not on PATH.`,
|
|
178
181
|
cases: [
|
|
179
182
|
{
|
|
180
|
-
host:
|
|
183
|
+
host: hosts[0],
|
|
181
184
|
event: LIVE_PROBE_EVENT,
|
|
182
185
|
fixture: "allow",
|
|
183
186
|
issue: "hook-command-missing",
|
|
184
187
|
detail: `${DEFT_HOOK_COMMAND_MARKER} not found on PATH`,
|
|
185
188
|
},
|
|
186
189
|
],
|
|
190
|
+
hosts: hosts.map((host) => ({ host, status: "unavailable" })),
|
|
191
|
+
durationMs: elapsedMs(started),
|
|
187
192
|
};
|
|
188
193
|
}
|
|
189
194
|
const spawnHook = seams.spawnHook ??
|
|
190
|
-
((input) => spawnHookWithStdin(input.command, input.args, input.stdin, input.cwd, input.env
|
|
195
|
+
((input) => spawnHookWithStdin(input.command, input.args, input.stdin, input.cwd, input.env));
|
|
191
196
|
const failures = [];
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
const hostResults = [];
|
|
198
|
+
for (const host of hosts) {
|
|
199
|
+
let hostFailure = null;
|
|
200
|
+
for (const fixture of ["allow", "deny"]) {
|
|
201
|
+
hostFailure = runFixtureProbe(command, host, root, fixture, spawnHook);
|
|
202
|
+
if (hostFailure !== null) {
|
|
203
|
+
failures.push(hostFailure);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
hostResults.push({ host, status: hostFailure === null ? "functional" : "non-functional" });
|
|
199
208
|
}
|
|
200
209
|
if (failures.length === 0) {
|
|
201
210
|
return {
|
|
202
211
|
code: 0,
|
|
203
|
-
message:
|
|
212
|
+
message: `deft agent hooks live probe passed allow/deny fixtures for ${hosts.join(", ")}.`,
|
|
204
213
|
cases: [],
|
|
214
|
+
hosts: hostResults,
|
|
215
|
+
durationMs: elapsedMs(started),
|
|
205
216
|
};
|
|
206
217
|
}
|
|
207
218
|
const summary = failures
|
|
208
|
-
.map((entry) => `${entry.fixture}: ${entry.issue} (${entry.detail})`)
|
|
219
|
+
.map((entry) => `${entry.host}/${entry.fixture}: ${entry.issue} (${entry.detail})`)
|
|
209
220
|
.join("; ");
|
|
210
221
|
return {
|
|
211
222
|
code: 1,
|
|
212
223
|
message: `deft agent hooks live probe FAILED: ${summary}. Recovery: reinstall @deftai/directive and run \`deft update\`.`,
|
|
213
224
|
cases: failures,
|
|
225
|
+
hosts: hostResults,
|
|
226
|
+
durationMs: elapsedMs(started),
|
|
214
227
|
};
|
|
215
228
|
}
|
|
216
229
|
//# sourceMappingURL=agent-hooks-live-probe.js.map
|
|
@@ -22,7 +22,7 @@ export function evaluateAgentHooks(projectRoot, hostHooksPolicy = loadHostHooksP
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
const registrations = inspectAgentHookDeposit(root, hostHooksPolicy);
|
|
25
|
-
const unhealthy = registrations.filter((entry) => entry.status
|
|
25
|
+
const unhealthy = registrations.filter((entry) => entry.status === "missing" || entry.status === "drifted");
|
|
26
26
|
if (unhealthy.length > 0) {
|
|
27
27
|
return {
|
|
28
28
|
code: 1,
|
|
@@ -30,18 +30,26 @@ export function evaluateAgentHooks(projectRoot, hostHooksPolicy = loadHostHooksP
|
|
|
30
30
|
unhealthy
|
|
31
31
|
.map((entry) => ` - ${entry.host}: ${entry.status} at ${entry.path} — ${entry.detail}`)
|
|
32
32
|
.join("\n") +
|
|
33
|
-
"\n Recovery: run `deft update` (or `directive init`) to refresh project hooks."
|
|
33
|
+
"\n Recovery: run `deft update` (or `directive init`) to refresh project hooks." +
|
|
34
|
+
" If a failed host is intentionally unused, inspect `deft policy:show --field=hostHooks`, " +
|
|
35
|
+
"set `plan.policy.hostHooks.<host> = false`, and run `deft update` again.",
|
|
34
36
|
stream: "stderr",
|
|
35
37
|
registrations,
|
|
36
38
|
};
|
|
37
39
|
}
|
|
40
|
+
const disabledHosts = registrations
|
|
41
|
+
.filter((entry) => entry.status === "disabled")
|
|
42
|
+
.map((entry) => entry.host[0]?.toUpperCase() + entry.host.slice(1));
|
|
38
43
|
return {
|
|
39
44
|
code: 0,
|
|
40
45
|
message: "✓ deft agent hooks registered and structurally valid for Claude, Grok, Cursor, Codex " +
|
|
41
46
|
"(SessionStart + PreToolUse direct-write and spawn/Task tools; compact re-arm deposited for Claude/Grok/Cursor; " +
|
|
42
47
|
"Codex has no native compact hook — re-run session ritual manually after compaction). " +
|
|
43
48
|
'Read-only explore: prefer Grok role `default_capability_mode = "read-only"`; hooks also honor ' +
|
|
44
|
-
"DEFT_HOOK_READ_ONLY=1 and explore subagent_type. Codex runtime trust is user-controlled and must be reviewed with `/hooks`; shell/MCP policy is deferred."
|
|
49
|
+
"DEFT_HOOK_READ_ONLY=1 and explore subagent_type. Codex runtime trust is user-controlled and must be reviewed with `/hooks`; shell/MCP policy is deferred." +
|
|
50
|
+
(disabledHosts.length > 0
|
|
51
|
+
? ` Intentional hostHooks disabled: ${disabledHosts.join(", ")}.`
|
|
52
|
+
: ""),
|
|
45
53
|
stream: "stdout",
|
|
46
54
|
registrations,
|
|
47
55
|
};
|
package/dist/verify-env/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.96.0",
|
|
4
4
|
"description": "TypeScript engine core for the Directive framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -243,6 +243,14 @@
|
|
|
243
243
|
"types": "./dist/capacity/index.d.ts",
|
|
244
244
|
"default": "./dist/capacity/index.js"
|
|
245
245
|
},
|
|
246
|
+
"./freshness": {
|
|
247
|
+
"types": "./dist/freshness/index.d.ts",
|
|
248
|
+
"default": "./dist/freshness/index.js"
|
|
249
|
+
},
|
|
250
|
+
"./parent-turn-shape": {
|
|
251
|
+
"types": "./dist/parent-turn-shape/index.d.ts",
|
|
252
|
+
"default": "./dist/parent-turn-shape/index.js"
|
|
253
|
+
},
|
|
246
254
|
"./intake": {
|
|
247
255
|
"types": "./dist/intake/index.d.ts",
|
|
248
256
|
"default": "./dist/intake/index.js"
|
|
@@ -342,8 +350,8 @@
|
|
|
342
350
|
"provenance": true
|
|
343
351
|
},
|
|
344
352
|
"dependencies": {
|
|
345
|
-
"@deftai/directive-content": "^0.
|
|
346
|
-
"@deftai/directive-types": "^0.
|
|
353
|
+
"@deftai/directive-content": "^0.96.0",
|
|
354
|
+
"@deftai/directive-types": "^0.96.0",
|
|
347
355
|
"archiver": "^8.0.0"
|
|
348
356
|
},
|
|
349
357
|
"scripts": {
|