@maestrofrontier/frontier 1.4.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 (43) hide show
  1. package/AGENTS.md +214 -0
  2. package/CLAUDE.md +29 -0
  3. package/LICENSE +21 -0
  4. package/README.md +521 -0
  5. package/bin/maestro.cjs +75 -0
  6. package/commands/compress.md +36 -0
  7. package/commands/context-bar.md +30 -0
  8. package/commands/frontier.md +124 -0
  9. package/commands/settings.md +101 -0
  10. package/commands/terse.md +23 -0
  11. package/commands/update.md +59 -0
  12. package/docs/orchestration.md +168 -0
  13. package/frontier/cli.cjs +248 -0
  14. package/frontier/config.cjs +441 -0
  15. package/frontier/dispatch.cjs +255 -0
  16. package/frontier/judge.cjs +92 -0
  17. package/frontier/run.cjs +148 -0
  18. package/frontier/schema.cjs +112 -0
  19. package/frontier/semaphore.cjs +49 -0
  20. package/frontier/synthesize.cjs +79 -0
  21. package/hooks/frontier-autorun.cjs +124 -0
  22. package/hooks/hooks.json +103 -0
  23. package/hooks/maestro-doctrine-guard.cjs +81 -0
  24. package/hooks/maestro-gate-reminder.cjs +58 -0
  25. package/hooks/maestro-gate-telemetry.cjs +77 -0
  26. package/hooks/maestro-loop-guard.cjs +76 -0
  27. package/hooks/maestro-phase-scope.cjs +118 -0
  28. package/hooks/maestro-statusline-sync.cjs +152 -0
  29. package/hooks/maestro-subagent-guard.cjs +148 -0
  30. package/hooks/maestro-terse-mode.cjs +189 -0
  31. package/hooks/maestro-toolbudget-advisory.cjs +127 -0
  32. package/integrations/README.md +87 -0
  33. package/integrations/cline/skills/frontier/SKILL.md +75 -0
  34. package/integrations/codex/prompts/frontier.md +66 -0
  35. package/integrations/codex/prompts/update.md +36 -0
  36. package/integrations/cursor/commands/frontier.md +63 -0
  37. package/integrations/cursor/commands/update.md +34 -0
  38. package/integrations/gemini/commands/frontier.toml +76 -0
  39. package/integrations/windsurf/workflows/frontier.md +70 -0
  40. package/package.json +52 -0
  41. package/scripts/install.cjs +490 -0
  42. package/settings/cli.cjs +140 -0
  43. package/settings/config.cjs +309 -0
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env node
2
+ // Maestro Settings — aggregating front-end over the three existing toggle
3
+ // stores. Reads and writes terse (config.json terseLevel + live flag),
4
+ // frontier (frontier-state.json, via frontier/config.cjs), and context-bar
5
+ // (.context-bar-disabled next to the status-line script). It owns no state
6
+ // of its own; the existing readers stay the source of truth. Zero deps, CJS.
7
+ //
8
+ // Hardened I/O (atomic temp+rename, O_NOFOLLOW, 0600, symlink refusal,
9
+ // size-capped reads, whitelist validation) is ported from
10
+ // frontier/config.cjs and hooks/maestro-terse-mode.cjs. See
11
+ // docs/settings-design.md for the full design.
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+ const frontier = require('../frontier/config.cjs');
19
+
20
+ const TERSE_LEVELS = ['off', 'lite', 'full', 'ultra'];
21
+ const FLAG_LEVELS = ['lite', 'full', 'ultra']; // 'off' = remove the flag
22
+ const MAX_CONFIG_BYTES = 1 << 16; // 64 KB cap for config.json / settings.json
23
+
24
+ // Human labels for model ids. Presentation only — the model SET is always
25
+ // sourced from frontier DEFAULTS.adapters so there is one source of truth;
26
+ // an id with no label here falls back to the id itself.
27
+ const MODEL_LABELS = {
28
+ opus: 'Opus 4.8',
29
+ 'gpt-5.5': 'GPT-5.5 (Codex)',
30
+ gemini: 'Gemini 3.1 Pro',
31
+ };
32
+
33
+ // ---------- directory resolvers ----------
34
+
35
+ function claudeDir() {
36
+ return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
37
+ }
38
+ function terseFlagPath() { return path.join(claudeDir(), '.maestro-terse'); }
39
+ function configJsonPath() { return path.join(frontier.configDir(), 'config.json'); }
40
+
41
+ // ---------- hardened low-level I/O ----------
42
+
43
+ function safeWrite(targetPath, contents) {
44
+ try {
45
+ const dir = path.dirname(targetPath);
46
+ fs.mkdirSync(dir, { recursive: true });
47
+ try { if (fs.lstatSync(dir).isSymbolicLink()) return false; } catch { return false; }
48
+ try {
49
+ if (fs.lstatSync(targetPath).isSymbolicLink()) return false;
50
+ } catch (e) {
51
+ if (e.code !== 'ENOENT') return false;
52
+ }
53
+ const tempPath = path.join(dir, '.' + path.basename(targetPath) + '.' + process.pid + '.' + Date.now() + '.tmp');
54
+ const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
55
+ const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | O_NOFOLLOW;
56
+ let fd;
57
+ try {
58
+ if (O_NOFOLLOW === 0) { try { if (fs.lstatSync(tempPath).isSymbolicLink()) return false; } catch {} }
59
+ fd = fs.openSync(tempPath, flags, 0o600);
60
+ fs.writeSync(fd, contents);
61
+ try { fs.fchmodSync(fd, 0o600); } catch {}
62
+ } finally {
63
+ if (fd !== undefined) fs.closeSync(fd);
64
+ }
65
+ fs.renameSync(tempPath, targetPath);
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ function safeRead(targetPath, maxBytes) {
73
+ try {
74
+ let st;
75
+ try { st = fs.lstatSync(targetPath); } catch { return null; }
76
+ if (st.isSymbolicLink() || !st.isFile()) return null;
77
+ if (st.size > maxBytes) return null;
78
+ const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
79
+ let fd, out;
80
+ try {
81
+ if (O_NOFOLLOW === 0) { try { if (fs.lstatSync(targetPath).isSymbolicLink()) return null; } catch {} }
82
+ fd = fs.openSync(targetPath, fs.constants.O_RDONLY | O_NOFOLLOW);
83
+ const buf = Buffer.alloc(maxBytes);
84
+ const n = fs.readSync(fd, buf, 0, maxBytes, 0);
85
+ out = buf.slice(0, n).toString('utf8');
86
+ } finally {
87
+ if (fd !== undefined) fs.closeSync(fd);
88
+ }
89
+ return out;
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ // ---------- terse ----------
96
+
97
+ function readTerse() {
98
+ const env = String(process.env.MAESTRO_TERSE_LEVEL || '').toLowerCase();
99
+ const envValid = TERSE_LEVELS.includes(env);
100
+ let configLevel = null;
101
+ const raw = safeRead(configJsonPath(), MAX_CONFIG_BYTES);
102
+ if (raw) {
103
+ try {
104
+ const c = JSON.parse(raw);
105
+ const v = String((c && c.terseLevel) || '').toLowerCase();
106
+ if (TERSE_LEVELS.includes(v)) configLevel = v;
107
+ } catch {}
108
+ }
109
+ let level, source;
110
+ if (envValid) { level = env; source = 'env'; }
111
+ else if (configLevel) { level = configLevel; source = 'config'; }
112
+ else { level = 'off'; source = 'default'; }
113
+ return { level, source, envOverride: envValid ? env : null, configLevel: configLevel || 'off' };
114
+ }
115
+
116
+ function setTerse(level) {
117
+ const lvl = String(level == null ? '' : level).toLowerCase();
118
+ if (!TERSE_LEVELS.includes(lvl)) {
119
+ return { ok: false, error: 'terse level must be off, lite, full, or ultra' };
120
+ }
121
+ let cfg = {};
122
+ const raw = safeRead(configJsonPath(), MAX_CONFIG_BYTES);
123
+ if (raw) {
124
+ try {
125
+ const parsed = JSON.parse(raw);
126
+ if (parsed && typeof parsed === 'object') cfg = parsed;
127
+ } catch {
128
+ return { ok: false, error: 'config.json exists but is not valid JSON; refusing to overwrite it' };
129
+ }
130
+ }
131
+ cfg.terseLevel = lvl;
132
+ if (!safeWrite(configJsonPath(), JSON.stringify(cfg, null, 2))) {
133
+ return { ok: false, error: 'failed to write config.json' };
134
+ }
135
+ if (FLAG_LEVELS.includes(lvl)) safeWrite(terseFlagPath(), lvl);
136
+ else { try { fs.unlinkSync(terseFlagPath()); } catch {} }
137
+ const env = String(process.env.MAESTRO_TERSE_LEVEL || '').toLowerCase();
138
+ const warning = TERSE_LEVELS.includes(env)
139
+ ? 'MAESTRO_TERSE_LEVEL=' + env + ' is set in the environment and overrides this until unset'
140
+ : null;
141
+ return { ok: true, warning };
142
+ }
143
+
144
+ // ---------- context-bar ----------
145
+
146
+ function expandHome(p) {
147
+ if (p === '~') return os.homedir();
148
+ if (p && (p.startsWith('~/') || p.startsWith('~\\'))) return path.join(os.homedir(), p.slice(2));
149
+ return p;
150
+ }
151
+
152
+ function resolveStatuslineDir() {
153
+ const base = claudeDir();
154
+ const fallback = { dir: path.join(base, 'statusline'), scriptOk: false, resolved: false };
155
+ const raw = safeRead(path.join(base, 'settings.json'), MAX_CONFIG_BYTES);
156
+ if (!raw) return fallback;
157
+ let cmd = null;
158
+ try {
159
+ const s = JSON.parse(raw);
160
+ const sl = s && s.statusLine;
161
+ cmd = typeof sl === 'string' ? sl : (sl && typeof sl === 'object' ? sl.command : null);
162
+ } catch {
163
+ return fallback;
164
+ }
165
+ if (!cmd || typeof cmd !== 'string') return fallback;
166
+ const tokens = cmd.split(/\s+/).map(t => t.replace(/^["']|["']$/g, '')).filter(Boolean);
167
+ let tok = tokens.find(t => /context-bar(\.(ps1|sh|cmd|bat|js|cjs))?$/i.test(path.basename(t)));
168
+ if (!tok) tok = tokens.find(t => /[\\/]/.test(t) || /\.(ps1|sh|cmd|bat|js|cjs)$/i.test(t));
169
+ if (!tok) return fallback;
170
+ const expanded = expandHome(tok);
171
+ return {
172
+ dir: path.dirname(expanded),
173
+ scriptOk: /context-bar/i.test(path.basename(expanded)),
174
+ resolved: true,
175
+ };
176
+ }
177
+
178
+ function readContextBar() {
179
+ const r = resolveStatuslineDir();
180
+ const flag = path.join(r.dir, '.context-bar-disabled');
181
+ let present = false;
182
+ try { present = fs.statSync(flag).isFile(); } catch {}
183
+ return { enabled: !present, dir: r.dir, flagPath: flag, scriptConfirmed: r.scriptOk, resolved: r.resolved };
184
+ }
185
+
186
+ function setContextBar(enabled) {
187
+ const r = resolveStatuslineDir();
188
+ const flag = path.join(r.dir, '.context-bar-disabled');
189
+ if (enabled) {
190
+ try { fs.unlinkSync(flag); } catch {}
191
+ } else {
192
+ if (!safeWrite(flag, '')) return { ok: false, error: 'failed to write context-bar flag at ' + flag };
193
+ }
194
+ const warning = r.scriptOk
195
+ ? null
196
+ : 'could not confirm the status line is the Maestro context bar; using ' + r.dir;
197
+ return { ok: true, warning };
198
+ }
199
+
200
+ // ---------- frontier (delegated to frontier/config.cjs) ----------
201
+
202
+ function readFrontier(scope) { return frontier.loadState(scope); }
203
+
204
+ function saveFrontier(state, scope) {
205
+ return frontier.saveState(state, scope)
206
+ ? { ok: true, warning: null }
207
+ : { ok: false, error: 'failed to write frontier state' };
208
+ }
209
+
210
+ function setFrontier(spec, opts) {
211
+ opts = opts || {};
212
+ const s = String(spec == null ? '' : spec).trim();
213
+ const idx = s.indexOf(':');
214
+ const head = (idx === -1 ? s : s.slice(0, idx)).toLowerCase();
215
+ const tail = idx === -1 ? '' : s.slice(idx + 1).trim();
216
+
217
+ if (head === 'off' || head === '') return saveFrontier({ mode: 'off' }, opts.scope);
218
+
219
+ if (head === 'single') {
220
+ const model = (tail || opts.model || '').trim();
221
+ if (!frontier.validateModel(model)) return { ok: false, error: 'unknown model: ' + (model || '(none)') };
222
+ return saveFrontier({ mode: 'single', model }, opts.scope);
223
+ }
224
+
225
+ if (head === 'fusion') {
226
+ const preset = (tail || opts.preset || '').trim();
227
+ if (!frontier.validatePreset(preset)) return { ok: false, error: 'unknown preset: ' + (preset || '(none)') };
228
+ const state = { mode: 'fusion', preset };
229
+ if (preset === 'custom') {
230
+ const models = Array.isArray(opts.models)
231
+ ? opts.models
232
+ : String(opts.models || '').split(',').map(m => m.trim()).filter(Boolean);
233
+ if (models.length === 0) return { ok: false, error: 'custom preset requires --models a,b,c' };
234
+ if (models.length > 8) return { ok: false, error: 'custom preset exceeds the 8-model limit' };
235
+ const unknown = models.filter(m => !frontier.validateModel(m));
236
+ if (unknown.length) return { ok: false, error: 'unknown model(s): ' + unknown.join(', ') };
237
+ state.models = models;
238
+ }
239
+ if (opts.judge != null) {
240
+ if (!frontier.validateModel(opts.judge)) return { ok: false, error: 'unknown judge model: ' + opts.judge };
241
+ state.judgeModel = opts.judge;
242
+ }
243
+ if (opts.synth != null) {
244
+ if (!frontier.validateModel(opts.synth)) return { ok: false, error: 'unknown synth model: ' + opts.synth };
245
+ state.synthModel = opts.synth;
246
+ }
247
+ return saveFrontier(state, opts.scope);
248
+ }
249
+
250
+ return { ok: false, error: 'frontier value must be off, single:<model>, or fusion:<preset>' };
251
+ }
252
+
253
+ // ---------- aggregate ----------
254
+
255
+ function readAll(scope) {
256
+ return { terse: readTerse(), frontier: readFrontier(scope), contextBar: readContextBar() };
257
+ }
258
+
259
+ // The available-values catalog: every toggle value a picker can offer. The
260
+ // frontier model/preset SET is sourced from frontier DEFAULTS so there is no
261
+ // second list to drift; `custom` is the one preset value frontier accepts
262
+ // that is not a DEFAULTS preset (validatePreset special-cases it).
263
+ function catalog() {
264
+ const cfg = frontier.DEFAULTS;
265
+ const models = Object.keys(cfg.adapters).map(id => ({ id, label: MODEL_LABELS[id] || id }));
266
+ const presets = Object.keys(cfg.presets).map(id => ({ id, models: cfg.presets[id].slice() }));
267
+ presets.push({ id: 'custom', models: null });
268
+ return {
269
+ terse: { key: 'terse', values: TERSE_LEVELS.slice() },
270
+ frontier: {
271
+ modes: ['off', 'single', 'fusion'],
272
+ models,
273
+ presets,
274
+ stageModels: models.map(m => m.id),
275
+ defaults: { judge: cfg.judgeModel, synth: cfg.synthModel },
276
+ presetStages: cfg.presetStages || {},
277
+ },
278
+ contextBar: { key: 'context-bar', values: ['on', 'off'] },
279
+ };
280
+ }
281
+
282
+ function setKey(key, value, opts) {
283
+ const k = String(key == null ? '' : key).toLowerCase();
284
+ if (k === 'terse') return setTerse(value);
285
+ if (k === 'frontier') return setFrontier(value, opts);
286
+ if (k === 'context-bar' || k === 'contextbar' || k === 'bar') {
287
+ const v = String(value == null ? '' : value).toLowerCase();
288
+ if (v !== 'on' && v !== 'off') return { ok: false, error: 'context-bar value must be on or off' };
289
+ return setContextBar(v === 'on');
290
+ }
291
+ return { ok: false, error: 'unknown key: ' + key + ' (use terse, frontier, or context-bar)' };
292
+ }
293
+
294
+ module.exports = {
295
+ TERSE_LEVELS,
296
+ claudeDir,
297
+ terseFlagPath,
298
+ configJsonPath,
299
+ resolveStatuslineDir,
300
+ readTerse,
301
+ setTerse,
302
+ readFrontier,
303
+ setFrontier,
304
+ readContextBar,
305
+ setContextBar,
306
+ readAll,
307
+ catalog,
308
+ setKey,
309
+ };