@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/mcp/index.js CHANGED
@@ -4599,6 +4599,118 @@ var init_handoff = __esm({
4599
4599
  }
4600
4600
  });
4601
4601
 
4602
+ // src/core/metrics/provider-detection.ts
4603
+ var provider_detection_exports = {};
4604
+ __export(provider_detection_exports, {
4605
+ detectRuntimeProviderContext: () => detectRuntimeProviderContext,
4606
+ resetRuntimeProviderContextCache: () => resetRuntimeProviderContextCache,
4607
+ selectRuntimeProviderContext: () => selectRuntimeProviderContext
4608
+ });
4609
+ import { basename as basename2 } from "node:path";
4610
+ import {
4611
+ detectProjectProviders,
4612
+ getProvider,
4613
+ resolveAlias
4614
+ } from "@cleocode/caamp";
4615
+ function inferProviderFromVendor(vendor) {
4616
+ const value = (vendor ?? "").trim().toLowerCase();
4617
+ if (!value) return void 0;
4618
+ if (value.includes("anthropic") || value.includes("claude")) return "anthropic";
4619
+ if (value.includes("openai") || value.includes("codex") || value.includes("chatgpt"))
4620
+ return "openai";
4621
+ if (value.includes("google") || value.includes("gemini")) return "google";
4622
+ if (value.includes("xai") || value.includes("grok")) return "xai";
4623
+ return void 0;
4624
+ }
4625
+ function getRuntimeHints(snapshot) {
4626
+ const argv = snapshot.argv ?? process.argv;
4627
+ const env = snapshot.env ?? process.env;
4628
+ const hints = /* @__PURE__ */ new Set();
4629
+ const bin = basename2(argv[1] ?? argv[0] ?? "").replace(/\.[^.]+$/, "");
4630
+ if (bin) hints.add(bin);
4631
+ if (env["CLAUDE_CODE_ENABLE_TELEMETRY"] || env["CLAUDE_CODE_ENTRYPOINT"]) {
4632
+ hints.add("claude-code");
4633
+ hints.add("claude");
4634
+ }
4635
+ if (env["OPENCODE_AGENT"] || env["OPENCODE"]) {
4636
+ hints.add("opencode");
4637
+ }
4638
+ if (env["CURSOR_TRACE_ID"] || env["CURSOR_AGENT"]) {
4639
+ hints.add("cursor");
4640
+ }
4641
+ return Array.from(hints);
4642
+ }
4643
+ function pickDetectionByHint(detections, hints) {
4644
+ for (const hint of hints) {
4645
+ const resolved = resolveAlias(hint);
4646
+ const direct = detections.find(
4647
+ (entry) => entry.provider.id === resolved || entry.provider.id === hint
4648
+ );
4649
+ if (direct) return direct;
4650
+ const byAlias = detections.find(
4651
+ (entry) => entry.provider.aliases.includes(hint) || entry.provider.agentFlag === hint || entry.provider.toolName.toLowerCase() === hint.toLowerCase()
4652
+ );
4653
+ if (byAlias) return byAlias;
4654
+ const provider = getProvider(resolved);
4655
+ if (provider) {
4656
+ return {
4657
+ provider,
4658
+ installed: true,
4659
+ methods: [],
4660
+ projectDetected: false
4661
+ };
4662
+ }
4663
+ }
4664
+ return null;
4665
+ }
4666
+ function selectRuntimeProviderContext(detections, snapshot = {}) {
4667
+ const hints = getRuntimeHints(snapshot);
4668
+ const hinted = pickDetectionByHint(detections, hints);
4669
+ const projectMatches = detections.filter((entry) => entry.projectDetected);
4670
+ const installed = detections.filter((entry) => entry.installed);
4671
+ const selected = hinted ?? (projectMatches.length === 1 ? projectMatches[0] : null) ?? (installed.length === 1 ? installed[0] : null);
4672
+ if (!selected) {
4673
+ return {
4674
+ runtimeCandidates: installed.map((entry) => entry.provider.id)
4675
+ };
4676
+ }
4677
+ return {
4678
+ runtimeProviderId: selected.provider.id,
4679
+ runtimeToolName: selected.provider.toolName,
4680
+ runtimeVendor: selected.provider.vendor,
4681
+ runtimeInstructionFile: selected.provider.instructFile,
4682
+ runtimeProjectDetected: selected.projectDetected,
4683
+ runtimeDetectionMethods: selected.methods,
4684
+ runtimeCandidates: installed.map((entry) => entry.provider.id),
4685
+ inferredModelProvider: inferProviderFromVendor(selected.provider.vendor)
4686
+ };
4687
+ }
4688
+ function detectRuntimeProviderContext(snapshot = {}) {
4689
+ if (!snapshot.cwd && !snapshot.argv && !snapshot.env && cachedRuntimeProvider) {
4690
+ return cachedRuntimeProvider;
4691
+ }
4692
+ try {
4693
+ const detections = detectProjectProviders(snapshot.cwd ?? process.cwd());
4694
+ const context = selectRuntimeProviderContext(detections, snapshot);
4695
+ if (!snapshot.cwd && !snapshot.argv && !snapshot.env) {
4696
+ cachedRuntimeProvider = context;
4697
+ }
4698
+ return context;
4699
+ } catch {
4700
+ return {};
4701
+ }
4702
+ }
4703
+ function resetRuntimeProviderContextCache() {
4704
+ cachedRuntimeProvider = null;
4705
+ }
4706
+ var cachedRuntimeProvider;
4707
+ var init_provider_detection = __esm({
4708
+ "src/core/metrics/provider-detection.ts"() {
4709
+ "use strict";
4710
+ cachedRuntimeProvider = null;
4711
+ }
4712
+ });
4713
+
4602
4714
  // src/store/data-safety.ts
4603
4715
  import { eq as eq8 } from "drizzle-orm";
4604
4716
  async function checkTaskExists(taskId, cwd, config = {}) {
@@ -7067,14 +7179,16 @@ async function writeMemoryBridge(projectRoot, config) {
7067
7179
  }
7068
7180
  writeFileSync2(bridgePath, content, "utf-8");
7069
7181
  return { path: bridgePath, written: true };
7070
- } catch {
7182
+ } catch (err) {
7183
+ console.error("[CLEO] Failed to write memory bridge:", err instanceof Error ? err.message : String(err));
7071
7184
  return { path: bridgePath, written: false };
7072
7185
  }
7073
7186
  }
7074
7187
  async function refreshMemoryBridge(projectRoot) {
7075
7188
  try {
7076
7189
  await writeMemoryBridge(projectRoot);
7077
- } catch {
7190
+ } catch (err) {
7191
+ console.error("[CLEO] Memory bridge refresh failed:", err instanceof Error ? err.message : String(err));
7078
7192
  }
7079
7193
  }
