@iamdevlinph/codex-kit 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Portable Codex setup for new devices and multiple projects. It provides:
4
4
 
5
5
  - four custom subagents: explorer, quick implementer, implementer, and reviewer
6
- - global subagent-routing guidance without a `commit-pusher`
6
+ - automatic global role routing
7
7
  - a reusable, stack-neutral `AGENTS.md` template
8
8
  - safe commands for global setup, project synchronization, and reconciliation
9
9
 
@@ -20,12 +20,30 @@ codex-kit global install
20
20
  codex-kit global configure
21
21
  ```
22
22
 
23
+ Start a new Codex task or restart the client after installation. Codex may ask
24
+ you to review and trust the new command hooks once; approve them after confirming
25
+ that they run the installed `${CODEX_HOME:-~/.codex}/codex-kit/routing-hook.js`.
26
+
23
27
  `npm install --global @iamdevlinph/codex-kit@latest` works as an alternative.
24
28
  For one-off use without a global installation, prefix a command with
25
29
  `pnpm dlx @iamdevlinph/codex-kit@latest`.
26
30
 
27
- `global install` copies reusable agents to `${CODEX_HOME:-~/.codex}` and
28
- maintains a marked routing section in the global `AGENTS.md`.
31
+ `global install` copies reusable agents to `${CODEX_HOME:-~/.codex}`, maintains
32
+ a marked routing section in the global `AGENTS.md`, and adds package-owned
33
+ handlers to the global `hooks.json` without replacing existing hooks.
34
+
35
+ The Sol root plans, routes, coordinates, and validates. On every prompt, the
36
+ routing hook supplies the current `SUBAGENT_ROUTING.md`; the root classifies the
37
+ task and delegates to the exact matching role. The role's agent TOML—not the
38
+ routing policy—selects its model and reasoning effort. Planning, conversation,
39
+ status, and small read-only checks may stay on the root.
40
+
41
+ The hook rejects direct root writes through Codex edit tools and obvious
42
+ write-shaped shell commands. `SubagentStart` opens a 15-minute write allowance
43
+ for that worker's `session_id + turn_id`; `SubagentStop` closes it. This identifies
44
+ the delegated lane without hard-coding a model. Codex hooks do not intercept
45
+ every possible indirect shell-write path, so this is a workflow guardrail rather
46
+ than a security boundary.
29
47
 
30
48
  `global configure` sets these defaults while preserving unrelated settings:
31
49
 
@@ -55,8 +73,8 @@ pnpm dlx @iamdevlinph/codex-kit@latest global list
55
73
  ```
56
74
 
57
75
  The summary shows the Codex home, orchestrator, normal and Plan-mode reasoning
58
- effort, routing status, and installed custom agents without dumping unrelated
59
- configuration.
76
+ effort, routing-file and routing-hook status, and installed custom agents without
77
+ dumping unrelated configuration.
60
78
 
61
79
  Uninstall package-managed global files:
62
80
 
@@ -64,8 +82,8 @@ Uninstall package-managed global files:
64
82
  pnpm dlx @iamdevlinph/codex-kit@latest global uninstall
