@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 +892 -7
- package/dist/cli/index.js.map +3 -3
- package/package.json +7 -7
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
|
|
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
|