@maestrofrontier/frontier 1.4.5 → 1.6.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.
Files changed (51) hide show
  1. package/.agents/plugins/marketplace.json +21 -21
  2. package/.codex-plugin/plugin.json +29 -29
  3. package/.cursorrules +197 -194
  4. package/AGENTS.md +3 -3
  5. package/README.md +368 -368
  6. package/bin/maestro.cjs +75 -75
  7. package/commands/compress.md +36 -36
  8. package/commands/frontier.md +124 -124
  9. package/commands/terse.md +23 -23
  10. package/docs/codex.md +167 -167
  11. package/docs/orchestration.md +168 -168
  12. package/frontier/cli.cjs +279 -252
  13. package/frontier/config.cjs +468 -468
  14. package/frontier/dispatch.cjs +267 -255
  15. package/frontier/judge.cjs +92 -92
  16. package/frontier/progress.cjs +138 -0
  17. package/frontier/run.cjs +201 -180
  18. package/frontier/schema.cjs +112 -112
  19. package/frontier/semaphore.cjs +49 -49
  20. package/frontier/synthesize.cjs +79 -79
  21. package/hooks/frontier-autorun.cjs +135 -120
  22. package/hooks/hooks.json +103 -103
  23. package/hooks/maestro-doctrine-guard.cjs +81 -81
  24. package/hooks/maestro-gate-reminder.cjs +22 -7
  25. package/hooks/maestro-gate-telemetry.cjs +79 -77
  26. package/hooks/maestro-phase-scope.cjs +118 -118
  27. package/hooks/maestro-statusline-sync.cjs +152 -152
  28. package/hooks/maestro-subagent-guard.cjs +148 -148
  29. package/hooks/maestro-terse-mode.cjs +189 -189
  30. package/hooks/maestro-toolbudget-advisory.cjs +127 -127
  31. package/integrations/README.md +111 -111
  32. package/integrations/cline/skills/frontier/SKILL.md +75 -75
  33. package/integrations/codex/prompts/frontier.md +70 -70
  34. package/integrations/codex/prompts/update.md +39 -39
  35. package/integrations/codex/skills/maestro-frontier/SKILL.md +122 -122
  36. package/integrations/codex/skills/maestro-settings/SKILL.md +55 -55
  37. package/integrations/codex/skills/maestro-terse/SKILL.md +58 -58
  38. package/integrations/codex/skills/maestro-update/SKILL.md +31 -31
  39. package/integrations/cursor/commands/frontier.md +63 -63
  40. package/integrations/cursor/commands/update.md +34 -34
  41. package/integrations/gemini/commands/frontier.toml +76 -76
  42. package/integrations/windsurf/workflows/frontier.md +70 -70
  43. package/package.json +59 -58
  44. package/scripts/install.cjs +1014 -1014
  45. package/settings/cli.cjs +140 -140
  46. package/settings/config.cjs +309 -309
  47. package/skills/maestro-frontier/SKILL.md +122 -122
  48. package/skills/maestro-settings/SKILL.md +55 -55
  49. package/skills/maestro-terse/SKILL.md +58 -58
  50. package/skills/maestro-update/SKILL.md +31 -31
  51. package/skills/terse/SKILL.md +74 -74
