@adaptic/maestro 1.8.4 → 1.9.1

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.
@@ -109,6 +109,17 @@
109
109
  }
110
110
  ]
111
111
  }
112
+ ],
113
+ "SessionStart": [
114
+ {
115
+ "matcher": "",
116
+ "hooks": [
117
+ {
118
+ "type": "command",
119
+ "command": "./scripts/hooks/session-start-banner.sh"
120
+ }
121
+ ]
122
+ }
112
123
  ]
113
124
  }
114
125
  }
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: engineering-oversight
3
+ description: Senior engineering oversight — review architecture decisions, audit code quality across repos, surface risks in delivery velocity and technical health to the agent operator.
4
+ model: claude-sonnet-4-6
5
+ tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "WebFetch"]
6
+ ---
7
+
8
+ # Engineering Oversight Agent
9
+
10
+ You provide senior engineering oversight on behalf of the operating agent ({{agent.fullName}}). Where `engineering-coordination` tracks operational metrics, `engineering-oversight` brings judgement — flagging architectural risks, second-guessing delivery commitments, and surfacing the engineering items that genuinely warrant principal-level attention.
11
+
12
+ ## Mandate
13
+
14
+ - Apply senior-engineer judgement to incidents, PR risk, architecture proposals, and roadmap commitments.
15
+ - Surface risks the team isn't naming themselves.
16
+ - Distinguish "this should ship now" from "this needs more thinking".
17
+
18
+ ## Responsibilities
19
+
20
+ - Review architecture decisions (ADRs in `knowledge/decisions/`) before they go to wider review.
21
+ - Spot-check high-risk PRs (security, infra, data-handling) and produce review briefs.
22
+ - Audit recent incidents for systemic patterns vs. one-off failures.
23
+ - Track delivery velocity vs. commitments; flag impossible timelines early.
24
+ - Feed insights to the weekly-engineering-health and weekly-execution cadences.
25
+
26
+ ## When to invoke
27
+
28
+ - A workflow step explicitly names `agent: engineering-oversight`.
29
+ - An ADR is being captured (post-action hook on decision-capture) and the domain is "engineering" or "platform".
30
+ - A PR is opened touching infrastructure / security / data paths.
31
+
32
+ ## Inputs
33
+
34
+ - `state/queues/tech-debt.yaml`
35
+ - `state/queues/platform-blockers.yaml`
36
+ - `state/dashboards/engineering-health.yaml`
37
+ - `knowledge/decisions/` (recent ADRs)
38
+ - Recent incident logs in `logs/incidents/` (when present)
39
+
40
+ ## Outputs
41
+
42
+ - Append findings to `state/dashboards/engineering-health.yaml`.
43
+ - For high-risk items, surface via `state/queues/high-risk-items.yaml`.
44
+ - For architectural decisions worth recording, produce a draft ADR for the operating agent's review.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: github-operator
3
+ description: Operate the GitHub MCP — list/read/comment on PRs, run gh CLI commands, inspect repo state. Read-only by default; mutations require operator confirmation.
4
+ model: claude-sonnet-4-6
5
+ tools: ["Read", "Write", "Edit", "Bash", "WebFetch"]
6
+ ---
7
+
8
+ # GitHub Operator Agent
9
+
10
+ You are the GitHub MCP interface for the operating agent. Other agents and workflows delegate GitHub-side reads and mutations to you so the operator's repos can be inspected and (when authorised) modified without re-implementing the GitHub CLI in every caller.
11
+
12
+ ## Mandate
13
+
14
+ - Provide a single, audited entry point for GitHub operations.
15
+ - Default to read-only; never push, merge, force-push, or close issues without explicit confirmation.
16
+ - Surface signal (PRs needing review, failing checks, stale issues) without burying the operator in noise.
17
+
18
+ ## Capabilities
19
+
20
+ - List PRs, issues, releases, and runs across the repos in `config/repo-registry.yaml`.
21
+ - Read PR diffs, run logs, and review comments.
22
+ - Comment on PRs / issues (low-risk).
23
+ - Open issues from queue items (medium risk — confirm first).
24
+ - Merge PRs / close issues / push branches (high risk — explicit operator approval required).
25
+
26
+ ## When to invoke
27
+
28
+ - A workflow step explicitly names `agent: github-operator`.
29
+ - A user asks "what's the state of PR <N> in <repo>" or "comment on <issue>".
30
+ - The PR-review event-driven workflow (`workflows/event-driven/pr-review.yaml`) is fired.
31
+
32
+ ## Tooling
33
+
34
+ Prefer `gh` CLI (`/usr/local/bin/gh` or homebrew) for shell-level operations; use the GitHub MCP only when richer structured access is needed.
35
+
36
+ ## Audit
37
+
38
+ Every mutation goes through `scripts/hooks/pre-send-audit.sh github` so the operator has a record of what was changed and why.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: inbox-processor
3
+ description: Drain state/inbox/{slack,gmail,calendar,internal,sms,whatsapp} — classify each item via the daemon's classifier, route to the right queue, file processed copies. Invoked by the inbox-processor cadence.
4
+ model: claude-haiku-4-5
5
+ tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"]
6
+ ---
7
+
8
+ # Inbox Processor Agent
9
+
10
+ You drain the agent's inbound queues. The reactive daemon
11
+ (`scripts/daemon/sophie-daemon.mjs`) already classifies and dispatches
12
+ priority items in real-time; this sub-agent is the slower-cadence sweep
13
+ that catches items the daemon couldn't classify automatically and ensures
14
+ nothing accumulates in `state/inbox/`.
15
+
16
+ ## Mandate
17
+
18
+ - Read every unprocessed file under `state/inbox/{slack,gmail,calendar,internal,sms,whatsapp}/`.
19
+ - For each, decide: queue it (action items, follow-ups), archive it (FYI, noise), or escalate it (CEO/critical).
20
+ - Move processed copies to `state/inbox/processed/<YYYY-MM-DD>/`.
21
+ - Update the relevant `state/queues/*.yaml`.
22
+
23
+ ## Decision rules
24
+
25
+ Follow `policies/action-classification.yaml`. When in doubt:
26
+
27
+ - Anything from the principal that asks a question → action-stack
28
+ - Anything with a deadline → upcoming-deadlines + relevant domain queue
29
+ - Anything from a regulator / legal counterparty → legal-obligations + escalate-to-principal
30
+ - Anything that mentions the agent or the principal by name in a multi-party thread → action-stack
31
+
32
+ ## When to invoke
33
+
34
+ - The `inbox-processor` cadence tick (every 5 minutes).
35
+ - Manual: `node scripts/cadence/enqueue-cadence-tick.mjs inbox-processor --source=manual`.
36
+
37
+ ## Outputs
38
+
39
+ Per-tick summary line to `logs/workflows/<date>-inbox-processor.jsonl` with counts of items routed, archived, escalated.
package/bin/maestro.mjs CHANGED
@@ -27,6 +27,7 @@ import { createHash } from "node:crypto";
27
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
28
28
  const MAESTRO_ROOT = resolve(__dirname, "..");
