@adaptic/maestro 1.8.4 → 1.9.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/.claude/settings.json +11 -0
- package/agents/engineering-oversight/agent.md +44 -0
- package/agents/github-operator/agent.md +38 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/bin/maestro.mjs +302 -4
- package/framework-features.json +107 -0
- package/lib/feature-init.mjs +297 -0
- package/package.json +5 -2
- package/scaffold/config/known-agents.json +57 -8
- package/scripts/cost/track-claude-usage.mjs +154 -0
- package/scripts/daemon/cadence-consumer.mjs +73 -2
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/emergency-stop.sh +56 -19
- package/scripts/hooks/session-start-banner.sh +79 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/resume-operations.sh +50 -13
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-rag.mjs +103 -0
- package/scripts/setup/init-session-router.mjs +38 -0
package/.claude/settings.json
CHANGED
|
@@ -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))
|
|
1461
|
-
|
|
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
|
+
}
|