@maestrofrontier/frontier 1.4.4 → 1.5.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/.agents/plugins/marketplace.json +21 -0
- package/.codex-plugin/plugin.json +29 -0
- package/.cursorrules +197 -194
- package/AGENTS.md +214 -214
- package/CLAUDE.md +29 -29
- package/README.md +368 -278
- package/bin/maestro.cjs +75 -75
- package/commands/compress.md +36 -36
- package/commands/frontier.md +124 -124
- package/commands/terse.md +23 -23
- package/docs/codex.md +167 -98
- package/docs/orchestration.md +168 -168
- package/frontier/cli.cjs +279 -248
- package/frontier/config.cjs +468 -441
- package/frontier/dispatch.cjs +267 -255
- package/frontier/judge.cjs +92 -92
- package/frontier/run.cjs +201 -148
- package/frontier/schema.cjs +112 -112
- package/frontier/semaphore.cjs +49 -49
- package/frontier/synthesize.cjs +79 -79
- package/hooks/frontier-autorun.cjs +127 -124
- package/hooks/hooks.json +103 -103
- package/hooks/maestro-doctrine-guard.cjs +81 -81
- package/hooks/maestro-gate-reminder.cjs +22 -7
- package/hooks/maestro-gate-telemetry.cjs +79 -77
- package/hooks/maestro-phase-scope.cjs +118 -118
- package/hooks/maestro-statusline-sync.cjs +152 -152
- package/hooks/maestro-subagent-guard.cjs +148 -148
- package/hooks/maestro-terse-mode.cjs +189 -189
- package/hooks/maestro-toolbudget-advisory.cjs +127 -127
- package/integrations/README.md +111 -94
- package/integrations/cline/skills/frontier/SKILL.md +75 -75
- package/integrations/codex/prompts/frontier.md +70 -66
- package/integrations/codex/prompts/update.md +39 -36
- package/integrations/codex/skills/maestro-frontier/SKILL.md +122 -0
- package/integrations/codex/skills/{settings → maestro-settings}/SKILL.md +55 -46
- package/integrations/codex/skills/{terse → maestro-terse}/SKILL.md +58 -49
- package/integrations/codex/skills/maestro-update/SKILL.md +31 -0
- package/integrations/cursor/commands/frontier.md +63 -63
- package/integrations/cursor/commands/update.md +34 -34
- package/integrations/gemini/commands/frontier.toml +76 -76
- package/integrations/windsurf/workflows/frontier.md +70 -70
- package/package.json +58 -55
- package/scripts/install.cjs +1014 -605
- package/settings/cli.cjs +140 -140
- package/settings/config.cjs +309 -309
- package/skills/maestro-frontier/SKILL.md +122 -0
- package/skills/maestro-settings/SKILL.md +55 -0
- package/skills/maestro-terse/SKILL.md +58 -0
- package/skills/maestro-update/SKILL.md +31 -0
- package/skills/terse/SKILL.md +74 -0
- package/integrations/codex/skills/frontier/SKILL.md +0 -91
- package/integrations/codex/skills/update/SKILL.md +0 -29
|
@@ -1,124 +1,127 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Maestro Frontier autorun hook (UserPromptSubmit). When the engine is
|
|
3
|
-
// armed (frontier-state mode != 'off'), every user prompt is run through
|
|
4
|
-
// the configured preset/model via runFrontier, and the synthesized answer
|
|
5
|
-
// is injected back as additionalContext with a relay instruction + a
|
|
6
|
-
// one-line preset header, so the live session relays it. mode == 'off' ->
|
|
7
|
-
// zero overhead: no engine require, no spawn, no injected context.
|
|
8
|
-
//
|
|
9
|
-
// Recursion guard (load-bearing): the engine spawns child `claude -p`
|
|
10
|
-
// CLIs, and headless `claude -p` re-fires UserPromptSubmit hooks (verified
|
|
11
|
-
// against the Claude Code hooks contract). dispatch.cjs sets FUSION_DEPTH
|
|
12
|
-
// on every spawned child; this hook no-ops whenever FUSION_DEPTH is present,
|
|
13
|
-
// BEFORE any engine require or spawn. Without it the first armed prompt
|
|
14
|
-
// would fork the engine recursively.
|
|
15
|
-
//
|
|
16
|
-
// Degrade-to-normal: any engine error, empty answer, or thrown exception
|
|
17
|
-
// exits 0 with empty stdout (logging only to stderr), so a broken engine
|
|
18
|
-
// never blocks or corrupts a turn — the session just answers normally. A
|
|
19
|
-
// UserPromptSubmit hook can only ADD context; it cannot suppress the main
|
|
20
|
-
// turn, which is exactly the relay model.
|
|
21
|
-
//
|
|
22
|
-
// .cjs so Node treats it as CommonJS regardless of any parent "type":
|
|
23
|
-
// "module" package.json. configDir/state patterns reused from
|
|
24
|
-
// frontier/config.cjs; structure ported from maestro-terse-mode.cjs.
|
|
25
|
-
|
|
26
|
-
'use strict';
|
|
27
|
-
|
|
28
|
-
const fs = require('fs');
|
|
29
|
-
|
|
30
|
-
function noop() { process.exit(0); }
|
|
31
|
-
|
|
32
|
-
let data = {};
|
|
33
|
-
try { data = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { noop(); }
|
|
34
|
-
|
|
35
|
-
if (data.hook_event_name !== 'UserPromptSubmit') noop();
|
|
36
|
-
|
|
37
|
-
// Recursion guard FIRST: never run the engine inside an engine-spawned CLI.
|
|
38
|
-
// The engine sets FUSION_DEPTH on every child (dispatch.cjs:108); depth >= 1
|
|
39
|
-
// means we are inside a spawned panel/judge/synth process. Mirror run.cjs's
|
|
40
|
-
// own parseInt check (run.cjs:27) so the two layers agree and a stray
|
|
41
|
-
// FUSION_DEPTH='0'/'' in the environment reads as "not a child".
|
|
42
|
-
const fusionDepth = parseInt(process.env.FUSION_DEPTH || '0', 10);
|
|
43
|
-
if (Number.isFinite(fusionDepth) && fusionDepth >= 1) noop();
|
|
44
|
-
|
|
45
|
-
let state;
|
|
46
|
-
try {
|
|
47
|
-
const cfg = require('../frontier/config.cjs');
|
|
48
|
-
const cwd = data.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Maestro Frontier autorun hook (UserPromptSubmit). When the engine is
|
|
3
|
+
// armed (frontier-state mode != 'off'), every user prompt is run through
|
|
4
|
+
// the configured preset/model via runFrontier, and the synthesized answer
|
|
5
|
+
// is injected back as additionalContext with a relay instruction + a
|
|
6
|
+
// one-line preset header, so the live session relays it. mode == 'off' ->
|
|
7
|
+
// zero overhead: no engine require, no spawn, no injected context.
|
|
8
|
+
//
|
|
9
|
+
// Recursion guard (load-bearing): the engine spawns child `claude -p`
|
|
10
|
+
// CLIs, and headless `claude -p` re-fires UserPromptSubmit hooks (verified
|
|
11
|
+
// against the Claude Code hooks contract). dispatch.cjs sets FUSION_DEPTH
|
|
12
|
+
// on every spawned child; this hook no-ops whenever FUSION_DEPTH is present,
|
|
13
|
+
// BEFORE any engine require or spawn. Without it the first armed prompt
|
|
14
|
+
// would fork the engine recursively.
|
|
15
|
+
//
|
|
16
|
+
// Degrade-to-normal: any engine error, empty answer, or thrown exception
|
|
17
|
+
// exits 0 with empty stdout (logging only to stderr), so a broken engine
|
|
18
|
+
// never blocks or corrupts a turn — the session just answers normally. A
|
|
19
|
+
// UserPromptSubmit hook can only ADD context; it cannot suppress the main
|
|
20
|
+
// turn, which is exactly the relay model.
|
|
21
|
+
//
|
|
22
|
+
// .cjs so Node treats it as CommonJS regardless of any parent "type":
|
|
23
|
+
// "module" package.json. configDir/state patterns reused from
|
|
24
|
+
// frontier/config.cjs; structure ported from maestro-terse-mode.cjs.
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
|
|
30
|
+
function noop() { process.exit(0); }
|
|
31
|
+
|
|
32
|
+
let data = {};
|
|
33
|
+
try { data = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { noop(); }
|
|
34
|
+
|
|
35
|
+
if (data.hook_event_name !== 'UserPromptSubmit') noop();
|
|
36
|
+
|
|
37
|
+
// Recursion guard FIRST: never run the engine inside an engine-spawned CLI.
|
|
38
|
+
// The engine sets FUSION_DEPTH on every child (dispatch.cjs:108); depth >= 1
|
|
39
|
+
// means we are inside a spawned panel/judge/synth process. Mirror run.cjs's
|
|
40
|
+
// own parseInt check (run.cjs:27) so the two layers agree and a stray
|
|
41
|
+
// FUSION_DEPTH='0'/'' in the environment reads as "not a child".
|
|
42
|
+
const fusionDepth = parseInt(process.env.FUSION_DEPTH || '0', 10);
|
|
43
|
+
if (Number.isFinite(fusionDepth) && fusionDepth >= 1) noop();
|
|
44
|
+
|
|
45
|
+
let state;
|
|
46
|
+
try {
|
|
47
|
+
const cfg = require('../frontier/config.cjs');
|
|
48
|
+
const cwd = data.cwd || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.cwd();
|
|
49
|
+
const scope = cfg.resolveScope([], { cwd });
|
|
50
|
+
state = cfg.loadState(scope);
|
|
51
|
+
} catch {
|
|
52
|
+
noop();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Off -> zero overhead: no run.cjs require, no spawn, no injected context.
|
|
56
|
+
if (!state || state.mode === 'off') noop();
|
|
57
|
+
|
|
58
|
+
const prompt = String(data.prompt || '');
|
|
59
|
+
|
|
60
|
+
// Optional length gate (default 0 = every prompt). Skips trivially short
|
|
61
|
+
// prompts ("yes"/"ok") so they don't pay a full engine run.
|
|
62
|
+
const rawMin = Number(state.autorunMinChars);
|
|
63
|
+
const minChars = Number.isFinite(rawMin) && rawMin > 0 ? rawMin : 0;
|
|
64
|
+
if (prompt.trim().length < minChars) noop();
|
|
65
|
+
|
|
66
|
+
// Any unexpected throw after the await boundary degrades to a normal turn,
|
|
67
|
+
// never a non-zero exit / unhandled rejection.
|
|
68
|
+
run().catch((e) => {
|
|
69
|
+
process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
|
|
70
|
+
process.exit(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
async function run() {
|
|
74
|
+
let result;
|
|
75
|
+
const runStart = Date.now();
|
|
76
|
+
try {
|
|
77
|
+
const { runFrontier } = require('../frontier/run.cjs');
|
|
78
|
+
result = await runFrontier({ prompt, state });
|
|
79
|
+
} catch (e) {
|
|
80
|
+
process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
|
|
81
|
+
noop();
|
|
82
|
+
}
|
|
83
|
+
const runMs = Date.now() - runStart;
|
|
84
|
+
|
|
85
|
+
if (!result || result.status !== 'ok' || !result.final) {
|
|
86
|
+
if (result && result.status === 'error') {
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
'frontier-autorun: engine error [' + result.failure_reason + ']: ' + result.error + '\n');
|
|
89
|
+
}
|
|
90
|
+
noop();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const modelCount = result.responses ? result.responses.length : 1;
|
|
94
|
+
const banner = '⚡ Frontier \xb7 ' + presetHeader(state) + ' \xb7 ' + modelCount + ' models \xb7 ' + Math.round(runMs / 1000) + 's';
|
|
95
|
+
|
|
96
|
+
const context =
|
|
97
|
+
'MAESTRO FRONTIER AUTORUN — ' + presetHeader(state) + '\n\n' +
|
|
98
|
+
'The Maestro Frontier engine already ran this prompt through the panel ' +
|
|
99
|
+
'above and produced the answer below. Begin your response with this exact ' +
|
|
100
|
+
'banner line (verbatim, on its own line): ' + banner + '\n\n' +
|
|
101
|
+
'Then relay the answer — you may reformat for clarity, but do not redo ' +
|
|
102
|
+
'the work or contradict it:\n\n' +
|
|
103
|
+
result.final;
|
|
104
|
+
|
|
105
|
+
// fs.writeSync (synchronous, unbuffered) guarantees the full payload reaches
|
|
106
|
+
// stdout before exit. process.exit() does NOT drain a pipe-backed
|
|
107
|
+
// process.stdout, which would truncate a large engine answer (multi-KB
|
|
108
|
+
// synthesis) into malformed JSON the hook consumer cannot parse.
|
|
109
|
+
fs.writeSync(1, JSON.stringify({
|
|
110
|
+
hookSpecificOutput: {
|
|
111
|
+
hookEventName: 'UserPromptSubmit',
|
|
112
|
+
additionalContext: context,
|
|
113
|
+
},
|
|
114
|
+
}));
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function presetHeader(st) {
|
|
119
|
+
if (st.mode === 'single') return 'single · ' + st.model;
|
|
120
|
+
if (st.mode === 'fusion') {
|
|
121
|
+
const preset = st.preset === 'custom'
|
|
122
|
+
? 'custom (' + (Array.isArray(st.models) ? st.models.join(', ') : '') + ')'
|
|
123
|
+
: st.preset;
|
|
124
|
+
return 'fusion · ' + preset;
|
|
125
|
+
}
|
|
126
|
+
return String(st.mode);
|
|
127
|
+
}
|
package/hooks/hooks.json
CHANGED
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"PreToolUse": [
|
|
4
|
-
{
|
|
5
|
-
"matcher": "Read",
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-doctrine-guard.cjs\""
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
],
|
|
14
|
-
"SessionStart": [
|
|
15
|
-
{
|
|
16
|
-
"matcher": "",
|
|
17
|
-
"hooks": [
|
|
18
|
-
{
|
|
19
|
-
"type": "command",
|
|
20
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-terse-mode.cjs\""
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"type": "command",
|
|
24
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-statusline-sync.cjs\""
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
],
|
|
29
|
-
"UserPromptSubmit": [
|
|
30
|
-
{
|
|
31
|
-
"matcher": "",
|
|
32
|
-
"hooks": [
|
|
33
|
-
{
|
|
34
|
-
"type": "command",
|
|
35
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-gate-reminder.cjs\""
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"type": "command",
|
|
39
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-terse-mode.cjs\""
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"type": "command",
|
|
43
|
-
"command": "node \"
|
|
44
|
-
"timeout": 300
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
"SubagentStop": [
|
|
50
|
-
{
|
|
51
|
-
"matcher": "",
|
|
52
|
-
"hooks": [
|
|
53
|
-
{
|
|
54
|
-
"type": "command",
|
|
55
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-subagent-guard.cjs\""
|
|
56
|
-
}
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
],
|
|
60
|
-
"Stop": [
|
|
61
|
-
{
|
|
62
|
-
"matcher": "",
|
|
63
|
-
"hooks": [
|
|
64
|
-
{
|
|
65
|
-
"type": "command",
|
|
66
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-loop-guard.cjs\""
|
|
67
|
-
}
|
|
68
|
-
]
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"PostToolUse": [
|
|
72
|
-
{
|
|
73
|
-
"matcher": "Edit|Write|NotebookEdit|Bash",
|
|
74
|
-
"hooks": [
|
|
75
|
-
{
|
|
76
|
-
"type": "command",
|
|
77
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-phase-scope.cjs\""
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"matcher": "Edit|Write|NotebookEdit",
|
|
83
|
-
"hooks": [
|
|
84
|
-
{
|
|
85
|
-
"type": "command",
|
|
86
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-toolbudget-advisory.cjs\""
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
}
|
|
90
|
-
],
|
|
91
|
-
"SessionEnd": [
|
|
92
|
-
{
|
|
93
|
-
"matcher": "",
|
|
94
|
-
"hooks": [
|
|
95
|
-
{
|
|
96
|
-
"type": "command",
|
|
97
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-gate-telemetry.cjs\""
|
|
98
|
-
}
|
|
99
|
-
]
|
|
100
|
-
}
|
|
101
|
-
]
|
|
102
|
-
}
|
|
103
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Read",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-doctrine-guard.cjs\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"SessionStart": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-terse-mode.cjs\""
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-statusline-sync.cjs\""
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"UserPromptSubmit": [
|
|
30
|
+
{
|
|
31
|
+
"matcher": "",
|
|
32
|
+
"hooks": [
|
|
33
|
+
{
|
|
34
|
+
"type": "command",
|
|
35
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-gate-reminder.cjs\""
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-terse-mode.cjs\""
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "command",
|
|
43
|
+
"command": "node -e \"require(require('path').join(process.env.CLAUDE_PLUGIN_ROOT || process.env.PLUGIN_ROOT, 'hooks', 'frontier-autorun.cjs'))\"",
|
|
44
|
+
"timeout": 300
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"SubagentStop": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-subagent-guard.cjs\""
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"Stop": [
|
|
61
|
+
{
|
|
62
|
+
"matcher": "",
|
|
63
|
+
"hooks": [
|
|
64
|
+
{
|
|
65
|
+
"type": "command",
|
|
66
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-loop-guard.cjs\""
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"PostToolUse": [
|
|
72
|
+
{
|
|
73
|
+
"matcher": "Edit|Write|NotebookEdit|Bash",
|
|
74
|
+
"hooks": [
|
|
75
|
+
{
|
|
76
|
+
"type": "command",
|
|
77
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-phase-scope.cjs\""
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"matcher": "Edit|Write|NotebookEdit",
|
|
83
|
+
"hooks": [
|
|
84
|
+
{
|
|
85
|
+
"type": "command",
|
|
86
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-toolbudget-advisory.cjs\""
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"SessionEnd": [
|
|
92
|
+
{
|
|
93
|
+
"matcher": "",
|
|
94
|
+
"hooks": [
|
|
95
|
+
{
|
|
96
|
+
"type": "command",
|
|
97
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/maestro-gate-telemetry.cjs\""
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Maestro PreToolUse doctrine-read guard. Enforces AGENTS.md S7.2
|
|
3
|
-
// structurally: when the doctrine is autoloaded (CLAUDE.md/AGENTS.md
|
|
4
|
-
// present at cwd, which Claude Code injects at session start), a Read
|
|
5
|
-
// of AGENTS.md or CLAUDE.md re-buys tokens for content already in
|
|
6
|
-
// context. This hook replaces the probabilistic S7.2 prose line with a
|
|
7
|
-
// deterministic deny.
|
|
8
|
-
//
|
|
9
|
-
// Modes via MAESTRO_DOCTRINE_GUARD:
|
|
10
|
-
// - "always" (default): deny every doctrine Read while doctrine files
|
|
11
|
-
// exist at cwd. Safe on Claude Code, where subagents receive the
|
|
12
|
-
// project doctrine automatically; the deny reason tells the model
|
|
13
|
-
// to use the in-context copy.
|
|
14
|
-
// - "once": allow the first doctrine Read per session (marker file in
|
|
15
|
-
// the OS temp dir keyed by session_id), deny repeats. For runtimes
|
|
16
|
-
// whose subagents genuinely lack the doctrine in context (S7.2:
|
|
17
|
-
// "a subagent without it in context reads AGENTS.md once").
|
|
18
|
-
// - "0": disabled.
|
|
19
|
-
//
|
|
20
|
-
// When no doctrine file exists at cwd nothing was autoloaded, so reads
|
|
21
|
-
// pass through untouched (e.g. inspecting another repo's AGENTS.md).
|
|
22
|
-
// docs/orchestration.md is never guarded -- it is the on-demand layer
|
|
23
|
-
// and reading it is the intended path. Fails open on any error.
|
|
24
|
-
//
|
|
25
|
-
// Payload fields verified against code.claude.com/docs/en/hooks
|
|
26
|
-
// (PreToolUse input: session_id, cwd, tool_name, tool_input; output:
|
|
27
|
-
// hookSpecificOutput.permissionDecision allow|deny|ask + reason),
|
|
28
|
-
// 2026-06-11.
|
|
29
|
-
//
|
|
30
|
-
// .cjs so Node treats it as CommonJS regardless of any "type": "module"
|
|
31
|
-
// package.json in a parent directory of the install location.
|
|
32
|
-
|
|
33
|
-
const fs = require('fs');
|
|
34
|
-
const os = require('os');
|
|
35
|
-
const path = require('path');
|
|
36
|
-
|
|
37
|
-
const mode = process.env.MAESTRO_DOCTRINE_GUARD || 'always';
|
|
38
|
-
if (mode === '0') process.exit(0);
|
|
39
|
-
|
|
40
|
-
let data = {};
|
|
41
|
-
try { data = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
42
|
-
if (data.tool_name !== 'Read' || !data.tool_input) process.exit(0);
|
|
43
|
-
|
|
44
|
-
const fp = data.tool_input.file_path;
|
|
45
|
-
if (typeof fp !== 'string') process.exit(0);
|
|
46
|
-
const base = path.basename(fp).toLowerCase();
|
|
47
|
-
if (base !== 'agents.md' && base !== 'claude.md') process.exit(0);
|
|
48
|
-
|
|
49
|
-
// Guard only when the doctrine was actually autoloaded: a doctrine file
|
|
50
|
-
// at cwd is what Claude Code injects at session start.
|
|
51
|
-
const cwd = typeof data.cwd === 'string' ? data.cwd : process.cwd();
|
|
52
|
-
let autoloaded = false;
|
|
53
|
-
try {
|
|
54
|
-
autoloaded = fs.existsSync(path.join(cwd, 'CLAUDE.md'))
|
|
55
|
-
|| fs.existsSync(path.join(cwd, 'AGENTS.md'));
|
|
56
|
-
} catch { autoloaded = false; }
|
|
57
|
-
if (!autoloaded) process.exit(0);
|
|
58
|
-
|
|
59
|
-
if (mode === 'once' && data.session_id) {
|
|
60
|
-
const marker = path.join(
|
|
61
|
-
os.tmpdir(),
|
|
62
|
-
`maestro-doctrine-guard-${String(data.session_id).replace(/[^a-zA-Z0-9-]/g, '_')}`
|
|
63
|
-
);
|
|
64
|
-
if (!fs.existsSync(marker)) {
|
|
65
|
-
try { fs.writeFileSync(marker, '1'); } catch { /* still allow */ }
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
process.stdout.write(JSON.stringify({
|
|
71
|
-
hookSpecificOutput: {
|
|
72
|
-
hookEventName: 'PreToolUse',
|
|
73
|
-
permissionDecision: 'deny',
|
|
74
|
-
permissionDecisionReason: 'maestro-doctrine-guard: denied Read of '
|
|
75
|
-
+ path.basename(fp) + ' -- the doctrine is autoloaded into context '
|
|
76
|
-
+ 'at session start (AGENTS.md S7.2); use the in-context copy '
|
|
77
|
-
+ 'instead of re-reading it from disk. The on-demand protocol '
|
|
78
|
-
+ 'layer lives in docs/orchestration.md, which is not blocked.'
|
|
79
|
-
}
|
|
80
|
-
}));
|
|
81
|
-
process.exit(0);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Maestro PreToolUse doctrine-read guard. Enforces AGENTS.md S7.2
|
|
3
|
+
// structurally: when the doctrine is autoloaded (CLAUDE.md/AGENTS.md
|
|
4
|
+
// present at cwd, which Claude Code injects at session start), a Read
|
|
5
|
+
// of AGENTS.md or CLAUDE.md re-buys tokens for content already in
|
|
6
|
+
// context. This hook replaces the probabilistic S7.2 prose line with a
|
|
7
|
+
// deterministic deny.
|
|
8
|
+
//
|
|
9
|
+
// Modes via MAESTRO_DOCTRINE_GUARD:
|
|
10
|
+
// - "always" (default): deny every doctrine Read while doctrine files
|
|
11
|
+
// exist at cwd. Safe on Claude Code, where subagents receive the
|
|
12
|
+
// project doctrine automatically; the deny reason tells the model
|
|
13
|
+
// to use the in-context copy.
|
|
14
|
+
// - "once": allow the first doctrine Read per session (marker file in
|
|
15
|
+
// the OS temp dir keyed by session_id), deny repeats. For runtimes
|
|
16
|
+
// whose subagents genuinely lack the doctrine in context (S7.2:
|
|
17
|
+
// "a subagent without it in context reads AGENTS.md once").
|
|
18
|
+
// - "0": disabled.
|
|
19
|
+
//
|
|
20
|
+
// When no doctrine file exists at cwd nothing was autoloaded, so reads
|
|
21
|
+
// pass through untouched (e.g. inspecting another repo's AGENTS.md).
|
|
22
|
+
// docs/orchestration.md is never guarded -- it is the on-demand layer
|
|
23
|
+
// and reading it is the intended path. Fails open on any error.
|
|
24
|
+
//
|
|
25
|
+
// Payload fields verified against code.claude.com/docs/en/hooks
|
|
26
|
+
// (PreToolUse input: session_id, cwd, tool_name, tool_input; output:
|
|
27
|
+
// hookSpecificOutput.permissionDecision allow|deny|ask + reason),
|
|
28
|
+
// 2026-06-11.
|
|
29
|
+
//
|
|
30
|
+
// .cjs so Node treats it as CommonJS regardless of any "type": "module"
|
|
31
|
+
// package.json in a parent directory of the install location.
|
|
32
|
+
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
const os = require('os');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
|
|
37
|
+
const mode = process.env.MAESTRO_DOCTRINE_GUARD || 'always';
|
|
38
|
+
if (mode === '0') process.exit(0);
|
|
39
|
+
|
|
40
|
+
let data = {};
|
|
41
|
+
try { data = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
42
|
+
if (data.tool_name !== 'Read' || !data.tool_input) process.exit(0);
|
|
43
|
+
|
|
44
|
+
const fp = data.tool_input.file_path;
|
|
45
|
+
if (typeof fp !== 'string') process.exit(0);
|
|
46
|
+
const base = path.basename(fp).toLowerCase();
|
|
47
|
+
if (base !== 'agents.md' && base !== 'claude.md') process.exit(0);
|
|
48
|
+
|
|
49
|
+
// Guard only when the doctrine was actually autoloaded: a doctrine file
|
|
50
|
+
// at cwd is what Claude Code injects at session start.
|
|
51
|
+
const cwd = typeof data.cwd === 'string' ? data.cwd : process.cwd();
|
|
52
|
+
let autoloaded = false;
|
|
53
|
+
try {
|
|
54
|
+
autoloaded = fs.existsSync(path.join(cwd, 'CLAUDE.md'))
|
|
55
|
+
|| fs.existsSync(path.join(cwd, 'AGENTS.md'));
|
|
56
|
+
} catch { autoloaded = false; }
|
|
57
|
+
if (!autoloaded) process.exit(0);
|
|
58
|
+
|
|
59
|
+
if (mode === 'once' && data.session_id) {
|
|
60
|
+
const marker = path.join(
|
|
61
|
+
os.tmpdir(),
|
|
62
|
+
`maestro-doctrine-guard-${String(data.session_id).replace(/[^a-zA-Z0-9-]/g, '_')}`
|
|
63
|
+
);
|
|
64
|
+
if (!fs.existsSync(marker)) {
|
|
65
|
+
try { fs.writeFileSync(marker, '1'); } catch { /* still allow */ }
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
process.stdout.write(JSON.stringify({
|
|
71
|
+
hookSpecificOutput: {
|
|
72
|
+
hookEventName: 'PreToolUse',
|
|
73
|
+
permissionDecision: 'deny',
|
|
74
|
+
permissionDecisionReason: 'maestro-doctrine-guard: denied Read of '
|
|
75
|
+
+ path.basename(fp) + ' -- the doctrine is autoloaded into context '
|
|
76
|
+
+ 'at session start (AGENTS.md S7.2); use the in-context copy '
|
|
77
|
+
+ 'instead of re-reading it from disk. The on-demand protocol '
|
|
78
|
+
+ 'layer lives in docs/orchestration.md, which is not blocked.'
|
|
79
|
+
}
|
|
80
|
+
}));
|
|
81
|
+
process.exit(0);
|