29
29
  const SCAFFOLD_DIR = join(MAESTRO_ROOT, "scaffold");
30
+ const FRAMEWORK_FEATURES_PATH = join(MAESTRO_ROOT, "framework-features.json");
30
31
 
31
32
  // Plist architecture marker — must match the value emitted by
32
33
  // scripts/local-triggers/generate-plists.sh. Used by doctor + upgrade
@@ -1056,7 +1057,7 @@ function parseUpgradeFlags(args) {
1056
1057
  return flags;
1057
1058
  }
1058
1059
 
1059
- function upgrade(args = []) {
1060
+ async function upgrade(args = []) {
1060
1061
  const flags = parseUpgradeFlags(args);
1061
1062
  if (flags === null) {
1062
1063
  console.log(`
@@ -1282,6 +1283,36 @@ Per-file behaviour:
1282
1283
  // upgrade summary so the operator sees both layers of change.
1283
1284
  printCadenceBusSummary(cadenceMigration, flags);
1284
1285
 
1286
+ // Reconcile framework features — run auto-init steps, queue manual ones.
1287
+ if (!flags.dryRun && existsSync(FRAMEWORK_FEATURES_PATH)) {
1288
+ try {
1289
+ const { loadRegistry, reconcileFeatures } = await import("../lib/feature-init.mjs");
1290
+ const registry = loadRegistry(FRAMEWORK_FEATURES_PATH);
1291
+ console.log();
1292
+ log("Reconciling framework features...");
1293
+ const result = reconcileFeatures({
1294
+ maestroRoot: MAESTRO_ROOT,
1295
+ agentRoot: cwd,
1296
+ registry,
1297
+ runAuto: true,
1298
+ logger: (entry) => {
1299
+ if (entry.stage === "completed") ok(`init: ${entry.feature}`);
1300
+ else if (entry.stage === "failed") warn(`init failed: ${entry.feature} — ${entry.message}`);
1301
+ else if (entry.stage === "pending") console.log(` ~ pending: ${entry.feature} (run \`maestro init ${entry.feature} --apply\`)`);
1302
+ },
1303
+ });
1304
+ if (result.pending.length) {
1305
+ warn(`${result.pending.length} feature(s) still pending — run: maestro init`);
1306
+ } else if (result.ranAuto.length) {
1307
+ ok(`${result.ranAuto.length} feature(s) initialised this run`);
1308
+ } else {
1309
+ ok("All framework features up to date");
1310
+ }
1311
+ } catch (e) {
1312
+ warn(`feature reconciliation skipped: ${e.message}`);
1313
+ }
1314
+ }
1315
+
1285
1316
  console.log();