65
83
  ```
66
84
 
67
- Modified managed files are preserved unless `--force` is supplied. An existing
68
- unmanaged `commit-pusher.toml` is reported but never silently deleted.
85
+ Uninstall removes only codex-kit's handlers from `hooks.json` and preserves other
86
+ hooks. Modified managed files are preserved unless `--force` is supplied.
69
87
 
70
88
  ## Commands
71
89
 
@@ -175,7 +193,7 @@ commands do not add network latency or depend on registry availability.
175
193
  ## Requirements
176
194
 
177
195
  - Node.js 20 or newer
178
- - Codex with custom subagent support
196
+ - Codex with custom subagent and lifecycle-hook support
179
197
 
180
198
  The published package has no runtime dependencies and uses only Node.js
181
199
  standard-library modules.
@@ -189,3 +207,6 @@ redistribute the package beyond applicable law and npm's service terms.
189
207
 
190
208
  - [Codex custom subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents)
191
209
  - [Codex `AGENTS.md` discovery](https://learn.chatgpt.com/docs/agent-configuration/agents-md)
210
+ - [Codex hooks](https://learn.chatgpt.com/docs/hooks)
211
+ - [Delegate Mode](https://github.com/Raylinkh/delegate-mode), whose short-lived
212
+ subagent allowance pattern informed codex-kit's write guard
@@ -1,36 +1,43 @@
1
1
  # Subagent Routing
2
2
 
3
- Keep focused, tightly coupled work in the parent task. Delegate only bounded,
4
- independent work when it materially reduces parent-context growth, latency, or
5
- cost. The user does not need to request subagents explicitly.
3
+ The root parent is the planner and orchestrator. Before acting on a substantive
4
+ task, classify the work using this file. Route by role and task shape, never by a
5
+ model name; each agent definition owns its model and reasoning effort.
6
6
 
7
- The parent owns requirements, architecture, integration, and final validation.
8
- Never delegate the overall objective. Avoid parallel write-heavy work and never
9
- assign overlapping files to multiple agents.
10
-
11
- Do not delegate trivial conversation, known-target edits limited to one or two
12
- files, or straightforward commands. A slow command alone is not a reason to
13
- delegate.
14
-
15
- When delegation is worthwhile:
16
-
17
- - Prefer one subagent. Add more only for independent, non-overlapping work.
18
- - Give each agent a bounded task and request a concise, decision-ready report.
19
- - For parallel implementation, assign explicit file or module ownership.
20
- - Wait for relevant agents and synthesize their results in the parent task.
21
- - Reuse prior exploration and evidence instead of repeating it.
7
+ The parent owns requirements, architecture, sequencing, integration, and final
8
+ validation. It may handle planning, conversation, status reporting, and small
9
+ read-only checks directly. When a route below matches, spawn that exact role
10
+ before performing the role's work. The user does not need to request delegation.
22
11
 
23
12
  Select custom agents by exact name:
24
13
 
25
- - Broad repository discovery or contract tracing: `code-explorer`
26
- - Mechanical, well-specified one- or two-file change: `quick-implementer`
27
- - Multi-file behavior change, debugging, or substantial tests: `implementer`
28
- - Independent review of high-risk or difficult-to-validate changes: `code-reviewer`
29
-
30
- Use either `quick-implementer` or `implementer` for a change, not both. Use the
31
- lowest reasoning effort that reliably fits the work. Do not substitute a generic
32
- agent when a matching custom role is available.
14
+ - Broad repository discovery, contract tracing, or search across many files:
15
+ `code-explorer`
16
+ - Mechanical, well-specified implementation localized to one or two files:
17
+ `quick-implementer`
18
+ - Multi-file behavior changes, debugging, migrations, or substantial tests:
19
+ `implementer`
20
+ - Independent review of security-sensitive, architectural, public-API,
21
+ concurrency, migration, or otherwise difficult-to-validate changes:
22
+ `code-reviewer`
23
+
24
+ For tasks with multiple phases, sequence only the roles that add value. For
25
+ example, use `code-explorer` before implementation only when broad discovery is
26
+ actually needed, and use `code-reviewer` after implementation only when the
27
+ change meets its risk threshold. Avoid parallel write-heavy work and never assign
28
+ overlapping files to multiple agents.
29
+
30
+ An assigned implementation agent performs its edits directly and must not
31
+ delegate them again. Use either `quick-implementer` or `implementer` for one
32
+ implementation slice, not both. Read-only agents never modify files.
33
+
34
+ For every delegation:
35
+
36
+ - Give the agent a bounded task and the relevant requirements.
37
+ - Request a concise, decision-ready result.
38
+ - Wait for the result before integrating or validating dependent work.
39
+ - Reuse returned evidence instead of repeating the same exploration.
33
40
 
34
41
  There is no commit-pusher role. Never delegate Git publishing. Do not stage,
35
- commit, or push unless the user explicitly requests that operation in the current
36
- task; if requested, the parent handles it after review.
42
+ commit, tag, or push unless the user explicitly requests that operation in the
43
+ current task; if requested, the parent handles it after review.
package/bin/codex-kit.js CHANGED
@@ -10,6 +10,7 @@ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
10
  const ASSETS = join(ROOT, "assets");
11
11
  const AGENTS_DIR = join(ASSETS, "agents");
12
12
  const ROUTING_FILE = join(ASSETS, "SUBAGENT_ROUTING.md");
13
+ const ROUTING_HOOK_FILE = join(ROOT, "bin", "routing-hook.js");
13
14
  const TEMPLATE_FILE = join(ASSETS, "TEMPLATE_AGENTS.md");
14
15
  const PACKAGE = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
15
16
  const GLOBAL_BEGIN = "<!-- BEGIN codex-kit:subagent-routing -->";
@@ -44,6 +45,116 @@ function write(file, data) {
44
45
  writeFileSync(temporary, data);
45
46
  renameSync(temporary, file);
46
47
  }
48
+ function readJsonObject(file) {
49
+ if (!existsSync(file))
50
+ return {};
51
+ try {
52
+ const value = JSON.parse(readText(file));
53
+ if (isRecord(value))
54
+ return value;
55
+ }
56
+ catch {
57
+ // Use the actionable error below for invalid JSON and non-object roots.
58
+ }
59
+ throw new Error(`${file} must contain a JSON object; fix or move it before installing.`);
60
+ }
61
+ function shellQuote(value) {
62
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
63
+ }
64
+ function hookCommands(file) {
65
+ return {
66
+ command: `/usr/bin/env node ${shellQuote(file)}`,
67
+ commandWindows: `node ${JSON.stringify(file)}`,
68
+ };
69
+ }
70
+ function removeHookHandlers(root, state) {
71
+ const hooks = root.hooks;
72
+ if (!isRecord(hooks))
73
+ return;
74
+ for (const [event, groupsValue] of Object.entries(hooks)) {
75
+ if (!Array.isArray(groupsValue))
76
+ continue;
77
+ const groups = groupsValue.flatMap((groupValue) => {
78
+ if (!isRecord(groupValue) || !Array.isArray(groupValue.hooks))
79
+ return [groupValue];
80
+ const handlers = groupValue.hooks.filter((handler) => {
81
+ if (!isRecord(handler))
82
+ return true;
83
+ return handler.command !== state.command && handler.commandWindows !== state.commandWindows;
84
+ });
85
+ return handlers.length ? [{ ...groupValue, hooks: handlers }] : [];
86
+ });
87
+ if (groups.length)
88
+ hooks[event] = groups;
89
+ else
90
+ delete hooks[event];
91
+ }
92
+ }
93
+ function installRoutingHooks(home, previous) {
94
+ const target = join(home, "hooks.json");
95
+ const hookFile = join(home, "codex-kit", "routing-hook.js");
96
+ const commands = hookCommands(hookFile);
97
+ const created = previous?.created ?? !existsSync(target);
98
+ const root = readJsonObject(target);
99
+ if (previous)
100
+ removeHookHandlers(root, previous);
101
+ const hooks = isRecord(root.hooks) ? root.hooks : {};
102
+ root.hooks = hooks;
103
+ const handler = {
104
+ type: "command",
105
+ command: commands.command,
106
+ commandWindows: commands.commandWindows,
107
+ timeout: 5,
108
+ };
109
+ const promptGroups = Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
110
+ const toolGroups = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
111
+ hooks.UserPromptSubmit = [
112
+ ...promptGroups,
113
+ { hooks: [{ ...handler, statusMessage: "Loading subagent routing" }] },
114
+ ];
115
+ const startGroups = Array.isArray(hooks.SubagentStart) ? hooks.SubagentStart : [];
116
+ hooks.SubagentStart = [
117
+ ...startGroups,
118
+ { hooks: [{ ...handler, statusMessage: "Opening delegated write lane" }] },
119
+ ];
120
+ const stopGroups = Array.isArray(hooks.SubagentStop) ? hooks.SubagentStop : [];
121
+ hooks.SubagentStop = [
122
+ ...stopGroups,
123
+ { hooks: [{ ...handler, statusMessage: "Closing delegated write lane" }] },
124
+ ];
125
+ hooks.PreToolUse = [
126
+ ...toolGroups,
127
+ {
128
+ matcher: "Bash|Edit|Write|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions.apply_patch",
129
+ hooks: [{ ...handler, statusMessage: "Checking orchestrator routing" }],
130
+ },
131
+ ];
132
+ const updated = `${JSON.stringify(root, null, 2)}\n`;
133
+ const original = existsSync(target) ? readText(target) : "";
134
+ if (updated !== original) {
135
+ backup(target);
136
+ write(target, updated);
137
+ console.log(`updated: ${target}`);
138
+ }
139
+ return { target, ...commands, created };
140
+ }
141
+ function uninstallRoutingHooks(state) {
142
+ if (!existsSync(state.target)) {
143
+ console.warn(`preserved missing hooks file: ${state.target}`);
144
+ return;
145
+ }
146
+ const root = readJsonObject(state.target);
147
+ removeHookHandlers(root, state);
148
+ const hooks = root.hooks;
149
+ if (isRecord(hooks) && !Object.keys(hooks).length)
150
+ delete root.hooks;
151
+ backup(state.target);
152
+ if (state.created && !Object.keys(root).length)
153
+ rmSync(state.target);
154
+ else
155
+ write(state.target, `${JSON.stringify(root, null, 2)}\n`);
156
+ console.log(`removed managed routing hooks from: ${state.target}`);
157
+ }
47
158
  function loadState(home) {
48
159
  const file = join(home, STATE_FILE);
49
160
  if (!existsSync(file))
@@ -234,6 +345,7 @@ function installGlobal(options) {
234
345
  version: PACKAGE.version,
235
346
  files: {},
236
347
  globalAgents: null,
348
+ hooks: null,
237
349
  config: prior.config ?? null,
238
350
  };
239
351
  for (const name of readdirSync(AGENTS_DIR).filter((name) => name.endsWith(".toml")).sort()) {
@@ -245,6 +357,9 @@ function installGlobal(options) {
245
357
  const routingRecord = installFile(ROUTING_FILE, join(home, "SUBAGENT_ROUTING.md"), "routing", prior, options.force);
246
358
  if (routingRecord)
247
359
  next.files.routing = routingRecord;
360
+ const hookRecord = installFile(ROUTING_HOOK_FILE, join(home, "codex-kit", "routing-hook.js"), "routing-hook", prior, options.force);
361
+ if (hookRecord)
362
+ next.files["routing-hook"] = hookRecord;
248
363
  for (const [key, record] of Object.entries(prior.files)) {
249
364
  if (key in next.files)
250
365
  continue;
@@ -272,6 +387,7 @@ function installGlobal(options) {
272
387
  console.log(`updated: ${globalAgents}`);
273
388
  }
274
389
  next.globalAgents = { target: globalAgents };
390
+ next.hooks = installRoutingHooks(home, prior.hooks);
275
391
  saveState(home, next);
276
392
  const commitPusher = join(home, "agents", "commit-pusher.toml");
277
393
  if (existsSync(commitPusher)) {
@@ -360,6 +476,11 @@ function listGlobal(options) {
360
476
  const hasRoutingBlock = existsSync(globalAgents) && readText(globalAgents).includes(GLOBAL_BEGIN);
361
477
  console.log(`Global routing: ${hasRoutingBlock ? "installed" : "not installed"}`);
362
478
  console.log(`Routing file: ${existsSync(routingFile) ? routingFile : "missing"}`);
479
+ const routingHook = state.hooks;
480
+ const hooksInstalled = Boolean(routingHook &&
481
+ existsSync(routingHook.target) &&
482
+ readText(routingHook.target).includes(routingHook.command));
483
+ console.log(`Routing hook: ${hooksInstalled ? "installed" : "not installed"}`);
363
484
  console.log("Custom agents:");
364
485
  const agents = existsSync(agentsDir)
365
486
  ? readdirSync(agentsDir).filter((name) => name.endsWith(".toml")).sort()
@@ -441,6 +562,14 @@ function uninstallGlobal(options) {
441
562
  }
442
563
  }
443
564
  }
565
+ if (state.hooks)
566
+ uninstallRoutingHooks(state.hooks);
567
+ const allowancesDir = join(home, "codex-kit", "allowances");
568
+ if (existsSync(allowancesDir))
569
+ rmSync(allowancesDir, { recursive: true, force: true });
570
+ const kitDir = join(home, "codex-kit");
571
+ if (existsSync(kitDir) && !readdirSync(kitDir).length)
572
+ rmSync(kitDir, { recursive: true });
444
573
  rmSync(statePath);
445
574
  console.log(`Codex kit uninstalled from ${home}`);
446
575
  }
@@ -647,22 +776,30 @@ function help() {
647
776
  console.log(`codex-kit ${PACKAGE.version}
