agent-composer 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/providers/AnthropicCompatibleProvider.d.ts +2 -0
- package/dist/providers/AnthropicCompatibleProvider.js +16 -0
- package/dist/providers/AnthropicCompatibleProvider.js.map +1 -1
- package/dist/util/brief.d.ts +25 -0
- package/dist/util/brief.js +37 -0
- package/dist/util/brief.js.map +1 -0
- package/package.json +1 -1
- package/plugin/composer-mastermind/agents/coder.md +2 -3
- package/plugin/composer-mastermind/agents/explorer.md +51 -0
- package/plugin/composer-mastermind/hooks/boundary_guard.sh +37 -20
- package/plugin/composer-mastermind/hooks/lint-on-save.sh +24 -0
- package/plugin/composer-mastermind/plugin.json +21 -7
- package/plugin/composer-mastermind/skills/composer-mastermind/SKILL.md +55 -1
|
@@ -63,6 +63,22 @@ export class AnthropicCompatibleProvider {
|
|
|
63
63
|
if (this.thinking)
|
|
64
64
|
params.thinking = this.thinking;
|
|
65
65
|
const msg = await this.client.messages.create(params);
|
|
66
|
+
// Best-effort GLM cache-hit telemetry
|
|
67
|
+
try {
|
|
68
|
+
const fs = await import("node:fs");
|
|
69
|
+
const entry = {
|
|
70
|
+
ts: new Date().toISOString(),
|
|
71
|
+
model: this.modelLabel,
|
|
72
|
+
input_tokens: msg.usage.input_tokens,
|
|
73
|
+
output_tokens: msg.usage.output_tokens,
|
|
74
|
+
cache_creation_input_tokens: msg.usage.cache_creation_input_tokens ?? 0,
|
|
75
|
+
cache_read_input_tokens: msg.usage.cache_read_input_tokens ?? 0,
|
|
76
|
+
};
|
|
77
|
+
fs.appendFileSync("/tmp/composer-glm-usage.jsonl", JSON.stringify(entry) + "\n");
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// best-effort telemetry; never break the provider on log failure
|
|
81
|
+
}
|
|
66
82
|
const text = msg.content
|
|
67
83
|
.map((b) => (b.type === "text" && typeof b.text === "string" ? b.text : ""))
|
|
68
84
|
.join("");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnthropicCompatibleProvider.js","sourceRoot":"","sources":["../../src/providers/AnthropicCompatibleProvider.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,gEAAgE;AAChE,yEAAyE;AACzE,EAAE;AACF,4EAA4E;AAC5E,uEAAuE;AACvE,4EAA4E;AAC5E,0DAA0D;AAC1D,mEAAmE;AACnE,uEAAuE;AACvE,wCAAwC;AAExC,OAAO,SAAS,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"AnthropicCompatibleProvider.js","sourceRoot":"","sources":["../../src/providers/AnthropicCompatibleProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const SliceSchema: z.ZodObject<{
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
startLine: z.ZodOptional<z.ZodNumber>;
|
|
5
|
+
endLine: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
note: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare const BriefSchema: z.ZodObject<{
|
|
9
|
+
runId: z.ZodString;
|
|
10
|
+
createdAt: z.ZodString;
|
|
11
|
+
task: z.ZodString;
|
|
12
|
+
files: z.ZodArray<z.ZodString>;
|
|
13
|
+
symbols: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
+
deps: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
15
|
+
slices: z.ZodArray<z.ZodObject<{
|
|
16
|
+
file: z.ZodString;
|
|
17
|
+
startLine: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
endLine: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
note: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type Slice = z.infer<typeof SliceSchema>;
|
|
23
|
+
export type Brief = z.infer<typeof BriefSchema>;
|
|
24
|
+
export declare function newBrief(task: string): Brief;
|
|
25
|
+
export declare function writeBrief(brief: Brief, dir?: string): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export const SliceSchema = z.object({
|
|
6
|
+
file: z.string().min(1),
|
|
7
|
+
startLine: z.number().int().positive().optional(),
|
|
8
|
+
endLine: z.number().int().positive().optional(),
|
|
9
|
+
note: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
export const BriefSchema = z.object({
|
|
12
|
+
runId: z.string().uuid(),
|
|
13
|
+
createdAt: z.string().datetime(),
|
|
14
|
+
task: z.string().min(1),
|
|
15
|
+
files: z.array(z.string().min(1)),
|
|
16
|
+
symbols: z.array(z.string().min(1)).optional(),
|
|
17
|
+
deps: z.array(z.string().min(1)).optional(),
|
|
18
|
+
slices: z.array(SliceSchema),
|
|
19
|
+
});
|
|
20
|
+
export function newBrief(task) {
|
|
21
|
+
return BriefSchema.parse({
|
|
22
|
+
runId: randomUUID(),
|
|
23
|
+
createdAt: new Date().toISOString(),
|
|
24
|
+
task,
|
|
25
|
+
files: [],
|
|
26
|
+
slices: [],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function writeBrief(brief, dir = ".composer/briefs") {
|
|
30
|
+
const validated = BriefSchema.parse(brief);
|
|
31
|
+
const absDir = resolve(dir);
|
|
32
|
+
mkdirSync(absDir, { recursive: true });
|
|
33
|
+
const path = resolve(absDir, `${validated.runId}.json`);
|
|
34
|
+
writeFileSync(path, JSON.stringify(validated, null, 2), "utf8");
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=brief.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief.js","sourceRoot":"","sources":["../../src/util/brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACjD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC/C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IACxB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC9C,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC3C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;CAC7B,CAAC,CAAC;AAKH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,WAAW,CAAC,KAAK,CAAC;QACvB,KAAK,EAAE,UAAU,EAAE;QACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,IAAI;QACJ,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,EAAE;KACX,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,GAAG,GAAG,kBAAkB;IAC/D,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,KAAK,OAAO,CAAC,CAAC;IACxD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -18,14 +18,13 @@ You are the Composer **Coder** subagent. Your job is two-step:
|
|
|
18
18
|
- If GLM returns a unified diff → apply via `Edit` (or multiple `Edit` calls).
|
|
19
19
|
- If GLM returns full file content → use `Write`.
|
|
20
20
|
- If GLM returns a code block targeting a specific location → use `Edit` with the matching `old_string` / `new_string`.
|
|
21
|
-
5.
|
|
22
|
-
6. Return a 1-3 sentence summary of what changed (file + line range + intent). DO NOT return GLM's raw output — only the final result.
|
|
21
|
+
5. Return a 1-3 sentence summary of what changed (file + line range + intent). DO NOT return GLM's raw output — only the final result.
|
|
23
22
|
|
|
24
23
|
# Hard rules
|
|
25
24
|
|
|
26
25
|
- DO call `composer_code` exactly ONCE per task. If GLM's output is malformed, fail to the orchestrator with a short error.
|
|
27
26
|
- DO apply patches via Edit/Write — that's why those tools are in your list.
|
|
28
|
-
- DO
|
|
27
|
+
- DO NOT re-Read after Edit/Write — trust the tool's return value. PostToolUse hooks run lint + tsc as the verification gate. If a real bug shipped, the reviewer subagent catches it on the next pass.
|
|
29
28
|
- DO NOT write code yourself or modify GLM's output beyond mechanical patch application.
|
|
30
29
|
- DO NOT call composer_code more than once — if it fails, return the error.
|
|
31
30
|
- DO NOT use Bash/sed/awk/perl. Edit/Write only.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explorer
|
|
3
|
+
description: Use when the orchestrator needs to map a large repo before dispatching workers. Walks Glob/Grep, writes a typed brief at .composer/briefs/<runId>.json via the Write tool, returns briefPath only — workers consume the brief instead of re-discovering.
|
|
4
|
+
tools: Read, Glob, Grep, Write
|
|
5
|
+
model: haiku
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the Composer **Explorer** subagent. Your job is to map the relevant slice of the repo, **write a Brief JSON file to disk using the `Write` tool**, and return the path. Workers (coder / researcher / reviewer) consume the brief — they do NOT re-discover.
|
|
9
|
+
|
|
10
|
+
# Tools you have
|
|
11
|
+
|
|
12
|
+
`Read`, `Glob`, `Grep`, **`Write`**. The `Write` tool IS available to you. Use it. It is NOT a "composer MCP tool" — it is the standard file-writing tool. The only Write target you are allowed is `.composer/briefs/<runId>.json` (gitignored). Writing the brief is mandatory; returning the JSON in your text reply is NOT a substitute and the orchestrator will reject the dispatch.
|
|
13
|
+
|
|
14
|
+
# Workflow
|
|
15
|
+
|
|
16
|
+
1. Receive `{ task }` from the orchestrator.
|
|
17
|
+
2. Use `Glob` and `Grep` to locate the smallest set of files / symbols / dependency edges that touch the task. **Cap at 30 tool calls total.** Stop early if the picture is clear.
|
|
18
|
+
3. Construct a Brief JSON object matching the schema in `src/util/brief.ts`:
|
|
19
|
+
```
|
|
20
|
+
{
|
|
21
|
+
"runId": "<v4-uuid>", // see UUID rule below
|
|
22
|
+
"createdAt": "<ISO-8601 with .SSSZ>",
|
|
23
|
+
"task": "<orchestrator's task string verbatim>",
|
|
24
|
+
"files": ["repo/relative/path.ts", ...], // deduped
|
|
25
|
+
"symbols": ["ExportedName", ...], // optional
|
|
26
|
+
"deps": ["upstream/or/downstream.ts"], // optional
|
|
27
|
+
"slices": [{ "file": "...", "startLine": N, "endLine": M, "note": "..." }]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
4. **Write the brief.** Call the `Write` tool with:
|
|
31
|
+
- `file_path`: absolute path `<repo>/.composer/briefs/<runId>.json` (use the same absolute prefix you saw in Read results)
|
|
32
|
+
- `content`: `JSON.stringify(brief, null, 2)` shape — 2-space indented JSON, trailing newline OK
|
|
33
|
+
5. Return ONLY `{ briefPath, fileCount, sliceCount, summary }`. `summary` is 1-3 sentences: shape of the work, what's NOT in scope. Do NOT echo file contents in the summary. Do NOT paste the brief JSON in your reply — the orchestrator already has the path.
|
|
34
|
+
|
|
35
|
+
# UUID rule (strict)
|
|
36
|
+
|
|
37
|
+
`runId` MUST match `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$` — lowercase hex only. Examples (use as templates, vary the digits):
|
|
38
|
+
- `f47ac10b-58cc-4372-a567-0e02b2c3d479`
|
|
39
|
+
- `3c8d2a4f-91e7-45ba-8c3b-5f6d7e9a1b2c`
|
|
40
|
+
|
|
41
|
+
Non-hex characters (`g`-`z`) WILL fail `BriefSchema.parse` and the orchestrator will reject the brief. If unsure, pick characters strictly from `0123456789abcdef`.
|
|
42
|
+
|
|
43
|
+
# Hard rules
|
|
44
|
+
|
|
45
|
+
- DO use the `Write` tool to persist the brief. NOT writing the file is a dispatch failure.
|
|
46
|
+
- DO refuse with a short error if fewer than 10 files match the task — that's an inline-dispatch case. Tell the orchestrator to dispatch inline instead.
|
|
47
|
+
- DO NOT call any `mcp__composer__*` tool. You are pure discovery — the composer MCP tools are for coder/researcher/reviewer.
|
|
48
|
+
- DO NOT write or edit any file except `.composer/briefs/<runId>.json`.
|
|
49
|
+
- DO NOT run Bash.
|
|
50
|
+
- DO NOT return raw matches or file contents in the summary. Return the brief path; the worker reads the brief.
|
|
51
|
+
- DO NOT exceed 30 Glob+Grep calls. If still unclear, write a partial brief and flag the gap in `summary`.
|
|
@@ -1,57 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Composer boundary enforcement (PreToolUse hook)
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# - Allows Edit/Write inside a subagent — that is how `coder` applies the
|
|
10
|
-
# patch composer_code returned. Detection covers three field-name shapes
|
|
11
|
-
# Claude Code has emitted across versions: transcript_path containing
|
|
12
|
-
# "/subagents/", agent_id/agentId set, is_sidechain/isSidechain true.
|
|
13
|
-
# - Wraps the deny decision in {"hookSpecificOutput":{...}} as Claude Code
|
|
14
|
-
# v2.1.150 requires; top-level fields parse cleanly but are ignored.
|
|
15
|
-
#
|
|
16
|
-
# Disable per-session with: COMPOSER_DANGEROUSLY_BYPASS_PERMISSIONS=1 claude
|
|
2
|
+
# Wave 1 F1.5 — Composer boundary enforcement (PreToolUse hook).
|
|
3
|
+
# Contract per docs/adr/0001-contracts.md §C0.4:
|
|
4
|
+
# stdin: Anthropic PreToolUse JSON
|
|
5
|
+
# stdout: optional {"permissionDecision":"deny"|"allow"|"ask", ...}
|
|
6
|
+
# exit: always 0; semantics carried by JSON
|
|
7
|
+
# Fail-closed: any unexpected condition (missing jq / empty stdin /
|
|
8
|
+
# malformed JSON / absent tool_name) MUST emit a deny payload.
|
|
17
9
|
|
|
18
10
|
set -u
|
|
19
11
|
|
|
20
12
|
emit_deny() {
|
|
21
13
|
local reason="$1"
|
|
14
|
+
# Claude Code v2.1.150+ requires the decision wrapped in hookSpecificOutput.
|
|
15
|
+
# Top-level {hookEventName,permissionDecision,permissionDecisionReason} is
|
|
16
|
+
# parsed without error but silently ignored — Edit/Write succeed anyway.
|
|
22
17
|
jq -nc --arg r "$reason" \
|
|
23
18
|
'{hookSpecificOutput:{hookEventName:"PreToolUse", permissionDecision:"deny", permissionDecisionReason:$r}}' 2>/dev/null \
|
|
24
19
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$reason"
|
|
25
20
|
exit 0
|
|
26
21
|
}
|
|
27
22
|
|
|
23
|
+
# 1. Dependency: jq is mandatory for safe JSON parsing.
|
|
28
24
|
if ! command -v jq >/dev/null 2>&1; then
|
|
25
|
+
# Cannot use emit_deny (depends on jq); inline fallback.
|
|
29
26
|
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"boundary_guard: jq missing, failing closed"}}\n'
|
|
30
27
|
exit 0
|
|
31
28
|
fi
|
|
32
29
|
|
|
30
|
+
# 2. Read tool-call JSON from stdin.
|
|
33
31
|
INPUT="$(cat || true)"
|
|
34
32
|
if [[ -z "$INPUT" ]]; then
|
|
35
33
|
emit_deny "boundary_guard: empty stdin, failing closed"
|
|
36
34
|
fi
|
|
37
35
|
|
|
36
|
+
# 3. Parse tool_name. jq surfaces parse errors to stderr (suppressed) and
|
|
37
|
+
# returns empty on missing/null — both treated as malformed.
|
|
38
38
|
TOOL="$(jq -r '.tool_name // empty' <<<"$INPUT" 2>/dev/null)"
|
|
39
39
|
if [[ -z "$TOOL" ]]; then
|
|
40
40
|
emit_deny "boundary_guard: malformed JSON or missing tool_name, failing closed"
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
+
# 3.5. COMPOSER_DANGEROUSLY_BYPASS_PERMISSIONS (dev-only escape hatch).
|
|
44
|
+
# When env var equals "1" or "true", allow every tool. Stderr-warns on
|
|
45
|
+
# every invocation so the bypass is visible in transcripts. Intended
|
|
46
|
+
# for bootstrap / dev sessions only. NEVER set in CI or runtime.
|
|
47
|
+
# See ADR 0001 amendment (2026-05-23, Wave-3 Step 1).
|
|
43
48
|
BYPASS="${COMPOSER_DANGEROUSLY_BYPASS_PERMISSIONS:-}"
|
|
44
49
|
if [[ "$BYPASS" == "1" || "$BYPASS" == "true" ]]; then
|
|
45
50
|
printf 'WARN boundary_guard: COMPOSER_DANGEROUSLY_BYPASS_PERMISSIONS=%s — boundary disabled, dev mode only\n' "$BYPASS" >&2
|
|
46
51
|
exit 0
|
|
47
52
|
fi
|
|
48
53
|
|
|
54
|
+
# 3.6. STOP_EVOLVE killswitch.
|
|
55
|
+
# If sentinel file exists AND the tool is a composer dispatch, deny.
|
|
56
|
+
# Sentinel path overridable via COMPOSER_STOP_EVOLVE_FILE (tests use
|
|
57
|
+
# a temp file); default "$CLAUDE_PROJECT_DIR/STOP_EVOLVE", falling
|
|
58
|
+
# back to "./STOP_EVOLVE" when CLAUDE_PROJECT_DIR is unset.
|
|
49
59
|
STOP_FILE="${COMPOSER_STOP_EVOLVE_FILE:-${CLAUDE_PROJECT_DIR:-.}/STOP_EVOLVE}"
|
|
50
60
|
if [[ -e "$STOP_FILE" ]] && [[ "$TOOL" == mcp__composer__* ]]; then
|
|
51
61
|
emit_deny "STOP_EVOLVE sentinel present at $STOP_FILE — Composer dispatches paused."
|
|
52
62
|
fi
|
|
53
63
|
|
|
54
|
-
# Subagent context bypass
|
|
64
|
+
# 3.7. Subagent context bypass.
|
|
65
|
+
# Composer's coder subagent must Edit/Write to apply GLM's patch output.
|
|
66
|
+
# The hook fires identically for main-thread and subagent calls — without
|
|
67
|
+
# this carve-out the apply step is impossible. Detect subagent via the
|
|
68
|
+
# three field-name shapes Claude Code has emitted across recent versions.
|
|
55
69
|
TRANSCRIPT="$(jq -r '.transcript_path // empty' <<<"$INPUT" 2>/dev/null)"
|
|
56
70
|
AGENT_ID="$(jq -r '.agent_id // .agentId // empty' <<<"$INPUT" 2>/dev/null)"
|
|
57
71
|
SIDECHAIN="$(jq -r '.is_sidechain // .isSidechain // empty' <<<"$INPUT" 2>/dev/null)"
|
|
@@ -61,12 +75,15 @@ if [[ "$TRANSCRIPT" == */subagents/* ]] \
|
|
|
61
75
|
exit 0
|
|
62
76
|
fi
|
|
63
77
|
|
|
78
|
+
# 4. Block list — native dangerous tools + MCP-prefixed variants.
|
|
64
79
|
case "$TOOL" in
|
|
65
|
-
Edit|Write|NotebookEdit \
|
|
66
|
-
| mcp__*__write_file | mcp__*__edit_file \
|
|
67
|
-
| mcp__*__write | mcp__*__edit)
|
|
68
|
-
emit_deny "
|
|
80
|
+
Bash|Edit|Write|NotebookEdit \
|
|
81
|
+
| mcp__*__write_file | mcp__*__edit_file | mcp__*__bash \
|
|
82
|
+
| mcp__*__write | mcp__*__edit | mcp__*__exec)
|
|
83
|
+
emit_deny "DENY: dispatch Task(subagent_type=\"coder\"); no Bash workaround."
|
|
69
84
|
;;
|
|
70
85
|
esac
|
|
71
86
|
|
|
87
|
+
# 5. Pass-through. Emit nothing — Anthropic treats absent JSON + exit 0
|
|
88
|
+
# as implicit allow. (Explicit allow JSON is also valid but noisier.)
|
|
72
89
|
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PostToolUse hook: auto-lint after Edit/Write/NotebookEdit. Fail-soft.
|
|
3
|
+
set -u
|
|
4
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
5
|
+
INPUT="$(cat || true)"
|
|
6
|
+
[[ -z "$INPUT" ]] && exit 0
|
|
7
|
+
FILE="$(jq -r '.tool_input.file_path // empty' <<<"$INPUT" 2>/dev/null)"
|
|
8
|
+
[[ -z "$FILE" || ! -f "$FILE" ]] && exit 0
|
|
9
|
+
run() { command -v "$1" >/dev/null 2>&1 || return 0; "$@" >&2 2>&1 || printf 'lint-on-save: %s issues on %s\n' "$1" "$FILE" >&2; }
|
|
10
|
+
case "$FILE" in
|
|
11
|
+
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs|*.vue) run eslint --fix "$FILE" ;;
|
|
12
|
+
*.php)
|
|
13
|
+
if [[ -x vendor/bin/pint ]]; then
|
|
14
|
+
vendor/bin/pint "$FILE" >&2 2>&1 || printf 'lint-on-save: pint issues on %s\n' "$FILE" >&2
|
|
15
|
+
else
|
|
16
|
+
run php-cs-fixer fix "$FILE"
|
|
17
|
+
fi
|
|
18
|
+
;;
|
|
19
|
+
*.py) run ruff format "$FILE"; run ruff check --fix "$FILE" ;;
|
|
20
|
+
*.rs) run rustfmt "$FILE" ;;
|
|
21
|
+
*.go) run gofmt -w "$FILE" ;;
|
|
22
|
+
*.json|*.md|*.yml|*.yaml|*.css|*.scss|*.html) run prettier --write "$FILE" ;;
|
|
23
|
+
esac
|
|
24
|
+
exit 0
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "composer-mastermind",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Multi-agent orchestrator: Claude as brain, GLM/agy as executors. Dispatches code/research/review work to subagents wired through the @composer-mcp/server MCP server.",
|
|
5
5
|
"claudeCodeVersion": ">=4.6",
|
|
6
|
-
"requires": [
|
|
6
|
+
"requires": [
|
|
7
|
+
"agent-composer"
|
|
8
|
+
],
|
|
7
9
|
"settings": {
|
|
8
|
-
"PreToolUse": [
|
|
9
|
-
|
|
10
|
+
"PreToolUse": [
|
|
11
|
+
"bash:hooks/boundary_guard.sh"
|
|
12
|
+
],
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
"bash:hooks/lint-on-save.sh"
|
|
15
|
+
],
|
|
16
|
+
"Stop": [
|
|
17
|
+
"bash:hooks/learn.sh"
|
|
18
|
+
]
|
|
10
19
|
},
|
|
11
|
-
"skills": [
|
|
20
|
+
"skills": [
|
|
21
|
+
"skills/composer-mastermind/SKILL.md"
|
|
22
|
+
],
|
|
12
23
|
"agents": [
|
|
13
24
|
"agents/coder.md",
|
|
14
25
|
"agents/researcher.md",
|
|
15
|
-
"agents/reviewer.md"
|
|
26
|
+
"agents/reviewer.md",
|
|
27
|
+
"agents/explorer.md"
|
|
28
|
+
],
|
|
29
|
+
"commands": [
|
|
30
|
+
"commands/evolve.md"
|
|
16
31
|
],
|
|
17
|
-
"commands": ["commands/evolve.md"],
|
|
18
32
|
"license": "MIT"
|
|
19
33
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: composer-mastermind
|
|
3
|
-
description: MUST USE for
|
|
3
|
+
description: MUST USE for code-change requests that require Edit/Write/NotebookEdit on real files (add, modify, fix, refactor, implement, write, change, update files). Routes work to subagents (researcher / coder / reviewer) via Task tool. SKIP this skill — answer inline — when (a) the request is a one-line review/refusal/clarification with no file mutation, (b) the request is a destructive op the orchestrator should refuse (rm, drop, delete, reset --hard, --force), or (c) the user has already given an exact answer-key like a code snippet to paste verbatim. Main Claude does NOT call Edit/Write/NotebookEdit directly; the boundary_guard hook denies them and requires dispatch.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Composer Mastermind
|
|
@@ -51,6 +51,35 @@ pinned. For tiny clarifications / refusals / one-line answers,
|
|
|
51
51
|
multi-step refactors) is where dispatch pays. Trust your
|
|
52
52
|
expected-output estimate; if under 5 lines, just answer.
|
|
53
53
|
|
|
54
|
+
**Fan-out cap:** Max 3 parallel worker dispatches per turn for repos with >500 source files. Beyond 3, the prompt-cache misses compound faster than the parallelism saves wall time. If file slices overlap across workers, dispatch SEQUENTIALLY — parallel workers on the same files duplicate every Read.
|
|
55
|
+
|
|
56
|
+
# Explorer protocol (large-repo dispatches)
|
|
57
|
+
|
|
58
|
+
For repos with >500 source files (or any unfamiliar codebase), prefer an
|
|
59
|
+
**explorer → workers** shape over re-greping in the main session before
|
|
60
|
+
every dispatch. Re-discovery is the single biggest token bleed in this
|
|
61
|
+
architecture: each fresh worker boots without prompt-cache hits and
|
|
62
|
+
re-Reads the same files.
|
|
63
|
+
|
|
64
|
+
Dispatch shape:
|
|
65
|
+
|
|
66
|
+
| Situation | Dispatch |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Small repo, files already pinned in conversation | **Inline** — call worker directly with `{ prompt, context }` |
|
|
69
|
+
| Large repo, single worker | `explorer` → consume `briefPath` → ONE worker dispatch with `{ briefPath, task }` |
|
|
70
|
+
| Large repo, multiple workers | `explorer` ONCE → fan out workers, each consumes the **same** `briefPath` |
|
|
71
|
+
|
|
72
|
+
`briefPath` convention: explorer writes `.composer/briefs/<runId>.json`
|
|
73
|
+
(zod schema in `src/util/brief.ts`). Workers re-validate with
|
|
74
|
+
`BriefSchema.parse(readFileSync(briefPath))` before touching files. The
|
|
75
|
+
brief is the shared cache prefix — passing the same path to N siblings
|
|
76
|
+
is what keeps prompt-cache warm across the fan-out.
|
|
77
|
+
|
|
78
|
+
**When to skip the explorer:** orchestrator already knows the exact
|
|
79
|
+
file:line targets (e.g. user said "edit src/foo.ts line 42") OR the
|
|
80
|
+
expected worker output is <500 tokens. The explorer dispatch costs
|
|
81
|
+
~1.5k cache tokens itself; don't pay that for an inline-sized task.
|
|
82
|
+
|
|
54
83
|
# Spend authorization
|
|
55
84
|
|
|
56
85
|
Read `composer.config.json` `spendAuthorization.mode` before any
|
|
@@ -69,6 +98,31 @@ dispatch that hits a real-money provider (`anthropic`,
|
|
|
69
98
|
CLI providers (`agy`) are billed separately by the user's own auth
|
|
70
99
|
and do not count toward these caps. Mock providers are always free.
|
|
71
100
|
|
|
101
|
+
# Headless invocation
|
|
102
|
+
|
|
103
|
+
When composer-mastermind runs inside a headless `claude -p` (eval harness,
|
|
104
|
+
test runner, CI dispatch, scheduled job, any non-interactive context), prefer
|
|
105
|
+
**Haiku** as the orchestrator model. Build-2 dogfood measurement showed
|
|
106
|
+
-66 % cost vs Opus 4.7 on the orchestrator side, with no quality regression
|
|
107
|
+
on the 3 eval tasks. Workers (GLM / agy) are unchanged.
|
|
108
|
+
|
|
109
|
+
How to invoke:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
claude -p --model claude-haiku-4-5-20251001 \
|
|
113
|
+
--output-format json --permission-mode bypassPermissions \
|
|
114
|
+
"<your prompt>"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Rules:
|
|
118
|
+
|
|
119
|
+
- Interactive sessions (user is watching): default Opus 4.7. Haiku
|
|
120
|
+
hands off integration nuance the user expects.
|
|
121
|
+
- Headless / scheduled / eval: default Haiku. The orchestrator's job is
|
|
122
|
+
delegation, not reasoning — Haiku is sufficient for routing + summary.
|
|
123
|
+
- Override when the orchestration plan itself is non-trivial (>3
|
|
124
|
+
dispatches, cross-file integration). Then Opus 4.7 earns its keep.
|
|
125
|
+
|
|
72
126
|
# Token discipline
|
|
73
127
|
|
|
74
128
|
- You hold context, plans, and integration. Workers hold execution.
|