@cleocode/cleo 2026.3.16 → 2026.3.17

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
@@ -4626,13 +4626,13 @@ async function getDb(cwd) {
4626
4626
  if (!_gitTrackingChecked) {
4627
4627
  _gitTrackingChecked = true;
4628
4628
  try {
4629
- const { execFileSync: execFileSync13 } = await import("node:child_process");
4629
+ const { execFileSync: execFileSync15 } = await import("node:child_process");
4630
4630
  const gitCwd = resolve3(dbPath, "..", "..");
4631
4631
  const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
4632
4632
  const log7 = getLogger("sqlite");
4633
4633
  for (const fileToCheck of filesToCheck) {
4634
4634
  try {
4635
- execFileSync13("git", ["ls-files", "--error-unmatch", fileToCheck], {
4635
+ execFileSync15("git", ["ls-files", "--error-unmatch", fileToCheck], {
4636
4636
  cwd: gitCwd,
4637
4637
  stdio: "pipe"
4638
4638
  });
@@ -15396,6 +15396,7 @@ async function getProjectStats(opts, accessor) {
15396
15396
  const active = tasks2.filter((t) => t.status === "active").length;
15397
15397
  const done = tasks2.filter((t) => t.status === "done").length;
15398
15398
  const blocked = tasks2.filter((t) => t.status === "blocked").length;
15399
+ const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
15399
15400
  const totalActive = tasks2.length;
15400
15401
  const cutoff = new Date(Date.now() - periodDays * 864e5).toISOString();
15401
15402
  const entries = await queryAuditEntries(opts.cwd);
@@ -15412,11 +15413,45 @@ async function getProjectStats(opts, accessor) {
15412
15413
  (e) => isArchive(e) && e.timestamp >= cutoff
15413
15414
  ).length;
15414
15415
  const completionRate = createdInPeriod > 0 ? Math.round(completedInPeriod / createdInPeriod * 1e4) / 100 : 0;
15415
- const totalCreated = entries.filter(isCreate).length;
15416
- const totalCompleted = entries.filter(isComplete).length;
15417
- const totalArchived = entries.filter(isArchive).length;
15416
+ let totalCreated = 0;
15417
+ let totalCompleted = 0;
15418
+ let totalCancelled = 0;
15419
+ let totalArchived = 0;
15420
+ let archivedCompleted = 0;
15421
+ let archivedCount = 0;
15422
+ try {
15423
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
15424
+ const { count: dbCount, eq: dbEq, and: dbAnd } = await import("drizzle-orm");
15425
+ const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
15426
+ const db = await getDb2(opts.cwd);
15427
+ const statusRows = await db.select({ status: tasksTable.status, c: dbCount() }).from(tasksTable).groupBy(tasksTable.status).all();
15428
+ const statusMap = {};
15429
+ for (const row of statusRows) {
15430
+ statusMap[row.status] = row.c;
15431
+ }
15432
+ archivedCount = statusMap["archived"] ?? 0;
15433
+ totalCreated = Object.values(statusMap).reduce((sum, n) => sum + n, 0);
15434
+ totalCancelled = statusMap["cancelled"] ?? 0;
15435
+ totalArchived = archivedCount;
15436
+ const archivedDoneRow = await db.select({ c: dbCount() }).from(tasksTable).where(dbAnd(dbEq(tasksTable.status, "archived"), dbEq(tasksTable.archiveReason, "completed"))).get();
15437
+ archivedCompleted = archivedDoneRow?.c ?? 0;
15438
+ totalCompleted = (statusMap["done"] ?? 0) + archivedCompleted;
15439
+ } catch {
15440
+ totalCreated = entries.filter(isCreate).length;
15441
+ totalCompleted = entries.filter(isComplete).length;
15442
+ totalArchived = entries.filter(isArchive).length;
15443
+ }
15418
15444
  return {
15419
- currentState: { pending, active, done, blocked, totalActive },
15445
+ currentState: {
15446
+ pending,
15447
+ active,
15448
+ done,
15449
+ blocked,
15450
+ cancelled,
15451
+ totalActive,
15452
+ archived: archivedCount,
15453
+ grandTotal: totalActive + archivedCount
15454
+ },
15420
15455
  completionMetrics: {
15421
15456
  periodDays,
15422
15457
  completedInPeriod,
@@ -15428,7 +15463,7 @@ async function getProjectStats(opts, accessor) {
15428
15463
  completedInPeriod,
15429
15464
  archivedInPeriod
15430
15465
  },
15431
- allTime: { totalCreated, totalCompleted, totalArchived }
15466
+ allTime: { totalCreated, totalCompleted, totalCancelled, totalArchived, archivedCompleted }
15432
15467
  };
15433
15468
  }
15434
15469
  function rankBlockedTask(task, allTasks, focusTask) {
@@ -15472,7 +15507,18 @@ async function getDashboard(opts, accessor) {
15472
15507
  const active = tasks2.filter((t) => t.status === "active").length;
15473
15508
  const done = tasks2.filter((t) => t.status === "done").length;
15474
15509
  const blocked = tasks2.filter((t) => t.status === "blocked").length;
15510
+ const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
15475
15511
  const total = tasks2.length;
15512
+ let archived = 0;
15513
+ try {
15514
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
15515
+ const { count: dbCount, eq: dbEq } = await import("drizzle-orm");
15516
+ const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
15517
+ const db = await getDb2(opts.cwd);
15518
+ const row = await db.select({ c: dbCount() }).from(tasksTable).where(dbEq(tasksTable.status, "archived")).get();
15519
+ archived = row?.c ?? 0;
15520
+ } catch {
15521
+ }
15476
15522
  const project = data.project?.name ?? "Unknown Project";
15477
15523
  const currentPhase = data.project?.currentPhase ?? null;
15478
15524
  const focusId = data.focus?.currentTask ?? null;
@@ -15480,7 +15526,7 @@ async function getDashboard(opts, accessor) {
15480
15526
  if (focusId) {
15481
15527
  focusTask = tasks2.find((t) => t.id === focusId) ?? null;
15482
15528
  }
15483
- const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done").sort((a, b) => {
15529
+ const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done" && t.status !== "cancelled").sort((a, b) => {
15484
15530
  const pDiff = (PRIORITY_ORDER[a.priority ?? "low"] ?? 9) - (PRIORITY_ORDER[b.priority ?? "low"] ?? 9);
15485
15531
  if (pDiff !== 0) return pDiff;
15486
15532
  return (a.createdAt ?? "").localeCompare(b.createdAt ?? "");
@@ -15493,6 +15539,7 @@ async function getDashboard(opts, accessor) {
15493
15539
  }).map((r) => r.task);
15494
15540
  const labelMap = {};
15495
15541
  for (const t of tasks2) {
15542
+ if (t.status === "cancelled") continue;
15496
15543
  for (const label of t.labels ?? []) {
15497
15544
  labelMap[label] = (labelMap[label] ?? 0) + 1;
15498
15545
  }
@@ -15501,7 +15548,7 @@ async function getDashboard(opts, accessor) {
15501
15548
  return {
15502
15549
  project,
15503
15550
  currentPhase,
15504
- summary: { pending, active, blocked, done, total },
15551
+ summary: { pending, active, blocked, done, cancelled, total, archived, grandTotal: total + archived },
15505
15552
  focus: { currentTask: focusId, task: focusTask },
15506
15553
  highPriority: { count: highPriority.length, tasks: highPriority.slice(0, 5) },
15507
15554
  blockedTasks: {
@@ -19553,7 +19600,9 @@ async function systemDash(projectRoot, params) {
19553
19600
  blocked: summary.blocked,
19554
19601
  done: summary.done,
19555
19602
  cancelled: summary.cancelled ?? 0,
19556
- total: summary.total
19603
+ total: summary.total,
19604
+ archived: summary.archived ?? 0,
19605
+ grandTotal: summary.grandTotal ?? summary.total
19557
19606
  },
19558
19607
  taskWork: data.focus ?? data.taskWork,
19559
19608
  activeSession: data.activeSession ?? null,
@@ -19573,17 +19622,18 @@ async function systemStats(projectRoot, params) {
19573
19622
  const result = await getProjectStats({ period: String(params?.period ?? 30), cwd: projectRoot }, accessor);
19574
19623
  const taskData = await accessor.loadTaskFile();
19575
19624
  const tasks2 = taskData?.tasks ?? [];
19625
+ const activeTasks = tasks2.filter((t) => t.status !== "cancelled");
19576
19626
  const byPriority = {};
19577
- for (const t of tasks2) {
19627
+ for (const t of activeTasks) {
19578
19628
  byPriority[t.priority] = (byPriority[t.priority] ?? 0) + 1;
19579
19629
  }
19580
19630
  const byType = {};
19581
- for (const t of tasks2) {
19631
+ for (const t of activeTasks) {
19582
19632
  const type = t.type || "task";
19583
19633
  byType[type] = (byType[type] ?? 0) + 1;
19584
19634
  }
19585
19635
  const byPhase = {};
19586
- for (const t of tasks2) {
19636
+ for (const t of activeTasks) {
19587
19637
  const phase = t.phase || "unassigned";
19588
19638
  byPhase[phase] = (byPhase[phase] ?? 0) + 1;
19589
19639
  }
@@ -19613,7 +19663,9 @@ async function systemStats(projectRoot, params) {
19613
19663
  done: currentState.done,
19614
19664
  blocked: currentState.blocked,
19615
19665
  cancelled: tasks2.filter((t) => t.status === "cancelled").length,
19616
- totalActive: currentState.totalActive
19666
+ totalActive: currentState.totalActive,
19667
+ archived: currentState.archived ?? 0,
19668
+ grandTotal: currentState.grandTotal ?? currentState.totalActive
19617
19669
  },
19618
19670
  byPriority,
19619
19671
  byType,
@@ -19638,10 +19690,10 @@ async function systemLog(projectRoot, filters) {
19638
19690
  }
19639
19691
  async function queryAuditLogSqlite(projectRoot, filters) {
19640
19692
  try {
19641
- const { join: join76 } = await import("node:path");
19642
- const { existsSync: existsSync71 } = await import("node:fs");
19643
- const dbPath = join76(projectRoot, ".cleo", "tasks.db");
19644
- if (!existsSync71(dbPath)) {
19693
+ const { join: join77 } = await import("node:path");
19694
+ const { existsSync: existsSync72 } = await import("node:fs");
19695
+ const dbPath = join77(projectRoot, ".cleo", "tasks.db");
19696
+ if (!existsSync72(dbPath)) {
19645
19697
  const offset = filters?.offset ?? 0;
19646
19698
  const limit = filters?.limit ?? 20;
19647
19699
  return {
@@ -20497,10 +20549,10 @@ async function readProjectMeta(projectPath) {
20497
20549
  }
20498
20550
  async function readProjectId(projectPath) {
20499
20551
  try {
20500
- const { readFileSync: readFileSync52, existsSync: existsSync71 } = await import("node:fs");
20552
+ const { readFileSync: readFileSync53, existsSync: existsSync72 } = await import("node:fs");
20501
20553
  const infoPath = join33(projectPath, ".cleo", "project-info.json");
20502
- if (!existsSync71(infoPath)) return "";
20503
- const data = JSON.parse(readFileSync52(infoPath, "utf-8"));
20554
+ if (!existsSync72(infoPath)) return "";
20555
+ const data = JSON.parse(readFileSync53(infoPath, "utf-8"));
20504
20556
  return typeof data.projectId === "string" ? data.projectId : "";
20505
20557
  } catch {
20506
20558
  return "";
@@ -27978,11 +28030,353 @@ var init_changelog_writer = __esm({
27978
28030
  }
27979
28031
  });
27980
28032
 
27981
- // src/core/release/release-manifest.ts
27982
- import { existsSync as existsSync48, renameSync as renameSync7 } from "node:fs";
27983
- import { readFile as readFile10 } from "node:fs/promises";
28033
+ // src/core/release/github-pr.ts
27984
28034
  import { execFileSync as execFileSync6 } from "node:child_process";
28035
+ function isGhCliAvailable() {
28036
+ try {
28037
+ execFileSync6("gh", ["--version"], { stdio: "pipe" });
28038
+ return true;
28039
+ } catch {
28040
+ return false;
28041
+ }
28042
+ }
28043
+ function extractRepoOwnerAndName(remote) {
28044
+ const trimmed = remote.trim();
28045
+ const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
28046
+ if (httpsMatch) {
28047
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
28048
+ }
28049
+ const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
28050
+ if (sshMatch) {
28051
+ return { owner: sshMatch[1], repo: sshMatch[2] };
28052
+ }
28053
+ return null;
28054
+ }
28055
+ async function detectBranchProtection(branch, remote, projectRoot) {
28056
+ const cwdOpts = projectRoot ? { cwd: projectRoot } : {};
28057
+ if (isGhCliAvailable()) {
28058
+ try {
28059
+ const remoteUrl = execFileSync6("git", ["remote", "get-url", remote], {
28060
+ encoding: "utf-8",
28061
+ stdio: "pipe",
28062
+ ...cwdOpts
28063
+ }).trim();
28064
+ const identity = extractRepoOwnerAndName(remoteUrl);
28065
+ if (identity) {
28066
+ const { owner, repo } = identity;
28067
+ try {
28068
+ execFileSync6(
28069
+ "gh",
28070
+ ["api", `/repos/${owner}/${repo}/branches/${branch}/protection`],
28071
+ {
28072
+ encoding: "utf-8",
28073
+ stdio: "pipe",
28074
+ ...cwdOpts
28075
+ }
28076
+ );
28077
+ return { protected: true, detectionMethod: "gh-api" };
28078
+ } catch (apiErr) {
28079
+ const stderr2 = apiErr instanceof Error && "stderr" in apiErr ? String(apiErr.stderr ?? "") : "";
28080
+ if (stderr2.includes("404") || stderr2.includes("Not Found")) {
28081
+ return { protected: false, detectionMethod: "gh-api" };
28082
+ }
28083
+ }
28084
+ }
28085
+ } catch {
28086
+ }
28087
+ }
28088
+ try {
28089
+ const result = execFileSync6(
28090
+ "git",
28091
+ ["push", "--dry-run", remote, `HEAD:${branch}`],
28092
+ {
28093
+ encoding: "utf-8",
28094
+ stdio: "pipe",
28095
+ ...cwdOpts
28096
+ }
28097
+ );
28098
+ const output = typeof result === "string" ? result : "";
28099
+ if (output.includes("protected branch") || output.includes("GH006") || output.includes("refusing to allow")) {
28100
+ return { protected: true, detectionMethod: "push-dry-run" };
28101
+ }
28102
+ return { protected: false, detectionMethod: "push-dry-run" };
28103
+ } catch (pushErr) {
28104
+ const stderr2 = pushErr instanceof Error && "stderr" in pushErr ? String(pushErr.stderr ?? "") : pushErr instanceof Error ? pushErr.message : String(pushErr);
28105
+ if (stderr2.includes("protected branch") || stderr2.includes("GH006") || stderr2.includes("refusing to allow")) {
28106
+ return { protected: true, detectionMethod: "push-dry-run" };
28107
+ }
28108
+ return {
28109
+ protected: false,
28110
+ detectionMethod: "unknown",
28111
+ error: stderr2
28112
+ };
28113
+ }
28114
+ }
28115
+ function buildPRBody(opts) {
28116
+ const epicLine = opts.epicId ? `**Epic**: ${opts.epicId}
28117
+
28118
+ ` : "";
28119
+ return [
28120
+ `## Release v${opts.version}`,
28121
+ "",
28122
+ `${epicLine}This PR merges the ${opts.head} branch into ${opts.base} to publish the release.`,
28123
+ "",
28124
+ "### Checklist",
28125
+ "- [ ] CHANGELOG.md updated",
28126
+ "- [ ] All release tasks complete",
28127
+ "- [ ] Version bump committed",
28128
+ "",
28129
+ "---",
28130
+ "*Created by CLEO release pipeline*"
28131
+ ].join("\n");
28132
+ }
28133
+ function formatManualPRInstructions(opts) {
28134
+ const epicSuffix = opts.epicId ? ` (${opts.epicId})` : "";
28135
+ return [
28136
+ "Branch protection detected or gh CLI unavailable. Create the PR manually:",
28137
+ "",
28138
+ ` gh pr create \\`,
28139
+ ` --base ${opts.base} \\`,
28140
+ ` --head ${opts.head} \\`,
28141
+ ` --title "${opts.title}" \\`,
28142
+ ` --body "Release v${opts.version}${epicSuffix}"`,
28143
+ "",
28144
+ `Or visit: https://github.com/[owner]/[repo]/compare/${opts.base}...${opts.head}`,
28145
+ "",
28146
+ "After merging, CI will automatically publish to npm."
28147
+ ].join("\n");
28148
+ }
28149
+ async function createPullRequest(opts) {
28150
+ if (!isGhCliAvailable()) {
28151
+ return {
28152
+ mode: "manual",
28153
+ instructions: formatManualPRInstructions(opts)
28154
+ };
28155
+ }
28156
+ const body = buildPRBody(opts);
28157
+ const args = [
28158
+ "pr",
28159
+ "create",
28160
+ "--base",
28161
+ opts.base,
28162
+ "--head",
28163
+ opts.head,
28164
+ "--title",
28165
+ opts.title,
28166
+ "--body",
28167
+ body
28168
+ ];
28169
+ if (opts.labels && opts.labels.length > 0) {
28170
+ for (const label of opts.labels) {
28171
+ args.push("--label", label);
28172
+ }
28173
+ }
28174
+ try {
28175
+ const output = execFileSync6("gh", args, {
28176
+ encoding: "utf-8",
28177
+ stdio: "pipe",
28178
+ ...opts.projectRoot ? { cwd: opts.projectRoot } : {}
28179
+ });
28180
+ const prUrl = output.trim();
28181
+ const numberMatch = prUrl.match(/\/pull\/(\d+)$/);
28182
+ const prNumber = numberMatch ? parseInt(numberMatch[1], 10) : void 0;
28183
+ return {
28184
+ mode: "created",
28185
+ prUrl,
28186
+ prNumber
28187
+ };
28188
+ } catch (err) {
28189
+ const stderr2 = err instanceof Error && "stderr" in err ? String(err.stderr ?? "") : err instanceof Error ? err.message : String(err);
28190
+ if (stderr2.includes("already exists")) {
28191
+ const urlMatch = stderr2.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
28192
+ const existingUrl = urlMatch ? urlMatch[0] : void 0;
28193
+ return {
28194
+ mode: "skipped",
28195
+ prUrl: existingUrl,
28196
+ instructions: "PR already exists"
28197
+ };
28198
+ }
28199
+ return {
28200
+ mode: "manual",
28201
+ instructions: formatManualPRInstructions(opts),
28202
+ error: stderr2
28203
+ };
28204
+ }
28205
+ }
28206
+ var init_github_pr = __esm({
28207
+ "src/core/release/github-pr.ts"() {
28208
+ "use strict";
28209
+ }
28210
+ });
28211
+
28212
+ // src/core/release/channel.ts
28213
+ function getDefaultChannelConfig() {
28214
+ return {
28215
+ main: "main",
28216
+ develop: "develop",
28217
+ feature: "feature/"
28218
+ };
28219
+ }
28220
+ function resolveChannelFromBranch(branch, config) {
28221
+ const cfg = config ?? getDefaultChannelConfig();
28222
+ if (cfg.custom) {
28223
+ if (Object.prototype.hasOwnProperty.call(cfg.custom, branch)) {
28224
+ return cfg.custom[branch];
28225
+ }
28226
+ let bestPrefix = "";
28227
+ let bestChannel;
28228
+ for (const [key, channel] of Object.entries(cfg.custom)) {
28229
+ if (branch.startsWith(key) && key.length > bestPrefix.length) {
28230
+ bestPrefix = key;
28231
+ bestChannel = channel;
28232
+ }
28233
+ }
28234
+ if (bestChannel !== void 0) {
28235
+ return bestChannel;
28236
+ }
28237
+ }
28238
+ if (branch === cfg.main) {
28239
+ return "latest";
28240
+ }
28241
+ if (branch === cfg.develop) {
28242
+ return "beta";
28243
+ }
28244
+ const alphaPrefixes = ["feature/", "hotfix/", "release/"];
28245
+ if (cfg.feature && !alphaPrefixes.includes(cfg.feature)) {
28246
+ alphaPrefixes.push(cfg.feature);
28247
+ }
28248
+ for (const prefix of alphaPrefixes) {
28249
+ if (branch.startsWith(prefix)) {
28250
+ return "alpha";
28251
+ }
28252
+ }
28253
+ return "alpha";
28254
+ }
28255
+ function channelToDistTag(channel) {
28256
+ const tags = {
28257
+ latest: "latest",
28258
+ beta: "beta",
28259
+ alpha: "alpha"
28260
+ };
28261
+ return tags[channel];
28262
+ }
28263
+ function describeChannel(channel) {
28264
+ const descriptions = {
28265
+ latest: "stable release published to npm @latest",
28266
+ beta: "pre-release published to npm @beta (develop branch)",
28267
+ alpha: "early pre-release published to npm @alpha (feature/hotfix branches)"
28268
+ };
28269
+ return descriptions[channel];
28270
+ }
28271
+ var init_channel = __esm({
28272
+ "src/core/release/channel.ts"() {
28273
+ "use strict";
28274
+ }
28275
+ });
28276
+
28277
+ // src/core/release/release-config.ts
28278
+ import { existsSync as existsSync48, readFileSync as readFileSync38 } from "node:fs";
27985
28279
  import { join as join46 } from "node:path";
28280
+ function readConfigValueSync(path, defaultValue, cwd) {
28281
+ try {
28282
+ const configPath = join46(getCleoDir(cwd), "config.json");
28283
+ if (!existsSync48(configPath)) return defaultValue;
28284
+ const config = JSON.parse(readFileSync38(configPath, "utf-8"));
28285
+ const keys = path.split(".");
28286
+ let value = config;
28287
+ for (const key of keys) {
28288
+ if (value == null || typeof value !== "object") return defaultValue;
28289
+ value = value[key];
28290
+ }
28291
+ return value ?? defaultValue;
28292
+ } catch {
28293
+ return defaultValue;
28294
+ }
28295
+ }
28296
+ function loadReleaseConfig(cwd) {
28297
+ return {
28298
+ versioningScheme: readConfigValueSync("release.versioning.scheme", DEFAULTS2.versioningScheme, cwd),
28299
+ tagPrefix: readConfigValueSync("release.versioning.tagPrefix", DEFAULTS2.tagPrefix, cwd),
28300
+ changelogFormat: readConfigValueSync("release.changelog.format", DEFAULTS2.changelogFormat, cwd),
28301
+ changelogFile: readConfigValueSync("release.changelog.file", DEFAULTS2.changelogFile, cwd),
28302
+ artifactType: readConfigValueSync("release.artifact.type", DEFAULTS2.artifactType, cwd),
28303
+ gates: readConfigValueSync("release.gates", [], cwd),
28304
+ versionBump: {
28305
+ files: readConfigValueSync("release.versionBump.files", [], cwd)
28306
+ },
28307
+ security: {
28308
+ enableProvenance: readConfigValueSync("release.security.enableProvenance", false, cwd),
28309
+ slsaLevel: readConfigValueSync("release.security.slsaLevel", 3, cwd),
28310
+ requireSignedCommits: readConfigValueSync("release.security.requireSignedCommits", false, cwd)
28311
+ }
28312
+ };
28313
+ }
28314
+ function getDefaultGitFlowConfig() {
28315
+ return {
28316
+ enabled: true,
28317
+ branches: {
28318
+ main: "main",
28319
+ develop: "develop",
28320
+ featurePrefix: "feature/",
28321
+ hotfixPrefix: "hotfix/",
28322
+ releasePrefix: "release/"
28323
+ }
28324
+ };
28325
+ }
28326
+ function getGitFlowConfig(config) {
28327
+ const defaults = getDefaultGitFlowConfig();
28328
+ if (!config.gitflow) return defaults;
28329
+ return {
28330
+ enabled: config.gitflow.enabled ?? defaults.enabled,
28331
+ branches: {
28332
+ main: config.gitflow.branches?.main ?? defaults.branches.main,
28333
+ develop: config.gitflow.branches?.develop ?? defaults.branches.develop,
28334
+ featurePrefix: config.gitflow.branches?.featurePrefix ?? defaults.branches.featurePrefix,
28335
+ hotfixPrefix: config.gitflow.branches?.hotfixPrefix ?? defaults.branches.hotfixPrefix,
28336
+ releasePrefix: config.gitflow.branches?.releasePrefix ?? defaults.branches.releasePrefix
28337
+ }
28338
+ };
28339
+ }
28340
+ function getDefaultChannelConfig2() {
28341
+ return {
28342
+ main: "latest",
28343
+ develop: "beta",
28344
+ feature: "alpha"
28345
+ };
28346
+ }
28347
+ function getChannelConfig(config) {
28348
+ const defaults = getDefaultChannelConfig2();
28349
+ if (!config.channels) return defaults;
28350
+ return {
28351
+ main: config.channels.main ?? defaults.main,
28352
+ develop: config.channels.develop ?? defaults.develop,
28353
+ feature: config.channels.feature ?? defaults.feature,
28354
+ custom: config.channels.custom
28355
+ };
28356
+ }
28357
+ function getPushMode(config) {
28358
+ return config.push?.mode ?? "auto";
28359
+ }
28360
+ var DEFAULTS2;
28361
+ var init_release_config = __esm({
28362
+ "src/core/release/release-config.ts"() {
28363
+ "use strict";
28364
+ init_paths();
28365
+ DEFAULTS2 = {
28366
+ versioningScheme: "calver",
28367
+ tagPrefix: "v",
28368
+ changelogFormat: "keepachangelog",
28369
+ changelogFile: "CHANGELOG.md",
28370
+ artifactType: "generic-tarball"
28371
+ };
28372
+ }
28373
+ });
28374
+
28375
+ // src/core/release/release-manifest.ts
28376
+ import { existsSync as existsSync49, renameSync as renameSync7 } from "node:fs";
28377
+ import { readFile as readFile10 } from "node:fs/promises";
28378
+ import { execFileSync as execFileSync7 } from "node:child_process";
28379
+ import { join as join47 } from "node:path";
27986
28380
  import { eq as eq14, desc as desc4 } from "drizzle-orm";
27987
28381
  function isValidVersion(version) {
27988
28382
  return /^v?\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/.test(version);
@@ -28074,25 +28468,67 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28074
28468
  const chores = [];
28075
28469
  const docs = [];
28076
28470
  const tests = [];
28077
- const other = [];
28471
+ const changes = [];
28472
+ function stripConventionalPrefix(title) {
28473
+ return title.replace(/^(feat|fix|docs?|test|chore|refactor|style|ci|build|perf)(\([^)]+\))?:\s*/i, "");
28474
+ }
28475
+ function capitalize(s) {
28476
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
28477
+ }
28478
+ function buildEntry(task) {
28479
+ const cleanTitle = capitalize(stripConventionalPrefix(task.title));
28480
+ const desc6 = task.description?.trim();
28481
+ const shouldIncludeDesc = (() => {
28482
+ if (!desc6 || desc6.length === 0) return false;
28483
+ const titleNorm = cleanTitle.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
28484
+ const descNorm = desc6.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
28485
+ if (titleNorm === descNorm) return false;
28486
+ if (descNorm.startsWith(titleNorm) && descNorm.length < titleNorm.length * 1.3) return false;
28487
+ return desc6.length >= 20;
28488
+ })();
28489
+ if (shouldIncludeDesc) {
28490
+ const descDisplay = desc6.length > 150 ? desc6.slice(0, 147) + "..." : desc6;
28491
+ return `- **${cleanTitle}**: ${descDisplay} (${task.id})`;
28492
+ }
28493
+ return `- ${cleanTitle} (${task.id})`;
28494
+ }
28495
+ function categorizeTask(task) {
28496
+ if (task.type === "epic") return "changes";
28497
+ const labels = task.labels ?? [];
28498
+ const titleLower = stripConventionalPrefix(task.title).toLowerCase();
28499
+ const rawTitleLower = task.title.toLowerCase();
28500
+ if (/^feat(\([^)]+\))?:/.test(task.title.toLowerCase())) return "features";
28501
+ if (/^fix(\([^)]+\))?:/.test(task.title.toLowerCase())) return "fixes";
28502
+ if (/^docs?(\([^)]+\))?:/.test(task.title.toLowerCase())) return "docs";
28503
+ if (/^test(\([^)]+\))?:/.test(task.title.toLowerCase())) return "tests";
28504
+ if (/^(chore|refactor|style|ci|build|perf)(\([^)]+\))?:/.test(task.title.toLowerCase())) return "chores";
28505
+ if (labels.some((l) => ["feat", "feature", "enhancement", "add"].includes(l.toLowerCase()))) return "features";
28506
+ if (labels.some((l) => ["fix", "bug", "bugfix", "regression"].includes(l.toLowerCase()))) return "fixes";
28507
+ if (labels.some((l) => ["docs", "documentation"].includes(l.toLowerCase()))) return "docs";
28508
+ if (labels.some((l) => ["test", "testing"].includes(l.toLowerCase()))) return "tests";
28509
+ if (labels.some((l) => ["chore", "refactor", "cleanup", "maintenance"].includes(l.toLowerCase()))) return "chores";
28510
+ if (titleLower.startsWith("add ") || titleLower.includes("implement") || titleLower.startsWith("create ") || titleLower.startsWith("introduce ")) return "features";
28511
+ if (titleLower.includes("bug") || titleLower.startsWith("fix") || titleLower.includes("regression") || titleLower.includes("broken")) return "fixes";
28512
+ if (titleLower.startsWith("doc") || titleLower.includes("documentation") || titleLower.includes("readme") || titleLower.includes("changelog")) return "docs";
28513
+ if (titleLower.startsWith("test") || titleLower.includes("test") && titleLower.includes("add")) return "tests";
28514
+ if (titleLower.startsWith("chore") || titleLower.includes("refactor") || titleLower.includes("cleanup") || titleLower.includes("migrate") || titleLower.includes("upgrade") || titleLower.includes("remove ") || titleLower.startsWith("audit")) return "chores";
28515
+ if (rawTitleLower.startsWith("feat")) return "features";
28516
+ return "changes";
28517
+ }
28078
28518
  for (const taskId of releaseTasks) {
28079
28519
  const task = taskMap.get(taskId);
28080
28520
  if (!task) continue;
28081
- const titleLower = task.title.toLowerCase();
28082
- const entry = `- ${task.title} (${task.id})`;
28083
- if (titleLower.startsWith("feat") || titleLower.includes("add ") || titleLower.includes("implement")) {
28084
- features.push(entry);
28085
- } else if (titleLower.startsWith("fix") || titleLower.includes("bug")) {
28086
- fixes.push(entry);
28087
- } else if (titleLower.startsWith("doc") || titleLower.includes("documentation")) {
28088
- docs.push(entry);
28089
- } else if (titleLower.startsWith("test") || titleLower.includes("test")) {
28090
- tests.push(entry);
28091
- } else if (titleLower.startsWith("chore") || titleLower.includes("refactor")) {
28092
- chores.push(entry);
28093
- } else {
28094
- other.push(entry);
28095
- }
28521
+ if (task.type === "epic") continue;
28522
+ if (task.labels?.some((l) => l.toLowerCase() === "epic")) continue;
28523
+ if (/^epic:/i.test(task.title.trim())) continue;
28524
+ const category = categorizeTask(task);
28525
+ const entry = buildEntry(task);
28526
+ if (category === "features") features.push(entry);
28527
+ else if (category === "fixes") fixes.push(entry);
28528
+ else if (category === "docs") docs.push(entry);
28529
+ else if (category === "tests") tests.push(entry);
28530
+ else if (category === "chores") chores.push(entry);
28531
+ else changes.push(entry);
28096
28532
  }
28097
28533
  const sections = [];
28098
28534
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -28127,14 +28563,14 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28127
28563
  sections.push(...chores);
28128
28564
  sections.push("");
28129
28565
  }
28130
- if (other.length > 0) {
28131
- sections.push("### Other");
28132
- sections.push(...other);
28566
+ if (changes.length > 0) {
28567
+ sections.push("### Changes");
28568
+ sections.push(...changes);
28133
28569
  sections.push("");
28134
28570
  }
28135
28571
  const changelog = sections.join("\n");
28136
28572
  await db.update(releaseManifests).set({ changelog }).where(eq14(releaseManifests.version, normalizedVersion)).run();
28137
- const changelogPath = join46(cwd ?? process.cwd(), "CHANGELOG.md");
28573
+ const changelogPath = join47(cwd ?? process.cwd(), "CHANGELOG.md");
28138
28574
  let existingChangelogContent = "";
28139
28575
  try {
28140
28576
  existingChangelogContent = await readFile10(changelogPath, "utf8");
@@ -28153,7 +28589,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28153
28589
  docs: docs.length,
28154
28590
  tests: tests.length,
28155
28591
  chores: chores.length,
28156
- other: other.length
28592
+ changes: changes.length
28157
28593
  }
28158
28594
  };
28159
28595
  }
@@ -28254,19 +28690,19 @@ async function runReleaseGates(version, loadTasksFn, cwd) {
28254
28690
  message: incompleteTasks.length === 0 ? "All tasks completed" : `${incompleteTasks.length} tasks not completed: ${incompleteTasks.join(", ")}`
28255
28691
  });
28256
28692
  const projectRoot = cwd ?? getProjectRoot();
28257
- const distPath = join46(projectRoot, "dist", "cli", "index.js");
28258
- const isNodeProject = existsSync48(join46(projectRoot, "package.json"));
28693
+ const distPath = join47(projectRoot, "dist", "cli", "index.js");
28694
+ const isNodeProject = existsSync49(join47(projectRoot, "package.json"));
28259
28695
  if (isNodeProject) {
28260
28696
  gates.push({
28261
28697
  name: "build_artifact",
28262
- status: existsSync48(distPath) ? "passed" : "failed",
28263
- message: existsSync48(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
28698
+ status: existsSync49(distPath) ? "passed" : "failed",
28699
+ message: existsSync49(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
28264
28700
  });
28265
28701
  }
28266
28702
  let workingTreeClean = true;
28267
28703
  let dirtyFiles = [];
28268
28704
  try {
28269
- const porcelain = execFileSync6("git", ["status", "--porcelain"], {
28705
+ const porcelain = execFileSync7("git", ["status", "--porcelain"], {
28270
28706
  cwd: projectRoot,
28271
28707
  encoding: "utf-8",
28272
28708
  stdio: "pipe"
@@ -28283,27 +28719,60 @@ async function runReleaseGates(version, loadTasksFn, cwd) {
28283
28719
  const isPreRelease = normalizedVersion.includes("-");
28284
28720
  let currentBranch = "";
28285
28721
  try {
28286
- currentBranch = execFileSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28722
+ currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28287
28723
  cwd: projectRoot,
28288
28724
  encoding: "utf-8",
28289
28725
  stdio: "pipe"
28290
28726
  }).trim();
28291
28727
  } catch {
28292
28728
  }
28293
- const expectedBranch = isPreRelease ? "develop" : "main";
28294
- const branchOk = !currentBranch || currentBranch === expectedBranch || currentBranch === "HEAD";
28729
+ const releaseConfig = loadReleaseConfig(cwd);
28730
+ const gitFlowCfg = getGitFlowConfig(releaseConfig);
28731
+ const channelCfg = getChannelConfig(releaseConfig);
28732
+ const expectedBranch = isPreRelease ? gitFlowCfg.branches.develop : gitFlowCfg.branches.main;
28733
+ const isFeatureBranch = currentBranch.startsWith(gitFlowCfg.branches.featurePrefix) || currentBranch.startsWith(gitFlowCfg.branches.hotfixPrefix) || currentBranch.startsWith(gitFlowCfg.branches.releasePrefix);
28734
+ const branchOk = !currentBranch || currentBranch === "HEAD" || currentBranch === expectedBranch || isPreRelease && isFeatureBranch;
28735
+ const detectedChannel = currentBranch ? resolveChannelFromBranch(currentBranch, channelCfg) : isPreRelease ? "beta" : "latest";
28295
28736
  gates.push({
28296
28737
  name: "branch_target",
28297
28738
  status: branchOk ? "passed" : "failed",
28298
- message: branchOk ? `On correct branch: ${currentBranch}` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
28739
+ message: branchOk ? `On correct branch: ${currentBranch} (channel: ${detectedChannel})` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
28740
+ });
28741
+ const pushMode = getPushMode(releaseConfig);
28742
+ let requiresPR = false;
28743
+ if (pushMode === "pr") {
28744
+ requiresPR = true;
28745
+ } else if (pushMode === "auto") {
28746
+ try {
28747
+ const protectionResult = await detectBranchProtection(
28748
+ expectedBranch,
28749
+ "origin",
28750
+ projectRoot
28751
+ );
28752
+ requiresPR = protectionResult.protected;
28753
+ } catch {
28754
+ requiresPR = false;
28755
+ }
28756
+ }
28757
+ gates.push({
28758
+ name: "branch_protection",
28759
+ status: "passed",
28760
+ message: requiresPR ? `Branch '${expectedBranch}' is protected \u2014 release.ship will create a PR` : `Branch '${expectedBranch}' allows direct push`
28299
28761
  });
28300
28762
  const allPassed = gates.every((g) => g.status === "passed");
28763
+ const metadata = {
28764
+ channel: detectedChannel,
28765
+ requiresPR,
28766
+ targetBranch: expectedBranch,
28767
+ currentBranch
28768
+ };
28301
28769
  return {
28302
28770
  version: normalizedVersion,
28303
28771
  allPassed,
28304
28772
  gates,
28305
28773
  passedCount: gates.filter((g) => g.status === "passed").length,
28306
- failedCount: gates.filter((g) => g.status === "failed").length
28774
+ failedCount: gates.filter((g) => g.status === "failed").length,
28775
+ metadata
28307
28776
  };
28308
28777
  }
28309
28778
  async function rollbackRelease(version, reason, cwd) {
@@ -28326,7 +28795,7 @@ async function rollbackRelease(version, reason, cwd) {
28326
28795
  };
28327
28796
  }
28328
28797
  async function readPushPolicy(cwd) {
28329
- const configPath = join46(getCleoDirAbsolute(cwd), "config.json");
28798
+ const configPath = join47(getCleoDirAbsolute(cwd), "config.json");
28330
28799
  const config = await readJson(configPath);
28331
28800
  if (!config) return void 0;
28332
28801
  const release2 = config.release;
@@ -28340,6 +28809,33 @@ async function pushRelease(version, remote, cwd, opts) {
28340
28809
  const normalizedVersion = normalizeVersion(version);
28341
28810
  const projectRoot = getProjectRoot(cwd);
28342
28811
  const pushPolicy = await readPushPolicy(cwd);
28812
+ const configPushMode = getPushMode(loadReleaseConfig(cwd));
28813
+ const effectivePushMode = opts?.mode ?? pushPolicy?.mode ?? configPushMode;
28814
+ if (effectivePushMode === "pr" || effectivePushMode === "auto") {
28815
+ const targetRemoteForCheck = remote ?? pushPolicy?.remote ?? "origin";
28816
+ let branchIsProtected = effectivePushMode === "pr";
28817
+ if (effectivePushMode === "auto") {
28818
+ try {
28819
+ const protection = await detectBranchProtection(
28820
+ pushPolicy?.allowedBranches?.[0] ?? "main",
28821
+ targetRemoteForCheck,
28822
+ projectRoot
28823
+ );
28824
+ branchIsProtected = protection.protected;
28825
+ } catch {
28826
+ branchIsProtected = false;
28827
+ }
28828
+ }
28829
+ if (branchIsProtected) {
28830
+ return {
28831
+ version: normalizedVersion,
28832
+ status: "requires_pr",
28833
+ remote: targetRemoteForCheck,
28834
+ pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
28835
+ requiresPR: true
28836
+ };
28837
+ }
28838
+ }
28343
28839
  if (pushPolicy && pushPolicy.enabled === false && !opts?.explicitPush) {
28344
28840
  throw new Error(
28345
28841
  "Push is disabled by config (release.push.enabled=false). Use --push to override."
@@ -28347,7 +28843,7 @@ async function pushRelease(version, remote, cwd, opts) {
28347
28843
  }
28348
28844
  const targetRemote = remote ?? pushPolicy?.remote ?? "origin";
28349
28845
  if (pushPolicy?.requireCleanTree) {
28350
- const statusOutput = execFileSync6("git", ["status", "--porcelain"], {
28846
+ const statusOutput = execFileSync7("git", ["status", "--porcelain"], {
28351
28847
  cwd: projectRoot,
28352
28848
  timeout: 1e4,
28353
28849
  encoding: "utf-8",
@@ -28360,7 +28856,7 @@ async function pushRelease(version, remote, cwd, opts) {
28360
28856
  }
28361
28857
  }
28362
28858
  if (pushPolicy?.allowedBranches && pushPolicy.allowedBranches.length > 0) {
28363
- const currentBranch = execFileSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28859
+ const currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28364
28860
  cwd: projectRoot,
28365
28861
  timeout: 1e4,
28366
28862
  encoding: "utf-8",
@@ -28372,7 +28868,7 @@ async function pushRelease(version, remote, cwd, opts) {
28372
28868
  );
28373
28869
  }
28374
28870
  }
28375
- execFileSync6("git", ["push", targetRemote, "--follow-tags"], {
28871
+ execFileSync7("git", ["push", targetRemote, "--follow-tags"], {
28376
28872
  cwd: projectRoot,
28377
28873
  timeout: 6e4,
28378
28874
  encoding: "utf-8",
@@ -28403,6 +28899,9 @@ var init_release_manifest = __esm({
28403
28899
  init_paths();
28404
28900
  init_json();
28405
28901
  init_changelog_writer();
28902
+ init_github_pr();
28903
+ init_channel();
28904
+ init_release_config();
28406
28905
  }
28407
28906
  });
28408
28907
 
@@ -28483,7 +28982,7 @@ var init_guards = __esm({
28483
28982
  });
28484
28983
 
28485
28984
  // src/dispatch/engines/release-engine.ts
28486
- import { execFileSync as execFileSync7 } from "node:child_process";
28985
+ import { execFileSync as execFileSync8 } from "node:child_process";
28487
28986
  function isAgentContext() {
28488
28987
  return !!(process.env["CLEO_SESSION_ID"] || process.env["CLAUDE_AGENT_TYPE"]);
28489
28988
  }
@@ -28625,7 +29124,7 @@ async function releasePush(version, remote, projectRoot, opts) {
28625
29124
  const result = await pushRelease(version, remote, projectRoot, opts);
28626
29125
  let commitSha;
28627
29126
  try {
28628
- commitSha = execFileSync7("git", ["rev-parse", "HEAD"], {
29127
+ commitSha = execFileSync8("git", ["rev-parse", "HEAD"], {
28629
29128
  cwd: projectRoot ?? process.cwd(),
28630
29129
  encoding: "utf-8",
28631
29130
  stdio: "pipe"
@@ -28655,7 +29154,17 @@ async function releaseShip(params, projectRoot) {
28655
29154
  return engineError("E_INVALID_INPUT", "epicId is required");
28656
29155
  }
28657
29156
  const cwd = projectRoot ?? resolveProjectRoot();
29157
+ const logStep = (n, total, label, done, error) => {
29158
+ if (done === void 0) {
29159
+ console.log(`[Step ${n}/${total}] ${label}...`);
29160
+ } else if (done) {
29161
+ console.log(` \u2713 ${label}`);
29162
+ } else {
29163
+ console.log(` \u2717 ${label}: ${error ?? "failed"}`);
29164
+ }
29165
+ };
28658
29166
  try {
29167
+ logStep(1, 7, "Validate release gates");
28659
29168
  const gatesResult = await runReleaseGates(
28660
29169
  version,
28661
29170
  () => loadTasks2(projectRoot),
@@ -28663,10 +29172,32 @@ async function releaseShip(params, projectRoot) {
28663
29172
  );
28664
29173
  if (gatesResult && !gatesResult.allPassed) {
28665
29174
  const failedGates = gatesResult.gates.filter((g) => g.status === "failed");
29175
+ logStep(1, 7, "Validate release gates", false, failedGates.map((g) => g.name).join(", "));
28666
29176
  return engineError("E_LIFECYCLE_GATE_FAILED", `Release gates failed for ${version}: ${failedGates.map((g) => g.name).join(", ")}`, {
28667
29177
  details: { gates: gatesResult.gates, failedCount: gatesResult.failedCount }
28668
29178
  });
28669
29179
  }
29180
+ logStep(1, 7, "Validate release gates", true);
29181
+ let resolvedChannel = "latest";
29182
+ let currentBranchForPR = "HEAD";
29183
+ try {
29184
+ const branchName = execFileSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
29185
+ cwd,
29186
+ encoding: "utf-8",
29187
+ stdio: "pipe"
29188
+ }).trim();
29189
+ currentBranchForPR = branchName;
29190
+ const channelEnum = resolveChannelFromBranch(branchName);
29191
+ resolvedChannel = channelToDistTag(channelEnum);
29192
+ } catch {
29193
+ }
29194
+ const gateMetadata = gatesResult.metadata;
29195
+ const requiresPRFromGates = gateMetadata?.requiresPR ?? false;
29196
+ const targetBranchFromGates = gateMetadata?.targetBranch;
29197
+ if (gateMetadata?.currentBranch) {
29198
+ currentBranchForPR = gateMetadata.currentBranch;
29199
+ }
29200
+ logStep(2, 7, "Check epic completeness");
28670
29201
  let releaseTaskIds = [];
28671
29202
  try {
28672
29203
  const manifest = await showManifestRelease(version, projectRoot);
@@ -28677,10 +29208,13 @@ async function releaseShip(params, projectRoot) {
28677
29208
  const epicCheck = await checkEpicCompleteness(releaseTaskIds, projectRoot, epicAccessor);
28678
29209
  if (epicCheck.hasIncomplete) {
28679
29210
  const incomplete = epicCheck.epics.filter((e) => e.missingChildren.length > 0).map((e) => `${e.epicId}: missing ${e.missingChildren.map((c) => c.id).join(", ")}`).join("; ");
29211
+ logStep(2, 7, "Check epic completeness", false, incomplete);
28680
29212
  return engineError("E_LIFECYCLE_GATE_FAILED", `Epic completeness check failed: ${incomplete}`, {
28681
29213
  details: { epics: epicCheck.epics }
28682
29214
  });
28683
29215
  }
29216
+ logStep(2, 7, "Check epic completeness", true);
29217
+ logStep(3, 7, "Check task double-listing");
28684
29218
  const allReleases = await listManifestReleases(projectRoot);
28685
29219
  const existingReleases = (allReleases.releases ?? []).filter((r) => r.version !== version);
28686
29220
  const doubleCheck = checkDoubleListing(
@@ -28689,10 +29223,13 @@ async function releaseShip(params, projectRoot) {
28689
29223
  );
28690
29224
  if (doubleCheck.hasDoubleListing) {
28691
29225
  const dupes = doubleCheck.duplicates.map((d) => `${d.taskId} (in ${d.releases.join(", ")})`).join("; ");
29226
+ logStep(3, 7, "Check task double-listing", false, dupes);
28692
29227
  return engineError("E_VALIDATION", `Double-listing detected: ${dupes}`, {
28693
29228
  details: { duplicates: doubleCheck.duplicates }
28694
29229
  });
28695
29230
  }
29231
+ logStep(3, 7, "Check task double-listing", true);
29232
+ logStep(4, 7, "Generate CHANGELOG");
28696
29233
  const changelogResult = await generateReleaseChangelog(
28697
29234
  version,
28698
29235
  () => loadTasks2(projectRoot),
@@ -28700,62 +29237,128 @@ async function releaseShip(params, projectRoot) {
28700
29237
  );
28701
29238
  const changelogPath = `${cwd}/CHANGELOG.md`;
28702
29239
  const generatedContent = changelogResult.changelog ?? "";
29240
+ logStep(4, 7, "Generate CHANGELOG", true);
29241
+ const loadedConfig = loadReleaseConfig(cwd);
29242
+ const pushMode = getPushMode(loadedConfig);
29243
+ const gitflowCfg = getGitFlowConfig(loadedConfig);
29244
+ const targetBranch = targetBranchFromGates ?? gitflowCfg.branches.main;
28703
29245
  if (dryRun) {
28704
- return {
28705
- success: true,
28706
- data: {
28707
- version,
28708
- epicId,
28709
- dryRun: true,
28710
- wouldDo: [
28711
- `write CHANGELOG section for ${version} (${generatedContent.length} chars)`,
28712
- "git add CHANGELOG.md",
28713
- `git commit -m "release: ship v${version} (${epicId})"`,
28714
- `git tag -a v${version} -m "Release v${version}"`,
28715
- `git push ${remote ?? "origin"} --follow-tags`,
28716
- "markReleasePushed(...)"
28717
- ]
28718
- }
29246
+ const wouldCreatePR = requiresPRFromGates || pushMode === "pr";
29247
+ const dryRunOutput = {
29248
+ version,
29249
+ epicId,
29250
+ dryRun: true,
29251
+ channel: resolvedChannel,
29252
+ pushMode,
29253
+ wouldDo: [
29254
+ `write CHANGELOG section for ${version} (${generatedContent.length} chars)`,
29255
+ "git add CHANGELOG.md",
29256
+ `git commit -m "release: ship v${version} (${epicId})"`,
29257
+ `git tag -a v${version} -m "Release v${version}"`
29258
+ ]
28719
29259
  };
29260
+ if (wouldCreatePR) {
29261
+ const ghAvailable = isGhCliAvailable();
29262
+ dryRunOutput["wouldDo"].push(
29263
+ ghAvailable ? `gh pr create --base ${targetBranch} --head ${currentBranchForPR} --title "release: ship v${version}"` : `manual PR: ${currentBranchForPR} \u2192 ${targetBranch} (gh CLI not available)`
29264
+ );
29265
+ dryRunOutput["wouldCreatePR"] = true;
29266
+ dryRunOutput["prTitle"] = `release: ship v${version}`;
29267
+ dryRunOutput["prTargetBranch"] = targetBranch;
29268
+ } else {
29269
+ dryRunOutput["wouldDo"].push(
29270
+ `git push ${remote ?? "origin"} --follow-tags`
29271
+ );
29272
+ dryRunOutput["wouldCreatePR"] = false;
29273
+ }
29274
+ dryRunOutput["wouldDo"].push("markReleasePushed(...)");
29275
+ return { success: true, data: dryRunOutput };
28720
29276
  }
28721
- await writeChangelogSection(version, generatedContent, [], changelogPath);
29277
+ logStep(5, 7, "Commit release");
28722
29278
  const gitCwd = { cwd, encoding: "utf-8", stdio: "pipe" };
28723
29279
  try {
28724
- execFileSync7("git", ["add", "CHANGELOG.md"], gitCwd);
29280
+ execFileSync8("git", ["add", "CHANGELOG.md"], gitCwd);
28725
29281
  } catch (err) {
28726
29282
  const msg = err.message ?? String(err);
29283
+ logStep(5, 7, "Commit release", false, `git add failed: ${msg}`);
28727
29284
  return engineError("E_GENERAL", `git add failed: ${msg}`);
28728
29285
  }
28729
29286
  try {
28730
- execFileSync7(
29287
+ execFileSync8(
28731
29288
  "git",
28732
29289
  ["commit", "-m", `release: ship v${version} (${epicId})`],
28733
29290
  gitCwd
28734
29291
  );
28735
29292
  } catch (err) {
28736
29293
  const msg = err.stderr ?? err.message ?? String(err);
29294
+ logStep(5, 7, "Commit release", false, `git commit failed: ${msg}`);
28737
29295
  return engineError("E_GENERAL", `git commit failed: ${msg}`);
28738
29296
  }
29297
+ logStep(5, 7, "Commit release", true);
28739
29298
  let commitSha;
28740
29299
  try {
28741
- commitSha = execFileSync7("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
29300
+ commitSha = execFileSync8("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
28742
29301
  } catch {
28743
29302
  }
29303
+ logStep(6, 7, "Tag release");
28744
29304
  const gitTag = `v${version.replace(/^v/, "")}`;
28745
29305
  try {
28746
- execFileSync7("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
29306
+ execFileSync8("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
28747
29307
  } catch (err) {
28748
29308
  const msg = err.stderr ?? err.message ?? String(err);
29309
+ logStep(6, 7, "Tag release", false, `git tag failed: ${msg}`);
28749
29310
  return engineError("E_GENERAL", `git tag failed: ${msg}`);
28750
29311
  }
28751
- try {
28752
- execFileSync7("git", ["push", remote ?? "origin", "--follow-tags"], gitCwd);
28753
- } catch (err) {
28754
- const execError = err;
28755
- const msg = (execError.stderr ?? execError.message ?? "").slice(0, 500);
28756
- return engineError("E_GENERAL", `git push failed: ${msg}`, {
28757
- details: { exitCode: execError.status }
29312
+ logStep(6, 7, "Tag release", true);
29313
+ logStep(7, 7, "Push / create PR");
29314
+ let prResult = null;
29315
+ const pushResult = await pushRelease(version, remote, projectRoot, {
29316
+ explicitPush: true,
29317
+ mode: pushMode
29318
+ });
29319
+ if (pushResult.requiresPR || requiresPRFromGates) {
29320
+ const prBody = buildPRBody({
29321
+ base: targetBranch,
29322
+ head: currentBranchForPR,
29323
+ title: `release: ship v${version}`,
29324
+ body: "",
29325
+ version,
29326
+ epicId,
29327
+ projectRoot: cwd
29328
+ });
29329
+ prResult = await createPullRequest({
29330
+ base: targetBranch,
29331
+ head: currentBranchForPR,
29332
+ title: `release: ship v${version}`,
29333
+ body: prBody,
29334
+ labels: ["release", resolvedChannel],
29335
+ version,
29336
+ epicId,
29337
+ projectRoot: cwd
28758
29338
  });
29339
+ if (prResult.mode === "created") {
29340
+ console.log(` \u2713 Push / create PR`);
29341
+ console.log(` PR created: ${prResult.prUrl}`);
29342
+ console.log(` \u2192 Next: merge the PR, then CI will publish to npm @${resolvedChannel}`);
29343
+ } else if (prResult.mode === "skipped") {
29344
+ console.log(` \u2713 Push / create PR`);
29345
+ console.log(` PR already exists: ${prResult.prUrl}`);
29346
+ } else {
29347
+ console.log(` ! Push / create PR \u2014 manual PR required:`);
29348
+ console.log(prResult.instructions);
29349
+ }
29350
+ } else {
29351
+ try {
29352
+ execFileSync8("git", ["push", remote ?? "origin", "--follow-tags"], gitCwd);
29353
+ logStep(7, 7, "Push / create PR", true);
29354
+ } catch (err) {
29355
+ const execError = err;
29356
+ const msg = (execError.stderr ?? execError.message ?? "").slice(0, 500);
29357
+ logStep(7, 7, "Push / create PR", false, `git push failed: ${msg}`);
29358
+ return engineError("E_GENERAL", `git push failed: ${msg}`, {
29359
+ details: { exitCode: execError.status }
29360
+ });
29361
+ }
28759
29362
  }
28760
29363
  const pushedAt = (/* @__PURE__ */ new Date()).toISOString();
28761
29364
  await markReleasePushed(version, pushedAt, projectRoot, { commitSha, gitTag });
@@ -28767,7 +29370,16 @@ async function releaseShip(params, projectRoot) {
28767
29370
  commitSha,
28768
29371
  gitTag,
28769
29372
  pushedAt,
28770
- changelog: changelogPath
29373
+ changelog: changelogPath,
29374
+ channel: resolvedChannel,
29375
+ ...prResult ? {
29376
+ pr: {
29377
+ mode: prResult.mode,
29378
+ prUrl: prResult.prUrl,
29379
+ prNumber: prResult.prNumber,
29380
+ instructions: prResult.instructions
29381
+ }
29382
+ } : {}
28771
29383
  }
28772
29384
  };
28773
29385
  } catch (err) {
@@ -28780,15 +29392,17 @@ var init_release_engine = __esm({
28780
29392
  init_platform();
28781
29393
  init_data_accessor();
28782
29394
  init_release_manifest();
28783
- init_changelog_writer();
28784
29395
  init_guards();
29396
+ init_github_pr();
29397
+ init_channel();
29398
+ init_release_config();
28785
29399
  init_error();
28786
29400
  }
28787
29401
  });
28788
29402
 
28789
29403
  // src/dispatch/engines/template-parser.ts
28790
- import { readFileSync as readFileSync38, readdirSync as readdirSync12, existsSync as existsSync49 } from "fs";
28791
- import { join as join47 } from "path";
29404
+ import { readFileSync as readFileSync39, readdirSync as readdirSync12, existsSync as existsSync50 } from "fs";
29405
+ import { join as join48 } from "path";
28792
29406
  import { parse as parseYaml } from "yaml";
28793
29407
  function deriveSubcommand(filename) {
28794
29408
  let stem = filename.replace(/\.ya?ml$/i, "");
@@ -28802,8 +29416,8 @@ function deriveSubcommand(filename) {
28802
29416
  return firstWord.toLowerCase();
28803
29417
  }
28804
29418
  function parseTemplateFile(templateDir, filename) {
28805
- const filePath = join47(templateDir, filename);
28806
- const raw = readFileSync38(filePath, "utf-8");
29419
+ const filePath = join48(templateDir, filename);
29420
+ const raw = readFileSync39(filePath, "utf-8");
28807
29421
  const parsed = parseYaml(raw);
28808
29422
  const name = typeof parsed.name === "string" ? parsed.name : filename;
28809
29423
  const titlePrefix = typeof parsed.title === "string" ? parsed.title : "";
@@ -28852,8 +29466,8 @@ function parseTemplateFile(templateDir, filename) {
28852
29466
  };
28853
29467
  }
28854
29468
  function parseIssueTemplates(projectRoot) {
28855
- const templateDir = join47(projectRoot, ".github", "ISSUE_TEMPLATE");
28856
- if (!existsSync49(templateDir)) {
29469
+ const templateDir = join48(projectRoot, ".github", "ISSUE_TEMPLATE");
29470
+ if (!existsSync50(templateDir)) {
28857
29471
  return engineError("E_NOT_FOUND", `Issue template directory not found: ${templateDir}`);
28858
29472
  }
28859
29473
  let files;
@@ -30532,26 +31146,26 @@ var init_check = __esm({
30532
31146
  });
30533
31147
 
30534
31148
  // src/core/adrs/validate.ts
30535
- import { readFileSync as readFileSync39, readdirSync as readdirSync13, existsSync as existsSync50 } from "node:fs";
30536
- import { join as join48 } from "node:path";
31149
+ import { readFileSync as readFileSync40, readdirSync as readdirSync13, existsSync as existsSync51 } from "node:fs";
31150
+ import { join as join49 } from "node:path";
30537
31151
  import AjvModule3 from "ajv";
30538
31152
  async function validateAllAdrs(projectRoot) {
30539
- const adrsDir = join48(projectRoot, ".cleo", "adrs");
30540
- const schemaPath = join48(projectRoot, "schemas", "adr-frontmatter.schema.json");
30541
- if (!existsSync50(schemaPath)) {
31153
+ const adrsDir = join49(projectRoot, ".cleo", "adrs");
31154
+ const schemaPath = join49(projectRoot, "schemas", "adr-frontmatter.schema.json");
31155
+ if (!existsSync51(schemaPath)) {
30542
31156
  return {
30543
31157
  valid: false,
30544
31158
  errors: [{ file: "schemas/adr-frontmatter.schema.json", field: "schema", message: "Schema file not found" }],
30545
31159
  checked: 0
30546
31160
  };
30547
31161
  }
30548
- if (!existsSync50(adrsDir)) {
31162
+ if (!existsSync51(adrsDir)) {
30549
31163
  return { valid: true, errors: [], checked: 0 };
30550
31164
  }
30551
- const schema = JSON.parse(readFileSync39(schemaPath, "utf-8"));
31165
+ const schema = JSON.parse(readFileSync40(schemaPath, "utf-8"));
30552
31166
  const ajv = new Ajv3({ allErrors: true });
30553
31167
  const validate = ajv.compile(schema);
30554
- const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join48(adrsDir, f));
31168
+ const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join49(adrsDir, f));
30555
31169
  const errors = [];
30556
31170
  for (const filePath of files) {
30557
31171
  const record = parseAdrFile(filePath, projectRoot);
@@ -30578,15 +31192,15 @@ var init_validate = __esm({
30578
31192
  });
30579
31193
 
30580
31194
  // src/core/adrs/list.ts
30581
- import { readdirSync as readdirSync14, existsSync as existsSync51 } from "node:fs";
30582
- import { join as join49 } from "node:path";
31195
+ import { readdirSync as readdirSync14, existsSync as existsSync52 } from "node:fs";
31196
+ import { join as join50 } from "node:path";
30583
31197
  async function listAdrs(projectRoot, opts) {
30584
- const adrsDir = join49(projectRoot, ".cleo", "adrs");
30585
- if (!existsSync51(adrsDir)) {
31198
+ const adrsDir = join50(projectRoot, ".cleo", "adrs");
31199
+ if (!existsSync52(adrsDir)) {
30586
31200
  return { adrs: [], total: 0 };
30587
31201
  }
30588
31202
  const files = readdirSync14(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
30589
- const records = files.map((f) => parseAdrFile(join49(adrsDir, f), projectRoot));
31203
+ const records = files.map((f) => parseAdrFile(join50(adrsDir, f), projectRoot));
30590
31204
  const filtered = records.filter((r) => {
30591
31205
  if (opts?.status && r.frontmatter.Status !== opts.status) return false;
30592
31206
  if (opts?.since && r.frontmatter.Date < opts.since) return false;
@@ -30611,14 +31225,14 @@ var init_list2 = __esm({
30611
31225
  });
30612
31226
 
30613
31227
  // src/core/adrs/show.ts
30614
- import { existsSync as existsSync52, readdirSync as readdirSync15 } from "node:fs";
30615
- import { join as join50 } from "node:path";
31228
+ import { existsSync as existsSync53, readdirSync as readdirSync15 } from "node:fs";
31229
+ import { join as join51 } from "node:path";
30616
31230
  async function showAdr(projectRoot, adrId) {
30617
- const adrsDir = join50(projectRoot, ".cleo", "adrs");
30618
- if (!existsSync52(adrsDir)) return null;
31231
+ const adrsDir = join51(projectRoot, ".cleo", "adrs");
31232
+ if (!existsSync53(adrsDir)) return null;
30619
31233
  const files = readdirSync15(adrsDir).filter((f) => f.startsWith(adrId) && f.endsWith(".md"));
30620
31234
  if (files.length === 0) return null;
30621
- const filePath = join50(adrsDir, files[0]);
31235
+ const filePath = join51(adrsDir, files[0]);
30622
31236
  return parseAdrFile(filePath, projectRoot);
30623
31237
  }
30624
31238
  var init_show2 = __esm({
@@ -30629,8 +31243,8 @@ var init_show2 = __esm({
30629
31243
  });
30630
31244
 
30631
31245
  // src/core/adrs/find.ts
30632
- import { readdirSync as readdirSync16, existsSync as existsSync53 } from "node:fs";
30633
- import { join as join51 } from "node:path";
31246
+ import { readdirSync as readdirSync16, existsSync as existsSync54 } from "node:fs";
31247
+ import { join as join52 } from "node:path";
30634
31248
  function normalise(s) {
30635
31249
  return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
30636
31250
  }
@@ -30647,8 +31261,8 @@ function matchedTerms(target, terms) {
30647
31261
  return terms.filter((term) => t.includes(term));
30648
31262
  }
30649
31263
  async function findAdrs(projectRoot, query, opts) {
30650
- const adrsDir = join51(projectRoot, ".cleo", "adrs");
30651
- if (!existsSync53(adrsDir)) {
31264
+ const adrsDir = join52(projectRoot, ".cleo", "adrs");
31265
+ if (!existsSync54(adrsDir)) {
30652
31266
  return { adrs: [], query, total: 0 };
30653
31267
  }
30654
31268
  const files = readdirSync16(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
@@ -30657,7 +31271,7 @@ async function findAdrs(projectRoot, query, opts) {
30657
31271
  const filterKeywords = opts?.keywords ? parseTags(opts.keywords) : null;
30658
31272
  const results = [];
30659
31273
  for (const file of files) {
30660
- const record = parseAdrFile(join51(adrsDir, file), projectRoot);
31274
+ const record = parseAdrFile(join52(adrsDir, file), projectRoot);
30661
31275
  const fm = record.frontmatter;
30662
31276
  if (opts?.status && fm.Status !== opts.status) continue;
30663
31277
  if (filterTopics && filterTopics.length > 0) {
@@ -30735,12 +31349,12 @@ var init_adrs = __esm({
30735
31349
  });
30736
31350
 
30737
31351
  // src/core/admin/sync.ts
30738
- import { join as join52 } from "node:path";
31352
+ import { join as join53 } from "node:path";
30739
31353
  import { rm as rm2, rmdir, stat as stat2 } from "node:fs/promises";
30740
31354
  async function getSyncStatus(projectRoot) {
30741
31355
  try {
30742
31356
  const cleoDir = getCleoDir(projectRoot);
30743
- const stateFile = join52(cleoDir, "sync", "todowrite-session.json");
31357
+ const stateFile = join53(cleoDir, "sync", "todowrite-session.json");
30744
31358
  const sessionState = await readJson(stateFile);
30745
31359
  if (!sessionState) {
30746
31360
  return {
@@ -30784,8 +31398,8 @@ async function getSyncStatus(projectRoot) {
30784
31398
  async function clearSyncState(projectRoot, dryRun) {
30785
31399
  try {
30786
31400
  const cleoDir = getCleoDir(projectRoot);
30787
- const syncDir = join52(cleoDir, "sync");
30788
- const stateFile = join52(syncDir, "todowrite-session.json");
31401
+ const syncDir = join53(cleoDir, "sync");
31402
+ const stateFile = join53(syncDir, "todowrite-session.json");
30789
31403
  let exists = false;
30790
31404
  try {
30791
31405
  await stat2(stateFile);
@@ -31552,8 +32166,8 @@ var init_import_tasks = __esm({
31552
32166
  // src/core/snapshot/index.ts
31553
32167
  import { createHash as createHash6 } from "node:crypto";
31554
32168
  import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir10 } from "node:fs/promises";
31555
- import { existsSync as existsSync54 } from "node:fs";
31556
- import { join as join53, dirname as dirname14 } from "node:path";
32169
+ import { existsSync as existsSync55 } from "node:fs";
32170
+ import { join as join54, dirname as dirname14 } from "node:path";
31557
32171
  function toSnapshotTask(task) {
31558
32172
  return {
31559
32173
  id: task.id,
@@ -31606,7 +32220,7 @@ async function exportSnapshot(cwd) {
31606
32220
  }
31607
32221
  async function writeSnapshot(snapshot, outputPath) {
31608
32222
  const dir = dirname14(outputPath);
31609
- if (!existsSync54(dir)) {
32223
+ if (!existsSync55(dir)) {
31610
32224
  await mkdir10(dir, { recursive: true });
31611
32225
  }
31612
32226
  await writeFile10(outputPath, JSON.stringify(snapshot, null, 2) + "\n");
@@ -31622,7 +32236,7 @@ async function readSnapshot(inputPath) {
31622
32236
  function getDefaultSnapshotPath(cwd) {
31623
32237
  const cleoDir = getCleoDirAbsolute(cwd);
31624
32238
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
31625
- return join53(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
32239
+ return join54(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
31626
32240
  }
31627
32241
  async function importSnapshot(snapshot, cwd) {
31628
32242
  const accessor = await getAccessor(cwd);
@@ -31920,15 +32534,15 @@ var init_archive_stats2 = __esm({
31920
32534
  });
31921
32535
 
31922
32536
  // src/core/project-info.ts
31923
- import { readFileSync as readFileSync40, existsSync as existsSync55 } from "node:fs";
31924
- import { join as join54 } from "node:path";
32537
+ import { readFileSync as readFileSync41, existsSync as existsSync56 } from "node:fs";
32538
+ import { join as join55 } from "node:path";
31925
32539
  function getProjectInfoSync(cwd) {
31926
32540
  const projectRoot = cwd ?? process.cwd();
31927
32541
  const cleoDir = getCleoDirAbsolute(projectRoot);
31928
- const infoPath = join54(cleoDir, "project-info.json");
31929
- if (!existsSync55(infoPath)) return null;
32542
+ const infoPath = join55(cleoDir, "project-info.json");
32543
+ if (!existsSync56(infoPath)) return null;
31930
32544
  try {
31931
- const raw = readFileSync40(infoPath, "utf-8");
32545
+ const raw = readFileSync41(infoPath, "utf-8");
31932
32546
  const data = JSON.parse(raw);
31933
32547
  if (typeof data.projectHash !== "string" || data.projectHash.length === 0) {
31934
32548
  return null;
@@ -32015,8 +32629,8 @@ var init_defaults = __esm({
32015
32629
  });
32016
32630
 
32017
32631
  // src/mcp/lib/config.ts
32018
- import { readFileSync as readFileSync41, existsSync as existsSync56 } from "fs";
32019
- import { join as join55 } from "path";
32632
+ import { readFileSync as readFileSync42, existsSync as existsSync57 } from "fs";
32633
+ import { join as join56 } from "path";
32020
32634
  function loadFromEnv(key) {
32021
32635
  const envKey = `${ENV_PREFIX}${key.toUpperCase()}`;
32022
32636
  return process.env[envKey];
@@ -32037,12 +32651,12 @@ function parseEnvValue2(key, value) {
32037
32651
  }
32038
32652
  function loadFromFile(projectRoot) {
32039
32653
  const root = projectRoot || process.cwd();
32040
- const configPath = join55(root, ".cleo", "config.json");
32041
- if (!existsSync56(configPath)) {
32654
+ const configPath = join56(root, ".cleo", "config.json");
32655
+ if (!existsSync57(configPath)) {
32042
32656
  return {};
32043
32657
  }
32044
32658
  try {
32045
- const content = readFileSync41(configPath, "utf-8");
32659
+ const content = readFileSync42(configPath, "utf-8");
32046
32660
  const config = JSON.parse(content);
32047
32661
  const result = {};
32048
32662
  if (config.mcp) {
@@ -32382,8 +32996,8 @@ __export(session_grade_exports, {
32382
32996
  gradeSession: () => gradeSession,
32383
32997
  readGrades: () => readGrades
32384
32998
  });
32385
- import { join as join56 } from "node:path";
32386
- import { existsSync as existsSync57 } from "node:fs";
32999
+ import { join as join57 } from "node:path";
33000
+ import { existsSync as existsSync58 } from "node:fs";
32387
33001
  import { readFile as readFile14, appendFile, mkdir as mkdir11 } from "node:fs/promises";
32388
33002
  async function gradeSession(sessionId, cwd) {
32389
33003
  const sessionEntries = await queryAudit({ sessionId });
@@ -32563,9 +33177,9 @@ function detectDuplicateCreates(entries) {
32563
33177
  async function appendGradeResult(result, cwd) {
32564
33178
  try {
32565
33179
  const cleoDir = getCleoDirAbsolute(cwd);
32566
- const metricsDir = join56(cleoDir, "metrics");
33180
+ const metricsDir = join57(cleoDir, "metrics");
32567
33181
  await mkdir11(metricsDir, { recursive: true });
32568
- const gradesPath = join56(metricsDir, "GRADES.jsonl");
33182
+ const gradesPath = join57(metricsDir, "GRADES.jsonl");
32569
33183
  const line = JSON.stringify({ ...result, evaluator: "auto" }) + "\n";
32570
33184
  await appendFile(gradesPath, line, "utf8");
32571
33185
  } catch {
@@ -32574,8 +33188,8 @@ async function appendGradeResult(result, cwd) {
32574
33188
  async function readGrades(sessionId, cwd) {
32575
33189
  try {
32576
33190
  const cleoDir = getCleoDirAbsolute(cwd);
32577
- const gradesPath = join56(cleoDir, "metrics", "GRADES.jsonl");
32578
- if (!existsSync57(gradesPath)) return [];
33191
+ const gradesPath = join57(cleoDir, "metrics", "GRADES.jsonl");
33192
+ if (!existsSync58(gradesPath)) return [];
32579
33193
  const content = await readFile14(gradesPath, "utf8");
32580
33194
  const results = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
32581
33195
  return sessionId ? results.filter((r) => r.sessionId === sessionId) : results;
@@ -34812,6 +35426,7 @@ var init_phase = __esm({
34812
35426
  });
34813
35427
 
34814
35428
  // src/dispatch/domains/pipeline.ts
35429
+ import { execFileSync as execFileSync9 } from "node:child_process";
34815
35430
  var PipelineHandler;
34816
35431
  var init_pipeline2 = __esm({
34817
35432
  "src/dispatch/domains/pipeline.ts"() {
@@ -34822,6 +35437,7 @@ var init_pipeline2 = __esm({
34822
35437
  init_data_accessor();
34823
35438
  init_engine();
34824
35439
  init_release_engine();
35440
+ init_channel();
34825
35441
  init_phase();
34826
35442
  init_phases();
34827
35443
  init_pipeline_manifest_sqlite();
@@ -34907,6 +35523,7 @@ var init_pipeline2 = __esm({
34907
35523
  "manifest.stats",
34908
35524
  "release.list",
34909
35525
  "release.show",
35526
+ "release.channel.show",
34910
35527
  "phase.show",
34911
35528
  "phase.list",
34912
35529
  "chain.show",
@@ -35149,6 +35766,29 @@ var init_pipeline2 = __esm({
35149
35766
  const result = await releaseShow(version, this.projectRoot);
35150
35767
  return this.wrapEngineResult(result, "query", "release.show", startTime);
35151
35768
  }
35769
+ case "channel.show": {
35770
+ let currentBranch = "unknown";
35771
+ try {
35772
+ currentBranch = execFileSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
35773
+ encoding: "utf-8",
35774
+ stdio: "pipe",
35775
+ cwd: this.projectRoot
35776
+ }).trim();
35777
+ } catch {
35778
+ }
35779
+ const resolvedChannel = resolveChannelFromBranch(currentBranch);
35780
+ const distTag = channelToDistTag(resolvedChannel);
35781
+ const description = describeChannel(resolvedChannel);
35782
+ return this.wrapEngineResult({
35783
+ success: true,
35784
+ data: {
35785
+ branch: currentBranch,
35786
+ channel: resolvedChannel,
35787
+ distTag,
35788
+ description
35789
+ }
35790
+ }, "query", "release.channel.show", startTime);
35791
+ }
35152
35792
  default:
35153
35793
  return this.errorResponse(
35154
35794
  "query",
@@ -35753,11 +36393,11 @@ var init_pipeline2 = __esm({
35753
36393
  });
35754
36394
 
35755
36395
  // src/core/issue/diagnostics.ts
35756
- import { execFileSync as execFileSync8 } from "node:child_process";
36396
+ import { execFileSync as execFileSync10 } from "node:child_process";
35757
36397
  function collectDiagnostics() {
35758
36398
  const getVersion3 = (cmd, args) => {
35759
36399
  try {
35760
- return execFileSync8(cmd, args, {
36400
+ return execFileSync10(cmd, args, {
35761
36401
  encoding: "utf-8",
35762
36402
  stdio: ["pipe", "pipe", "pipe"]
35763
36403
  }).trim();
@@ -35808,7 +36448,7 @@ var init_build_config = __esm({
35808
36448
  "use strict";
35809
36449
  BUILD_CONFIG = {
35810
36450
  "name": "@cleocode/cleo",
35811
- "version": "2026.3.16",
36451
+ "version": "2026.3.17",
35812
36452
  "description": "CLEO V2 - TypeScript task management CLI for AI coding agents",
35813
36453
  "repository": {
35814
36454
  "owner": "kryptobaseddev",
@@ -35817,7 +36457,7 @@ var init_build_config = __esm({
35817
36457
  "url": "https://github.com/kryptobaseddev/cleo.git",
35818
36458
  "issuesUrl": "https://github.com/kryptobaseddev/cleo/issues"
35819
36459
  },
35820
- "buildDate": "2026-03-07T05:25:13.762Z",
36460
+ "buildDate": "2026-03-07T07:08:25.337Z",
35821
36461
  "templates": {
35822
36462
  "issueTemplatesDir": "templates/issue-templates"
35823
36463
  }
@@ -35826,8 +36466,8 @@ var init_build_config = __esm({
35826
36466
  });
35827
36467
 
35828
36468
  // src/core/issue/template-parser.ts
35829
- import { existsSync as existsSync58, readFileSync as readFileSync42, readdirSync as readdirSync17, writeFileSync as writeFileSync8 } from "node:fs";
35830
- import { join as join57, basename as basename10 } from "node:path";
36469
+ import { existsSync as existsSync59, readFileSync as readFileSync43, readdirSync as readdirSync17, writeFileSync as writeFileSync8 } from "node:fs";
36470
+ import { join as join58, basename as basename10 } from "node:path";
35831
36471
  function extractYamlField(content, field) {
35832
36472
  const regex = new RegExp(`^${field}:\\s*["']?(.+?)["']?\\s*$`, "m");
35833
36473
  const match = content.match(regex);
@@ -35859,9 +36499,9 @@ function extractYamlArray(content, field) {
35859
36499
  return items;
35860
36500
  }
35861
36501
  function parseTemplateFile2(filePath) {
35862
- if (!existsSync58(filePath)) return null;
36502
+ if (!existsSync59(filePath)) return null;
35863
36503
  try {
35864
- const content = readFileSync42(filePath, "utf-8");
36504
+ const content = readFileSync43(filePath, "utf-8");
35865
36505
  const fileName = basename10(filePath);
35866
36506
  const stem = fileName.replace(/\.ya?ml$/, "");
35867
36507
  const name = extractYamlField(content, "name");
@@ -35878,12 +36518,12 @@ function parseTemplateFile2(filePath) {
35878
36518
  function parseIssueTemplates2(projectDir) {
35879
36519
  try {
35880
36520
  const packageRoot = getPackageRoot();
35881
- const packagedTemplateDir = join57(packageRoot, PACKAGED_TEMPLATE_DIR);
35882
- if (existsSync58(packagedTemplateDir)) {
36521
+ const packagedTemplateDir = join58(packageRoot, PACKAGED_TEMPLATE_DIR);
36522
+ if (existsSync59(packagedTemplateDir)) {
35883
36523
  const templates3 = [];
35884
36524
  for (const file of readdirSync17(packagedTemplateDir)) {
35885
36525
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
35886
- const template = parseTemplateFile2(join57(packagedTemplateDir, file));
36526
+ const template = parseTemplateFile2(join58(packagedTemplateDir, file));
35887
36527
  if (template) templates3.push(template);
35888
36528
  }
35889
36529
  if (templates3.length > 0) return templates3;
@@ -35891,12 +36531,12 @@ function parseIssueTemplates2(projectDir) {
35891
36531
  } catch {
35892
36532
  }
35893
36533
  const dir = projectDir ?? getProjectRoot();
35894
- const templateDir = join57(dir, TEMPLATE_DIR);
35895
- if (!existsSync58(templateDir)) return [];
36534
+ const templateDir = join58(dir, TEMPLATE_DIR);
36535
+ if (!existsSync59(templateDir)) return [];
35896
36536
  const templates2 = [];
35897
36537
  for (const file of readdirSync17(templateDir)) {
35898
36538
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
35899
- const template = parseTemplateFile2(join57(templateDir, file));
36539
+ const template = parseTemplateFile2(join58(templateDir, file));
35900
36540
  if (template) templates2.push(template);
35901
36541
  }
35902
36542
  return templates2;
@@ -35905,10 +36545,10 @@ function getTemplateConfig(cwd) {
35905
36545
  const projectDir = cwd ?? getProjectRoot();
35906
36546
  const liveTemplates = parseIssueTemplates2(projectDir);
35907
36547
  if (liveTemplates.length > 0) return liveTemplates;
35908
- const cachePath = join57(getCleoDir(cwd), CACHE_FILE);
35909
- if (existsSync58(cachePath)) {
36548
+ const cachePath = join58(getCleoDir(cwd), CACHE_FILE);
36549
+ if (existsSync59(cachePath)) {
35910
36550
  try {
35911
- const cached = JSON.parse(readFileSync42(cachePath, "utf-8"));
36551
+ const cached = JSON.parse(readFileSync43(cachePath, "utf-8"));
35912
36552
  if (cached.templates?.length > 0) return cached.templates;
35913
36553
  } catch {
35914
36554
  }
@@ -35964,7 +36604,7 @@ var init_template_parser2 = __esm({
35964
36604
  });
35965
36605
 
35966
36606
  // src/core/issue/create.ts
35967
- import { execFileSync as execFileSync9 } from "node:child_process";
36607
+ import { execFileSync as execFileSync11 } from "node:child_process";
35968
36608
  function buildIssueBody(subcommand, rawBody, severity, area) {
35969
36609
  const template = getTemplateForSubcommand2(subcommand);
35970
36610
  const sectionLabel = template?.name ?? "Description";
@@ -35983,7 +36623,7 @@ function buildIssueBody(subcommand, rawBody, severity, area) {
35983
36623
  }
35984
36624
  function checkGhCli() {
35985
36625
  try {
35986
- execFileSync9("gh", ["--version"], {
36626
+ execFileSync11("gh", ["--version"], {
35987
36627
  encoding: "utf-8",
35988
36628
  stdio: ["pipe", "pipe", "pipe"]
35989
36629
  });
@@ -35993,7 +36633,7 @@ function checkGhCli() {
35993
36633
  });
35994
36634
  }
35995
36635
  try {
35996
- execFileSync9("gh", ["auth", "status", "--hostname", "github.com"], {
36636
+ execFileSync11("gh", ["auth", "status", "--hostname", "github.com"], {
35997
36637
  encoding: "utf-8",
35998
36638
  stdio: ["pipe", "pipe", "pipe"]
35999
36639
  });
@@ -36005,7 +36645,7 @@ function checkGhCli() {
36005
36645
  }
36006
36646
  function addGhIssue(title, body, labels) {
36007
36647
  try {
36008
- const result = execFileSync9("gh", [
36648
+ const result = execFileSync11("gh", [
36009
36649
  "issue",
36010
36650
  "create",
36011
36651
  "--repo",
@@ -36967,8 +37607,8 @@ var init_tools = __esm({
36967
37607
  });
36968
37608
 
36969
37609
  // src/core/nexus/query.ts
36970
- import { join as join58, basename as basename11 } from "node:path";
36971
- import { existsSync as existsSync59, readFileSync as readFileSync43 } from "node:fs";
37610
+ import { join as join59, basename as basename11 } from "node:path";
37611
+ import { existsSync as existsSync60, readFileSync as readFileSync44 } from "node:fs";
36972
37612
  import { z as z3 } from "zod";
36973
37613
  function validateSyntax(query) {
36974
37614
  if (!query) return false;
@@ -37004,9 +37644,9 @@ function getCurrentProject() {
37004
37644
  return process.env["NEXUS_CURRENT_PROJECT"];
37005
37645
  }
37006
37646
  try {
37007
- const infoPath = join58(process.cwd(), ".cleo", "project-info.json");
37008
- if (existsSync59(infoPath)) {
37009
- const data = JSON.parse(readFileSync43(infoPath, "utf-8"));
37647
+ const infoPath = join59(process.cwd(), ".cleo", "project-info.json");
37648
+ if (existsSync60(infoPath)) {
37649
+ const data = JSON.parse(readFileSync44(infoPath, "utf-8"));
37010
37650
  if (typeof data.name === "string" && data.name.length > 0) {
37011
37651
  return data.name;
37012
37652
  }
@@ -37042,7 +37682,7 @@ async function resolveProjectPath2(projectName) {
37042
37682
  return project.path;
37043
37683
  }
37044
37684
  async function readProjectTasks(projectPath) {
37045
- const tasksDbPath = join58(projectPath, ".cleo", "tasks.db");
37685
+ const tasksDbPath = join59(projectPath, ".cleo", "tasks.db");
37046
37686
  try {
37047
37687
  const accessor = await getAccessor(projectPath);
37048
37688
  const taskFile = await accessor.loadTaskFile();
@@ -37456,8 +38096,8 @@ var init_deps2 = __esm({
37456
38096
 
37457
38097
  // src/core/nexus/sharing/index.ts
37458
38098
  import { readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
37459
- import { existsSync as existsSync60, readdirSync as readdirSync18, statSync as statSync8 } from "node:fs";
37460
- import { join as join59, relative as relative4 } from "node:path";
38099
+ import { existsSync as existsSync61, readdirSync as readdirSync18, statSync as statSync8 } from "node:fs";
38100
+ import { join as join60, relative as relative4 } from "node:path";
37461
38101
  function matchesPattern(filePath, pattern) {
37462
38102
  const normalizedPath = filePath.replace(/^\/+|\/+$/g, "");
37463
38103
  const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
@@ -37482,7 +38122,7 @@ function collectCleoFiles(cleoDir) {
37482
38122
  const entries = readdirSync18(dir);
37483
38123
  for (const entry of entries) {
37484
38124
  if (entry === ".git") continue;
37485
- const fullPath = join59(dir, entry);
38125
+ const fullPath = join60(dir, entry);
37486
38126
  const relPath = relative4(cleoDir, fullPath);
37487
38127
  try {
37488
38128
  const stat5 = statSync8(fullPath);
@@ -37542,7 +38182,7 @@ function generateGitignoreEntries(sharing) {
37542
38182
  async function syncGitignore(cwd) {
37543
38183
  const config = await loadConfig(cwd);
37544
38184
  const projectRoot = getProjectRoot(cwd);
37545
- const gitignorePath = join59(projectRoot, ".gitignore");
38185
+ const gitignorePath = join60(projectRoot, ".gitignore");
37546
38186
  const entries = generateGitignoreEntries(config.sharing);
37547
38187
  const managedSection = [
37548
38188
  "",
@@ -37552,7 +38192,7 @@ async function syncGitignore(cwd) {
37552
38192
  ""
37553
38193
  ].join("\n");
37554
38194
  let content = "";
37555
- if (existsSync60(gitignorePath)) {
38195
+ if (existsSync61(gitignorePath)) {
37556
38196
  content = await readFile15(gitignorePath, "utf-8");
37557
38197
  }
37558
38198
  const startIdx = content.indexOf(GITIGNORE_START);
@@ -39114,40 +39754,49 @@ var init_session_resolver = __esm({
39114
39754
  }
39115
39755
  });
39116
39756
 
39757
+ // src/core/tasks/id-generator.ts
39758
+ function normalizeTaskId(input) {
39759
+ if (typeof input !== "string") return null;
39760
+ const trimmed = input.trim();
39761
+ if (trimmed === "") return null;
39762
+ const match = trimmed.match(/^[Tt]?(\d+)(?:_.*)?$/);
39763
+ if (!match) return null;
39764
+ return `T${match[1]}`;
39765
+ }
39766
+ var init_id_generator = __esm({
39767
+ "src/core/tasks/id-generator.ts"() {
39768
+ "use strict";
39769
+ init_data_accessor();
39770
+ }
39771
+ });
39772
+
39117
39773
  // src/dispatch/lib/security.ts
39118
39774
  import { resolve as resolve9, normalize, relative as relative5, isAbsolute as isAbsolute2 } from "path";
39119
- function sanitizeTaskId(id) {
39120
- if (typeof id !== "string") {
39775
+ function sanitizeTaskId(value) {
39776
+ if (typeof value !== "string") {
39121
39777
  throw new SecurityError(
39122
39778
  "Task ID must be a string",
39123
39779
  "E_INVALID_TASK_ID",
39124
39780
  "taskId"
39125
39781
  );
39126
39782
  }
39127
- const trimmed = id.trim();
39128
- if (trimmed.length === 0) {
39129
- throw new SecurityError(
39130
- "Task ID cannot be empty",
39131
- "E_INVALID_TASK_ID",
39132
- "taskId"
39133
- );
39134
- }
39135
- if (!TASK_ID_PATTERN2.test(trimmed)) {
39783
+ const normalized = normalizeTaskId(value);
39784
+ if (normalized === null) {
39136
39785
  throw new SecurityError(
39137
- `Invalid task ID format: "${trimmed}". Must match pattern T[0-9]+ (e.g., T123)`,
39786
+ `Invalid task ID format: ${value}`,
39138
39787
  "E_INVALID_TASK_ID",
39139
39788
  "taskId"
39140
39789
  );
39141
39790
  }
39142
- const numericPart = parseInt(trimmed.slice(1), 10);
39791
+ const numericPart = parseInt(normalized.slice(1), 10);
39143
39792
  if (numericPart > MAX_TASK_ID_NUMBER) {
39144
39793
  throw new SecurityError(
39145
- `Task ID numeric value exceeds maximum (${MAX_TASK_ID_NUMBER}): ${trimmed}`,
39794
+ `Task ID exceeds maximum value: ${value}`,
39146
39795
  "E_INVALID_TASK_ID",
39147
39796
  "taskId"
39148
39797
  );
39149
39798
  }
39150
- return trimmed;
39799
+ return normalized;
39151
39800
  }
39152
39801
  function sanitizePath(path, projectRoot) {
39153
39802
  if (typeof path !== "string") {
@@ -39246,14 +39895,14 @@ function sanitizeParams(params, projectRoot, context) {
39246
39895
  if (value === void 0 || value === null) {
39247
39896
  continue;
39248
39897
  }
39249
- if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId")) {
39898
+ if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId" || key === "parentId" || key === "newParentId" || key === "relatedId" || key === "targetId")) {
39250
39899
  if (key === "parent" && value === "") {
39251
39900
  continue;
39252
39901
  }
39253
39902
  sanitized[key] = sanitizeTaskId(value);
39254
39903
  continue;
39255
39904
  }
39256
- if (key === "depends" && Array.isArray(value)) {
39905
+ if ((key === "depends" || key === "addDepends" || key === "removeDepends") && Array.isArray(value)) {
39257
39906
  sanitized[key] = value.map((v) => {
39258
39907
  if (typeof v === "string") {
39259
39908
  return sanitizeTaskId(v);
@@ -39303,10 +39952,11 @@ function sanitizeParams(params, projectRoot, context) {
39303
39952
  }
39304
39953
  return sanitized;
39305
39954
  }
39306
- var SecurityError, TASK_ID_PATTERN2, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
39955
+ var SecurityError, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
39307
39956
  var init_security = __esm({
39308
39957
  "src/dispatch/lib/security.ts"() {
39309
39958
  "use strict";
39959
+ init_id_generator();
39310
39960
  init_schema();
39311
39961
  init_status_registry();
39312
39962
  SecurityError = class extends Error {
@@ -39317,7 +39967,6 @@ var init_security = __esm({
39317
39967
  this.name = "SecurityError";
39318
39968
  }
39319
39969
  };
39320
- TASK_ID_PATTERN2 = /^T[0-9]+$/;
39321
39970
  MAX_TASK_ID_NUMBER = 999999;
39322
39971
  CONTROL_CHAR_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
39323
39972
  DEFAULT_MAX_CONTENT_LENGTH = 64 * 1024;
@@ -39465,13 +40114,13 @@ var init_field_context = __esm({
39465
40114
  });
39466
40115
 
39467
40116
  // src/core/sessions/context-alert.ts
39468
- import { existsSync as existsSync61, readFileSync as readFileSync44, writeFileSync as writeFileSync9 } from "node:fs";
39469
- import { join as join60 } from "node:path";
40117
+ import { existsSync as existsSync62, readFileSync as readFileSync45, writeFileSync as writeFileSync9 } from "node:fs";
40118
+ import { join as join61 } from "node:path";
39470
40119
  function getCurrentSessionId(cwd) {
39471
40120
  if (process.env.CLEO_SESSION) return process.env.CLEO_SESSION;
39472
- const sessionFile = join60(getCleoDir(cwd), ".current-session");
39473
- if (existsSync61(sessionFile)) {
39474
- return readFileSync44(sessionFile, "utf-8").trim() || null;
40121
+ const sessionFile = join61(getCleoDir(cwd), ".current-session");
40122
+ if (existsSync62(sessionFile)) {
40123
+ return readFileSync45(sessionFile, "utf-8").trim() || null;
39475
40124
  }
39476
40125
  return null;
39477
40126
  }
@@ -40718,14 +41367,14 @@ __export(logger_exports, {
40718
41367
  logFileExists: () => logFileExists,
40719
41368
  readMigrationLog: () => readMigrationLog
40720
41369
  });
40721
- import { existsSync as existsSync64, mkdirSync as mkdirSync16, statSync as statSync9, appendFileSync as appendFileSync4 } from "node:fs";
40722
- import { join as join67, dirname as dirname16, relative as relative6 } from "node:path";
41370
+ import { existsSync as existsSync65, mkdirSync as mkdirSync16, statSync as statSync9, appendFileSync as appendFileSync4 } from "node:fs";
41371
+ import { join as join68, dirname as dirname16, relative as relative6 } from "node:path";
40723
41372
  function createMigrationLogger(cleoDir, config) {
40724
41373
  return new MigrationLogger(cleoDir, config);
40725
41374
  }
40726
41375
  function readMigrationLog(logPath) {
40727
- const { readFileSync: readFileSync52 } = __require("node:fs");
40728
- const content = readFileSync52(logPath, "utf-8");
41376
+ const { readFileSync: readFileSync53 } = __require("node:fs");
41377
+ const content = readFileSync53(logPath, "utf-8");
40729
41378
  return content.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
40730
41379
  }
40731
41380
  function logFileExists(logPath) {
@@ -40740,14 +41389,14 @@ function logFileExists(logPath) {
40740
41389
  function getLatestMigrationLog(cleoDir) {
40741
41390
  try {
40742
41391
  const { readdirSync: readdirSync21, statSync: statSync10 } = __require("node:fs");
40743
- const logsDir = join67(cleoDir, "logs");
40744
- if (!existsSync64(logsDir)) {
41392
+ const logsDir = join68(cleoDir, "logs");
41393
+ if (!existsSync65(logsDir)) {
40745
41394
  return null;
40746
41395
  }
40747
41396
  const files = readdirSync21(logsDir).filter((f) => f.startsWith("migration-") && f.endsWith(".jsonl")).map((f) => ({
40748
41397
  name: f,
40749
- path: join67(logsDir, f),
40750
- mtime: statSync10(join67(logsDir, f)).mtime.getTime()
41398
+ path: join68(logsDir, f),
41399
+ mtime: statSync10(join68(logsDir, f)).mtime.getTime()
40751
41400
  })).sort((a, b) => b.mtime - a.mtime);
40752
41401
  return files.length > 0 ? files[0].path : null;
40753
41402
  } catch {
@@ -40777,9 +41426,9 @@ var init_logger2 = __esm({
40777
41426
  consoleOutput: config.consoleOutput ?? false
40778
41427
  };
40779
41428
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
40780
- this.logPath = join67(cleoDir, "logs", `migration-${timestamp}.jsonl`);
41429
+ this.logPath = join68(cleoDir, "logs", `migration-${timestamp}.jsonl`);
40781
41430
  const logsDir = dirname16(this.logPath);
40782
- if (!existsSync64(logsDir)) {
41431
+ if (!existsSync65(logsDir)) {
40783
41432
  mkdirSync16(logsDir, { recursive: true });
40784
41433
  }
40785
41434
  this.startTime = Date.now();
@@ -40868,7 +41517,7 @@ var init_logger2 = __esm({
40868
41517
  sourcePath: relative6(this.cleoDir, sourcePath),
40869
41518
  ...additionalData
40870
41519
  };
40871
- if (existsSync64(sourcePath)) {
41520
+ if (existsSync65(sourcePath)) {
40872
41521
  try {
40873
41522
  const stats2 = statSync9(sourcePath);
40874
41523
  data.sourceSize = stats2.size;
@@ -40878,7 +41527,7 @@ var init_logger2 = __esm({
40878
41527
  }
40879
41528
  if (targetPath) {
40880
41529
  data.targetPath = relative6(this.cleoDir, targetPath);
40881
- if (existsSync64(targetPath)) {
41530
+ if (existsSync65(targetPath)) {
40882
41531
  try {
40883
41532
  const stats2 = statSync9(targetPath);
40884
41533
  data.targetSize = stats2.size;
@@ -40947,15 +41596,15 @@ var init_logger2 = __esm({
40947
41596
  */
40948
41597
  cleanupOldLogs() {
40949
41598
  try {
40950
- const logsDir = join67(this.cleoDir, "logs");
40951
- if (!existsSync64(logsDir)) {
41599
+ const logsDir = join68(this.cleoDir, "logs");
41600
+ if (!existsSync65(logsDir)) {
40952
41601
  return;
40953
41602
  }
40954
41603
  const { readdirSync: readdirSync21, unlinkSync: unlinkSync5 } = __require("node:fs");
40955
41604
  const files = readdirSync21(logsDir).filter((f) => f.startsWith("migration-") && f.endsWith(".jsonl")).map((f) => ({
40956
41605
  name: f,
40957
- path: join67(logsDir, f),
40958
- mtime: statSync9(join67(logsDir, f)).mtime.getTime()
41606
+ path: join68(logsDir, f),
41607
+ mtime: statSync9(join68(logsDir, f)).mtime.getTime()
40959
41608
  })).sort((a, b) => b.mtime - a.mtime);
40960
41609
  const filesToRemove = files.slice(this.config.maxLogFiles);
40961
41610
  for (const file of filesToRemove) {
@@ -41051,8 +41700,8 @@ __export(state_exports, {
41051
41700
  verifySourceIntegrity: () => verifySourceIntegrity
41052
41701
  });
41053
41702
  import { readFile as readFile19, writeFile as writeFile13, unlink as unlink5 } from "node:fs/promises";
41054
- import { join as join68 } from "node:path";
41055
- import { existsSync as existsSync65 } from "node:fs";
41703
+ import { join as join69 } from "node:path";
41704
+ import { existsSync as existsSync66 } from "node:fs";
41056
41705
  import { createHash as createHash8 } from "node:crypto";
41057
41706
  async function computeFileChecksum(filePath) {
41058
41707
  try {
@@ -41074,8 +41723,8 @@ async function countRecords(filePath, key) {
41074
41723
  async function createMigrationState(cleoDir, sourceFiles) {
41075
41724
  const files = sourceFiles ?? {};
41076
41725
  if (!files.todoJson) {
41077
- const todoPath = join68(cleoDir, "todo.json");
41078
- if (existsSync65(todoPath)) {
41726
+ const todoPath = join69(cleoDir, "todo.json");
41727
+ if (existsSync66(todoPath)) {
41079
41728
  files.todoJson = {
41080
41729
  path: todoPath,
41081
41730
  checksum: await computeFileChecksum(todoPath),
@@ -41084,8 +41733,8 @@ async function createMigrationState(cleoDir, sourceFiles) {
41084
41733
  }
41085
41734
  }
41086
41735
  if (!files.sessionsJson) {
41087
- const sessionsPath = join68(cleoDir, "sessions.json");
41088
- if (existsSync65(sessionsPath)) {
41736
+ const sessionsPath = join69(cleoDir, "sessions.json");
41737
+ if (existsSync66(sessionsPath)) {
41089
41738
  files.sessionsJson = {
41090
41739
  path: sessionsPath,
41091
41740
  checksum: await computeFileChecksum(sessionsPath),
@@ -41094,8 +41743,8 @@ async function createMigrationState(cleoDir, sourceFiles) {
41094
41743
  }
41095
41744
  }
41096
41745
  if (!files.archiveJson) {
41097
- const archivePath = join68(cleoDir, "todo-archive.json");
41098
- if (existsSync65(archivePath)) {
41746
+ const archivePath = join69(cleoDir, "todo-archive.json");
41747
+ if (existsSync66(archivePath)) {
41099
41748
  files.archiveJson = {
41100
41749
  path: archivePath,
41101
41750
  checksum: await computeFileChecksum(archivePath),
@@ -41123,7 +41772,7 @@ async function createMigrationState(cleoDir, sourceFiles) {
41123
41772
  return state;
41124
41773
  }
41125
41774
  async function writeMigrationState(cleoDir, state) {
41126
- const statePath = join68(cleoDir, STATE_FILENAME);
41775
+ const statePath = join69(cleoDir, STATE_FILENAME);
41127
41776
  const tempPath = `${statePath}.tmp`;
41128
41777
  await writeFile13(tempPath, JSON.stringify(state, null, 2));
41129
41778
  await writeFile13(statePath, await readFile19(tempPath));
@@ -41198,7 +41847,7 @@ async function addMigrationWarning(cleoDir, warning) {
41198
41847
  }
41199
41848
  async function loadMigrationState(cleoDir) {
41200
41849
  try {
41201
- const statePath = join68(cleoDir, STATE_FILENAME);
41850
+ const statePath = join69(cleoDir, STATE_FILENAME);
41202
41851
  const content = await readFile19(statePath, "utf-8");
41203
41852
  return JSON.parse(content);
41204
41853
  } catch {
@@ -41240,7 +41889,7 @@ async function failMigration(cleoDir, error) {
41240
41889
  }
41241
41890
  async function clearMigrationState(cleoDir) {
41242
41891
  try {
41243
- const statePath = join68(cleoDir, STATE_FILENAME);
41892
+ const statePath = join69(cleoDir, STATE_FILENAME);
41244
41893
  await unlink5(statePath);
41245
41894
  } catch {
41246
41895
  }
@@ -41280,7 +41929,7 @@ async function verifySourceIntegrity(cleoDir) {
41280
41929
  const missing = [];
41281
41930
  for (const [key, fileInfo] of Object.entries(state.sourceFiles)) {
41282
41931
  if (!fileInfo) continue;
41283
- if (!existsSync65(fileInfo.path)) {
41932
+ if (!existsSync66(fileInfo.path)) {
41284
41933
  missing.push(key);
41285
41934
  continue;
41286
41935
  }
@@ -41311,8 +41960,8 @@ __export(migration_sqlite_exports, {
41311
41960
  migrateJsonToSqlite: () => migrateJsonToSqlite2,
41312
41961
  migrateJsonToSqliteAtomic: () => migrateJsonToSqliteAtomic
41313
41962
  });
41314
- import { existsSync as existsSync66, readFileSync as readFileSync47 } from "node:fs";
41315
- import { join as join69, dirname as dirname17 } from "node:path";
41963
+ import { existsSync as existsSync67, readFileSync as readFileSync48 } from "node:fs";
41964
+ import { join as join70, dirname as dirname17 } from "node:path";
41316
41965
  import { mkdirSync as mkdirSync17 } from "node:fs";
41317
41966
  import { drizzle as drizzle4 } from "drizzle-orm/sqlite-proxy";
41318
41967
  import { migrate as migrate4 } from "drizzle-orm/sqlite-proxy/migrator";
@@ -41350,26 +41999,26 @@ function countJsonRecords(cleoDir) {
41350
41999
  let tasks2 = 0;
41351
42000
  let archived = 0;
41352
42001
  let sessions2 = 0;
41353
- const todoPath = join69(cleoDir, "todo.json");
41354
- if (existsSync66(todoPath)) {
42002
+ const todoPath = join70(cleoDir, "todo.json");
42003
+ if (existsSync67(todoPath)) {
41355
42004
  try {
41356
- const data = JSON.parse(readFileSync47(todoPath, "utf-8"));
42005
+ const data = JSON.parse(readFileSync48(todoPath, "utf-8"));
41357
42006
  tasks2 = (data.tasks ?? []).length;
41358
42007
  } catch {
41359
42008
  }
41360
42009
  }
41361
- const archivePath = join69(cleoDir, "todo-archive.json");
41362
- if (existsSync66(archivePath)) {
42010
+ const archivePath = join70(cleoDir, "todo-archive.json");
42011
+ if (existsSync67(archivePath)) {
41363
42012
  try {
41364
- const data = JSON.parse(readFileSync47(archivePath, "utf-8"));
42013
+ const data = JSON.parse(readFileSync48(archivePath, "utf-8"));
41365
42014
  archived = (data.tasks ?? data.archivedTasks ?? []).length;
41366
42015
  } catch {
41367
42016
  }
41368
42017
  }
41369
- const sessionsPath = join69(cleoDir, "sessions.json");
41370
- if (existsSync66(sessionsPath)) {
42018
+ const sessionsPath = join70(cleoDir, "sessions.json");
42019
+ if (existsSync67(sessionsPath)) {
41371
42020
  try {
41372
- const data = JSON.parse(readFileSync47(sessionsPath, "utf-8"));
42021
+ const data = JSON.parse(readFileSync48(sessionsPath, "utf-8"));
41373
42022
  sessions2 = (data.sessions ?? []).length;
41374
42023
  } catch {
41375
42024
  }
@@ -41446,13 +42095,13 @@ async function migrateJsonToSqliteAtomic(cwd, tempDbPath, logger) {
41446
42095
  }
41447
42096
  }
41448
42097
  async function runMigrationDataImport(db, cleoDir, result, logger) {
41449
- const todoPath = join69(cleoDir, "todo.json");
41450
- if (existsSync66(todoPath)) {
42098
+ const todoPath = join70(cleoDir, "todo.json");
42099
+ if (existsSync67(todoPath)) {
41451
42100
  try {
41452
42101
  logger?.info("import", "read-todo", "Reading todo.json", {
41453
42102
  path: todoPath.replace(cleoDir, ".")
41454
42103
  });
41455
- const todoData = JSON.parse(readFileSync47(todoPath, "utf-8"));
42104
+ const todoData = JSON.parse(readFileSync48(todoPath, "utf-8"));
41456
42105
  const tasks2 = topoSortTasks(todoData.tasks ?? []);
41457
42106
  const totalTasks = tasks2.length;
41458
42107
  logger?.info("import", "tasks-start", `Starting import of ${totalTasks} tasks`, {
@@ -41521,13 +42170,13 @@ async function runMigrationDataImport(db, cleoDir, result, logger) {
41521
42170
  result.warnings.push("todo.json not found, skipping task import");
41522
42171
  logger?.warn("import", "todo-missing", "todo.json not found, skipping task import");
41523
42172
  }
41524
- const archivePath = join69(cleoDir, "todo-archive.json");
41525
- if (existsSync66(archivePath)) {
42173
+ const archivePath = join70(cleoDir, "todo-archive.json");
42174
+ if (existsSync67(archivePath)) {
41526
42175
  try {
41527
42176
  logger?.info("import", "read-archive", "Reading todo-archive.json", {
41528
42177
  path: archivePath.replace(cleoDir, ".")
41529
42178
  });
41530
- const archiveData = JSON.parse(readFileSync47(archivePath, "utf-8"));
42179
+ const archiveData = JSON.parse(readFileSync48(archivePath, "utf-8"));
41531
42180
  const archivedTasks = topoSortTasks(archiveData.tasks ?? archiveData.archivedTasks ?? []);
41532
42181
  const totalArchived = archivedTasks.length;
41533
42182
  logger?.info("import", "archive-start", `Starting import of ${totalArchived} archived tasks`, {
@@ -41580,13 +42229,13 @@ async function runMigrationDataImport(db, cleoDir, result, logger) {
41580
42229
  logger?.error("import", "parse-archive", errorMsg);
41581
42230
  }
41582
42231
  }
41583
- const sessionsPath = join69(cleoDir, "sessions.json");
41584
- if (existsSync66(sessionsPath)) {
42232
+ const sessionsPath = join70(cleoDir, "sessions.json");
42233
+ if (existsSync67(sessionsPath)) {
41585
42234
  try {
41586
42235
  logger?.info("import", "read-sessions", "Reading sessions.json", {
41587
42236
  path: sessionsPath.replace(cleoDir, ".")
41588
42237
  });
41589
- const sessionsData = JSON.parse(readFileSync47(sessionsPath, "utf-8"));
42238
+ const sessionsData = JSON.parse(readFileSync48(sessionsPath, "utf-8"));
41590
42239
  const sessions2 = sessionsData.sessions ?? [];
41591
42240
  const totalSessions = sessions2.length;
41592
42241
  logger?.info("import", "sessions-start", `Starting import of ${totalSessions} sessions`, {
@@ -41718,10 +42367,10 @@ async function migrateJsonToSqlite2(cwd, options) {
41718
42367
  return result;
41719
42368
  }
41720
42369
  const db = await getDb(cwd);
41721
- const todoPath = join69(cleoDir, "todo.json");
41722
- if (existsSync66(todoPath)) {
42370
+ const todoPath = join70(cleoDir, "todo.json");
42371
+ if (existsSync67(todoPath)) {
41723
42372
  try {
41724
- const todoData = JSON.parse(readFileSync47(todoPath, "utf-8"));
42373
+ const todoData = JSON.parse(readFileSync48(todoPath, "utf-8"));
41725
42374
  const tasks2 = topoSortTasks(todoData.tasks ?? []);
41726
42375
  for (const task of tasks2) {
41727
42376
  try {
@@ -41770,10 +42419,10 @@ async function migrateJsonToSqlite2(cwd, options) {
41770
42419
  } else {
41771
42420
  result.warnings.push("todo.json not found, skipping task import");
41772
42421
  }
41773
- const archivePath = join69(cleoDir, "todo-archive.json");
41774
- if (existsSync66(archivePath)) {
42422
+ const archivePath = join70(cleoDir, "todo-archive.json");
42423
+ if (existsSync67(archivePath)) {
41775
42424
  try {
41776
- const archiveData = JSON.parse(readFileSync47(archivePath, "utf-8"));
42425
+ const archiveData = JSON.parse(readFileSync48(archivePath, "utf-8"));
41777
42426
  const archivedTasks = topoSortTasks(archiveData.tasks ?? archiveData.archivedTasks ?? []);
41778
42427
  for (const task of archivedTasks) {
41779
42428
  try {
@@ -41810,11 +42459,11 @@ async function migrateJsonToSqlite2(cwd, options) {
41810
42459
  result.errors.push(`Failed to parse todo-archive.json: ${String(err)}`);
41811
42460
  }
41812
42461
  }
41813
- const sessionsPath = join69(cleoDir, "sessions.json");
41814
- if (existsSync66(sessionsPath)) {
42462
+ const sessionsPath = join70(cleoDir, "sessions.json");
42463
+ if (existsSync67(sessionsPath)) {
41815
42464
  try {
41816
42465
  const sessionsData = JSON.parse(
41817
- readFileSync47(sessionsPath, "utf-8")
42466
+ readFileSync48(sessionsPath, "utf-8")
41818
42467
  );
41819
42468
  const sessions2 = sessionsData.sessions ?? [];
41820
42469
  for (const session of sessions2) {
@@ -41882,8 +42531,8 @@ var init_migration_sqlite = __esm({
41882
42531
 
41883
42532
  // src/cli/index.ts
41884
42533
  import { Command, Help } from "commander";
41885
- import { readFileSync as readFileSync51 } from "node:fs";
41886
- import { join as join75 } from "node:path";
42534
+ import { readFileSync as readFileSync52 } from "node:fs";
42535
+ import { join as join76 } from "node:path";
41887
42536
 
41888
42537
  // src/cli/commands/add.ts
41889
42538
  init_cli();
@@ -42733,14 +43382,14 @@ init_errors();
42733
43382
  init_exit_codes();
42734
43383
  init_paths();
42735
43384
  init_json();
42736
- import { join as join61 } from "node:path";
43385
+ import { join as join62 } from "node:path";
42737
43386
  var VALID_CATEGORIES = ["write", "read", "sync", "maintenance"];
42738
43387
  var VALID_RELEVANCE = ["critical", "high", "medium", "low"];
42739
43388
  async function locateCommandsIndex() {
42740
43389
  const cleoHome = getCleoHome();
42741
43390
  const paths = [
42742
- join61(cleoHome, "docs", "commands", "COMMANDS-INDEX.json"),
42743
- join61(process.cwd(), "docs", "commands", "COMMANDS-INDEX.json")
43391
+ join62(cleoHome, "docs", "commands", "COMMANDS-INDEX.json"),
43392
+ join62(process.cwd(), "docs", "commands", "COMMANDS-INDEX.json")
42744
43393
  ];
42745
43394
  for (const p of paths) {
42746
43395
  const data = await readJson(p);
@@ -42821,9 +43470,9 @@ init_errors();
42821
43470
  init_json();
42822
43471
  init_paths();
42823
43472
  import { readdir as readdir2, readFile as readFile16 } from "node:fs/promises";
42824
- import { join as join62 } from "node:path";
43473
+ import { join as join63 } from "node:path";
42825
43474
  async function getScriptNames(projectRoot) {
42826
- const scriptsDir = join62(projectRoot, "scripts");
43475
+ const scriptsDir = join63(projectRoot, "scripts");
42827
43476
  try {
42828
43477
  const files = await readdir2(scriptsDir);
42829
43478
  return files.filter((f) => f.endsWith(".sh")).map((f) => f.replace(".sh", "")).sort();
@@ -42832,7 +43481,7 @@ async function getScriptNames(projectRoot) {
42832
43481
  }
42833
43482
  }
42834
43483
  async function getIndexedCommands(projectRoot) {
42835
- const indexPath = join62(projectRoot, "docs", "commands", "COMMANDS-INDEX.json");
43484
+ const indexPath = join63(projectRoot, "docs", "commands", "COMMANDS-INDEX.json");
42836
43485
  const index5 = await readJson(indexPath);
42837
43486
  if (!index5) return [];
42838
43487
  return index5.commands.map((c) => c.name).sort();
@@ -42865,7 +43514,7 @@ async function runGapCheck(_projectRoot, filterId) {
42865
43514
  const reviewFiles = files.filter((f) => f.endsWith(".md"));
42866
43515
  for (const file of reviewFiles) {
42867
43516
  if (filterId && !file.includes(filterId)) continue;
42868
- const filePath = join62(reviewDir, file);
43517
+ const filePath = join63(reviewDir, file);
42869
43518
  const content = await readFile16(filePath, "utf-8");
42870
43519
  const taskMatch = file.match(/^(T\d+)/);
42871
43520
  const taskId = taskMatch ? taskMatch[1] : "UNKNOWN";
@@ -42969,7 +43618,7 @@ init_paths();
42969
43618
  init_json();
42970
43619
  init_data_accessor();
42971
43620
  import { readFile as readFile17, rm as rm3, stat as stat3 } from "node:fs/promises";
42972
- import { join as join63 } from "node:path";
43621
+ import { join as join64 } from "node:path";
42973
43622
  function parseTaskId(content) {
42974
43623
  const match = content.match(/^\[T(\d+)\]/);
42975
43624
  return match ? `T${match[1]}` : null;
@@ -43025,7 +43674,7 @@ function registerExtractCommand(program2) {
43025
43674
  const accessor = await getAccessor();
43026
43675
  const taskData = await accessor.loadTaskFile();
43027
43676
  const cleoDir = getCleoDir();
43028
- const stateFile = join63(cleoDir, "sync", "todowrite-session.json");
43677
+ const stateFile = join64(cleoDir, "sync", "todowrite-session.json");
43029
43678
  let sessionState = null;
43030
43679
  try {
43031
43680
  sessionState = await readJson(stateFile);
@@ -43211,17 +43860,17 @@ init_renderers();
43211
43860
  init_errors();
43212
43861
  init_exit_codes();
43213
43862
  init_paths();
43214
- import { spawn as spawn2, execFileSync as execFileSync10 } from "node:child_process";
43863
+ import { spawn as spawn2, execFileSync as execFileSync12 } from "node:child_process";
43215
43864
  import { readFile as readFile18, writeFile as writeFile12, mkdir as mkdir12, rm as rm4, stat as stat4 } from "node:fs/promises";
43216
- import { join as join64 } from "node:path";
43865
+ import { join as join65 } from "node:path";
43217
43866
  var DEFAULT_PORT = 3456;
43218
43867
  var DEFAULT_HOST = "127.0.0.1";
43219
43868
  function getWebPaths() {
43220
43869
  const cleoHome = getCleoHome();
43221
43870
  return {
43222
- pidFile: join64(cleoHome, "web-server.pid"),
43223
- configFile: join64(cleoHome, "web-server.json"),
43224
- logFile: join64(cleoHome, "logs", "web-server.log")
43871
+ pidFile: join65(cleoHome, "web-server.pid"),
43872
+ configFile: join65(cleoHome, "web-server.json"),
43873
+ logFile: join65(cleoHome, "logs", "web-server.log")
43225
43874
  };
43226
43875
  }
43227
43876
  function isProcessRunning(pid) {
@@ -43265,19 +43914,19 @@ function registerWebCommand(program2) {
43265
43914
  throw new CleoError(1 /* GENERAL_ERROR */, `Server already running (PID: ${status.pid})`);
43266
43915
  }
43267
43916
  const projectRoot = process.env["CLEO_ROOT"] ?? process.cwd();
43268
- const distMcpDir = join64(projectRoot, "dist", "mcp");
43269
- await mkdir12(join64(getCleoHome(), "logs"), { recursive: true });
43917
+ const distMcpDir = join65(projectRoot, "dist", "mcp");
43918
+ await mkdir12(join65(getCleoHome(), "logs"), { recursive: true });
43270
43919
  await writeFile12(configFile, JSON.stringify({
43271
43920
  port,
43272
43921
  host,
43273
43922
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
43274
43923
  }));
43275
- const webIndexPath = join64(distMcpDir, "index.js");
43924
+ const webIndexPath = join65(distMcpDir, "index.js");
43276
43925
  try {
43277
43926
  await stat4(webIndexPath);
43278
43927
  } catch {
43279
43928
  try {
43280
- execFileSync10("npm", ["run", "build"], { cwd: projectRoot, stdio: "ignore" });
43929
+ execFileSync12("npm", ["run", "build"], { cwd: projectRoot, stdio: "ignore" });
43281
43930
  } catch {
43282
43931
  throw new CleoError(1 /* GENERAL_ERROR */, `Build failed. Check logs: ${logFile}`);
43283
43932
  }
@@ -43496,13 +44145,13 @@ init_renderers();
43496
44145
  init_errors();
43497
44146
  init_exit_codes();
43498
44147
  init_paths();
43499
- import { existsSync as existsSync62, readFileSync as readFileSync45, writeFileSync as writeFileSync10, mkdirSync as mkdirSync15 } from "node:fs";
43500
- import { execFileSync as execFileSync11 } from "node:child_process";
43501
- import { join as join65, dirname as dirname15 } from "node:path";
44148
+ import { existsSync as existsSync63, readFileSync as readFileSync46, writeFileSync as writeFileSync10, mkdirSync as mkdirSync15 } from "node:fs";
44149
+ import { execFileSync as execFileSync13 } from "node:child_process";
44150
+ import { join as join66, dirname as dirname15 } from "node:path";
43502
44151
  function getChangelogSource(cwd) {
43503
44152
  const configPath = getConfigPath(cwd);
43504
44153
  try {
43505
- const config = JSON.parse(readFileSync45(configPath, "utf-8"));
44154
+ const config = JSON.parse(readFileSync46(configPath, "utf-8"));
43506
44155
  return config?.release?.changelog?.source ?? "CHANGELOG.md";
43507
44156
  } catch {
43508
44157
  return "CHANGELOG.md";
@@ -43511,7 +44160,7 @@ function getChangelogSource(cwd) {
43511
44160
  function getEnabledPlatforms(cwd) {
43512
44161
  const configPath = getConfigPath(cwd);
43513
44162
  try {
43514
- const config = JSON.parse(readFileSync45(configPath, "utf-8"));
44163
+ const config = JSON.parse(readFileSync46(configPath, "utf-8"));
43515
44164
  const outputs = config?.release?.changelog?.outputs ?? [];
43516
44165
  return outputs.filter((o) => o.enabled);
43517
44166
  } catch {
@@ -43533,7 +44182,7 @@ function getDefaultOutputPath(platform2) {
43533
44182
  function getGitHubRepoSlug(cwd) {
43534
44183
  const projectRoot = getProjectRoot(cwd);
43535
44184
  try {
43536
- const remoteUrl = execFileSync11("git", ["remote", "get-url", "origin"], {
44185
+ const remoteUrl = execFileSync13("git", ["remote", "get-url", "origin"], {
43537
44186
  cwd: projectRoot,
43538
44187
  encoding: "utf-8",
43539
44188
  stdio: ["pipe", "pipe", "pipe"]
@@ -43638,11 +44287,11 @@ function registerGenerateChangelogCommand(program2) {
43638
44287
  const targetPlatform = opts["platform"];
43639
44288
  const dryRun = !!opts["dryRun"];
43640
44289
  const sourceFile = getChangelogSource();
43641
- const sourcePath = join65(getProjectRoot(), sourceFile);
43642
- if (!existsSync62(sourcePath)) {
44290
+ const sourcePath = join66(getProjectRoot(), sourceFile);
44291
+ if (!existsSync63(sourcePath)) {
43643
44292
  throw new CleoError(4 /* NOT_FOUND */, `Changelog source not found: ${sourcePath}`);
43644
44293
  }
43645
- const sourceContent = readFileSync45(sourcePath, "utf-8");
44294
+ const sourceContent = readFileSync46(sourcePath, "utf-8");
43646
44295
  const repoSlug = getGitHubRepoSlug();
43647
44296
  const results = [];
43648
44297
  if (targetPlatform) {
@@ -43651,7 +44300,7 @@ function registerGenerateChangelogCommand(program2) {
43651
44300
  const outputPath = platformConfig?.path ?? getDefaultOutputPath(targetPlatform);
43652
44301
  const content = generateForPlatform(targetPlatform, sourceContent, repoSlug, limit);
43653
44302
  if (!dryRun) {
43654
- const fullPath = join65(getProjectRoot(), outputPath);
44303
+ const fullPath = join66(getProjectRoot(), outputPath);
43655
44304
  mkdirSync15(dirname15(fullPath), { recursive: true });
43656
44305
  writeFileSync10(fullPath, content, "utf-8");
43657
44306
  }
@@ -43672,7 +44321,7 @@ function registerGenerateChangelogCommand(program2) {
43672
44321
  limit
43673
44322
  );
43674
44323
  if (!dryRun) {
43675
- const fullPath = join65(getProjectRoot(), platformConfig.path);
44324
+ const fullPath = join66(getProjectRoot(), platformConfig.path);
43676
44325
  mkdirSync15(dirname15(fullPath), { recursive: true });
43677
44326
  writeFileSync10(fullPath, content, "utf-8");
43678
44327
  }
@@ -43703,7 +44352,7 @@ function registerGenerateChangelogCommand(program2) {
43703
44352
  init_cli();
43704
44353
  init_renderers();
43705
44354
  init_build_config();
43706
- import { execFileSync as execFileSync12 } from "node:child_process";
44355
+ import { execFileSync as execFileSync14 } from "node:child_process";
43707
44356
  var CLEO_REPO2 = BUILD_CONFIG.repository.fullName;
43708
44357
  function registerIssueCommand(program2) {
43709
44358
  const issueCmd = program2.command("issue").description("File bug reports, feature requests, or questions to CLEO GitHub repo");
@@ -43742,7 +44391,7 @@ async function handleIssueType(issueType, opts) {
43742
44391
  if (opts["open"] && typeof result["url"] === "string" && result["url"].startsWith("https://")) {
43743
44392
  const issueNumber = result["url"].match(/(\d+)$/)?.[1] ?? "unknown";
43744
44393
  try {
43745
- execFileSync12("gh", ["issue", "view", issueNumber, "--repo", CLEO_REPO2, "--web"], {
44394
+ execFileSync14("gh", ["issue", "view", issueNumber, "--repo", CLEO_REPO2, "--web"], {
43746
44395
  stdio: ["pipe", "pipe", "pipe"]
43747
44396
  });
43748
44397
  } catch {
@@ -44427,22 +45076,22 @@ function registerPlanCommand(program2) {
44427
45076
  }
44428
45077
 
44429
45078
  // src/core/otel/index.ts
44430
- import { readFileSync as readFileSync46, existsSync as existsSync63, writeFileSync as writeFileSync11, copyFileSync as copyFileSync4 } from "node:fs";
44431
- import { join as join66 } from "node:path";
45079
+ import { readFileSync as readFileSync47, existsSync as existsSync64, writeFileSync as writeFileSync11, copyFileSync as copyFileSync4 } from "node:fs";
45080
+ import { join as join67 } from "node:path";
44432
45081
  function getProjectRoot2() {
44433
45082
  let dir = process.cwd();
44434
45083
  while (dir !== "/") {
44435
- if (existsSync63(join66(dir, ".cleo", "config.json"))) return dir;
44436
- dir = join66(dir, "..");
45084
+ if (existsSync64(join67(dir, ".cleo", "config.json"))) return dir;
45085
+ dir = join67(dir, "..");
44437
45086
  }
44438
45087
  return process.cwd();
44439
45088
  }
44440
45089
  function getTokenFilePath() {
44441
- return join66(getProjectRoot2(), ".cleo", "metrics", "TOKEN_USAGE.jsonl");
45090
+ return join67(getProjectRoot2(), ".cleo", "metrics", "TOKEN_USAGE.jsonl");
44442
45091
  }
44443
45092
  function readJsonlFile(filePath) {
44444
- if (!existsSync63(filePath)) return [];
44445
- const content = readFileSync46(filePath, "utf-8").trim();
45093
+ if (!existsSync64(filePath)) return [];
45094
+ const content = readFileSync47(filePath, "utf-8").trim();
44446
45095
  if (!content) return [];
44447
45096
  return content.split("\n").map((line) => JSON.parse(line));
44448
45097
  }
@@ -44524,7 +45173,7 @@ async function getRealTokenUsage(_opts) {
44524
45173
  }
44525
45174
  async function clearOtelData() {
44526
45175
  const tokenFile = getTokenFilePath();
44527
- if (existsSync63(tokenFile)) {
45176
+ if (existsSync64(tokenFile)) {
44528
45177
  const backup = `${tokenFile}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
44529
45178
  copyFileSync4(tokenFile, backup);
44530
45179
  writeFileSync11(tokenFile, "");
@@ -44889,8 +45538,8 @@ init_storage_preflight();
44889
45538
  init_paths();
44890
45539
  init_storage_preflight();
44891
45540
  init_agent_outputs();
44892
- import { existsSync as existsSync67, readFileSync as readFileSync48, writeFileSync as writeFileSync12, mkdirSync as mkdirSync18, readdirSync as readdirSync19, copyFileSync as copyFileSync5 } from "node:fs";
44893
- import { join as join70 } from "node:path";
45541
+ import { existsSync as existsSync68, readFileSync as readFileSync49, writeFileSync as writeFileSync12, mkdirSync as mkdirSync18, readdirSync as readdirSync19, copyFileSync as copyFileSync5 } from "node:fs";
45542
+ import { join as join71 } from "node:path";
44894
45543
 
44895
45544
  // src/store/index.ts
44896
45545
  init_atomic();
@@ -44926,8 +45575,8 @@ async function runUpgrade(options = {}) {
44926
45575
  return { success: false, upToDate: false, dryRun: isDryRun, actions, applied: 0, errors: [String(err)] };
44927
45576
  }
44928
45577
  const cleoDir = getCleoDirAbsolute(options.cwd);
44929
- const dbPath = join70(cleoDir, "tasks.db");
44930
- const dbExists2 = existsSync67(dbPath);
45578
+ const dbPath = join71(cleoDir, "tasks.db");
45579
+ const dbExists2 = existsSync68(dbPath);
44931
45580
  const legacyRecordCount = preflight.details.todoJsonTaskCount + preflight.details.archiveJsonTaskCount + preflight.details.sessionsJsonCount;
44932
45581
  const needsMigration = !dbExists2 && legacyRecordCount > 0;
44933
45582
  const needsCleanup = dbExists2 && preflight.migrationNeeded;
@@ -44943,7 +45592,7 @@ async function runUpgrade(options = {}) {
44943
45592
  let migrationLock = null;
44944
45593
  try {
44945
45594
  const cleoDir2 = getCleoDirAbsolute(options.cwd);
44946
- const dbPath2 = join70(cleoDir2, "tasks.db");
45595
+ const dbPath2 = join71(cleoDir2, "tasks.db");
44947
45596
  try {
44948
45597
  migrationLock = await acquireLock(dbPath2, { stale: 3e4, retries: 0 });
44949
45598
  } catch {
@@ -44968,23 +45617,23 @@ async function runUpgrade(options = {}) {
44968
45617
  } = await Promise.resolve().then(() => (init_state(), state_exports));
44969
45618
  const logger = new MigrationLogger2(cleoDir2);
44970
45619
  await createMigrationState2(cleoDir2, {
44971
- todoJson: { path: join70(cleoDir2, "todo.json"), checksum: "" },
44972
- sessionsJson: { path: join70(cleoDir2, "sessions.json"), checksum: "" },
44973
- archiveJson: { path: join70(cleoDir2, "todo-archive.json"), checksum: "" }
45620
+ todoJson: { path: join71(cleoDir2, "todo.json"), checksum: "" },
45621
+ sessionsJson: { path: join71(cleoDir2, "sessions.json"), checksum: "" },
45622
+ archiveJson: { path: join71(cleoDir2, "todo-archive.json"), checksum: "" }
44974
45623
  });
44975
45624
  await updateMigrationPhase2(cleoDir2, "backup");
44976
45625
  logger.info("init", "start", "Migration state initialized");
44977
- const dbBackupPath = join70(cleoDir2, "backups", "safety", `tasks.db.pre-migration.${Date.now()}`);
44978
- const dbTempPath = join70(cleoDir2, "tasks.db.migrating");
44979
- if (existsSync67(dbPath2)) {
44980
- const backupDir = join70(cleoDir2, "backups", "safety");
44981
- if (!existsSync67(backupDir)) {
45626
+ const dbBackupPath = join71(cleoDir2, "backups", "safety", `tasks.db.pre-migration.${Date.now()}`);
45627
+ const dbTempPath = join71(cleoDir2, "tasks.db.migrating");
45628
+ if (existsSync68(dbPath2)) {
45629
+ const backupDir = join71(cleoDir2, "backups", "safety");
45630
+ if (!existsSync68(backupDir)) {
44982
45631
  mkdirSync18(backupDir, { recursive: true });
44983
45632
  }
44984
45633
  copyFileSync5(dbPath2, dbBackupPath);
44985
45634
  const { createHash: createHash9 } = await import("node:crypto");
44986
- const origChecksum = createHash9("sha256").update(readFileSync48(dbPath2)).digest("hex");
44987
- const backupChecksum = createHash9("sha256").update(readFileSync48(dbBackupPath)).digest("hex");
45635
+ const origChecksum = createHash9("sha256").update(readFileSync49(dbPath2)).digest("hex");
45636
+ const backupChecksum = createHash9("sha256").update(readFileSync49(dbBackupPath)).digest("hex");
44988
45637
  if (origChecksum !== backupChecksum) {
44989
45638
  throw new Error(
44990
45639
  `Backup verification failed: checksum mismatch. Aborting migration to prevent data loss.`
@@ -44999,14 +45648,14 @@ async function runUpgrade(options = {}) {
44999
45648
  }
45000
45649
  logger.info("backup", "verified", "Backup integrity verified", { checksum: origChecksum });
45001
45650
  }
45002
- if (existsSync67(dbTempPath)) {
45651
+ if (existsSync68(dbTempPath)) {
45003
45652
  const { unlinkSync: unlinkSync5 } = await import("node:fs");
45004
45653
  unlinkSync5(dbTempPath);
45005
45654
  }
45006
- const configPath = join70(cleoDir2, "config.json");
45655
+ const configPath = join71(cleoDir2, "config.json");
45007
45656
  let configBackup = null;
45008
- if (existsSync67(configPath)) {
45009
- configBackup = readFileSync48(configPath, "utf-8");
45657
+ if (existsSync68(configPath)) {
45658
+ configBackup = readFileSync49(configPath, "utf-8");
45010
45659
  }
45011
45660
  const { resetDbState: resetDbState2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
45012
45661
  resetDbState2();
@@ -45032,7 +45681,7 @@ async function runUpgrade(options = {}) {
45032
45681
  resetDbState2();
45033
45682
  if (result.success) {
45034
45683
  const totalImported = result.tasksImported + result.archivedImported;
45035
- if (totalImported === 0 && existsSync67(dbBackupPath)) {
45684
+ if (totalImported === 0 && existsSync68(dbBackupPath)) {
45036
45685
  copyFileSync5(dbBackupPath, dbPath2);
45037
45686
  if (configBackup) {
45038
45687
  writeFileSync12(configPath, configBackup);
@@ -45046,9 +45695,9 @@ async function runUpgrade(options = {}) {
45046
45695
  errors.push("Migration imported 0 tasks \u2014 restored from backup to prevent data loss");
45047
45696
  } else {
45048
45697
  let config = {};
45049
- if (existsSync67(configPath)) {
45698
+ if (existsSync68(configPath)) {
45050
45699
  try {
45051
- config = JSON.parse(readFileSync48(configPath, "utf-8"));
45700
+ config = JSON.parse(readFileSync49(configPath, "utf-8"));
45052
45701
  } catch {
45053
45702
  }
45054
45703
  }
@@ -45074,7 +45723,7 @@ async function runUpgrade(options = {}) {
45074
45723
  logger.info("complete", "finish", "Migration completed successfully");
45075
45724
  }
45076
45725
  } else {
45077
- if (existsSync67(dbBackupPath)) {
45726
+ if (existsSync68(dbBackupPath)) {
45078
45727
  copyFileSync5(dbBackupPath, dbPath2);
45079
45728
  }
45080
45729
  if (configBackup) {
@@ -45097,12 +45746,12 @@ async function runUpgrade(options = {}) {
45097
45746
  } catch (err) {
45098
45747
  try {
45099
45748
  const cleoDir2 = getCleoDirAbsolute(options.cwd);
45100
- const dbPath2 = join70(cleoDir2, "tasks.db");
45101
- const safetyDir = join70(cleoDir2, "backups", "safety");
45102
- if (existsSync67(safetyDir)) {
45749
+ const dbPath2 = join71(cleoDir2, "tasks.db");
45750
+ const safetyDir = join71(cleoDir2, "backups", "safety");
45751
+ if (existsSync68(safetyDir)) {
45103
45752
  const backups = readdirSync19(safetyDir).filter((f) => f.startsWith("tasks.db.pre-migration.")).sort().reverse();
45104
- if (backups.length > 0 && !existsSync67(dbPath2)) {
45105
- copyFileSync5(join70(safetyDir, backups[0]), dbPath2);
45753
+ if (backups.length > 0 && !existsSync68(dbPath2)) {
45754
+ copyFileSync5(join71(safetyDir, backups[0]), dbPath2);
45106
45755
  }
45107
45756
  }
45108
45757
  } catch {
@@ -45137,7 +45786,7 @@ async function runUpgrade(options = {}) {
45137
45786
  details: preflight.summary
45138
45787
  });
45139
45788
  }
45140
- if (existsSync67(dbPath)) {
45789
+ if (existsSync68(dbPath)) {
45141
45790
  try {
45142
45791
  const { runAllRepairs: runAllRepairs2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
45143
45792
  const repairActions = await runAllRepairs2(options.cwd, isDryRun);
@@ -45147,8 +45796,8 @@ async function runUpgrade(options = {}) {
45147
45796
  } catch {
45148
45797
  }
45149
45798
  }
45150
- if (existsSync67(dbPath)) {
45151
- const legacySequenceFiles = [".sequence", ".sequence.json"].filter((f) => existsSync67(join70(cleoDir, f)));
45799
+ if (existsSync68(dbPath)) {
45800
+ const legacySequenceFiles = [".sequence", ".sequence.json"].filter((f) => existsSync68(join71(cleoDir, f)));
45152
45801
  if (legacySequenceFiles.length > 0) {
45153
45802
  if (isDryRun) {
45154
45803
  actions.push({
@@ -45177,7 +45826,7 @@ async function runUpgrade(options = {}) {
45177
45826
  }
45178
45827
  if (needsCleanup) {
45179
45828
  const staleJsonFiles = ["todo.json", "sessions.json", "todo-archive.json", ".sequence", ".sequence.json"];
45180
- const foundStale = staleJsonFiles.filter((f) => existsSync67(join70(cleoDir, f)));
45829
+ const foundStale = staleJsonFiles.filter((f) => existsSync68(join71(cleoDir, f)));
45181
45830
  if (foundStale.length > 0) {
45182
45831
  if (isDryRun) {
45183
45832
  actions.push({
@@ -45187,15 +45836,15 @@ async function runUpgrade(options = {}) {
45187
45836
  });
45188
45837
  } else {
45189
45838
  try {
45190
- const backupDir = join70(cleoDir, ".backups", `legacy-json-${Date.now()}`);
45839
+ const backupDir = join71(cleoDir, ".backups", `legacy-json-${Date.now()}`);
45191
45840
  mkdirSync18(backupDir, { recursive: true });
45192
45841
  for (const f of foundStale) {
45193
- const src = join70(cleoDir, f);
45194
- copyFileSync5(src, join70(backupDir, f));
45842
+ const src = join71(cleoDir, f);
45843
+ copyFileSync5(src, join71(backupDir, f));
45195
45844
  }
45196
45845
  const { unlinkSync: unlinkSync5 } = await import("node:fs");
45197
45846
  for (const f of foundStale) {
45198
- unlinkSync5(join70(cleoDir, f));
45847
+ unlinkSync5(join71(cleoDir, f));
45199
45848
  }
45200
45849
  actions.push({
45201
45850
  action: "stale_json_cleanup",
@@ -45215,7 +45864,7 @@ async function runUpgrade(options = {}) {
45215
45864
  if (options.includeGlobal) {
45216
45865
  try {
45217
45866
  const globalDir = getCleoHome();
45218
- const globalPreflight = checkStorageMigration(join70(globalDir, ".."));
45867
+ const globalPreflight = checkStorageMigration(join71(globalDir, ".."));
45219
45868
  if (globalPreflight.migrationNeeded) {
45220
45869
  actions.push({
45221
45870
  action: "global_storage_check",
@@ -45236,8 +45885,8 @@ async function runUpgrade(options = {}) {
45236
45885
  try {
45237
45886
  const projectRoot = getProjectRoot(options.cwd);
45238
45887
  if (isDryRun) {
45239
- const gitignorePath = join70(cleoDir, ".gitignore");
45240
- if (!existsSync67(gitignorePath)) {
45888
+ const gitignorePath = join71(cleoDir, ".gitignore");
45889
+ if (!existsSync68(gitignorePath)) {
45241
45890
  actions.push({ action: "gitignore_integrity", status: "preview", details: "Would create .cleo/.gitignore from template" });
45242
45891
  } else {
45243
45892
  actions.push({ action: "gitignore_integrity", status: "preview", details: "Would verify .cleo/.gitignore matches template" });
@@ -45280,12 +45929,12 @@ async function runUpgrade(options = {}) {
45280
45929
  try {
45281
45930
  const projectRootForContext = getProjectRoot(options.cwd);
45282
45931
  if (isDryRun) {
45283
- const contextPath = join70(cleoDir, "project-context.json");
45284
- if (!existsSync67(contextPath)) {
45932
+ const contextPath = join71(cleoDir, "project-context.json");
45933
+ if (!existsSync68(contextPath)) {
45285
45934
  actions.push({ action: "project_context_detection", status: "preview", details: "Would detect and create project-context.json" });
45286
45935
  } else {
45287
45936
  try {
45288
- const context = JSON.parse(readFileSync48(contextPath, "utf-8"));
45937
+ const context = JSON.parse(readFileSync49(contextPath, "utf-8"));
45289
45938
  if (context.detectedAt) {
45290
45939
  const daysSince = (Date.now() - new Date(context.detectedAt).getTime()) / (1e3 * 60 * 60 * 24);
45291
45940
  if (daysSince > 30) {
@@ -45446,7 +46095,7 @@ async function runUpgrade(options = {}) {
45446
46095
  init_runtime();
45447
46096
  import { readFile as readFile20 } from "node:fs/promises";
45448
46097
  import * as readline from "node:readline";
45449
- import { join as join71 } from "node:path";
46098
+ import { join as join72 } from "node:path";
45450
46099
  import { execFile as execFile7 } from "node:child_process";
45451
46100
  import { promisify as promisify9 } from "node:util";
45452
46101
  init_build_config();
@@ -45455,7 +46104,7 @@ var GITHUB_REPO = BUILD_CONFIG.repository.fullName;
45455
46104
  async function getCurrentVersion() {
45456
46105
  const cleoHome = getCleoHome();
45457
46106
  try {
45458
- const content = await readFile20(join71(cleoHome, "VERSION"), "utf-8");
46107
+ const content = await readFile20(join72(cleoHome, "VERSION"), "utf-8");
45459
46108
  return (content.split("\n")[0] ?? "unknown").trim();
45460
46109
  } catch {
45461
46110
  return "unknown";
@@ -45502,7 +46151,7 @@ async function writeRuntimeVersionMetadata(mode, source, version) {
45502
46151
  `installed=${(/* @__PURE__ */ new Date()).toISOString()}`
45503
46152
  ];
45504
46153
  await import("node:fs/promises").then(
45505
- ({ writeFile: writeFile15, mkdir: mkdir14 }) => mkdir14(cleoHome, { recursive: true }).then(() => writeFile15(join71(cleoHome, "VERSION"), `${lines.join("\n")}
46154
+ ({ writeFile: writeFile15, mkdir: mkdir14 }) => mkdir14(cleoHome, { recursive: true }).then(() => writeFile15(join72(cleoHome, "VERSION"), `${lines.join("\n")}
45506
46155
  `, "utf-8"))
45507
46156
  );
45508
46157
  }
@@ -45861,14 +46510,14 @@ function registerVerifyCommand(program2) {
45861
46510
 
45862
46511
  // src/cli/commands/detect-drift.ts
45863
46512
  init_renderers();
45864
- import { readFileSync as readFileSync49, existsSync as existsSync68, readdirSync as readdirSync20 } from "node:fs";
45865
- import { join as join72, dirname as dirname18 } from "node:path";
46513
+ import { readFileSync as readFileSync50, existsSync as existsSync69, readdirSync as readdirSync20 } from "node:fs";
46514
+ import { join as join73, dirname as dirname18 } from "node:path";
45866
46515
  import { fileURLToPath as fileURLToPath5 } from "node:url";
45867
46516
  function findProjectRoot() {
45868
46517
  const currentFile = fileURLToPath5(import.meta.url);
45869
46518
  let currentDir = dirname18(currentFile);
45870
46519
  while (currentDir !== "/") {
45871
- if (existsSync68(join72(currentDir, "package.json"))) {
46520
+ if (existsSync69(join73(currentDir, "package.json"))) {
45872
46521
  return currentDir;
45873
46522
  }
45874
46523
  const parent = dirname18(currentDir);
@@ -45906,16 +46555,16 @@ function registerDetectDriftCommand(program2) {
45906
46555
  };
45907
46556
  const safeRead = (path) => {
45908
46557
  try {
45909
- return readFileSync49(path, "utf-8");
46558
+ return readFileSync50(path, "utf-8");
45910
46559
  } catch {
45911
46560
  return "";
45912
46561
  }
45913
46562
  };
45914
46563
  try {
45915
- const specPath = join72(projectRoot, "docs", "specs", "CLEO-OPERATIONS-REFERENCE.md");
45916
- const queryPath = join72(projectRoot, "src", "mcp", "gateways", "query.ts");
45917
- const mutatePath = join72(projectRoot, "src", "mcp", "gateways", "mutate.ts");
45918
- if (!existsSync68(specPath)) {
46564
+ const specPath = join73(projectRoot, "docs", "specs", "CLEO-OPERATIONS-REFERENCE.md");
46565
+ const queryPath = join73(projectRoot, "src", "mcp", "gateways", "query.ts");
46566
+ const mutatePath = join73(projectRoot, "src", "mcp", "gateways", "mutate.ts");
46567
+ if (!existsSync69(specPath)) {
45919
46568
  addCheck("Gateway-to-spec sync", "fail", "CLEO-OPERATIONS-REFERENCE.md missing", [{
45920
46569
  severity: "error",
45921
46570
  category: "spec",
@@ -45923,7 +46572,7 @@ function registerDetectDriftCommand(program2) {
45923
46572
  file: specPath,
45924
46573
  recommendation: "Create docs/specs/CLEO-OPERATIONS-REFERENCE.md with canonical operation definitions"
45925
46574
  }]);
45926
- } else if (!existsSync68(queryPath) || !existsSync68(mutatePath)) {
46575
+ } else if (!existsSync69(queryPath) || !existsSync69(mutatePath)) {
45927
46576
  addCheck("Gateway-to-spec sync", "fail", "MCP gateway files missing", [{
45928
46577
  severity: "error",
45929
46578
  category: "implementation",
@@ -45977,16 +46626,16 @@ function registerDetectDriftCommand(program2) {
45977
46626
  }]);
45978
46627
  }
45979
46628
  try {
45980
- const cliDir = join72(projectRoot, "src", "cli", "commands");
45981
- const coreDir = join72(projectRoot, "src", "core");
45982
- if (!existsSync68(cliDir)) {
46629
+ const cliDir = join73(projectRoot, "src", "cli", "commands");
46630
+ const coreDir = join73(projectRoot, "src", "core");
46631
+ if (!existsSync69(cliDir)) {
45983
46632
  addCheck("CLI-to-core sync", "fail", "CLI commands directory missing", [{
45984
46633
  severity: "error",
45985
46634
  category: "structure",
45986
46635
  message: "src/cli/commands/ directory not found",
45987
46636
  recommendation: "Verify TypeScript source structure is intact"
45988
46637
  }]);
45989
- } else if (!existsSync68(coreDir)) {
46638
+ } else if (!existsSync69(coreDir)) {
45990
46639
  addCheck("CLI-to-core sync", "fail", "Core directory missing", [{
45991
46640
  severity: "error",
45992
46641
  category: "structure",
@@ -46001,8 +46650,8 @@ function registerDetectDriftCommand(program2) {
46001
46650
  addCheck("CLI-to-core sync", "fail", `Error: ${e.message}`);
46002
46651
  }
46003
46652
  try {
46004
- const domainsDir = join72(projectRoot, "src", "mcp", "domains");
46005
- if (!existsSync68(domainsDir)) {
46653
+ const domainsDir = join73(projectRoot, "src", "mcp", "domains");
46654
+ if (!existsSync69(domainsDir)) {
46006
46655
  addCheck("Domain handler coverage", "fail", "MCP domains directory missing", [{
46007
46656
  severity: "error",
46008
46657
  category: "structure",
@@ -46017,8 +46666,8 @@ function registerDetectDriftCommand(program2) {
46017
46666
  addCheck("Domain handler coverage", "fail", `Error: ${e.message}`);
46018
46667
  }
46019
46668
  try {
46020
- const matrixPath = join72(projectRoot, "src", "dispatch", "lib", "capability-matrix.ts");
46021
- if (!existsSync68(matrixPath)) {
46669
+ const matrixPath = join73(projectRoot, "src", "dispatch", "lib", "capability-matrix.ts");
46670
+ if (!existsSync69(matrixPath)) {
46022
46671
  addCheck("Capability matrix", "fail", "Capability matrix missing", [{
46023
46672
  severity: "error",
46024
46673
  category: "configuration",
@@ -46032,8 +46681,8 @@ function registerDetectDriftCommand(program2) {
46032
46681
  addCheck("Capability matrix", "fail", `Error: ${e.message}`);
46033
46682
  }
46034
46683
  try {
46035
- const schemaPath = join72(projectRoot, "src", "store", "schema.ts");
46036
- if (!existsSync68(schemaPath)) {
46684
+ const schemaPath = join73(projectRoot, "src", "store", "schema.ts");
46685
+ if (!existsSync69(schemaPath)) {
46037
46686
  addCheck("Schema validation", "fail", "Schema definition missing", [{
46038
46687
  severity: "error",
46039
46688
  category: "data-model",
@@ -46058,10 +46707,10 @@ function registerDetectDriftCommand(program2) {
46058
46707
  addCheck("Schema validation", "fail", `Error: ${e.message}`);
46059
46708
  }
46060
46709
  try {
46061
- const visionPath = join72(projectRoot, "docs", "concepts", "CLEO-VISION.md");
46062
- const specPath = join72(projectRoot, "docs", "specs", "PORTABLE-BRAIN-SPEC.md");
46710
+ const visionPath = join73(projectRoot, "docs", "concepts", "CLEO-VISION.md");
46711
+ const specPath = join73(projectRoot, "docs", "specs", "PORTABLE-BRAIN-SPEC.md");
46063
46712
  const issues = [];
46064
- if (!existsSync68(visionPath)) {
46713
+ if (!existsSync69(visionPath)) {
46065
46714
  issues.push({
46066
46715
  severity: "error",
46067
46716
  category: "vision",
@@ -46070,7 +46719,7 @@ function registerDetectDriftCommand(program2) {
46070
46719
  recommendation: "Create docs/concepts/CLEO-VISION.md with project vision"
46071
46720
  });
46072
46721
  }
46073
- if (!existsSync68(specPath)) {
46722
+ if (!existsSync69(specPath)) {
46074
46723
  issues.push({
46075
46724
  severity: "error",
46076
46725
  category: "spec",
@@ -46107,8 +46756,8 @@ function registerDetectDriftCommand(program2) {
46107
46756
  addCheck("Canonical identity", "fail", `Error: ${e.message}`);
46108
46757
  }
46109
46758
  try {
46110
- const injectionPath = join72(projectRoot, ".cleo", "templates", "CLEO-INJECTION.md");
46111
- if (!existsSync68(injectionPath)) {
46759
+ const injectionPath = join73(projectRoot, ".cleo", "templates", "CLEO-INJECTION.md");
46760
+ if (!existsSync69(injectionPath)) {
46112
46761
  addCheck("Agent injection", "fail", "Agent injection template missing", [{
46113
46762
  severity: "error",
46114
46763
  category: "agent-support",
@@ -46133,8 +46782,8 @@ function registerDetectDriftCommand(program2) {
46133
46782
  addCheck("Agent injection", "fail", `Error: ${e.message}`);
46134
46783
  }
46135
46784
  try {
46136
- const exitCodesPath = join72(projectRoot, "src", "types", "exit-codes.ts");
46137
- if (!existsSync68(exitCodesPath)) {
46785
+ const exitCodesPath = join73(projectRoot, "src", "types", "exit-codes.ts");
46786
+ if (!existsSync69(exitCodesPath)) {
46138
46787
  addCheck("Exit codes", "fail", "Exit codes definition missing", [{
46139
46788
  severity: "error",
46140
46789
  category: "protocol",
@@ -46373,8 +47022,8 @@ function registerRemoteCommand(program2) {
46373
47022
  init_renderers();
46374
47023
  init_paths();
46375
47024
  import { mkdir as mkdir13, writeFile as writeFile14, readFile as readFile21 } from "node:fs/promises";
46376
- import { existsSync as existsSync69, readFileSync as readFileSync50 } from "node:fs";
46377
- import { join as join73, resolve as resolve10, dirname as dirname19 } from "node:path";
47025
+ import { existsSync as existsSync70, readFileSync as readFileSync51 } from "node:fs";
47026
+ import { join as join74, resolve as resolve10, dirname as dirname19 } from "node:path";
46378
47027
  import { homedir as homedir4 } from "node:os";
46379
47028
  import { fileURLToPath as fileURLToPath6 } from "node:url";
46380
47029
  function registerInstallGlobalCommand(program2) {
@@ -46384,17 +47033,17 @@ function registerInstallGlobalCommand(program2) {
46384
47033
  const warnings = [];
46385
47034
  try {
46386
47035
  const cleoHome = getCleoHome();
46387
- const globalTemplatesDir = join73(cleoHome, "templates");
47036
+ const globalTemplatesDir = join74(cleoHome, "templates");
46388
47037
  if (!isDryRun) {
46389
47038
  await mkdir13(globalTemplatesDir, { recursive: true });
46390
47039
  }
46391
47040
  try {
46392
47041
  const thisFile = fileURLToPath6(import.meta.url);
46393
47042
  const packageRoot = resolve10(dirname19(thisFile), "..", "..", "..");
46394
- const templatePath = join73(packageRoot, "templates", "CLEO-INJECTION.md");
46395
- if (existsSync69(templatePath)) {
46396
- const content = readFileSync50(templatePath, "utf-8");
46397
- const globalPath = join73(globalTemplatesDir, "CLEO-INJECTION.md");
47043
+ const templatePath = join74(packageRoot, "templates", "CLEO-INJECTION.md");
47044
+ if (existsSync70(templatePath)) {
47045
+ const content = readFileSync51(templatePath, "utf-8");
47046
+ const globalPath = join74(globalTemplatesDir, "CLEO-INJECTION.md");
46398
47047
  if (!isDryRun) {
46399
47048
  await writeFile14(globalPath, content);
46400
47049
  }
@@ -46404,12 +47053,12 @@ function registerInstallGlobalCommand(program2) {
46404
47053
  warnings.push("Could not refresh CLEO-INJECTION.md template");
46405
47054
  }
46406
47055
  const globalAgentsDir = getAgentsHome();
46407
- const globalAgentsMd = join73(globalAgentsDir, "AGENTS.md");
47056
+ const globalAgentsMd = join74(globalAgentsDir, "AGENTS.md");
46408
47057
  try {
46409
47058
  const { inject, getInstalledProviders: getInstalledProviders3, injectAll: injectAll2, buildInjectionContent: buildInjectionContent2 } = await import("@cleocode/caamp");
46410
47059
  if (!isDryRun) {
46411
47060
  await mkdir13(globalAgentsDir, { recursive: true });
46412
- if (existsSync69(globalAgentsMd)) {
47061
+ if (existsSync70(globalAgentsMd)) {
46413
47062
  const content = await readFile21(globalAgentsMd, "utf8");
46414
47063
  const stripped = content.replace(/\n?<!-- CLEO:START -->[\s\S]*?<!-- CLEO:END -->\n?/g, "");
46415
47064
  if (stripped !== content) {
@@ -46428,8 +47077,8 @@ function registerInstallGlobalCommand(program2) {
46428
47077
  const injectionContent = buildInjectionContent2({ references: ["@~/.agents/AGENTS.md"] });
46429
47078
  if (!isDryRun) {
46430
47079
  for (const provider of providers) {
46431
- const instructFilePath = join73(provider.pathGlobal, provider.instructFile);
46432
- if (existsSync69(instructFilePath)) {
47080
+ const instructFilePath = join74(provider.pathGlobal, provider.instructFile);
47081
+ if (existsSync70(instructFilePath)) {
46433
47082
  const fileContent = await readFile21(instructFilePath, "utf8");
46434
47083
  const stripped = fileContent.replace(/\n?<!-- CLEO:START -->[\s\S]*?<!-- CLEO:END -->\n?/g, "");
46435
47084
  if (stripped !== fileContent) {
@@ -46444,7 +47093,7 @@ function registerInstallGlobalCommand(program2) {
46444
47093
  }
46445
47094
  } else {
46446
47095
  for (const p of providers) {
46447
- const displayPath = join73(p.pathGlobal, p.instructFile).replace(homedir4(), "~");
47096
+ const displayPath = join74(p.pathGlobal, p.instructFile).replace(homedir4(), "~");
46448
47097
  created.push(`${displayPath} (would update CAAMP block)`);
46449
47098
  }
46450
47099
  }
@@ -46650,7 +47299,7 @@ init_paths();
46650
47299
  init_paths();
46651
47300
  init_brain_sqlite();
46652
47301
  init_brain_search();
46653
- import { existsSync as existsSync70 } from "node:fs";
47302
+ import { existsSync as existsSync71 } from "node:fs";
46654
47303
  import { createRequire as createRequire5 } from "node:module";
46655
47304
  var _require5 = createRequire5(import.meta.url);
46656
47305
  var { DatabaseSync: DatabaseSync3 } = _require5("node:sqlite");
@@ -46694,7 +47343,7 @@ async function migrateClaudeMem(projectRoot, options = {}) {
46694
47343
  errors: [],
46695
47344
  dryRun
46696
47345
  };
46697
- if (!existsSync70(sourcePath)) {
47346
+ if (!existsSync71(sourcePath)) {
46698
47347
  throw new Error(
46699
47348
  `claude-mem database not found at: ${sourcePath}
46700
47349
  Expected location: ~/.claude-mem/claude-mem.db
@@ -47096,10 +47745,10 @@ init_config();
47096
47745
  // src/cli/logger-bootstrap.ts
47097
47746
  init_logger();
47098
47747
  init_project_info();
47099
- import { join as join74 } from "node:path";
47748
+ import { join as join75 } from "node:path";
47100
47749
  function initCliLogger(cwd, loggingConfig) {
47101
47750
  const projectInfo = getProjectInfoSync(cwd);
47102
- initLogger(join74(cwd, ".cleo"), loggingConfig, projectInfo?.projectHash);
47751
+ initLogger(join75(cwd, ".cleo"), loggingConfig, projectInfo?.projectHash);
47103
47752
  }
47104
47753
 
47105
47754
  // src/cli/index.ts
@@ -47332,8 +47981,8 @@ Upgrade options:
47332
47981
  }
47333
47982
  function getPackageVersion() {
47334
47983
  try {
47335
- const moduleRoot = join75(import.meta.dirname ?? "", "..", "..");
47336
- const pkg = JSON.parse(readFileSync51(join75(moduleRoot, "package.json"), "utf-8"));
47984
+ const moduleRoot = join76(import.meta.dirname ?? "", "..", "..");
47985
+ const pkg = JSON.parse(readFileSync52(join76(moduleRoot, "package.json"), "utf-8"));
47337
47986
  return pkg.version ?? "0.0.0";
47338
47987
  } catch {
47339
47988
  return "0.0.0";
@@ -47444,7 +48093,7 @@ program.hook("preAction", async () => {
47444
48093
  const config = await loadConfig();
47445
48094
  initCliLogger(process.cwd(), config.logging);
47446
48095
  const { pruneAuditLog: pruneAuditLog2 } = await Promise.resolve().then(() => (init_audit_prune(), audit_prune_exports));
47447
- pruneAuditLog2(join75(process.cwd(), ".cleo"), config.logging).catch(() => {
48096
+ pruneAuditLog2(join76(process.cwd(), ".cleo"), config.logging).catch(() => {
47448
48097
  });
47449
48098
  } catch {
47450
48099
  }
@@ -47484,7 +48133,7 @@ program.hook("preAction", (thisCommand) => {
47484
48133
  }
47485
48134
  });
47486
48135
  if (process.argv[2] === "mcp") {
47487
- const mcpPath = join75(import.meta.dirname ?? "", "..", "mcp", "index.js");
48136
+ const mcpPath = join76(import.meta.dirname ?? "", "..", "mcp", "index.js");
47488
48137
  const { spawn: spawn3 } = await import("node:child_process");
47489
48138
  const child = spawn3(
47490
48139
  process.execPath,