@cleocode/cleo 2026.4.18 → 2026.4.19

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/dist/cli/index.js CHANGED
@@ -11114,13 +11114,54 @@ function reconcileJournal(nativeDb, migrationsFolder, existenceTable, logSubsyst
11114
11114
  }
11115
11115
  }
11116
11116
  }
11117
+ if (tableExists(nativeDb, "__drizzle_migrations") && tableExists(nativeDb, existenceTable)) {
11118
+ const localMigrations = readMigrationFiles({ migrationsFolder });
11119
+ const journalEntries = nativeDb.prepare('SELECT hash FROM "__drizzle_migrations"').all();
11120
+ const journaledHashes = new Set(journalEntries.map((e) => e.hash));
11121
+ for (const migration of localMigrations) {
11122
+ if (journaledHashes.has(migration.hash)) continue;
11123
+ const alterColumnRegex = /ALTER\s+TABLE\s+[`"]?(\w+)[`"]?\s+ADD\s+COLUMN\s+[`"]?(\w+)[`"]?/gi;
11124
+ const alterMatches = [];
11125
+ const sqlStatements = Array.isArray(migration.sql) ? migration.sql : [migration.sql ?? ""];
11126
+ const fullSql = sqlStatements.join("\n");
11127
+ for (const m2 of fullSql.matchAll(alterColumnRegex)) {
11128
+ alterMatches.push({ table: m2[1], column: m2[2] });
11129
+ }
11130
+ if (alterMatches.length === 0) continue;
11131
+ const allColumnsExist = alterMatches.every(({ table, column }) => {
11132
+ if (!tableExists(nativeDb, table)) return false;
11133
+ const cols = nativeDb.prepare(`PRAGMA table_info(${table})`).all();
11134
+ return cols.some((c) => c.name === column);
11135
+ });
11136
+ if (allColumnsExist) {
11137
+ const log11 = getLogger(logSubsystem);
11138
+ log11.warn(
11139
+ { migration: migration.name, columns: alterMatches },
11140
+ `Detected partially-applied migration ${migration.name} \u2014 columns exist but journal entry missing. Auto-reconciling.`
11141
+ );
11142
+ nativeDb.exec(
11143
+ `INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES ('${migration.hash}', ${migration.folderMillis})`
11144
+ );
11145
+ }
11146
+ }
11147
+ }
11148
+ }
11149
+ function isDuplicateColumnError(err) {
11150
+ if (!(err instanceof Error)) return false;
11151
+ return /duplicate column name/i.test(err.message);
11117
11152
  }
11118
- function migrateWithRetry(db, migrationsFolder) {
11153
+ function migrateWithRetry(db, migrationsFolder, nativeDb, existenceTable, logSubsystem) {
11154
+ let duplicateColumnReconciled = false;
11119
11155
  for (let attempt = 1; attempt <= MAX_MIGRATION_RETRIES; attempt++) {
11120
11156
  try {
11121
11157
  migrate(db, { migrationsFolder });
11122
11158
  return;
11123
11159
  } catch (err) {
11160
+ if (isDuplicateColumnError(err) && !duplicateColumnReconciled && nativeDb !== void 0 && existenceTable !== void 0 && logSubsystem !== void 0) {
11161
+ duplicateColumnReconciled = true;
11162
+ reconcileJournal(nativeDb, migrationsFolder, existenceTable, logSubsystem);
11163
+ continue;
11164
+ }
11124
11165
  if (!isSqliteBusy(err) || attempt === MAX_MIGRATION_RETRIES) {
11125
11166
  throw err;
11126
11167
  }
@@ -13478,7 +13519,7 @@ function runMigrations(nativeDb, db) {
13478
13519
  }
13479
13520
  reconcileJournal(nativeDb, migrationsFolder, "tasks", "sqlite");
13480
13521
  ensureColumns(nativeDb, "sessions", REQUIRED_SESSION_COLUMNS, "sqlite");
13481
- migrateWithRetry(db, migrationsFolder);
13522
+ migrateWithRetry(db, migrationsFolder, nativeDb, "tasks", "sqlite");
13482
13523
  ensureColumns(nativeDb, "tasks", REQUIRED_TASK_COLUMNS, "sqlite");
13483
13524
  }
13484
13525
  function closeDb() {
@@ -13604,7 +13645,7 @@ function runBrainMigrations(nativeDb, db) {
13604
13645
  createSafetyBackup(_dbPath2);
13605
13646
  }
13606
13647
  reconcileJournal(nativeDb, migrationsFolder, "brain_decisions", "brain");
13607
- migrateWithRetry(db, migrationsFolder);
13648
+ migrateWithRetry(db, migrationsFolder, nativeDb, "brain_decisions", "brain");
13608
13649
  }
13609
13650
  function loadBrainVecExtension(nativeDb) {
13610
13651
  try {
@@ -34416,9 +34457,15 @@ async function addTask(options, cwd, accessor) {
34416
34457
  if (lifecycleMode === "strict") {
34417
34458
  throw new CleoError(
34418
34459
  6 /* VALIDATION_ERROR */,
34419
- 'Tasks must have a parent (epic or task) in strict mode. Use --parent <epicId> or set lifecycle.mode to "advisory".',
34460
+ 'Tasks must have a parent (epic or task) in strict mode. Use --parent <epicId>, --type epic for a root-level epic, or set lifecycle.mode to "advisory".',
34420
34461
  {
34421
- fix: 'cleo add "Task title" --parent T### --acceptance "AC1|AC2|AC3"'
34462
+ fix: 'cleo add "Task title" --parent T### --acceptance "AC1|AC2|AC3"',
34463
+ alternatives: [
34464
+ {
34465
+ action: "Create as epic",
34466
+ command: 'cleo add "Epic title" --type epic --priority high'
34467
+ }
34468
+ ]
34422
34469
  }
34423
34470
  );
34424
34471
  }
@@ -74007,6 +74054,57 @@ async function initProject(opts = {}) {
74007
74054
  ".cleo/ was found in root .gitignore and has been removed. CLEO uses .cleo/.gitignore for selective tracking."
74008
74055
  );
74009
74056
  }
74057
+ try {
74058
+ const cantDir = join105(cleoDir, "cant");
74059
+ const cantAgentsDir = join105(cantDir, "agents");
74060
+ const hasCantFiles = existsSync106(cantDir) && readdirSync36(cantDir, { recursive: true }).some(
74061
+ (f2) => typeof f2 === "string" && f2.endsWith(".cant")
74062
+ );
74063
+ if (!hasCantFiles) {
74064
+ let starterBundleSrc = null;
74065
+ try {
74066
+ const { createRequire: createRequire13 } = await import("node:module");
74067
+ const req = createRequire13(import.meta.url);
74068
+ const cleoOsPkgMain = req.resolve("@cleocode/cleo-os/package.json");
74069
+ const cleoOsPkgRoot = dirname21(cleoOsPkgMain);
74070
+ const candidate = join105(cleoOsPkgRoot, "starter-bundle");
74071
+ if (existsSync106(candidate)) {
74072
+ starterBundleSrc = candidate;
74073
+ }
74074
+ } catch {
74075
+ }
74076
+ if (!starterBundleSrc) {
74077
+ const packageRoot = getPackageRoot();
74078
+ const fallbacks = [
74079
+ join105(packageRoot, "..", "cleo-os", "starter-bundle"),
74080
+ join105(packageRoot, "..", "..", "packages", "cleo-os", "starter-bundle")
74081
+ ];
74082
+ starterBundleSrc = fallbacks.find((p2) => existsSync106(p2)) ?? null;
74083
+ }
74084
+ if (starterBundleSrc) {
74085
+ await mkdir16(cantDir, { recursive: true });
74086
+ await mkdir16(cantAgentsDir, { recursive: true });
74087
+ const teamSrc = join105(starterBundleSrc, "team.cant");
74088
+ const teamDst = join105(cantDir, "team.cant");
74089
+ if (existsSync106(teamSrc) && !existsSync106(teamDst)) {
74090
+ await copyFile4(teamSrc, teamDst);
74091
+ }
74092
+ const agentsSrc = join105(starterBundleSrc, "agents");
74093
+ if (existsSync106(agentsSrc)) {
74094
+ const agentFiles = readdirSync36(agentsSrc).filter((f2) => f2.endsWith(".cant"));
74095
+ for (const agentFile of agentFiles) {
74096
+ const dst = join105(cantAgentsDir, agentFile);
74097
+ if (!existsSync106(dst)) {
74098
+ await copyFile4(join105(agentsSrc, agentFile), dst);
74099
+ }
74100
+ }
74101
+ }
74102
+ created.push("starter-bundle: team + agent .cant files deployed to .cleo/cant/");
74103
+ }
74104
+ }
74105
+ } catch (err) {
74106
+ warnings.push(`Starter bundle deploy: ${err instanceof Error ? err.message : String(err)}`);
74107
+ }
74010
74108
  if (opts.installSeedAgents) {
74011
74109
  try {
74012
74110
  const seedDir = await resolveSeedAgentsDir();
@@ -75307,7 +75405,6 @@ __export(src_exports, {
75307
75405
  });
75308
75406
  var init_src2 = __esm({
75309
75407
  "packages/core/src/index.ts"() {
75310
- "use strict";
75311
75408
  init_src();
75312
75409
  init_adapters();
75313
75410
  init_admin();
@@ -111594,6 +111691,12 @@ async function orchestrateClassify(request, context, projectRoot) {
111594
111691
  };
111595
111692
  }
111596
111693
  }
111694
+ function evictFanoutManifest() {
111695
+ while (fanoutManifestStore.size > FANOUT_MANIFEST_MAX_SIZE) {
111696
+ const oldest = fanoutManifestStore.keys().next().value;
111697
+ if (oldest !== void 0) fanoutManifestStore.delete(oldest);
111698
+ }
111699
+ }
111597
111700
  async function orchestrateFanout(items, projectRoot) {
111598
111701
  const manifestEntryId = `fanout-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
111599
111702
  try {
@@ -111638,6 +111741,7 @@ async function orchestrateFanout(items, projectRoot) {
111638
111741
  results,
111639
111742
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
111640
111743
  });
111744
+ evictFanoutManifest();
111641
111745
  return {
111642
111746
  success: true,
111643
111747
  data: {
@@ -111739,7 +111843,7 @@ async function orchestrateAnalyzeParallelSafety(taskIds, projectRoot) {
111739
111843
  };
111740
111844
  }
111741
111845
  }
111742
- var OrchestrateHandler, fanoutManifestStore;
111846
+ var OrchestrateHandler, FANOUT_MANIFEST_MAX_SIZE, fanoutManifestStore;
111743
111847
  var init_orchestrate2 = __esm({
111744
111848
  "packages/cleo/src/dispatch/domains/orchestrate.ts"() {
111745
111849
  "use strict";
@@ -112223,6 +112327,7 @@ var init_orchestrate2 = __esm({
112223
112327
  };
112224
112328
  }
112225
112329
  };
112330
+ FANOUT_MANIFEST_MAX_SIZE = 64;
112226
112331
  fanoutManifestStore = /* @__PURE__ */ new Map();
112227
112332
  }
112228
112333
  });
@@ -118452,6 +118557,786 @@ Task ${taskId} reassigned to you by ${active.agentId}. Run: cleo show ${taskId}
118452
118557
  { command: "agent health" }
118453
118558
  );
118454
118559
  });
