@nodii/dev 0.6.26 → 0.6.27
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/cli.js +8 -13
- package/dist/core.js +1 -1
- package/dist/managed-hooks.js +30 -27
- package/dist/nodii-dev.js +13 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -766,10 +766,6 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
766
766
|
return;
|
|
767
767
|
}
|
|
768
768
|
if (["claude-figma-pre", "figma-pre"].includes(action)) {
|
|
769
|
-
// Fail closed: Claude Code only blocks a tool call on exit code 2, and
|
|
770
|
-
// treats any other non-zero exit as a non-blocking error that lets the
|
|
771
|
-
// write through. Every failure here (bad stdin, missing config, Dev
|
|
772
|
-
// error, lock denied) must therefore exit 2.
|
|
773
769
|
let payload;
|
|
774
770
|
try {
|
|
775
771
|
payload = JSON.parse(await stdinText());
|
|
@@ -778,6 +774,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
778
774
|
if (hookRuntime(payload) !== "codex") return emit(result, json, "allowed");
|
|
779
775
|
return;
|
|
780
776
|
} catch (error) {
|
|
777
|
+
// A known policy denial still blocks. Hook/runtime failures fail open
|
|
778
|
+
// and stay silent so a broken environment cannot stop the session.
|
|
779
|
+
if (!["FIGMA_FILE_KEY_MISSING", "FIGMA_LOCK_DENIED"].includes(error?.code)) return;
|
|
781
780
|
if (payload && hookRuntime(payload) === "codex") {
|
|
782
781
|
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: `Blocked Figma write: ${error?.message ?? String(error)}` } }));
|
|
783
782
|
return;
|
|
@@ -791,7 +790,6 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
791
790
|
}
|
|
792
791
|
}
|
|
793
792
|
if (action === "safety-pre") {
|
|
794
|
-
// A malformed or unavailable safety check must not turn into permission.
|
|
795
793
|
let payload;
|
|
796
794
|
try {
|
|
797
795
|
payload = JSON.parse(await stdinText());
|
|
@@ -803,6 +801,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
803
801
|
else for (const nudge of nudges) console.log(nudge);
|
|
804
802
|
return;
|
|
805
803
|
} catch (error) {
|
|
804
|
+
// Only an evaluated safety rule may deny. Discovery, parsing, git,
|
|
805
|
+
// config and Dev errors fail open and produce no output.
|
|
806
|
+
if (!String(error?.code ?? "").startsWith("SAFETY_")) return;
|
|
806
807
|
if (payload && hookRuntime(payload) === "codex") {
|
|
807
808
|
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: error?.message ?? String(error) } }));
|
|
808
809
|
return;
|
|
@@ -821,14 +822,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
821
822
|
payload = JSON.parse(await stdinText());
|
|
822
823
|
await recordHookFire(payload).catch(() => {});
|
|
823
824
|
await safetyPostHook(payload);
|
|
824
|
-
} catch
|
|
825
|
-
// A post-hook cannot undo a completed command; report state-tracking
|
|
826
|
-
// failure without making Claude treat the completed command as failed.
|
|
827
|
-
const message = `nodii-dev safety post hook: ${error?.code ?? "ERROR"}: ${error?.message ?? String(error)}`;
|
|
828
|
-
if (payload && hookRuntime(payload) === "codex")
|
|
829
|
-
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: message } }));
|
|
830
|
-
else console.error(message);
|
|
831
|
-
}
|
|
825
|
+
} catch {}
|
|
832
826
|
return;
|
|
833
827
|
}
|
|
834
828
|
if (action === "lane-heartbeat") {
|
|
@@ -854,6 +848,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
854
848
|
return emit(await postHook(payload), json, "logged");
|
|
855
849
|
} catch (error) {
|
|
856
850
|
const outcome = postHookFailure(error);
|
|
851
|
+
if (!outcome.exitCode) return;
|
|
857
852
|
if (hookRuntime(payload) === "codex")
|
|
858
853
|
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: outcome.message } }));
|
|
859
854
|
else {
|
package/dist/core.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { homedir, platform } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
|
|
7
|
-
export const VERSION = "0.6.
|
|
7
|
+
export const VERSION = "0.6.27";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
package/dist/managed-hooks.js
CHANGED
|
@@ -11,7 +11,7 @@ export const CODEX_HOOK_EVENTS = [
|
|
|
11
11
|
];
|
|
12
12
|
|
|
13
13
|
export const managedHooksDir = (home = homedir()) => join(home, ".config", "nodii-dev", "codex-managed-hooks");
|
|
14
|
-
export const MANAGED_DISPATCH_MIN_VERSION = "0.6.
|
|
14
|
+
export const MANAGED_DISPATCH_MIN_VERSION = "0.6.27";
|
|
15
15
|
|
|
16
16
|
/** Only a single nodii-dev invocation is allowed; shell composition is never accepted. */
|
|
17
17
|
export function nodiiCommandArgs(command) {
|
|
@@ -110,33 +110,36 @@ function responseOf(stdout) {
|
|
|
110
110
|
|
|
111
111
|
/** Execute the current generated hook set behind the stable managed dispatcher. */
|
|
112
112
|
export async function dispatchManagedHook(event, payloadText, deps = {}) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
113
|
+
try {
|
|
114
|
+
if (!CODEX_HOOK_EVENTS.includes(event)) return emptyResponse(event);
|
|
115
|
+
const env = deps.env ?? process.env;
|
|
116
|
+
if (!laneConfig(env)) return emptyResponse(event);
|
|
117
|
+
const dir = deps.dir ?? env.NODII_DEV_MANAGED_HOOKS_DIR ?? managedHooksDir();
|
|
118
|
+
const config = JSON.parse(await (deps.read ?? readFile)(join(dir, "hooks.json"), "utf8"));
|
|
119
|
+
validateManagedHooks(config.hooks ?? {});
|
|
120
|
+
const payload = JSON.parse(payloadText);
|
|
121
|
+
const target = matchTarget(event, payload);
|
|
122
|
+
const handlers = (config.hooks?.[event] ?? []).filter((entry) => {
|
|
123
|
+
try { return new RegExp(entry.matcher || ".*").test(target); } catch { return false; }
|
|
124
|
+
}).flatMap((entry) => entry.hooks ?? []);
|
|
125
|
+
const results = await Promise.all(handlers.map((handler) => (deps.run ?? runHandler)(nodiiCommandArgs(handler.command), payloadText, deps)));
|
|
126
|
+
// A failed handler is unavailable policy, not a denial. Ignore both its
|
|
127
|
+
// output and stderr; doctor is where hook health remains visible.
|
|
128
|
+
const successful = results.filter((result) => result.status === 0);
|
|
129
|
+
const messages = successful.map((result) => responseOf(result.stdout)).filter(Boolean);
|
|
130
|
+
if (["PreToolUse", "PermissionRequest"].includes(event)) {
|
|
131
|
+
const denied = messages.find((message) => message.hookSpecificOutput?.permissionDecision === "deny");
|
|
132
|
+
if (denied) return `${JSON.stringify(denied)}\n`;
|
|
133
|
+
const contexts = messages.map((message) => message.hookSpecificOutput?.additionalContext).filter(Boolean);
|
|
134
|
+
return contexts.length ? `${JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: contexts.join("\n") } })}\n` : "";
|
|
135
|
+
}
|
|
136
|
+
const structured = messages.map((message) => message.hookSpecificOutput?.additionalContext).filter(Boolean);
|
|
137
|
+
const plain = successful.map((result) => responseOf(result.stdout) ? "" : result.stdout.trim()).filter(Boolean);
|
|
138
|
+
if (["Stop", "SubagentStop"].includes(event)) return "{}\n";
|
|
139
|
+
return [...structured, ...plain].join("\n") + ([...structured, ...plain].length ? "\n" : "");
|
|
140
|
+
} catch {
|
|
141
|
+
return emptyResponse(event);
|
|
135
142
|
}
|
|
136
|
-
const structured = messages.map((message) => message.hookSpecificOutput?.additionalContext).filter(Boolean);
|
|
137
|
-
const plain = results.map((result) => responseOf(result.stdout) ? "" : result.stdout.trim()).filter(Boolean);
|
|
138
|
-
if (["Stop", "SubagentStop"].includes(event)) return "{}\n";
|
|
139
|
-
return [...structured, ...plain, ...errors].join("\n") + ([...structured, ...plain, ...errors].length ? "\n" : "");
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
const versionParts = (value) => String(value ?? "").split(".").map((part) => Number.parseInt(part, 10));
|
package/dist/nodii-dev.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fail, main } from "./cli.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Hooks run in the agent's environment, which may be incomplete or may not be
|
|
5
|
+
// a git worktree. An unavailable hook must never turn into a refused tool call.
|
|
6
|
+
// Intentional policy denials are handled inside the hook command before they
|
|
7
|
+
// reach this boundary.
|
|
8
|
+
main().catch((error) => {
|
|
9
|
+
const denial = String(error?.code ?? "").startsWith("SAFETY_") ||
|
|
10
|
+
["FIGMA_FILE_KEY_MISSING", "FIGMA_LOCK_DENIED", "WRITE_OUT_OF_SCOPE"].includes(error?.code);
|
|
11
|
+
if (process.argv[2] === "hook" && !denial) {
|
|
12
|
+
process.exitCode = 0;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
fail(error);
|
|
16
|
+
});
|