@cleocode/cleo 2026.3.29 → 2026.3.31

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
@@ -4439,6 +4439,118 @@ var init_sqlite = __esm({
4439
4439
  }
4440
4440
  });
4441
4441
 
4442
+ // src/core/metrics/provider-detection.ts
4443
+ var provider_detection_exports = {};
4444
+ __export(provider_detection_exports, {
4445
+ detectRuntimeProviderContext: () => detectRuntimeProviderContext,
4446
+ resetRuntimeProviderContextCache: () => resetRuntimeProviderContextCache,
4447
+ selectRuntimeProviderContext: () => selectRuntimeProviderContext
4448
+ });
4449
+ import { basename as basename3 } from "node:path";
4450
+ import {
4451
+ detectProjectProviders,
4452
+ getProvider,
4453
+ resolveAlias
4454
+ } from "@cleocode/caamp";
4455
+ function inferProviderFromVendor(vendor) {
4456
+ const value = (vendor ?? "").trim().toLowerCase();
4457
+ if (!value) return void 0;
4458
+ if (value.includes("anthropic") || value.includes("claude")) return "anthropic";
4459
+ if (value.includes("openai") || value.includes("codex") || value.includes("chatgpt"))
4460
+ return "openai";
4461
+ if (value.includes("google") || value.includes("gemini")) return "google";
4462
+ if (value.includes("xai") || value.includes("grok")) return "xai";
4463
+ return void 0;
4464
+ }
4465
+ function getRuntimeHints(snapshot) {
4466
+ const argv = snapshot.argv ?? process.argv;
4467
+ const env = snapshot.env ?? process.env;
4468
+ const hints = /* @__PURE__ */ new Set();
4469
+ const bin = basename3(argv[1] ?? argv[0] ?? "").replace(/\.[^.]+$/, "");
4470
+ if (bin) hints.add(bin);
4471
+ if (env["CLAUDE_CODE_ENABLE_TELEMETRY"] || env["CLAUDE_CODE_ENTRYPOINT"]) {
4472
+ hints.add("claude-code");
4473
+ hints.add("claude");
4474
+ }
4475
+ if (env["OPENCODE_AGENT"] || env["OPENCODE"]) {
4476
+ hints.add("opencode");
4477
+ }
4478
+ if (env["CURSOR_TRACE_ID"] || env["CURSOR_AGENT"]) {
4479
+ hints.add("cursor");
4480
+ }
4481
+ return Array.from(hints);
4482
+ }
4483
+ function pickDetectionByHint(detections, hints) {
4484
+ for (const hint of hints) {
4485
+ const resolved = resolveAlias(hint);
4486
+ const direct = detections.find(
4487
+ (entry) => entry.provider.id === resolved || entry.provider.id === hint
4488
+ );
4489
+ if (direct) return direct;
4490
+ const byAlias = detections.find(
4491
+ (entry) => entry.provider.aliases.includes(hint) || entry.provider.agentFlag === hint || entry.provider.toolName.toLowerCase() === hint.toLowerCase()
4492
+ );
4493
+ if (byAlias) return byAlias;
4494
+ const provider = getProvider(resolved);
4495
+ if (provider) {
4496
+ return {
4497
+ provider,
4498
+ installed: true,
4499
+ methods: [],
4500
+ projectDetected: false
4501
+ };
4502
+ }
4503
+ }
4504
+ return null;
4505
+ }
4506
+ function selectRuntimeProviderContext(detections, snapshot = {}) {
4507
+ const hints = getRuntimeHints(snapshot);
4508
+ const hinted = pickDetectionByHint(detections, hints);
4509
+ const projectMatches = detections.filter((entry) => entry.projectDetected);
4510
+ const installed = detections.filter((entry) => entry.installed);
4511
+ const selected = hinted ?? (projectMatches.length === 1 ? projectMatches[0] : null) ?? (installed.length === 1 ? installed[0] : null);
4512
+ if (!selected) {
4513
+ return {
4514
+ runtimeCandidates: installed.map((entry) => entry.provider.id)
4515
+ };
4516
+ }
4517
+ return {
4518
+ runtimeProviderId: selected.provider.id,
4519
+ runtimeToolName: selected.provider.toolName,
4520
+ runtimeVendor: selected.provider.vendor,
4521
+ runtimeInstructionFile: selected.provider.instructFile,
4522
+ runtimeProjectDetected: selected.projectDetected,
4523
+ runtimeDetectionMethods: selected.methods,
4524
+ runtimeCandidates: installed.map((entry) => entry.provider.id),
4525
+ inferredModelProvider: inferProviderFromVendor(selected.provider.vendor)
4526
+ };
4527
+ }
4528
+ function detectRuntimeProviderContext(snapshot = {}) {
4529
+ if (!snapshot.cwd && !snapshot.argv && !snapshot.env && cachedRuntimeProvider) {
4530
+ return cachedRuntimeProvider;
4531
+ }
4532
+ try {
4533
+ const detections = detectProjectProviders(snapshot.cwd ?? process.cwd());
4534
+ const context = selectRuntimeProviderContext(detections, snapshot);
4535
+ if (!snapshot.cwd && !snapshot.argv && !snapshot.env) {
4536
+ cachedRuntimeProvider = context;
4537
+ }
4538
+ return context;
4539
+ } catch {
4540
+ return {};
4541
+ }
4542
+ }
4543
+ function resetRuntimeProviderContextCache() {
4544
+ cachedRuntimeProvider = null;
4545
+ }
4546
+ var cachedRuntimeProvider;
4547
+ var init_provider_detection = __esm({
4548
+ "src/core/metrics/provider-detection.ts"() {
4549
+ "use strict";
4550
+ cachedRuntimeProvider = null;
4551
+ }
4552
+ });
4553
+
4442
4554
  // src/store/parsers.ts
4443
4555
  function safeParseJson(str) {
4444
4556
  if (!str) return void 0;
@@ -8482,14 +8594,16 @@ async function writeMemoryBridge(projectRoot, config) {
8482
8594
  }
8483
8595
  writeFileSync4(bridgePath, content, "utf-8");
8484
8596
  return { path: bridgePath, written: true };
8485
- } catch {
8597
+ } catch (err) {
8598
+ console.error("[CLEO] Failed to write memory bridge:", err instanceof Error ? err.message : String(err));
8486
8599
  return { path: bridgePath, written: false };
8487
8600
  }
8488
8601
  }
8489
8602
  async function refreshMemoryBridge(projectRoot) {
8490
8603
  try {
8491
8604
  await writeMemoryBridge(projectRoot);
8492
- } catch {
8605
+ } catch (err) {
8606
+ console.error("[CLEO] Memory bridge refresh failed:", err instanceof Error ? err.message : String(err));
8493
8607
  }
8494
8608
  }