118560
+ agent.command("install <path>").description("Install an agent from a .cantz archive or agent directory").option("--global", "Install to global tier (~/.local/share/cleo/cant/agents/)").action(async (sourcePath, opts) => {
118561
+ try {
118562
+ const { existsSync: existsSync131, mkdirSync: mkdirSync31, cpSync, readFileSync: readFileSync101, rmSync: rmSync3, statSync: statSync22 } = await import("node:fs");
118563
+ const { join: join131, basename: basename19, resolve: resolve16 } = await import("node:path");
118564
+ const { homedir: homedir7 } = await import("node:os");
118565
+ const { tmpdir: tmpdir3 } = await import("node:os");
118566
+ const resolvedPath = resolve16(sourcePath);
118567
+ if (!existsSync131(resolvedPath)) {
118568
+ cliOutput(
118569
+ {
118570
+ success: false,
118571
+ error: {
118572
+ code: "E_NOT_FOUND",
118573
+ message: `Path does not exist: ${resolvedPath}`
118574
+ }
118575
+ },
118576
+ { command: "agent install" }
118577
+ );
118578
+ process.exitCode = 4;
118579
+ return;
118580
+ }
118581
+ let agentDir;
118582
+ let agentName;
118583
+ let tempDir = null;
118584
+ const isCantzArchive = resolvedPath.endsWith(".cantz") && statSync22(resolvedPath).isFile();
118585
+ if (isCantzArchive) {
118586
+ const { execFileSync: execFileSync19 } = await import("node:child_process");
118587
+ tempDir = join131(tmpdir3(), `cleo-agent-install-${Date.now()}`);
118588
+ mkdirSync31(tempDir, { recursive: true });
118589
+ try {
118590
+ execFileSync19("unzip", ["-o", "-q", resolvedPath, "-d", tempDir], {
118591
+ encoding: "utf-8",
118592
+ timeout: 3e4
118593
+ });
118594
+ } catch (unzipErr) {
118595
+ if (tempDir) rmSync3(tempDir, { recursive: true, force: true });
118596
+ cliOutput(
118597
+ {
118598
+ success: false,
118599
+ error: {
118600
+ code: "E_VALIDATION",
118601
+ message: `Failed to extract .cantz archive: ${String(unzipErr)}`
118602
+ }
118603
+ },
118604
+ { command: "agent install" }
118605
+ );
118606
+ process.exitCode = 6;
118607
+ return;
118608
+ }
118609
+ const { readdirSync: readdirSync42 } = await import("node:fs");
118610
+ const topLevel = readdirSync42(tempDir).filter((entry) => {
118611
+ const entryPath = join131(tempDir, entry);
118612
+ return statSync22(entryPath).isDirectory();
118613
+ });
118614
+ if (topLevel.length !== 1) {
118615
+ if (tempDir) rmSync3(tempDir, { recursive: true, force: true });
118616
+ cliOutput(
118617
+ {
118618
+ success: false,
118619
+ error: {
118620
+ code: "E_VALIDATION",
118621
+ message: `Archive must contain exactly one top-level directory, found ${topLevel.length}`
118622
+ }
118623
+ },
118624
+ { command: "agent install" }
118625
+ );
118626
+ process.exitCode = 6;
118627
+ return;
118628
+ }
118629
+ agentName = topLevel[0];
118630
+ agentDir = join131(tempDir, agentName);
118631
+ } else if (statSync22(resolvedPath).isDirectory()) {
118632
+ agentDir = resolvedPath;
118633
+ agentName = basename19(resolvedPath);
118634
+ } else {
118635
+ cliOutput(
118636
+ {
118637
+ success: false,
118638
+ error: {
118639
+ code: "E_VALIDATION",
118640
+ message: `Path must be a .cantz file or a directory: ${resolvedPath}`
118641
+ }
118642
+ },
118643
+ { command: "agent install" }
118644
+ );
118645
+ process.exitCode = 6;
118646
+ return;
118647
+ }
118648
+ const personaPath = join131(agentDir, "persona.cant");
118649
+ if (!existsSync131(personaPath)) {
118650
+ if (tempDir) rmSync3(tempDir, { recursive: true, force: true });
118651
+ cliOutput(
118652
+ {
118653
+ success: false,
118654
+ error: {
118655
+ code: "E_VALIDATION",
118656
+ message: `Agent directory must contain persona.cant: ${personaPath}`
118657
+ }
118658
+ },
118659
+ { command: "agent install" }
118660
+ );
118661
+ process.exitCode = 6;
118662
+ return;
118663
+ }
118664
+ const isGlobal = opts["global"] === true;
118665
+ let targetRoot;
118666
+ if (isGlobal) {
118667
+ const home = homedir7();
118668
+ const xdgData = process.env["XDG_DATA_HOME"] ?? join131(home, ".local", "share");
118669
+ targetRoot = join131(xdgData, "cleo", "cant", "agents");
118670
+ } else {
118671
+ targetRoot = join131(process.cwd(), ".cleo", "cant", "agents");
118672
+ }
118673
+ const targetDir = join131(targetRoot, agentName);
118674
+ mkdirSync31(targetRoot, { recursive: true });
118675
+ cpSync(agentDir, targetDir, { recursive: true, force: true });
118676
+ if (tempDir) {
118677
+ rmSync3(tempDir, { recursive: true, force: true });
118678
+ }
118679
+ let registered = false;
118680
+ try {
118681
+ const persona = readFileSync101(join131(targetDir, "persona.cant"), "utf-8");
118682
+ const descMatch = persona.match(/description:\s*"([^"]+)"/);
118683
+ const displayName = descMatch?.[1] ?? agentName;
118684
+ const { AgentRegistryAccessor: AgentRegistryAccessor2, getDb: getDb4 } = await Promise.resolve().then(() => (init_internal(), internal_exports));
118685
+ await getDb4();
118686
+ const registry2 = new AgentRegistryAccessor2(process.cwd());
118687
+ const existing = await registry2.get(agentName);
118688
+ if (!existing) {
118689
+ await registry2.register({
118690
+ agentId: agentName,
118691
+ displayName,
118692
+ apiKey: "local-installed",
118693
+ apiBaseUrl: "local",
118694
+ classification: "specialist",
118695
+ privacyTier: "private",
118696
+ capabilities: [],
118697
+ skills: [],
118698
+ transportType: "http",
118699
+ transportConfig: {},
118700
+ isActive: false
118701
+ });
118702
+ registered = true;
118703
+ }
118704
+ } catch {
118705
+ }
118706
+ cliOutput(
118707
+ {
118708
+ success: true,
118709
+ data: {
118710
+ agent: agentName,
118711
+ tier: isGlobal ? "global" : "project",
118712
+ path: targetDir,
118713
+ registered
118714
+ }
118715
+ },
118716
+ { command: "agent install" }
118717
+ );
118718
+ } catch (err) {
118719
+ cliOutput(
118720
+ { success: false, error: { code: "E_INSTALL", message: String(err) } },
118721
+ { command: "agent install" }
118722
+ );
118723
+ process.exitCode = 1;
118724
+ }
118725
+ });
118726
+ agent.command("pack <dir>").description("Package an agent directory as a .cantz archive").action(async (dir) => {
118727
+ try {
118728
+ const { existsSync: existsSync131, statSync: statSync22 } = await import("node:fs");
118729
+ const { resolve: resolve16, basename: basename19, dirname: dirname29 } = await import("node:path");
118730
+ const { execFileSync: execFileSync19 } = await import("node:child_process");
118731
+ const resolvedDir = resolve16(dir);
118732
+ if (!existsSync131(resolvedDir) || !statSync22(resolvedDir).isDirectory()) {
118733
+ cliOutput(
118734
+ {
118735
+ success: false,
118736
+ error: {
118737
+ code: "E_NOT_FOUND",
118738
+ message: `Directory does not exist: ${resolvedDir}`
118739
+ }
118740
+ },
118741
+ { command: "agent pack" }
118742
+ );
118743
+ process.exitCode = 4;
118744
+ return;
118745
+ }
118746
+ const { join: join131 } = await import("node:path");
118747
+ const personaPath = join131(resolvedDir, "persona.cant");
118748
+ if (!existsSync131(personaPath)) {
118749
+ cliOutput(
118750
+ {
118751
+ success: false,
118752
+ error: {
118753
+ code: "E_VALIDATION",
118754
+ message: `Agent directory must contain persona.cant: ${personaPath}`
118755
+ }
118756
+ },
118757
+ { command: "agent pack" }
118758
+ );
118759
+ process.exitCode = 6;
118760
+ return;
118761
+ }
118762
+ const agentName = basename19(resolvedDir);
118763
+ const archiveName = `${agentName}.cantz`;
118764
+ const archivePath = resolve16(archiveName);
118765
+ const parentDir = dirname29(resolvedDir);
118766
+ try {
118767
+ execFileSync19("zip", ["-r", archivePath, agentName], {
118768
+ cwd: parentDir,
118769
+ encoding: "utf-8",
118770
+ timeout: 3e4
118771
+ });
118772
+ } catch (zipErr) {
118773
+ cliOutput(
118774
+ {
118775
+ success: false,
118776
+ error: {
118777
+ code: "E_PACK",
118778
+ message: `Failed to create archive: ${String(zipErr)}`
118779
+ }
118780
+ },
118781
+ { command: "agent pack" }
118782
+ );
118783
+ process.exitCode = 1;
118784
+ return;
118785
+ }
118786
+ const archiveStats = statSync22(archivePath);
118787
+ const { readdirSync: readdirSync42 } = await import("node:fs");
118788
+ let fileCount = 0;
118789
+ const countFiles2 = (dirPath) => {
118790
+ const entries = readdirSync42(dirPath, { withFileTypes: true });
118791
+ for (const entry of entries) {
118792
+ if (entry.isFile()) {
118793
+ fileCount++;
118794
+ } else if (entry.isDirectory()) {
118795
+ countFiles2(join131(dirPath, entry.name));
118796
+ }
118797
+ }
118798
+ };
118799
+ countFiles2(resolvedDir);
118800
+ cliOutput(
118801
+ {
118802
+ success: true,
118803
+ data: {
118804
+ archive: archivePath,
118805
+ agent: agentName,
118806
+ files: fileCount,
118807
+ size: archiveStats.size
118808
+ }
118809
+ },
118810
+ { command: "agent pack" }
118811
+ );
118812
+ } catch (err) {
118813
+ cliOutput(
118814
+ { success: false, error: { code: "E_PACK", message: String(err) } },
118815
+ { command: "agent pack" }
118816
+ );
118817
+ process.exitCode = 1;
118818
+ }
118819
+ });
118820
+ agent.command("create").description("Scaffold a new agent package with persona.cant and manifest.json").requiredOption("--name <name>", "Agent name (kebab-case)").requiredOption("--role <role>", "Agent role: orchestrator, lead, worker, or docs-worker").option("--tier <tier>", "Agent tier: low, mid, or high (defaults based on role)").option("--team <teamName>", "Team this agent belongs to").option("--domain <description>", "Domain description for file permissions and context").option("--global", "Create in global tier (~/.local/share/cleo/cant/agents/)").option("--seed-brain", "Create expertise/mental-model-seed.md and seed a BRAIN observation").option("--parent <parentAgent>", "Parent agent name in the hierarchy").action(async (opts) => {
118821
+ try {
118822
+ const { existsSync: existsSync131, mkdirSync: mkdirSync31, writeFileSync: writeFileSync25 } = await import("node:fs");
118823
+ const { join: join131 } = await import("node:path");
118824
+ const { homedir: homedir7 } = await import("node:os");
118825
+ const name2 = opts["name"];
118826
+ const role = opts["role"];
118827
+ const tier = opts["tier"] ?? inferTierFromRole(role);
118828
+ const team = opts["team"];
118829
+ const domain2 = opts["domain"];
118830
+ const isGlobal = opts["global"] === true;
118831
+ const seedBrain = opts["seedBrain"] === true;
118832
+ const parent = opts["parent"];
118833
+ const validRoles = ["orchestrator", "lead", "worker", "docs-worker"];
118834
+ if (!validRoles.includes(role)) {
118835
+ cliOutput(
118836
+ {
118837
+ success: false,
118838
+ error: {
118839
+ code: "E_VALIDATION",
118840
+ message: `Invalid role "${role}". Must be one of: ${validRoles.join(", ")}`,
118841
+ fix: `cleo agent create --name ${name2} --role worker`
118842
+ }
118843
+ },
118844
+ { command: "agent create" }
118845
+ );
118846
+ process.exitCode = 6;
118847
+ return;
118848
+ }
118849
+ const validTiers = ["low", "mid", "high"];
118850
+ if (!validTiers.includes(tier)) {
118851
+ cliOutput(
118852
+ {
118853
+ success: false,
118854
+ error: {
118855
+ code: "E_VALIDATION",
118856
+ message: `Invalid tier "${tier}". Must be one of: ${validTiers.join(", ")}`,
118857
+ fix: `cleo agent create --name ${name2} --role ${role} --tier mid`
118858
+ }
118859
+ },
118860
+ { command: "agent create" }
118861
+ );
118862
+ process.exitCode = 6;
118863
+ return;
118864
+ }
118865
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name2)) {
118866
+ cliOutput(
118867
+ {
118868
+ success: false,
118869
+ error: {
118870
+ code: "E_VALIDATION",
118871
+ message: `Agent name must be kebab-case: "${name2}"`,
118872
+ fix: "Use lowercase letters, numbers, and hyphens. Must start with a letter."
118873
+ }
118874
+ },
118875
+ { command: "agent create" }
118876
+ );
118877
+ process.exitCode = 6;
118878
+ return;
118879
+ }
118880
+ let targetRoot;
118881
+ if (isGlobal) {
118882
+ const home = homedir7();
118883
+ const xdgData = process.env["XDG_DATA_HOME"] ?? join131(home, ".local", "share");
118884
+ targetRoot = join131(xdgData, "cleo", "cant", "agents");
118885
+ } else {
118886
+ targetRoot = join131(process.cwd(), ".cleo", "cant", "agents");
118887
+ }
118888
+ const agentDir = join131(targetRoot, name2);
118889
+ if (existsSync131(agentDir)) {
118890
+ cliOutput(
118891
+ {
118892
+ success: false,
118893
+ error: {
118894
+ code: "E_VALIDATION",
118895
+ message: `Agent directory already exists: ${agentDir}`,
118896
+ fix: "Remove the existing directory or choose a different name."
118897
+ }
118898
+ },
118899
+ { command: "agent create" }
118900
+ );
118901
+ process.exitCode = 6;
118902
+ return;
118903
+ }
118904
+ mkdirSync31(agentDir, { recursive: true });
118905
+ const personaContent = generatePersonaCant({
118906
+ name: name2,
118907
+ role,
118908
+ tier,
118909
+ team,
118910
+ domain: domain2,
118911
+ parent
118912
+ });
118913
+ writeFileSync25(join131(agentDir, "persona.cant"), personaContent, "utf-8");
118914
+ const manifest = generateManifest2({ name: name2, role, tier, domain: domain2 });
118915
+ writeFileSync25(
118916
+ join131(agentDir, "manifest.json"),
118917
+ `${JSON.stringify(manifest, null, 2)}
118918
+ `,
118919
+ "utf-8"
118920
+ );
118921
+ const createdFiles = [
118922
+ join131(agentDir, "persona.cant"),
118923
+ join131(agentDir, "manifest.json")
118924
+ ];
118925
+ if (team) {
118926
+ const teamConfigContent = generateTeamConfig(name2, role, team);
118927
+ writeFileSync25(join131(agentDir, "team-config.cant"), teamConfigContent, "utf-8");
118928
+ createdFiles.push(join131(agentDir, "team-config.cant"));
118929
+ }
118930
+ if (seedBrain) {
118931
+ const expertiseDir = join131(agentDir, "expertise");
118932
+ mkdirSync31(expertiseDir, { recursive: true });
118933
+ const seedContent = generateMentalModelSeed(name2, role, domain2);
118934
+ writeFileSync25(join131(expertiseDir, "mental-model-seed.md"), seedContent, "utf-8");
118935
+ createdFiles.push(join131(expertiseDir, "mental-model-seed.md"));
118936
+ try {
118937
+ const { execFile: execFile9 } = await import("node:child_process");
118938
+ const { promisify: promisify9 } = await import("node:util");
118939
+ const execFileAsync6 = promisify9(execFile9);
118940
+ await execFileAsync6(
118941
+ "cleo",
118942
+ [
118943
+ "observe",
118944
+ `Agent ${name2} created with role ${role}`,
118945
+ "--title",
118946
+ `Agent creation: ${name2}`
118947
+ ],
118948
+ { encoding: "utf-8", timeout: 1e4 }
118949
+ ).catch(() => {
118950
+ });
118951
+ } catch {
118952
+ }
118953
+ }
118954
+ let registered = false;
118955
+ try {
118956
+ const { AgentRegistryAccessor: AgentRegistryAccessor2, getDb: getDb4 } = await Promise.resolve().then(() => (init_internal(), internal_exports));
118957
+ await getDb4();
118958
+ const registry2 = new AgentRegistryAccessor2(process.cwd());
118959
+ const existing = await registry2.get(name2);
118960
+ if (!existing) {
118961
+ const descMatch = personaContent.match(/description:\s*"([^"]+)"/);
118962
+ const displayName = descMatch?.[1] ?? name2;
118963
+ await registry2.register({
118964
+ agentId: name2,
118965
+ displayName,
118966
+ apiKey: "local-created",
118967
+ apiBaseUrl: "local",
118968
+ classification: role,
118969
+ privacyTier: "private",
118970
+ capabilities: [],
118971
+ skills: [],
118972
+ transportType: "http",
118973
+ transportConfig: {},
118974
+ isActive: false
118975
+ });
118976
+ registered = true;
118977
+ }
118978
+ } catch {
118979
+ }
118980
+ cliOutput(
118981
+ {
118982
+ success: true,
118983
+ data: {
118984
+ agent: name2,
118985
+ role,
118986
+ tier,
118987
+ directory: agentDir,
118988
+ scope: isGlobal ? "global" : "project",
118989
+ files: createdFiles,
118990
+ registered,
118991
+ brainSeeded: seedBrain
118992
+ }
118993
+ },
118994
+ { command: "agent create" }
118995
+ );
118996
+ } catch (err) {
118997
+ cliOutput(
118998
+ { success: false, error: { code: "E_CREATE", message: String(err) } },
118999
+ { command: "agent create" }
119000
+ );
119001
+ process.exitCode = 1;
119002
+ }
119003
+ });
119004
+ }
119005
+ function inferTierFromRole(role) {
119006
+ if (role === "orchestrator") return "high";
119007
+ return "mid";
119008
+ }
119009
+ function generatePersonaCant(params) {
119010
+ const { name: name2, role, tier, team, domain: domain2, parent } = params;
119011
+ switch (role) {
119012
+ case "orchestrator":
119013
+ return generateOrchestratorPersona(name2, tier, team, parent);
119014
+ case "lead":
119015
+ return generateLeadPersona(name2, tier, team, domain2, parent);
119016
+ case "worker":
119017
+ return generateWorkerPersona(name2, tier, team, domain2, parent);
119018
+ case "docs-worker":
119019
+ return generateDocsWorkerPersona(name2, tier, team, domain2, parent);
119020
+ default:
119021
+ return generateWorkerPersona(name2, tier, team, domain2, parent);
119022
+ }
119023
+ }
119024
+ function generateOrchestratorPersona(name2, tier, team, parent) {
119025
+ const parentLine = parent ? `
119026
+ parent: ${parent}` : "";
119027
+ const teamComment = team ? `
119028
+ # Team: ${team}` : "";
119029
+ return `---
119030
+ kind: agent
119031
+ version: "1"
119032
+ ---
119033
+
119034
+ # ${name2} \u2014 orchestrator agent.${teamComment}
119035
+ # Coordinates the team, classifies work, dispatches to leads/workers.
119036
+
119037
+ agent ${name2}:
119038
+ role: orchestrator${parentLine}
119039
+ tier: ${tier}
119040
+ description: "Orchestrator agent. Reads task context, classifies work, dispatches to leads, and synthesizes results. Does not execute code \u2014 coordinates."
119041
+ consult-when: "Cross-team decisions, scope changes, human-in-the-loop escalation, or when a lead reports a blocking ambiguity"
119042
+
119043
+ context_sources:
119044
+ - source: decisions
119045
+ query: "recent architectural and project decisions"
119046
+ max_entries: 5
119047
+ - source: patterns
119048
+ query: "project conventions and established patterns"
119049
+ max_entries: 3
119050
+ on_overflow: escalate_tier
119051
+
119052
+ mental_model:
119053
+ scope: project
119054
+ max_tokens: 2000
119055
+ on_load:
119056
+ validate: true
119057
+
119058
+ permissions:
119059
+ tasks: read, write
119060
+ session: read, write
119061
+ memory: read, write
119062
+
119063
+ skills:
119064
+ - ct-cleo
119065
+ - ct-task-executor
119066
+
119067
+ tools:
119068
+ core: [Read, Grep, Glob]
119069
+ dispatch: [dispatch_worker, report_to_user]
119070
+
119071
+ on SessionStart:
119072
+ session "Read active tasks and recent decisions to build situational awareness"
119073
+ context: [active-tasks, memory-bridge, recent-decisions]
119074
+
119075
+ on TaskCompleted:
119076
+ if **the completed task unblocks downstream work**:
119077
+ session "Reassess task queue and dispatch next work"
119078
+ `;
119079
+ }
119080
+ function generateLeadPersona(name2, tier, team, domain2, parent) {
119081
+ const parentLine = parent ? `
119082
+ parent: ${parent}` : "\n parent: cleo-orchestrator";
119083
+ const teamComment = team ? `
119084
+ # Team: ${team}` : "";
119085
+ const domainDesc = domain2 ? ` Specializes in ${domain2}.` : "";
119086
+ return `---
119087
+ kind: agent
119088
+ version: "1"
119089
+ ---
119090
+
119091
+ # ${name2} \u2014 lead agent.${teamComment}
119092
+ # Decomposes tasks, reviews worker output, decides technical approach.
119093
+ # MUST NOT hold Edit/Write/Bash tools (TEAM-002 / ULTRAPLAN 10.3).
119094
+
119095
+ agent ${name2}:
119096
+ role: lead${parentLine}
119097
+ tier: ${tier}
119098
+ description: "Development lead.${domainDesc} Decomposes tasks into concrete implementation steps, reviews worker output, and decides technical approach. Does not write code directly."
119099
+ consult-when: "Implementation strategy, code architecture, refactoring direction, task decomposition, or when workers need clarification"
119100
+
119101
+ context_sources:
119102
+ - source: patterns
119103
+ query: "codebase conventions and architecture patterns"
119104
+ max_entries: 5
119105
+ - source: decisions
119106
+ query: "technical decisions affecting implementation"
119107
+ max_entries: 3
119108
+ on_overflow: escalate_tier
119109
+
119110
+ mental_model:
119111
+ scope: project
119112
+ max_tokens: 1000
119113
+ on_load:
119114
+ validate: true
119115
+
119116
+ permissions:
119117
+ files:
119118
+ read: ["**/*"]
119119
+
119120
+ skills:
119121
+ - ct-cleo
119122
+ - ct-dev-workflow
119123
+ - ct-task-executor
119124
+
119125
+ tools:
119126
+ core: [Read, Grep, Glob]
119127
+ dispatch: [dispatch_worker, report_to_orchestrator]
119128
+
119129
+ on SessionStart:
119130
+ session "Review current task assignments and worker availability"
119131
+ context: [active-tasks, memory-bridge]
119132
+
119133
+ on TaskCompleted:
119134
+ if **the completed task introduced new code**:
119135
+ session "Review worker output for quality and completeness before reporting to orchestrator"
119136
+ `;
119137
+ }
119138
+ function generateWorkerPersona(name2, tier, team, domain2, parent) {
119139
+ const parentLine = parent ? `
119140
+ parent: ${parent}` : "\n parent: dev-lead";
119141
+ const teamComment = team ? `
119142
+ # Team: ${team}` : "";
119143
+ const domainDesc = domain2 ? ` Specializes in ${domain2}.` : "";
119144
+ const writeGlobs = deriveWriteGlobs(domain2);
119145
+ return `---
119146
+ kind: agent
119147
+ version: "1"
119148
+ ---
119149
+
119150
+ # ${name2} \u2014 worker agent.${teamComment}
119151
+ # Executes code changes within declared file globs.
119152
+
119153
+ agent ${name2}:
119154
+ role: worker${parentLine}
119155
+ tier: ${tier}
119156
+ description: "Code worker.${domainDesc} Reads requirements, writes code, runs tests, and validates changes. Operates within declared file permission globs."
119157
+ consult-when: "Writing code, fixing bugs, running tests, formatting, or any file modification task"
119158
+
119159
+ context_sources:
119160
+ - source: patterns
119161
+ query: "coding conventions and testing patterns"
119162
+ max_entries: 5
119163
+ - source: learnings
119164
+ query: "past implementation mistakes and fixes"
119165
+ max_entries: 3
119166
+ on_overflow: escalate_tier
119167
+
119168
+ mental_model:
119169
+ scope: project
119170
+ max_tokens: 1000
119171
+ on_load:
119172
+ validate: true
119173
+
119174
+ permissions:
119175
+ files:
119176
+ write: ${JSON.stringify(writeGlobs)}
119177
+ read: ["**/*"]
119178
+ delete: ${JSON.stringify(writeGlobs)}
119179
+
119180
+ skills:
119181
+ - ct-cleo
119182
+ - ct-dev-workflow
119183
+ - ct-task-executor
119184
+
119185
+ tools:
119186
+ core: [Read, Edit, Write, Bash, Glob, Grep]
119187
+
119188
+ on SessionStart:
119189
+ session "Check assigned task and read relevant source files before starting work"
119190
+ context: [active-tasks, memory-bridge]
119191
+
119192
+ on PostToolUse:
119193
+ if tool.name == "Write" or tool.name == "Edit":
119194
+ session "Verify the change compiles and passes lint before proceeding"
119195
+ `;
119196
+ }
119197
+ function generateDocsWorkerPersona(name2, tier, team, domain2, parent) {
119198
+ const parentLine = parent ? `
119199
+ parent: ${parent}` : "\n parent: dev-lead";
119200
+ const teamComment = team ? `
119201
+ # Team: ${team}` : "";
119202
+ const domainDesc = domain2 ? ` Specializes in ${domain2} documentation.` : "";
119203
+ return `---
119204
+ kind: agent
119205
+ version: "1"
119206
+ ---
119207
+
119208
+ # ${name2} \u2014 documentation worker agent.${teamComment}
119209
+ # Writes and maintains documentation within declared globs.
119210
+
119211
+ agent ${name2}:
119212
+ role: worker${parentLine}
119213
+ tier: ${tier}
119214
+ description: "Documentation worker.${domainDesc} Writes READMEs, updates guides, adds TSDoc comments, and maintains project documentation. Operates within declared documentation file globs."
119215
+ consult-when: "Writing documentation, updating READMEs, adding TSDoc comments, or improving existing docs"
119216
+
119217
+ context_sources:
119218
+ - source: patterns
119219
+ query: "documentation conventions and style patterns"
119220
+ max_entries: 3
119221
+ - source: decisions
119222
+ query: "architectural decisions needing documentation"
119223
+ max_entries: 3
119224
+ on_overflow: escalate_tier
119225
+
119226
+ mental_model:
119227
+ scope: project
119228
+ max_tokens: 1000
119229
+ on_load:
119230
+ validate: true
119231
+
119232
+ permissions:
119233
+ files:
119234
+ write: ["docs/**", "**/*.md", "**/*.mdx"]
119235
+ read: ["**/*"]
119236
+ delete: ["docs/**"]
119237
+
119238
+ skills:
119239
+ - ct-cleo
119240
+ - ct-documentor
119241
+ - ct-docs-write
119242
+
119243
+ tools:
119244
+ core: [Read, Edit, Write, Bash, Glob, Grep]
119245
+
119246
+ on SessionStart:
119247
+ session "Check assigned documentation task and review existing docs for context"
119248
+ context: [active-tasks, memory-bridge]
119249
+
119250
+ on PostToolUse:
119251
+ if tool.name == "Write" or tool.name == "Edit":
119252
+ session "Verify markdown renders correctly and follows project style conventions"
119253
+ `;
119254
+ }
119255
+ function deriveWriteGlobs(domain2) {
119256
+ const defaults = ["src/**", "packages/**", "lib/**", "test/**", "tests/**"];
119257
+ if (!domain2) return defaults;
119258
+ const lower = domain2.toLowerCase();
119259
+ if (lower.includes("frontend") || lower.includes("ui") || lower.includes("component")) {
119260
+ return ["src/**", "packages/**", "components/**", "styles/**", "public/**", "test/**"];
119261
+ }
119262
+ if (lower.includes("backend") || lower.includes("api") || lower.includes("server")) {
119263
+ return ["src/**", "packages/**", "lib/**", "api/**", "test/**", "tests/**"];
119264
+ }
119265
+ if (lower.includes("infra") || lower.includes("deploy") || lower.includes("ci")) {
119266
+ return [".github/**", "infra/**", "deploy/**", "scripts/**", "Dockerfile*"];
119267
+ }
119268
+ if (lower.includes("test") || lower.includes("qa") || lower.includes("quality")) {
119269
+ return ["test/**", "tests/**", "src/**/*.test.*", "src/**/*.spec.*", "packages/**/*.test.*"];
119270
+ }
119271
+ if (lower.includes("rust") || lower.includes("crate")) {
119272
+ return ["crates/**", "src/**", "Cargo.toml", "test/**"];
119273
+ }
119274
+ if (lower.includes("doc")) {
119275
+ return ["docs/**", "**/*.md", "**/*.mdx"];
119276
+ }
119277
+ return defaults;
119278
+ }
119279
+ function generateManifest2(params) {
119280
+ return {
119281
+ name: params.name,
119282
+ version: "1.0.0",
119283
+ description: `${capitalizeFirst(params.role)} agent${params.domain ? ` for ${params.domain}` : ""}`,
119284
+ cant: {
119285
+ minVersion: "1",
119286
+ tier: params.tier,
119287
+ role: params.role === "docs-worker" ? "worker" : params.role
119288
+ },
119289
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
119290
+ };
119291
+ }
119292
+ function generateTeamConfig(name2, role, team) {
119293
+ return `---
119294
+ kind: team-config
119295
+ version: "1"
119296
+ ---
119297
+
119298
+ # Team membership for ${name2}
119299
+
119300
+ team ${team}:
119301
+ member ${name2}:
119302
+ role: ${role}
119303
+ status: active
119304
+ `;
119305
+ }
119306
+ function generateMentalModelSeed(name2, role, domain2) {
119307
+ const domainSection = domain2 ? `## Domain
119308
+
119309
+ ${domain2}
119310
+ ` : `## Domain
119311
+
119312
+ TODO: Describe the domain this agent specializes in.
119313
+ `;
119314
+ return `# Mental Model Seed: ${name2}
119315
+
119316
+ > Auto-generated at ${(/* @__PURE__ */ new Date()).toISOString()}
119317
+ > Role: ${role}
119318
+
119319
+ ${domainSection}
119320
+ ## Key Patterns
119321
+
119322
+ TODO: Document recurring patterns this agent should recognize.
119323
+
119324
+ ## Known Pitfalls
119325
+
119326
+ TODO: Document common mistakes or anti-patterns in this domain.
119327
+
119328
+ ## Decision History
119329
+
119330
+ TODO: Track important decisions and their rationale.
119331
+
119332
+ ## Learning Log
119333
+
119334
+ TODO: Record discoveries and insights as the agent operates.
119335
+ `;
119336
+ }
119337
+ function capitalizeFirst(str) {
119338
+ if (str.length === 0) return str;
119339
+ return str.charAt(0).toUpperCase() + str.slice(1);
118455
119340
  }
118456
119341
 
118457
119342
  // packages/cleo/src/cli/commands/agents.ts