@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.
- package/.agents/plugins/marketplace.json +21 -21
- package/.codex-plugin/plugin.json +29 -29
- package/.cursorrules +197 -194
- package/AGENTS.md +3 -3
- package/README.md +368 -368
- 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 -167
- package/docs/orchestration.md +168 -168
- package/frontier/cli.cjs +279 -252
- package/frontier/config.cjs +468 -468
- package/frontier/dispatch.cjs +267 -255
- package/frontier/judge.cjs +92 -92
- package/frontier/progress.cjs +138 -0
- package/frontier/run.cjs +201 -180
- package/frontier/schema.cjs +112 -112
- package/frontier/semaphore.cjs +49 -49
- package/frontier/synthesize.cjs +79 -79
- package/hooks/frontier-autorun.cjs +135 -120
- 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 -111
- package/integrations/cline/skills/frontier/SKILL.md +75 -75
- package/integrations/codex/prompts/frontier.md +70 -70
- package/integrations/codex/prompts/update.md +39 -39
- package/integrations/codex/skills/maestro-frontier/SKILL.md +122 -122
- package/integrations/codex/skills/maestro-settings/SKILL.md +55 -55
- package/integrations/codex/skills/maestro-terse/SKILL.md +58 -58
- package/integrations/codex/skills/maestro-update/SKILL.md +31 -31
- 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 +59 -58
- package/scripts/install.cjs +1014 -1014
- package/settings/cli.cjs +140 -140
- package/settings/config.cjs +309 -309
- package/skills/maestro-frontier/SKILL.md +122 -122
- package/skills/maestro-settings/SKILL.md +55 -55
- package/skills/maestro-terse/SKILL.md +58 -58
- package/skills/maestro-update/SKILL.md +31 -31
- package/skills/terse/SKILL.md +74 -74
package/frontier/judge.cjs
CHANGED
|
@@ -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 };
|