648
777
 
649
778
  Usage:
650
- codex-kit global install [--codex-home PATH] [--force]
651
- codex-kit global configure [--orchestrator MODEL] [--reasoning-effort LEVEL]
652
- codex-kit global list [--codex-home PATH]
653
- codex-kit global uninstall [--codex-home PATH]
654
- codex-kit project init [--cwd PATH]
655
- codex-kit project sync [--cwd PATH]
656
- codex-kit project status [--cwd PATH]
657
- codex-kit project mark-applied [--cwd PATH]
658
- codex-kit version check
659
- codex-kit --help
660
- codex-kit --version
779
+ codex-kit <command> [options]
780
+
781
+ Commands:
782
+ global install Install or update package-owned agents and routing guidance.
783
+ global configure Set the orchestrator and normal/Plan reasoning defaults.
784
+ global list Show model settings, routing status, and custom agents.
785
+ global uninstall Restore managed config values and remove package-owned files.
786
+ project init Initialize AGENTS.md, TEMPLATE_AGENTS.md, and project state.
787
+ project sync Refresh TEMPLATE_AGENTS.md without editing AGENTS.md.
788
+ project status Show whether template changes still need reconciliation.
789
+ project mark-applied Record the current template as reconciled with AGENTS.md.
790
+ version check Compare the installed version with the latest npm release.
791
+ --help Show this help.
792
+ --version Print the installed version.
793
+
794
+ Options:
795
+ --codex-home PATH Use a Codex home other than CODEX_HOME or ~/.codex.
796
+ --cwd PATH Use a project directory other than the current directory.
797
+ --force Replace modified files or config managed by codex-kit.
798
+ --orchestrator MODEL Set the root/orchestrator model (default: gpt-5.6-sol).
799
+ --model MODEL Alias for --orchestrator.
800
+ --reasoning-effort LEVEL Set normal and Plan-mode effort (default: high).
661
801
 
