@heretyc/subagent-mcp 2.12.4 → 2.12.5-beta.1

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
@@ -1,163 +1,193 @@
1
- # subagent-mcp
2
-
3
- [![npm version](https://img.shields.io/npm/v/@heretyc/subagent-mcp?label=npm)](https://www.npmjs.com/package/@heretyc/subagent-mcp)
4
- [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
5
- [![node](https://img.shields.io/node/v/@heretyc/subagent-mcp)](https://www.npmjs.com/package/@heretyc/subagent-mcp)
6
- [![CI](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml/badge.svg)](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
7
-
8
- Turn any AI coding assistant into a **manager of other AI agents**. subagent-mcp
9
- lets your Claude Code or Codex session start, watch, message, and stop a whole
10
- team of AI sub-agents — on macOS, Linux, and Windows. **No direct API calls. No
11
- API keys.** It drives the `claude` and `codex` command-line tools you already
12
- signed into.
13
-
14
- ## What it does
15
-
16
- Normally one AI assistant does all the work in one conversation. That
17
- conversation fills up, slows down, and eventually forgets its early context. As
18
- of a certain size it "compacts" — summarizing and dropping detail.
19
-
20
- subagent-mcp changes the shape of the work. Your assistant becomes an
21
- **orchestrator**: it hands each task to a fresh sub-agent, watches the results,
22
- and moves on — without ever doing the reading or writing itself.
23
-
24
- ## Why it works this way
25
-
26
- Four bets drive the whole design. The full reasoning lives in
27
- [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md); the short version:
28
-
29
- - **The effective memory grows geometrically.** Because the orchestrator only
30
- ever holds *summaries* of what its sub-agents did — never the raw files — its
31
- own memory fills up far more slowly. That means **very long tasks with little
32
- or no compaction**.
33
- - **Mixed providers avoid blind spots.** Using Claude *and* Codex together means
34
- one vendor's weak spot or outage doesn't blind or block the whole job — and
35
- you're never locked in to a single vendor.
36
- - **Ruthless token efficiency.** Every hand-off is compressed and every read is
37
- kept small, so the system stays fast and cheap even on marathon tasks.
38
- - **Durable, authoritative reminders.** The operating rules are re-injected every
39
- turn through official host hooks, so the assistant doesn't drift off-task or
40
- start making things up over a long run.
41
-
42
- ## Install
43
-
44
- ### What you need first
45
-
46
- - Node.js 18 or newer (`node --version`)
47
- - `claude` CLI — installed and signed in (`claude --version`)
48
- - `codex` CLI — installed and signed in (`codex --version`; optional if you
49
- only use Claude)
50
-
51
- Building from source needs extra developer tools — see
52
- [CONTRIBUTING.md](CONTRIBUTING.md).
53
-
54
- ### Install the package
55
-
56
- ```bash
57
- npm install -g @heretyc/subagent-mcp
58
- ```
59
-
60
- This is the standard install for everyone. (Organizations pinning the package
61
- through GitHub Packages should see [docs/registration.md](docs/registration.md).)
62
-
63
- ### Wire it into your assistant
64
-
65
- ```bash
66
- subagent-mcp setup
67
- ```
68
-
69
- Installing the package only ships the program — it does **not** connect anything
70
- on its own. `subagent-mcp setup` finds your Claude Code / Codex install and
71
- registers both the server and the per-turn orchestration hooks. Preview first
72
- with `subagent-mcp setup --dry-run`.
73
-
74
- ### Restart, then turn on the invariant
75
-
76
- Restart your Claude Code or Codex session so it picks up the new tools. On Codex,
77
- run `/hooks` and trust the new hook. Then (recommended):
78
-
79
- ```bash
80
- subagent-mcp init --global
81
- ```
82
-
83
- This writes a small managed "always delegate" rule block into your global
84
- assistant config once, so it works across every project. For a single project
85
- instead, use `subagent-mcp init --root /path/to/project`. Full per-platform
86
- wiring (Gemini CLI, Claude Desktop, manual setup) is in
87
- [docs/registration.md](docs/registration.md).
88
-
89
- ## How to operate it
90
-
91
- ### Orchestration mode: ON vs OFF
92
-
93
- - **ON** — your assistant acts as a pure manager. It never reads or writes files
94
- directly; it delegates every step to sub-agents. Best for big, long-running
95
- jobs.
96
- - **OFF** — your assistant works normally, on its own, with no delegation rules.
97
-
98
- Flip it with the `orchestration-mode` tool. (Desktop apps can toggle the mode
99
- but don't receive the per-turn hook reminders.)
100
-
101
- ### The 8 tools at a glance
102
-
103
- | Tool | What it does |
104
- |---|---|
105
- | `launch_agent` | Start a sub-agent on a task (just give a prompt + a task category) |
106
- | `poll_agent` | Check how an agent is doing and read its latest output |
107
- | `kill_agent` | Stop a running or quiet agent |
108
- | `send_message` | Send a follow-up message to a live agent |
109
- | `list_agents` | See every agent and its current status |
110
- | `wait` | Pause until a specific agent finishes |
111
- | `orchestration-mode` | Turn manager-mode ON or OFF |
112
- | `model-selection-mode` | Let the server auto-pick the model, or allow manual overrides |
113
-
114
- You never have to choose a model. Give `launch_agent` a plain-English prompt and
115
- a **task category** (e.g. "coding", "debugging", "security_review") and the
116
- server picks the best provider, model, and effort for that kind of work.
117
-
118
- ### How many run at once
119
-
120
- There is a single machine-wide limit on how many sub-agents run at the same time
121
- across everything on your computer. The **default is 20**. When the limit is
122
- reached, a new `launch_agent` is turned down right away it does not wait in a
123
- queue. You change the number in the `global-concurrency.jsonc` file in the
124
- install folder (minimum 10); the file is re-read on every launch, so no restart
125
- is needed.
126
-
127
- The same settings file includes `checkForUpdates` (default `true`). When a newer
128
- npm version exists, the per-turn hook can show a throttled notice to run
129
- `subagent-mcp update` and then `subagent-mcp setup`. Set `checkForUpdates` to
130
- `false`, or run with `SUBAGENT_UPDATE_CHECK=0` / `false`, to disable that check.
131
-
132
- ## Basic debugging
133
-
134
- - **"An agent looks stuck."** A quiet agent is usually **still alive**, not
135
- dead. After ~10 minutes with no output an agent is marked `stalled` — it's
136
- thinking or waiting on a hand-off. It recovers on its own. **Do not kill a
137
- stalled agent**, and `wait` will not return on one.
138
- - **"It won't start a new agent (cap reached)."** You've hit the concurrent
139
- limit. Use `list_agents` to see what's running and `kill_agent` on anything
140
- you no longer need that frees a slot immediately. Raising the number in
141
- `global-concurrency.jsonc` also works.
142
- - **"Where are the logs?"** Each agent's recent output is available any time via
143
- `poll_agent`. The server's own diagnostics go to your host's normal MCP
144
- server log (your Claude Code or Codex session logs) — all server logging goes
145
- to stderr, never mixed into results.
146
-
147
- ## Documentation
148
-
149
- | Document | Contents |
150
- |---|---|
151
- | [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md) | Why it's built this way — the full design rationale |
152
- | [docs/registration.md](docs/registration.md) | Per-platform setup: Claude Code, Codex, Gemini CLI, Claude Desktop |
153
- | [docs/tools.md](docs/tools.md) | Full tool reference — all eight tools, parameters, return shapes |
154
- | [docs/usage.md](docs/usage.md) | Model & effort matrix, ultracode mode, usage examples |
155
- | [docs/SPEC.md](docs/SPEC.md) | Full technical specification |
156
- | [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md) | Agent status meanings (processing / stalled / finished / errored / stopped) |
157
- | [CONTRIBUTING.md](CONTRIBUTING.md) | Developer guide build, test, publish, contribution workflow |
158
-
159
- ## License
160
-
161
- Apache-2.0 Copyright 2026 Lexi Blackburn
162
-
163
- See [LICENSE](LICENSE).
1
+ # subagent-mcp
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@heretyc/subagent-mcp?label=npm)](https://www.npmjs.com/package/@heretyc/subagent-mcp)
4
+ [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
5
+ [![node](https://img.shields.io/node/v/@heretyc/subagent-mcp)](https://www.npmjs.com/package/@heretyc/subagent-mcp)
6
+ [![CI](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml/badge.svg)](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
7
+
8
+ Turn any AI coding assistant into a **manager of other AI agents**. subagent-mcp
9
+ lets your Claude Code or Codex session start, watch, message, and stop a whole
10
+ team of AI sub-agents — on macOS, Linux, and Windows. **No direct API calls. No
11
+ API keys.** It drives the `claude` and `codex` command-line tools you already
12
+ signed into.
13
+
14
+ ## What it does
15
+
16
+ Normally one AI assistant does all the work in one conversation. That
17
+ conversation fills up, slows down, and eventually forgets its early context. As
18
+ of a certain size it "compacts" — summarizing and dropping detail.
19
+
20
+ subagent-mcp changes the shape of the work. Your assistant becomes an
21
+ **orchestrator**: it hands each task to a fresh sub-agent, watches the results,
22
+ and moves on — without ever doing the reading or writing itself.
23
+
24
+ ## Why it works this way
25
+
26
+ Four bets drive the whole design. The full reasoning lives in
27
+ [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md); the short version:
28
+
29
+ - **The effective memory grows geometrically.** Because the orchestrator only
30
+ ever holds *summaries* of what its sub-agents did — never the raw files — its
31
+ own memory fills up far more slowly. That means **very long tasks with little
32
+ or no compaction**.
33
+ - **Mixed providers avoid blind spots.** Using Claude *and* Codex together means
34
+ one vendor's weak spot or outage doesn't blind or block the whole job — and
35
+ you're never locked in to a single vendor.
36
+ - **Ruthless token efficiency.** Every hand-off is compressed and every read is
37
+ kept small, so the system stays fast and cheap even on marathon tasks.
38
+ - **Durable, authoritative reminders.** The operating rules are re-injected every
39
+ turn through official host hooks, so the assistant doesn't drift off-task or
40
+ start making things up over a long run.
41
+
42
+ ## Install
43
+
44
+ ### What you need first
45
+
46
+ - Node.js 18 or newer (`node --version`)
47
+ - `claude` CLI — installed and signed in (`claude --version`)
48
+ - `codex` CLI — installed and signed in (`codex --version`; optional if you
49
+ only use Claude)
50
+
51
+ Building from source needs extra developer tools — see
52
+ [CONTRIBUTING.md](CONTRIBUTING.md).
53
+
54
+ ### Install the package
55
+
56
+ ```bash
57
+ npm install -g @heretyc/subagent-mcp
58
+ ```
59
+
60
+ This is the standard install for everyone. (Organizations pinning the package
61
+ through GitHub Packages should see [docs/registration.md](docs/registration.md).)
62
+
63
+ ### Wire it into your assistant
64
+
65
+ ```bash
66
+ subagent-mcp setup
67
+ ```
68
+
69
+ Installing the package only ships the program — it does **not** connect anything
70
+ on its own. `subagent-mcp setup` finds your Claude Code / Codex install and
71
+ registers both the server and the per-turn orchestration hooks. Preview first
72
+ with `subagent-mcp setup --dry-run`.
73
+
74
+ ### Restart, then turn on the invariant
75
+
76
+ Restart your Claude Code or Codex session so it picks up the new tools. On Codex,
77
+ run `/hooks` and trust the new hook. Then (recommended):
78
+
79
+ ```bash
80
+ subagent-mcp init --global
81
+ ```
82
+
83
+ This writes a small managed "always delegate" rule block into your global
84
+ assistant config once, so it works across every project. For a single project
85
+ instead, use `subagent-mcp init --root /path/to/project`. Full per-platform
86
+ wiring (Gemini CLI, Claude Desktop, manual setup) is in
87
+ [docs/registration.md](docs/registration.md).
88
+
89
+ ## How to operate it
90
+
91
+ ### Orchestration mode: ON vs OFF
92
+
93
+ - **ON** — your assistant acts as a pure manager. It never reads or writes files
94
+ directly; it delegates every step to sub-agents. Best for big, long-running
95
+ jobs.
96
+ - **OFF** — your assistant works normally, on its own, with no delegation rules.
97
+
98
+ Flip it with the `orchestration-mode` tool. (Desktop apps can toggle the mode
99
+ but don't receive the per-turn hook reminders.)
100
+
101
+ ### The 9 tools at a glance
102
+
103
+ | Tool | What it does |
104
+ |---|---|
105
+ | `launch_agent` | Start a sub-agent on a task (just give a prompt + a task category) |
106
+ | `poll_agent` | Check how an agent is doing and read its latest output |
107
+ | `kill_agent` | Stop a running or quiet agent |
108
+ | `send_message` | Send a follow-up message to a live agent |
109
+ | `list_agents` | See every agent and its current status |
110
+ | `wait` | Pause until a specific agent finishes |
111
+ | `respond_permission` | Approve or deny a sub-agent's parked permission request |
112
+ | `orchestration-mode` | Turn manager-mode ON or OFF |
113
+ | `model-selection-mode` | Let the server auto-pick the model, or allow manual overrides |
114
+
115
+ You never have to choose a model. Give `launch_agent` a plain-English prompt and
116
+ a **task category** (e.g. "coding", "debugging", "security_review") and the
117
+ server picks the best provider, model, and effort for that kind of work.
118
+
119
+ ### How many run at once
120
+
121
+ There is a single machine-wide limit on how many sub-agents run at the same time
122
+ across everything on your computer. The **default is 20**. When the limit is
123
+ reached, a new `launch_agent` is turned down right away — it does not wait in a
124
+ queue. You change the number in the `global-subagent-mcp-config.jsonc` file in
125
+ the install folder (minimum 10); the file is re-read on every launch, so no
126
+ restart is needed.
127
+
128
+ > **Renamed in 2.12.5:** this file was `global-concurrency.jsonc`. The old name
129
+ > is still read (with a one-time deprecation notice) when the new file is absent,
130
+ > for one major version. Rename it yourself when convenient — nothing auto-renames
131
+ > it.
132
+
133
+ The same settings file includes `checkForUpdates` (default `true`). When a newer
134
+ npm version exists, the per-turn hook can show a throttled notice to run
135
+ `subagent-mcp update` and then `subagent-mcp setup`. Set `checkForUpdates` to
136
+ `false`, or run with `SUBAGENT_UPDATE_CHECK=0` / `false`, to disable that check.
137
+
138
+ ## Permissions
139
+
140
+ Launched sub-agents run **gated** by default (new in 2.12.5). Set
141
+ `permissionsCeiling` in `global-subagent-mcp-config.jsonc`:
142
+
143
+ | Mode | What a sub-agent can do |
144
+ |---|---|
145
+ | `auto` | **Default.** Safe reads auto-allow, dangerous actions auto-deny, everything else parks for your decision. |
146
+ | `manual` | Same, but *every* non-denied action parks for a decision — nothing auto-allows. |
147
+ | `yolo` | No gating at all — the pre-2.12.5 behavior. |
148
+
149
+ When a sub-agent's action parks, its status becomes `permission_requested` and it
150
+ shows up in `poll_agent`/`list_agents` and returns early from `wait`. Answer it
151
+ with the **`respond_permission`** tool:
152
+
153
+ ```
154
+ respond_permission(agent_id="…", decision="allow" | "deny", reason="…")
155
+ ```
156
+
157
+ One-time only (no session-wide grants). Omit `request_id` to answer the oldest
158
+ pending request. Unanswered requests auto-deny after 5 minutes; the sub-agent
159
+ keeps running either way. Full spec: [docs/spec/permissions.md](docs/spec/permissions.md).
160
+
161
+ ## Basic debugging
162
+
163
+ - **"An agent looks stuck."** A quiet agent is usually **still alive**, not
164
+ dead. After ~10 minutes with no output an agent is marked `stalled` — it's
165
+ thinking or waiting on a hand-off. It recovers on its own. **Do not kill a
166
+ stalled agent**, and `wait` will not return on one.
167
+ - **"It won't start a new agent (cap reached)."** You've hit the concurrent
168
+ limit. Use `list_agents` to see what's running and `kill_agent` on anything
169
+ you no longer need — that frees a slot immediately. Raising the number in
170
+ `global-concurrency.jsonc` also works.
171
+ - **"Where are the logs?"** Each agent's recent output is available any time via
172
+ `poll_agent`. The server's own diagnostics go to your host's normal MCP
173
+ server log (your Claude Code or Codex session logs) — all server logging goes
174
+ to stderr, never mixed into results.
175
+
176
+ ## Documentation
177
+
178
+ | Document | Contents |
179
+ |---|---|
180
+ | [docs/spec/arch-rationale.md](docs/spec/arch-rationale.md) | Why it's built this way — the full design rationale |
181
+ | [docs/registration.md](docs/registration.md) | Per-platform setup: Claude Code, Codex, Gemini CLI, Claude Desktop |
182
+ | [docs/tools.md](docs/tools.md) | Full tool reference — all eight tools, parameters, return shapes |
183
+ | [docs/usage.md](docs/usage.md) | Model & effort matrix, ultracode mode, usage examples |
184
+ | [docs/SPEC.md](docs/SPEC.md) | Full technical specification |
185
+ | [docs/spec/permissions.md](docs/spec/permissions.md) | Permission system — ceiling modes, shared engine, threat model |
186
+ | [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md) | Agent status meanings (processing / stalled / finished / errored / stopped / permission_requested) |
187
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Developer guide — build, test, publish, contribution workflow |
188
+
189
+ ## License
190
+
191
+ Apache-2.0 — Copyright 2026 Lexi Blackburn
192
+
193
+ See [LICENSE](LICENSE).
@@ -1,67 +1,67 @@
1
- #!/usr/bin/env python3
2
- """advanced-ruleset.py — final-authority model-routing override hook for subagent-mcp.
3
-
4
- (a) PERFORMANCE WARNING: this script runs synchronously inside EVERY launch_agent
5
- call. Slow rules slow every agent launch. Keep rules lean and low-latency —
6
- no network calls, no heavy imports at module top. This is YOUR responsibility;
7
- you have been warned.
8
-
9
- (b) OUTPUT CONTRACT (routing mode): print to stdout ONE JSON array — the modified
10
- candidate list (reorder / filter / replace allowed). Template:
11
- [
12
- {"provider": "claude", "model": "sonnet", "effort": "high", "rank": 1},
13
- {"provider": "codex", "model": "gpt-5.5", "effort": "xhigh", "rank": 2}
14
- ]
15
- Valid providers: claude, codex. Valid models: haiku, sonnet, opus, opus-4-8, fable (claude);
16
- gpt-5.5 (codex). Valid efforts: haiku -> "none" only; sonnet -> medium|high|xhigh|max;
17
- fable -> medium|high|xhigh|max; opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> medium|high|xhigh.
18
- "rank" on output is ignored. An EMPTY array vetoes the launch. Anything else
19
- invalid fails the launch hard — the server validates strictly.
20
-
21
- (c) INPUT CONTRACT (routing mode, invoked as: <python> advanced-ruleset.py route):
22
- stdin receives one JSON object:
23
- { "candidates": [ {"provider","model","effort","rank"} ... ], # rank 1..N best->worst
24
- "context": { "task_category": str, "cwd": str,
25
- "selection_mode": "auto"|"provider"|"provider_model"|"explicit",
26
- "provider": str|None, "model": str|None, "effort": str|None } }
27
- OS environment variables are visible natively (os.environ).
28
-
29
- ENV-CHECK MODE (no arguments): prints {"ready": true|false, "load-rules": true|false}.
30
- Runs once per MCP server process. load-rules false => ruleset silently disabled
31
- for the rest of the process. Set LOAD_RULES = True below to activate.
32
- """
33
- import json
34
- import sys
35
-
36
- LOAD_RULES = False
37
-
38
- # --- Requirements stub (scaffold itself is stdlib-only) ----------------------
39
- # List third-party distributions your rules import, e.g.:
40
- # REQUIREMENTS = ["requests", "pyyaml"]
41
- # Install with: <python> -m pip install <name> ...
42
- REQUIREMENTS = []
43
-
44
- def missing_requirements():
45
- """pip-check helper: returns the REQUIREMENTS entries not importable here."""
46
- import importlib.util
47
- return [r for r in REQUIREMENTS
48
- if importlib.util.find_spec(r.replace("-", "_")) is None]
49
-
50
- def env_check():
51
- missing = missing_requirements()
52
- json.dump({"ready": not missing, "load-rules": bool(LOAD_RULES)}, sys.stdout)
53
-
54
- def apply_rules(candidates, context):
55
- """YOUR RULES HERE. Default: passthrough (returns the list unchanged)."""
56
- return candidates
57
-
58
- def route():
59
- payload = json.load(sys.stdin)
60
- out = apply_rules(payload.get("candidates", []), payload.get("context", {}))
61
- json.dump(out, sys.stdout)
62
-
63
- if __name__ == "__main__":
64
- if len(sys.argv) > 1 and sys.argv[1] == "route":
65
- route()
66
- else:
67
- env_check()
1
+ #!/usr/bin/env python3
2
+ """advanced-ruleset.py — final-authority model-routing override hook for subagent-mcp.
3
+
4
+ (a) PERFORMANCE WARNING: this script runs synchronously inside EVERY launch_agent
5
+ call. Slow rules slow every agent launch. Keep rules lean and low-latency —
6
+ no network calls, no heavy imports at module top. This is YOUR responsibility;
7
+ you have been warned.
8
+
9
+ (b) OUTPUT CONTRACT (routing mode): print to stdout ONE JSON array — the modified
10
+ candidate list (reorder / filter / replace allowed). Template:
11
+ [
12
+ {"provider": "claude", "model": "sonnet", "effort": "high", "rank": 1},
13
+ {"provider": "codex", "model": "gpt-5.5", "effort": "xhigh", "rank": 2}
14
+ ]
15
+ Valid providers: claude, codex. Valid models: haiku, sonnet, opus, opus-4-8, fable (claude);
16
+ gpt-5.5 (codex). Valid efforts: haiku -> "none" only; sonnet -> medium|high|xhigh|max;
17
+ fable -> medium|high|xhigh|max; opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> medium|high|xhigh.
18
+ "rank" on output is ignored. An EMPTY array vetoes the launch. Anything else
19
+ invalid fails the launch hard — the server validates strictly.
20
+
21
+ (c) INPUT CONTRACT (routing mode, invoked as: <python> advanced-ruleset.py route):
22
+ stdin receives one JSON object:
23
+ { "candidates": [ {"provider","model","effort","rank"} ... ], # rank 1..N best->worst
24
+ "context": { "task_category": str, "cwd": str,
25
+ "selection_mode": "auto"|"provider"|"provider_model"|"explicit",
26
+ "provider": str|None, "model": str|None, "effort": str|None } }
27
+ OS environment variables are visible natively (os.environ).
28
+
29
+ ENV-CHECK MODE (no arguments): prints {"ready": true|false, "load-rules": true|false}.
30
+ Runs once per MCP server process. load-rules false => ruleset silently disabled
31
+ for the rest of the process. Set LOAD_RULES = True below to activate.
32
+ """
33
+ import json
34
+ import sys
35
+
36
+ LOAD_RULES = False
37
+
38
+ # --- Requirements stub (scaffold itself is stdlib-only) ----------------------
39
+ # List third-party distributions your rules import, e.g.:
40
+ # REQUIREMENTS = ["requests", "pyyaml"]
41
+ # Install with: <python> -m pip install <name> ...
42
+ REQUIREMENTS = []
43
+
44
+ def missing_requirements():
45
+ """pip-check helper: returns the REQUIREMENTS entries not importable here."""
46
+ import importlib.util
47
+ return [r for r in REQUIREMENTS
48
+ if importlib.util.find_spec(r.replace("-", "_")) is None]
49
+
50
+ def env_check():
51
+ missing = missing_requirements()
52
+ json.dump({"ready": not missing, "load-rules": bool(LOAD_RULES)}, sys.stdout)
53
+
54
+ def apply_rules(candidates, context):
55
+ """YOUR RULES HERE. Default: passthrough (returns the list unchanged)."""
56
+ return candidates
57
+
58
+ def route():
59
+ payload = json.load(sys.stdin)
60
+ out = apply_rules(payload.get("candidates", []), payload.get("context", {}))
61
+ json.dump(out, sys.stdout)
62
+
63
+ if __name__ == "__main__":
64
+ if len(sys.argv) > 1 and sys.argv[1] == "route":
65
+ route()
66
+ else:
67
+ env_check()