glm-coding-router 0.1.1 → 0.3.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/LICENSE +21 -21
- package/README.md +82 -4
- package/dist/bin/glm-chat.js +4 -2
- package/dist/bin/glm-fast.js +50 -0
- package/dist/bin/glm-review.js +4 -2
- package/dist/bin/glm-worker.js +4 -2
- package/dist/cli.js +7 -0
- package/dist/commands/delegate.js +134 -0
- package/dist/core/config.js +10 -0
- package/dist/core/errors.js +24 -0
- package/dist/core/git.js +31 -0
- package/dist/core/profile.js +57 -0
- package/dist/core/worktree.js +118 -0
- package/dist/templates/agents-block.js +44 -44
- package/dist/templates/claude-block.js +47 -47
- package/dist/templates/glm-delegation-skill.js +65 -65
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 hieu9721
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hieu9721
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -27,9 +27,9 @@ Claude / Codex → shell → glm-worker → claude.exe harness → Z.ai endpoint
|
|
|
27
27
|
│
|
|
28
28
|
┌───────────────┼───────────────┐
|
|
29
29
|
▼ ▼ ▼
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
glm-chat glm-fast glm-worker glm-review
|
|
31
|
+
│ │ │ │
|
|
32
|
+
└─────────┴──────────┴──────────┘
|
|
33
33
|
claude.exe
|
|
34
34
|
(injected environment only)
|
|
35
35
|
│
|
|
@@ -67,6 +67,7 @@ After `glm-router init`:
|
|
|
67
67
|
|
|
68
68
|
```powershell
|
|
69
69
|
glm-chat
|
|
70
|
+
glm-fast
|
|
70
71
|
glm-worker "Implement validation and add tests"
|
|
71
72
|
glm-review "Analyze the auth module"
|
|
72
73
|
```
|
|
@@ -85,6 +86,18 @@ glm-chat --any-claude-flag
|
|
|
85
86
|
|
|
86
87
|
Your normal `claude` command and its authentication are untouched.
|
|
87
88
|
|
|
89
|
+
## glm-fast
|
|
90
|
+
|
|
91
|
+
Interactive GLM-backed session pinned to the **fast model** (`models.fast`,
|
|
92
|
+
`glm-5.3-flash` by default) — every model slot in the child environment maps to
|
|
93
|
+
it, so whichever tier Claude Code picks, it gets the fast model. Same pass-through
|
|
94
|
+
arguments as `glm-chat`:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
glm-fast
|
|
98
|
+
glm-fast --profile air
|
|
99
|
+
```
|
|
100
|
+
|
|
88
101
|
## glm-worker
|
|
89
102
|
|
|
90
103
|
Headless implementation worker:
|
|
@@ -123,6 +136,70 @@ glm-review "Inspect this repository"
|
|
|
123
136
|
|
|
124
137
|
Runs with `--tools Read,Glob,Grep` — it cannot edit files or run commands.
|
|
125
138
|
|
|
139
|
+
## Profiles
|
|
140
|
+
|
|
141
|
+
All four task binaries (`glm-chat`, `glm-fast`, `glm-worker`, `glm-review`)
|
|
142
|
+
accept `--profile <name>` to overlay saved model/maxTurns settings. Profiles
|
|
143
|
+
live in `config.json`:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"profiles": {
|
|
148
|
+
"test": { "workerMaxTurns": 10, "fast": "glm-5.3-flash" },
|
|
149
|
+
"frontend": { "main": "glm-5.3", "reviewMaxTurns": 30 }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```powershell
|
|
155
|
+
glm-worker --profile test "Add failing test then fix it"
|
|
156
|
+
glm-review --profile frontend "Review the component tree"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Fields (all optional): `main`, `fast`, `workerMaxTurns`, `reviewMaxTurns`.
|
|
160
|
+
Unknown profile names fail with `ERROR [11]` listing the available ones.
|
|
161
|
+
Note: `--profile` belongs to these wrappers — it shadows Claude Code's own
|
|
162
|
+
`--profile` flag inside them.
|
|
163
|
+
|
|
164
|
+
## delegate
|
|
165
|
+
|
|
166
|
+
Run a GLM worker in an **isolated git worktree** so parallel tasks never trample
|
|
167
|
+
each other's working tree (`glm-router delegate backend|frontend|tests`):
|
|
168
|
+
|
|
169
|
+
```powershell
|
|
170
|
+
glm-router delegate backend "Implement refresh token validation in internal/auth"
|
|
171
|
+
Get-Content task.md | glm-router delegate auth-refresh
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Each run creates a worktree at `<repo>.glm-worktrees\<name>` (outside the repo,
|
|
175
|
+
so your checkout's status stays clean) on a new branch `glm/delegate/<name>`
|
|
176
|
+
cut from `HEAD`, and runs the standard `glm-worker` inside it. The worktree and
|
|
177
|
+
branch are **kept** after the run — the tool never commits, merges, or deletes
|
|
178
|
+
your work; the footer prints the path and the merge command:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
[glm-router] worktree kept at D:\code\my-repo.glm-worktrees\backend
|
|
182
|
+
[glm-router] next: inspect it, then merge glm/delegate/backend (or discard with git worktree remove)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- Prompt priority is stdin → arguments, same as `glm-worker`.
|
|
186
|
+
- Profiles: `--profile test` explicitly, or — when omitted — a profile literally
|
|
187
|
+
named after the delegate (`delegate test` → the `test` profile) if one exists.
|
|
188
|
+
- `--remove` deletes the worktree **after a successful run only**; plain
|
|
189
|
+
`git worktree remove` is used, so git refuses (and the worktree is kept) when
|
|
190
|
+
the worker left uncommitted changes. The branch is always kept.
|
|
191
|
+
- Pre-flight checks fail fast (`ERROR [31]`) when the branch or directory
|
|
192
|
+
already exists, or the repo has no commits yet; outside a git repo →
|
|
193
|
+
`ERROR [30]`. Uncommitted changes in your main checkout are **not** visible
|
|
194
|
+
to the worker — it starts from the last commit.
|
|
195
|
+
- `--dry-run` prints the plan; `--json` prints pre-flight and result objects.
|
|
196
|
+
- Run several delegates concurrently — distinct names cannot collide:
|
|
197
|
+
|
|
198
|
+
```powershell
|
|
199
|
+
glm-router delegate backend "Task A" # terminal 1
|
|
200
|
+
glm-router delegate tests "Task B" # terminal 2
|
|
201
|
+
```
|
|
202
|
+
|
|
126
203
|
## CLI reference
|
|
127
204
|
|
|
128
205
|
```text
|
|
@@ -133,6 +210,7 @@ glm-router key set store ZAI_API_KEY (Windows User Environment)
|
|
|
133
210
|
glm-router key check key configured? from which source?
|
|
134
211
|
glm-router config show
|
|
135
212
|
glm-router config set models.main glm-5.3
|
|
213
|
+
glm-router delegate <name> run a GLM worker in an isolated git worktree
|
|
136
214
|
glm-router project init CLAUDE.md / AGENTS.md managed blocks (--dry-run supported)
|
|
137
215
|
glm-router project remove
|
|
138
216
|
glm-router skill install optional Codex delegation skill
|
|
@@ -241,7 +319,7 @@ npm publish
|
|
|
241
319
|
```
|
|
242
320
|
|
|
243
321
|
`prepublishOnly` runs build + tests. The package ships only `dist/`; the four binaries
|
|
244
|
-
(`glm-router`, `glm-chat`, `glm-worker`, `glm-review`) are declared in `bin`.
|
|
322
|
+
(`glm-router`, `glm-chat`, `glm-fast`, `glm-worker`, `glm-review`) are declared in `bin`.
|
|
245
323
|
|
|
246
324
|
## License
|
|
247
325
|
|
package/dist/bin/glm-chat.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createGlmEnv } from "../core/env.js";
|
|
|
5
5
|
import { Errors, formatGlmError, GlmRouterError } from "../core/errors.js";
|
|
6
6
|
import { isMainModule } from "../core/main-guard.js";
|
|
7
7
|
import { logger, redact } from "../core/logging.js";
|
|
8
|
+
import { applyProfile, extractProfileFlag } from "../core/profile.js";
|
|
8
9
|
import { spawnAgent } from "../core/process.js";
|
|
9
10
|
import { resolveZaiApiKey } from "../core/zai-key.js";
|
|
10
11
|
/**
|
|
@@ -12,7 +13,8 @@ import { resolveZaiApiKey } from "../core/zai-key.js";
|
|
|
12
13
|
* spawn claude interactively with pass-through arguments.
|
|
13
14
|
*/
|
|
14
15
|
export async function runChat(argv) {
|
|
15
|
-
const
|
|
16
|
+
const { rest, profile } = extractProfileFlag(argv);
|
|
17
|
+
const config = applyProfile(loadConfig(), profile);
|
|
16
18
|
const resolved = resolveZaiApiKey();
|
|
17
19
|
if (!resolved) {
|
|
18
20
|
throw Errors.zaiKeyMissing();
|
|
@@ -22,7 +24,7 @@ export async function runChat(argv) {
|
|
|
22
24
|
logger.debug(`spawning ${claudePath}`);
|
|
23
25
|
logger.debug(redact(`env ANTHROPIC_BASE_URL=${env.ANTHROPIC_BASE_URL}`, [resolved.key]));
|
|
24
26
|
return spawnAgent(claudePath, {
|
|
25
|
-
args: [...
|
|
27
|
+
args: [...rest],
|
|
26
28
|
cwd: process.cwd(),
|
|
27
29
|
env,
|
|
28
30
|
interactive: true,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from "../core/config.js";
|
|
3
|
+
import { locateClaude } from "../core/claude.js";
|
|
4
|
+
import { createGlmEnv } from "../core/env.js";
|
|
5
|
+
import { Errors, formatGlmError, GlmRouterError } from "../core/errors.js";
|
|
6
|
+
import { isMainModule } from "../core/main-guard.js";
|
|
7
|
+
import { logger, redact } from "../core/logging.js";
|
|
8
|
+
import { applyProfile, extractProfileFlag } from "../core/profile.js";
|
|
9
|
+
import { spawnAgent } from "../core/process.js";
|
|
10
|
+
import { resolveZaiApiKey } from "../core/zai-key.js";
|
|
11
|
+
/**
|
|
12
|
+
* Effective config for glm-fast: every model slot pinned to the fast model
|
|
13
|
+
* (specs/glm-fast-profiles.md). Applied AFTER the profile so a profile's
|
|
14
|
+
* `fast` model flows into all slots; a profile's `main` is overridden by design.
|
|
15
|
+
*/
|
|
16
|
+
export function fastModelConfig(config) {
|
|
17
|
+
return { ...config, models: { main: config.models.fast, fast: config.models.fast } };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* glm-fast (spec §54, specs/glm-fast-profiles.md): interactive chat pinned to
|
|
21
|
+
* the fast model. Pass-through args like glm-chat; supports --profile.
|
|
22
|
+
*/
|
|
23
|
+
export async function runFast(argv) {
|
|
24
|
+
const { rest, profile } = extractProfileFlag(argv);
|
|
25
|
+
const config = fastModelConfig(applyProfile(loadConfig(), profile));
|
|
26
|
+
const resolved = resolveZaiApiKey();
|
|
27
|
+
if (!resolved) {
|
|
28
|
+
throw Errors.zaiKeyMissing();
|
|
29
|
+
}
|
|
30
|
+
const claudePath = locateClaude(config);
|
|
31
|
+
const env = createGlmEnv(config, resolved.key);
|
|
32
|
+
logger.debug(`spawning ${claudePath}`);
|
|
33
|
+
logger.debug(redact(`env ANTHROPIC_BASE_URL=${env.ANTHROPIC_BASE_URL}`, [resolved.key]));
|
|
34
|
+
return spawnAgent(claudePath, {
|
|
35
|
+
args: [...rest],
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
env,
|
|
38
|
+
interactive: true,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (isMainModule(import.meta.url)) {
|
|
42
|
+
runFast(process.argv.slice(2)).then((code) => process.exit(code), (error) => {
|
|
43
|
+
if (error instanceof GlmRouterError) {
|
|
44
|
+
process.stderr.write(formatGlmError(error) + "\n");
|
|
45
|
+
process.exit(error.exitCode);
|
|
46
|
+
}
|
|
47
|
+
process.stderr.write(String(error) + "\n");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
}
|
package/dist/bin/glm-review.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createGlmEnv } from "../core/env.js";
|
|
|
5
5
|
import { Errors, formatGlmError, GlmRouterError } from "../core/errors.js";
|
|
6
6
|
import { isMainModule } from "../core/main-guard.js";
|
|
7
7
|
import { logger, redact } from "../core/logging.js";
|
|
8
|
+
import { applyProfile, extractProfileFlag } from "../core/profile.js";
|
|
8
9
|
import { readStdin, resolvePrompt } from "../core/prompt.js";
|
|
9
10
|
import { spawnAgent } from "../core/process.js";
|
|
10
11
|
import { resolveZaiApiKey } from "../core/zai-key.js";
|
|
@@ -18,8 +19,9 @@ export function buildReviewArgs(prompt, config) {
|
|
|
18
19
|
* discovery, duplicate detection, dependency inspection, and review.
|
|
19
20
|
*/
|
|
20
21
|
export async function runReview(argv) {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
22
|
+
const { rest, profile } = extractProfileFlag(argv);
|
|
23
|
+
const prompt = await resolvePrompt(rest, readStdin, "glm-review");
|
|
24
|
+
const config = applyProfile(loadConfig(), profile);
|
|
23
25
|
const resolved = resolveZaiApiKey();
|
|
24
26
|
if (!resolved) {
|
|
25
27
|
throw Errors.zaiKeyMissing();
|
package/dist/bin/glm-worker.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createGlmEnv } from "../core/env.js";
|
|
|
5
5
|
import { Errors, formatGlmError, GlmRouterError } from "../core/errors.js";
|
|
6
6
|
import { isMainModule } from "../core/main-guard.js";
|
|
7
7
|
import { logger, redact } from "../core/logging.js";
|
|
8
|
+
import { applyProfile, extractProfileFlag } from "../core/profile.js";
|
|
8
9
|
import { resolvePrompt } from "../core/prompt.js";
|
|
9
10
|
import { spawnAgent } from "../core/process.js";
|
|
10
11
|
import { resolveZaiApiKey } from "../core/zai-key.js";
|
|
@@ -28,8 +29,9 @@ export function buildWorkerArgs(prompt, config) {
|
|
|
28
29
|
* --dangerously-skip-permissions.
|
|
29
30
|
*/
|
|
30
31
|
export async function runWorker(argv) {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
32
|
+
const { rest, profile } = extractProfileFlag(argv);
|
|
33
|
+
const prompt = await resolvePrompt(rest);
|
|
34
|
+
const config = applyProfile(loadConfig(), profile);
|
|
33
35
|
const resolved = resolveZaiApiKey();
|
|
34
36
|
if (!resolved) {
|
|
35
37
|
throw Errors.zaiKeyMissing();
|
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ import { projectInitCommand } from "./commands/project-init.js";
|
|
|
13
13
|
import { projectRemoveCommand } from "./commands/project-remove.js";
|
|
14
14
|
import { skillInstallCommand, skillRemoveCommand } from "./commands/skill.js";
|
|
15
15
|
import { uninstallCommand } from "./commands/uninstall.js";
|
|
16
|
+
import { delegateCommand } from "./commands/delegate.js";
|
|
16
17
|
const program = new Command();
|
|
17
18
|
program
|
|
18
19
|
.name("glm-router")
|
|
@@ -87,6 +88,12 @@ skill
|
|
|
87
88
|
.command("remove")
|
|
88
89
|
.description("remove the glm-delegation skill")
|
|
89
90
|
.action(() => execute(() => Promise.resolve(skillRemoveCommand(globalOptions()))));
|
|
91
|
+
program
|
|
92
|
+
.command("delegate <name> [prompt...]")
|
|
93
|
+
.description("run a GLM worker in an isolated git worktree (branch glm/delegate/<name>)")
|
|
94
|
+
.option("--profile <name>", "profile overlay (defaults to a profile named <name> if defined)")
|
|
95
|
+
.option("--remove", "remove the worktree after a successful run (branch is always kept)")
|
|
96
|
+
.action((name, prompt, commandOptions) => execute(() => delegateCommand(name, prompt, { ...globalOptions(), ...commandOptions })));
|
|
90
97
|
program
|
|
91
98
|
.command("uninstall")
|
|
92
99
|
.description("guided removal (keeps ZAI_API_KEY by default)")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { loadConfig } from "../core/config.js";
|
|
3
|
+
import { locateClaude } from "../core/claude.js";
|
|
4
|
+
import { createGlmEnv } from "../core/env.js";
|
|
5
|
+
import { Errors } from "../core/errors.js";
|
|
6
|
+
import { gitTopLevel } from "../core/git.js";
|
|
7
|
+
import { logger, redact } from "../core/logging.js";
|
|
8
|
+
import { spawnAgent } from "../core/process.js";
|
|
9
|
+
import { applyProfile } from "../core/profile.js";
|
|
10
|
+
import { readStdin, resolvePrompt } from "../core/prompt.js";
|
|
11
|
+
import { resolveZaiApiKey } from "../core/zai-key.js";
|
|
12
|
+
import { createDelegateWorktree, delegateBranch, delegateWorktreePath, removeDelegateWorktree, rollbackDelegateBranch, validateDelegateName, } from "../core/worktree.js";
|
|
13
|
+
import { buildWorkerArgs } from "../bin/glm-worker.js";
|
|
14
|
+
import { emitJson } from "./context.js";
|
|
15
|
+
/** A profile literally named after the delegate applies unless --profile says otherwise. */
|
|
16
|
+
function resolveProfileName(name, options, config) {
|
|
17
|
+
if (options.profile) {
|
|
18
|
+
return options.profile;
|
|
19
|
+
}
|
|
20
|
+
return config.profiles[name] ? name : undefined;
|
|
21
|
+
}
|
|
22
|
+
function banner(text, quiet) {
|
|
23
|
+
if (!quiet) {
|
|
24
|
+
process.stdout.write(`${text}\n`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* glm-router delegate (spec §54 v0.3, specs/delegate-worktrees.md): run a GLM
|
|
29
|
+
* worker in an isolated git worktree. Worktree + branch are kept after the run
|
|
30
|
+
* (no automatic git commits); --remove drops the worktree after success only.
|
|
31
|
+
*/
|
|
32
|
+
export async function delegateCommand(name, promptArgs, options, deps = {}) {
|
|
33
|
+
validateDelegateName(name);
|
|
34
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
35
|
+
const env = deps.env ?? process.env;
|
|
36
|
+
const runGit = deps.runGit;
|
|
37
|
+
const repoRoot = await gitTopLevel(cwd, runGit);
|
|
38
|
+
if (!repoRoot) {
|
|
39
|
+
throw Errors.gitRepoRequired(cwd);
|
|
40
|
+
}
|
|
41
|
+
const home = deps.home ?? os.homedir();
|
|
42
|
+
const baseConfig = loadConfig(home);
|
|
43
|
+
const profileName = resolveProfileName(name, options, baseConfig);
|
|
44
|
+
const config = applyProfile(baseConfig, profileName);
|
|
45
|
+
const resolved = resolveZaiApiKey({ env, readUserEnv: deps.readUserEnv });
|
|
46
|
+
if (!resolved) {
|
|
47
|
+
throw Errors.zaiKeyMissing();
|
|
48
|
+
}
|
|
49
|
+
const claudePath = locateClaude(config, env);
|
|
50
|
+
const prompt = await resolvePrompt(promptArgs, deps.readStdinFn ?? readStdin, "glm-router delegate");
|
|
51
|
+
const branch = delegateBranch(name);
|
|
52
|
+
const worktreePath = delegateWorktreePath(repoRoot, name);
|
|
53
|
+
if (options.dryRun) {
|
|
54
|
+
if (options.json) {
|
|
55
|
+
emitJson({ name, profile: profileName ?? null, worktree: worktreePath, branch, dryRun: true });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
banner(`would create worktree ${worktreePath}`, options.quiet);
|
|
59
|
+
banner(`would create branch ${branch} (from HEAD)`, options.quiet);
|
|
60
|
+
banner(`would run glm-worker in the worktree with the given prompt`, options.quiet);
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
// Collision checks run inside createDelegateWorktree before anything is written.
|
|
65
|
+
const createdPath = await createDelegateWorktree(repoRoot, name, { runGit });
|
|
66
|
+
if (options.json) {
|
|
67
|
+
emitJson({ name, profile: profileName ?? null, worktree: createdPath, branch });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
banner(`[glm-router] delegate ${name}`, options.quiet);
|
|
71
|
+
banner(`[glm-router] worktree ${createdPath}`, options.quiet);
|
|
72
|
+
banner(`[glm-router] branch ${branch}`, options.quiet);
|
|
73
|
+
if (profileName) {
|
|
74
|
+
banner(`[glm-router] profile ${profileName}`, options.quiet);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const args = buildWorkerArgs(prompt, config);
|
|
78
|
+
const childEnv = createGlmEnv(config, resolved.key, env);
|
|
79
|
+
logger.debug(redact(`spawning ${claudePath} in ${createdPath}`, [resolved.key]));
|
|
80
|
+
const spawn = deps.spawn ?? ((binPath, spawnOptions) => spawnAgent(binPath, {
|
|
81
|
+
args: [...spawnOptions.args],
|
|
82
|
+
cwd: spawnOptions.cwd,
|
|
83
|
+
env: spawnOptions.env,
|
|
84
|
+
interactive: false,
|
|
85
|
+
}));
|
|
86
|
+
let exitCode;
|
|
87
|
+
try {
|
|
88
|
+
exitCode = await spawn(claudePath, { args, cwd: createdPath, env: childEnv, interactive: false });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// Worker never started: roll back the pristine worktree and its branch so
|
|
92
|
+
// the same delegate name can simply be re-run.
|
|
93
|
+
const worktreeComplaint = await removeDelegateWorktree(repoRoot, createdPath, { runGit });
|
|
94
|
+
if (worktreeComplaint) {
|
|
95
|
+
logger.debug(`worktree kept after spawn failure: ${worktreeComplaint}`);
|
|
96
|
+
}
|
|
97
|
+
const branchComplaint = await rollbackDelegateBranch(repoRoot, branch, { runGit });
|
|
98
|
+
if (branchComplaint) {
|
|
99
|
+
logger.debug(`branch kept after spawn failure: ${branchComplaint}`);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
let removed = false;
|
|
104
|
+
if (options.remove && exitCode === 0) {
|
|
105
|
+
const complaint = await removeDelegateWorktree(repoRoot, createdPath, { runGit });
|
|
106
|
+
if (complaint) {
|
|
107
|
+
removed = false;
|
|
108
|
+
const message = `git refused to remove the worktree (it may contain uncommitted work):\n ${complaint.trim().split("\n").join("\n ")}`;
|
|
109
|
+
if (options.json) {
|
|
110
|
+
emitJson({ name, exitCode, worktree: createdPath, branch, removed: false, note: message });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
banner(`[glm-router] ${message}`, options.quiet);
|
|
114
|
+
banner(`[glm-router] worktree kept at ${createdPath}`, options.quiet);
|
|
115
|
+
}
|
|
116
|
+
return exitCode;
|
|
117
|
+
}
|
|
118
|
+
removed = true;
|
|
119
|
+
}
|
|
120
|
+
if (options.json) {
|
|
121
|
+
emitJson({ name, exitCode, worktree: createdPath, branch, removed });
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
banner(`[glm-router] worker exited ${exitCode}`, options.quiet);
|
|
125
|
+
if (removed) {
|
|
126
|
+
banner(`[glm-router] worktree removed; branch ${branch} kept`, options.quiet);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
banner(`[glm-router] worktree kept at ${createdPath}`, options.quiet);
|
|
130
|
+
banner(`[glm-router] next: inspect it, then merge ${branch} (or discard with git worktree remove)`, options.quiet);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return exitCode;
|
|
134
|
+
}
|
package/dist/core/config.js
CHANGED
|
@@ -6,6 +6,13 @@ import { configDir, configPath } from "./paths.js";
|
|
|
6
6
|
export const DEFAULT_ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
7
7
|
export const DEFAULT_MAIN_MODEL = "glm-5.3";
|
|
8
8
|
export const DEFAULT_FAST_MODEL = "glm-5.3-flash";
|
|
9
|
+
/** Named model/maxTurns overlay selected via --profile (specs/glm-fast-profiles.md). */
|
|
10
|
+
export const ProfileSchema = z.object({
|
|
11
|
+
main: z.string().min(1).optional(),
|
|
12
|
+
fast: z.string().min(1).optional(),
|
|
13
|
+
workerMaxTurns: z.number().int().positive().optional(),
|
|
14
|
+
reviewMaxTurns: z.number().int().positive().optional(),
|
|
15
|
+
});
|
|
9
16
|
export const ConfigSchema = z.object({
|
|
10
17
|
schemaVersion: z.literal(1),
|
|
11
18
|
provider: z.object({
|
|
@@ -30,6 +37,8 @@ export const ConfigSchema = z.object({
|
|
|
30
37
|
// Optional executable overrides used by discovery (spec §33, §34).
|
|
31
38
|
claudePath: z.string().min(1).optional(),
|
|
32
39
|
codexPath: z.string().min(1).optional(),
|
|
40
|
+
// Named overlays selected via --profile (specs/glm-fast-profiles.md).
|
|
41
|
+
profiles: z.record(z.string(), ProfileSchema).default({}),
|
|
33
42
|
});
|
|
34
43
|
export function defaultConfig() {
|
|
35
44
|
return {
|
|
@@ -49,6 +58,7 @@ export function defaultConfig() {
|
|
|
49
58
|
codex: true,
|
|
50
59
|
codexSkill: true,
|
|
51
60
|
},
|
|
61
|
+
profiles: {},
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
64
|
/**
|
package/dist/core/errors.js
CHANGED
|
@@ -96,6 +96,30 @@ export const Errors = {
|
|
|
96
96
|
],
|
|
97
97
|
exitCode: ExitCode.InvalidArgs,
|
|
98
98
|
}),
|
|
99
|
+
gitNotFound: () => new GlmRouterError({
|
|
100
|
+
name: "GIT_NOT_FOUND",
|
|
101
|
+
message: "git was not found on PATH.",
|
|
102
|
+
hint: ["delegate needs git for worktree isolation.", "", "Install Git for Windows: https://git-scm.com/download/win"],
|
|
103
|
+
exitCode: ExitCode.ProjectRootNotFound,
|
|
104
|
+
}),
|
|
105
|
+
gitRepoRequired: (cwd) => new GlmRouterError({
|
|
106
|
+
name: "GIT_REPO_REQUIRED",
|
|
107
|
+
message: `Not inside a git repository (cwd: ${cwd}).`,
|
|
108
|
+
hint: ["delegate runs each worker in a git worktree and needs a repo root.", "", "Run it from inside the project's git repository, or create one:", "", " git init"],
|
|
109
|
+
exitCode: ExitCode.ProjectRootNotFound,
|
|
110
|
+
}),
|
|
111
|
+
worktreeFailed: (operation, cause, hint) => new GlmRouterError({
|
|
112
|
+
name: "WORKTREE_FAILED",
|
|
113
|
+
message: `git ${operation} failed: ${cause.trim() || "unknown git error"}`,
|
|
114
|
+
hint: hint ?? ["Fix the state git describes above, then re-run the delegate command."],
|
|
115
|
+
exitCode: ExitCode.ManagedFileWriteFailed,
|
|
116
|
+
}),
|
|
117
|
+
invalidDelegateName: (name) => new GlmRouterError({
|
|
118
|
+
name: "INVALID_DELEGATE_NAME",
|
|
119
|
+
message: `"${name}" is not a valid delegate name.`,
|
|
120
|
+
hint: ["Use letters, digits, dots, dashes, underscores; start with a letter or digit.", "", "Examples: backend, auth-refresh, tests.v2"],
|
|
121
|
+
exitCode: ExitCode.InvalidArgs,
|
|
122
|
+
}),
|
|
99
123
|
managedBlockCorrupt: (file, cause) => new GlmRouterError({
|
|
100
124
|
name: "MANAGED_BLOCK_CORRUPT",
|
|
101
125
|
message: `Managed block in ${file} is malformed: ${cause}`,
|
package/dist/core/git.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Errors } from "./errors.js";
|
|
4
|
+
function isENOENT(error) {
|
|
5
|
+
return error !== null && typeof error === "object" && error.code === "ENOENT";
|
|
6
|
+
}
|
|
7
|
+
/** Real git runner: resolves with the exit code instead of throwing on failure. */
|
|
8
|
+
export const runGit = (args, cwd) => new Promise((resolve, reject) => {
|
|
9
|
+
execFile("git", args, { cwd, windowsHide: true, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
10
|
+
if (error && isENOENT(error)) {
|
|
11
|
+
reject(Errors.gitNotFound());
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const code = error && typeof error.code === "number"
|
|
15
|
+
? error.code
|
|
16
|
+
: 0;
|
|
17
|
+
resolve({ code, stdout: stdout ?? "", stderr: stderr ?? "" });
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Strict repo-root lookup for delegate (specs/delegate-worktrees.md):
|
|
22
|
+
* unlike findProjectRoot, undefined when cwd is not inside a git repo.
|
|
23
|
+
*/
|
|
24
|
+
export async function gitTopLevel(cwd, run = runGit) {
|
|
25
|
+
const result = await run(["rev-parse", "--show-toplevel"], cwd);
|
|
26
|
+
if (result.code !== 0) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const root = result.stdout.trim();
|
|
30
|
+
return root.length > 0 ? path.resolve(root) : undefined;
|
|
31
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Errors } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Remove the router's --profile flag from argv (specs/glm-fast-profiles.md).
|
|
4
|
+
* Accepts `--profile name` and `--profile=name`; the first occurrence wins,
|
|
5
|
+
* later ones are consumed too so they never leak into the forwarded args.
|
|
6
|
+
*/
|
|
7
|
+
export function extractProfileFlag(argv) {
|
|
8
|
+
const rest = [];
|
|
9
|
+
let profile;
|
|
10
|
+
for (let i = 0; i < argv.length; i++) {
|
|
11
|
+
const arg = argv[i];
|
|
12
|
+
if (arg === "--profile") {
|
|
13
|
+
const value = argv[i + 1];
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
throw Errors.configInvalid("--profile requires a profile name");
|
|
16
|
+
}
|
|
17
|
+
profile ??= value;
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg.startsWith("--profile=")) {
|
|
22
|
+
const value = arg.slice("--profile=".length);
|
|
23
|
+
if (value.length === 0) {
|
|
24
|
+
throw Errors.configInvalid("--profile requires a profile name");
|
|
25
|
+
}
|
|
26
|
+
profile ??= value;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
rest.push(arg);
|
|
30
|
+
}
|
|
31
|
+
return { rest, profile };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Overlay a named profile onto the config (specs/glm-fast-profiles.md):
|
|
35
|
+
* main/fast models plus worker/review maxTurns. Unknown names fail with
|
|
36
|
+
* ERROR [11] listing the defined profiles.
|
|
37
|
+
*/
|
|
38
|
+
export function applyProfile(config, name) {
|
|
39
|
+
if (!name) {
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
const profile = config.profiles[name];
|
|
43
|
+
if (!profile) {
|
|
44
|
+
const known = Object.keys(config.profiles);
|
|
45
|
+
const listed = known.length > 0 ? known.sort().join(", ") : "(none defined)";
|
|
46
|
+
throw Errors.configInvalid(`unknown profile "${name}" — available profiles: ${listed}`);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...config,
|
|
50
|
+
models: {
|
|
51
|
+
main: profile.main ?? config.models.main,
|
|
52
|
+
fast: profile.fast ?? config.models.fast,
|
|
53
|
+
},
|
|
54
|
+
worker: { maxTurns: profile.workerMaxTurns ?? config.worker.maxTurns },
|
|
55
|
+
review: { maxTurns: profile.reviewMaxTurns ?? config.review.maxTurns },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { runGit } from "./git.js";
|
|
4
|
+
import { Errors } from "./errors.js";
|
|
5
|
+
import { logger } from "./logging.js";
|
|
6
|
+
/** Branch prefix for delegate worktrees (specs/delegate-worktrees.md). */
|
|
7
|
+
export const DELEGATE_BRANCH_PREFIX = "glm/delegate/";
|
|
8
|
+
/** Valid delegate slugs: no path separators, spaces, or leading dash (specs/delegate-worktrees.md). */
|
|
9
|
+
export function validateDelegateName(name) {
|
|
10
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name) || name === "." || name === ".." || name.toLowerCase() === ".git") {
|
|
11
|
+
throw Errors.invalidDelegateName(name);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function delegateBranch(name) {
|
|
15
|
+
return `${DELEGATE_BRANCH_PREFIX}${name}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Worktrees live outside the repo so the main checkout's status stays clean
|
|
19
|
+
* and no .gitignore edit is ever needed: <parent-of-root>/<repo>.glm-worktrees/<name>
|
|
20
|
+
*/
|
|
21
|
+
export function worktreeBaseDir(repoRoot) {
|
|
22
|
+
return path.join(path.dirname(repoRoot), `${path.basename(repoRoot)}.glm-worktrees`);
|
|
23
|
+
}
|
|
24
|
+
export function delegateWorktreePath(repoRoot, name) {
|
|
25
|
+
return path.join(worktreeBaseDir(repoRoot), name);
|
|
26
|
+
}
|
|
27
|
+
function ok(result) {
|
|
28
|
+
return result.code === 0;
|
|
29
|
+
}
|
|
30
|
+
/** True when a local branch already exists (rev-parse --verify --quiet). */
|
|
31
|
+
export async function branchExists(repoRoot, branch, deps = {}) {
|
|
32
|
+
const run = deps.runGit ?? runGit;
|
|
33
|
+
const result = await run(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], repoRoot);
|
|
34
|
+
return ok(result);
|
|
35
|
+
}
|
|
36
|
+
/** True when HEAD resolves (the repo has at least one commit). */
|
|
37
|
+
export async function headIsBorn(repoRoot, deps = {}) {
|
|
38
|
+
const run = deps.runGit ?? runGit;
|
|
39
|
+
const result = await run(["rev-parse", "--verify", "--quiet", "HEAD"], repoRoot);
|
|
40
|
+
return ok(result);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pre-flight collision checks shared by the real run and --dry-run
|
|
44
|
+
* (specs/delegate-worktrees.md): fail before creating anything.
|
|
45
|
+
*/
|
|
46
|
+
export async function assertWorktreeAvailable(repoRoot, name, deps = {}) {
|
|
47
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
48
|
+
const branch = delegateBranch(name);
|
|
49
|
+
const worktreePath = delegateWorktreePath(repoRoot, name);
|
|
50
|
+
if (await branchExists(repoRoot, branch, deps)) {
|
|
51
|
+
throw Errors.worktreeFailed("branch", `branch ${branch} already exists`, [
|
|
52
|
+
"Inspect or merge it first:",
|
|
53
|
+
"",
|
|
54
|
+
` git log ${branch}`,
|
|
55
|
+
` git merge ${branch}`,
|
|
56
|
+
"",
|
|
57
|
+
`or delete it if it is unwanted:`,
|
|
58
|
+
"",
|
|
59
|
+
` git branch -D ${branch}`,
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
if (existsSync(worktreePath)) {
|
|
63
|
+
throw Errors.worktreeFailed("worktree add", `path already exists: ${worktreePath}`, [
|
|
64
|
+
"A previous worktree directory is in the way:",
|
|
65
|
+
"",
|
|
66
|
+
` ${worktreePath}`,
|
|
67
|
+
"",
|
|
68
|
+
"Remove it (or run `git worktree prune`) and re-run the delegate command.",
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
if (!(await headIsBorn(repoRoot, deps))) {
|
|
72
|
+
throw Errors.worktreeFailed("worktree add", "HEAD does not point at a commit (repository has no commits yet)", [
|
|
73
|
+
"delegate creates the worktree from HEAD, so the repository needs at least one commit first:",
|
|
74
|
+
"",
|
|
75
|
+
" git add -A && git commit -m \"initial commit\"",
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
return worktreePath;
|
|
79
|
+
}
|
|
80
|
+
/** Create the delegate worktree + branch from HEAD. Returns the worktree path. */
|
|
81
|
+
export async function createDelegateWorktree(repoRoot, name, deps = {}) {
|
|
82
|
+
const run = deps.runGit ?? runGit;
|
|
83
|
+
const worktreePath = await assertWorktreeAvailable(repoRoot, name, deps);
|
|
84
|
+
const branch = delegateBranch(name);
|
|
85
|
+
logger.debug(`git worktree add -b ${branch} ${worktreePath}`);
|
|
86
|
+
const result = await run(["worktree", "add", "-b", branch, worktreePath], repoRoot);
|
|
87
|
+
if (!ok(result)) {
|
|
88
|
+
throw Errors.worktreeFailed("worktree add", result.stderr || result.stdout, ["The worktree was not created; the repository was left as it was."]);
|
|
89
|
+
}
|
|
90
|
+
return worktreePath;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Remove a delegate worktree with plain `git worktree remove` — git refuses
|
|
94
|
+
* on dirty/untracked trees, which is exactly the safety we want
|
|
95
|
+
* (specs/delegate-worktrees.md, `--remove`). Never touches the branch.
|
|
96
|
+
* Returns undefined when removal succeeded, or git's complaint when not.
|
|
97
|
+
*/
|
|
98
|
+
export async function removeDelegateWorktree(repoRoot, worktreePath, deps = {}) {
|
|
99
|
+
const run = deps.runGit ?? runGit;
|
|
100
|
+
const result = await run(["worktree", "remove", worktreePath], repoRoot);
|
|
101
|
+
if (ok(result)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return result.stderr || result.stdout || "git worktree remove failed";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete a branch this invocation created and never advanced (worker never
|
|
108
|
+
* started). Only ever called on the spawn-failure rollback path, where the
|
|
109
|
+
* branch provably sits at HEAD — never on branches a worker may have moved.
|
|
110
|
+
*/
|
|
111
|
+
export async function rollbackDelegateBranch(repoRoot, branch, deps = {}) {
|
|
112
|
+
const run = deps.runGit ?? runGit;
|
|
113
|
+
const result = await run(["branch", "-D", branch], repoRoot);
|
|
114
|
+
if (ok(result)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return result.stderr || result.stdout || "git branch -D failed";
|
|
118
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
/** Managed block content for AGENTS.md (spec §24). Keep in sync with the spec. */
|
|
2
|
-
export const AGENTS_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
-
|
|
4
|
-
## GLM Worker Delegation
|
|
5
|
-
|
|
6
|
-
Available commands:
|
|
7
|
-
|
|
8
|
-
- \`glm-worker "<task>"\`
|
|
9
|
-
- \`glm-review "<task>"\`
|
|
10
|
-
|
|
11
|
-
Codex is the primary orchestrator.
|
|
12
|
-
|
|
13
|
-
Delegate:
|
|
14
|
-
- CRUD
|
|
15
|
-
- boilerplate
|
|
16
|
-
- tests
|
|
17
|
-
- documentation
|
|
18
|
-
- mechanical refactoring
|
|
19
|
-
- repository exploration
|
|
20
|
-
- straightforward implementation
|
|
21
|
-
|
|
22
|
-
Keep in Codex:
|
|
23
|
-
- requirements
|
|
24
|
-
- planning
|
|
25
|
-
- architecture
|
|
26
|
-
- ambiguous business logic
|
|
27
|
-
- complex debugging
|
|
28
|
-
- security decisions
|
|
29
|
-
- integration
|
|
30
|
-
- final review
|
|
31
|
-
|
|
32
|
-
Before delegation create a task packet:
|
|
33
|
-
|
|
34
|
-
TASK
|
|
35
|
-
SCOPE
|
|
36
|
-
FILES ALLOWED TO MODIFY
|
|
37
|
-
FILES NOT TO MODIFY
|
|
38
|
-
REQUIREMENTS
|
|
39
|
-
CONSTRAINTS
|
|
40
|
-
ACCEPTANCE CRITERIA
|
|
41
|
-
VALIDATION
|
|
42
|
-
EXPECTED OUTPUT
|
|
43
|
-
|
|
44
|
-
Never trust a worker's success report without inspecting the resulting changes.
|
|
45
|
-
|
|
2
|
+
export const AGENTS_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
+
|
|
4
|
+
## GLM Worker Delegation
|
|
5
|
+
|
|
6
|
+
Available commands:
|
|
7
|
+
|
|
8
|
+
- \`glm-worker "<task>"\`
|
|
9
|
+
- \`glm-review "<task>"\`
|
|
10
|
+
|
|
11
|
+
Codex is the primary orchestrator.
|
|
12
|
+
|
|
13
|
+
Delegate:
|
|
14
|
+
- CRUD
|
|
15
|
+
- boilerplate
|
|
16
|
+
- tests
|
|
17
|
+
- documentation
|
|
18
|
+
- mechanical refactoring
|
|
19
|
+
- repository exploration
|
|
20
|
+
- straightforward implementation
|
|
21
|
+
|
|
22
|
+
Keep in Codex:
|
|
23
|
+
- requirements
|
|
24
|
+
- planning
|
|
25
|
+
- architecture
|
|
26
|
+
- ambiguous business logic
|
|
27
|
+
- complex debugging
|
|
28
|
+
- security decisions
|
|
29
|
+
- integration
|
|
30
|
+
- final review
|
|
31
|
+
|
|
32
|
+
Before delegation create a task packet:
|
|
33
|
+
|
|
34
|
+
TASK
|
|
35
|
+
SCOPE
|
|
36
|
+
FILES ALLOWED TO MODIFY
|
|
37
|
+
FILES NOT TO MODIFY
|
|
38
|
+
REQUIREMENTS
|
|
39
|
+
CONSTRAINTS
|
|
40
|
+
ACCEPTANCE CRITERIA
|
|
41
|
+
VALIDATION
|
|
42
|
+
EXPECTED OUTPUT
|
|
43
|
+
|
|
44
|
+
Never trust a worker's success report without inspecting the resulting changes.
|
|
45
|
+
|
|
46
46
|
<!-- glm-coding-router:end -->`;
|
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
/** Managed block content for CLAUDE.md (spec §20). Keep in sync with the spec. */
|
|
2
|
-
export const CLAUDE_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
-
|
|
4
|
-
## GLM Worker Delegation
|
|
5
|
-
|
|
6
|
-
GLM workers available:
|
|
7
|
-
|
|
8
|
-
- \`glm-worker "<task>"\`
|
|
9
|
-
- \`glm-review "<task>"\`
|
|
10
|
-
|
|
11
|
-
Delegate well-scoped, implementation-heavy work to GLM.
|
|
12
|
-
|
|
13
|
-
Use GLM for:
|
|
14
|
-
- repository exploration
|
|
15
|
-
- CRUD
|
|
16
|
-
- boilerplate
|
|
17
|
-
- tests
|
|
18
|
-
- documentation
|
|
19
|
-
- mechanical refactoring
|
|
20
|
-
- straightforward implementation
|
|
21
|
-
|
|
22
|
-
Claude remains responsible for:
|
|
23
|
-
- requirements
|
|
24
|
-
- architecture
|
|
25
|
-
- ambiguous business rules
|
|
26
|
-
- security-sensitive decisions
|
|
27
|
-
- complex debugging
|
|
28
|
-
- integration
|
|
29
|
-
- final review
|
|
30
|
-
|
|
31
|
-
Before delegation, define:
|
|
32
|
-
- task
|
|
33
|
-
- scope
|
|
34
|
-
- allowed files
|
|
35
|
-
- forbidden files
|
|
36
|
-
- requirements
|
|
37
|
-
- constraints
|
|
38
|
-
- acceptance criteria
|
|
39
|
-
- validation command
|
|
40
|
-
- expected output
|
|
41
|
-
|
|
42
|
-
After worker completion:
|
|
43
|
-
1. inspect the actual diff
|
|
44
|
-
2. validate against requirements
|
|
45
|
-
3. run relevant tests
|
|
46
|
-
4. resolve integration problems
|
|
47
|
-
5. accept only after verification
|
|
48
|
-
|
|
2
|
+
export const CLAUDE_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
+
|
|
4
|
+
## GLM Worker Delegation
|
|
5
|
+
|
|
6
|
+
GLM workers available:
|
|
7
|
+
|
|
8
|
+
- \`glm-worker "<task>"\`
|
|
9
|
+
- \`glm-review "<task>"\`
|
|
10
|
+
|
|
11
|
+
Delegate well-scoped, implementation-heavy work to GLM.
|
|
12
|
+
|
|
13
|
+
Use GLM for:
|
|
14
|
+
- repository exploration
|
|
15
|
+
- CRUD
|
|
16
|
+
- boilerplate
|
|
17
|
+
- tests
|
|
18
|
+
- documentation
|
|
19
|
+
- mechanical refactoring
|
|
20
|
+
- straightforward implementation
|
|
21
|
+
|
|
22
|
+
Claude remains responsible for:
|
|
23
|
+
- requirements
|
|
24
|
+
- architecture
|
|
25
|
+
- ambiguous business rules
|
|
26
|
+
- security-sensitive decisions
|
|
27
|
+
- complex debugging
|
|
28
|
+
- integration
|
|
29
|
+
- final review
|
|
30
|
+
|
|
31
|
+
Before delegation, define:
|
|
32
|
+
- task
|
|
33
|
+
- scope
|
|
34
|
+
- allowed files
|
|
35
|
+
- forbidden files
|
|
36
|
+
- requirements
|
|
37
|
+
- constraints
|
|
38
|
+
- acceptance criteria
|
|
39
|
+
- validation command
|
|
40
|
+
- expected output
|
|
41
|
+
|
|
42
|
+
After worker completion:
|
|
43
|
+
1. inspect the actual diff
|
|
44
|
+
2. validate against requirements
|
|
45
|
+
3. run relevant tests
|
|
46
|
+
4. resolve integration problems
|
|
47
|
+
5. accept only after verification
|
|
48
|
+
|
|
49
49
|
<!-- glm-coding-router:end -->`;
|
|
@@ -1,68 +1,68 @@
|
|
|
1
1
|
/** Codex skill definition (spec §26). Keep in sync with the spec. */
|
|
2
2
|
export const GLM_DELEGATION_SKILL_NAME = "glm-delegation";
|
|
3
|
-
export const GLM_DELEGATION_SKILL_MD = `---
|
|
4
|
-
name: glm-delegation
|
|
5
|
-
description: >
|
|
6
|
-
Delegate well-scoped implementation, testing,
|
|
7
|
-
repository exploration, boilerplate, CRUD,
|
|
8
|
-
documentation, and mechanical refactoring to
|
|
9
|
-
GLM Coding Plan workers.
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# GLM Delegation
|
|
13
|
-
|
|
14
|
-
Available commands:
|
|
15
|
-
|
|
16
|
-
glm-worker "<task>"
|
|
17
|
-
glm-review "<task>"
|
|
18
|
-
|
|
19
|
-
## Use glm-review for
|
|
20
|
-
|
|
21
|
-
- repository exploration
|
|
22
|
-
- dependency analysis
|
|
23
|
-
- locating implementations
|
|
24
|
-
- call-chain discovery
|
|
25
|
-
- code review
|
|
26
|
-
|
|
27
|
-
## Use glm-worker for
|
|
28
|
-
|
|
29
|
-
- CRUD
|
|
30
|
-
- unit tests
|
|
31
|
-
- implementation
|
|
32
|
-
- documentation
|
|
33
|
-
- repetitive changes
|
|
34
|
-
- mechanical refactoring
|
|
35
|
-
|
|
36
|
-
## Keep in primary Codex agent
|
|
37
|
-
|
|
38
|
-
- requirements
|
|
39
|
-
- architecture
|
|
40
|
-
- ambiguous rules
|
|
41
|
-
- security-sensitive design
|
|
42
|
-
- difficult debugging
|
|
43
|
-
- integration
|
|
44
|
-
- final acceptance
|
|
45
|
-
|
|
46
|
-
## Delegation packet
|
|
47
|
-
|
|
48
|
-
Always provide:
|
|
49
|
-
|
|
50
|
-
TASK
|
|
51
|
-
SCOPE
|
|
52
|
-
ALLOWED FILES
|
|
53
|
-
FORBIDDEN FILES
|
|
54
|
-
REQUIREMENTS
|
|
55
|
-
CONSTRAINTS
|
|
56
|
-
ACCEPTANCE CRITERIA
|
|
57
|
-
VALIDATION
|
|
58
|
-
EXPECTED OUTPUT
|
|
59
|
-
|
|
60
|
-
## Verification
|
|
61
|
-
|
|
62
|
-
After GLM finishes:
|
|
63
|
-
|
|
64
|
-
- inspect the actual diff
|
|
65
|
-
- independently run relevant validation
|
|
66
|
-
- compare implementation with requirements
|
|
67
|
-
- reject or correct worker output when needed
|
|
3
|
+
export const GLM_DELEGATION_SKILL_MD = `---
|
|
4
|
+
name: glm-delegation
|
|
5
|
+
description: >
|
|
6
|
+
Delegate well-scoped implementation, testing,
|
|
7
|
+
repository exploration, boilerplate, CRUD,
|
|
8
|
+
documentation, and mechanical refactoring to
|
|
9
|
+
GLM Coding Plan workers.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# GLM Delegation
|
|
13
|
+
|
|
14
|
+
Available commands:
|
|
15
|
+
|
|
16
|
+
glm-worker "<task>"
|
|
17
|
+
glm-review "<task>"
|
|
18
|
+
|
|
19
|
+
## Use glm-review for
|
|
20
|
+
|
|
21
|
+
- repository exploration
|
|
22
|
+
- dependency analysis
|
|
23
|
+
- locating implementations
|
|
24
|
+
- call-chain discovery
|
|
25
|
+
- code review
|
|
26
|
+
|
|
27
|
+
## Use glm-worker for
|
|
28
|
+
|
|
29
|
+
- CRUD
|
|
30
|
+
- unit tests
|
|
31
|
+
- implementation
|
|
32
|
+
- documentation
|
|
33
|
+
- repetitive changes
|
|
34
|
+
- mechanical refactoring
|
|
35
|
+
|
|
36
|
+
## Keep in primary Codex agent
|
|
37
|
+
|
|
38
|
+
- requirements
|
|
39
|
+
- architecture
|
|
40
|
+
- ambiguous rules
|
|
41
|
+
- security-sensitive design
|
|
42
|
+
- difficult debugging
|
|
43
|
+
- integration
|
|
44
|
+
- final acceptance
|
|
45
|
+
|
|
46
|
+
## Delegation packet
|
|
47
|
+
|
|
48
|
+
Always provide:
|
|
49
|
+
|
|
50
|
+
TASK
|
|
51
|
+
SCOPE
|
|
52
|
+
ALLOWED FILES
|
|
53
|
+
FORBIDDEN FILES
|
|
54
|
+
REQUIREMENTS
|
|
55
|
+
CONSTRAINTS
|
|
56
|
+
ACCEPTANCE CRITERIA
|
|
57
|
+
VALIDATION
|
|
58
|
+
EXPECTED OUTPUT
|
|
59
|
+
|
|
60
|
+
## Verification
|
|
61
|
+
|
|
62
|
+
After GLM finishes:
|
|
63
|
+
|
|
64
|
+
- inspect the actual diff
|
|
65
|
+
- independently run relevant validation
|
|
66
|
+
- compare implementation with requirements
|
|
67
|
+
- reject or correct worker output when needed
|
|
68
68
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glm-coding-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "GLM Coding Plan workers for Claude Code and Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"glm-router": "./dist/cli.js",
|
|
14
14
|
"glm-chat": "./dist/bin/glm-chat.js",
|
|
15
15
|
"glm-worker": "./dist/bin/glm-worker.js",
|
|
16
|
-
"glm-review": "./dist/bin/glm-review.js"
|
|
16
|
+
"glm-review": "./dist/bin/glm-review.js",
|
|
17
|
+
"glm-fast": "./dist/bin/glm-fast.js"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist"
|