8495
8609
  async function getLastHandoffSafe(projectRoot) {
@@ -9885,28 +9999,25 @@ async function ensureInjection(projectRoot) {
9885
9999
  }
9886
10000
  const { getInstalledProviders: getInstalledProviders3, inject, injectAll: injectAll2, buildInjectionContent: buildInjectionContent2 } = caamp;
9887
10001
  const providers = getInstalledProviders3();
9888
- if (providers.length === 0) {
9889
- return {
9890
- action: "skipped",
9891
- path: join28(projectRoot, "AGENTS.md"),
9892
- details: "No AI agent providers detected, skipping injection"
9893
- };
9894
- }
9895
10002
  const actions = [];
9896
- for (const provider of providers) {
9897
- const instructFile = join28(projectRoot, provider.pathProject, provider.instructFile);
9898
- await stripCLEOBlocks(instructFile);
9899
- }
9900
- await stripCLEOBlocks(join28(projectRoot, "AGENTS.md"));
9901
- const removedStale = await removeStaleAgentInjection(projectRoot);
9902
- if (removedStale) {
9903
- actions.push("removed deprecated AGENT-INJECTION.md");
9904
- }
9905
- const injectionContent = buildInjectionContent2({ references: ["@AGENTS.md"] });
9906
- const results = await injectAll2(providers, projectRoot, "project", injectionContent);
9907
- for (const [filePath, action] of results) {
9908
- const fileName = basename4(filePath);
9909
- actions.push(`${fileName} (${action})`);
10003
+ if (providers.length === 0) {
10004
+ actions.push("No providers detected (AGENTS.md created without provider injection)");
10005
+ } else {
10006
+ for (const provider of providers) {
10007
+ const instructFile = join28(projectRoot, provider.pathProject, provider.instructFile);
10008
+ await stripCLEOBlocks(instructFile);
10009
+ }
10010
+ await stripCLEOBlocks(join28(projectRoot, "AGENTS.md"));
10011
+ const removedStale = await removeStaleAgentInjection(projectRoot);
10012
+ if (removedStale) {
10013
+ actions.push("removed deprecated AGENT-INJECTION.md");
10014
+ }
10015
+ const injectionContent = buildInjectionContent2({ references: ["@AGENTS.md"] });
10016
+ const results = await injectAll2(providers, projectRoot, "project", injectionContent);
10017
+ for (const [filePath, action] of results) {
10018
+ const fileName = basename4(filePath);
10019
+ actions.push(`${fileName} (${action})`);
10020
+ }
9910
10021
  }
9911
10022
  const agentsMdPath = join28(projectRoot, "AGENTS.md");
9912
10023
  const agentsMdLines = ["@~/.cleo/templates/CLEO-INJECTION.md"];
@@ -9914,6 +10025,10 @@ async function ensureInjection(projectRoot) {
9914
10025
  if (existsSync26(projectContextPath)) {
9915
10026
  agentsMdLines.push("@.cleo/project-context.json");
9916
10027
  }
10028
+ const memoryBridgePath = join28(projectRoot, ".cleo", "memory-bridge.md");
10029
+ if (existsSync26(memoryBridgePath)) {
10030
+ agentsMdLines.push("@.cleo/memory-bridge.md");
10031
+ }
9917
10032
  const contributorBlock = buildContributorInjectionBlock(projectRoot);
9918
10033
  if (contributorBlock) {
9919
10034
  agentsMdLines.push(contributorBlock);
@@ -10114,6 +10229,7 @@ __export(scaffold_exports, {
10114
10229
  CLEO_GITIGNORE_FALLBACK: () => CLEO_GITIGNORE_FALLBACK,
10115
10230
  REQUIRED_CLEO_SUBDIRS: () => REQUIRED_CLEO_SUBDIRS,
10116
10231
  REQUIRED_GLOBAL_SUBDIRS: () => REQUIRED_GLOBAL_SUBDIRS,
10232
+ checkBrainDb: () => checkBrainDb,
10117
10233
  checkCleoGitRepo: () => checkCleoGitRepo,
10118
10234
  checkCleoStructure: () => checkCleoStructure,
10119
10235
  checkConfig: () => checkConfig,
@@ -10121,10 +10237,12 @@ __export(scaffold_exports, {
10121
10237
  checkGlobalHome: () => checkGlobalHome,
10122
10238
  checkGlobalTemplates: () => checkGlobalTemplates,
10123
10239
  checkLogDir: () => checkLogDir,
10240
+ checkMemoryBridge: () => checkMemoryBridge,
10124
10241
  checkProjectContext: () => checkProjectContext,
10125
10242
  checkProjectInfo: () => checkProjectInfo,
10126
10243
  checkSqliteDb: () => checkSqliteDb,
10127
10244
  createDefaultConfig: () => createDefaultConfig,
10245
+ ensureBrainDb: () => ensureBrainDb,
10128
10246
  ensureCleoGitRepo: () => ensureCleoGitRepo,
10129
10247
  ensureCleoStructure: () => ensureCleoStructure,
10130
10248
  ensureConfig: () => ensureConfig,
@@ -10739,6 +10857,79 @@ function checkSqliteDb(projectRoot) {
10739
10857
  fix: null
10740
10858
  };
10741
10859
  }
10860
+ async function ensureBrainDb(projectRoot) {
10861
+ const cleoDir = getCleoDirAbsolute(projectRoot);
10862
+ const dbPath = join29(cleoDir, "brain.db");
10863
+ if (existsSync27(dbPath)) {
10864
+ return { action: "skipped", path: dbPath, details: "brain.db already exists" };
10865
+ }
10866
+ try {
10867
+ const { getBrainDb: getBrainDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
10868
+ await getBrainDb2(projectRoot);
10869
+ return { action: "created", path: dbPath, details: "Brain database initialized" };
10870
+ } catch (err) {
10871
+ return {
10872
+ action: "skipped",
10873
+ path: dbPath,
10874
+ details: `Failed to initialize brain.db: ${err instanceof Error ? err.message : String(err)}`
10875
+ };
10876
+ }
10877
+ }
10878
+ function checkBrainDb(projectRoot) {
10879
+ const cleoDir = getCleoDirAbsolute(projectRoot);
10880
+ const dbPath = join29(cleoDir, "brain.db");
10881
+ if (!existsSync27(dbPath)) {
10882
+ return {
10883
+ id: "brain_db",
10884
+ category: "scaffold",
10885
+ status: "failed",
10886
+ message: "brain.db not found",
10887
+ details: { path: dbPath, exists: false },
10888
+ fix: "cleo init"
10889
+ };
10890
+ }
10891
+ const stat5 = statSync4(dbPath);
10892
+ if (stat5.size === 0) {
10893
+ return {
10894
+ id: "brain_db",
10895
+ category: "scaffold",
10896
+ status: "warning",
10897
+ message: "brain.db exists but is empty (0 bytes)",
10898
+ details: { path: dbPath, exists: true, size: 0 },
10899
+ fix: "cleo upgrade"
10900
+ };
10901
+ }
10902
+ return {
10903
+ id: "brain_db",
10904
+ category: "scaffold",
10905
+ status: "passed",
10906
+ message: `brain.db exists (${stat5.size} bytes)`,
10907
+ details: { path: dbPath, exists: true, size: stat5.size },
10908
+ fix: null
10909
+ };
10910
+ }
10911
+ function checkMemoryBridge(projectRoot) {
10912
+ const cleoDir = getCleoDirAbsolute(projectRoot);
10913
+ const bridgePath = join29(cleoDir, "memory-bridge.md");
10914
+ if (!existsSync27(bridgePath)) {
10915
+ return {
10916
+ id: "memory_bridge",
10917
+ category: "scaffold",
10918
+ status: "warning",
10919
+ message: "memory-bridge.md not found",
10920
+ details: { path: bridgePath, exists: false },
10921
+ fix: "cleo init or cleo refresh-memory"
10922
+ };
10923
+ }
10924
+ return {
10925
+ id: "memory_bridge",
10926
+ category: "scaffold",
10927
+ status: "passed",
10928
+ message: "memory-bridge.md exists",
10929
+ details: { path: bridgePath, exists: true },
10930
+ fix: null
10931
+ };
10932
+ }
10742
10933
  async function ensureGlobalHome() {
10743
10934
  const cleoHome = getCleoHome();
10744
10935
  const alreadyExists = existsSync27(cleoHome);
@@ -11924,6 +12115,28 @@ var init_registry2 = __esm({
11924
12115
  }
11925
12116
  });
11926
12117
 
12118
+ // src/core/adapters/adapter-registry.ts
12119
+ var ADAPTER_REGISTRY;
12120
+ var init_adapter_registry = __esm({
12121
+ "src/core/adapters/adapter-registry.ts"() {
12122
+ "use strict";
12123
+ ADAPTER_REGISTRY = {
12124
+ "claude-code": async () => {
12125
+ const { ClaudeCodeAdapter } = await import("@cleocode/adapter-claude-code");
12126
+ return new ClaudeCodeAdapter();
12127
+ },
12128
+ "opencode": async () => {
12129
+ const { OpenCodeAdapter } = await import("@cleocode/adapter-opencode");
12130
+ return new OpenCodeAdapter();
12131
+ },
12132
+ "cursor": async () => {
12133
+ const { CursorAdapter } = await import("@cleocode/adapter-cursor");
12134
+ return new CursorAdapter();
12135
+ }
12136
+ };
12137
+ }
12138
+ });
12139
+
11927
12140
  // src/core/adapters/discovery.ts
11928
12141
  import { execFileSync as execFileSync3 } from "node:child_process";
11929
12142
  import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync22 } from "node:fs";
@@ -11993,13 +12206,16 @@ var log4, AdapterManager;
11993
12206
  var init_manager = __esm({
11994
12207
  "src/core/adapters/manager.ts"() {
11995
12208
  "use strict";
12209
+ init_registry();
11996
12210
  init_logger();
12211
+ init_adapter_registry();
11997
12212
  init_discovery();
11998
12213
  log4 = getLogger("adapter-manager");
11999
12214
  AdapterManager = class _AdapterManager {
12000
12215
  static instance = null;
12001
12216
  adapters = /* @__PURE__ */ new Map();
12002
12217
  manifests = /* @__PURE__ */ new Map();
12218
+ hookCleanups = /* @__PURE__ */ new Map();
12003
12219
  activeId = null;
12004
12220
  projectRoot;
12005
12221
  constructor(projectRoot) {
@@ -12043,7 +12259,7 @@ var init_manager = __esm({
12043
12259
  }
12044
12260
  /**
12045
12261
  * Load and initialize an adapter by manifest ID.
12046
- * The adapter module is dynamically imported from the manifest's entryPoint.
12262
+ * Uses the static adapter registry for reliable bundled operation.
12047
12263
  */
12048
12264
  async activate(adapterId) {
12049
12265
  const manifest = this.manifests.get(adapterId);
@@ -12055,20 +12271,18 @@ var init_manager = __esm({
12055
12271
  this.activeId = adapterId;
12056
12272
  return existing;
12057
12273
  }
12058
- const { resolve: resolve12 } = await import("node:path");
12059
- const entryPath = resolve12(
12060
- this.projectRoot,
12061
- "packages",
12062
- "adapters",
12063
- manifest.provider,
12064
- manifest.entryPoint
12065
- );
12274
+ const factory = ADAPTER_REGISTRY[adapterId];
12275
+ if (!factory) {
12276
+ throw new Error(`No adapter registered in static registry: ${adapterId}`);
12277
+ }
12066
12278
  try {
12067
- const mod = await import(entryPath);
12068
- const adapter = typeof mod.default === "function" ? new mod.default() : typeof mod.createAdapter === "function" ? await mod.createAdapter() : mod.default;
12279
+ const adapter = await factory();
12069
12280
  await adapter.initialize(this.projectRoot);
12070
12281
  this.adapters.set(adapterId, adapter);
12071
12282
  this.activeId = adapterId;
12283
+ if (adapter.hooks) {
12284
+ await this.wireAdapterHooks(adapterId, adapter);
12285
+ }
12072
12286
  log4.info({ adapterId, provider: manifest.provider }, "Adapter activated");
12073
12287
  return adapter;
12074
12288
  } catch (err) {
@@ -12150,6 +12364,7 @@ var init_manager = __esm({
12150
12364
  async dispose() {
12151
12365
  for (const [id, adapter] of this.adapters) {
12152
12366
  try {
12367
+ await this.cleanupAdapterHooks(id, adapter);
12153
12368
  await adapter.dispose();
12154
12369
  log4.info({ adapterId: id }, "Adapter disposed");
12155
12370
  } catch (err) {
@@ -12157,6 +12372,7 @@ var init_manager = __esm({
12157
12372
  }
12158
12373
  }
12159
12374
  this.adapters.clear();
12375
+ this.hookCleanups.clear();
12160
12376
  this.activeId = null;
12161
12377
  }
12162
12378
  /** Dispose a single adapter. */
@@ -12164,6 +12380,7 @@ var init_manager = __esm({
12164
12380
  const adapter = this.adapters.get(adapterId);
12165
12381
  if (!adapter) return;
12166
12382
  try {
12383
+ await this.cleanupAdapterHooks(adapterId, adapter);
12167
12384
  await adapter.dispose();
12168
12385
  } catch (err) {
12169
12386
  log4.error({ adapterId, err }, "Failed to dispose adapter");
@@ -12173,6 +12390,51 @@ var init_manager = __esm({
12173
12390
  this.activeId = null;
12174
12391
  }
12175
12392
  }
12393
+ /**
12394
+ * Wire an adapter's hook event map into CLEO's HookRegistry.
12395
+ * Creates bridging handlers at priority 50 for each mapped event.
12396
+ */
12397
+ async wireAdapterHooks(adapterId, adapter) {
12398
+ if (!adapter.hooks) return;
12399
+ try {
12400
+ await adapter.hooks.registerNativeHooks(this.projectRoot);
12401
+ } catch (err) {
12402
+ log4.error({ adapterId, err }, "Failed to register native hooks");
12403
+ }
12404
+ const eventMap = adapter.hooks.getEventMap?.();
12405
+ if (!eventMap) return;
12406
+ const cleanups = [];
12407
+ for (const [_providerEvent, caampEvent] of Object.entries(eventMap)) {
12408
+ const hookId = `adapter-${adapterId}-${caampEvent}`;
12409
+ const unregister = hooks.register({
12410
+ id: hookId,
12411
+ event: caampEvent,
12412
+ priority: 50,
12413
+ handler: async (_projectRoot, payload) => {
12414
+ log4.debug({ adapterId, event: caampEvent, payload }, "Adapter hook dispatched");
12415
+ }
12416
+ });
12417
+ cleanups.push(unregister);
12418
+ }
12419
+ this.hookCleanups.set(adapterId, cleanups);
12420
+ }
12421
+ /**
12422
+ * Clean up hook registrations for an adapter.
12423
+ */
12424
+ async cleanupAdapterHooks(adapterId, adapter) {
12425
+ const cleanups = this.hookCleanups.get(adapterId);
12426
+ if (cleanups) {
12427
+ for (const fn of cleanups) {
12428
+ fn();
12429
+ }
12430
+ this.hookCleanups.delete(adapterId);
12431
+ }
12432
+ try {
12433
+ await adapter.hooks?.unregisterNativeHooks();
12434
+ } catch (err) {
12435
+ log4.error({ adapterId, err }, "Failed to unregister native hooks");
12436
+ }
12437
+ }
12176
12438
  };
12177
12439
  }
12178
12440
  });
@@ -12180,6 +12442,7 @@ var init_manager = __esm({
12180
12442
  // src/core/adapters/index.ts
12181
12443
  var adapters_exports = {};
12182
12444
  __export(adapters_exports, {
12445
+ ADAPTER_REGISTRY: () => ADAPTER_REGISTRY,
12183
12446
  AdapterManager: () => AdapterManager,
12184
12447
  detectProvider: () => detectProvider,
12185
12448
  discoverAdapterManifests: () => discoverAdapterManifests
@@ -12187,6 +12450,7 @@ __export(adapters_exports, {
12187
12450
  var init_adapters = __esm({
12188
12451
  "src/core/adapters/index.ts"() {
12189
12452
  "use strict";
12453
+ init_adapter_registry();
12190
12454
  init_manager();
12191
12455
  init_discovery();
12192
12456
  }
@@ -12410,6 +12674,14 @@ async function initProject(opts = {}) {
12410
12674
  } catch (err) {
12411
12675
  created.push(`tasks.db (deferred: ${err instanceof Error ? err.message : String(err)})`);
12412
12676
  }
12677
+ try {
12678
+ const brainResult = await ensureBrainDb(projRoot);
12679
+ if (brainResult.action === "created") {
12680
+ created.push("brain.db");
12681
+ }
12682
+ } catch (err) {
12683
+ created.push(`brain.db (deferred: ${err instanceof Error ? err.message : String(err)})`);
12684
+ }
12413
12685
  if (force) {
12414
12686
  const gitignoreResult = await ensureGitignore(projRoot);
12415
12687
  if (gitignoreResult.action === "skipped") {
@@ -12499,6 +12771,14 @@ async function initProject(opts = {}) {
12499
12771
  } catch (err) {
12500
12772
  warnings.push(`Project detection failed: ${err instanceof Error ? err.message : String(err)}`);
12501
12773
  }
12774
+ try {
12775
+ const bridgeResult = await writeMemoryBridge(projRoot);
12776
+ if (bridgeResult.written) {
12777
+ created.push("memory-bridge.md");
12778
+ }
12779
+ } catch (err) {
12780
+ warnings.push(`Memory bridge: ${err instanceof Error ? err.message : String(err)}`);
12781
+ }
12502
12782
  try {
12503
12783
  const injectionResult = await ensureInjection(projRoot);
12504
12784
  if (injectionResult.action !== "skipped") {
@@ -12532,6 +12812,21 @@ async function initProject(opts = {}) {
12532
12812
  const detected = mgr.detectActive();
12533
12813
  if (detected.length > 0) {
12534
12814
  created.push(`adapters: active provider detected (${detected.join(", ")})`);
12815
+ for (const adapterId of detected) {
12816
+ try {
12817
+ const adapter = await mgr.activate(adapterId);
12818
+ const installResult = await adapter.install.install({
12819
+ projectDir: projRoot
12820
+ });
12821
+ if (installResult.success) {
12822
+ created.push(`adapter install (${adapterId}): installed`);
12823
+ } else {
12824
+ warnings.push(`adapter install (${adapterId}): failed`);
12825
+ }
12826
+ } catch (err) {
12827
+ warnings.push(`adapter activate/install (${adapterId}): ${err instanceof Error ? err.message : String(err)}`);
12828
+ }
12829
+ }
12535
12830
  }
12536
12831
  }
12537
12832
  } catch (err) {
@@ -12598,6 +12893,7 @@ var init_init = __esm({
12598
12893
  init_paths();
12599
12894
  init_scaffold();
12600
12895
  init_schema_management();
12896
+ init_memory_bridge();
12601
12897
  }
12602
12898
  });
12603
12899
 
@@ -14546,6 +14842,13 @@ async function startSession(options, cwd, accessor) {
14546
14842
  );
14547
14843
  }
14548
14844
  }
14845
+ let detectedProviderId = null;
14846
+ try {
14847
+ const { detectRuntimeProviderContext: detectRuntimeProviderContext2 } = await Promise.resolve().then(() => (init_provider_detection(), provider_detection_exports));
14848
+ const ctx = detectRuntimeProviderContext2();
14849
+ detectedProviderId = ctx.runtimeProviderId ?? null;
14850
+ } catch {
14851
+ }
14549
14852
  const session = {
14550
14853
  id: generateSessionId(),
14551
14854
  name: options.name,
@@ -14560,7 +14863,7 @@ async function startSession(options, cwd, accessor) {
14560
14863
  notes: [],
14561
14864
  tasksCompleted: [],
14562
14865
  tasksCreated: [],
14563
- providerId: options.providerId ?? null
14866
+ providerId: options.providerId ?? detectedProviderId ?? null
14564
14867
  };
14565
14868
  if (options.grade) {
14566
14869
  session.notes = ["[grade-mode:enabled]", ...session.notes ?? []];
@@ -14570,13 +14873,22 @@ async function startSession(options, cwd, accessor) {
14570
14873
  }
14571
14874
  sessions2.push(session);
14572
14875
  await saveSessions(sessions2, cwd, accessor);
14876
+ if (session.providerId) {
14877
+ Promise.resolve().then(() => (init_adapters(), adapters_exports)).then(({ AdapterManager: AdapterManager2 }) => {
14878
+ const mgr = AdapterManager2.getInstance(cwd ?? process.cwd());
14879
+ mgr.discover();
14880
+ return mgr.activate(session.providerId);
14881
+ }).catch(() => {
14882
+ });
14883
+ }
14573
14884
  const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
14574
14885
  hooks2.dispatch("onSessionStart", cwd ?? process.cwd(), {
14575
14886
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14576
14887
  sessionId: session.id,
14577
14888
  name: options.name,
14578
14889
  scope,
14579
- agent: options.agent
14890
+ agent: options.agent,
14891
+ providerId: session.providerId ?? void 0
14580
14892
  }).catch(() => {
14581
14893
  });
14582
14894
  return session;
@@ -14612,7 +14924,8 @@ async function endSession(options = {}, cwd, accessor) {
14612
14924
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14613
14925
  sessionId: session.id,
14614
14926
  duration,
14615
- tasksCompleted: session.tasksCompleted || []
14927
+ tasksCompleted: session.tasksCompleted || [],
14928
+ providerId: session.providerId ?? void 0
14616
14929
  }).catch(() => {
14617
14930
  });
14618
14931
  const { bridgeSessionToMemory: bridgeSessionToMemory2 } = await Promise.resolve().then(() => (init_session_memory_bridge(), session_memory_bridge_exports));
@@ -15130,7 +15443,7 @@ async function initializeDefaultAdapters() {
15130
15443
  }
15131
15444
  }
15132
15445
  var SpawnAdapterRegistry, spawnRegistry;
15133
- var init_adapter_registry = __esm({
15446
+ var init_adapter_registry2 = __esm({
15134
15447
  "src/core/spawn/adapter-registry.ts"() {
15135
15448
  "use strict";
15136
15449
  SpawnAdapterRegistry = class {
@@ -16359,6 +16672,18 @@ async function startupHealthCheck(projectRoot) {
16359
16672
  } else {
16360
16673
  checks.push({ check: "log_dir", status: "pass", message: "Log directory present" });
16361
16674
  }
16675
+ const brainDbCheck = checkBrainDb(root);
16676
+ checks.push({
16677
+ check: "brain_db",
16678
+ status: brainDbCheck.status === "passed" ? "pass" : "warn",
16679
+ message: brainDbCheck.message
16680
+ });
16681
+ const memBridgeCheck = checkMemoryBridge(root);
16682
+ checks.push({
16683
+ check: "memory_bridge",
16684
+ status: memBridgeCheck.status === "passed" ? "pass" : "warn",
16685
+ message: memBridgeCheck.message
16686
+ });
16362
16687
  const hasFailures = failures.length > 0;
16363
16688
  const state = hasFailures && !projectHealthy ? "needs_upgrade" : "healthy";
16364
16689
  return {
@@ -16567,7 +16892,7 @@ async function loadCompletionEnforcement(cwd) {
16567
16892
  const lifecycleModeRaw = await getRawConfigValue("lifecycle.mode", cwd);
16568
16893
  const acceptanceMode = modeRaw === "off" || modeRaw === "warn" || modeRaw === "block" ? modeRaw : "warn";
16569
16894
  const acceptanceRequiredForPriorities = Array.isArray(prioritiesRaw) ? prioritiesRaw.filter((p) => typeof p === "string") : ["critical", "high"];
16570
- const verificationEnabled = verificationEnabledRaw !== false;
16895
+ const verificationEnabled = verificationEnabledRaw === true;
16571
16896
  const verificationRequiredGates = Array.isArray(verificationRequiredGatesRaw) ? verificationRequiredGatesRaw.filter((g) => typeof g === "string").filter(isVerificationGate) : DEFAULT_VERIFICATION_REQUIRED_GATES;
16572
16897
  const verificationMaxRounds = typeof verificationMaxRoundsRaw === "number" && Number.isInteger(verificationMaxRoundsRaw) ? verificationMaxRoundsRaw : 5;
16573
16898
  const lifecycleMode = lifecycleModeRaw === "strict" || lifecycleModeRaw === "warn" || lifecycleModeRaw === "advisory" || lifecycleModeRaw === "none" || lifecycleModeRaw === "off" ? lifecycleModeRaw : "off";
@@ -20625,104 +20950,8 @@ async function resolveProviderFromModelRegistry(model) {
20625
20950
  return resolveProviderFromModelIndex(index5, model);
20626
20951
  }
20627
20952
 
20628
- // src/core/metrics/provider-detection.ts
20629
- import { basename as basename3 } from "node:path";
20630
- import {
20631
- detectProjectProviders,
20632
- getProvider,
20633
- resolveAlias
20634
- } from "@cleocode/caamp";
20635
- function inferProviderFromVendor(vendor) {
20636
- const value = (vendor ?? "").trim().toLowerCase();
20637
- if (!value) return void 0;
20638
- if (value.includes("anthropic") || value.includes("claude")) return "anthropic";
20639
- if (value.includes("openai") || value.includes("codex") || value.includes("chatgpt"))
20640
- return "openai";
20641
- if (value.includes("google") || value.includes("gemini")) return "google";
20642
- if (value.includes("xai") || value.includes("grok")) return "xai";
20643
- return void 0;
20644
- }
20645
- function getRuntimeHints(snapshot) {
20646
- const argv = snapshot.argv ?? process.argv;
20647
- const env = snapshot.env ?? process.env;
20648
- const hints = /* @__PURE__ */ new Set();
20649
- const bin = basename3(argv[1] ?? argv[0] ?? "").replace(/\.[^.]+$/, "");
20650
- if (bin) hints.add(bin);
20651
- if (env["CLAUDE_CODE_ENABLE_TELEMETRY"] || env["CLAUDE_CODE_ENTRYPOINT"]) {
20652
- hints.add("claude-code");
20653
- hints.add("claude");
20654
- }
20655
- if (env["OPENCODE_AGENT"] || env["OPENCODE"]) {
20656
- hints.add("opencode");
20657
- }
20658
- if (env["CURSOR_TRACE_ID"] || env["CURSOR_AGENT"]) {
20659
- hints.add("cursor");
20660
- }
20661
- return Array.from(hints);
20662
- }
20663
- function pickDetectionByHint(detections, hints) {
20664
- for (const hint of hints) {
20665
- const resolved = resolveAlias(hint);
20666
- const direct = detections.find(
20667
- (entry) => entry.provider.id === resolved || entry.provider.id === hint
20668
- );
20669
- if (direct) return direct;
20670
- const byAlias = detections.find(
20671
- (entry) => entry.provider.aliases.includes(hint) || entry.provider.agentFlag === hint || entry.provider.toolName.toLowerCase() === hint.toLowerCase()
20672
- );
20673
- if (byAlias) return byAlias;
20674
- const provider = getProvider(resolved);
20675
- if (provider) {
20676
- return {
20677
- provider,
20678
- installed: true,
20679
- methods: [],
20680
- projectDetected: false
20681
- };
20682
- }
20683
- }
20684
- return null;
20685
- }
20686
- function selectRuntimeProviderContext(detections, snapshot = {}) {
20687
- const hints = getRuntimeHints(snapshot);
20688
- const hinted = pickDetectionByHint(detections, hints);
20689
- const projectMatches = detections.filter((entry) => entry.projectDetected);
20690
- const installed = detections.filter((entry) => entry.installed);
20691
- const selected = hinted ?? (projectMatches.length === 1 ? projectMatches[0] : null) ?? (installed.length === 1 ? installed[0] : null);
20692
- if (!selected) {
20693
- return {
20694
- runtimeCandidates: installed.map((entry) => entry.provider.id)
20695
- };
20696
- }
20697
- return {
20698
- runtimeProviderId: selected.provider.id,
20699
- runtimeToolName: selected.provider.toolName,
20700
- runtimeVendor: selected.provider.vendor,
20701
- runtimeInstructionFile: selected.provider.instructFile,
20702
- runtimeProjectDetected: selected.projectDetected,
20703
- runtimeDetectionMethods: selected.methods,
20704
- runtimeCandidates: installed.map((entry) => entry.provider.id),
20705
- inferredModelProvider: inferProviderFromVendor(selected.provider.vendor)
20706
- };
20707
- }
20708
- var cachedRuntimeProvider = null;
20709
- function detectRuntimeProviderContext(snapshot = {}) {
20710
- if (!snapshot.cwd && !snapshot.argv && !snapshot.env && cachedRuntimeProvider) {
20711
- return cachedRuntimeProvider;
20712
- }
20713
- try {
20714
- const detections = detectProjectProviders(snapshot.cwd ?? process.cwd());
20715
- const context = selectRuntimeProviderContext(detections, snapshot);
20716
- if (!snapshot.cwd && !snapshot.argv && !snapshot.env) {
20717
- cachedRuntimeProvider = context;
20718
- }
20719
- return context;
20720
- } catch {
20721
- return {};
20722
- }
20723
- }
20724
-
20725
20953
  // src/core/metrics/token-service.ts
20954
+ init_provider_detection();
20726
20955
  function normalizeProvider(provider, model, runtimeProvider) {
20727
20956
  const value = (provider ?? "").trim().toLowerCase();
20728
20957
  const modelValue = (model ?? "").trim().toLowerCase();
@@ -27745,6 +27974,7 @@ function injectContext(protocolType, params, projectRoot) {
27745
27974
  // src/dispatch/engines/session-engine.ts
27746
27975
  init_handoff();
27747
27976
  init_sessions();
27977
+ init_sessions();
27748
27978
 
27749
27979
  // src/core/sessions/session-id.ts
27750
27980
  import { randomBytes as randomBytes7 } from "node:crypto";
@@ -27893,29 +28123,31 @@ async function sessionStart(projectRoot, params) {
27893
28123
  try {
27894
28124
  let accessor = await getAccessor(projectRoot);
27895
28125
  let taskData = await accessor.loadTaskFile();
28126
+ let scope;
28127
+ try {
28128
+ scope = parseScope2(params.scope);
28129
+ } catch (err) {
28130
+ return engineError("E_INVALID_INPUT", err instanceof Error ? err.message : "Invalid scope");
28131
+ }
28132
+ if (scope.type !== "global") {
28133
+ const rootTask = taskData.tasks?.find((t) => t.id === scope.rootTaskId);
28134
+ if (!rootTask) {
28135
+ return engineError("E_NOT_FOUND", `Root task '${scope.rootTaskId}' not found`);
28136
+ }
28137
+ }
27896
28138
  const activeSessionId = taskData._meta?.activeSession;
27897
28139
  if (activeSessionId) {
27898
28140
  await sessionEnd(projectRoot);
27899
28141
  accessor = await getAccessor(projectRoot);
27900
28142
  taskData = await accessor.loadTaskFile();
27901
28143
  }
27902
- const scopeParts = params.scope.split(":");
27903
- const scopeType = scopeParts[0] || "task";
27904
- const rootTaskId = scopeParts[1] || "";
27905
- if (!rootTaskId) {
27906
- return engineError("E_INVALID_INPUT", "Scope must include a task ID (e.g., epic:T001)");
27907
- }
27908
- const rootTask = taskData.tasks?.find((t) => t.id === rootTaskId);
27909
- if (!rootTask) {
27910
- return engineError("E_NOT_FOUND", `Root task '${rootTaskId}' not found`);
27911
- }
27912
28144
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
27913
28145
  const sessionId = generateSessionId2();
27914
28146
  let previousSessionId = null;
27915
28147
  {
27916
28148
  const sessions2 = await accessor.loadSessions();
27917
28149
  const sameScope = sessions2.filter(
27918
- (s) => s.status === "ended" && s.endedAt && s.scope?.rootTaskId === rootTaskId && s.scope?.type === scopeType
28150
+ (s) => s.status === "ended" && s.endedAt && s.scope?.type === scope.type && (scope.type === "global" || s.scope?.rootTaskId === scope.rootTaskId)
27919
28151
  ).sort(
27920
28152
  (a, b) => new Date(b.endedAt).getTime() - new Date(a.endedAt).getTime()
27921
28153
  );
@@ -27924,16 +28156,13 @@ async function sessionStart(projectRoot, params) {
27924
28156
  }
27925
28157
  }
27926
28158
  const agentIdentifier = params.agentIdentifier ?? process.env.CLEO_AGENT_ID ?? null;
27927
- const startingTaskId = params.startTask || (params.autoStart ? rootTaskId : null);
28159
+ const rootTaskId = scope.type !== "global" ? scope.rootTaskId : void 0;
28160
+ const startingTaskId = params.startTask || (params.autoStart && rootTaskId ? rootTaskId : null);
27928
28161
  const newSession = {
27929
28162
  id: sessionId,
27930
28163
  status: "active",
27931
28164
  name: params.name || `session-${sessionId}`,
27932
- scope: {
27933
- type: scopeType,
27934
- rootTaskId,
27935
- includeDescendants: true
27936
- },
28165
+ scope: scope.type === "global" ? { type: "global" } : { type: scope.type, rootTaskId: scope.rootTaskId, includeDescendants: true },
27937
28166
  taskWork: {
27938
28167
  taskId: startingTaskId,
27939
28168
  setAt: now2
@@ -27964,7 +28193,7 @@ async function sessionStart(projectRoot, params) {
27964
28193
  const startingTask = params.startTask;
27965
28194
  if (startingTask) {
27966
28195
  taskData.focus.currentTask = startingTask;
27967
- } else if (params.autoStart) {
28196
+ } else if (params.autoStart && rootTaskId) {
27968
28197
  taskData.focus.currentTask = rootTaskId;
27969
28198
  }
27970
28199
  if (taskData._meta) {
@@ -28570,7 +28799,7 @@ async function orchestrateValidate(taskId, projectRoot) {
28570
28799
  async function orchestrateSpawnExecute(taskId, adapterId, protocolType, projectRoot, _tier) {
28571
28800
  const cwd = projectRoot ?? process.cwd();
28572
28801
  try {
28573
- const { initializeDefaultAdapters: initializeDefaultAdapters2, spawnRegistry: spawnRegistry2 } = await Promise.resolve().then(() => (init_adapter_registry(), adapter_registry_exports));
28802
+ const { initializeDefaultAdapters: initializeDefaultAdapters2, spawnRegistry: spawnRegistry2 } = await Promise.resolve().then(() => (init_adapter_registry2(), adapter_registry_exports));
28574
28803
  await initializeDefaultAdapters2();
28575
28804
  let adapter;
28576
28805
  if (adapterId) {
@@ -29699,7 +29928,10 @@ async function checkEpicCompleteness(releaseTaskIds, cwd, accessor) {
29699
29928
  if (!epic) continue;
29700
29929
  const allChildren = data.tasks.filter((t) => t.parentId === epicId && t.id !== epicId);
29701
29930
  const includedSet = new Set(includedTasks);
29702
- const missingChildren = allChildren.filter((t) => !includedSet.has(t.id) && !releaseSet.has(t.id)).map((t) => ({ id: t.id, title: t.title, status: t.status }));
29931
+ const parentIds = new Set(data.tasks.filter((t) => t.parentId).map((t) => t.parentId));
29932
+ const missingChildren = allChildren.filter(
29933
+ (t) => t.status === "done" && !parentIds.has(t.id) && !includedSet.has(t.id) && !releaseSet.has(t.id)
29934
+ ).map((t) => ({ id: t.id, title: t.title, status: t.status }));
29703
29935
  if (missingChildren.length > 0) hasIncomplete = true;
29704
29936
  epics.push({
29705
29937
  epicId,
@@ -29856,9 +30088,9 @@ init_tasks_schema();
29856
30088
  init_pagination();
29857
30089
  init_paths();
29858
30090
  import { execFileSync as execFileSync5 } from "node:child_process";
29859
- import { existsSync as existsSync42, renameSync as renameSync7 } from "node:fs";
30091
+ import { existsSync as existsSync43, renameSync as renameSync7 } from "node:fs";
29860
30092
  import { readFile as readFile11 } from "node:fs/promises";
29861
- import { join as join43 } from "node:path";
30093
+ import { join as join44 } from "node:path";
29862
30094
  import { and as and6, count as count4, desc as desc5, eq as eq15 } from "drizzle-orm";
29863
30095
 
29864
30096
  // src/core/release/changelog-writer.ts
@@ -29952,6 +30184,170 @@ function escapeRegex(str) {
29952
30184
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
29953
30185
  }
29954
30186
 
30187
+ // src/core/release/version-bump.ts
30188
+ init_exit_codes();
30189
+ init_errors();
30190
+ init_paths();
30191
+ import { existsSync as existsSync42, readFileSync as readFileSync29, writeFileSync as writeFileSync7 } from "node:fs";
30192
+ import { join as join43 } from "node:path";
30193
+ function readConfigValueSync2(path, defaultValue, cwd) {
30194
+ try {
30195
+ const configPath = join43(getCleoDir(cwd), "config.json");
30196
+ if (!existsSync42(configPath)) return defaultValue;
30197
+ const config = JSON.parse(readFileSync29(configPath, "utf-8"));
30198
+ const keys = path.split(".");
30199
+ let value = config;
30200
+ for (const key of keys) {
30201
+ if (value == null || typeof value !== "object") return defaultValue;
30202
+ value = value[key];
30203
+ }
30204
+ return value ?? defaultValue;
30205
+ } catch {
30206
+ return defaultValue;
30207
+ }
30208
+ }
30209
+ var VERSION_WITH_PRERELEASE = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
30210
+ function validateVersionFormat(version) {
30211
+ return VERSION_WITH_PRERELEASE.test(version);
30212
+ }
30213
+ function getVersionBumpConfig(cwd) {
30214
+ try {
30215
+ const raw = readConfigValueSync2("release.versionBump.files", [], cwd);
30216
+ return raw.map((entry) => ({
30217
+ file: entry.path ?? entry.file ?? "",
30218
+ strategy: entry.strategy,
30219
+ field: entry.jsonPath?.replace(/^\./, "") ?? entry.field,
30220
+ key: entry.key,
30221
+ section: entry.section,
30222
+ pattern: entry.sedPattern ?? entry.pattern
30223
+ })).filter((t) => t.file !== "");
30224
+ } catch {
30225
+ return [];
30226
+ }
30227
+ }
30228
+ function bumpFile(target, newVersion, projectRoot) {
30229
+ const filePath = join43(projectRoot, target.file);
30230
+ if (!existsSync42(filePath)) {
30231
+ return {
30232
+ file: target.file,
30233
+ strategy: target.strategy,
30234
+ success: false,
30235
+ error: `File not found: ${target.file}`
30236
+ };
30237
+ }
30238
+ try {
30239
+ const content = readFileSync29(filePath, "utf-8");
30240
+ let previousVersion;
30241
+ let newContent;
30242
+ switch (target.strategy) {
30243
+ case "plain": {
30244
+ previousVersion = content.trim();
30245
+ newContent = newVersion + "\n";
30246
+ break;
30247
+ }
30248
+ case "json": {
30249
+ const field = target.field ?? "version";
30250
+ const json = JSON.parse(content);
30251
+ previousVersion = getNestedField(json, field);
30252
+ setNestedField(json, field, newVersion);
30253
+ newContent = JSON.stringify(json, null, 2) + "\n";
30254
+ break;
30255
+ }
30256
+ case "toml": {
30257
+ const key = target.key ?? "version";
30258
+ const versionRegex = new RegExp(`^(${key}\\s*=\\s*")([^"]+)(")`, "m");
30259
+ const match = content.match(versionRegex);
30260
+ previousVersion = match?.[2];
30261
+ newContent = content.replace(versionRegex, `$1${newVersion}$3`);
30262
+ break;
30263
+ }
30264
+ case "sed": {
30265
+ const pattern = target.pattern ?? "";
30266
+ if (!pattern.includes("{{VERSION}}")) {
30267
+ return {
30268
+ file: target.file,
30269
+ strategy: target.strategy,
30270
+ success: false,
30271
+ error: "sed strategy requires {{VERSION}} placeholder in pattern"
30272
+ };
30273
+ }
30274
+ const regex = new RegExp(pattern.replace("{{VERSION}}", "([\\d.]+)"));
30275
+ const match = content.match(regex);
30276
+ previousVersion = match?.[1];
30277
+ newContent = content.replace(regex, pattern.replace("{{VERSION}}", newVersion));
30278
+ break;
30279
+ }
30280
+ default:
30281
+ return {
30282
+ file: target.file,
30283
+ strategy: target.strategy,
30284
+ success: false,
30285
+ error: `Unknown strategy: ${target.strategy}`
30286
+ };
30287
+ }
30288
+ writeFileSync7(filePath, newContent, "utf-8");
30289
+ return {
30290
+ file: target.file,
30291
+ strategy: target.strategy,
30292
+ success: true,
30293
+ previousVersion,
30294
+ newVersion
30295
+ };
30296
+ } catch (err) {
30297
+ return {
30298
+ file: target.file,
30299
+ strategy: target.strategy,
30300
+ success: false,
30301
+ error: String(err)
30302
+ };
30303
+ }
30304
+ }
30305
+ function bumpVersionFromConfig(newVersion, options = {}, cwd) {
30306
+ if (!validateVersionFormat(newVersion)) {
30307
+ throw new CleoError(
30308
+ 6 /* VALIDATION_ERROR */,
30309
+ `Invalid version: '${newVersion}' (expected X.Y.Z or YYYY.M.patch)`
30310
+ );
30311
+ }
30312
+ const targets = getVersionBumpConfig(cwd);
30313
+ if (targets.length === 0) {
30314
+ throw new CleoError(
30315
+ 1 /* GENERAL_ERROR */,
30316
+ "No version bump targets configured. Add release.versionBump.files to .cleo/config.json"
30317
+ );
30318
+ }
30319
+ const projectRoot = getProjectRoot(cwd);
30320
+ const results = [];
30321
+ if (options.dryRun) {
30322
+ for (const target of targets) {
30323
+ results.push({
30324
+ file: target.file,
30325
+ strategy: target.strategy,
30326
+ success: true,
30327
+ newVersion
30328
+ });
30329
+ }
30330
+ return { results, allSuccess: true };
30331
+ }
30332
+ for (const target of targets) {
30333
+ results.push(bumpFile(target, newVersion, projectRoot));
30334
+ }
30335
+ const allSuccess = results.every((r) => r.success);
30336
+ return { results, allSuccess };
30337
+ }
30338
+ function getNestedField(obj, path) {
30339
+ return path.split(".").reduce((acc, key) => acc?.[key], obj);
30340
+ }
30341
+ function setNestedField(obj, path, value) {
30342
+ const parts = path.split(".");
30343
+ let current = obj;
30344
+ for (let i = 0; i < parts.length - 1; i++) {
30345
+ if (typeof current[parts[i]] !== "object") current[parts[i]] = {};
30346
+ current = current[parts[i]];
30347
+ }
30348
+ current[parts[parts.length - 1]] = value;
30349
+ }
30350
+
29955
30351
  // src/core/release/release-manifest.ts
29956
30352
  function normalizeLimit2(limit) {
29957
30353
  return typeof limit === "number" && limit > 0 ? limit : void 0;
@@ -30177,7 +30573,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
30177
30573
  }
30178
30574
  const changelog = sections.join("\n");
30179
30575
  await db.update(releaseManifests).set({ changelog }).where(eq15(releaseManifests.version, normalizedVersion)).run();
30180
- const changelogPath = join43(cwd ?? process.cwd(), "CHANGELOG.md");
30576
+ const changelogPath = join44(cwd ?? process.cwd(), "CHANGELOG.md");
30181
30577
  let existingChangelogContent = "";
30182
30578
  try {
30183
30579
  existingChangelogContent = await readFile11(changelogPath, "utf8");
@@ -30291,13 +30687,13 @@ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
30291
30687
  message: incompleteTasks.length === 0 ? "All tasks completed" : `${incompleteTasks.length} tasks not completed: ${incompleteTasks.join(", ")}`
30292
30688
  });
30293
30689
  const projectRoot = cwd ?? getProjectRoot();
30294
- const distPath = join43(projectRoot, "dist", "cli", "index.js");
30295
- const isNodeProject = existsSync42(join43(projectRoot, "package.json"));
30690
+ const distPath = join44(projectRoot, "dist", "cli", "index.js");
30691
+ const isNodeProject = existsSync43(join44(projectRoot, "package.json"));
30296
30692
  if (isNodeProject) {
30297
30693
  gates.push({
30298
30694
  name: "build_artifact",
30299
- status: existsSync42(distPath) ? "passed" : "failed",
30300
- message: existsSync42(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
30695
+ status: existsSync43(distPath) ? "passed" : "failed",
30696
+ message: existsSync43(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
30301
30697
  });
30302
30698
  }
30303
30699
  if (opts?.dryRun) {
@@ -30307,6 +30703,8 @@ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
30307
30703
  message: "Skipped in dry-run mode"
30308
30704
  });
30309
30705
  } else {
30706
+ const bumpTargets = getVersionBumpConfig(cwd);
30707
+ const allowedDirty = /* @__PURE__ */ new Set(["CHANGELOG.md", ...bumpTargets.map((t) => t.file)]);
30310
30708
  let workingTreeClean = true;
30311
30709
  let dirtyFiles = [];
30312
30710
  try {
@@ -30315,14 +30713,15 @@ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
30315
30713
  encoding: "utf-8",
30316
30714
  stdio: "pipe"
30317
30715
  });
30318
- dirtyFiles = porcelain.split("\n").filter((l) => l.trim()).filter((l) => !l.startsWith("?? ")).map((l) => l.slice(3).trim()).filter((f) => f !== "CHANGELOG.md" && f !== "VERSION" && f !== "package.json");
30716
+ dirtyFiles = porcelain.split("\n").filter((l) => l.trim()).filter((l) => !l.startsWith("?? ")).map((l) => l.slice(3).trim()).filter((f) => !allowedDirty.has(f));
30319
30717
  workingTreeClean = dirtyFiles.length === 0;
30320
30718
  } catch {
30321
30719
  }
30720
+ const excludeList = [...allowedDirty].join(", ");
30322
30721
  gates.push({
30323
30722
  name: "clean_working_tree",
30324
30723
  status: workingTreeClean ? "passed" : "failed",
30325
- message: workingTreeClean ? "Working tree clean (excluding CHANGELOG.md, VERSION, package.json)" : `Uncommitted changes in: ${dirtyFiles.slice(0, 5).join(", ")}${dirtyFiles.length > 5 ? ` (+${dirtyFiles.length - 5} more)` : ""}`
30724
+ message: workingTreeClean ? `Working tree clean (excluding ${excludeList})` : `Uncommitted changes in: ${dirtyFiles.slice(0, 5).join(", ")}${dirtyFiles.length > 5 ? ` (+${dirtyFiles.length - 5} more)` : ""}`
30326
30725
  });
30327
30726
  }
30328
30727
  const isPreRelease = normalizedVersion.includes("-");
@@ -30437,7 +30836,7 @@ async function rollbackRelease(version, reason, cwd) {
30437
30836
  };
30438
30837
  }
30439
30838
  async function readPushPolicy(cwd) {
30440
- const configPath = join43(getCleoDirAbsolute(cwd), "config.json");
30839
+ const configPath = join44(getCleoDirAbsolute(cwd), "config.json");
30441
30840
  const config = await readJson(configPath);
30442
30841
  if (!config) return void 0;
30443
30842
  const release2 = config.release;
@@ -30535,170 +30934,6 @@ async function markReleasePushed(version, pushedAt, cwd, provenance) {
30535
30934
  }).where(eq15(releaseManifests.version, normalizedVersion)).run();
30536
30935
  }
30537
30936
 
30538
- // src/core/release/version-bump.ts
30539
- init_exit_codes();
30540
- init_errors();
30541
- init_paths();
30542
- import { existsSync as existsSync43, readFileSync as readFileSync29, writeFileSync as writeFileSync7 } from "node:fs";
30543
- import { join as join44 } from "node:path";
30544
- function readConfigValueSync2(path, defaultValue, cwd) {
30545
- try {
30546
- const configPath = join44(getCleoDir(cwd), "config.json");
30547
- if (!existsSync43(configPath)) return defaultValue;
30548
- const config = JSON.parse(readFileSync29(configPath, "utf-8"));
30549
- const keys = path.split(".");
30550
- let value = config;
30551
- for (const key of keys) {
30552
- if (value == null || typeof value !== "object") return defaultValue;
30553
- value = value[key];
30554
- }
30555
- return value ?? defaultValue;
30556
- } catch {
30557
- return defaultValue;
30558
- }
30559
- }
30560
- var VERSION_WITH_PRERELEASE = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
30561
- function validateVersionFormat(version) {
30562
- return VERSION_WITH_PRERELEASE.test(version);
30563
- }
30564
- function getVersionBumpConfig(cwd) {
30565
- try {
30566
- const raw = readConfigValueSync2("release.versionBump.files", [], cwd);
30567
- return raw.map((entry) => ({
30568
- file: entry.path ?? entry.file ?? "",
30569
- strategy: entry.strategy,
30570
- field: entry.jsonPath?.replace(/^\./, "") ?? entry.field,
30571
- key: entry.key,
30572
- section: entry.section,
30573
- pattern: entry.sedPattern ?? entry.pattern
30574
- })).filter((t) => t.file !== "");
30575
- } catch {
30576
- return [];
30577
- }
30578
- }
30579
- function bumpFile(target, newVersion, projectRoot) {
30580
- const filePath = join44(projectRoot, target.file);
30581
- if (!existsSync43(filePath)) {
30582
- return {
30583
- file: target.file,
30584
- strategy: target.strategy,
30585
- success: false,
30586
- error: `File not found: ${target.file}`
30587
- };
30588
- }
30589
- try {
30590
- const content = readFileSync29(filePath, "utf-8");
30591
- let previousVersion;
30592
- let newContent;
30593
- switch (target.strategy) {
30594
- case "plain": {
30595
- previousVersion = content.trim();
30596
- newContent = newVersion + "\n";
30597
- break;
30598
- }
30599
- case "json": {
30600
- const field = target.field ?? "version";
30601
- const json = JSON.parse(content);
30602
- previousVersion = getNestedField(json, field);
30603
- setNestedField(json, field, newVersion);
30604
- newContent = JSON.stringify(json, null, 2) + "\n";
30605
- break;
30606
- }
30607
- case "toml": {
30608
- const key = target.key ?? "version";
30609
- const versionRegex = new RegExp(`^(${key}\\s*=\\s*")([^"]+)(")`, "m");
30610
- const match = content.match(versionRegex);
30611
- previousVersion = match?.[2];
30612
- newContent = content.replace(versionRegex, `$1${newVersion}$3`);
30613
- break;
30614
- }
30615
- case "sed": {
30616
- const pattern = target.pattern ?? "";
30617
- if (!pattern.includes("{{VERSION}}")) {
30618
- return {
30619
- file: target.file,
30620
- strategy: target.strategy,
30621
- success: false,
30622
- error: "sed strategy requires {{VERSION}} placeholder in pattern"
30623
- };
30624
- }
30625
- const regex = new RegExp(pattern.replace("{{VERSION}}", "([\\d.]+)"));
30626
- const match = content.match(regex);
30627
- previousVersion = match?.[1];
30628
- newContent = content.replace(regex, pattern.replace("{{VERSION}}", newVersion));
30629
- break;
30630
- }
30631
- default:
30632
- return {
30633
- file: target.file,
30634
- strategy: target.strategy,
30635
- success: false,
30636
- error: `Unknown strategy: ${target.strategy}`
30637
- };
30638
- }
30639
- writeFileSync7(filePath, newContent, "utf-8");
30640
- return {
30641
- file: target.file,
30642
- strategy: target.strategy,
30643
- success: true,
30644
- previousVersion,
30645
- newVersion
30646
- };
30647
- } catch (err) {
30648
- return {
30649
- file: target.file,
30650
- strategy: target.strategy,
30651
- success: false,
30652
- error: String(err)
30653
- };
30654
- }
30655
- }
30656
- function bumpVersionFromConfig(newVersion, options = {}, cwd) {
30657
- if (!validateVersionFormat(newVersion)) {
30658
- throw new CleoError(
30659
- 6 /* VALIDATION_ERROR */,
30660
- `Invalid version: '${newVersion}' (expected X.Y.Z or YYYY.M.patch)`
30661
- );
30662
- }
30663
- const targets = getVersionBumpConfig(cwd);
30664
- if (targets.length === 0) {
30665
- throw new CleoError(
30666
- 1 /* GENERAL_ERROR */,
30667
- "No version bump targets configured. Add release.versionBump.files to .cleo/config.json"
30668
- );
30669
- }
30670
- const projectRoot = getProjectRoot(cwd);
30671
- const results = [];
30672
- if (options.dryRun) {
30673
- for (const target of targets) {
30674
- results.push({
30675
- file: target.file,
30676
- strategy: target.strategy,
30677
- success: true,
30678
- newVersion
30679
- });
30680
- }
30681
- return { results, allSuccess: true };
30682
- }
30683
- for (const target of targets) {
30684
- results.push(bumpFile(target, newVersion, projectRoot));
30685
- }
30686
- const allSuccess = results.every((r) => r.success);
30687
- return { results, allSuccess };
30688
- }
30689
- function getNestedField(obj, path) {
30690
- return path.split(".").reduce((acc, key) => acc?.[key], obj);
30691
- }
30692
- function setNestedField(obj, path, value) {
30693
- const parts = path.split(".");
30694
- let current = obj;
30695
- for (let i = 0; i < parts.length - 1; i++) {
30696
- if (typeof current[parts[i]] !== "object") current[parts[i]] = {};
30697
- current = current[parts[i]];
30698
- }
30699
- current[parts[parts.length - 1]] = value;
30700
- }
30701
-
30702
30937
  // src/dispatch/engines/release-engine.ts
30703
30938
  init_data_accessor();
30704
30939
  init_error();
@@ -45215,7 +45450,7 @@ import { execFileSync as execFileSync13 } from "node:child_process";
45215
45450
  // src/config/build-config.ts
45216
45451
  var BUILD_CONFIG = {
45217
45452
  "name": "@cleocode/cleo",
45218
- "version": "2026.3.29",
45453
+ "version": "2026.3.31",
45219
45454
  "description": "CLEO V2 - TypeScript task management CLI for AI coding agents",
45220
45455
  "repository": {
45221
45456
  "owner": "kryptobaseddev",
@@ -45224,7 +45459,7 @@ var BUILD_CONFIG = {
45224
45459
  "url": "https://github.com/kryptobaseddev/cleo.git",
45225
45460
  "issuesUrl": "https://github.com/kryptobaseddev/cleo/issues"
45226
45461
  },
45227
- "buildDate": "2026-03-16T14:27:12.409Z",
45462
+ "buildDate": "2026-03-16T23:36:19.886Z",
45228
45463
  "templates": {
45229
45464
  "issueTemplatesDir": "templates/issue-templates"
45230
45465
  }
@@ -47933,6 +48168,30 @@ async function runUpgrade(options = {}) {
47933
48168
  }
47934
48169
  } catch {
47935
48170
  }
48171
+ try {
48172
+ const { ensureBrainDb: ensureBrainDb2 } = await Promise.resolve().then(() => (init_scaffold(), scaffold_exports));
48173
+ const brainResult = await ensureBrainDb2(projectRootForMaint);
48174
+ if (brainResult.action !== "skipped") {
48175
+ actions.push({
48176
+ action: "ensure_brain_db",
48177
+ status: "applied",
48178
+ details: brainResult.details ?? "brain.db initialized"
48179
+ });
48180
+ }
48181
+ } catch {
48182
+ }
48183
+ try {
48184
+ const { writeMemoryBridge: writeMemoryBridge2 } = await Promise.resolve().then(() => (init_memory_bridge(), memory_bridge_exports));
48185
+ const bridgeResult = await writeMemoryBridge2(projectRootForMaint);
48186
+ if (bridgeResult.written) {
48187
+ actions.push({
48188
+ action: "memory_bridge",
48189
+ status: "applied",
48190
+ details: "memory-bridge.md regenerated"
48191
+ });
48192
+ }
48193
+ } catch {
48194
+ }
47936
48195
  try {
47937
48196
  const rootGitignoreResult = await removeCleoFromRootGitignore(projectRootForMaint);
47938
48197
  if (rootGitignoreResult.removed) {