@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.
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-backup.mjs — Off-machine state backup wizard.
4
+ *
5
+ * Configures `.maestro/backup-config.yaml` with bucket details and seeds
6
+ * a daily launchd plist that runs scripts/maintenance/backup-to-cloud.sh.
7
+ * Requires interactive input — does NOT auto-run.
8
+ */
9
+ import { existsSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { join, resolve, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
15
+
16
+ const ok = (m) => process.stdout.write(`[init-backup] ✓ ${m}\n`);
17
+ const warn = (m) => process.stdout.write(`[init-backup] ⚠ ${m}\n`);
18
+
19
+ mkdirSync(join(AGENT_DIR, ".maestro"), { recursive: true });
20
+ const cfg = join(AGENT_DIR, ".maestro/backup-config.yaml");
21
+ if (existsSync(cfg)) {
22
+ ok("backup-config.yaml already present");
23
+ process.exit(0);
24
+ }
25
+
26
+ writeFileSync(cfg, `# Off-machine state backup configuration
27
+ # Fill in the bucket details, then run:
28
+ # maestro init backup-replication --apply
29
+ #
30
+ # Backups cover (configurable below):
31
+ # - state/ (queues, dashboards, inboxes — minus state/tmp/)
32
+ # - knowledge/
33
+ # - outputs/
34
+ # - logs/ (rotated archives only — current-day logs excluded)
35
+ # - config/agent.json (identity SOT)
36
+
37
+ enabled: false
38
+ provider: gcs # gcs | s3 | rsync
39
+ bucket: "" # e.g. adaptic-maestro-backups
40
+ prefix: agent-name-here # e.g. ravi-ai (typically the repoSlug)
41
+ schedule: "0 3 * * *" # daily 03:00 local (overrides via launchd plist)
42
+ include:
43
+ - state
44
+ - knowledge
45
+ - outputs
46
+ - logs
47
+ - config/agent.json
48
+ exclude:
49
+ - state/tmp
50
+ - state/rag/index/*.bin
51
+ retention_days: 30
52
+ `);
53
+ ok(`wrote ${cfg}`);
54
+ warn("Backup is configured but NOT enabled. Edit .maestro/backup-config.yaml + set enabled: true, then re-run init.");
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-cadence-bus.mjs — Idempotent init step for the cadence-bus feature.
4
+ *
5
+ * Called by `maestro init` / `maestro upgrade` (via the feature-init
6
+ * tracker). Safe to run repeatedly. Verifies state/cadence-bus/{inbox,
7
+ * claimed,processed,failed,dlq} exist + logs/cadence-bus/ exists +
8
+ * generated launchd plists are on the cadence-bus arch.
9
+ */
10
+ import { dirname, join, resolve } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
16
+
17
+ const ok = (m) => process.stdout.write(`[init-cadence-bus] ✓ ${m}\n`);
18
+ const warn = (m) => process.stdout.write(`[init-cadence-bus] ⚠ ${m}\n`);
19
+
20
+ // Directory tree (mirror of cadence-bus.mjs's bootstrap).
21
+ for (const d of [
22
+ "state/cadence-bus/inbox",
23
+ "state/cadence-bus/claimed",
24
+ "state/cadence-bus/processed",
25
+ "state/cadence-bus/failed",
26
+ "state/cadence-bus/dlq",
27
+ "logs/cadence-bus",
28
+ ]) {
29
+ const full = join(AGENT_DIR, d);
30
+ if (!existsSync(full)) {
31
+ mkdirSync(full, { recursive: true });
32
+ ok(`created ${d}`);
33
+ }
34
+ }
35
+
36
+ // Version marker file.
37
+ const versionFile = join(AGENT_DIR, "state/cadence-bus/VERSION");
38
+ if (!existsSync(versionFile)) {
39
+ writeFileSync(versionFile, "1\n");
40
+ ok("seeded state/cadence-bus/VERSION");
41
+ }
42
+
43
+ // Plist arch verification — flag (don't fix here, that's upgrade's job).
44
+ const plistDir = join(AGENT_DIR, "scripts/local-triggers/plists");
45
+ if (existsSync(plistDir)) {
46
+ let legacy = 0;
47
+ for (const name of readdirSync(plistDir)) {
48
+ if (!name.endsWith(".plist")) continue;
49
+ const body = readFileSync(join(plistDir, name), "utf-8");
50
+ if (body.includes("run-trigger.sh")) legacy++;
51
+ }
52
+ if (legacy > 0) {
53
+ warn(`${legacy} generated plist(s) still use the legacy spawn-per-tick pattern.`);
54
+ warn(" Run: npx @adaptic/maestro upgrade (will back up + regenerate)");
55
+ } else {
56
+ ok("plist arch verified");
57
+ }
58
+ }
59
+
60
+ ok("done");
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-cost-tracking.mjs — Set up local cost tracking ledger + dashboard.
4
+ *
5
+ * State:
6
+ * state/cost-tracking/<date>.jsonl — per-day token ledger
7
+ * state/dashboards/cost-summary.yaml — rollup
8
+ *
9
+ * The actual tracker is scripts/cost/track-claude-usage.mjs (shipped by the
10
+ * framework) which is wired into the cadence-consumer's session-spawn path.
11
+ */
12
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
13
+ import { join, resolve, dirname } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
18
+
19
+ const ok = (m) => process.stdout.write(`[init-cost-tracking] ✓ ${m}\n`);
20
+
21
+ for (const d of ["state/cost-tracking", "state/dashboards"]) {
22
+ const full = join(AGENT_DIR, d);
23
+ if (!existsSync(full)) {
24
+ mkdirSync(full, { recursive: true });
25
+ ok(`created ${d}`);
26
+ }
27
+ }
28
+
29
+ const summary = join(AGENT_DIR, "state/dashboards/cost-summary.yaml");
30
+ if (!existsSync(summary)) {
31
+ writeFileSync(summary, `# Cost summary — auto-regenerated by scripts/cost/track-claude-usage.mjs
32
+ generated: null
33
+ window_days: 7
34
+ totals:
35
+ sessions: 0
36
+ input_tokens: 0
37
+ output_tokens: 0
38
+ estimated_usd: 0
39
+ by_model: {}
40
+ by_cadence: {}
41
+ `);
42
+ ok("seeded cost-summary.yaml");
43
+ }
44
+
45
+ ok("done");
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-decision-capture.mjs — Wire decision-capture automation.
4
+ *
5
+ * Ensures knowledge/decisions/{decision-schema,index}.yaml exist and
6
+ * creates scripts/decisions/capture-decision.mjs locally (the helper
7
+ * that the agent invokes when recording a decision).
8
+ */
9
+ import { existsSync, mkdirSync, writeFileSync, copyFileSync } from "node:fs";
10
+ import { join, resolve, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
15
+ const MAESTRO_ROOT = process.env.MAESTRO_ROOT || resolve(__dirname, "..", "..");
16
+
17
+ const ok = (m) => process.stdout.write(`[init-decision-capture] ✓ ${m}\n`);
18
+
19
+ for (const d of ["knowledge/decisions", "knowledge/decisions/archive", "scripts/decisions"]) {
20
+ const full = join(AGENT_DIR, d);
21
+ if (!existsSync(full)) {
22
+ mkdirSync(full, { recursive: true });
23
+ ok(`created ${d}`);
24
+ }
25
+ }
26
+
27
+ // Copy the framework's capture-decision helper if present.
28
+ const src = join(MAESTRO_ROOT, "scripts/decisions/capture-decision.mjs");
29
+ const dst = join(AGENT_DIR, "scripts/decisions/capture-decision.mjs");
30
+ if (existsSync(src) && !existsSync(dst)) {
31
+ copyFileSync(src, dst);
32
+ ok(`copied capture-decision.mjs`);
33
+ }
34
+
35
+ // Seed index.yaml + schema if missing.
36
+ const schemaPath = join(AGENT_DIR, "knowledge/decisions/decision-schema.yaml");
37
+ if (!existsSync(schemaPath)) {
38
+ writeFileSync(schemaPath, `# Decision Record Schema (Maestro decision-capture v1)
39
+ # Each decision is a YAML doc named DEC-YYYY-MM-DD-NNN.yaml under knowledge/decisions/.
40
+
41
+ schema:
42
+ version: "1"
43
+ fields:
44
+ id: { type: string, format: "DEC-YYYY-MM-DD-NNN", required: true }
45
+ date: { type: date, format: "YYYY-MM-DD", required: true }
46
+ title: { type: string, required: true }
47
+ domain: { type: string, required: true }
48
+ decision_maker: { type: string, required: true }
49
+ decision_text: { type: string, required: true }
50
+ context: { type: string, required: true }
51
+ rationale: { type: string, required: true }
52
+ alternatives: { type: array, required: false }
53
+ stakeholders: { type: array, required: false }
54
+ expires_at: { type: date, required: false }
55
+ status: { type: string, enum: [active, superseded, reversed, completed], default: active }
56
+ `);
57
+ ok("seeded decision-schema.yaml");
58
+ }
59
+
60
+ const indexPath = join(AGENT_DIR, "knowledge/decisions/index.yaml");
61
+ if (!existsSync(indexPath)) {
62
+ writeFileSync(indexPath, "# Decision Index — auto-maintained by capture-decision.mjs\ndecisions: []\n");
63
+ ok("seeded decision index");
64
+ }
65
+
66
+ ok("done");
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-known-agents.mjs — Scaffold config/known-agents.json
4
+ *
5
+ * The Adaptic agent roster is shipped as a starter; agents that have
6
+ * already populated their own known-agents.json keep theirs (this init
7
+ * step is idempotent and never overwrites).
8
+ *
9
+ * classifier.mjs reads this file at runtime to detect peer @mentions in
10
+ * Slack/Gmail so an agent doesn't respond to messages routed at another
11
+ * agent.
12
+ */
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { join, dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
19
+
20
+ const ok = (m) => process.stdout.write(`[init-known-agents] ✓ ${m}\n`);
21
+
22
+ const TARGET = join(AGENT_DIR, "config", "known-agents.json");
23
+ const MAESTRO_ROOT = process.env.MAESTRO_ROOT || resolve(__dirname, "..", "..");
24
+ const SCAFFOLD = join(MAESTRO_ROOT, "scaffold", "config", "known-agents.json");
25
+
26
+ mkdirSync(dirname(TARGET), { recursive: true });
27
+
28
+ if (existsSync(TARGET)) {
29
+ // Already present — verify shape and exit. We don't merge upstream
30
+ // additions into existing files to avoid surprising the operator.
31
+ try {
32
+ const current = JSON.parse(readFileSync(TARGET, "utf-8"));
33
+ const count = Array.isArray(current.agents) ? current.agents.length : 0;
34
+ ok(`already present (${count} agents)`);
35
+ process.exit(0);
36
+ } catch (err) {
37
+ process.stderr.write(`[init-known-agents] existing file is invalid JSON: ${err.message}\n`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+
42
+ // Copy from scaffold (the framework's canonical roster); fall back to a
43
+ // minimal stub if the scaffold is missing.
44
+ let payload;
45
+ if (existsSync(SCAFFOLD)) {
46
+ payload = readFileSync(SCAFFOLD, "utf-8");
47
+ ok(`copied from ${SCAFFOLD}`);
48
+ } else {
49
+ payload = JSON.stringify({
50
+ schema_version: "1",
51
+ agents: [],
52
+ }, null, 2) + "\n";
53
+ ok("scaffold not found — wrote empty roster");
54
+ }
55
+
56
+ writeFileSync(TARGET, payload);
57
+ ok(`wrote ${TARGET}`);
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-memory-executive.mjs — Create memory/executive/ + memory/workstreams/
4
+ * Referenced by agents/ceo-briefing/agent.md and several skill prompts.
5
+ *
6
+ * Seeds priorities.md and open-loops.md so morning-brief / evening-wrap
7
+ * don't fail on a missing file.
8
+ */
9
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
10
+ import { join, resolve, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
15
+
16
+ const ok = (m) => process.stdout.write(`[init-memory-executive] ✓ ${m}\n`);
17
+
18
+ const DIRS = [
19
+ "memory/executive",
20
+ "memory/workstreams",
21
+ ];
22
+
23
+ for (const d of DIRS) {
24
+ const full = join(AGENT_DIR, d);
25
+ if (!existsSync(full)) {
26
+ mkdirSync(full, { recursive: true });
27
+ ok(`created ${d}`);
28
+ }
29
+ }
30
+
31
+ const seeds = {
32
+ "memory/executive/priorities.md": "# Executive Priorities\n\nCurrent strategic priorities maintained by the agent. Updated weekly.\n\n_Populate via /init-maestro or by the weekly-priorities cadence._\n",
33
+ "memory/executive/open-loops.md": "# Open Loops\n\nOutstanding commitments awaiting closure. Updated by inbox-processor and evening-wrap cadences.\n\n_Format: each loop has owner, deadline, blocker, last-touched._\n",
34
+ "memory/workstreams/README.md": "# Workstreams\n\nOne markdown file per active workstream. Updated by the relevant cadence (weekly-execution, weekly-engineering-health, etc.) to reflect current status, blockers, and next milestones.\n",
35
+ };
36
+
37
+ for (const [path, body] of Object.entries(seeds)) {
38
+ const full = join(AGENT_DIR, path);
39
+ if (!existsSync(full)) {
40
+ writeFileSync(full, body);
41
+ ok(`seeded ${path}`);
42
+ }
43
+ }
44
+
45
+ ok("done");
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-rag.mjs — Bootstrap the local RAG index.
4
+ *
5
+ * Auto-discovers candidate repos under ~/ and writes them to
6
+ * config/repo-registry.yaml. By default this script is *not* auto-run
7
+ * because picking the right repos requires operator judgement. When run
8
+ * interactively, it lists candidates and asks for confirmation; when run
9
+ * with --yes it picks all ~/{repo}-ai siblings + adapticai/* clones.
10
+ */
11
+ import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync, readFileSync } from "node:fs";
12
+ import { join, resolve, dirname } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
18
+
19
+ const ok = (m) => process.stdout.write(`[init-rag] ✓ ${m}\n`);
20
+ const warn = (m) => process.stdout.write(`[init-rag] ⚠ ${m}\n`);
21
+
22
+ const args = new Set(process.argv.slice(2));
23
+ const autoYes = args.has("--yes") || args.has("-y");
24
+
25
+ for (const d of ["state/rag", "state/rag/index", "scripts/rag"]) {
26
+ const full = join(AGENT_DIR, d);
27
+ if (!existsSync(full)) {
28
+ mkdirSync(full, { recursive: true });
29
+ ok(`created ${d}`);
30
+ }
31
+ }
32
+
33
+ // Discover candidate repos under $HOME.
34
+ function discoverRepos() {
35
+ const home = homedir();
36
+ const candidates = [];
37
+ for (const name of readdirSync(home)) {
38
+ if (name.startsWith(".") || name === "ravi-ai" || name === "Library" || name === "Applications") continue;
39
+ const full = join(home, name);
40
+ try {
41
+ const st = statSync(full);
42
+ if (!st.isDirectory()) continue;
43
+ // Heuristic: looks like a code repo (.git or package.json or pyproject.toml).
44
+ const isRepo =
45
+ existsSync(join(full, ".git")) ||
46
+ existsSync(join(full, "package.json")) ||
47
+ existsSync(join(full, "pyproject.toml")) ||
48
+ existsSync(join(full, "go.mod")) ||
49
+ existsSync(join(full, "Cargo.toml"));
50
+ if (isRepo) {
51
+ candidates.push({ name, path: full });
52
+ }
53
+ } catch { /* ignore */ }
54
+ }
55
+ return candidates;
56
+ }
57
+
58
+ const candidates = discoverRepos();
59
+ ok(`discovered ${candidates.length} candidate repo(s) under ${homedir()}`);
60
+
61
+ const registryPath = join(AGENT_DIR, "config/repo-registry.yaml");
62
+ let registry = { repositories: [] };
63
+ if (existsSync(registryPath)) {
64
+ // Crude YAML re-read: just check whether `repositories: []` is empty.
65
+ const body = readFileSync(registryPath, "utf-8");
66
+ if (/repositories:\s*\[\s*\]/.test(body) || /repositories:\s*$/m.test(body)) {
67
+ // empty list — overwrite with discovered set if user opted in.
68
+ } else {
69
+ ok("config/repo-registry.yaml already populated — leaving as-is");
70
+ process.exit(0);
71
+ }
72
+ }
73
+
74
+ if (!autoYes) {
75
+ warn("Not running interactively — to populate, run with --yes:");
76
+ warn(" node scripts/setup/init-rag.mjs --yes");
77
+ warn("Candidates that would be added:");
78
+ for (const c of candidates) warn(` - ${c.name} (${c.path})`);
79
+ process.exit(0);
80
+ }
81
+
82
+ // Build the YAML.
83
+ const lines = [
84
+ "# Source Repository Registry",
85
+ "# Populated by scripts/setup/init-rag.mjs. Add/remove entries as needed.",
86
+ "# Format:",
87
+ "# - name: repo-slug",
88
+ "# path: ~/repo-slug",
89
+ "# purpose: what this repo contains",
90
+ "# index: true | false (indexed by state/rag/)",
91
+ "",
92
+ "repositories:",
93
+ ];
94
+ for (const c of candidates) {
95
+ lines.push(` - name: ${c.name}`);
96
+ lines.push(` path: ${c.path}`);
97
+ lines.push(` purpose: ""`);
98
+ lines.push(` index: true`);
99
+ }
100
+ writeFileSync(registryPath, lines.join("\n") + "\n");
101
+ ok(`wrote ${candidates.length} entries to config/repo-registry.yaml`);
102
+
103
+ ok("done");
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-session-router.mjs — Opt-in session router enablement.
4
+ *
5
+ * Session router enables LRU reuse of long-running Claude sessions for
6
+ * conversational items (Slack threads, Gmail threads). Requires
7
+ * SESSION_ROUTER_ENABLED=1 in .env. This init step appends that line if
8
+ * absent, but is intentionally `auto: false` in the registry so the
9
+ * operator confirms the decision.
10
+ */
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
12
+ import { join, resolve, dirname } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
17
+
18
+ const ok = (m) => process.stdout.write(`[init-session-router] ✓ ${m}\n`);
19
+ const warn = (m) => process.stdout.write(`[init-session-router] ⚠ ${m}\n`);
20
+
21
+ const envFile = join(AGENT_DIR, ".env");
22
+ if (!existsSync(envFile)) {
23
+ warn(".env not present — create it first (copy .env.example).");
24
+ process.exit(0);
25
+ }
26
+
27
+ const body = readFileSync(envFile, "utf-8");
28
+ if (/^SESSION_ROUTER_ENABLED=/m.test(body)) {
29
+ ok("SESSION_ROUTER_ENABLED already configured");
30
+ } else {
31
+ const updated = body.replace(/\n*$/, "") + `\n\n# Session router (LRU reuse) — disabled by default. Set to 1 to enable.\nSESSION_ROUTER_ENABLED=1\n`;
32
+ writeFileSync(envFile, updated);
33
+ ok("appended SESSION_ROUTER_ENABLED=1 to .env");
34
+ }
35
+
36
+ // Touch the registry dir.
37
+ mkdirSync(join(AGENT_DIR, "state/sessions"), { recursive: true });
38
+ ok("done");