7080
7194
  async function getLastHandoffSafe(projectRoot) {
@@ -8470,28 +8584,25 @@ async function ensureInjection(projectRoot) {
8470
8584
  }
8471
8585
  const { getInstalledProviders: getInstalledProviders2, inject, injectAll: injectAll2, buildInjectionContent: buildInjectionContent2 } = caamp;
8472
8586
  const providers = getInstalledProviders2();
8473
- if (providers.length === 0) {
8474
- return {
8475
- action: "skipped",
8476
- path: join26(projectRoot, "AGENTS.md"),
8477
- details: "No AI agent providers detected, skipping injection"
8478
- };
8479
- }
8480
8587
  const actions = [];
8481
- for (const provider of providers) {
8482
- const instructFile = join26(projectRoot, provider.pathProject, provider.instructFile);
8483
- await stripCLEOBlocks(instructFile);
8484
- }
8485
- await stripCLEOBlocks(join26(projectRoot, "AGENTS.md"));
8486
- const removedStale = await removeStaleAgentInjection(projectRoot);
8487
- if (removedStale) {
8488
- actions.push("removed deprecated AGENT-INJECTION.md");
8489
- }
8490
- const injectionContent = buildInjectionContent2({ references: ["@AGENTS.md"] });
8491
- const results = await injectAll2(providers, projectRoot, "project", injectionContent);
8492
- for (const [filePath, action] of results) {
8493
- const fileName = basename3(filePath);
8494
- actions.push(`${fileName} (${action})`);
8588
+ if (providers.length === 0) {
8589
+ actions.push("No providers detected (AGENTS.md created without provider injection)");
8590
+ } else {
8591
+ for (const provider of providers) {
8592
+ const instructFile = join26(projectRoot, provider.pathProject, provider.instructFile);
8593
+ await stripCLEOBlocks(instructFile);
8594
+ }
8595
+ await stripCLEOBlocks(join26(projectRoot, "AGENTS.md"));
8596
+ const removedStale = await removeStaleAgentInjection(projectRoot);
8597
+ if (removedStale) {
8598
+ actions.push("removed deprecated AGENT-INJECTION.md");
8599
+ }
8600
+ const injectionContent = buildInjectionContent2({ references: ["@AGENTS.md"] });
8601
+ const results = await injectAll2(providers, projectRoot, "project", injectionContent);
8602
+ for (const [filePath, action] of results) {
8603
+ const fileName = basename3(filePath);
8604
+ actions.push(`${fileName} (${action})`);
8605
+ }
8495
8606
  }
8496
8607
  const agentsMdPath = join26(projectRoot, "AGENTS.md");
8497
8608
  const agentsMdLines = ["@~/.cleo/templates/CLEO-INJECTION.md"];
@@ -8499,6 +8610,10 @@ async function ensureInjection(projectRoot) {
8499
8610
  if (existsSync23(projectContextPath)) {
8500
8611
  agentsMdLines.push("@.cleo/project-context.json");
8501
8612
  }
8613
+ const memoryBridgePath = join26(projectRoot, ".cleo", "memory-bridge.md");
8614
+ if (existsSync23(memoryBridgePath)) {
8615
+ agentsMdLines.push("@.cleo/memory-bridge.md");
8616
+ }
8502
8617
  const contributorBlock = buildContributorInjectionBlock(projectRoot);
8503
8618
  if (contributorBlock) {
8504
8619
  agentsMdLines.push(contributorBlock);
@@ -8699,6 +8814,7 @@ __export(scaffold_exports, {
8699
8814
  CLEO_GITIGNORE_FALLBACK: () => CLEO_GITIGNORE_FALLBACK,
8700
8815
  REQUIRED_CLEO_SUBDIRS: () => REQUIRED_CLEO_SUBDIRS,
8701
8816
  REQUIRED_GLOBAL_SUBDIRS: () => REQUIRED_GLOBAL_SUBDIRS,
8817
+ checkBrainDb: () => checkBrainDb,
8702
8818
  checkCleoGitRepo: () => checkCleoGitRepo,
8703
8819
  checkCleoStructure: () => checkCleoStructure,
8704
8820
  checkConfig: () => checkConfig,
@@ -8706,10 +8822,12 @@ __export(scaffold_exports, {
8706
8822
  checkGlobalHome: () => checkGlobalHome,
8707
8823
  checkGlobalTemplates: () => checkGlobalTemplates,
8708
8824
  checkLogDir: () => checkLogDir,
8825
+ checkMemoryBridge: () => checkMemoryBridge,
8709
8826
  checkProjectContext: () => checkProjectContext,
8710
8827
  checkProjectInfo: () => checkProjectInfo,
8711
8828
  checkSqliteDb: () => checkSqliteDb,
8712
8829
  createDefaultConfig: () => createDefaultConfig,
8830
+ ensureBrainDb: () => ensureBrainDb,
8713
8831
  ensureCleoGitRepo: () => ensureCleoGitRepo,
8714
8832
  ensureCleoStructure: () => ensureCleoStructure,
8715
8833
  ensureConfig: () => ensureConfig,
@@ -9324,6 +9442,79 @@ function checkSqliteDb(projectRoot) {
9324
9442
  fix: null
9325
9443
  };
9326
9444
  }
9445
+ async function ensureBrainDb(projectRoot) {
9446
+ const cleoDir = getCleoDirAbsolute(projectRoot);
9447
+ const dbPath = join27(cleoDir, "brain.db");
9448
+ if (existsSync24(dbPath)) {
9449
+ return { action: "skipped", path: dbPath, details: "brain.db already exists" };
9450
+ }
9451
+ try {
9452
+ const { getBrainDb: getBrainDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
9453
+ await getBrainDb2(projectRoot);
9454
+ return { action: "created", path: dbPath, details: "Brain database initialized" };
9455
+ } catch (err) {
9456
+ return {
9457
+ action: "skipped",
9458
+ path: dbPath,
9459
+ details: `Failed to initialize brain.db: ${err instanceof Error ? err.message : String(err)}`
9460
+ };
9461
+ }
9462
+ }
9463
+ function checkBrainDb(projectRoot) {
9464
+ const cleoDir = getCleoDirAbsolute(projectRoot);
9465
+ const dbPath = join27(cleoDir, "brain.db");
9466
+ if (!existsSync24(dbPath)) {
9467
+ return {
9468
+ id: "brain_db",
9469
+ category: "scaffold",
9470
+ status: "failed",
9471
+ message: "brain.db not found",
9472
+ details: { path: dbPath, exists: false },
9473
+ fix: "cleo init"
9474
+ };
9475
+ }
9476
+ const stat3 = statSync3(dbPath);
9477
+ if (stat3.size === 0) {
9478
+ return {
9479
+ id: "brain_db",
9480
+ category: "scaffold",
9481
+ status: "warning",
9482
+ message: "brain.db exists but is empty (0 bytes)",
9483
+ details: { path: dbPath, exists: true, size: 0 },
9484
+ fix: "cleo upgrade"
9485
+ };
9486
+ }
9487
+ return {
9488
+ id: "brain_db",
9489
+ category: "scaffold",
9490
+ status: "passed",
9491
+ message: `brain.db exists (${stat3.size} bytes)`,
9492
+ details: { path: dbPath, exists: true, size: stat3.size },
9493
+ fix: null
9494
+ };
9495
+ }
9496
+ function checkMemoryBridge(projectRoot) {
9497
+ const cleoDir = getCleoDirAbsolute(projectRoot);
9498
+ const bridgePath = join27(cleoDir, "memory-bridge.md");
9499
+ if (!existsSync24(bridgePath)) {
9500
+ return {
9501
+ id: "memory_bridge",
9502
+ category: "scaffold",
9503
+ status: "warning",
9504
+ message: "memory-bridge.md not found",
9505
+ details: { path: bridgePath, exists: false },
9506
+ fix: "cleo init or cleo refresh-memory"
9507
+ };
9508
+ }
9509
+ return {
9510
+ id: "memory_bridge",
9511
+ category: "scaffold",
9512
+ status: "passed",
9513
+ message: "memory-bridge.md exists",
9514
+ details: { path: bridgePath, exists: true },
9515
+ fix: null
9516
+ };
9517
+ }
9327
9518
  async function ensureGlobalHome() {
9328
9519
  const cleoHome = getCleoHome();
9329
9520
  const alreadyExists = existsSync24(cleoHome);
@@ -10404,6 +10595,28 @@ var init_registry2 = __esm({
10404
10595
  }
10405
10596
  });
10406
10597
 
10598
+ // src/core/adapters/adapter-registry.ts
10599
+ var ADAPTER_REGISTRY;
10600
+ var init_adapter_registry = __esm({
10601
+ "src/core/adapters/adapter-registry.ts"() {
10602
+ "use strict";
10603
+ ADAPTER_REGISTRY = {
10604
+ "claude-code": async () => {
10605
+ const { ClaudeCodeAdapter } = await import("@cleocode/adapter-claude-code");
10606
+ return new ClaudeCodeAdapter();
10607
+ },
10608
+ "opencode": async () => {
10609
+ const { OpenCodeAdapter } = await import("@cleocode/adapter-opencode");
10610
+ return new OpenCodeAdapter();
10611
+ },
10612
+ "cursor": async () => {
10613
+ const { CursorAdapter } = await import("@cleocode/adapter-cursor");
10614
+ return new CursorAdapter();
10615
+ }
10616
+ };
10617
+ }
10618
+ });
10619
+
10407
10620
  // src/core/adapters/discovery.ts
10408
10621
  import { execFileSync as execFileSync2 } from "node:child_process";
10409
10622
  import { existsSync as existsSync28, readdirSync as readdirSync12, readFileSync as readFileSync18 } from "node:fs";
@@ -10469,13 +10682,16 @@ var log4, AdapterManager;
10469
10682
  var init_manager = __esm({
10470
10683
  "src/core/adapters/manager.ts"() {
10471
10684
  "use strict";
10685
+ init_registry();
10472
10686
  init_logger();
10687
+ init_adapter_registry();
10473
10688
  init_discovery();
10474
10689
  log4 = getLogger("adapter-manager");
10475
10690
  AdapterManager = class _AdapterManager {
10476
10691
  static instance = null;
10477
10692
  adapters = /* @__PURE__ */ new Map();
10478
10693
  manifests = /* @__PURE__ */ new Map();
10694
+ hookCleanups = /* @__PURE__ */ new Map();
10479
10695
  activeId = null;
10480
10696
  projectRoot;
10481
10697
  constructor(projectRoot) {
@@ -10519,7 +10735,7 @@ var init_manager = __esm({
10519
10735
  }
10520
10736
  /**
10521
10737
  * Load and initialize an adapter by manifest ID.
10522
- * The adapter module is dynamically imported from the manifest's entryPoint.
10738
+ * Uses the static adapter registry for reliable bundled operation.
10523
10739
  */
10524
10740
  async activate(adapterId) {
10525
10741
  const manifest = this.manifests.get(adapterId);
@@ -10531,20 +10747,18 @@ var init_manager = __esm({
10531
10747
  this.activeId = adapterId;
10532
10748
  return existing;
10533
10749
  }
10534
- const { resolve: resolve10 } = await import("node:path");
10535
- const entryPath = resolve10(
10536
- this.projectRoot,
10537
- "packages",
10538
- "adapters",
10539
- manifest.provider,
10540
- manifest.entryPoint
10541
- );
10750
+ const factory = ADAPTER_REGISTRY[adapterId];
10751
+ if (!factory) {
10752
+ throw new Error(`No adapter registered in static registry: ${adapterId}`);
10753
+ }
10542
10754
  try {
10543
- const mod = await import(entryPath);
10544
- const adapter = typeof mod.default === "function" ? new mod.default() : typeof mod.createAdapter === "function" ? await mod.createAdapter() : mod.default;
10755
+ const adapter = await factory();
10545
10756
  await adapter.initialize(this.projectRoot);
10546
10757
  this.adapters.set(adapterId, adapter);
10547
10758
  this.activeId = adapterId;
10759
+ if (adapter.hooks) {
10760
+ await this.wireAdapterHooks(adapterId, adapter);
10761
+ }
10548
10762
  log4.info({ adapterId, provider: manifest.provider }, "Adapter activated");
10549
10763
  return adapter;
10550
10764
  } catch (err) {
@@ -10626,6 +10840,7 @@ var init_manager = __esm({
10626
10840
  async dispose() {
10627
10841
  for (const [id, adapter] of this.adapters) {
10628
10842
  try {
10843
+ await this.cleanupAdapterHooks(id, adapter);
10629
10844
  await adapter.dispose();
10630
10845
  log4.info({ adapterId: id }, "Adapter disposed");
10631
10846
  } catch (err) {
@@ -10633,6 +10848,7 @@ var init_manager = __esm({
10633
10848
  }
10634
10849
  }
10635
10850
  this.adapters.clear();
10851
+ this.hookCleanups.clear();
10636
10852
  this.activeId = null;
10637
10853
  }
10638
10854
  /** Dispose a single adapter. */
@@ -10640,6 +10856,7 @@ var init_manager = __esm({
10640
10856
  const adapter = this.adapters.get(adapterId);
10641
10857
  if (!adapter) return;
10642
10858
  try {
10859
+ await this.cleanupAdapterHooks(adapterId, adapter);
10643
10860
  await adapter.dispose();
10644
10861
  } catch (err) {
10645
10862
  log4.error({ adapterId, err }, "Failed to dispose adapter");
@@ -10649,6 +10866,51 @@ var init_manager = __esm({
10649
10866
  this.activeId = null;
10650
10867
  }
10651
10868
  }
10869
+ /**
10870
+ * Wire an adapter's hook event map into CLEO's HookRegistry.
10871
+ * Creates bridging handlers at priority 50 for each mapped event.
10872
+ */
10873
+ async wireAdapterHooks(adapterId, adapter) {
10874
+ if (!adapter.hooks) return;
10875
+ try {
10876
+ await adapter.hooks.registerNativeHooks(this.projectRoot);
10877
+ } catch (err) {
10878
+ log4.error({ adapterId, err }, "Failed to register native hooks");
10879
+ }
10880
+ const eventMap = adapter.hooks.getEventMap?.();
10881
+ if (!eventMap) return;
10882
+ const cleanups = [];
10883
+ for (const [_providerEvent, caampEvent] of Object.entries(eventMap)) {
10884
+ const hookId = `adapter-${adapterId}-${caampEvent}`;
10885
+ const unregister = hooks.register({
10886
+ id: hookId,
10887
+ event: caampEvent,
10888
+ priority: 50,
10889
+ handler: async (_projectRoot, payload) => {
10890
+ log4.debug({ adapterId, event: caampEvent, payload }, "Adapter hook dispatched");
10891
+ }
10892
+ });
10893
+ cleanups.push(unregister);
10894
+ }
10895
+ this.hookCleanups.set(adapterId, cleanups);
10896
+ }
10897
+ /**
10898
+ * Clean up hook registrations for an adapter.
10899
+ */
10900
+ async cleanupAdapterHooks(adapterId, adapter) {
10901
+ const cleanups = this.hookCleanups.get(adapterId);
10902
+ if (cleanups) {
10903
+ for (const fn of cleanups) {
10904
+ fn();
10905
+ }
10906
+ this.hookCleanups.delete(adapterId);
10907
+ }
10908
+ try {
10909
+ await adapter.hooks?.unregisterNativeHooks();
10910
+ } catch (err) {
10911
+ log4.error({ adapterId, err }, "Failed to unregister native hooks");
10912
+ }
10913
+ }
10652
10914
  };
10653
10915
  }
10654
10916
  });
@@ -10656,6 +10918,7 @@ var init_manager = __esm({
10656
10918
  // src/core/adapters/index.ts
10657
10919
  var adapters_exports = {};
10658
10920
  __export(adapters_exports, {
10921
+ ADAPTER_REGISTRY: () => ADAPTER_REGISTRY,
10659
10922
  AdapterManager: () => AdapterManager,
10660
10923
  detectProvider: () => detectProvider,
10661
10924
  discoverAdapterManifests: () => discoverAdapterManifests
@@ -10663,6 +10926,7 @@ __export(adapters_exports, {
10663
10926
  var init_adapters = __esm({
10664
10927
  "src/core/adapters/index.ts"() {
10665
10928
  "use strict";
10929
+ init_adapter_registry();
10666
10930
  init_manager();
10667
10931
  init_discovery();
10668
10932
  }
@@ -12893,6 +13157,13 @@ async function startSession(options, cwd, accessor) {
12893
13157
  );
12894
13158
  }
12895
13159
  }
13160
+ let detectedProviderId = null;
13161
+ try {
13162
+ const { detectRuntimeProviderContext: detectRuntimeProviderContext2 } = await Promise.resolve().then(() => (init_provider_detection(), provider_detection_exports));
13163
+ const ctx = detectRuntimeProviderContext2();
13164
+ detectedProviderId = ctx.runtimeProviderId ?? null;
13165
+ } catch {
13166
+ }
12896
13167
  const session = {
12897
13168
  id: generateSessionId(),
12898
13169
  name: options.name,
@@ -12907,7 +13178,7 @@ async function startSession(options, cwd, accessor) {
12907
13178
  notes: [],
12908
13179
  tasksCompleted: [],
12909
13180
  tasksCreated: [],
12910
- providerId: options.providerId ?? null
13181
+ providerId: options.providerId ?? detectedProviderId ?? null
12911
13182
  };
12912
13183
  if (options.grade) {
12913
13184
  session.notes = ["[grade-mode:enabled]", ...session.notes ?? []];
@@ -12917,13 +13188,22 @@ async function startSession(options, cwd, accessor) {
12917
13188
  }
12918
13189
  sessions2.push(session);
12919
13190
  await saveSessions(sessions2, cwd, accessor);
13191
+ if (session.providerId) {
13192
+ Promise.resolve().then(() => (init_adapters(), adapters_exports)).then(({ AdapterManager: AdapterManager2 }) => {
13193
+ const mgr = AdapterManager2.getInstance(cwd ?? process.cwd());
13194
+ mgr.discover();
13195
+ return mgr.activate(session.providerId);
13196
+ }).catch(() => {
13197
+ });
13198
+ }
12920
13199
  const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
12921
13200
  hooks2.dispatch("onSessionStart", cwd ?? process.cwd(), {
12922
13201
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12923
13202
  sessionId: session.id,
12924
13203
  name: options.name,
12925
13204
  scope,
12926
- agent: options.agent
13205
+ agent: options.agent,
13206
+ providerId: session.providerId ?? void 0
12927
13207
  }).catch(() => {
12928
13208
  });
12929
13209
  return session;
@@ -12959,7 +13239,8 @@ async function endSession(options = {}, cwd, accessor) {
12959
13239
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12960
13240
  sessionId: session.id,
12961
13241
  duration,
12962
- tasksCompleted: session.tasksCompleted || []
13242
+ tasksCompleted: session.tasksCompleted || [],
13243
+ providerId: session.providerId ?? void 0
12963
13244
  }).catch(() => {
12964
13245
  });
12965
13246
  const { bridgeSessionToMemory: bridgeSessionToMemory2 } = await Promise.resolve().then(() => (init_session_memory_bridge(), session_memory_bridge_exports));
@@ -13329,7 +13610,7 @@ async function initializeDefaultAdapters() {
13329
13610
  }
13330
13611
  }
13331
13612
  var SpawnAdapterRegistry, spawnRegistry;
13332
- var init_adapter_registry = __esm({
13613
+ var init_adapter_registry2 = __esm({
13333
13614
  "src/core/spawn/adapter-registry.ts"() {
13334
13615
  "use strict";
13335
13616
  SpawnAdapterRegistry = class {
@@ -14577,6 +14858,18 @@ async function startupHealthCheck(projectRoot) {
14577
14858
  } else {
14578
14859
  checks.push({ check: "log_dir", status: "pass", message: "Log directory present" });
14579
14860
  }
14861
+ const brainDbCheck = checkBrainDb(root);
14862
+ checks.push({
14863
+ check: "brain_db",
14864
+ status: brainDbCheck.status === "passed" ? "pass" : "warn",
14865
+ message: brainDbCheck.message
14866
+ });
14867
+ const memBridgeCheck = checkMemoryBridge(root);
14868
+ checks.push({
14869
+ check: "memory_bridge",
14870
+ status: memBridgeCheck.status === "passed" ? "pass" : "warn",
14871
+ message: memBridgeCheck.message
14872
+ });
14580
14873
  const hasFailures = failures.length > 0;
14581
14874
  const state = hasFailures && !projectHealthy ? "needs_upgrade" : "healthy";
14582
14875
  return {
@@ -14665,7 +14958,7 @@ async function loadCompletionEnforcement(cwd) {
14665
14958
  const lifecycleModeRaw = await getRawConfigValue("lifecycle.mode", cwd);
14666
14959
  const acceptanceMode = modeRaw === "off" || modeRaw === "warn" || modeRaw === "block" ? modeRaw : "warn";
14667
14960
  const acceptanceRequiredForPriorities = Array.isArray(prioritiesRaw) ? prioritiesRaw.filter((p) => typeof p === "string") : ["critical", "high"];
14668
- const verificationEnabled = verificationEnabledRaw !== false;
14961
+ const verificationEnabled = verificationEnabledRaw === true;
14669
14962
  const verificationRequiredGates = Array.isArray(verificationRequiredGatesRaw) ? verificationRequiredGatesRaw.filter((g) => typeof g === "string").filter(isVerificationGate) : DEFAULT_VERIFICATION_REQUIRED_GATES;
14670
14963
  const verificationMaxRounds = typeof verificationMaxRoundsRaw === "number" && Number.isInteger(verificationMaxRoundsRaw) ? verificationMaxRoundsRaw : 5;
14671
14964
  const lifecycleMode = lifecycleModeRaw === "strict" || lifecycleModeRaw === "warn" || lifecycleModeRaw === "advisory" || lifecycleModeRaw === "none" || lifecycleModeRaw === "off" ? lifecycleModeRaw : "off";
@@ -20993,116 +21286,20 @@ async function resolveProviderFromModelRegistry(model) {
20993
21286
  return resolveProviderFromModelIndex(index5, model);
20994
21287
  }
20995
21288
 
20996
- // src/core/metrics/provider-detection.ts
20997
- import { basename as basename2 } from "node:path";
20998
- import {
20999
- detectProjectProviders,
21000
- getProvider,
21001
- resolveAlias
21002
- } from "@cleocode/caamp";
21003
- function inferProviderFromVendor(vendor) {
21004
- const value = (vendor ?? "").trim().toLowerCase();
21005
- if (!value) return void 0;
21006
- if (value.includes("anthropic") || value.includes("claude")) return "anthropic";
21007
- if (value.includes("openai") || value.includes("codex") || value.includes("chatgpt"))
21289
+ // src/core/metrics/token-service.ts
21290
+ init_provider_detection();
21291
+ function normalizeProvider(provider, model, runtimeProvider) {
21292
+ const value = (provider ?? "").trim().toLowerCase();
21293
+ const modelValue = (model ?? "").trim().toLowerCase();
21294
+ const runtimeValue = (runtimeProvider ?? "").trim().toLowerCase();
21295
+ if (value) return value;
21296
+ if (modelValue.startsWith("gpt") || modelValue.startsWith("o1") || modelValue.startsWith("o3") || modelValue.startsWith("text-embedding")) {
21008
21297
  return "openai";
21009
- if (value.includes("google") || value.includes("gemini")) return "google";
21010
- if (value.includes("xai") || value.includes("grok")) return "xai";
21011
- return void 0;
21012
- }
21013
- function getRuntimeHints(snapshot) {
21014
- const argv = snapshot.argv ?? process.argv;
21015
- const env = snapshot.env ?? process.env;
21016
- const hints = /* @__PURE__ */ new Set();
21017
- const bin = basename2(argv[1] ?? argv[0] ?? "").replace(/\.[^.]+$/, "");
21018
- if (bin) hints.add(bin);
21019
- if (env["CLAUDE_CODE_ENABLE_TELEMETRY"] || env["CLAUDE_CODE_ENTRYPOINT"]) {
21020
- hints.add("claude-code");
21021
- hints.add("claude");
21022
21298
  }
21023
- if (env["OPENCODE_AGENT"] || env["OPENCODE"]) {
21024
- hints.add("opencode");
21025
- }
21026
- if (env["CURSOR_TRACE_ID"] || env["CURSOR_AGENT"]) {
21027
- hints.add("cursor");
21028
- }
21029
- return Array.from(hints);
21030
- }
21031
- function pickDetectionByHint(detections, hints) {
21032
- for (const hint of hints) {
21033
- const resolved = resolveAlias(hint);
21034
- const direct = detections.find(
21035
- (entry) => entry.provider.id === resolved || entry.provider.id === hint
21036
- );
21037
- if (direct) return direct;
21038
- const byAlias = detections.find(
21039
- (entry) => entry.provider.aliases.includes(hint) || entry.provider.agentFlag === hint || entry.provider.toolName.toLowerCase() === hint.toLowerCase()
21040
- );
21041
- if (byAlias) return byAlias;
21042
- const provider = getProvider(resolved);
21043
- if (provider) {
21044
- return {
21045
- provider,
21046
- installed: true,
21047
- methods: [],
21048
- projectDetected: false
21049
- };
21050
- }
21051
- }
21052
- return null;
21053
- }
21054
- function selectRuntimeProviderContext(detections, snapshot = {}) {
21055
- const hints = getRuntimeHints(snapshot);
21056
- const hinted = pickDetectionByHint(detections, hints);
21057
- const projectMatches = detections.filter((entry) => entry.projectDetected);
21058
- const installed = detections.filter((entry) => entry.installed);
21059
- const selected = hinted ?? (projectMatches.length === 1 ? projectMatches[0] : null) ?? (installed.length === 1 ? installed[0] : null);
21060
- if (!selected) {
21061
- return {
21062
- runtimeCandidates: installed.map((entry) => entry.provider.id)
21063
- };
21064
- }
21065
- return {
21066
- runtimeProviderId: selected.provider.id,
21067
- runtimeToolName: selected.provider.toolName,
21068
- runtimeVendor: selected.provider.vendor,
21069
- runtimeInstructionFile: selected.provider.instructFile,
21070
- runtimeProjectDetected: selected.projectDetected,
21071
- runtimeDetectionMethods: selected.methods,
21072
- runtimeCandidates: installed.map((entry) => entry.provider.id),
21073
- inferredModelProvider: inferProviderFromVendor(selected.provider.vendor)
21074
- };
21075
- }
21076
- var cachedRuntimeProvider = null;
21077
- function detectRuntimeProviderContext(snapshot = {}) {
21078
- if (!snapshot.cwd && !snapshot.argv && !snapshot.env && cachedRuntimeProvider) {
21079
- return cachedRuntimeProvider;
21080
- }
21081
- try {
21082
- const detections = detectProjectProviders(snapshot.cwd ?? process.cwd());
21083
- const context = selectRuntimeProviderContext(detections, snapshot);
21084
- if (!snapshot.cwd && !snapshot.argv && !snapshot.env) {
21085
- cachedRuntimeProvider = context;
21086
- }
21087
- return context;
21088
- } catch {
21089
- return {};
21090
- }
21091
- }
21092
-
21093
- // src/core/metrics/token-service.ts
21094
- function normalizeProvider(provider, model, runtimeProvider) {
21095
- const value = (provider ?? "").trim().toLowerCase();
21096
- const modelValue = (model ?? "").trim().toLowerCase();
21097
- const runtimeValue = (runtimeProvider ?? "").trim().toLowerCase();
21098
- if (value) return value;
21099
- if (modelValue.startsWith("gpt") || modelValue.startsWith("o1") || modelValue.startsWith("o3") || modelValue.startsWith("text-embedding")) {
21100
- return "openai";
21101
- }
21102
- if (modelValue.includes("claude")) return "anthropic";
21103
- if (modelValue.includes("gemini")) return "google";
21104
- if (runtimeValue) return runtimeValue;
21105
- return "unknown";
21299
+ if (modelValue.includes("claude")) return "anthropic";
21300
+ if (modelValue.includes("gemini")) return "google";
21301
+ if (runtimeValue) return runtimeValue;
21302
+ return "unknown";
21106
21303
  }
21107
21304
  async function resolveMeasurementProvider(input) {
21108
21305
  const runtime = detectRuntimeProviderContext({ cwd: input.cwd });
@@ -22205,6 +22402,7 @@ init_agent_outputs();
22205
22402
  init_paths();
22206
22403
  init_scaffold();
22207
22404
  init_schema_management();
22405
+ init_memory_bridge();
22208
22406
  import { existsSync as existsSync29, readdirSync as readdirSync13, readFileSync as readFileSync19 } from "node:fs";
22209
22407
  import { copyFile as copyFile3, lstat, mkdir as mkdir9, readFile as readFile8, symlink, unlink as unlink3 } from "node:fs/promises";
22210
22408
  import { basename as basename5, dirname as dirname10, join as join33 } from "node:path";
@@ -22378,6 +22576,14 @@ async function initProject(opts = {}) {
22378
22576
  } catch (err) {
22379
22577
  created.push(`tasks.db (deferred: ${err instanceof Error ? err.message : String(err)})`);
22380
22578
  }
22579
+ try {
22580
+ const brainResult = await ensureBrainDb(projRoot);
22581
+ if (brainResult.action === "created") {
22582
+ created.push("brain.db");
22583
+ }
22584
+ } catch (err) {
22585
+ created.push(`brain.db (deferred: ${err instanceof Error ? err.message : String(err)})`);
22586
+ }
22381
22587
  if (force) {
22382
22588
  const gitignoreResult = await ensureGitignore(projRoot);
22383
22589
  if (gitignoreResult.action === "skipped") {
@@ -22467,6 +22673,14 @@ async function initProject(opts = {}) {
22467
22673
  } catch (err) {
22468
22674
  warnings.push(`Project detection failed: ${err instanceof Error ? err.message : String(err)}`);
22469
22675
  }
22676
+ try {
22677
+ const bridgeResult = await writeMemoryBridge(projRoot);
22678
+ if (bridgeResult.written) {
22679
+ created.push("memory-bridge.md");
22680
+ }
22681
+ } catch (err) {
22682
+ warnings.push(`Memory bridge: ${err instanceof Error ? err.message : String(err)}`);
22683
+ }
22470
22684
  try {
22471
22685
  const injectionResult = await ensureInjection(projRoot);
22472
22686
  if (injectionResult.action !== "skipped") {
@@ -22500,6 +22714,21 @@ async function initProject(opts = {}) {
22500
22714
  const detected = mgr.detectActive();
22501
22715
  if (detected.length > 0) {
22502
22716
  created.push(`adapters: active provider detected (${detected.join(", ")})`);
22717
+ for (const adapterId of detected) {
22718
+ try {
22719
+ const adapter = await mgr.activate(adapterId);
22720
+ const installResult = await adapter.install.install({
22721
+ projectDir: projRoot
22722
+ });
22723
+ if (installResult.success) {
22724
+ created.push(`adapter install (${adapterId}): installed`);
22725
+ } else {
22726
+ warnings.push(`adapter install (${adapterId}): failed`);
22727
+ }
22728
+ } catch (err) {
22729
+ warnings.push(`adapter activate/install (${adapterId}): ${err instanceof Error ? err.message : String(err)}`);
22730
+ }
22731
+ }
22503
22732
  }
22504
22733
  }
22505
22734
  } catch (err) {
@@ -24604,6 +24833,7 @@ function injectContext(protocolType, params, projectRoot) {
24604
24833
  // src/dispatch/engines/session-engine.ts
24605
24834
  init_handoff();
24606
24835
  init_sessions();
24836
+ init_sessions();
24607
24837
 
24608
24838
  // src/core/sessions/session-id.ts
24609
24839
  import { randomBytes as randomBytes9 } from "node:crypto";
@@ -24893,29 +25123,31 @@ async function sessionStart(projectRoot, params) {
24893
25123
  try {
24894
25124
  let accessor = await getAccessor(projectRoot);
24895
25125
  let taskData = await accessor.loadTaskFile();
25126
+ let scope;
25127
+ try {
25128
+ scope = parseScope2(params.scope);
25129
+ } catch (err) {
25130
+ return engineError("E_INVALID_INPUT", err instanceof Error ? err.message : "Invalid scope");
25131
+ }
25132
+ if (scope.type !== "global") {
25133
+ const rootTask = taskData.tasks?.find((t) => t.id === scope.rootTaskId);
25134
+ if (!rootTask) {
25135
+ return engineError("E_NOT_FOUND", `Root task '${scope.rootTaskId}' not found`);
25136
+ }
25137
+ }
24896
25138
  const activeSessionId = taskData._meta?.activeSession;
24897
25139
  if (activeSessionId) {
24898
25140
  await sessionEnd(projectRoot);
24899
25141
  accessor = await getAccessor(projectRoot);
24900
25142
  taskData = await accessor.loadTaskFile();
24901
25143
  }
24902
- const scopeParts = params.scope.split(":");
24903
- const scopeType = scopeParts[0] || "task";
24904
- const rootTaskId = scopeParts[1] || "";
24905
- if (!rootTaskId) {
24906
- return engineError("E_INVALID_INPUT", "Scope must include a task ID (e.g., epic:T001)");
24907
- }
24908
- const rootTask = taskData.tasks?.find((t) => t.id === rootTaskId);
24909
- if (!rootTask) {
24910
- return engineError("E_NOT_FOUND", `Root task '${rootTaskId}' not found`);
24911
- }
24912
25144
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
24913
25145
  const sessionId = generateSessionId2();
24914
25146
  let previousSessionId = null;
24915
25147
  {
24916
25148
  const sessions2 = await accessor.loadSessions();
24917
25149
  const sameScope = sessions2.filter(
24918
- (s) => s.status === "ended" && s.endedAt && s.scope?.rootTaskId === rootTaskId && s.scope?.type === scopeType
25150
+ (s) => s.status === "ended" && s.endedAt && s.scope?.type === scope.type && (scope.type === "global" || s.scope?.rootTaskId === scope.rootTaskId)
24919
25151
  ).sort(
24920
25152
  (a, b) => new Date(b.endedAt).getTime() - new Date(a.endedAt).getTime()
24921
25153
  );
@@ -24924,16 +25156,13 @@ async function sessionStart(projectRoot, params) {
24924
25156
  }
24925
25157
  }
24926
25158
  const agentIdentifier = params.agentIdentifier ?? process.env.CLEO_AGENT_ID ?? null;
24927
- const startingTaskId = params.startTask || (params.autoStart ? rootTaskId : null);
25159
+ const rootTaskId = scope.type !== "global" ? scope.rootTaskId : void 0;
25160
+ const startingTaskId = params.startTask || (params.autoStart && rootTaskId ? rootTaskId : null);
24928
25161
  const newSession = {
24929
25162
  id: sessionId,
24930
25163
  status: "active",
24931
25164
  name: params.name || `session-${sessionId}`,
24932
- scope: {
24933
- type: scopeType,
24934
- rootTaskId,
24935
- includeDescendants: true
24936
- },
25165
+ scope: scope.type === "global" ? { type: "global" } : { type: scope.type, rootTaskId: scope.rootTaskId, includeDescendants: true },
24937
25166
  taskWork: {
24938
25167
  taskId: startingTaskId,
24939
25168
  setAt: now2
@@ -24964,7 +25193,7 @@ async function sessionStart(projectRoot, params) {
24964
25193
  const startingTask = params.startTask;
24965
25194
  if (startingTask) {
24966
25195
  taskData.focus.currentTask = startingTask;
24967
- } else if (params.autoStart) {
25196
+ } else if (params.autoStart && rootTaskId) {
24968
25197
  taskData.focus.currentTask = rootTaskId;
24969
25198
  }
24970
25199
  if (taskData._meta) {
@@ -25570,7 +25799,7 @@ async function orchestrateValidate(taskId, projectRoot) {
25570
25799
  async function orchestrateSpawnExecute(taskId, adapterId, protocolType, projectRoot, _tier) {
25571
25800
  const cwd = projectRoot ?? process.cwd();
25572
25801
  try {
25573
- const { initializeDefaultAdapters: initializeDefaultAdapters2, spawnRegistry: spawnRegistry2 } = await Promise.resolve().then(() => (init_adapter_registry(), adapter_registry_exports));
25802
+ const { initializeDefaultAdapters: initializeDefaultAdapters2, spawnRegistry: spawnRegistry2 } = await Promise.resolve().then(() => (init_adapter_registry2(), adapter_registry_exports));
25574
25803
  await initializeDefaultAdapters2();
25575
25804
  let adapter;
25576
25805
  if (adapterId) {
@@ -26699,7 +26928,10 @@ async function checkEpicCompleteness(releaseTaskIds, cwd, accessor) {
26699
26928
  if (!epic) continue;
26700
26929
  const allChildren = data.tasks.filter((t) => t.parentId === epicId && t.id !== epicId);
26701
26930
  const includedSet = new Set(includedTasks);
26702
- const missingChildren = allChildren.filter((t) => !includedSet.has(t.id) && !releaseSet.has(t.id)).map((t) => ({ id: t.id, title: t.title, status: t.status }));
26931
+ const parentIds = new Set(data.tasks.filter((t) => t.parentId).map((t) => t.parentId));
26932
+ const missingChildren = allChildren.filter(
26933
+ (t) => t.status === "done" && !parentIds.has(t.id) && !includedSet.has(t.id) && !releaseSet.has(t.id)
26934
+ ).map((t) => ({ id: t.id, title: t.title, status: t.status }));
26703
26935
  if (missingChildren.length > 0) hasIncomplete = true;
26704
26936
  epics.push({
26705
26937
  epicId,
@@ -26854,9 +27086,9 @@ init_json();
26854
27086
  init_sqlite();
26855
27087
  init_tasks_schema();
26856
27088
  import { execFileSync as execFileSync5 } from "node:child_process";
26857
- import { existsSync as existsSync41, renameSync as renameSync7 } from "node:fs";
27089
+ import { existsSync as existsSync42, renameSync as renameSync7 } from "node:fs";
26858
27090
  import { readFile as readFile11 } from "node:fs/promises";
26859
- import { join as join42 } from "node:path";
27091
+ import { join as join43 } from "node:path";
26860
27092
  import { and as and6, count as count4, desc as desc5, eq as eq15 } from "drizzle-orm";
26861
27093
  init_paths();
26862
27094
 
@@ -26951,6 +27183,170 @@ function escapeRegex(str) {
26951
27183
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26952
27184
  }
26953
27185
 
27186
+ // src/core/release/version-bump.ts
27187
+ init_exit_codes();
27188
+ init_errors();
27189
+ init_paths();
27190
+ import { existsSync as existsSync41, readFileSync as readFileSync26, writeFileSync as writeFileSync6 } from "node:fs";
27191
+ import { join as join42 } from "node:path";
27192
+ function readConfigValueSync2(path, defaultValue, cwd) {
27193
+ try {
27194
+ const configPath = join42(getCleoDir(cwd), "config.json");
27195
+ if (!existsSync41(configPath)) return defaultValue;
27196
+ const config = JSON.parse(readFileSync26(configPath, "utf-8"));
27197
+ const keys = path.split(".");
27198
+ let value = config;
27199
+ for (const key of keys) {
27200
+ if (value == null || typeof value !== "object") return defaultValue;
27201
+ value = value[key];
27202
+ }
27203
+ return value ?? defaultValue;
27204
+ } catch {
27205
+ return defaultValue;
27206
+ }
27207
+ }
27208
+ var VERSION_WITH_PRERELEASE = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
27209
+ function validateVersionFormat(version) {
27210
+ return VERSION_WITH_PRERELEASE.test(version);
27211
+ }
27212
+ function getVersionBumpConfig(cwd) {
27213
+ try {
27214
+ const raw = readConfigValueSync2("release.versionBump.files", [], cwd);
27215
+ return raw.map((entry) => ({
27216
+ file: entry.path ?? entry.file ?? "",
27217
+ strategy: entry.strategy,
27218
+ field: entry.jsonPath?.replace(/^\./, "") ?? entry.field,
27219
+ key: entry.key,
27220
+ section: entry.section,
27221
+ pattern: entry.sedPattern ?? entry.pattern
27222
+ })).filter((t) => t.file !== "");
27223
+ } catch {
27224
+ return [];
27225
+ }
27226
+ }
27227
+ function bumpFile(target, newVersion, projectRoot) {
27228
+ const filePath = join42(projectRoot, target.file);
27229
+ if (!existsSync41(filePath)) {
27230
+ return {
27231
+ file: target.file,
27232
+ strategy: target.strategy,
27233
+ success: false,
27234
+ error: `File not found: ${target.file}`
27235
+ };
27236
+ }
27237
+ try {
27238
+ const content = readFileSync26(filePath, "utf-8");
27239
+ let previousVersion;
27240
+ let newContent;
27241
+ switch (target.strategy) {
27242
+ case "plain": {
27243
+ previousVersion = content.trim();
27244
+ newContent = newVersion + "\n";
27245
+ break;
27246
+ }
27247
+ case "json": {
27248
+ const field = target.field ?? "version";
27249
+ const json = JSON.parse(content);
27250
+ previousVersion = getNestedField(json, field);
27251
+ setNestedField(json, field, newVersion);
27252
+ newContent = JSON.stringify(json, null, 2) + "\n";
27253
+ break;
27254
+ }
27255
+ case "toml": {
27256
+ const key = target.key ?? "version";
27257
+ const versionRegex = new RegExp(`^(${key}\\s*=\\s*")([^"]+)(")`, "m");
27258
+ const match = content.match(versionRegex);
27259
+ previousVersion = match?.[2];
27260
+ newContent = content.replace(versionRegex, `$1${newVersion}$3`);
27261
+ break;
27262
+ }
27263
+ case "sed": {
27264
+ const pattern = target.pattern ?? "";
27265
+ if (!pattern.includes("{{VERSION}}")) {
27266
+ return {
27267
+ file: target.file,
27268
+ strategy: target.strategy,
27269
+ success: false,
27270
+ error: "sed strategy requires {{VERSION}} placeholder in pattern"
27271
+ };
27272
+ }
27273
+ const regex = new RegExp(pattern.replace("{{VERSION}}", "([\\d.]+)"));
27274
+ const match = content.match(regex);
27275
+ previousVersion = match?.[1];
27276
+ newContent = content.replace(regex, pattern.replace("{{VERSION}}", newVersion));
27277
+ break;
27278
+ }
27279
+ default:
27280
+ return {
27281
+ file: target.file,
27282
+ strategy: target.strategy,
27283
+ success: false,
27284
+ error: `Unknown strategy: ${target.strategy}`
27285
+ };
27286
+ }
27287
+ writeFileSync6(filePath, newContent, "utf-8");
27288
+ return {
27289
+ file: target.file,
27290
+ strategy: target.strategy,
27291
+ success: true,
27292
+ previousVersion,
27293
+ newVersion
27294
+ };
27295
+ } catch (err) {
27296
+ return {
27297
+ file: target.file,
27298
+ strategy: target.strategy,
27299
+ success: false,
27300
+ error: String(err)
27301
+ };
27302
+ }
27303
+ }
27304
+ function bumpVersionFromConfig(newVersion, options = {}, cwd) {
27305
+ if (!validateVersionFormat(newVersion)) {
27306
+ throw new CleoError(
27307
+ 6 /* VALIDATION_ERROR */,
27308
+ `Invalid version: '${newVersion}' (expected X.Y.Z or YYYY.M.patch)`
27309
+ );
27310
+ }
27311
+ const targets = getVersionBumpConfig(cwd);
27312
+ if (targets.length === 0) {
27313
+ throw new CleoError(
27314
+ 1 /* GENERAL_ERROR */,
27315
+ "No version bump targets configured. Add release.versionBump.files to .cleo/config.json"
27316
+ );
27317
+ }
27318
+ const projectRoot = getProjectRoot(cwd);
27319
+ const results = [];
27320
+ if (options.dryRun) {
27321
+ for (const target of targets) {
27322
+ results.push({
27323
+ file: target.file,
27324
+ strategy: target.strategy,
27325
+ success: true,
27326
+ newVersion
27327
+ });
27328
+ }
27329
+ return { results, allSuccess: true };
27330
+ }
27331
+ for (const target of targets) {
27332
+ results.push(bumpFile(target, newVersion, projectRoot));
27333
+ }
27334
+ const allSuccess = results.every((r) => r.success);
27335
+ return { results, allSuccess };
27336
+ }
27337
+ function getNestedField(obj, path) {
27338
+ return path.split(".").reduce((acc, key) => acc?.[key], obj);
27339
+ }
27340
+ function setNestedField(obj, path, value) {
27341
+ const parts = path.split(".");
27342
+ let current = obj;
27343
+ for (let i = 0; i < parts.length - 1; i++) {
27344
+ if (typeof current[parts[i]] !== "object") current[parts[i]] = {};
27345
+ current = current[parts[i]];
27346
+ }
27347
+ current[parts[parts.length - 1]] = value;
27348
+ }
27349
+
26954
27350
  // src/core/release/release-manifest.ts
26955
27351
  function normalizeLimit2(limit) {
26956
27352
  return typeof limit === "number" && limit > 0 ? limit : void 0;
@@ -27176,7 +27572,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
27176
27572
  }
27177
27573
  const changelog = sections.join("\n");
27178
27574
  await db.update(releaseManifests).set({ changelog }).where(eq15(releaseManifests.version, normalizedVersion)).run();
27179
- const changelogPath = join42(cwd ?? process.cwd(), "CHANGELOG.md");
27575
+ const changelogPath = join43(cwd ?? process.cwd(), "CHANGELOG.md");
27180
27576
  let existingChangelogContent = "";
27181
27577
  try {
27182
27578
  existingChangelogContent = await readFile11(changelogPath, "utf8");
@@ -27290,13 +27686,13 @@ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
27290
27686
  message: incompleteTasks.length === 0 ? "All tasks completed" : `${incompleteTasks.length} tasks not completed: ${incompleteTasks.join(", ")}`
27291
27687
  });
27292
27688
  const projectRoot = cwd ?? getProjectRoot();
27293
- const distPath = join42(projectRoot, "dist", "cli", "index.js");
27294
- const isNodeProject = existsSync41(join42(projectRoot, "package.json"));
27689
+ const distPath = join43(projectRoot, "dist", "cli", "index.js");
27690
+ const isNodeProject = existsSync42(join43(projectRoot, "package.json"));
27295
27691
  if (isNodeProject) {
27296
27692
  gates.push({
27297
27693
  name: "build_artifact",
27298
- status: existsSync41(distPath) ? "passed" : "failed",
27299
- message: existsSync41(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
27694
+ status: existsSync42(distPath) ? "passed" : "failed",
27695
+ message: existsSync42(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
27300
27696
  });
27301
27697
  }
27302
27698
  if (opts?.dryRun) {
@@ -27306,6 +27702,8 @@ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
27306
27702
  message: "Skipped in dry-run mode"
27307
27703
  });
27308
27704
  } else {
27705
+ const bumpTargets = getVersionBumpConfig(cwd);
27706
+ const allowedDirty = /* @__PURE__ */ new Set(["CHANGELOG.md", ...bumpTargets.map((t) => t.file)]);
27309
27707
  let workingTreeClean = true;
27310
27708
  let dirtyFiles = [];
27311
27709
  try {
@@ -27314,14 +27712,15 @@ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
27314
27712
  encoding: "utf-8",
27315
27713
  stdio: "pipe"
27316
27714
  });
27317
- 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");
27715
+ dirtyFiles = porcelain.split("\n").filter((l) => l.trim()).filter((l) => !l.startsWith("?? ")).map((l) => l.slice(3).trim()).filter((f) => !allowedDirty.has(f));
27318
27716
  workingTreeClean = dirtyFiles.length === 0;
27319
27717
  } catch {
27320
27718
  }
27719
+ const excludeList = [...allowedDirty].join(", ");
27321
27720
  gates.push({
27322
27721
  name: "clean_working_tree",
27323
27722
  status: workingTreeClean ? "passed" : "failed",
27324
- 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)` : ""}`
27723
+ message: workingTreeClean ? `Working tree clean (excluding ${excludeList})` : `Uncommitted changes in: ${dirtyFiles.slice(0, 5).join(", ")}${dirtyFiles.length > 5 ? ` (+${dirtyFiles.length - 5} more)` : ""}`
27325
27724
  });
27326
27725
  }
27327
27726
  const isPreRelease = normalizedVersion.includes("-");
@@ -27436,7 +27835,7 @@ async function rollbackRelease(version, reason, cwd) {
27436
27835
  };
27437
27836
  }
27438
27837
  async function readPushPolicy(cwd) {
27439
- const configPath = join42(getCleoDirAbsolute(cwd), "config.json");
27838
+ const configPath = join43(getCleoDirAbsolute(cwd), "config.json");
27440
27839
  const config = await readJson(configPath);
27441
27840
  if (!config) return void 0;
27442
27841
  const release2 = config.release;
@@ -27534,170 +27933,6 @@ async function markReleasePushed(version, pushedAt, cwd, provenance) {
27534
27933
  }).where(eq15(releaseManifests.version, normalizedVersion)).run();
27535
27934
  }
27536
27935
 
27537
- // src/core/release/version-bump.ts
27538
- init_exit_codes();
27539
- init_errors();
27540
- init_paths();
27541
- import { existsSync as existsSync42, readFileSync as readFileSync26, writeFileSync as writeFileSync6 } from "node:fs";
27542
- import { join as join43 } from "node:path";
27543
- function readConfigValueSync2(path, defaultValue, cwd) {
27544
- try {
27545
- const configPath = join43(getCleoDir(cwd), "config.json");
27546
- if (!existsSync42(configPath)) return defaultValue;
27547
- const config = JSON.parse(readFileSync26(configPath, "utf-8"));
27548
- const keys = path.split(".");
27549
- let value = config;
27550
- for (const key of keys) {
27551
- if (value == null || typeof value !== "object") return defaultValue;
27552
- value = value[key];
27553
- }
27554
- return value ?? defaultValue;
27555
- } catch {
27556
- return defaultValue;
27557
- }
27558
- }
27559
- var VERSION_WITH_PRERELEASE = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
27560
- function validateVersionFormat(version) {
27561
- return VERSION_WITH_PRERELEASE.test(version);
27562
- }
27563
- function getVersionBumpConfig(cwd) {
27564
- try {
27565
- const raw = readConfigValueSync2("release.versionBump.files", [], cwd);
27566
- return raw.map((entry) => ({
27567
- file: entry.path ?? entry.file ?? "",
27568
- strategy: entry.strategy,
27569
- field: entry.jsonPath?.replace(/^\./, "") ?? entry.field,
27570
- key: entry.key,
27571
- section: entry.section,
27572
- pattern: entry.sedPattern ?? entry.pattern
27573
- })).filter((t) => t.file !== "");
27574
- } catch {
27575
- return [];
27576
- }
27577
- }
27578
- function bumpFile(target, newVersion, projectRoot) {
27579
- const filePath = join43(projectRoot, target.file);
27580
- if (!existsSync42(filePath)) {
27581
- return {
27582
- file: target.file,
27583
- strategy: target.strategy,
27584
- success: false,
27585
- error: `File not found: ${target.file}`
27586
- };
27587
- }
27588
- try {
27589
- const content = readFileSync26(filePath, "utf-8");
27590
- let previousVersion;
27591
- let newContent;
27592
- switch (target.strategy) {
27593
- case "plain": {
27594
- previousVersion = content.trim();
27595
- newContent = newVersion + "\n";
27596
- break;
27597
- }
27598
- case "json": {
27599
- const field = target.field ?? "version";
27600
- const json = JSON.parse(content);
27601
- previousVersion = getNestedField(json, field);
27602
- setNestedField(json, field, newVersion);
27603
- newContent = JSON.stringify(json, null, 2) + "\n";
27604
- break;
27605
- }
27606
- case "toml": {
27607
- const key = target.key ?? "version";
27608
- const versionRegex = new RegExp(`^(${key}\\s*=\\s*")([^"]+)(")`, "m");
27609
- const match = content.match(versionRegex);
27610
- previousVersion = match?.[2];
27611
- newContent = content.replace(versionRegex, `$1${newVersion}$3`);
27612
- break;
27613
- }
27614
- case "sed": {
27615
- const pattern = target.pattern ?? "";
27616
- if (!pattern.includes("{{VERSION}}")) {
27617
- return {
27618
- file: target.file,
27619
- strategy: target.strategy,
27620
- success: false,
27621
- error: "sed strategy requires {{VERSION}} placeholder in pattern"
27622
- };
27623
- }
27624
- const regex = new RegExp(pattern.replace("{{VERSION}}", "([\\d.]+)"));
27625
- const match = content.match(regex);
27626
- previousVersion = match?.[1];
27627
- newContent = content.replace(regex, pattern.replace("{{VERSION}}", newVersion));
27628
- break;
27629
- }
27630
- default:
27631
- return {
27632
- file: target.file,
27633
- strategy: target.strategy,
27634
- success: false,
27635
- error: `Unknown strategy: ${target.strategy}`
27636
- };
27637
- }
27638
- writeFileSync6(filePath, newContent, "utf-8");
27639
- return {
27640
- file: target.file,
27641
- strategy: target.strategy,
27642
- success: true,
27643
- previousVersion,
27644
- newVersion
27645
- };
27646
- } catch (err) {
27647
- return {
27648
- file: target.file,
27649
- strategy: target.strategy,
27650
- success: false,
27651
- error: String(err)
27652
- };
27653
- }
27654
- }
27655
- function bumpVersionFromConfig(newVersion, options = {}, cwd) {
27656
- if (!validateVersionFormat(newVersion)) {
27657
- throw new CleoError(
27658
- 6 /* VALIDATION_ERROR */,
27659
- `Invalid version: '${newVersion}' (expected X.Y.Z or YYYY.M.patch)`
27660
- );
27661
- }
27662
- const targets = getVersionBumpConfig(cwd);
27663
- if (targets.length === 0) {
27664
- throw new CleoError(
27665
- 1 /* GENERAL_ERROR */,
27666
- "No version bump targets configured. Add release.versionBump.files to .cleo/config.json"
27667
- );
27668
- }
27669
- const projectRoot = getProjectRoot(cwd);
27670
- const results = [];
27671
- if (options.dryRun) {
27672
- for (const target of targets) {
27673
- results.push({
27674
- file: target.file,
27675
- strategy: target.strategy,
27676
- success: true,
27677
- newVersion
27678
- });
27679
- }
27680
- return { results, allSuccess: true };
27681
- }
27682
- for (const target of targets) {
27683
- results.push(bumpFile(target, newVersion, projectRoot));
27684
- }
27685
- const allSuccess = results.every((r) => r.success);
27686
- return { results, allSuccess };
27687
- }
27688
- function getNestedField(obj, path) {
27689
- return path.split(".").reduce((acc, key) => acc?.[key], obj);
27690
- }
27691
- function setNestedField(obj, path, value) {
27692
- const parts = path.split(".");
27693
- let current = obj;
27694
- for (let i = 0; i < parts.length - 1; i++) {
27695
- if (typeof current[parts[i]] !== "object") current[parts[i]] = {};
27696
- current = current[parts[i]];
27697
- }
27698
- current[parts[parts.length - 1]] = value;
27699
- }
27700
-
27701
27936
  // src/dispatch/engines/release-engine.ts
27702
27937
  init_data_accessor();
27703
27938
  init_error();