@@ -1,49 +1,49 @@
1
- #!/usr/bin/env node
2
- // Maestro Frontier — bounded-concurrency map utility.
3
- // mapLimit(items, limit, asyncFn) -> Promise<settled[]>
4
- // Each settled item is {ok:true,value} or {ok:false,error}.
5
- // A rejected task releases its permit and does NOT starve the pool.
6
-
7
- 'use strict';
8
-
9
- /**
10
- * @template T, R
11
- * @param {T[]} items
12
- * @param {number} limit
13
- * @param {(item: T, index: number) => Promise<R>} asyncFn
14
- * @returns {Promise<({ok:true,value:R}|{ok:false,error:unknown})[]}
15
- */
16
- function mapLimit(items, limit, asyncFn) {
17
- return new Promise((resolve) => {
18
- const n = items.length;
19
- if (n === 0) { resolve([]); return; }
20
-
21
- const results = new Array(n);
22
- let nextIdx = 0; // index of next item to start
23
- let inFlight = 0; // currently running tasks
24
- let done = 0; // settled tasks
25
-
26
- function run() {
27
- while (inFlight < limit && nextIdx < n) {
28
- const idx = nextIdx++;
29
- inFlight++;
30
- Promise.resolve()
31
- .then(() => asyncFn(items[idx], idx))
32
- .then(
33
- (value) => { results[idx] = { ok: true, value }; },
34
- (error) => { results[idx] = { ok: false, error }; }
35
- )
36
- .finally(() => {
37
- inFlight--;
38
- done++;
39
- if (done === n) { resolve(results); return; }
40
- run();
41
- });
42
- }
43
- }
44
-
45
- run();
46
- });
47
- }
48
-
49
- module.exports = { mapLimit };
1
+ #!/usr/bin/env node
2
+ // Maestro Frontier — bounded-concurrency map utility.
3
+ // mapLimit(items, limit, asyncFn) -> Promise<settled[]>
4
+ // Each settled item is {ok:true,value} or {ok:false,error}.
5
+ // A rejected task releases its permit and does NOT starve the pool.
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @template T, R
11
+ * @param {T[]} items
12
+ * @param {number} limit
13
+ * @param {(item: T, index: number) => Promise<R>} asyncFn
14
+ * @returns {Promise<({ok:true,value:R}|{ok:false,error:unknown})[]}
15
+ */
16
+ function mapLimit(items, limit, asyncFn) {
17
+ return new Promise((resolve) => {
18
+ const n = items.length;
19
+ if (n === 0) { resolve([]); return; }
20
+
21
+ const results = new Array(n);
22
+ let nextIdx = 0; // index of next item to start
23
+ let inFlight = 0; // currently running tasks
24
+ let done = 0; // settled tasks
25
+
26
+ function run() {
27
+ while (inFlight < limit && nextIdx < n) {
28
+ const idx = nextIdx++;
29
+ inFlight++;
30
+ Promise.resolve()
31
+ .then(() => asyncFn(items[idx], idx))
32
+ .then(
33
+ (value) => { results[idx] = { ok: true, value }; },
34
+ (error) => { results[idx] = { ok: false, error }; }
35
+ )
36
+ .finally(() => {
37
+ inFlight--;
38
+ done++;
39
+ if (done === n) { resolve(results); return; }
40
+ run();
41
+ });
42
+ }
43
+ }
44
+
45
+ run();
46
+ });
47
+ }
48
+
49
+ module.exports = { mapLimit };
@@ -1,79 +1,79 @@
1
- #!/usr/bin/env node
2
- // Maestro Frontier — synthesis stage: build prompt + invoke Opus for final answer.
3
-
4
- 'use strict';
5
-
6
- const dispatch = require('./dispatch.cjs');
7
-
8
- /**
9
- * Build the synthesis prompt for Opus.
10
- * @param {string} userPrompt
11
- * @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
12
- * @param {object} cfg
13
- * @returns {string}
14
- */
15
- function buildSynthPrompt(userPrompt, bundle, cfg) {
16
- const antiMajority =
17
- 'Do NOT majority-vote or pick the most common answer; weigh correctness and evidence — ' +
18
- 'a single correct minority response outweighs a popular wrong one.';
19
-
20
- let groundingSection;
21
- if (bundle.analysis) {
22
- groundingSection =
23
- `PANEL ANALYSIS (structured):
24
- ${JSON.stringify(bundle.analysis, null, 2)}
25
-
26
- Ground your final answer in this analysis:
27
- - Adopt the consensus points as established facts.
28
- - RESOLVE contradictions by reasoning about which stance is most correct; do not dodge them.
29
- - Preserve unique insights that add value.
30
- - Address any blind spots the analysis identified.`;
31
- } else {
32
- const raw = bundle.responses.map(
33
- r => `### Response from ${r.model}\n${r.content}`
34
- ).join('\n\n');
35
- groundingSection =
36
- `RAW PANEL RESPONSES:
37
- ${raw}
38
-
39
- Ground your final answer in these responses.`;
40
- }
41
-
42
- return `You are a SYNTHESIZER producing the definitive final answer to a user question.
43
-
44
- USER QUESTION:
45
- ${userPrompt}
46
-
47
- ${groundingSection}
48
-
49
- IMPORTANT: ${antiMajority}
50
-
51
- Write the final answer as clear, direct prose. No JSON, no meta-commentary, no preamble about your process. Output the answer only.`;
52
- }
53
-
54
- /**
55
- * Run the synthesis stage. Returns the final answer string or '' on failure (degrades gracefully).
56
- * @param {string} userPrompt
57
- * @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
58
- * @param {object} cfg
59
- * @param {{ spawn?: Function }} [deps]
60
- * @returns {Promise<string>}
61
- */
62
- async function runSynth(userPrompt, bundle, cfg, deps) {
63
- const spawn = (deps && deps.spawn) || dispatch.spawnOne;
64
- let r;
65
- try {
66
- r = await spawn(
67
- buildSynthPrompt(userPrompt, bundle, cfg),
68
- cfg.adapters[cfg.synthModel],
69
- { timeoutMs: cfg.timeoutMs, fusionDepth: 1 }
70
- );
71
- } catch {
72
- return '';
73
- }
74
-
75
- if (r && r.ok && r.content) return r.content;
76
- return '';
77
- }
78
-
79
- module.exports = { buildSynthPrompt, runSynth };
1
+ #!/usr/bin/env node
2
+ // Maestro Frontier — synthesis stage: build prompt + invoke Opus for final answer.
3
+
4
+ 'use strict';
5
+
6
+ const dispatch = require('./dispatch.cjs');
7
+
8
+ /**
9
+ * Build the synthesis prompt for Opus.
10
+ * @param {string} userPrompt
11
+ * @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
12
+ * @param {object} cfg
13
+ * @returns {string}
14
+ */
15
+ function buildSynthPrompt(userPrompt, bundle, cfg) {
16
+ const antiMajority =
17
+ 'Do NOT majority-vote or pick the most common answer; weigh correctness and evidence — ' +
18
+ 'a single correct minority response outweighs a popular wrong one.';
19
+
20
+ let groundingSection;
21
+ if (bundle.analysis) {
22
+ groundingSection =
23
+ `PANEL ANALYSIS (structured):
24
+ ${JSON.stringify(bundle.analysis, null, 2)}
25
+
26
+ Ground your final answer in this analysis:
27
+ - Adopt the consensus points as established facts.
28
+ - RESOLVE contradictions by reasoning about which stance is most correct; do not dodge them.
29
+ - Preserve unique insights that add value.
30
+ - Address any blind spots the analysis identified.`;
31
+ } else {
32
+ const raw = bundle.responses.map(
33
+ r => `### Response from ${r.model}\n${r.content}`
34
+ ).join('\n\n');
35
+ groundingSection =
36
+ `RAW PANEL RESPONSES:
37
+ ${raw}
38
+
39
+ Ground your final answer in these responses.`;
40
+ }
41
+
42
+ return `You are a SYNTHESIZER producing the definitive final answer to a user question.
43
+
44
+ USER QUESTION:
45
+ ${userPrompt}
46
+
47
+ ${groundingSection}
48
+
49
+ IMPORTANT: ${antiMajority}
50
+
51
+ Write the final answer as clear, direct prose. No JSON, no meta-commentary, no preamble about your process. Output the answer only.`;
52
+ }
53
+
54
+ /**
55
+ * Run the synthesis stage. Returns the final answer string or '' on failure (degrades gracefully).
56
+ * @param {string} userPrompt
57
+ * @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
58
+ * @param {object} cfg
59
+ * @param {{ spawn?: Function }} [deps]
60
+ * @returns {Promise<string>}
61
+ */
62
+ async function runSynth(userPrompt, bundle, cfg, deps) {
63
+ const spawn = (deps && deps.spawn) || dispatch.spawnOne;
64
+ let r;
65
+ try {
66
+ r = await spawn(
67
+ buildSynthPrompt(userPrompt, bundle, cfg),
68
+ cfg.adapters[cfg.synthModel],
69
+ { timeoutMs: cfg.timeoutMs, fusionDepth: 1 }
70
+ );
71
+ } catch {
72
+ return '';
73
+ }
74
+
75
+ if (r && r.ok && r.content) return r.content;
76
+ return '';
77
+ }
78
+
79
+ module.exports = { buildSynthPrompt, runSynth };
@@ -1,120 +1,135 @@
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
- try {
76
- const { runFrontier } = require('../frontier/run.cjs');
77
- result = await runFrontier({ prompt, state });
78
- } catch (e) {
79
- process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
80
- noop();
81
- }
82
-
83
- if (!result || result.status !== 'ok' || !result.final) {
84
- if (result && result.status === 'error') {
85
- process.stderr.write(
86
- 'frontier-autorun: engine error [' + result.failure_reason + ']: ' + result.error + '\n');
87
- }
88
- noop();
89
- }
90
-
91
- const context =
92
- 'MAESTRO FRONTIER AUTORUN — ' + presetHeader(state) + '\n\n' +
93
- 'The Maestro Frontier engine already ran this prompt through the panel ' +
94
- 'above and produced the answer below. Relay it as your response — you ' +
95
- 'may reformat for clarity, but do not redo the work or contradict it:\n\n' +
96
- result.final;
97
-
98
- // fs.writeSync (synchronous, unbuffered) guarantees the full payload reaches
99
- // stdout before exit. process.exit() does NOT drain a pipe-backed
100
- // process.stdout, which would truncate a large engine answer (multi-KB
101
- // synthesis) into malformed JSON the hook consumer cannot parse.
102
- fs.writeSync(1, JSON.stringify({
103
- hookSpecificOutput: {
104
- hookEventName: 'UserPromptSubmit',
105
- additionalContext: context,
106
- },
107
- }));
108
- process.exit(0);
109
- }
110
-
111
- function presetHeader(st) {
112
- if (st.mode === 'single') return 'single · ' + st.model;
113
- if (st.mode === 'fusion') {
114
- const preset = st.preset === 'custom'
115
- ? 'custom (' + (Array.isArray(st.models) ? st.models.join(', ') : '') + ')'
116
- : st.preset;
117
- return 'fusion · ' + preset;
118
- }
119
- return String(st.mode);
120
- }
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
+ let scope;
47
+ try {
48
+ const cfg = require('../frontier/config.cjs');
49
+ const cwd = data.cwd || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.cwd();
50
+ scope = cfg.resolveScope([], { cwd });
51
+ state = cfg.loadState(scope);
52
+ } catch {
53
+ noop();
54
+ }
55
+
56
+ // Off -> zero overhead: no run.cjs require, no spawn, no injected context.
57
+ if (!state || state.mode === 'off') noop();
58
+
59
+ const prompt = String(data.prompt || '');
60
+
61
+ // Optional length gate (default 0 = every prompt). Skips trivially short
62
+ // prompts ("yes"/"ok") so they don't pay a full engine run.
63
+ const rawMin = Number(state.autorunMinChars);
64
+ const minChars = Number.isFinite(rawMin) && rawMin > 0 ? rawMin : 0;
65
+ if (prompt.trim().length < minChars) noop();
66
+
67
+ // Any unexpected throw after the await boundary degrades to a normal turn,
68
+ // never a non-zero exit / unhandled rejection.
69
+ run().catch((e) => {
70
+ process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
71
+ process.exit(0);
72
+ });
73
+
74
+ async function run() {
75
+ let result;
76
+ const runStart = Date.now();
77
+ // Live statusline progress: write the current stage as each one starts so
78
+ // the context-bar can show ƒ⠿ fanning / ƒ⚖ judging / ƒ✶ synth during the
79
+ // otherwise-silent blocking run. Cleared below on completion or error.
80
+ const progress = require('../frontier/progress.cjs');
81
+ const onProgress = progress.makeProgressWriter(scope);
82
+ try {
83
+ const { runFrontier } = require('../frontier/run.cjs');
84
+ result = await runFrontier({ prompt, state, deps: { onProgress } });
85
+ } catch (e) {
86
+ progress.clearProgress(scope);
87
+ process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
88
+ noop();
89
+ }
90
+ const runMs = Date.now() - runStart;
91
+ progress.clearProgress(scope);
92
+
93
+ if (!result || result.status !== 'ok' || !result.final) {
94
+ if (result && result.status === 'error') {
95
+ process.stderr.write(
96
+ 'frontier-autorun: engine error [' + result.failure_reason + ']: ' + result.error + '\n');
97
+ }
98
+ noop();
99
+ }
100
+
101
+ const modelCount = result.responses ? result.responses.length : 1;
102
+ const banner = '⚡ Frontier \xb7 ' + presetHeader(state) + ' \xb7 ' + modelCount + ' models \xb7 ' + Math.round(runMs / 1000) + 's';
103
+
104
+ const context =
105
+ 'MAESTRO FRONTIER AUTORUN — ' + presetHeader(state) + '\n\n' +
106
+ 'The Maestro Frontier engine already ran this prompt through the panel ' +
107
+ 'above and produced the answer below. Begin your response with this exact ' +
108
+ 'banner line (verbatim, on its own line): ' + banner + '\n\n' +
109
+ 'Then relay the answer — you may reformat for clarity, but do not redo ' +
110
+ 'the work or contradict it:\n\n' +
111
+ result.final;
112
+
113
+ // fs.writeSync (synchronous, unbuffered) guarantees the full payload reaches
114
+ // stdout before exit. process.exit() does NOT drain a pipe-backed
115
+ // process.stdout, which would truncate a large engine answer (multi-KB
116
+ // synthesis) into malformed JSON the hook consumer cannot parse.
117
+ fs.writeSync(1, JSON.stringify({
118
+ hookSpecificOutput: {
119
+ hookEventName: 'UserPromptSubmit',
120
+ additionalContext: context,
121
+ },
122
+ }));
123
+ process.exit(0);
124
+ }
125
+
126
+ function presetHeader(st) {
127
+ if (st.mode === 'single') return 'single · ' + st.model;
128
+ if (st.mode === 'fusion') {
129
+ const preset = st.preset === 'custom'
130
+ ? 'custom (' + (Array.isArray(st.models) ? st.models.join(', ') : '') + ')'
131
+ : st.preset;
132
+ return 'fusion · ' + preset;
133
+ }
134
+ return String(st.mode);
135
+ }