@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,92 +1,92 @@
1
- #!/usr/bin/env node
2
- // Maestro Frontier — judge stage: build prompt + invoke Opus to produce Analysis.
3
-
4
- 'use strict';
5
-
6
- const { parseAnalysis } = require('./schema.cjs');
7
- const dispatch = require('./dispatch.cjs');
8
-
9
- /**
10
- * Build the judge prompt for Opus.
11
- * @param {string} userPrompt
12
- * @param {import('./schema.cjs').PanelResponse[]} responses
13
- * @param {object} cfg
14
- * @returns {string}
15
- */
16
- function buildJudgePrompt(userPrompt, responses, cfg) {
17
- const sections = responses.map(
18
- r => `### Response from ${r.model}\n${r.content}`
19
- ).join('\n\n');
20
-
21
- const domainNote = (cfg.excluded_domains && cfg.excluded_domains.length > 0)
22
- ? `\nDisregard any claims sourced only from the following domains: ${cfg.excluded_domains.join(', ')}.\n`
23
- : '';
24
-
25
- return `You are a neutral JUDGE evaluating multiple AI panel responses to a user question.
26
-
27
- USER QUESTION:
28
- ${userPrompt}
29
-
30
- PANEL RESPONSES:
31
- ${sections}
32
- ${domainNote}
33
- INSTRUCTIONS:
34
- COMPARE the panel responses — do NOT merge or summarize them. Perform a structured analysis:
35
- - consensus: points that all or most models agree on
36
- - contradictions: topics where models disagree, with each model's specific stance
37
- - partial_coverage: points that only some models covered (with which models)
38
- - unique_insights: insights raised by exactly one model (with which model)
39
- - blind_spots: important points that NO model addressed
40
-
41
- OUTPUT IS ONLY a single JSON object with EXACTLY these keys:
42
- consensus (string[])
43
- contradictions (array of {topic: string, stances: [{model: string, stance: string}]})
44
- partial_coverage (array of {models: string[], point: string})
45
- unique_insights (array of {model: string, insight: string})
46
- blind_spots (string[])
47
-
48
- No prose before or after. No markdown fence. No extra keys. Output the raw JSON object only.`;
49
- }
50
-
51
- /**
52
- * Run the judge stage. Returns Analysis or undefined on any failure (degrades gracefully).
53
- * @param {string} userPrompt
54
- * @param {import('./schema.cjs').PanelResponse[]} responses
55
- * @param {object} cfg
56
- * @param {{ spawn?: Function }} [deps]
57
- * @returns {Promise<import('./schema.cjs').Analysis | undefined>}
58
- */
59
- async function runJudge(userPrompt, responses, cfg, deps) {
60
- const spawn = (deps && deps.spawn) || dispatch.spawnOne;
61
- let r;
62
- try {
63
- r = await spawn(
64
- buildJudgePrompt(userPrompt, responses, cfg),
65
- cfg.adapters[cfg.judgeModel],
66
- { timeoutMs: cfg.timeoutMs, fusionDepth: 1 }
67
- );
68
- } catch {
69
- return undefined;
70
- }
71
-
72
- if (!r || !r.ok || !r.content) return undefined;
73
-
74
- // Primary parse
75
- try {
76
- return parseAnalysis(r.content);
77
- } catch {
78
- // substring recovery: find outermost { ... }
79
- const first = r.content.indexOf('{');
80
- const last = r.content.lastIndexOf('}');
81
- if (first !== -1 && last > first) {
82
- try {
83
- return parseAnalysis(r.content.slice(first, last + 1));
84
- } catch {
85
- return undefined;
86
- }
87
- }
88
- return undefined;
89
- }
90
- }
91
-
92
- module.exports = { buildJudgePrompt, runJudge };
1
+ #!/usr/bin/env node
2
+ // Maestro Frontier — judge stage: build prompt + invoke Opus to produce Analysis.
3
+
4
+ 'use strict';
5
+
6
+ const { parseAnalysis } = require('./schema.cjs');
7
+ const dispatch = require('./dispatch.cjs');
8
+
9
+ /**
10
+ * Build the judge prompt for Opus.
11
+ * @param {string} userPrompt
12
+ * @param {import('./schema.cjs').PanelResponse[]} responses
13
+ * @param {object} cfg
14
+ * @returns {string}
15
+ */
16
+ function buildJudgePrompt(userPrompt, responses, cfg) {
17
+ const sections = responses.map(
18
+ r => `### Response from ${r.model}\n${r.content}`
19
+ ).join('\n\n');
20
+
21
+ const domainNote = (cfg.excluded_domains && cfg.excluded_domains.length > 0)
22
+ ? `\nDisregard any claims sourced only from the following domains: ${cfg.excluded_domains.join(', ')}.\n`
23
+ : '';
24
+
25
+ return `You are a neutral JUDGE evaluating multiple AI panel responses to a user question.
26
+
27
+ USER QUESTION:
28
+ ${userPrompt}
29
+
30
+ PANEL RESPONSES:
31
+ ${sections}
32
+ ${domainNote}
33
+ INSTRUCTIONS:
34
+ COMPARE the panel responses — do NOT merge or summarize them. Perform a structured analysis:
35
+ - consensus: points that all or most models agree on
36
+ - contradictions: topics where models disagree, with each model's specific stance
37
+ - partial_coverage: points that only some models covered (with which models)
38
+ - unique_insights: insights raised by exactly one model (with which model)
39
+ - blind_spots: important points that NO model addressed
40
+
41
+ OUTPUT IS ONLY a single JSON object with EXACTLY these keys:
42
+ consensus (string[])
43
+ contradictions (array of {topic: string, stances: [{model: string, stance: string}]})
44
+ partial_coverage (array of {models: string[], point: string})
45
+ unique_insights (array of {model: string, insight: string})
46
+ blind_spots (string[])
47
+
48
+ No prose before or after. No markdown fence. No extra keys. Output the raw JSON object only.`;
49
+ }
50
+
51
+ /**
52
+ * Run the judge stage. Returns Analysis or undefined on any failure (degrades gracefully).
53
+ * @param {string} userPrompt
54
+ * @param {import('./schema.cjs').PanelResponse[]} responses
55
+ * @param {object} cfg
56
+ * @param {{ spawn?: Function }} [deps]
57
+ * @returns {Promise<import('./schema.cjs').Analysis | undefined>}
58
+ */
59
+ async function runJudge(userPrompt, responses, cfg, deps) {
60
+ const spawn = (deps && deps.spawn) || dispatch.spawnOne;
61
+ let r;
62
+ try {
63
+ r = await spawn(
64
+ buildJudgePrompt(userPrompt, responses, cfg),
65
+ cfg.adapters[cfg.judgeModel],
66
+ { timeoutMs: cfg.timeoutMs, fusionDepth: 1 }
67
+ );
68
+ } catch {
69
+ return undefined;
70
+ }
71
+
72
+ if (!r || !r.ok || !r.content) return undefined;
73
+
74
+ // Primary parse
75
+ try {
76
+ return parseAnalysis(r.content);
77
+ } catch {
78
+ // substring recovery: find outermost { ... }
79
+ const first = r.content.indexOf('{');
80
+ const last = r.content.lastIndexOf('}');
81
+ if (first !== -1 && last > first) {
82
+ try {
83
+ return parseAnalysis(r.content.slice(first, last + 1));
84
+ } catch {
85
+ return undefined;
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+ }
91
+
92
+ module.exports = { buildJudgePrompt, runJudge };
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ // Maestro Frontier — live progress file for the statusline.
3
+ //
4
+ // The armed autorun runs the panel->judge->synth pipeline inside a blocking
5
+ // UserPromptSubmit hook (silent to the chat until it returns). To give a live
6
+ // signal during that wait, the hook wires makeProgressWriter(scope) as the
7
+ // engine's onProgress callback; it writes frontier-progress.<scope>.json with
8
+ // the current stage as each stage starts. The context-bar statusline reads
9
+ // that file and renders a transient phase (ƒ⠿ fanning 2/3 -> ƒ⚖ judging ->
10
+ // ƒ✶ synth) in place of the static armed badge, then snaps back when the file
11
+ // is cleared. clearProgress removes it on completion; the statusline also
12
+ // ignores a file whose ts is stale (>300s), so a crashed run never pins a
13
+ // phantom phase.
14
+ //
15
+ // Scope + path resolution reuse frontier/config.cjs so the file the engine
16
+ // writes is byte-for-byte the path the statusline computes (cc-<hash>). The
17
+ // statusline only ever renders the whitelisted phase words + clamped integer
18
+ // counts from this file -- never raw bytes -- so the file is presentation
19
+ // data, not a trust boundary.
20
+ //
21
+ // .cjs so Node treats it as CommonJS regardless of a parent "type": "module".
22
+
23
+ 'use strict';
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+ const { statePath } = require('./config.cjs');
28
+
29
+ // Phases the statusline knows how to render. The writer maps the engine's
30
+ // onProgress events onto exactly these; anything else is dropped.
31
+ const PHASES = ['panel', 'judge', 'synth', 'single'];
32
+
33
+ /**
34
+ * Progress file path for a scope, derived from statePath so the scope-alias
35
+ * and default/suffix rules match the state file (and the statusline reader)
36
+ * exactly: frontier-state[.scope].json -> frontier-progress[.scope].json.
37
+ * @param {string} [scope]
38
+ * @returns {string}
39
+ */
40
+ function progressPath(scope) {
41
+ const sp = statePath(scope);
42
+ return path.join(path.dirname(sp), path.basename(sp).replace('frontier-state', 'frontier-progress'));
43
+ }
44
+
45
+ /**
46
+ * Atomic, symlink-refusing, 0600 write of the progress record. Mirrors
47
+ * saveState in config.cjs. Never throws — progress is best-effort telemetry.
48
+ * @param {string} scope
49
+ * @param {{ phase:string, done?:number, total?:number }} rec
50
+ * @returns {boolean}
51
+ */
52
+ function writeProgress(scope, rec) {
53
+ if (!rec || PHASES.indexOf(rec.phase) === -1) return false;
54
+ const clampInt = (v) => {
55
+ const n = Math.floor(Number(v));
56
+ if (!Number.isFinite(n) || n < 0) return 0;
57
+ return n > 99 ? 99 : n;
58
+ };
59
+ const payload = JSON.stringify({
60
+ phase: rec.phase,
61
+ done: clampInt(rec.done),
62
+ total: clampInt(rec.total),
63
+ ts: Date.now(),
64
+ pid: process.pid,
65
+ });
66
+ try {
67
+ const p = progressPath(scope);
68
+ const dir = path.dirname(p);
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ try { if (fs.lstatSync(dir).isSymbolicLink()) return false; } catch { return false; }
71
+ try {
72
+ if (fs.lstatSync(p).isSymbolicLink()) return false;
73
+ } catch (e) {
74
+ if (e.code !== 'ENOENT') return false;
75
+ }
76
+ const tempPath = path.join(dir, '.frontier-progress.' + process.pid + '.' + Date.now() + '.tmp');
77
+ const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
78
+ const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | O_NOFOLLOW;
79
+ let fd;
80
+ try {
81
+ if (O_NOFOLLOW === 0) { try { if (fs.lstatSync(tempPath).isSymbolicLink()) return false; } catch {} }
82
+ fd = fs.openSync(tempPath, flags, 0o600);
83
+ fs.writeSync(fd, payload);
84
+ try { fs.fchmodSync(fd, 0o600); } catch {}
85
+ } finally {
86
+ if (fd !== undefined) fs.closeSync(fd);
87
+ }
88
+ fs.renameSync(tempPath, p);
89
+ return true;
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Remove the progress file for a scope. Never throws.
97
+ * @param {string} scope
98
+ */
99
+ function clearProgress(scope) {
100
+ try { fs.unlinkSync(progressPath(scope)); } catch {}
101
+ }
102
+
103
+ /**
104
+ * Build an onProgress(event) callback that writes the current stage to the
105
+ * progress file. Maps engine events -> statusline phases; ignores terminal
106
+ * events (panel-done/degraded/done — the caller clears the file on completion).
107
+ * Never throws.
108
+ * @param {string} scope
109
+ * @returns {(event:object)=>void}
110
+ */
111
+ function makeProgressWriter(scope) {
112
+ return function onProgress(ev) {
113
+ if (!ev || typeof ev.phase !== 'string') return;
114
+ let rec = null;
115
+ switch (ev.phase) {
116
+ case 'panel-start':
117
+ rec = { phase: 'panel', done: 0, total: Array.isArray(ev.models) ? ev.models.length : 0 };
118
+ break;
119
+ case 'panel-progress':
120
+ rec = { phase: 'panel', done: ev.done, total: ev.total };
121
+ break;
122
+ case 'judge-start':
123
+ rec = { phase: 'judge', done: 0, total: 0 };
124
+ break;
125
+ case 'synth-start':
126
+ rec = { phase: 'synth', done: 0, total: 0 };
127
+ break;
128
+ case 'single-start':
129
+ rec = { phase: 'single', done: 0, total: 0 };
130
+ break;
131
+ default:
132
+ return;
133
+ }
134
+ writeProgress(scope, rec);
135
+ };
136
+ }
137
+
138
+ module.exports = { progressPath, writeProgress, clearProgress, makeProgressWriter, PHASES };