1286
1317
  log("Agent-specific paths (config/, CLAUDE.md, knowledge/, memory/, state/, outputs/, logs/, .env) were NOT touched.");
1287
1318
  }
@@ -1457,8 +1488,128 @@ function doctor() {
1457
1488
 
1458
1489
  // ── Emergency-stop wiring ───────────────────────────────────────────────
1459
1490
  const stopScript = join(cwd, "scripts/emergency-stop.sh");
1460
- if (existsSync(stopScript)) ok("scripts/emergency-stop.sh present");
1461
- else { warn("scripts/emergency-stop.sh missing — kill-switch unavailable"); issues++; }
1491
+ if (existsSync(stopScript)) {
1492
+ const body = readFileSync(stopScript, "utf-8");
1493
+ if (body.includes("$AGENT_DIR/schedules") || body.includes("$SOPHIE_AI_DIR/schedules")) {
1494
+ warn("scripts/emergency-stop.sh still looks in $REPO/schedules/ (legacy path bug). Run: maestro upgrade");
1495
+ issues++;
1496
+ } else if (!body.includes("Library/LaunchAgents")) {
1497
+ warn("scripts/emergency-stop.sh doesn't reference ~/Library/LaunchAgents/ — kill-switch may be ineffective.");
1498
+ issues++;
1499
+ } else {
1500
+ ok("scripts/emergency-stop.sh present (uses ~/Library/LaunchAgents/)");
1501
+ }
1502
+ } else { warn("scripts/emergency-stop.sh missing — kill-switch unavailable"); issues++; }
1503
+
1504
+ // ── Feature-init state ──────────────────────────────────────────────────
1505
+ const featureState = join(cwd, ".maestro/features.json");
1506
+ if (existsSync(featureState)) {
1507
+ try {
1508
+ const s = JSON.parse(readFileSync(featureState, "utf-8"));
1509
+ const pending = (s.pending || []).length;
1510
+ const initialised = Object.keys(s.initialized || {}).length;
1511
+ if (pending > 0) {
1512
+ warn(`${pending} framework feature(s) pending init — run: maestro init`);
1513
+ issues++;
1514
+ } else {
1515
+ ok(`${initialised} framework feature(s) initialised`);
1516
+ }
1517
+ } catch {
1518
+ warn(".maestro/features.json is malformed — run: maestro upgrade");
1519
+ issues++;
1520
+ }
1521
+ } else {
1522
+ warn(".maestro/features.json not found — run: maestro upgrade");
1523
+ issues++;
1524
+ }
1525
+
1526
+ // ── Workflow / agent / skill cross-references ──────────────────────────
1527
+ // Scan workflows/ for `agent:` and `skill:` references and verify each
1528
+ // exists. Catches the "morning-brief references agent: foo that doesn't
1529
+ // exist" class of integration drift.
1530
+ const workflowsDir = join(cwd, "workflows");
1531
+ const agentsDir = join(cwd, "agents");
1532
+ const skillsDir = join(cwd, "plugins/maestro-skills/skills");
1533
+ if (existsSync(workflowsDir)) {
1534
+ const missingAgents = new Set();
1535
+ const missingSkills = new Set();
1536
+ const walkYaml = (dir) => {
1537
+ for (const name of readdirSync(dir)) {
1538
+ const full = join(dir, name);
1539
+ const st = statSync(full);
1540
+ if (st.isDirectory()) walkYaml(full);
1541
+ else if (st.isFile() && /\.ya?ml$/.test(name)) {
1542
+ const body = readFileSync(full, "utf-8");
1543
+ for (const m of body.matchAll(/^\s+agent:\s*([\w-]+)\b/gm)) {
1544
+ const a = m[1];
1545
+ if (a === "agent-self" || a === "agent_self") continue;
1546
+ if (!existsSync(join(agentsDir, a))) missingAgents.add(a);
1547
+ }
1548
+ for (const m of body.matchAll(/^\s+skill:\s*([\w-]+)\b/gm)) {
1549
+ const s = m[1];
1550
+ if (!existsSync(join(skillsDir, `${s}.md`))) missingSkills.add(s);
1551
+ }
1552
+ }
1553
+ }
1554
+ };
1555
+ try { walkYaml(workflowsDir); }
1556
+ catch (err) { warn(`workflow scan failed: ${err.message}`); }
1557
+ if (missingAgents.size === 0 && missingSkills.size === 0) {
1558
+ ok("All workflow agent/skill references resolved");
1559
+ } else {
1560
+ if (missingAgents.size) { warn(`Workflows reference ${missingAgents.size} missing agent(s): ${[...missingAgents].slice(0,5).join(", ")}${missingAgents.size > 5 ? "…" : ""}`); issues++; }
1561
+ if (missingSkills.size) { warn(`Workflows reference ${missingSkills.size} missing skill(s): ${[...missingSkills].slice(0,5).join(", ")}${missingSkills.size > 5 ? "…" : ""}`); issues++; }
1562
+ }
1563
+ }
1564
+
1565
+ // ── Cadence-bus DLQ depth ───────────────────────────────────────────────
1566
+ const dlqDir = join(cwd, "state/cadence-bus/dlq");
1567
+ if (existsSync(dlqDir)) {
1568
+ const dlqFiles = readdirSync(dlqDir).filter((n) => n.endsWith(".json"));
1569
+ if (dlqFiles.length === 0) ok("Cadence-bus DLQ is empty");
1570
+ else if (dlqFiles.length < 5) warn(`Cadence-bus DLQ: ${dlqFiles.length} event(s). Review: state/cadence-bus/dlq/`);
1571
+ else {
1572
+ fail(`Cadence-bus DLQ: ${dlqFiles.length} event(s) — investigate before clearing.`);
1573
+ issues++;
1574
+ }
1575
+ }
1576
+
1577
+ // ── Backup freshness (only when backup is configured) ──────────────────
1578
+ const backupCfg = join(cwd, ".maestro/backup-config.yaml");
1579
+ if (existsSync(backupCfg)) {
1580
+ const cfg = readFileSync(backupCfg, "utf-8");
1581
+ if (/enabled:\s*true/.test(cfg)) {
1582
+ const lastBackup = join(cwd, ".maestro/last-backup.json");
1583
+ if (existsSync(lastBackup)) {
1584
+ try {
1585
+ const lb = JSON.parse(readFileSync(lastBackup, "utf-8"));
1586
+ const age = Date.now() - new Date(lb.completed_at).getTime();
1587
+ if (age < 25 * 60 * 60 * 1000) ok(`Last backup ${Math.round(age / 3600000)}h ago (${lb.provider}://${lb.bucket})`);
1588
+ else { warn(`Last backup ${Math.round(age / 3600000)}h ago — investigate scripts/maintenance/backup-to-cloud.sh`); issues++; }
1589
+ } catch { warn("last-backup.json is malformed"); }
1590
+ } else {
1591
+ warn("Backup enabled but no successful run yet — schedule scripts/maintenance/backup-to-cloud.sh");
1592
+ issues++;
1593
+ }
1594
+ }
1595
+ }
1596
+
1597
+ // ── Cross-agent registry ────────────────────────────────────────────────
1598
+ const knownAgents = join(cwd, "config/known-agents.json");
1599
+ if (existsSync(knownAgents)) {
1600
+ try {
1601
+ const reg = JSON.parse(readFileSync(knownAgents, "utf-8"));
1602
+ const count = Array.isArray(reg.agents) ? reg.agents.length : 0;
1603
+ if (count > 0) ok(`Cross-agent registry: ${count} peer agent(s)`);
1604
+ else warn("config/known-agents.json present but empty — peer-agent detection won't work");
1605
+ } catch {
1606
+ warn("config/known-agents.json is malformed");
1607
+ issues++;
1608
+ }
1609
+ } else {
1610
+ warn("config/known-agents.json missing — run: maestro init known-agents-registry --apply");
1611
+ issues++;
1612
+ }
1462
1613
 
1463
1614
  console.log();
1464
1615
  if (issues === 0) ok("All checks passed.");
@@ -1466,6 +1617,138 @@ function doctor() {
1466
1617
  process.exitCode = issues === 0 ? 0 : 1;
1467
1618
  }
1468
1619
 
1620
+ // ---------------------------------------------------------------------------
1621
+ // INIT — run pending feature-init steps
1622
+ // ---------------------------------------------------------------------------
1623
+
1624
+ async function initCmd(args) {
1625
+ const cwd = process.cwd();
1626
+ const help = args.includes("--help") || args.includes("-h");
1627
+ const apply = args.includes("--apply") || args.includes("-y") || args.includes("--yes");
1628
+ const featureArg = args.find((a) => !a.startsWith("-"));
1629
+
1630
+ if (help) {
1631
+ console.log(`
1632
+ Usage: maestro init [feature-name] [--apply]
1633
+
1634
+ Reconciles this agent's .maestro/features.json against the framework
1635
+ feature registry (framework-features.json shipped with @adaptic/maestro).
1636
+ Auto-runnable features are executed directly; features requiring user
1637
+ input are listed and can be opted into individually with their name as
1638
+ the first argument.
1639
+
1640
+ maestro init list pending features (dry-run)
1641
+ maestro init --apply run every auto-runnable pending step
1642
+ maestro init <feature> --apply run only the named feature's init step
1643
+ maestro init --help this help
1644
+
1645
+ After init, run \`maestro doctor\` to confirm everything is in place.
1646
+ `);
1647
+ return;
1648
+ }
1649
+
1650
+ const { loadRegistry, loadAgentState, reconcileFeatures, listPending, markInitialised, diffFeatures } = await import("../lib/feature-init.mjs");
1651
+ let registry;
1652
+ try { registry = loadRegistry(FRAMEWORK_FEATURES_PATH); }
1653
+ catch (err) { fail(err.message); process.exit(1); }
1654
+
1655
+ if (featureArg) {
1656
+ // Targeted init — run just one feature's command.
1657
+ const def = registry.features?.[featureArg];
1658
+ if (!def) { fail(`unknown feature: ${featureArg}`); process.exit(1); }
1659
+ log(`Running init for ${featureArg}: ${def.init?.description || ""}`);
1660
+ if (!apply) { warn("dry-run — pass --apply to actually run."); return; }
1661
+ const r = await runFeatureInit({ name: featureArg, def, cwd });
1662
+ if (r.ok) {
1663
+ markInitialised(cwd, featureArg, def.version);
1664
+ ok(`initialised: ${featureArg} v${def.version}`);
1665
+ } else {
1666
+ fail(`init failed: ${r.error}`);
1667
+ process.exit(1);
1668
+ }
1669
+ return;
1670
+ }
1671
+
1672
+ // Reconcile + list pending.
1673
+ const state = loadAgentState(cwd);
1674
+ const diff = diffFeatures(registry, state);
1675
+ if (diff.length === 0 && (state.pending || []).length === 0) {
1676
+ ok("All framework features are initialised.");
1677
+ return;
1678
+ }
1679
+
1680
+ // Show what's pending before doing anything.
1681
+ if (!apply) {
1682
+ log(`${diff.length} feature(s) need initialisation:`);
1683
+ for (const item of diff) {
1684
+ const auto = item.definition.init?.auto;
1685
+ console.log(` ${auto ? "•" : "?"} ${item.definition.title || item.name} (v${item.version}) — ${auto ? "auto" : "needs --apply"}`);
1686
+ if (item.definition.init?.description) console.log(` ${item.definition.init.description}`);
1687
+ }
1688
+ console.log();
1689
+ log("Run `maestro init --apply` to execute every auto-runnable step.");
1690
+ return;
1691
+ }
1692
+
1693
+ // --apply: run auto features now.
1694
+ log("Running auto-init steps...");
1695
+ const result = reconcileFeatures({
1696
+ maestroRoot: MAESTRO_ROOT,
1697
+ agentRoot: cwd,
1698
+ registry,
1699
+ runAuto: true,
1700
+ logger: (entry) => {
1701
+ const sym = entry.stage === "completed" ? `${C.green}✓${C.reset}` : entry.stage === "failed" ? `${C.red}✗${C.reset}` : entry.stage === "pending" ? `${C.yellow}~${C.reset}` : `${C.cyan}…${C.reset}`;
1702
+ console.log(` ${sym} ${entry.feature}${entry.message ? ` — ${entry.message}` : ""}`);
1703
+ },
1704
+ });
1705
+
1706
+ console.log();
1707
+ if (result.ranAuto.length) ok(`${result.ranAuto.length} feature(s) initialised: ${result.ranAuto.join(", ")}`);
1708
+ if (result.pending.length) warn(`${result.pending.length} feature(s) still pending (need manual decision): ${result.pending.join(", ")}`);
1709
+ if (result.failed.length) {
1710
+ fail(`${result.failed.length} feature(s) failed during init:`);
1711
+ for (const f of result.failed) console.log(` ✗ ${f.name}: ${f.error}`);
1712
+ process.exit(1);
1713
+ }
1714
+ if (result.pending.length) {
1715
+ console.log();
1716
+ const pendingList = listPending(cwd);
1717
+ for (const p of pendingList) {
1718
+ console.log(` To initialise ${p.feature}: maestro init ${p.feature} --apply`);
1719
+ }
1720
+ }
1721
+ }
1722
+
1723
+ async function runFeatureInit({ name, def, cwd }) {
1724
+ const { execFileSync } = await import("node:child_process");
1725
+ const cmd = def.init?.command;
1726
+ if (!cmd || cmd === "true") return { ok: true };
1727
+ const parts = cmd.split(/\s+/);
1728
+ try {
1729
+ if (parts[0] === "node" && parts[1]) {
1730
+ const script = join(cwd, parts[1]);
1731
+ const fallback = join(MAESTRO_ROOT, parts[1]);
1732
+ const scriptPath = existsSync(script) ? script : (existsSync(fallback) ? fallback : null);
1733
+ if (!scriptPath) return { ok: false, error: `script not found: ${parts[1]}` };
1734
+ execFileSync(process.execPath, [scriptPath, ...parts.slice(2)], {
1735
+ cwd,
1736
+ env: { ...process.env, AGENT_ROOT: cwd, AGENT_DIR: cwd, MAESTRO_ROOT },
1737
+ stdio: "inherit",
1738
+ });
1739
+ return { ok: true };
1740
+ }
1741
+ execFileSync("/bin/bash", ["-lc", cmd], {
1742
+ cwd,
1743
+ env: { ...process.env, AGENT_ROOT: cwd, AGENT_DIR: cwd, MAESTRO_ROOT },
1744
+ stdio: "inherit",
1745
+ });
1746
+ return { ok: true };
1747
+ } catch (err) {
1748
+ return { ok: false, error: err.message };
1749
+ }
1750
+ }
1751
+
1469
1752
  // ---------------------------------------------------------------------------
1470
1753
  // MAIN
1471
1754
  // ---------------------------------------------------------------------------
@@ -1474,8 +1757,12 @@ const [, , command, ...args] = process.argv;
1474
1757
 
1475
1758
  switch (command) {
1476
1759
  case "create": create(args[0]); break;
1477
- case "upgrade": upgrade(args); break;
1760
+ case "upgrade": await upgrade(args); break;
1478
1761
  case "doctor": doctor(); break;
1762
+ case "init":
1763
+ case "update-init":
1764
+ case "init-update":
1765
+ await initCmd(args); break;
1479
1766
  case "--help": case "-h": case undefined:
1480
1767
  console.log(`
1481
1768
  Maestro — Autonomous AI Agent Operating System
@@ -1483,6 +1770,7 @@ Maestro — Autonomous AI Agent Operating System
1483
1770
  Usage:
1484
1771
  npx @adaptic/maestro create <dirname> Create a new agent repo
1485
1772
  npx @adaptic/maestro upgrade [--dry-run] Update framework files
1773
+ npx @adaptic/maestro init [feature] [--apply] Run pending feature-init steps
1486
1774
  npx @adaptic/maestro doctor Verify installation
1487
1775
 
1488
1776
  Upgrade flags:
@@ -1491,10 +1779,20 @@ Upgrade flags:
1491
1779
  --no-incoming Don't write .maestro/incoming/ shadows
1492
1780
  --verbose, -v List classification for every file
1493
1781
 
1782
+ Init flags:
1783
+ (no args) List pending features (dry-run)
1784
+ --apply, -y Run every auto-runnable pending step
1785
+ <feature> --apply Run only the named feature's init step
1786
+
1494
1787
  Workflow:
1495
1788
  1. npx @adaptic/maestro create my-agent
1496
1789
  2. cd my-agent
1497
1790
  3. claude "/init-maestro"
1791
+
1792
+ Upgrade workflow (existing agents):
1793
+ 1. npm update @adaptic/maestro
1794
+ 2. npm run upgrade (runs maestro upgrade + auto-init)
1795
+ 3. maestro init (run any deferred init steps)
1498
1796
  `);
1499
1797
  break;
1500
1798
  default:
@@ -0,0 +1,107 @@
1
+ {
2
+ "$schema": "https://adaptic.ai/schemas/maestro/framework-features.v1.json",
3
+ "schema_version": "1",
4
+ "_doc": "Versioned registry of framework features. Each entry pairs a feature name with its current version, the maestro version that introduced (or last revved) it, and a per-feature init descriptor. `maestro upgrade` reads this file, compares against the agent's .maestro/features.json, and runs `init.command` automatically for features marked `auto: true`. Features with `auto: false` land on the agent's pending-init queue and are surfaced by the SessionStart banner + completed via `maestro init`.",
5
+ "features": {
6
+ "cadence-bus": {
7
+ "version": "1",
8
+ "since": "1.8.0",
9
+ "title": "Cadence bus (persistent main session + lightweight launchd enqueue)",
10
+ "init": {
11
+ "auto": true,
12
+ "command": "node scripts/setup/init-cadence-bus.mjs",
13
+ "description": "Create state/cadence-bus/{inbox,claimed,processed,failed,dlq} + logs/cadence-bus/. Verify daemon plist uses cadence-bus arch."
14
+ }
15
+ },
16
+ "known-agents-registry": {
17
+ "version": "1",
18
+ "since": "1.9.0",
19
+ "title": "Cross-agent registry (peer agent recognition)",
20
+ "init": {
21
+ "auto": true,
22
+ "command": "node scripts/setup/init-known-agents.mjs",
23
+ "description": "Scaffold config/known-agents.json with the Adaptic agent roster so classifier.mjs can detect peer @mentions and avoid responding to messages routed at another agent."
24
+ }
25
+ },
26
+ "memory-executive-scaffold": {
27
+ "version": "1",
28
+ "since": "1.9.0",
29
+ "title": "Executive memory directory structure",
30
+ "init": {
31
+ "auto": true,
32
+ "command": "node scripts/setup/init-memory-executive.mjs",
33
+ "description": "Create memory/executive/ and memory/workstreams/ subdirectories with seed files. Referenced by agents/ceo-briefing/ and skill prompts."
34
+ }
35
+ },
36
+ "inputs-overnight-scaffold": {
37
+ "version": "1",
38
+ "since": "1.9.0",
39
+ "title": "Overnight inputs directory",
40
+ "init": {
41
+ "auto": true,
42
+ "command": "mkdir -p inputs/overnight && [ ! -f inputs/overnight/.gitkeep ] && touch inputs/overnight/.gitkeep || true",
43
+ "description": "Create inputs/overnight/ referenced by morning-brief skill."
44
+ }
45
+ },
46
+ "emergency-stop-paths": {
47
+ "version": "2",
48
+ "since": "1.9.0",
49
+ "title": "Emergency-stop / resume plist path fix",
50
+ "init": {
51
+ "auto": true,
52
+ "command": "true",
53
+ "description": "Cosmetic version bump — emergency-stop.sh and resume-operations.sh now look in ~/Library/LaunchAgents/ instead of $REPO/schedules/. No agent-side init needed; just receive the updated scripts."
54
+ }
55
+ },
56
+ "rag-foundation": {
57
+ "version": "1",
58
+ "since": "1.9.0",
59
+ "title": "RAG foundation (repo-registry + local index + retrieval helpers)",
60
+ "init": {
61
+ "auto": false,
62
+ "command": "node scripts/setup/init-rag.mjs",
63
+ "description": "Discover ~/* repos likely to be relevant to this agent (asks for selection), populate config/repo-registry.yaml, and seed state/rag/. Local sqlite-fts5 index by default. Requires interactive input — does not run automatically."
64
+ }
65
+ },
66
+ "decision-capture": {
67
+ "version": "1",
68
+ "since": "1.9.0",
69
+ "title": "Decision capture automation",
70
+ "init": {
71
+ "auto": true,
72
+ "command": "node scripts/setup/init-decision-capture.mjs",
73
+ "description": "Wire knowledge/decisions/ decision-log automation. Post-action hook detects decision-shaped tool calls and surfaces capture prompts. Schema-validated via knowledge/decisions/decision-schema.yaml."
74
+ }
75
+ },
76
+ "cost-tracking": {
77
+ "version": "1",
78
+ "since": "1.9.0",
79
+ "title": "Cost tracking + quota awareness",
80
+ "init": {
81
+ "auto": true,
82
+ "command": "node scripts/setup/init-cost-tracking.mjs",
83
+ "description": "Set up state/cost-tracking/, scripts/cost/track-claude-usage.mjs, and daily state/dashboards/cost-summary.yaml. Surfaces burn rate in doctor."
84
+ }
85
+ },
86
+ "session-router": {
87
+ "version": "1",
88
+ "since": "1.9.0",
89
+ "title": "Session router (LRU reuse of long-running Claude sessions)",
90
+ "init": {
91
+ "auto": false,
92
+ "command": "node scripts/setup/init-session-router.mjs",
93
+ "description": "Enable session-router for backlog / inbox processing. Opt-in: requires SESSION_ROUTER_ENABLED=1 in .env. Init wizard helps configure registry path + LRU caps."
94
+ }
95
+ },
96
+ "backup-replication": {
97
+ "version": "1",
98
+ "since": "1.9.0",
99
+ "title": "Off-machine state backup",
100
+ "init": {
101
+ "auto": false,
102
+ "command": "node scripts/setup/init-backup.mjs",
103
+ "description": "Configure off-machine backup of state/, knowledge/, outputs/, and rotated logs to GCS or S3. Requires bucket name + credentials. Doctor flags if last successful backup is >24h old once configured."
104
+ }
105
+ }
106
+ }
107
+ }