662
- Global configure defaults to gpt-5.6-sol with high normal and Plan-mode reasoning
663
- and edits only those top-level model settings in CODEX_HOME/config.toml.
664
- Global install manages custom agents and a marked routing block under CODEX_HOME.
665
- Project sync refreshes TEMPLATE_AGENTS.md and never merges it into AGENTS.md.`);
802
+ Run a command with only its applicable options.`);
666
803
  }
667
804
  export function main(argv = process.argv.slice(2)) {
668
805
  const options = parse(argv);
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const home = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7
+ const routingFile = join(home, "SUBAGENT_ROUTING.md");
8
+ const allowancesDir = join(home, "codex-kit", "allowances");
9
+ const allowanceTtlMs = 15 * 60 * 1000;
10
+ const writeTools = new Set([
11
+ "Edit",
12
+ "Write",
13
+ "MultiEdit",
14
+ "NotebookEdit",
15
+ "apply_patch",
16
+ "ApplyPatch",
17
+ "functions.apply_patch",
18
+ ]);
19
+ const writeCommands = new Set([
20
+ "chmod",
21
+ "chown",
22
+ "cp",
23
+ "dd",
24
+ "install",
25
+ "ln",
26
+ "mkdir",
27
+ "mv",
28
+ "rm",
29
+ "rmdir",
30
+ "rsync",
31
+ "tee",
32
+ "touch",
33
+ "truncate",
34
+ ]);
35
+ const gitWriteCommands = new Set([
36
+ "add",
37
+ "am",
38
+ "apply",
39
+ "checkout",
40
+ "cherry-pick",
41
+ "clean",
42
+ "commit",
43
+ "merge",
44
+ "mv",
45
+ "rebase",
46
+ "reset",
47
+ "restore",
48
+ "revert",
49
+ "rm",
50
+ "stash",
51
+ "switch",
52
+ ]);
53
+ const input = JSON.parse(readFileSync(0, "utf8"));
54
+ function allowanceFile(payload) {
55
+ if (!payload.session_id || !payload.turn_id)
56
+ return null;
57
+ const key = createHash("sha256")
58
+ .update(`${payload.session_id}\0${payload.turn_id}`)
59
+ .digest("hex");
60
+ return join(allowancesDir, `${key}.json`);
61
+ }
62
+ function writeAllowance(payload) {
63
+ const file = allowanceFile(payload);
64
+ if (!file)
65
+ return false;
66
+ mkdirSync(allowancesDir, { recursive: true });
67
+ const temporary = `${file}.${process.pid}.tmp`;
68
+ writeFileSync(temporary, JSON.stringify({
69
+ agentId: payload.agent_id,
70
+ agentType: payload.agent_type,
71
+ expiresAt: Date.now() + allowanceTtlMs,
72
+ }));
73
+ renameSync(temporary, file);
74
+ return true;
75
+ }
76
+ function removeAllowance(payload) {
77
+ const file = allowanceFile(payload);
78
+ if (file)
79
+ rmSync(file, { force: true });
80
+ }
81
+ function hasAllowance(payload) {
82
+ const file = allowanceFile(payload);
83
+ if (!file || !existsSync(file))
84
+ return false;
85
+ try {
86
+ const value = JSON.parse(readFileSync(file, "utf8"));
87
+ if (typeof value.expiresAt === "number" && value.expiresAt > Date.now())
88
+ return true;
89
+ }
90
+ catch {
91
+ // Invalid allowance files fail closed.
92
+ }
93
+ rmSync(file, { force: true });
94
+ return false;
95
+ }
96
+ function pruneAllowances() {
97
+ if (!existsSync(allowancesDir))
98
+ return;
99
+ for (const name of readdirSync(allowancesDir)) {
100
+ if (!name.endsWith(".json"))
101
+ continue;
102
+ const file = join(allowancesDir, name);
103
+ try {
104
+ const value = JSON.parse(readFileSync(file, "utf8"));
105
+ if (typeof value.expiresAt === "number" && value.expiresAt > Date.now())
106
+ continue;
107
+ }
108
+ catch {
109
+ // Invalid allowance files are stale.
110
+ }
111
+ rmSync(file, { force: true });
112
+ }
113
+ }
114
+ function isWriteShapedBash(command) {
115
+ if (/(^|[^<])(?:>>?|&>>?|\d>>?)\s*[^&]/.test(command))
116
+ return true;
117
+ const commands = command.split(/(?:&&|\|\||;|\|)/).map((part) => part.trim()).filter(Boolean);
118
+ return commands.some((segment) => {
119
+ const tokens = segment.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
120
+ const executable = tokens[0]?.split("/").at(-1);
121
+ if (!executable)
122
+ return false;
123
+ if (writeCommands.has(executable))
124
+ return true;
125
+ if ((executable === "sed" || executable === "perl") && tokens.slice(1, 5).some((token) => /^-[^-]*i/.test(token)))
126
+ return true;
127
+ return executable === "git" && Boolean(tokens[1] && gitWriteCommands.has(tokens[1]));
128
+ });
129
+ }
130
+ function hookContext(event, context) {
131
+ return JSON.stringify({
132
+ hookSpecificOutput: {
133
+ hookEventName: event,
134
+ additionalContext: context,
135
+ },
136
+ });
137
+ }
138
+ if (input.hook_event_name === "UserPromptSubmit" && existsSync(routingFile)) {
139
+ const routing = readFileSync(routingFile, "utf8").trim();
140
+ process.stdout.write(`Codex-kit routing policy for this turn:\n\n${routing}\n\n` +
141
+ "Classify the task using this policy before acting. When it requires delegation, " +
142
+ "spawn the exact named role before doing that role's work. Agent definitions, not " +
143
+ "this policy, determine each role's model and reasoning effort.");
144
+ }
145
+ if (input.hook_event_name === "SubagentStart") {
146
+ pruneAllowances();
147
+ const recorded = writeAllowance(input);
148
+ process.stdout.write(hookContext("SubagentStart", `You are the delegated ${input.agent_type ?? "worker"}. ` +
149
+ `${recorded ? "A temporary write lane is active." : "No write lane could be recorded."} ` +
150
+ "Follow the assigned scope, perform the role's work directly without further delegation, validate it, and return concise evidence."));
151
+ }
152
+ if (input.hook_event_name === "SubagentStop")
153
+ removeAllowance(input);
154
+ if (input.hook_event_name === "PreToolUse") {
155
+ const writeAttempt = writeTools.has(input.tool_name ?? "") ||
156
+ (input.tool_name === "Bash" && isWriteShapedBash(input.tool_input?.command ?? ""));
157
+ if (writeAttempt && !input.agent_id && !hasAllowance(input)) {
158
+ process.stdout.write(JSON.stringify({
159
+ hookSpecificOutput: {
160
+ hookEventName: "PreToolUse",
161
+ permissionDecision: "deny",
162
+ permissionDecisionReason: "The root orchestrator may not write directly. Consult SUBAGENT_ROUTING.md and delegate to the exact role selected there.",
163
+ },
164
+ }));
165
+ }
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamdevlinph/codex-kit",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Portable Codex subagents and project AGENTS.md defaults.",
5
5
  "type": "module",
6
6
  "bin": {