@cleocode/cleo 2026.3.16 → 2026.3.18

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
@@ -1803,6 +1803,16 @@ var init_registry = __esm({
1803
1803
  sessionRequired: false,
1804
1804
  requiredParams: ["version"]
1805
1805
  },
1806
+ {
1807
+ gateway: "query",
1808
+ domain: "pipeline",
1809
+ operation: "release.channel.show",
1810
+ description: "Show the current release channel based on git branch (latest/beta/alpha)",
1811
+ tier: 0,
1812
+ idempotent: true,
1813
+ sessionRequired: false,
1814
+ requiredParams: []
1815
+ },
1806
1816
  {
1807
1817
  gateway: "mutate",
1808
1818
  domain: "pipeline",
@@ -1863,6 +1873,16 @@ var init_registry = __esm({
1863
1873
  sessionRequired: false,
1864
1874
  requiredParams: []
1865
1875
  },
1876
+ {
1877
+ gateway: "mutate",
1878
+ domain: "pipeline",
1879
+ operation: "release.cancel",
1880
+ description: "pipeline.release.cancel (mutate)",
1881
+ tier: 0,
1882
+ idempotent: false,
1883
+ sessionRequired: false,
1884
+ requiredParams: ["version"]
1885
+ },
1866
1886
  {
1867
1887
  gateway: "mutate",
1868
1888
  domain: "pipeline",
@@ -4626,13 +4646,13 @@ async function getDb(cwd) {
4626
4646
  if (!_gitTrackingChecked) {
4627
4647
  _gitTrackingChecked = true;
4628
4648
  try {
4629
- const { execFileSync: execFileSync13 } = await import("node:child_process");
4649
+ const { execFileSync: execFileSync15 } = await import("node:child_process");
4630
4650
  const gitCwd = resolve3(dbPath, "..", "..");
4631
4651
  const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
4632
4652
  const log7 = getLogger("sqlite");
4633
4653
  for (const fileToCheck of filesToCheck) {
4634
4654
  try {
4635
- execFileSync13("git", ["ls-files", "--error-unmatch", fileToCheck], {
4655
+ execFileSync15("git", ["ls-files", "--error-unmatch", fileToCheck], {
4636
4656
  cwd: gitCwd,
4637
4657
  stdio: "pipe"
4638
4658
  });
@@ -15396,6 +15416,7 @@ async function getProjectStats(opts, accessor) {
15396
15416
  const active = tasks2.filter((t) => t.status === "active").length;
15397
15417
  const done = tasks2.filter((t) => t.status === "done").length;
15398
15418
  const blocked = tasks2.filter((t) => t.status === "blocked").length;
15419
+ const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
15399
15420
  const totalActive = tasks2.length;
15400
15421
  const cutoff = new Date(Date.now() - periodDays * 864e5).toISOString();
15401
15422
  const entries = await queryAuditEntries(opts.cwd);
@@ -15412,11 +15433,45 @@ async function getProjectStats(opts, accessor) {
15412
15433
  (e) => isArchive(e) && e.timestamp >= cutoff
15413
15434
  ).length;
15414
15435
  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;
15436
+ let totalCreated = 0;
15437
+ let totalCompleted = 0;
15438
+ let totalCancelled = 0;
15439
+ let totalArchived = 0;
15440
+ let archivedCompleted = 0;
15441
+ let archivedCount = 0;
15442
+ try {
15443
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
15444
+ const { count: dbCount, eq: dbEq, and: dbAnd } = await import("drizzle-orm");
15445
+ const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
15446
+ const db = await getDb2(opts.cwd);
15447
+ const statusRows = await db.select({ status: tasksTable.status, c: dbCount() }).from(tasksTable).groupBy(tasksTable.status).all();
15448
+ const statusMap = {};
15449
+ for (const row of statusRows) {
15450
+ statusMap[row.status] = row.c;
15451
+ }
15452
+ archivedCount = statusMap["archived"] ?? 0;
15453
+ totalCreated = Object.values(statusMap).reduce((sum, n) => sum + n, 0);
15454
+ totalCancelled = statusMap["cancelled"] ?? 0;
15455
+ totalArchived = archivedCount;
15456
+ const archivedDoneRow = await db.select({ c: dbCount() }).from(tasksTable).where(dbAnd(dbEq(tasksTable.status, "archived"), dbEq(tasksTable.archiveReason, "completed"))).get();
15457
+ archivedCompleted = archivedDoneRow?.c ?? 0;
15458
+ totalCompleted = (statusMap["done"] ?? 0) + archivedCompleted;
15459
+ } catch {
15460
+ totalCreated = entries.filter(isCreate).length;
15461
+ totalCompleted = entries.filter(isComplete).length;
15462
+ totalArchived = entries.filter(isArchive).length;
15463
+ }
15418
15464
  return {
15419
- currentState: { pending, active, done, blocked, totalActive },
15465
+ currentState: {
15466
+ pending,
15467
+ active,
15468
+ done,
15469
+ blocked,
15470
+ cancelled,
15471
+ totalActive,
15472
+ archived: archivedCount,
15473
+ grandTotal: totalActive + archivedCount
15474
+ },
15420
15475
  completionMetrics: {
15421
15476
  periodDays,
15422
15477
  completedInPeriod,
@@ -15428,7 +15483,7 @@ async function getProjectStats(opts, accessor) {
15428
15483
  completedInPeriod,
15429
15484
  archivedInPeriod
15430
15485
  },
15431
- allTime: { totalCreated, totalCompleted, totalArchived }
15486
+ allTime: { totalCreated, totalCompleted, totalCancelled, totalArchived, archivedCompleted }
15432
15487
  };
15433
15488
  }
15434
15489
  function rankBlockedTask(task, allTasks, focusTask) {
@@ -15472,7 +15527,18 @@ async function getDashboard(opts, accessor) {
15472
15527
  const active = tasks2.filter((t) => t.status === "active").length;
15473
15528
  const done = tasks2.filter((t) => t.status === "done").length;
15474
15529
  const blocked = tasks2.filter((t) => t.status === "blocked").length;
15530
+ const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
15475
15531
  const total = tasks2.length;
15532
+ let archived = 0;
15533
+ try {
15534
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
15535
+ const { count: dbCount, eq: dbEq } = await import("drizzle-orm");
15536
+ const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
15537
+ const db = await getDb2(opts.cwd);
15538
+ const row = await db.select({ c: dbCount() }).from(tasksTable).where(dbEq(tasksTable.status, "archived")).get();
15539
+ archived = row?.c ?? 0;
15540
+ } catch {
15541
+ }
15476
15542
  const project = data.project?.name ?? "Unknown Project";
15477
15543
  const currentPhase = data.project?.currentPhase ?? null;
15478
15544
  const focusId = data.focus?.currentTask ?? null;
@@ -15480,7 +15546,7 @@ async function getDashboard(opts, accessor) {
15480
15546
  if (focusId) {
15481
15547
  focusTask = tasks2.find((t) => t.id === focusId) ?? null;
15482
15548
  }
15483
- const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done").sort((a, b) => {
15549
+ const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done" && t.status !== "cancelled").sort((a, b) => {
15484
15550
  const pDiff = (PRIORITY_ORDER[a.priority ?? "low"] ?? 9) - (PRIORITY_ORDER[b.priority ?? "low"] ?? 9);
15485
15551
  if (pDiff !== 0) return pDiff;
15486
15552
  return (a.createdAt ?? "").localeCompare(b.createdAt ?? "");
@@ -15493,6 +15559,7 @@ async function getDashboard(opts, accessor) {
15493
15559
  }).map((r) => r.task);
15494
15560
  const labelMap = {};
15495
15561
  for (const t of tasks2) {
15562
+ if (t.status === "cancelled") continue;
15496
15563
  for (const label of t.labels ?? []) {
15497
15564
  labelMap[label] = (labelMap[label] ?? 0) + 1;
15498
15565
  }
@@ -15501,7 +15568,7 @@ async function getDashboard(opts, accessor) {
15501
15568
  return {
15502
15569
  project,
15503
15570
  currentPhase,
15504
- summary: { pending, active, blocked, done, total },
15571
+ summary: { pending, active, blocked, done, cancelled, total, archived, grandTotal: total + archived },
15505
15572
  focus: { currentTask: focusId, task: focusTask },
15506
15573
  highPriority: { count: highPriority.length, tasks: highPriority.slice(0, 5) },
15507
15574
  blockedTasks: {
@@ -19553,7 +19620,9 @@ async function systemDash(projectRoot, params) {
19553
19620
  blocked: summary.blocked,
19554
19621
  done: summary.done,
19555
19622
  cancelled: summary.cancelled ?? 0,
19556
- total: summary.total
19623
+ total: summary.total,
19624
+ archived: summary.archived ?? 0,
19625
+ grandTotal: summary.grandTotal ?? summary.total
19557
19626
  },
19558
19627
  taskWork: data.focus ?? data.taskWork,
19559
19628
  activeSession: data.activeSession ?? null,
@@ -19573,17 +19642,18 @@ async function systemStats(projectRoot, params) {
19573
19642
  const result = await getProjectStats({ period: String(params?.period ?? 30), cwd: projectRoot }, accessor);
19574
19643
  const taskData = await accessor.loadTaskFile();
19575
19644
  const tasks2 = taskData?.tasks ?? [];
19645
+ const activeTasks = tasks2.filter((t) => t.status !== "cancelled");
19576
19646
  const byPriority = {};
19577
- for (const t of tasks2) {
19647
+ for (const t of activeTasks) {
19578
19648
  byPriority[t.priority] = (byPriority[t.priority] ?? 0) + 1;
19579
19649
  }
19580
19650
  const byType = {};
19581
- for (const t of tasks2) {
19651
+ for (const t of activeTasks) {
19582
19652
  const type = t.type || "task";
19583
19653
  byType[type] = (byType[type] ?? 0) + 1;
19584
19654
  }
19585
19655
  const byPhase = {};
19586
- for (const t of tasks2) {
19656
+ for (const t of activeTasks) {
19587
19657
  const phase = t.phase || "unassigned";
19588
19658
  byPhase[phase] = (byPhase[phase] ?? 0) + 1;
19589
19659
  }
@@ -19613,7 +19683,9 @@ async function systemStats(projectRoot, params) {
19613
19683
  done: currentState.done,
19614
19684
  blocked: currentState.blocked,
19615
19685
  cancelled: tasks2.filter((t) => t.status === "cancelled").length,
19616
- totalActive: currentState.totalActive
19686
+ totalActive: currentState.totalActive,
19687
+ archived: currentState.archived ?? 0,
19688
+ grandTotal: currentState.grandTotal ?? currentState.totalActive
19617
19689
  },
19618
19690
  byPriority,
19619
19691
  byType,
@@ -19638,10 +19710,10 @@ async function systemLog(projectRoot, filters) {
19638
19710
  }
19639
19711
  async function queryAuditLogSqlite(projectRoot, filters) {
19640
19712
  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)) {
19713
+ const { join: join78 } = await import("node:path");
19714
+ const { existsSync: existsSync73 } = await import("node:fs");
19715
+ const dbPath = join78(projectRoot, ".cleo", "tasks.db");
19716
+ if (!existsSync73(dbPath)) {
19645
19717
  const offset = filters?.offset ?? 0;
19646
19718
  const limit = filters?.limit ?? 20;
19647
19719
  return {
@@ -20497,10 +20569,10 @@ async function readProjectMeta(projectPath) {
20497
20569
  }
20498
20570
  async function readProjectId(projectPath) {
20499
20571
  try {
20500
- const { readFileSync: readFileSync52, existsSync: existsSync71 } = await import("node:fs");
20572
+ const { readFileSync: readFileSync54, existsSync: existsSync73 } = await import("node:fs");
20501
20573
  const infoPath = join33(projectPath, ".cleo", "project-info.json");
20502
- if (!existsSync71(infoPath)) return "";
20503
- const data = JSON.parse(readFileSync52(infoPath, "utf-8"));
20574
+ if (!existsSync73(infoPath)) return "";
20575
+ const data = JSON.parse(readFileSync54(infoPath, "utf-8"));
20504
20576
  return typeof data.projectId === "string" ? data.projectId : "";
20505
20577
  } catch {
20506
20578
  return "";
@@ -27978,11 +28050,353 @@ var init_changelog_writer = __esm({
27978
28050
  }
27979
28051
  });
27980
28052
 
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";
28053
+ // src/core/release/github-pr.ts
27984
28054
  import { execFileSync as execFileSync6 } from "node:child_process";
28055
+ function isGhCliAvailable() {
28056
+ try {
28057
+ execFileSync6("gh", ["--version"], { stdio: "pipe" });
28058
+ return true;
28059
+ } catch {
28060
+ return false;
28061
+ }
28062
+ }
28063
+ function extractRepoOwnerAndName(remote) {
28064
+ const trimmed = remote.trim();
28065
+ const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
28066
+ if (httpsMatch) {
28067
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
28068
+ }
28069
+ const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
28070
+ if (sshMatch) {
28071
+ return { owner: sshMatch[1], repo: sshMatch[2] };
28072
+ }
28073
+ return null;
28074
+ }
28075
+ async function detectBranchProtection(branch, remote, projectRoot) {
28076
+ const cwdOpts = projectRoot ? { cwd: projectRoot } : {};
28077
+ if (isGhCliAvailable()) {
28078
+ try {
28079
+ const remoteUrl = execFileSync6("git", ["remote", "get-url", remote], {
28080
+ encoding: "utf-8",
28081
+ stdio: "pipe",
28082
+ ...cwdOpts
28083
+ }).trim();
28084
+ const identity = extractRepoOwnerAndName(remoteUrl);
28085
+ if (identity) {
28086
+ const { owner, repo } = identity;
28087
+ try {
28088
+ execFileSync6(
28089
+ "gh",
28090
+ ["api", `/repos/${owner}/${repo}/branches/${branch}/protection`],
28091
+ {
28092
+ encoding: "utf-8",
28093
+ stdio: "pipe",
28094
+ ...cwdOpts
28095
+ }
28096
+ );
28097
+ return { protected: true, detectionMethod: "gh-api" };
28098
+ } catch (apiErr) {
28099
+ const stderr2 = apiErr instanceof Error && "stderr" in apiErr ? String(apiErr.stderr ?? "") : "";
28100
+ if (stderr2.includes("404") || stderr2.includes("Not Found")) {
28101
+ return { protected: false, detectionMethod: "gh-api" };
28102
+ }
28103
+ }
28104
+ }
28105
+ } catch {
28106
+ }
28107
+ }
28108
+ try {
28109
+ const result = execFileSync6(
28110
+ "git",
28111
+ ["push", "--dry-run", remote, `HEAD:${branch}`],
28112
+ {
28113
+ encoding: "utf-8",
28114
+ stdio: "pipe",
28115
+ ...cwdOpts
28116
+ }
28117
+ );
28118
+ const output = typeof result === "string" ? result : "";
28119
+ if (output.includes("protected branch") || output.includes("GH006") || output.includes("refusing to allow")) {
28120
+ return { protected: true, detectionMethod: "push-dry-run" };
28121
+ }
28122
+ return { protected: false, detectionMethod: "push-dry-run" };
28123
+ } catch (pushErr) {
28124
+ const stderr2 = pushErr instanceof Error && "stderr" in pushErr ? String(pushErr.stderr ?? "") : pushErr instanceof Error ? pushErr.message : String(pushErr);
28125
+ if (stderr2.includes("protected branch") || stderr2.includes("GH006") || stderr2.includes("refusing to allow")) {
28126
+ return { protected: true, detectionMethod: "push-dry-run" };
28127
+ }
28128
+ return {
28129
+ protected: false,
28130
+ detectionMethod: "unknown",
28131
+ error: stderr2
28132
+ };
28133
+ }
28134
+ }
28135
+ function buildPRBody(opts) {
28136
+ const epicLine = opts.epicId ? `**Epic**: ${opts.epicId}
28137
+
28138
+ ` : "";
28139
+ return [
28140
+ `## Release v${opts.version}`,
28141
+ "",
28142
+ `${epicLine}This PR merges the ${opts.head} branch into ${opts.base} to publish the release.`,
28143
+ "",
28144
+ "### Checklist",
28145
+ "- [ ] CHANGELOG.md updated",
28146
+ "- [ ] All release tasks complete",
28147
+ "- [ ] Version bump committed",
28148
+ "",
28149
+ "---",
28150
+ "*Created by CLEO release pipeline*"
28151
+ ].join("\n");
28152
+ }
28153
+ function formatManualPRInstructions(opts) {
28154
+ const epicSuffix = opts.epicId ? ` (${opts.epicId})` : "";
28155
+ return [
28156
+ "Branch protection detected or gh CLI unavailable. Create the PR manually:",
28157
+ "",
28158
+ ` gh pr create \\`,
28159
+ ` --base ${opts.base} \\`,
28160
+ ` --head ${opts.head} \\`,
28161
+ ` --title "${opts.title}" \\`,
28162
+ ` --body "Release v${opts.version}${epicSuffix}"`,
28163
+ "",
28164
+ `Or visit: https://github.com/[owner]/[repo]/compare/${opts.base}...${opts.head}`,
28165
+ "",
28166
+ "After merging, CI will automatically publish to npm."
28167
+ ].join("\n");
28168
+ }
28169
+ async function createPullRequest(opts) {
28170
+ if (!isGhCliAvailable()) {
28171
+ return {
28172
+ mode: "manual",
28173
+ instructions: formatManualPRInstructions(opts)
28174
+ };
28175
+ }
28176
+ const body = buildPRBody(opts);
28177
+ const args = [
28178
+ "pr",
28179
+ "create",
28180
+ "--base",
28181
+ opts.base,
28182
+ "--head",
28183
+ opts.head,
28184
+ "--title",
28185
+ opts.title,
28186
+ "--body",
28187
+ body
28188
+ ];
28189
+ if (opts.labels && opts.labels.length > 0) {
28190
+ for (const label of opts.labels) {
28191
+ args.push("--label", label);
28192
+ }
28193
+ }
28194
+ try {
28195
+ const output = execFileSync6("gh", args, {
28196
+ encoding: "utf-8",
28197
+ stdio: "pipe",
28198
+ ...opts.projectRoot ? { cwd: opts.projectRoot } : {}
28199
+ });
28200
+ const prUrl = output.trim();
28201
+ const numberMatch = prUrl.match(/\/pull\/(\d+)$/);
28202
+ const prNumber = numberMatch ? parseInt(numberMatch[1], 10) : void 0;
28203
+ return {
28204
+ mode: "created",
28205
+ prUrl,
28206
+ prNumber
28207
+ };
28208
+ } catch (err) {
28209
+ const stderr2 = err instanceof Error && "stderr" in err ? String(err.stderr ?? "") : err instanceof Error ? err.message : String(err);
28210
+ if (stderr2.includes("already exists")) {
28211
+ const urlMatch = stderr2.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
28212
+ const existingUrl = urlMatch ? urlMatch[0] : void 0;
28213
+ return {
28214
+ mode: "skipped",
28215
+ prUrl: existingUrl,
28216
+ instructions: "PR already exists"
28217
+ };
28218
+ }
28219
+ return {
28220
+ mode: "manual",
28221
+ instructions: formatManualPRInstructions(opts),
28222
+ error: stderr2
28223
+ };
28224
+ }
28225
+ }
28226
+ var init_github_pr = __esm({
28227
+ "src/core/release/github-pr.ts"() {
28228
+ "use strict";
28229
+ }
28230
+ });
28231
+
28232
+ // src/core/release/channel.ts
28233
+ function getDefaultChannelConfig() {
28234
+ return {
28235
+ main: "main",
28236
+ develop: "develop",
28237
+ feature: "feature/"
28238
+ };
28239
+ }
28240
+ function resolveChannelFromBranch(branch, config) {
28241
+ const cfg = config ?? getDefaultChannelConfig();
28242
+ if (cfg.custom) {
28243
+ if (Object.prototype.hasOwnProperty.call(cfg.custom, branch)) {
28244
+ return cfg.custom[branch];
28245
+ }
28246
+ let bestPrefix = "";
28247
+ let bestChannel;
28248
+ for (const [key, channel] of Object.entries(cfg.custom)) {
28249
+ if (branch.startsWith(key) && key.length > bestPrefix.length) {
28250
+ bestPrefix = key;
28251
+ bestChannel = channel;
28252
+ }
28253
+ }
28254
+ if (bestChannel !== void 0) {
28255
+ return bestChannel;
28256
+ }
28257
+ }
28258
+ if (branch === cfg.main) {
28259
+ return "latest";
28260
+ }
28261
+ if (branch === cfg.develop) {
28262
+ return "beta";
28263
+ }
28264
+ const alphaPrefixes = ["feature/", "hotfix/", "release/"];
28265
+ if (cfg.feature && !alphaPrefixes.includes(cfg.feature)) {
28266
+ alphaPrefixes.push(cfg.feature);
28267
+ }
28268
+ for (const prefix of alphaPrefixes) {
28269
+ if (branch.startsWith(prefix)) {
28270
+ return "alpha";
28271
+ }
28272
+ }
28273
+ return "alpha";
28274
+ }
28275
+ function channelToDistTag(channel) {
28276
+ const tags = {
28277
+ latest: "latest",
28278
+ beta: "beta",
28279
+ alpha: "alpha"
28280
+ };
28281
+ return tags[channel];
28282
+ }
28283
+ function describeChannel(channel) {
28284
+ const descriptions = {
28285
+ latest: "stable release published to npm @latest",
28286
+ beta: "pre-release published to npm @beta (develop branch)",
28287
+ alpha: "early pre-release published to npm @alpha (feature/hotfix branches)"
28288
+ };
28289
+ return descriptions[channel];
28290
+ }
28291
+ var init_channel = __esm({
28292
+ "src/core/release/channel.ts"() {
28293
+ "use strict";
28294
+ }
28295
+ });
28296
+
28297
+ // src/core/release/release-config.ts
28298
+ import { existsSync as existsSync48, readFileSync as readFileSync38 } from "node:fs";
27985
28299
  import { join as join46 } from "node:path";
28300
+ function readConfigValueSync(path, defaultValue, cwd) {
28301
+ try {
28302
+ const configPath = join46(getCleoDir(cwd), "config.json");
28303
+ if (!existsSync48(configPath)) return defaultValue;
28304
+ const config = JSON.parse(readFileSync38(configPath, "utf-8"));
28305
+ const keys = path.split(".");
28306
+ let value = config;
28307
+ for (const key of keys) {
28308
+ if (value == null || typeof value !== "object") return defaultValue;
28309
+ value = value[key];
28310
+ }
28311
+ return value ?? defaultValue;
28312
+ } catch {
28313
+ return defaultValue;
28314
+ }
28315
+ }
28316
+ function loadReleaseConfig(cwd) {
28317
+ return {
28318
+ versioningScheme: readConfigValueSync("release.versioning.scheme", DEFAULTS2.versioningScheme, cwd),
28319
+ tagPrefix: readConfigValueSync("release.versioning.tagPrefix", DEFAULTS2.tagPrefix, cwd),
28320
+ changelogFormat: readConfigValueSync("release.changelog.format", DEFAULTS2.changelogFormat, cwd),
28321
+ changelogFile: readConfigValueSync("release.changelog.file", DEFAULTS2.changelogFile, cwd),
28322
+ artifactType: readConfigValueSync("release.artifact.type", DEFAULTS2.artifactType, cwd),
28323
+ gates: readConfigValueSync("release.gates", [], cwd),
28324
+ versionBump: {
28325
+ files: readConfigValueSync("release.versionBump.files", [], cwd)
28326
+ },
28327
+ security: {
28328
+ enableProvenance: readConfigValueSync("release.security.enableProvenance", false, cwd),
28329
+ slsaLevel: readConfigValueSync("release.security.slsaLevel", 3, cwd),
28330
+ requireSignedCommits: readConfigValueSync("release.security.requireSignedCommits", false, cwd)
28331
+ }
28332
+ };
28333
+ }
28334
+ function getDefaultGitFlowConfig() {
28335
+ return {
28336
+ enabled: true,
28337
+ branches: {
28338
+ main: "main",
28339
+ develop: "develop",
28340
+ featurePrefix: "feature/",
28341
+ hotfixPrefix: "hotfix/",
28342
+ releasePrefix: "release/"
28343
+ }
28344
+ };
28345
+ }
28346
+ function getGitFlowConfig(config) {
28347
+ const defaults = getDefaultGitFlowConfig();
28348
+ if (!config.gitflow) return defaults;
28349
+ return {
28350
+ enabled: config.gitflow.enabled ?? defaults.enabled,
28351
+ branches: {
28352
+ main: config.gitflow.branches?.main ?? defaults.branches.main,
28353
+ develop: config.gitflow.branches?.develop ?? defaults.branches.develop,
28354
+ featurePrefix: config.gitflow.branches?.featurePrefix ?? defaults.branches.featurePrefix,
28355
+ hotfixPrefix: config.gitflow.branches?.hotfixPrefix ?? defaults.branches.hotfixPrefix,
28356
+ releasePrefix: config.gitflow.branches?.releasePrefix ?? defaults.branches.releasePrefix
28357
+ }
28358
+ };
28359
+ }
28360
+ function getDefaultChannelConfig2() {
28361
+ return {
28362
+ main: "latest",
28363
+ develop: "beta",
28364
+ feature: "alpha"
28365
+ };
28366
+ }
28367
+ function getChannelConfig(config) {
28368
+ const defaults = getDefaultChannelConfig2();
28369
+ if (!config.channels) return defaults;
28370
+ return {
28371
+ main: config.channels.main ?? defaults.main,
28372
+ develop: config.channels.develop ?? defaults.develop,
28373
+ feature: config.channels.feature ?? defaults.feature,
28374
+ custom: config.channels.custom
28375
+ };
28376
+ }
28377
+ function getPushMode(config) {
28378
+ return config.push?.mode ?? "auto";
28379
+ }
28380
+ var DEFAULTS2;
28381
+ var init_release_config = __esm({
28382
+ "src/core/release/release-config.ts"() {
28383
+ "use strict";
28384
+ init_paths();
28385
+ DEFAULTS2 = {
28386
+ versioningScheme: "calver",
28387
+ tagPrefix: "v",
28388
+ changelogFormat: "keepachangelog",
28389
+ changelogFile: "CHANGELOG.md",
28390
+ artifactType: "generic-tarball"
28391
+ };
28392
+ }
28393
+ });
28394
+
28395
+ // src/core/release/release-manifest.ts
28396
+ import { existsSync as existsSync49, renameSync as renameSync7 } from "node:fs";
28397
+ import { readFile as readFile10 } from "node:fs/promises";
28398
+ import { execFileSync as execFileSync7 } from "node:child_process";
28399
+ import { join as join47 } from "node:path";
27986
28400
  import { eq as eq14, desc as desc4 } from "drizzle-orm";
27987
28401
  function isValidVersion(version) {
27988
28402
  return /^v?\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/.test(version);
@@ -28074,25 +28488,78 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28074
28488
  const chores = [];
28075
28489
  const docs = [];
28076
28490
  const tests = [];
28077
- const other = [];
28491
+ const changes = [];
28492
+ function stripConventionalPrefix(title) {
28493
+ return title.replace(/^(feat|fix|docs?|test|chore|refactor|style|ci|build|perf)(\([^)]+\))?:\s*/i, "");
28494
+ }
28495
+ function capitalize(s) {
28496
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
28497
+ }
28498
+ function buildEntry(task) {
28499
+ const cleanTitle = capitalize(stripConventionalPrefix(task.title));
28500
+ const safeDesc = task.description?.replace(/\r?\n/g, " ").replace(/\s{2,}/g, " ").trim();
28501
+ const desc6 = safeDesc;
28502
+ const shouldIncludeDesc = (() => {
28503
+ if (!desc6 || desc6.length === 0) return false;
28504
+ const titleNorm = cleanTitle.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
28505
+ const descNorm = desc6.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
28506
+ if (titleNorm === descNorm) return false;
28507
+ if (descNorm.startsWith(titleNorm) && descNorm.length < titleNorm.length * 1.3) return false;
28508
+ return desc6.length >= 20;
28509
+ })();
28510
+ if (shouldIncludeDesc) {
28511
+ const descDisplay = desc6.length > 150 ? desc6.slice(0, 147) + "..." : desc6;
28512
+ return `- **${cleanTitle}**: ${descDisplay} (${task.id})`;
28513
+ }
28514
+ return `- ${cleanTitle} (${task.id})`;
28515
+ }
28516
+ function categorizeTask(task) {
28517
+ if (task.type === "epic") return "changes";
28518
+ const taskType = (task.type ?? "").toLowerCase();
28519
+ if (taskType === "test") return "tests";
28520
+ if (taskType === "fix" || taskType === "bugfix") return "fixes";
28521
+ if (taskType === "feat" || taskType === "feature") return "features";
28522
+ if (taskType === "docs" || taskType === "doc") return "docs";
28523
+ if (taskType === "chore" || taskType === "refactor") return "chores";
28524
+ if (/^feat(\([^)]+\))?:/.test(task.title.toLowerCase())) return "features";
28525
+ if (/^fix(\([^)]+\))?:/.test(task.title.toLowerCase())) return "fixes";
28526
+ if (/^docs?(\([^)]+\))?:/.test(task.title.toLowerCase())) return "docs";
28527
+ if (/^test(\([^)]+\))?:/.test(task.title.toLowerCase())) return "tests";
28528
+ if (/^(chore|refactor|style|ci|build|perf)(\([^)]+\))?:/.test(task.title.toLowerCase())) return "chores";
28529
+ const labels = task.labels ?? [];
28530
+ if (labels.some((l) => ["test", "testing"].includes(l.toLowerCase()))) return "tests";
28531
+ if (labels.some((l) => ["fix", "bug", "bugfix", "regression"].includes(l.toLowerCase()))) return "fixes";
28532
+ if (labels.some((l) => ["feat", "feature", "enhancement", "add"].includes(l.toLowerCase()))) return "features";
28533
+ if (labels.some((l) => ["docs", "documentation"].includes(l.toLowerCase()))) return "docs";
28534
+ if (labels.some((l) => ["chore", "refactor", "cleanup", "maintenance"].includes(l.toLowerCase()))) return "chores";
28535
+ const titleLower = stripConventionalPrefix(task.title).toLowerCase();
28536
+ const rawTitleLower = task.title.toLowerCase();
28537
+ if (titleLower.startsWith("test") || titleLower.includes("test") && titleLower.includes("add")) return "tests";
28538
+ if (titleLower.includes("bug") || titleLower.startsWith("fix") || titleLower.includes("regression") || titleLower.includes("broken")) return "fixes";
28539
+ if (titleLower.startsWith("add ") || titleLower.includes("implement") || titleLower.startsWith("create ") || titleLower.startsWith("introduce ")) return "features";
28540
+ if (titleLower.startsWith("doc") || titleLower.includes("documentation") || titleLower.includes("readme") || titleLower.includes("changelog")) return "docs";
28541
+ if (titleLower.startsWith("chore") || titleLower.includes("refactor") || titleLower.includes("cleanup") || titleLower.includes("migrate") || titleLower.includes("upgrade") || titleLower.includes("remove ") || titleLower.startsWith("audit")) return "chores";
28542
+ if (rawTitleLower.startsWith("feat")) return "features";
28543
+ return "changes";
28544
+ }
28078
28545
  for (const taskId of releaseTasks) {
28079
28546
  const task = taskMap.get(taskId);
28080
28547
  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
- }
28548
+ if (task.type === "epic") continue;
28549
+ if (task.labels?.some((l) => l.toLowerCase() === "epic")) continue;
28550
+ if (/^epic:/i.test(task.title.trim())) continue;
28551
+ const labelsLower = (task.labels ?? []).map((l) => l.toLowerCase());
28552
+ if (labelsLower.some((l) => ["research", "internal", "spike", "audit"].includes(l))) continue;
28553
+ if (["spike", "research"].includes((task.type ?? "").toLowerCase())) continue;
28554
+ if (/^(research|investigate|audit|spike)\s/i.test(task.title.trim())) continue;
28555
+ const category = categorizeTask(task);
28556
+ const entry = buildEntry(task);
28557
+ if (category === "features") features.push(entry);
28558
+ else if (category === "fixes") fixes.push(entry);
28559
+ else if (category === "docs") docs.push(entry);
28560
+ else if (category === "tests") tests.push(entry);
28561
+ else if (category === "chores") chores.push(entry);
28562
+ else changes.push(entry);
28096
28563
  }
28097
28564
  const sections = [];
28098
28565
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -28127,14 +28594,14 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28127
28594
  sections.push(...chores);
28128
28595
  sections.push("");
28129
28596
  }
28130
- if (other.length > 0) {
28131
- sections.push("### Other");
28132
- sections.push(...other);
28597
+ if (changes.length > 0) {
28598
+ sections.push("### Changes");
28599
+ sections.push(...changes);
28133
28600
  sections.push("");
28134
28601
  }
28135
28602
  const changelog = sections.join("\n");
28136
28603
  await db.update(releaseManifests).set({ changelog }).where(eq14(releaseManifests.version, normalizedVersion)).run();
28137
- const changelogPath = join46(cwd ?? process.cwd(), "CHANGELOG.md");
28604
+ const changelogPath = join47(cwd ?? process.cwd(), "CHANGELOG.md");
28138
28605
  let existingChangelogContent = "";
28139
28606
  try {
28140
28607
  existingChangelogContent = await readFile10(changelogPath, "utf8");
@@ -28142,7 +28609,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28142
28609
  }
28143
28610
  const { customBlocks } = parseChangelogBlocks(existingChangelogContent);
28144
28611
  const changelogBody = sections.slice(2).join("\n");
28145
- await writeChangelogSection(normalizedVersion, changelogBody, customBlocks, changelogPath);
28612
+ await writeChangelogSection(normalizedVersion.replace(/^v/, ""), changelogBody, customBlocks, changelogPath);
28146
28613
  return {
28147
28614
  version: normalizedVersion,
28148
28615
  changelog,
@@ -28153,7 +28620,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28153
28620
  docs: docs.length,
28154
28621
  tests: tests.length,
28155
28622
  chores: chores.length,
28156
- other: other.length
28623
+ changes: changes.length
28157
28624
  }
28158
28625
  };
28159
28626
  }
@@ -28215,7 +28682,7 @@ async function tagRelease(version, cwd) {
28215
28682
  await db.update(releaseManifests).set({ status: "tagged", taggedAt }).where(eq14(releaseManifests.version, normalizedVersion)).run();
28216
28683
  return { version: normalizedVersion, status: "tagged", taggedAt };
28217
28684
  }
28218
- async function runReleaseGates(version, loadTasksFn, cwd) {
28685
+ async function runReleaseGates(version, loadTasksFn, cwd, opts) {
28219
28686
  if (!version) {
28220
28687
  throw new Error("version is required");
28221
28688
  }
@@ -28254,58 +28721,121 @@ async function runReleaseGates(version, loadTasksFn, cwd) {
28254
28721
  message: incompleteTasks.length === 0 ? "All tasks completed" : `${incompleteTasks.length} tasks not completed: ${incompleteTasks.join(", ")}`
28255
28722
  });
28256
28723
  const projectRoot = cwd ?? getProjectRoot();
28257
- const distPath = join46(projectRoot, "dist", "cli", "index.js");
28258
- const isNodeProject = existsSync48(join46(projectRoot, "package.json"));
28724
+ const distPath = join47(projectRoot, "dist", "cli", "index.js");
28725
+ const isNodeProject = existsSync49(join47(projectRoot, "package.json"));
28259
28726
  if (isNodeProject) {
28260
28727
  gates.push({
28261
28728
  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"
28729
+ status: existsSync49(distPath) ? "passed" : "failed",
28730
+ message: existsSync49(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
28264
28731
  });
28265
28732
  }
28266
- let workingTreeClean = true;
28267
- let dirtyFiles = [];
28268
- try {
28269
- const porcelain = execFileSync6("git", ["status", "--porcelain"], {
28270
- cwd: projectRoot,
28271
- encoding: "utf-8",
28272
- stdio: "pipe"
28733
+ if (opts?.dryRun) {
28734
+ gates.push({
28735
+ name: "clean_working_tree",
28736
+ status: "passed",
28737
+ message: "Skipped in dry-run mode"
28738
+ });
28739
+ } else {
28740
+ let workingTreeClean = true;
28741
+ let dirtyFiles = [];
28742
+ try {
28743
+ const porcelain = execFileSync7("git", ["status", "--porcelain"], {
28744
+ cwd: projectRoot,
28745
+ encoding: "utf-8",
28746
+ stdio: "pipe"
28747
+ });
28748
+ dirtyFiles = porcelain.split("\n").filter((l) => l.trim()).filter((l) => !l.startsWith("?? ")).map((l) => l.slice(3).trim()).filter((f) => f !== "CHANGELOG.md" && f !== "VERSION" && f !== "package.json");
28749
+ workingTreeClean = dirtyFiles.length === 0;
28750
+ } catch {
28751
+ }
28752
+ gates.push({
28753
+ name: "clean_working_tree",
28754
+ status: workingTreeClean ? "passed" : "failed",
28755
+ message: workingTreeClean ? "Working tree clean (excluding CHANGELOG.md, VERSION, package.json)" : `Uncommitted changes in: ${dirtyFiles.slice(0, 5).join(", ")}${dirtyFiles.length > 5 ? ` (+${dirtyFiles.length - 5} more)` : ""}`
28273
28756
  });
28274
- dirtyFiles = porcelain.split("\n").filter((l) => l.trim()).map((l) => l.slice(3).trim()).filter((f) => f !== "CHANGELOG.md" && f !== "VERSION" && f !== "package.json");
28275
- workingTreeClean = dirtyFiles.length === 0;
28276
- } catch {
28277
28757
  }
28278
- gates.push({
28279
- name: "clean_working_tree",
28280
- status: workingTreeClean ? "passed" : "failed",
28281
- message: workingTreeClean ? "Working tree clean (excluding CHANGELOG.md, VERSION, package.json)" : `Uncommitted changes in: ${dirtyFiles.slice(0, 5).join(", ")}${dirtyFiles.length > 5 ? ` (+${dirtyFiles.length - 5} more)` : ""}`
28282
- });
28283
28758
  const isPreRelease = normalizedVersion.includes("-");
28284
28759
  let currentBranch = "";
28285
28760
  try {
28286
- currentBranch = execFileSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28761
+ currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28287
28762
  cwd: projectRoot,
28288
28763
  encoding: "utf-8",
28289
28764
  stdio: "pipe"
28290
28765
  }).trim();
28291
28766
  } catch {
28292
28767
  }
28293
- const expectedBranch = isPreRelease ? "develop" : "main";
28294
- const branchOk = !currentBranch || currentBranch === expectedBranch || currentBranch === "HEAD";
28768
+ const releaseConfig = loadReleaseConfig(cwd);
28769
+ const gitFlowCfg = getGitFlowConfig(releaseConfig);
28770
+ const channelCfg = getChannelConfig(releaseConfig);
28771
+ const expectedBranch = isPreRelease ? gitFlowCfg.branches.develop : gitFlowCfg.branches.main;
28772
+ const isFeatureBranch = currentBranch.startsWith(gitFlowCfg.branches.featurePrefix) || currentBranch.startsWith(gitFlowCfg.branches.hotfixPrefix) || currentBranch.startsWith(gitFlowCfg.branches.releasePrefix);
28773
+ const branchOk = !currentBranch || currentBranch === "HEAD" || currentBranch === expectedBranch || isPreRelease && isFeatureBranch;
28774
+ const detectedChannel = currentBranch ? resolveChannelFromBranch(currentBranch, channelCfg) : isPreRelease ? "beta" : "latest";
28295
28775
  gates.push({
28296
28776
  name: "branch_target",
28297
28777
  status: branchOk ? "passed" : "failed",
28298
- message: branchOk ? `On correct branch: ${currentBranch}` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
28778
+ message: branchOk ? `On correct branch: ${currentBranch} (channel: ${detectedChannel})` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
28779
+ });
28780
+ const pushMode = getPushMode(releaseConfig);
28781
+ let requiresPR = false;
28782
+ if (pushMode === "pr") {
28783
+ requiresPR = true;
28784
+ } else if (pushMode === "auto") {
28785
+ try {
28786
+ const protectionResult = await detectBranchProtection(
28787
+ expectedBranch,
28788
+ "origin",
28789
+ projectRoot
28790
+ );
28791
+ requiresPR = protectionResult.protected;
28792
+ } catch {
28793
+ requiresPR = false;
28794
+ }
28795
+ }
28796
+ gates.push({
28797
+ name: "branch_protection",
28798
+ status: "passed",
28799
+ message: requiresPR ? `Branch '${expectedBranch}' is protected \u2014 release.ship will create a PR` : `Branch '${expectedBranch}' allows direct push`
28299
28800
  });
28300
28801
  const allPassed = gates.every((g) => g.status === "passed");
28802
+ const metadata = {
28803
+ channel: detectedChannel,
28804
+ requiresPR,
28805
+ targetBranch: expectedBranch,
28806
+ currentBranch
28807
+ };
28301
28808
  return {
28302
28809
  version: normalizedVersion,
28303
28810
  allPassed,
28304
28811
  gates,
28305
28812
  passedCount: gates.filter((g) => g.status === "passed").length,
28306
- failedCount: gates.filter((g) => g.status === "failed").length
28813
+ failedCount: gates.filter((g) => g.status === "failed").length,
28814
+ metadata
28307
28815
  };
28308
28816
  }
28817
+ async function cancelRelease(version, projectRoot) {
28818
+ if (!version) {
28819
+ throw new Error("version is required");
28820
+ }
28821
+ const normalizedVersion = normalizeVersion(version);
28822
+ const db = await getDb(projectRoot);
28823
+ const rows = await db.select().from(releaseManifests).where(eq14(releaseManifests.version, normalizedVersion)).limit(1).all();
28824
+ if (rows.length === 0) {
28825
+ return { success: false, message: `Release ${normalizedVersion} not found`, version: normalizedVersion };
28826
+ }
28827
+ const status = rows[0].status;
28828
+ const cancellableStates = ["draft", "prepared"];
28829
+ if (!cancellableStates.includes(status)) {
28830
+ return {
28831
+ success: false,
28832
+ message: `Cannot cancel a release in '${status}' state. Use 'release rollback' instead.`,
28833
+ version: normalizedVersion
28834
+ };
28835
+ }
28836
+ await db.delete(releaseManifests).where(eq14(releaseManifests.version, normalizedVersion)).run();
28837
+ return { success: true, message: `Release ${normalizedVersion} cancelled and removed`, version: normalizedVersion };
28838
+ }
28309
28839
  async function rollbackRelease(version, reason, cwd) {
28310
28840
  if (!version) {
28311
28841
  throw new Error("version is required");
@@ -28326,7 +28856,7 @@ async function rollbackRelease(version, reason, cwd) {
28326
28856
  };
28327
28857
  }
28328
28858
  async function readPushPolicy(cwd) {
28329
- const configPath = join46(getCleoDirAbsolute(cwd), "config.json");
28859
+ const configPath = join47(getCleoDirAbsolute(cwd), "config.json");
28330
28860
  const config = await readJson(configPath);
28331
28861
  if (!config) return void 0;
28332
28862
  const release2 = config.release;
@@ -28340,6 +28870,33 @@ async function pushRelease(version, remote, cwd, opts) {
28340
28870
  const normalizedVersion = normalizeVersion(version);
28341
28871
  const projectRoot = getProjectRoot(cwd);
28342
28872
  const pushPolicy = await readPushPolicy(cwd);
28873
+ const configPushMode = getPushMode(loadReleaseConfig(cwd));
28874
+ const effectivePushMode = opts?.mode ?? pushPolicy?.mode ?? configPushMode;
28875
+ if (effectivePushMode === "pr" || effectivePushMode === "auto") {
28876
+ const targetRemoteForCheck = remote ?? pushPolicy?.remote ?? "origin";
28877
+ let branchIsProtected = effectivePushMode === "pr";
28878
+ if (effectivePushMode === "auto") {
28879
+ try {
28880
+ const protection = await detectBranchProtection(
28881
+ pushPolicy?.allowedBranches?.[0] ?? "main",
28882
+ targetRemoteForCheck,
28883
+ projectRoot
28884
+ );
28885
+ branchIsProtected = protection.protected;
28886
+ } catch {
28887
+ branchIsProtected = false;
28888
+ }
28889
+ }
28890
+ if (branchIsProtected) {
28891
+ return {
28892
+ version: normalizedVersion,
28893
+ status: "requires_pr",
28894
+ remote: targetRemoteForCheck,
28895
+ pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
28896
+ requiresPR: true
28897
+ };
28898
+ }
28899
+ }
28343
28900
  if (pushPolicy && pushPolicy.enabled === false && !opts?.explicitPush) {
28344
28901
  throw new Error(
28345
28902
  "Push is disabled by config (release.push.enabled=false). Use --push to override."
@@ -28347,20 +28904,21 @@ async function pushRelease(version, remote, cwd, opts) {
28347
28904
  }
28348
28905
  const targetRemote = remote ?? pushPolicy?.remote ?? "origin";
28349
28906
  if (pushPolicy?.requireCleanTree) {
28350
- const statusOutput = execFileSync6("git", ["status", "--porcelain"], {
28907
+ const statusOutput = execFileSync7("git", ["status", "--porcelain"], {
28351
28908
  cwd: projectRoot,
28352
28909
  timeout: 1e4,
28353
28910
  encoding: "utf-8",
28354
28911
  stdio: ["pipe", "pipe", "pipe"]
28355
28912
  });
28356
- if (statusOutput.trim().length > 0) {
28913
+ const trackedDirty = statusOutput.split("\n").filter((l) => l.trim() && !l.startsWith("?? ")).join("\n");
28914
+ if (trackedDirty.trim().length > 0) {
28357
28915
  throw new Error(
28358
28916
  "Git working tree is not clean. Commit or stash changes before pushing (config: release.push.requireCleanTree=true)."
28359
28917
  );
28360
28918
  }
28361
28919
  }
28362
28920
  if (pushPolicy?.allowedBranches && pushPolicy.allowedBranches.length > 0) {
28363
- const currentBranch = execFileSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28921
+ const currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28364
28922
  cwd: projectRoot,
28365
28923
  timeout: 1e4,
28366
28924
  encoding: "utf-8",
@@ -28372,7 +28930,7 @@ async function pushRelease(version, remote, cwd, opts) {
28372
28930
  );
28373
28931
  }
28374
28932
  }
28375
- execFileSync6("git", ["push", targetRemote, "--follow-tags"], {
28933
+ execFileSync7("git", ["push", targetRemote, "--follow-tags"], {
28376
28934
  cwd: projectRoot,
28377
28935
  timeout: 6e4,
28378
28936
  encoding: "utf-8",
@@ -28403,6 +28961,9 @@ var init_release_manifest = __esm({
28403
28961
  init_paths();
28404
28962
  init_json();
28405
28963
  init_changelog_writer();
28964
+ init_github_pr();
28965
+ init_channel();
28966
+ init_release_config();
28406
28967
  }
28407
28968
  });
28408
28969
 
@@ -28482,8 +29043,181 @@ var init_guards = __esm({
28482
29043
  }
28483
29044
  });
28484
29045
 
29046
+ // src/core/release/version-bump.ts
29047
+ import { existsSync as existsSync50, readFileSync as readFileSync39, writeFileSync as writeFileSync8 } from "node:fs";
29048
+ import { join as join48 } from "node:path";
29049
+ function readConfigValueSync2(path, defaultValue, cwd) {
29050
+ try {
29051
+ const configPath = join48(getCleoDir(cwd), "config.json");
29052
+ if (!existsSync50(configPath)) return defaultValue;
29053
+ const config = JSON.parse(readFileSync39(configPath, "utf-8"));
29054
+ const keys = path.split(".");
29055
+ let value = config;
29056
+ for (const key of keys) {
29057
+ if (value == null || typeof value !== "object") return defaultValue;
29058
+ value = value[key];
29059
+ }
29060
+ return value ?? defaultValue;
29061
+ } catch {
29062
+ return defaultValue;
29063
+ }
29064
+ }
29065
+ function validateVersionFormat(version) {
29066
+ return VERSION_WITH_PRERELEASE.test(version);
29067
+ }
29068
+ function getVersionBumpConfig(cwd) {
29069
+ try {
29070
+ const raw = readConfigValueSync2("release.versionBump.files", [], cwd);
29071
+ return raw.map((entry) => ({
29072
+ file: entry.path ?? entry.file ?? "",
29073
+ strategy: entry.strategy,
29074
+ field: entry.jsonPath?.replace(/^\./, "") ?? entry.field,
29075
+ key: entry.key,
29076
+ section: entry.section,
29077
+ pattern: entry.sedPattern ?? entry.pattern
29078
+ })).filter((t) => t.file !== "");
29079
+ } catch {
29080
+ return [];
29081
+ }
29082
+ }
29083
+ function bumpFile(target, newVersion, projectRoot) {
29084
+ const filePath = join48(projectRoot, target.file);
29085
+ if (!existsSync50(filePath)) {
29086
+ return {
29087
+ file: target.file,
29088
+ strategy: target.strategy,
29089
+ success: false,
29090
+ error: `File not found: ${target.file}`
29091
+ };
29092
+ }
29093
+ try {
29094
+ const content = readFileSync39(filePath, "utf-8");
29095
+ let previousVersion;
29096
+ let newContent;
29097
+ switch (target.strategy) {
29098
+ case "plain": {
29099
+ previousVersion = content.trim();
29100
+ newContent = newVersion + "\n";
29101
+ break;
29102
+ }
29103
+ case "json": {
29104
+ const field = target.field ?? "version";
29105
+ const json = JSON.parse(content);
29106
+ previousVersion = getNestedField(json, field);
29107
+ setNestedField(json, field, newVersion);
29108
+ newContent = JSON.stringify(json, null, 2) + "\n";
29109
+ break;
29110
+ }
29111
+ case "toml": {
29112
+ const key = target.key ?? "version";
29113
+ const versionRegex = new RegExp(`^(${key}\\s*=\\s*")([^"]+)(")`, "m");
29114
+ const match = content.match(versionRegex);
29115
+ previousVersion = match?.[2];
29116
+ newContent = content.replace(versionRegex, `$1${newVersion}$3`);
29117
+ break;
29118
+ }
29119
+ case "sed": {
29120
+ const pattern = target.pattern ?? "";
29121
+ if (!pattern.includes("{{VERSION}}")) {
29122
+ return {
29123
+ file: target.file,
29124
+ strategy: target.strategy,
29125
+ success: false,
29126
+ error: "sed strategy requires {{VERSION}} placeholder in pattern"
29127
+ };
29128
+ }
29129
+ const regex = new RegExp(pattern.replace("{{VERSION}}", "([\\d.]+)"));
29130
+ const match = content.match(regex);
29131
+ previousVersion = match?.[1];
29132
+ newContent = content.replace(
29133
+ regex,
29134
+ pattern.replace("{{VERSION}}", newVersion)
29135
+ );
29136
+ break;
29137
+ }
29138
+ default:
29139
+ return {
29140
+ file: target.file,
29141
+ strategy: target.strategy,
29142
+ success: false,
29143
+ error: `Unknown strategy: ${target.strategy}`
29144
+ };
29145
+ }
29146
+ writeFileSync8(filePath, newContent, "utf-8");
29147
+ return {
29148
+ file: target.file,
29149
+ strategy: target.strategy,
29150
+ success: true,
29151
+ previousVersion,
29152
+ newVersion
29153
+ };
29154
+ } catch (err) {
29155
+ return {
29156
+ file: target.file,
29157
+ strategy: target.strategy,
29158
+ success: false,
29159
+ error: String(err)
29160
+ };
29161
+ }
29162
+ }
29163
+ function bumpVersionFromConfig(newVersion, options = {}, cwd) {
29164
+ if (!validateVersionFormat(newVersion)) {
29165
+ throw new CleoError(
29166
+ 6 /* VALIDATION_ERROR */,
29167
+ `Invalid version: '${newVersion}' (expected X.Y.Z or YYYY.M.patch)`
29168
+ );
29169
+ }
29170
+ const targets = getVersionBumpConfig(cwd);
29171
+ if (targets.length === 0) {
29172
+ throw new CleoError(
29173
+ 1 /* GENERAL_ERROR */,
29174
+ "No version bump targets configured. Add release.versionBump.files to .cleo/config.json"
29175
+ );
29176
+ }
29177
+ const projectRoot = getProjectRoot(cwd);
29178
+ const results = [];
29179
+ if (options.dryRun) {
29180
+ for (const target of targets) {
29181
+ results.push({
29182
+ file: target.file,
29183
+ strategy: target.strategy,
29184
+ success: true,
29185
+ newVersion
29186
+ });
29187
+ }
29188
+ return { results, allSuccess: true };
29189
+ }
29190
+ for (const target of targets) {
29191
+ results.push(bumpFile(target, newVersion, projectRoot));
29192
+ }
29193
+ const allSuccess = results.every((r) => r.success);
29194
+ return { results, allSuccess };
29195
+ }
29196
+ function getNestedField(obj, path) {
29197
+ return path.split(".").reduce((acc, key) => acc?.[key], obj);
29198
+ }
29199
+ function setNestedField(obj, path, value) {
29200
+ const parts = path.split(".");
29201
+ let current = obj;
29202
+ for (let i = 0; i < parts.length - 1; i++) {
29203
+ if (typeof current[parts[i]] !== "object") current[parts[i]] = {};
29204
+ current = current[parts[i]];
29205
+ }
29206
+ current[parts[parts.length - 1]] = value;
29207
+ }
29208
+ var VERSION_WITH_PRERELEASE;
29209
+ var init_version_bump = __esm({
29210
+ "src/core/release/version-bump.ts"() {
29211
+ "use strict";
29212
+ init_paths();
29213
+ init_errors();
29214
+ init_exit_codes();
29215
+ VERSION_WITH_PRERELEASE = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
29216
+ }
29217
+ });
29218
+
28485
29219
  // src/dispatch/engines/release-engine.ts
28486
- import { execFileSync as execFileSync7 } from "node:child_process";
29220
+ import { execFileSync as execFileSync8 } from "node:child_process";
28487
29221
  function isAgentContext() {
28488
29222
  return !!(process.env["CLEO_SESSION_ID"] || process.env["CLAUDE_AGENT_TYPE"]);
28489
29223
  }
@@ -28604,6 +29338,23 @@ async function releaseRollback(version, reason, projectRoot) {
28604
29338
  return engineError(code, message);
28605
29339
  }
28606
29340
  }
29341
+ async function releaseCancel(version, projectRoot) {
29342
+ if (!version) {
29343
+ return engineError("E_INVALID_INPUT", "version is required");
29344
+ }
29345
+ try {
29346
+ const result = await cancelRelease(version, projectRoot);
29347
+ if (!result.success) {
29348
+ const code = result.message.includes("not found") ? "E_NOT_FOUND" : "E_INVALID_STATE";
29349
+ return engineError(code, result.message);
29350
+ }
29351
+ return { success: true, data: result };
29352
+ } catch (err) {
29353
+ const message = err.message;
29354
+ const code = message.includes("not found") ? "E_NOT_FOUND" : "E_CANCEL_FAILED";
29355
+ return engineError(code, message);
29356
+ }
29357
+ }
28607
29358
  async function releasePush(version, remote, projectRoot, opts) {
28608
29359
  if (isAgentContext()) {
28609
29360
  const hasEntry = await hasManifestEntry(version, projectRoot);
@@ -28625,7 +29376,7 @@ async function releasePush(version, remote, projectRoot, opts) {
28625
29376
  const result = await pushRelease(version, remote, projectRoot, opts);
28626
29377
  let commitSha;
28627
29378
  try {
28628
- commitSha = execFileSync7("git", ["rev-parse", "HEAD"], {
29379
+ commitSha = execFileSync8("git", ["rev-parse", "HEAD"], {
28629
29380
  cwd: projectRoot ?? process.cwd(),
28630
29381
  encoding: "utf-8",
28631
29382
  stdio: "pipe"
@@ -28647,7 +29398,7 @@ async function releasePush(version, remote, projectRoot, opts) {
28647
29398
  }
28648
29399
  }
28649
29400
  async function releaseShip(params, projectRoot) {
28650
- const { version, epicId, remote, dryRun = false } = params;
29401
+ const { version, epicId, remote, dryRun = false, bump = true } = params;
28651
29402
  if (!version) {
28652
29403
  return engineError("E_INVALID_INPUT", "version is required");
28653
29404
  }
@@ -28655,18 +29406,71 @@ async function releaseShip(params, projectRoot) {
28655
29406
  return engineError("E_INVALID_INPUT", "epicId is required");
28656
29407
  }
28657
29408
  const cwd = projectRoot ?? resolveProjectRoot();
29409
+ const steps = [];
29410
+ const logStep = (n, total, label, done, error) => {
29411
+ let msg;
29412
+ if (done === void 0) {
29413
+ msg = `[Step ${n}/${total}] ${label}...`;
29414
+ } else if (done) {
29415
+ msg = ` \u2713 ${label}`;
29416
+ } else {
29417
+ msg = ` \u2717 ${label}: ${error ?? "failed"}`;
29418
+ }
29419
+ steps.push(msg);
29420
+ console.log(msg);
29421
+ };
29422
+ const bumpTargets = getVersionBumpConfig(cwd);
29423
+ const shouldBump = bump && bumpTargets.length > 0;
28658
29424
  try {
29425
+ if (shouldBump) {
29426
+ logStep(0, 8, "Bump version files");
29427
+ if (!dryRun) {
29428
+ const bumpResults = bumpVersionFromConfig(version, { dryRun: false }, cwd);
29429
+ if (!bumpResults.allSuccess) {
29430
+ const failed = bumpResults.results.filter((r) => !r.success).map((r) => r.file);
29431
+ steps.push(` ! Version bump partial: failed for ${failed.join(", ")}`);
29432
+ } else {
29433
+ logStep(0, 8, "Bump version files", true);
29434
+ }
29435
+ } else {
29436
+ logStep(0, 8, "Bump version files", true);
29437
+ }
29438
+ }
29439
+ logStep(1, 8, "Validate release gates");
28659
29440
  const gatesResult = await runReleaseGates(
28660
29441
  version,
28661
29442
  () => loadTasks2(projectRoot),
28662
- projectRoot
29443
+ projectRoot,
29444
+ { dryRun }
28663
29445
  );
28664
29446
  if (gatesResult && !gatesResult.allPassed) {
28665
29447
  const failedGates = gatesResult.gates.filter((g) => g.status === "failed");
29448
+ logStep(1, 8, "Validate release gates", false, failedGates.map((g) => g.name).join(", "));
28666
29449
  return engineError("E_LIFECYCLE_GATE_FAILED", `Release gates failed for ${version}: ${failedGates.map((g) => g.name).join(", ")}`, {
28667
29450
  details: { gates: gatesResult.gates, failedCount: gatesResult.failedCount }
28668
29451
  });
28669
29452
  }
29453
+ logStep(1, 8, "Validate release gates", true);
29454
+ let resolvedChannel = "latest";
29455
+ let currentBranchForPR = "HEAD";
29456
+ try {
29457
+ const branchName = execFileSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
29458
+ cwd,
29459
+ encoding: "utf-8",
29460
+ stdio: "pipe"
29461
+ }).trim();
29462
+ currentBranchForPR = branchName;
29463
+ const channelEnum = resolveChannelFromBranch(branchName);
29464
+ resolvedChannel = channelToDistTag(channelEnum);
29465
+ } catch {
29466
+ }
29467
+ const gateMetadata = gatesResult.metadata;
29468
+ const requiresPRFromGates = gateMetadata?.requiresPR ?? false;
29469
+ const targetBranchFromGates = gateMetadata?.targetBranch;
29470
+ if (gateMetadata?.currentBranch) {
29471
+ currentBranchForPR = gateMetadata.currentBranch;
29472
+ }
29473
+ logStep(2, 8, "Check epic completeness");
28670
29474
  let releaseTaskIds = [];
28671
29475
  try {
28672
29476
  const manifest = await showManifestRelease(version, projectRoot);
@@ -28677,10 +29481,13 @@ async function releaseShip(params, projectRoot) {
28677
29481
  const epicCheck = await checkEpicCompleteness(releaseTaskIds, projectRoot, epicAccessor);
28678
29482
  if (epicCheck.hasIncomplete) {
28679
29483
  const incomplete = epicCheck.epics.filter((e) => e.missingChildren.length > 0).map((e) => `${e.epicId}: missing ${e.missingChildren.map((c) => c.id).join(", ")}`).join("; ");
29484
+ logStep(2, 8, "Check epic completeness", false, incomplete);
28680
29485
  return engineError("E_LIFECYCLE_GATE_FAILED", `Epic completeness check failed: ${incomplete}`, {
28681
29486
  details: { epics: epicCheck.epics }
28682
29487
  });
28683
29488
  }
29489
+ logStep(2, 8, "Check epic completeness", true);
29490
+ logStep(3, 8, "Check task double-listing");
28684
29491
  const allReleases = await listManifestReleases(projectRoot);
28685
29492
  const existingReleases = (allReleases.releases ?? []).filter((r) => r.version !== version);
28686
29493
  const doubleCheck = checkDoubleListing(
@@ -28689,73 +29496,160 @@ async function releaseShip(params, projectRoot) {
28689
29496
  );
28690
29497
  if (doubleCheck.hasDoubleListing) {
28691
29498
  const dupes = doubleCheck.duplicates.map((d) => `${d.taskId} (in ${d.releases.join(", ")})`).join("; ");
29499
+ logStep(3, 8, "Check task double-listing", false, dupes);
28692
29500
  return engineError("E_VALIDATION", `Double-listing detected: ${dupes}`, {
28693
29501
  details: { duplicates: doubleCheck.duplicates }
28694
29502
  });
28695
29503
  }
28696
- const changelogResult = await generateReleaseChangelog(
29504
+ logStep(3, 8, "Check task double-listing", true);
29505
+ const loadedConfig = loadReleaseConfig(cwd);
29506
+ const pushMode = getPushMode(loadedConfig);
29507
+ const gitflowCfg = getGitFlowConfig(loadedConfig);
29508
+ const targetBranch = targetBranchFromGates ?? gitflowCfg.branches.main;
29509
+ if (dryRun) {
29510
+ logStep(4, 8, "Generate CHANGELOG");
29511
+ logStep(4, 8, "Generate CHANGELOG", true);
29512
+ const wouldCreatePR = requiresPRFromGates || pushMode === "pr";
29513
+ const filesToStagePreview = ["CHANGELOG.md", ...shouldBump ? bumpTargets.map((t) => t.file) : []];
29514
+ const wouldDo = [];
29515
+ if (shouldBump) {
29516
+ wouldDo.push(`bump version files: ${bumpTargets.map((t) => t.file).join(", ")} \u2192 ${version}`);
29517
+ }
29518
+ wouldDo.push(
29519
+ `write CHANGELOG.md: ## [${version}] - ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} (preview only, not written in dry-run)`,
29520
+ `git add ${filesToStagePreview.join(" ")}`,
29521
+ `git commit -m "release: ship v${version} (${epicId})"`,
29522
+ `git tag -a v${version} -m "Release v${version}"`
29523
+ );
29524
+ const dryRunOutput = {
29525
+ version,
29526
+ epicId,
29527
+ dryRun: true,
29528
+ channel: resolvedChannel,
29529
+ pushMode,
29530
+ wouldDo
29531
+ };
29532
+ if (wouldCreatePR) {
29533
+ const ghAvailable = isGhCliAvailable();
29534
+ dryRunOutput["wouldDo"].push(
29535
+ ghAvailable ? `gh pr create --base ${targetBranch} --head ${currentBranchForPR} --title "release: ship v${version}"` : `manual PR: ${currentBranchForPR} \u2192 ${targetBranch} (gh CLI not available)`
29536
+ );
29537
+ dryRunOutput["wouldCreatePR"] = true;
29538
+ dryRunOutput["prTitle"] = `release: ship v${version}`;
29539
+ dryRunOutput["prTargetBranch"] = targetBranch;
29540
+ } else {
29541
+ dryRunOutput["wouldDo"].push(
29542
+ `git push ${remote ?? "origin"} --follow-tags`
29543
+ );
29544
+ dryRunOutput["wouldCreatePR"] = false;
29545
+ }
29546
+ dryRunOutput["wouldDo"].push("markReleasePushed(...)");
29547
+ return { success: true, data: { ...dryRunOutput, steps } };
29548
+ }
29549
+ logStep(4, 8, "Generate CHANGELOG");
29550
+ await generateReleaseChangelog(
28697
29551
  version,
28698
29552
  () => loadTasks2(projectRoot),
28699
29553
  projectRoot
28700
29554
  );
28701
29555
  const changelogPath = `${cwd}/CHANGELOG.md`;
28702
- const generatedContent = changelogResult.changelog ?? "";
28703
- 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
- }
28719
- };
28720
- }
28721
- await writeChangelogSection(version, generatedContent, [], changelogPath);
29556
+ logStep(4, 8, "Generate CHANGELOG", true);
29557
+ logStep(5, 8, "Commit release");
28722
29558
  const gitCwd = { cwd, encoding: "utf-8", stdio: "pipe" };
29559
+ const filesToStage = ["CHANGELOG.md", ...shouldBump ? bumpTargets.map((t) => t.file) : []];
28723
29560
  try {
28724
- execFileSync7("git", ["add", "CHANGELOG.md"], gitCwd);
29561
+ execFileSync8("git", ["add", ...filesToStage], gitCwd);
28725
29562
  } catch (err) {
28726
29563
  const msg = err.message ?? String(err);
29564
+ logStep(5, 8, "Commit release", false, `git add failed: ${msg}`);
28727
29565
  return engineError("E_GENERAL", `git add failed: ${msg}`);
28728
29566
  }
28729
29567
  try {
28730
- execFileSync7(
29568
+ execFileSync8(
28731
29569
  "git",
28732
29570
  ["commit", "-m", `release: ship v${version} (${epicId})`],
28733
29571
  gitCwd
28734
29572
  );
28735
29573
  } catch (err) {
28736
29574
  const msg = err.stderr ?? err.message ?? String(err);
29575
+ logStep(5, 8, "Commit release", false, `git commit failed: ${msg}`);
28737
29576
  return engineError("E_GENERAL", `git commit failed: ${msg}`);
28738
29577
  }
29578
+ logStep(5, 8, "Commit release", true);
28739
29579
  let commitSha;
28740
29580
  try {
28741
- commitSha = execFileSync7("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
29581
+ commitSha = execFileSync8("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
28742
29582
  } catch {
28743
29583
  }
29584
+ logStep(6, 8, "Tag release");
28744
29585
  const gitTag = `v${version.replace(/^v/, "")}`;
28745
29586
  try {
28746
- execFileSync7("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
29587
+ execFileSync8("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
28747
29588
  } catch (err) {
28748
29589
  const msg = err.stderr ?? err.message ?? String(err);
29590
+ logStep(6, 8, "Tag release", false, `git tag failed: ${msg}`);
28749
29591
  return engineError("E_GENERAL", `git tag failed: ${msg}`);
28750
29592
  }
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 }
29593
+ logStep(6, 8, "Tag release", true);
29594
+ logStep(7, 8, "Push / create PR");
29595
+ let prResult = null;
29596
+ const pushResult = await pushRelease(version, remote, projectRoot, {
29597
+ explicitPush: true,
29598
+ mode: pushMode
29599
+ });
29600
+ if (pushResult.requiresPR || requiresPRFromGates) {
29601
+ const prBody = buildPRBody({
29602
+ base: targetBranch,
29603
+ head: currentBranchForPR,
29604
+ title: `release: ship v${version}`,
29605
+ body: "",
29606
+ version,
29607
+ epicId,
29608
+ projectRoot: cwd
28758
29609
  });
29610
+ prResult = await createPullRequest({
29611
+ base: targetBranch,
29612
+ head: currentBranchForPR,
29613
+ title: `release: ship v${version}`,
29614
+ body: prBody,
29615
+ labels: ["release", resolvedChannel],
29616
+ version,
29617
+ epicId,
29618
+ projectRoot: cwd
29619
+ });
29620
+ if (prResult.mode === "created") {
29621
+ const m1 = ` \u2713 Push / create PR`;
29622
+ const m2 = ` PR created: ${prResult.prUrl}`;
29623
+ const m3 = ` \u2192 Next: merge the PR, then CI will publish to npm @${resolvedChannel}`;
29624
+ steps.push(m1, m2, m3);
29625
+ console.log(m1);
29626
+ console.log(m2);
29627
+ console.log(m3);
29628
+ } else if (prResult.mode === "skipped") {
29629
+ const m1 = ` \u2713 Push / create PR`;
29630
+ const m2 = ` PR already exists: ${prResult.prUrl}`;
29631
+ steps.push(m1, m2);
29632
+ console.log(m1);
29633
+ console.log(m2);
29634
+ } else {
29635
+ const m1 = ` ! Push / create PR \u2014 manual PR required:`;
29636
+ const m2 = prResult.instructions ?? "";
29637
+ steps.push(m1, m2);
29638
+ console.log(m1);
29639
+ console.log(m2);
29640
+ }
29641
+ } else {
29642
+ try {
29643
+ execFileSync8("git", ["push", remote ?? "origin", "--follow-tags"], gitCwd);
29644
+ logStep(7, 8, "Push / create PR", true);
29645
+ } catch (err) {
29646
+ const execError = err;
29647
+ const msg = (execError.stderr ?? execError.message ?? "").slice(0, 500);
29648
+ logStep(7, 8, "Push / create PR", false, `git push failed: ${msg}`);
29649
+ return engineError("E_GENERAL", `git push failed: ${msg}`, {
29650
+ details: { exitCode: execError.status }
29651
+ });
29652
+ }
28759
29653
  }
28760
29654
  const pushedAt = (/* @__PURE__ */ new Date()).toISOString();
28761
29655
  await markReleasePushed(version, pushedAt, projectRoot, { commitSha, gitTag });
@@ -28767,7 +29661,17 @@ async function releaseShip(params, projectRoot) {
28767
29661
  commitSha,
28768
29662
  gitTag,
28769
29663
  pushedAt,
28770
- changelog: changelogPath
29664
+ changelog: changelogPath,
29665
+ channel: resolvedChannel,
29666
+ steps,
29667
+ ...prResult ? {
29668
+ pr: {
29669
+ mode: prResult.mode,
29670
+ prUrl: prResult.prUrl,
29671
+ prNumber: prResult.prNumber,
29672
+ instructions: prResult.instructions
29673
+ }
29674
+ } : {}
28771
29675
  }
28772
29676
  };
28773
29677
  } catch (err) {
@@ -28780,15 +29684,18 @@ var init_release_engine = __esm({
28780
29684
  init_platform();
28781
29685
  init_data_accessor();
28782
29686
  init_release_manifest();
28783
- init_changelog_writer();
28784
29687
  init_guards();
29688
+ init_github_pr();
29689
+ init_channel();
29690
+ init_release_config();
29691
+ init_version_bump();
28785
29692
  init_error();
28786
29693
  }
28787
29694
  });
28788
29695
 
28789
29696
  // 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";
29697
+ import { readFileSync as readFileSync40, readdirSync as readdirSync12, existsSync as existsSync51 } from "fs";
29698
+ import { join as join49 } from "path";
28792
29699
  import { parse as parseYaml } from "yaml";
28793
29700
  function deriveSubcommand(filename) {
28794
29701
  let stem = filename.replace(/\.ya?ml$/i, "");
@@ -28802,8 +29709,8 @@ function deriveSubcommand(filename) {
28802
29709
  return firstWord.toLowerCase();
28803
29710
  }
28804
29711
  function parseTemplateFile(templateDir, filename) {
28805
- const filePath = join47(templateDir, filename);
28806
- const raw = readFileSync38(filePath, "utf-8");
29712
+ const filePath = join49(templateDir, filename);
29713
+ const raw = readFileSync40(filePath, "utf-8");
28807
29714
  const parsed = parseYaml(raw);
28808
29715
  const name = typeof parsed.name === "string" ? parsed.name : filename;
28809
29716
  const titlePrefix = typeof parsed.title === "string" ? parsed.title : "";
@@ -28852,8 +29759,8 @@ function parseTemplateFile(templateDir, filename) {
28852
29759
  };
28853
29760
  }
28854
29761
  function parseIssueTemplates(projectRoot) {
28855
- const templateDir = join47(projectRoot, ".github", "ISSUE_TEMPLATE");
28856
- if (!existsSync49(templateDir)) {
29762
+ const templateDir = join49(projectRoot, ".github", "ISSUE_TEMPLATE");
29763
+ if (!existsSync51(templateDir)) {
28857
29764
  return engineError("E_NOT_FOUND", `Issue template directory not found: ${templateDir}`);
28858
29765
  }
28859
29766
  let files;
@@ -30532,26 +31439,26 @@ var init_check = __esm({
30532
31439
  });
30533
31440
 
30534
31441
  // 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";
31442
+ import { readFileSync as readFileSync41, readdirSync as readdirSync13, existsSync as existsSync52 } from "node:fs";
31443
+ import { join as join50 } from "node:path";
30537
31444
  import AjvModule3 from "ajv";
30538
31445
  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)) {
31446
+ const adrsDir = join50(projectRoot, ".cleo", "adrs");
31447
+ const schemaPath = join50(projectRoot, "schemas", "adr-frontmatter.schema.json");
31448
+ if (!existsSync52(schemaPath)) {
30542
31449
  return {
30543
31450
  valid: false,
30544
31451
  errors: [{ file: "schemas/adr-frontmatter.schema.json", field: "schema", message: "Schema file not found" }],
30545
31452
  checked: 0
30546
31453
  };
30547
31454
  }
30548
- if (!existsSync50(adrsDir)) {
31455
+ if (!existsSync52(adrsDir)) {
30549
31456
  return { valid: true, errors: [], checked: 0 };
30550
31457
  }
30551
- const schema = JSON.parse(readFileSync39(schemaPath, "utf-8"));
31458
+ const schema = JSON.parse(readFileSync41(schemaPath, "utf-8"));
30552
31459
  const ajv = new Ajv3({ allErrors: true });
30553
31460
  const validate = ajv.compile(schema);
30554
- const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join48(adrsDir, f));
31461
+ const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join50(adrsDir, f));
30555
31462
  const errors = [];
30556
31463
  for (const filePath of files) {
30557
31464
  const record = parseAdrFile(filePath, projectRoot);
@@ -30578,15 +31485,15 @@ var init_validate = __esm({
30578
31485
  });
30579
31486
 
30580
31487
  // src/core/adrs/list.ts
30581
- import { readdirSync as readdirSync14, existsSync as existsSync51 } from "node:fs";
30582
- import { join as join49 } from "node:path";
31488
+ import { readdirSync as readdirSync14, existsSync as existsSync53 } from "node:fs";
31489
+ import { join as join51 } from "node:path";
30583
31490
  async function listAdrs(projectRoot, opts) {
30584
- const adrsDir = join49(projectRoot, ".cleo", "adrs");
30585
- if (!existsSync51(adrsDir)) {
31491
+ const adrsDir = join51(projectRoot, ".cleo", "adrs");
31492
+ if (!existsSync53(adrsDir)) {
30586
31493
  return { adrs: [], total: 0 };
30587
31494
  }
30588
31495
  const files = readdirSync14(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
30589
- const records = files.map((f) => parseAdrFile(join49(adrsDir, f), projectRoot));
31496
+ const records = files.map((f) => parseAdrFile(join51(adrsDir, f), projectRoot));
30590
31497
  const filtered = records.filter((r) => {
30591
31498
  if (opts?.status && r.frontmatter.Status !== opts.status) return false;
30592
31499
  if (opts?.since && r.frontmatter.Date < opts.since) return false;
@@ -30611,14 +31518,14 @@ var init_list2 = __esm({
30611
31518
  });
30612
31519
 
30613
31520
  // src/core/adrs/show.ts
30614
- import { existsSync as existsSync52, readdirSync as readdirSync15 } from "node:fs";
30615
- import { join as join50 } from "node:path";
31521
+ import { existsSync as existsSync54, readdirSync as readdirSync15 } from "node:fs";
31522
+ import { join as join52 } from "node:path";
30616
31523
  async function showAdr(projectRoot, adrId) {
30617
- const adrsDir = join50(projectRoot, ".cleo", "adrs");
30618
- if (!existsSync52(adrsDir)) return null;
31524
+ const adrsDir = join52(projectRoot, ".cleo", "adrs");
31525
+ if (!existsSync54(adrsDir)) return null;
30619
31526
  const files = readdirSync15(adrsDir).filter((f) => f.startsWith(adrId) && f.endsWith(".md"));
30620
31527
  if (files.length === 0) return null;
30621
- const filePath = join50(adrsDir, files[0]);
31528
+ const filePath = join52(adrsDir, files[0]);
30622
31529
  return parseAdrFile(filePath, projectRoot);
30623
31530
  }
30624
31531
  var init_show2 = __esm({
@@ -30629,8 +31536,8 @@ var init_show2 = __esm({
30629
31536
  });
30630
31537
 
30631
31538
  // src/core/adrs/find.ts
30632
- import { readdirSync as readdirSync16, existsSync as existsSync53 } from "node:fs";
30633
- import { join as join51 } from "node:path";
31539
+ import { readdirSync as readdirSync16, existsSync as existsSync55 } from "node:fs";
31540
+ import { join as join53 } from "node:path";
30634
31541
  function normalise(s) {
30635
31542
  return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
30636
31543
  }
@@ -30647,8 +31554,8 @@ function matchedTerms(target, terms) {
30647
31554
  return terms.filter((term) => t.includes(term));
30648
31555
  }
30649
31556
  async function findAdrs(projectRoot, query, opts) {
30650
- const adrsDir = join51(projectRoot, ".cleo", "adrs");
30651
- if (!existsSync53(adrsDir)) {
31557
+ const adrsDir = join53(projectRoot, ".cleo", "adrs");
31558
+ if (!existsSync55(adrsDir)) {
30652
31559
  return { adrs: [], query, total: 0 };
30653
31560
  }
30654
31561
  const files = readdirSync16(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
@@ -30657,7 +31564,7 @@ async function findAdrs(projectRoot, query, opts) {
30657
31564
  const filterKeywords = opts?.keywords ? parseTags(opts.keywords) : null;
30658
31565
  const results = [];
30659
31566
  for (const file of files) {
30660
- const record = parseAdrFile(join51(adrsDir, file), projectRoot);
31567
+ const record = parseAdrFile(join53(adrsDir, file), projectRoot);
30661
31568
  const fm = record.frontmatter;
30662
31569
  if (opts?.status && fm.Status !== opts.status) continue;
30663
31570
  if (filterTopics && filterTopics.length > 0) {
@@ -30735,12 +31642,12 @@ var init_adrs = __esm({
30735
31642
  });
30736
31643
 
30737
31644
  // src/core/admin/sync.ts
30738
- import { join as join52 } from "node:path";
31645
+ import { join as join54 } from "node:path";
30739
31646
  import { rm as rm2, rmdir, stat as stat2 } from "node:fs/promises";
30740
31647
  async function getSyncStatus(projectRoot) {
30741
31648
  try {
30742
31649
  const cleoDir = getCleoDir(projectRoot);
30743
- const stateFile = join52(cleoDir, "sync", "todowrite-session.json");
31650
+ const stateFile = join54(cleoDir, "sync", "todowrite-session.json");
30744
31651
  const sessionState = await readJson(stateFile);
30745
31652
  if (!sessionState) {
30746
31653
  return {
@@ -30784,8 +31691,8 @@ async function getSyncStatus(projectRoot) {
30784
31691
  async function clearSyncState(projectRoot, dryRun) {
30785
31692
  try {
30786
31693
  const cleoDir = getCleoDir(projectRoot);
30787
- const syncDir = join52(cleoDir, "sync");
30788
- const stateFile = join52(syncDir, "todowrite-session.json");
31694
+ const syncDir = join54(cleoDir, "sync");
31695
+ const stateFile = join54(syncDir, "todowrite-session.json");
30789
31696
  let exists = false;
30790
31697
  try {
30791
31698
  await stat2(stateFile);
@@ -31552,8 +32459,8 @@ var init_import_tasks = __esm({
31552
32459
  // src/core/snapshot/index.ts
31553
32460
  import { createHash as createHash6 } from "node:crypto";
31554
32461
  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";
32462
+ import { existsSync as existsSync56 } from "node:fs";
32463
+ import { join as join55, dirname as dirname14 } from "node:path";
31557
32464
  function toSnapshotTask(task) {
31558
32465
  return {
31559
32466
  id: task.id,
@@ -31606,7 +32513,7 @@ async function exportSnapshot(cwd) {
31606
32513
  }
31607
32514
  async function writeSnapshot(snapshot, outputPath) {
31608
32515
  const dir = dirname14(outputPath);
31609
- if (!existsSync54(dir)) {
32516
+ if (!existsSync56(dir)) {
31610
32517
  await mkdir10(dir, { recursive: true });
31611
32518
  }
31612
32519
  await writeFile10(outputPath, JSON.stringify(snapshot, null, 2) + "\n");
@@ -31622,7 +32529,7 @@ async function readSnapshot(inputPath) {
31622
32529
  function getDefaultSnapshotPath(cwd) {
31623
32530
  const cleoDir = getCleoDirAbsolute(cwd);
31624
32531
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
31625
- return join53(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
32532
+ return join55(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
31626
32533
  }
31627
32534
  async function importSnapshot(snapshot, cwd) {
31628
32535
  const accessor = await getAccessor(cwd);
@@ -31920,15 +32827,15 @@ var init_archive_stats2 = __esm({
31920
32827
  });
31921
32828
 
31922
32829
  // src/core/project-info.ts
31923
- import { readFileSync as readFileSync40, existsSync as existsSync55 } from "node:fs";
31924
- import { join as join54 } from "node:path";
32830
+ import { readFileSync as readFileSync42, existsSync as existsSync57 } from "node:fs";
32831
+ import { join as join56 } from "node:path";
31925
32832
  function getProjectInfoSync(cwd) {
31926
32833
  const projectRoot = cwd ?? process.cwd();
31927
32834
  const cleoDir = getCleoDirAbsolute(projectRoot);
31928
- const infoPath = join54(cleoDir, "project-info.json");
31929
- if (!existsSync55(infoPath)) return null;
32835
+ const infoPath = join56(cleoDir, "project-info.json");
32836
+ if (!existsSync57(infoPath)) return null;
31930
32837
  try {
31931
- const raw = readFileSync40(infoPath, "utf-8");
32838
+ const raw = readFileSync42(infoPath, "utf-8");
31932
32839
  const data = JSON.parse(raw);
31933
32840
  if (typeof data.projectHash !== "string" || data.projectHash.length === 0) {
31934
32841
  return null;
@@ -32015,8 +32922,8 @@ var init_defaults = __esm({
32015
32922
  });
32016
32923
 
32017
32924
  // src/mcp/lib/config.ts
32018
- import { readFileSync as readFileSync41, existsSync as existsSync56 } from "fs";
32019
- import { join as join55 } from "path";
32925
+ import { readFileSync as readFileSync43, existsSync as existsSync58 } from "fs";
32926
+ import { join as join57 } from "path";
32020
32927
  function loadFromEnv(key) {
32021
32928
  const envKey = `${ENV_PREFIX}${key.toUpperCase()}`;
32022
32929
  return process.env[envKey];
@@ -32037,12 +32944,12 @@ function parseEnvValue2(key, value) {
32037
32944
  }
32038
32945
  function loadFromFile(projectRoot) {
32039
32946
  const root = projectRoot || process.cwd();
32040
- const configPath = join55(root, ".cleo", "config.json");
32041
- if (!existsSync56(configPath)) {
32947
+ const configPath = join57(root, ".cleo", "config.json");
32948
+ if (!existsSync58(configPath)) {
32042
32949
  return {};
32043
32950
  }
32044
32951
  try {
32045
- const content = readFileSync41(configPath, "utf-8");
32952
+ const content = readFileSync43(configPath, "utf-8");
32046
32953
  const config = JSON.parse(content);
32047
32954
  const result = {};
32048
32955
  if (config.mcp) {
@@ -32382,8 +33289,8 @@ __export(session_grade_exports, {
32382
33289
  gradeSession: () => gradeSession,
32383
33290
  readGrades: () => readGrades
32384
33291
  });
32385
- import { join as join56 } from "node:path";
32386
- import { existsSync as existsSync57 } from "node:fs";
33292
+ import { join as join58 } from "node:path";
33293
+ import { existsSync as existsSync59 } from "node:fs";
32387
33294
  import { readFile as readFile14, appendFile, mkdir as mkdir11 } from "node:fs/promises";
32388
33295
  async function gradeSession(sessionId, cwd) {
32389
33296
  const sessionEntries = await queryAudit({ sessionId });
@@ -32563,9 +33470,9 @@ function detectDuplicateCreates(entries) {
32563
33470
  async function appendGradeResult(result, cwd) {
32564
33471
  try {
32565
33472
  const cleoDir = getCleoDirAbsolute(cwd);
32566
- const metricsDir = join56(cleoDir, "metrics");
33473
+ const metricsDir = join58(cleoDir, "metrics");
32567
33474
  await mkdir11(metricsDir, { recursive: true });
32568
- const gradesPath = join56(metricsDir, "GRADES.jsonl");
33475
+ const gradesPath = join58(metricsDir, "GRADES.jsonl");
32569
33476
  const line = JSON.stringify({ ...result, evaluator: "auto" }) + "\n";
32570
33477
  await appendFile(gradesPath, line, "utf8");
32571
33478
  } catch {
@@ -32574,8 +33481,8 @@ async function appendGradeResult(result, cwd) {
32574
33481
  async function readGrades(sessionId, cwd) {
32575
33482
  try {
32576
33483
  const cleoDir = getCleoDirAbsolute(cwd);
32577
- const gradesPath = join56(cleoDir, "metrics", "GRADES.jsonl");
32578
- if (!existsSync57(gradesPath)) return [];
33484
+ const gradesPath = join58(cleoDir, "metrics", "GRADES.jsonl");
33485
+ if (!existsSync59(gradesPath)) return [];
32579
33486
  const content = await readFile14(gradesPath, "utf8");
32580
33487
  const results = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
32581
33488
  return sessionId ? results.filter((r) => r.sessionId === sessionId) : results;
@@ -34812,6 +35719,7 @@ var init_phase = __esm({
34812
35719
  });
34813
35720
 
34814
35721
  // src/dispatch/domains/pipeline.ts
35722
+ import { execFileSync as execFileSync9 } from "node:child_process";
34815
35723
  var PipelineHandler;
34816
35724
  var init_pipeline2 = __esm({
34817
35725
  "src/dispatch/domains/pipeline.ts"() {
@@ -34822,6 +35730,7 @@ var init_pipeline2 = __esm({
34822
35730
  init_data_accessor();
34823
35731
  init_engine();
34824
35732
  init_release_engine();
35733
+ init_channel();
34825
35734
  init_phase();
34826
35735
  init_phases();
34827
35736
  init_pipeline_manifest_sqlite();
@@ -34907,6 +35816,7 @@ var init_pipeline2 = __esm({
34907
35816
  "manifest.stats",
34908
35817
  "release.list",
34909
35818
  "release.show",
35819
+ "release.channel.show",
34910
35820
  "phase.show",
34911
35821
  "phase.list",
34912
35822
  "chain.show",
@@ -34926,6 +35836,7 @@ var init_pipeline2 = __esm({
34926
35836
  "release.push",
34927
35837
  "release.gates.run",
34928
35838
  "release.rollback",
35839
+ "release.cancel",
34929
35840
  "release.ship",
34930
35841
  "manifest.append",
34931
35842
  "manifest.archive",
@@ -35149,6 +36060,29 @@ var init_pipeline2 = __esm({
35149
36060
  const result = await releaseShow(version, this.projectRoot);
35150
36061
  return this.wrapEngineResult(result, "query", "release.show", startTime);
35151
36062
  }
36063
+ case "channel.show": {
36064
+ let currentBranch = "unknown";
36065
+ try {
36066
+ currentBranch = execFileSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
36067
+ encoding: "utf-8",
36068
+ stdio: "pipe",
36069
+ cwd: this.projectRoot
36070
+ }).trim();
36071
+ } catch {
36072
+ }
36073
+ const resolvedChannel = resolveChannelFromBranch(currentBranch);
36074
+ const distTag = channelToDistTag(resolvedChannel);
36075
+ const description = describeChannel(resolvedChannel);
36076
+ return this.wrapEngineResult({
36077
+ success: true,
36078
+ data: {
36079
+ branch: currentBranch,
36080
+ channel: resolvedChannel,
36081
+ distTag,
36082
+ description
36083
+ }
36084
+ }, "query", "release.channel.show", startTime);
36085
+ }
35152
36086
  default:
35153
36087
  return this.errorResponse(
35154
36088
  "query",
@@ -35267,6 +36201,20 @@ var init_pipeline2 = __esm({
35267
36201
  const result = await releaseRollback(version, reason, this.projectRoot);
35268
36202
  return this.wrapEngineResult(result, "mutate", "release.rollback", startTime);
35269
36203
  }
36204
+ case "cancel": {
36205
+ const version = params?.version;
36206
+ if (!version) {
36207
+ return this.errorResponse(
36208
+ "mutate",
36209
+ "release.cancel",
36210
+ "E_INVALID_INPUT",
36211
+ "version is required",
36212
+ startTime
36213
+ );
36214
+ }
36215
+ const result = await releaseCancel(version, this.projectRoot);
36216
+ return this.wrapEngineResult(result, "mutate", "release.cancel", startTime);
36217
+ }
35270
36218
  case "ship": {
35271
36219
  const version = params?.version;
35272
36220
  const epicId = params?.epicId;
@@ -35281,8 +36229,9 @@ var init_pipeline2 = __esm({
35281
36229
  }
35282
36230
  const remote = params?.remote;
35283
36231
  const dryRun = params?.dryRun;
36232
+ const bump = params?.bump;
35284
36233
  const result = await releaseShip(
35285
- { version, epicId, remote, dryRun },
36234
+ { version, epicId, remote, dryRun, bump },
35286
36235
  this.projectRoot
35287
36236
  );
35288
36237
  return this.wrapEngineResult(result, "mutate", "release.ship", startTime);
@@ -35753,11 +36702,11 @@ var init_pipeline2 = __esm({
35753
36702
  });
35754
36703
 
35755
36704
  // src/core/issue/diagnostics.ts
35756
- import { execFileSync as execFileSync8 } from "node:child_process";
36705
+ import { execFileSync as execFileSync10 } from "node:child_process";
35757
36706
  function collectDiagnostics() {
35758
36707
  const getVersion3 = (cmd, args) => {
35759
36708
  try {
35760
- return execFileSync8(cmd, args, {
36709
+ return execFileSync10(cmd, args, {
35761
36710
  encoding: "utf-8",
35762
36711
  stdio: ["pipe", "pipe", "pipe"]
35763
36712
  }).trim();
@@ -35808,7 +36757,7 @@ var init_build_config = __esm({
35808
36757
  "use strict";
35809
36758
  BUILD_CONFIG = {
35810
36759
  "name": "@cleocode/cleo",
35811
- "version": "2026.3.16",
36760
+ "version": "2026.3.18",
35812
36761
  "description": "CLEO V2 - TypeScript task management CLI for AI coding agents",
35813
36762
  "repository": {
35814
36763
  "owner": "kryptobaseddev",
@@ -35817,7 +36766,7 @@ var init_build_config = __esm({
35817
36766
  "url": "https://github.com/kryptobaseddev/cleo.git",
35818
36767
  "issuesUrl": "https://github.com/kryptobaseddev/cleo/issues"
35819
36768
  },
35820
- "buildDate": "2026-03-07T05:25:13.762Z",
36769
+ "buildDate": "2026-03-07T20:24:31.815Z",
35821
36770
  "templates": {
35822
36771
  "issueTemplatesDir": "templates/issue-templates"
35823
36772
  }
@@ -35826,8 +36775,8 @@ var init_build_config = __esm({
35826
36775
  });
35827
36776
 
35828
36777
  // 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";
36778
+ import { existsSync as existsSync60, readFileSync as readFileSync44, readdirSync as readdirSync17, writeFileSync as writeFileSync9 } from "node:fs";
36779
+ import { join as join59, basename as basename10 } from "node:path";
35831
36780
  function extractYamlField(content, field) {
35832
36781
  const regex = new RegExp(`^${field}:\\s*["']?(.+?)["']?\\s*$`, "m");
35833
36782
  const match = content.match(regex);
@@ -35859,9 +36808,9 @@ function extractYamlArray(content, field) {
35859
36808
  return items;
35860
36809
  }
35861
36810
  function parseTemplateFile2(filePath) {
35862
- if (!existsSync58(filePath)) return null;
36811
+ if (!existsSync60(filePath)) return null;
35863
36812
  try {
35864
- const content = readFileSync42(filePath, "utf-8");
36813
+ const content = readFileSync44(filePath, "utf-8");
35865
36814
  const fileName = basename10(filePath);
35866
36815
  const stem = fileName.replace(/\.ya?ml$/, "");
35867
36816
  const name = extractYamlField(content, "name");
@@ -35878,12 +36827,12 @@ function parseTemplateFile2(filePath) {
35878
36827
  function parseIssueTemplates2(projectDir) {
35879
36828
  try {
35880
36829
  const packageRoot = getPackageRoot();
35881
- const packagedTemplateDir = join57(packageRoot, PACKAGED_TEMPLATE_DIR);
35882
- if (existsSync58(packagedTemplateDir)) {
36830
+ const packagedTemplateDir = join59(packageRoot, PACKAGED_TEMPLATE_DIR);
36831
+ if (existsSync60(packagedTemplateDir)) {
35883
36832
  const templates3 = [];
35884
36833
  for (const file of readdirSync17(packagedTemplateDir)) {
35885
36834
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
35886
- const template = parseTemplateFile2(join57(packagedTemplateDir, file));
36835
+ const template = parseTemplateFile2(join59(packagedTemplateDir, file));
35887
36836
  if (template) templates3.push(template);
35888
36837
  }
35889
36838
  if (templates3.length > 0) return templates3;
@@ -35891,12 +36840,12 @@ function parseIssueTemplates2(projectDir) {
35891
36840
  } catch {
35892
36841
  }
35893
36842
  const dir = projectDir ?? getProjectRoot();
35894
- const templateDir = join57(dir, TEMPLATE_DIR);
35895
- if (!existsSync58(templateDir)) return [];
36843
+ const templateDir = join59(dir, TEMPLATE_DIR);
36844
+ if (!existsSync60(templateDir)) return [];
35896
36845
  const templates2 = [];
35897
36846
  for (const file of readdirSync17(templateDir)) {
35898
36847
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
35899
- const template = parseTemplateFile2(join57(templateDir, file));
36848
+ const template = parseTemplateFile2(join59(templateDir, file));
35900
36849
  if (template) templates2.push(template);
35901
36850
  }
35902
36851
  return templates2;
@@ -35905,10 +36854,10 @@ function getTemplateConfig(cwd) {
35905
36854
  const projectDir = cwd ?? getProjectRoot();
35906
36855
  const liveTemplates = parseIssueTemplates2(projectDir);
35907
36856
  if (liveTemplates.length > 0) return liveTemplates;
35908
- const cachePath = join57(getCleoDir(cwd), CACHE_FILE);
35909
- if (existsSync58(cachePath)) {
36857
+ const cachePath = join59(getCleoDir(cwd), CACHE_FILE);
36858
+ if (existsSync60(cachePath)) {
35910
36859
  try {
35911
- const cached = JSON.parse(readFileSync42(cachePath, "utf-8"));
36860
+ const cached = JSON.parse(readFileSync44(cachePath, "utf-8"));
35912
36861
  if (cached.templates?.length > 0) return cached.templates;
35913
36862
  } catch {
35914
36863
  }
@@ -35964,7 +36913,7 @@ var init_template_parser2 = __esm({
35964
36913
  });
35965
36914
 
35966
36915
  // src/core/issue/create.ts
35967
- import { execFileSync as execFileSync9 } from "node:child_process";
36916
+ import { execFileSync as execFileSync11 } from "node:child_process";
35968
36917
  function buildIssueBody(subcommand, rawBody, severity, area) {
35969
36918
  const template = getTemplateForSubcommand2(subcommand);
35970
36919
  const sectionLabel = template?.name ?? "Description";
@@ -35983,7 +36932,7 @@ function buildIssueBody(subcommand, rawBody, severity, area) {
35983
36932
  }
35984
36933
  function checkGhCli() {
35985
36934
  try {
35986
- execFileSync9("gh", ["--version"], {
36935
+ execFileSync11("gh", ["--version"], {
35987
36936
  encoding: "utf-8",
35988
36937
  stdio: ["pipe", "pipe", "pipe"]
35989
36938
  });
@@ -35993,7 +36942,7 @@ function checkGhCli() {
35993
36942
  });
35994
36943
  }
35995
36944
  try {
35996
- execFileSync9("gh", ["auth", "status", "--hostname", "github.com"], {
36945
+ execFileSync11("gh", ["auth", "status", "--hostname", "github.com"], {
35997
36946
  encoding: "utf-8",
35998
36947
  stdio: ["pipe", "pipe", "pipe"]
35999
36948
  });
@@ -36005,7 +36954,7 @@ function checkGhCli() {
36005
36954
  }
36006
36955
  function addGhIssue(title, body, labels) {
36007
36956
  try {
36008
- const result = execFileSync9("gh", [
36957
+ const result = execFileSync11("gh", [
36009
36958
  "issue",
36010
36959
  "create",
36011
36960
  "--repo",
@@ -36967,8 +37916,8 @@ var init_tools = __esm({
36967
37916
  });
36968
37917
 
36969
37918
  // 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";
37919
+ import { join as join60, basename as basename11 } from "node:path";
37920
+ import { existsSync as existsSync61, readFileSync as readFileSync45 } from "node:fs";
36972
37921
  import { z as z3 } from "zod";
36973
37922
  function validateSyntax(query) {
36974
37923
  if (!query) return false;
@@ -37004,9 +37953,9 @@ function getCurrentProject() {
37004
37953
  return process.env["NEXUS_CURRENT_PROJECT"];
37005
37954
  }
37006
37955
  try {
37007
- const infoPath = join58(process.cwd(), ".cleo", "project-info.json");
37008
- if (existsSync59(infoPath)) {
37009
- const data = JSON.parse(readFileSync43(infoPath, "utf-8"));
37956
+ const infoPath = join60(process.cwd(), ".cleo", "project-info.json");
37957
+ if (existsSync61(infoPath)) {
37958
+ const data = JSON.parse(readFileSync45(infoPath, "utf-8"));
37010
37959
  if (typeof data.name === "string" && data.name.length > 0) {
37011
37960
  return data.name;
37012
37961
  }
@@ -37042,7 +37991,7 @@ async function resolveProjectPath2(projectName) {
37042
37991
  return project.path;
37043
37992
  }
37044
37993
  async function readProjectTasks(projectPath) {
37045
- const tasksDbPath = join58(projectPath, ".cleo", "tasks.db");
37994
+ const tasksDbPath = join60(projectPath, ".cleo", "tasks.db");
37046
37995
  try {
37047
37996
  const accessor = await getAccessor(projectPath);
37048
37997
  const taskFile = await accessor.loadTaskFile();
@@ -37456,8 +38405,8 @@ var init_deps2 = __esm({
37456
38405
 
37457
38406
  // src/core/nexus/sharing/index.ts
37458
38407
  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";
38408
+ import { existsSync as existsSync62, readdirSync as readdirSync18, statSync as statSync8 } from "node:fs";
38409
+ import { join as join61, relative as relative4 } from "node:path";
37461
38410
  function matchesPattern(filePath, pattern) {
37462
38411
  const normalizedPath = filePath.replace(/^\/+|\/+$/g, "");
37463
38412
  const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
@@ -37482,7 +38431,7 @@ function collectCleoFiles(cleoDir) {
37482
38431
  const entries = readdirSync18(dir);
37483
38432
  for (const entry of entries) {
37484
38433
  if (entry === ".git") continue;
37485
- const fullPath = join59(dir, entry);
38434
+ const fullPath = join61(dir, entry);
37486
38435
  const relPath = relative4(cleoDir, fullPath);
37487
38436
  try {
37488
38437
  const stat5 = statSync8(fullPath);
@@ -37542,7 +38491,7 @@ function generateGitignoreEntries(sharing) {
37542
38491
  async function syncGitignore(cwd) {
37543
38492
  const config = await loadConfig(cwd);
37544
38493
  const projectRoot = getProjectRoot(cwd);
37545
- const gitignorePath = join59(projectRoot, ".gitignore");
38494
+ const gitignorePath = join61(projectRoot, ".gitignore");
37546
38495
  const entries = generateGitignoreEntries(config.sharing);
37547
38496
  const managedSection = [
37548
38497
  "",
@@ -37552,7 +38501,7 @@ async function syncGitignore(cwd) {
37552
38501
  ""
37553
38502
  ].join("\n");
37554
38503
  let content = "";
37555
- if (existsSync60(gitignorePath)) {
38504
+ if (existsSync62(gitignorePath)) {
37556
38505
  content = await readFile15(gitignorePath, "utf-8");
37557
38506
  }
37558
38507
  const startIdx = content.indexOf(GITIGNORE_START);
@@ -39114,40 +40063,49 @@ var init_session_resolver = __esm({
39114
40063
  }
39115
40064
  });
39116
40065
 
40066
+ // src/core/tasks/id-generator.ts
40067
+ function normalizeTaskId(input) {
40068
+ if (typeof input !== "string") return null;
40069
+ const trimmed = input.trim();
40070
+ if (trimmed === "") return null;
40071
+ const match = trimmed.match(/^[Tt]?(\d+)(?:_.*)?$/);
40072
+ if (!match) return null;
40073
+ return `T${match[1]}`;
40074
+ }
40075
+ var init_id_generator = __esm({
40076
+ "src/core/tasks/id-generator.ts"() {
40077
+ "use strict";
40078
+ init_data_accessor();
40079
+ }
40080
+ });
40081
+
39117
40082
  // src/dispatch/lib/security.ts
39118
40083
  import { resolve as resolve9, normalize, relative as relative5, isAbsolute as isAbsolute2 } from "path";
39119
- function sanitizeTaskId(id) {
39120
- if (typeof id !== "string") {
40084
+ function sanitizeTaskId(value) {
40085
+ if (typeof value !== "string") {
39121
40086
  throw new SecurityError(
39122
40087
  "Task ID must be a string",
39123
40088
  "E_INVALID_TASK_ID",
39124
40089
  "taskId"
39125
40090
  );
39126
40091
  }
39127
- const trimmed = id.trim();
39128
- if (trimmed.length === 0) {
40092
+ const normalized = normalizeTaskId(value);
40093
+ if (normalized === null) {
39129
40094
  throw new SecurityError(
39130
- "Task ID cannot be empty",
40095
+ `Invalid task ID format: ${value}`,
39131
40096
  "E_INVALID_TASK_ID",
39132
40097
  "taskId"
39133
40098
  );
39134
40099
  }
39135
- if (!TASK_ID_PATTERN2.test(trimmed)) {
39136
- throw new SecurityError(
39137
- `Invalid task ID format: "${trimmed}". Must match pattern T[0-9]+ (e.g., T123)`,
39138
- "E_INVALID_TASK_ID",
39139
- "taskId"
39140
- );
39141
- }
39142
- const numericPart = parseInt(trimmed.slice(1), 10);
40100
+ const numericPart = parseInt(normalized.slice(1), 10);
39143
40101
  if (numericPart > MAX_TASK_ID_NUMBER) {
39144
40102
  throw new SecurityError(
39145
- `Task ID numeric value exceeds maximum (${MAX_TASK_ID_NUMBER}): ${trimmed}`,
40103
+ `Task ID exceeds maximum value: ${value}`,
39146
40104
  "E_INVALID_TASK_ID",
39147
40105
  "taskId"
39148
40106
  );
39149
40107
  }
39150
- return trimmed;
40108
+ return normalized;
39151
40109
  }
39152
40110
  function sanitizePath(path, projectRoot) {
39153
40111
  if (typeof path !== "string") {
@@ -39246,14 +40204,14 @@ function sanitizeParams(params, projectRoot, context) {
39246
40204
  if (value === void 0 || value === null) {
39247
40205
  continue;
39248
40206
  }
39249
- if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId")) {
40207
+ if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId" || key === "parentId" || key === "newParentId" || key === "relatedId" || key === "targetId")) {
39250
40208
  if (key === "parent" && value === "") {
39251
40209
  continue;
39252
40210
  }
39253
40211
  sanitized[key] = sanitizeTaskId(value);
39254
40212
  continue;
39255
40213
  }
39256
- if (key === "depends" && Array.isArray(value)) {
40214
+ if ((key === "depends" || key === "addDepends" || key === "removeDepends") && Array.isArray(value)) {
39257
40215
  sanitized[key] = value.map((v) => {
39258
40216
  if (typeof v === "string") {
39259
40217
  return sanitizeTaskId(v);
@@ -39303,10 +40261,11 @@ function sanitizeParams(params, projectRoot, context) {
39303
40261
  }
39304
40262
  return sanitized;
39305
40263
  }
39306
- var SecurityError, TASK_ID_PATTERN2, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
40264
+ var SecurityError, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
39307
40265
  var init_security = __esm({
39308
40266
  "src/dispatch/lib/security.ts"() {
39309
40267
  "use strict";
40268
+ init_id_generator();
39310
40269
  init_schema();
39311
40270
  init_status_registry();
39312
40271
  SecurityError = class extends Error {
@@ -39317,7 +40276,6 @@ var init_security = __esm({
39317
40276
  this.name = "SecurityError";
39318
40277
  }
39319
40278
  };
39320
- TASK_ID_PATTERN2 = /^T[0-9]+$/;
39321
40279
  MAX_TASK_ID_NUMBER = 999999;
39322
40280
  CONTROL_CHAR_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
39323
40281
  DEFAULT_MAX_CONTENT_LENGTH = 64 * 1024;
@@ -39465,13 +40423,13 @@ var init_field_context = __esm({
39465
40423
  });
39466
40424
 
39467
40425
  // 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";
40426
+ import { existsSync as existsSync63, readFileSync as readFileSync46, writeFileSync as writeFileSync10 } from "node:fs";
40427
+ import { join as join62 } from "node:path";
39470
40428
  function getCurrentSessionId(cwd) {
39471
40429
  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;
40430
+ const sessionFile = join62(getCleoDir(cwd), ".current-session");
40431
+ if (existsSync63(sessionFile)) {
40432
+ return readFileSync46(sessionFile, "utf-8").trim() || null;
39475
40433
  }
39476
40434
  return null;
39477
40435
  }
@@ -40534,7 +41492,7 @@ async function stopTask2(sessionId, cwd) {
40534
41492
  }
40535
41493
  async function workHistory(sessionId, limit = 50, cwd) {
40536
41494
  const db = await getDb(cwd);
40537
- const rows = await db.select().from(taskWorkHistory).where(eq16(taskWorkHistory.sessionId, sessionId)).orderBy(desc5(taskWorkHistory.setAt)).limit(limit).all();
41495
+ const rows = await db.select().from(taskWorkHistory).where(eq16(taskWorkHistory.sessionId, sessionId)).orderBy(desc5(taskWorkHistory.setAt), desc5(taskWorkHistory.id)).limit(limit).all();
40538
41496
  return rows.map((r) => ({
40539
41497
  taskId: r.taskId,
40540
41498
  setAt: r.setAt,
@@ -40718,14 +41676,14 @@ __export(logger_exports, {
40718
41676
  logFileExists: () => logFileExists,
40719
41677
  readMigrationLog: () => readMigrationLog
40720
41678
  });
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";
41679
+ import { existsSync as existsSync66, mkdirSync as mkdirSync16, statSync as statSync9, appendFileSync as appendFileSync4 } from "node:fs";
41680
+ import { join as join69, dirname as dirname16, relative as relative6 } from "node:path";
40723
41681
  function createMigrationLogger(cleoDir, config) {
40724
41682
  return new MigrationLogger(cleoDir, config);
40725
41683
  }
40726
41684
  function readMigrationLog(logPath) {
40727
- const { readFileSync: readFileSync52 } = __require("node:fs");
40728
- const content = readFileSync52(logPath, "utf-8");
41685
+ const { readFileSync: readFileSync54 } = __require("node:fs");
41686
+ const content = readFileSync54(logPath, "utf-8");
40729
41687
  return content.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
40730
41688
  }
40731
41689
  function logFileExists(logPath) {
@@ -40740,14 +41698,14 @@ function logFileExists(logPath) {
40740
41698
  function getLatestMigrationLog(cleoDir) {
40741
41699
  try {
40742
41700
  const { readdirSync: readdirSync21, statSync: statSync10 } = __require("node:fs");
40743
- const logsDir = join67(cleoDir, "logs");
40744
- if (!existsSync64(logsDir)) {
41701
+ const logsDir = join69(cleoDir, "logs");
41702
+ if (!existsSync66(logsDir)) {
40745
41703
  return null;
40746
41704
  }
40747
41705
  const files = readdirSync21(logsDir).filter((f) => f.startsWith("migration-") && f.endsWith(".jsonl")).map((f) => ({
40748
41706
  name: f,
40749
- path: join67(logsDir, f),
40750
- mtime: statSync10(join67(logsDir, f)).mtime.getTime()
41707
+ path: join69(logsDir, f),
41708
+ mtime: statSync10(join69(logsDir, f)).mtime.getTime()
40751
41709
  })).sort((a, b) => b.mtime - a.mtime);
40752
41710
  return files.length > 0 ? files[0].path : null;
40753
41711
  } catch {
@@ -40777,9 +41735,9 @@ var init_logger2 = __esm({
40777
41735
  consoleOutput: config.consoleOutput ?? false
40778
41736
  };
40779
41737
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
40780
- this.logPath = join67(cleoDir, "logs", `migration-${timestamp}.jsonl`);
41738
+ this.logPath = join69(cleoDir, "logs", `migration-${timestamp}.jsonl`);
40781
41739
  const logsDir = dirname16(this.logPath);
40782
- if (!existsSync64(logsDir)) {
41740
+ if (!existsSync66(logsDir)) {
40783
41741
  mkdirSync16(logsDir, { recursive: true });
40784
41742
  }
40785
41743
  this.startTime = Date.now();
@@ -40868,7 +41826,7 @@ var init_logger2 = __esm({
40868
41826
  sourcePath: relative6(this.cleoDir, sourcePath),
40869
41827
  ...additionalData
40870
41828
  };
40871
- if (existsSync64(sourcePath)) {
41829
+ if (existsSync66(sourcePath)) {
40872
41830
  try {
40873
41831
  const stats2 = statSync9(sourcePath);
40874
41832
  data.sourceSize = stats2.size;
@@ -40878,7 +41836,7 @@ var init_logger2 = __esm({
40878
41836
  }
40879
41837
  if (targetPath) {
40880
41838
  data.targetPath = relative6(this.cleoDir, targetPath);
40881
- if (existsSync64(targetPath)) {
41839
+ if (existsSync66(targetPath)) {
40882
41840
  try {
40883
41841
  const stats2 = statSync9(targetPath);
40884
41842
  data.targetSize = stats2.size;
@@ -40947,15 +41905,15 @@ var init_logger2 = __esm({
40947
41905
  */
40948
41906
  cleanupOldLogs() {
40949
41907
  try {
40950
- const logsDir = join67(this.cleoDir, "logs");
40951
- if (!existsSync64(logsDir)) {
41908
+ const logsDir = join69(this.cleoDir, "logs");
41909
+ if (!existsSync66(logsDir)) {
40952
41910
  return;
40953
41911
  }
40954
41912
  const { readdirSync: readdirSync21, unlinkSync: unlinkSync5 } = __require("node:fs");
40955
41913
  const files = readdirSync21(logsDir).filter((f) => f.startsWith("migration-") && f.endsWith(".jsonl")).map((f) => ({
40956
41914
  name: f,
40957
- path: join67(logsDir, f),
40958
- mtime: statSync9(join67(logsDir, f)).mtime.getTime()
41915
+ path: join69(logsDir, f),
41916
+ mtime: statSync9(join69(logsDir, f)).mtime.getTime()
40959
41917
  })).sort((a, b) => b.mtime - a.mtime);
40960
41918
  const filesToRemove = files.slice(this.config.maxLogFiles);
40961
41919
  for (const file of filesToRemove) {
@@ -41051,8 +42009,8 @@ __export(state_exports, {
41051
42009
  verifySourceIntegrity: () => verifySourceIntegrity
41052
42010
  });
41053
42011
  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";
42012
+ import { join as join70 } from "node:path";
42013
+ import { existsSync as existsSync67 } from "node:fs";
41056
42014
  import { createHash as createHash8 } from "node:crypto";
41057
42015
  async function computeFileChecksum(filePath) {
41058
42016
  try {
@@ -41074,8 +42032,8 @@ async function countRecords(filePath, key) {
41074
42032
  async function createMigrationState(cleoDir, sourceFiles) {
41075
42033
  const files = sourceFiles ?? {};
41076
42034
  if (!files.todoJson) {
41077
- const todoPath = join68(cleoDir, "todo.json");
41078
- if (existsSync65(todoPath)) {
42035
+ const todoPath = join70(cleoDir, "todo.json");
42036
+ if (existsSync67(todoPath)) {
41079
42037
  files.todoJson = {
41080
42038
  path: todoPath,
41081
42039
  checksum: await computeFileChecksum(todoPath),
@@ -41084,8 +42042,8 @@ async function createMigrationState(cleoDir, sourceFiles) {
41084
42042
  }
41085
42043
  }
41086
42044
  if (!files.sessionsJson) {
41087
- const sessionsPath = join68(cleoDir, "sessions.json");
41088
- if (existsSync65(sessionsPath)) {
42045
+ const sessionsPath = join70(cleoDir, "sessions.json");
42046
+ if (existsSync67(sessionsPath)) {
41089
42047
  files.sessionsJson = {
41090
42048
  path: sessionsPath,
41091
42049
  checksum: await computeFileChecksum(sessionsPath),
@@ -41094,8 +42052,8 @@ async function createMigrationState(cleoDir, sourceFiles) {
41094
42052
  }
41095
42053
  }
41096
42054
  if (!files.archiveJson) {
41097
- const archivePath = join68(cleoDir, "todo-archive.json");
41098
- if (existsSync65(archivePath)) {
42055
+ const archivePath = join70(cleoDir, "todo-archive.json");
42056
+ if (existsSync67(archivePath)) {
41099
42057
  files.archiveJson = {
41100
42058
  path: archivePath,
41101
42059
  checksum: await computeFileChecksum(archivePath),
@@ -41123,7 +42081,7 @@ async function createMigrationState(cleoDir, sourceFiles) {
41123
42081
  return state;
41124
42082
  }
41125
42083
  async function writeMigrationState(cleoDir, state) {
41126
- const statePath = join68(cleoDir, STATE_FILENAME);
42084
+ const statePath = join70(cleoDir, STATE_FILENAME);
41127
42085
  const tempPath = `${statePath}.tmp`;
41128
42086
  await writeFile13(tempPath, JSON.stringify(state, null, 2));
41129
42087
  await writeFile13(statePath, await readFile19(tempPath));
@@ -41198,7 +42156,7 @@ async function addMigrationWarning(cleoDir, warning) {
41198
42156
  }
41199
42157
  async function loadMigrationState(cleoDir) {
41200
42158
  try {
41201
- const statePath = join68(cleoDir, STATE_FILENAME);
42159
+ const statePath = join70(cleoDir, STATE_FILENAME);
41202
42160
  const content = await readFile19(statePath, "utf-8");
41203
42161
  return JSON.parse(content);
41204
42162
  } catch {
@@ -41240,7 +42198,7 @@ async function failMigration(cleoDir, error) {
41240
42198
  }
41241
42199
  async function clearMigrationState(cleoDir) {
41242
42200
  try {
41243
- const statePath = join68(cleoDir, STATE_FILENAME);
42201
+ const statePath = join70(cleoDir, STATE_FILENAME);
41244
42202
  await unlink5(statePath);
41245
42203
  } catch {
41246
42204
  }
@@ -41280,7 +42238,7 @@ async function verifySourceIntegrity(cleoDir) {
41280
42238
  const missing = [];
41281
42239
  for (const [key, fileInfo] of Object.entries(state.sourceFiles)) {
41282
42240
  if (!fileInfo) continue;
41283
- if (!existsSync65(fileInfo.path)) {
42241
+ if (!existsSync67(fileInfo.path)) {
41284
42242
  missing.push(key);
41285
42243
  continue;
41286
42244
  }
@@ -41311,8 +42269,8 @@ __export(migration_sqlite_exports, {
41311
42269
  migrateJsonToSqlite: () => migrateJsonToSqlite2,
41312
42270
  migrateJsonToSqliteAtomic: () => migrateJsonToSqliteAtomic
41313
42271
  });
41314
- import { existsSync as existsSync66, readFileSync as readFileSync47 } from "node:fs";
41315
- import { join as join69, dirname as dirname17 } from "node:path";
42272
+ import { existsSync as existsSync68, readFileSync as readFileSync49 } from "node:fs";
42273
+ import { join as join71, dirname as dirname17 } from "node:path";
41316
42274
  import { mkdirSync as mkdirSync17 } from "node:fs";
41317
42275
  import { drizzle as drizzle4 } from "drizzle-orm/sqlite-proxy";
41318
42276
  import { migrate as migrate4 } from "drizzle-orm/sqlite-proxy/migrator";
@@ -41350,26 +42308,26 @@ function countJsonRecords(cleoDir) {
41350
42308
  let tasks2 = 0;
41351
42309
  let archived = 0;
41352
42310
  let sessions2 = 0;
41353
- const todoPath = join69(cleoDir, "todo.json");
41354
- if (existsSync66(todoPath)) {
42311
+ const todoPath = join71(cleoDir, "todo.json");
42312
+ if (existsSync68(todoPath)) {
41355
42313
  try {
41356
- const data = JSON.parse(readFileSync47(todoPath, "utf-8"));
42314
+ const data = JSON.parse(readFileSync49(todoPath, "utf-8"));
41357
42315
  tasks2 = (data.tasks ?? []).length;
41358
42316
  } catch {
41359
42317
  }
41360
42318
  }
41361
- const archivePath = join69(cleoDir, "todo-archive.json");
41362
- if (existsSync66(archivePath)) {
42319
+ const archivePath = join71(cleoDir, "todo-archive.json");
42320
+ if (existsSync68(archivePath)) {
41363
42321
  try {
41364
- const data = JSON.parse(readFileSync47(archivePath, "utf-8"));
42322
+ const data = JSON.parse(readFileSync49(archivePath, "utf-8"));
41365
42323
  archived = (data.tasks ?? data.archivedTasks ?? []).length;
41366
42324
  } catch {
41367
42325
  }
41368
42326
  }
41369
- const sessionsPath = join69(cleoDir, "sessions.json");
41370
- if (existsSync66(sessionsPath)) {
42327
+ const sessionsPath = join71(cleoDir, "sessions.json");
42328
+ if (existsSync68(sessionsPath)) {
41371
42329
  try {
41372
- const data = JSON.parse(readFileSync47(sessionsPath, "utf-8"));
42330
+ const data = JSON.parse(readFileSync49(sessionsPath, "utf-8"));
41373
42331
  sessions2 = (data.sessions ?? []).length;
41374
42332
  } catch {
41375
42333
  }
@@ -41446,13 +42404,13 @@ async function migrateJsonToSqliteAtomic(cwd, tempDbPath, logger) {
41446
42404
  }
41447
42405
  }
41448
42406
  async function runMigrationDataImport(db, cleoDir, result, logger) {
41449
- const todoPath = join69(cleoDir, "todo.json");
41450
- if (existsSync66(todoPath)) {
42407
+ const todoPath = join71(cleoDir, "todo.json");
42408
+ if (existsSync68(todoPath)) {
41451
42409
  try {
41452
42410
  logger?.info("import", "read-todo", "Reading todo.json", {
41453
42411
  path: todoPath.replace(cleoDir, ".")
41454
42412
  });
41455
- const todoData = JSON.parse(readFileSync47(todoPath, "utf-8"));
42413
+ const todoData = JSON.parse(readFileSync49(todoPath, "utf-8"));
41456
42414
  const tasks2 = topoSortTasks(todoData.tasks ?? []);
41457
42415
  const totalTasks = tasks2.length;
41458
42416
  logger?.info("import", "tasks-start", `Starting import of ${totalTasks} tasks`, {
@@ -41521,13 +42479,13 @@ async function runMigrationDataImport(db, cleoDir, result, logger) {
41521
42479
  result.warnings.push("todo.json not found, skipping task import");
41522
42480
  logger?.warn("import", "todo-missing", "todo.json not found, skipping task import");
41523
42481
  }
41524
- const archivePath = join69(cleoDir, "todo-archive.json");
41525
- if (existsSync66(archivePath)) {
42482
+ const archivePath = join71(cleoDir, "todo-archive.json");
42483
+ if (existsSync68(archivePath)) {
41526
42484
  try {
41527
42485
  logger?.info("import", "read-archive", "Reading todo-archive.json", {
41528
42486
  path: archivePath.replace(cleoDir, ".")
41529
42487
  });
41530
- const archiveData = JSON.parse(readFileSync47(archivePath, "utf-8"));
42488
+ const archiveData = JSON.parse(readFileSync49(archivePath, "utf-8"));
41531
42489
  const archivedTasks = topoSortTasks(archiveData.tasks ?? archiveData.archivedTasks ?? []);
41532
42490
  const totalArchived = archivedTasks.length;
41533
42491
  logger?.info("import", "archive-start", `Starting import of ${totalArchived} archived tasks`, {
@@ -41580,13 +42538,13 @@ async function runMigrationDataImport(db, cleoDir, result, logger) {
41580
42538
  logger?.error("import", "parse-archive", errorMsg);
41581
42539
  }
41582
42540
  }
41583
- const sessionsPath = join69(cleoDir, "sessions.json");
41584
- if (existsSync66(sessionsPath)) {
42541
+ const sessionsPath = join71(cleoDir, "sessions.json");
42542
+ if (existsSync68(sessionsPath)) {
41585
42543
  try {
41586
42544
  logger?.info("import", "read-sessions", "Reading sessions.json", {
41587
42545
  path: sessionsPath.replace(cleoDir, ".")
41588
42546
  });
41589
- const sessionsData = JSON.parse(readFileSync47(sessionsPath, "utf-8"));
42547
+ const sessionsData = JSON.parse(readFileSync49(sessionsPath, "utf-8"));
41590
42548
  const sessions2 = sessionsData.sessions ?? [];
41591
42549
  const totalSessions = sessions2.length;
41592
42550
  logger?.info("import", "sessions-start", `Starting import of ${totalSessions} sessions`, {
@@ -41718,10 +42676,10 @@ async function migrateJsonToSqlite2(cwd, options) {
41718
42676
  return result;
41719
42677
  }
41720
42678
  const db = await getDb(cwd);
41721
- const todoPath = join69(cleoDir, "todo.json");
41722
- if (existsSync66(todoPath)) {
42679
+ const todoPath = join71(cleoDir, "todo.json");
42680
+ if (existsSync68(todoPath)) {
41723
42681
  try {
41724
- const todoData = JSON.parse(readFileSync47(todoPath, "utf-8"));
42682
+ const todoData = JSON.parse(readFileSync49(todoPath, "utf-8"));
41725
42683
  const tasks2 = topoSortTasks(todoData.tasks ?? []);
41726
42684
  for (const task of tasks2) {
41727
42685
  try {
@@ -41770,10 +42728,10 @@ async function migrateJsonToSqlite2(cwd, options) {
41770
42728
  } else {
41771
42729
  result.warnings.push("todo.json not found, skipping task import");
41772
42730
  }
41773
- const archivePath = join69(cleoDir, "todo-archive.json");
41774
- if (existsSync66(archivePath)) {
42731
+ const archivePath = join71(cleoDir, "todo-archive.json");
42732
+ if (existsSync68(archivePath)) {
41775
42733
  try {
41776
- const archiveData = JSON.parse(readFileSync47(archivePath, "utf-8"));
42734
+ const archiveData = JSON.parse(readFileSync49(archivePath, "utf-8"));
41777
42735
  const archivedTasks = topoSortTasks(archiveData.tasks ?? archiveData.archivedTasks ?? []);
41778
42736
  for (const task of archivedTasks) {
41779
42737
  try {
@@ -41810,11 +42768,11 @@ async function migrateJsonToSqlite2(cwd, options) {
41810
42768
  result.errors.push(`Failed to parse todo-archive.json: ${String(err)}`);
41811
42769
  }
41812
42770
  }
41813
- const sessionsPath = join69(cleoDir, "sessions.json");
41814
- if (existsSync66(sessionsPath)) {
42771
+ const sessionsPath = join71(cleoDir, "sessions.json");
42772
+ if (existsSync68(sessionsPath)) {
41815
42773
  try {
41816
42774
  const sessionsData = JSON.parse(
41817
- readFileSync47(sessionsPath, "utf-8")
42775
+ readFileSync49(sessionsPath, "utf-8")
41818
42776
  );
41819
42777
  const sessions2 = sessionsData.sessions ?? [];
41820
42778
  for (const session of sessions2) {
@@ -41882,8 +42840,8 @@ var init_migration_sqlite = __esm({
41882
42840
 
41883
42841
  // src/cli/index.ts
41884
42842
  import { Command, Help } from "commander";
41885
- import { readFileSync as readFileSync51 } from "node:fs";
41886
- import { join as join75 } from "node:path";
42843
+ import { readFileSync as readFileSync53 } from "node:fs";
42844
+ import { join as join77 } from "node:path";
41887
42845
 
41888
42846
  // src/cli/commands/add.ts
41889
42847
  init_cli();
@@ -42498,12 +43456,13 @@ function registerReleaseCommand(program2) {
42498
43456
  notes: opts["notes"]
42499
43457
  }, { command: "release" });
42500
43458
  });
42501
- release2.command("ship <version>").description("Ship a release: gates \u2192 changelog \u2192 commit \u2192 tag \u2192 push").requiredOption("--epic <id>", "Epic task ID for commit message (e.g. T5576)").option("--dry-run", "Preview all actions without writing anything").option("--no-push", "Commit and tag but skip git push").option("--remote <remote>", "Git remote to push to (default: origin)").action(async (version, opts) => {
43459
+ release2.command("ship <version>").description("Ship a release: gates \u2192 changelog \u2192 commit \u2192 tag \u2192 push").requiredOption("--epic <id>", "Epic task ID for commit message (e.g. T5576)").option("--dry-run", "Preview all actions without writing anything").option("--no-push", "Commit and tag but skip git push").option("--no-bump", "Skip version file bumping (default: bump if configured)").option("--remote <remote>", "Git remote to push to (default: origin)").action(async (version, opts) => {
42502
43460
  await dispatchFromCli("mutate", "pipeline", "release.ship", {
42503
43461
  version,
42504
43462
  epicId: opts["epic"],
42505
43463
  dryRun: opts["dryRun"],
42506
43464
  push: opts["push"] !== false,
43465
+ bump: opts["bump"] !== false,
42507
43466
  remote: opts["remote"]
42508
43467
  }, { command: "release" });
42509
43468
  });
@@ -42516,6 +43475,9 @@ function registerReleaseCommand(program2) {
42516
43475
  release2.command("changelog <version>").description("Generate changelog for a release").action(async (version) => {
42517
43476
  await dispatchFromCli("mutate", "pipeline", "release.changelog", { version }, { command: "release" });
42518
43477
  });
43478
+ release2.command("cancel <version>").description("Cancel and remove a release in draft or prepared state").action(async (version) => {
43479
+ await dispatchFromCli("mutate", "pipeline", "release.cancel", { version }, { command: "release" });
43480
+ });
42519
43481
  }
42520
43482
 
42521
43483
  // src/cli/commands/env.ts
@@ -42733,14 +43695,14 @@ init_errors();
42733
43695
  init_exit_codes();
42734
43696
  init_paths();
42735
43697
  init_json();
42736
- import { join as join61 } from "node:path";
43698
+ import { join as join63 } from "node:path";
42737
43699
  var VALID_CATEGORIES = ["write", "read", "sync", "maintenance"];
42738
43700
  var VALID_RELEVANCE = ["critical", "high", "medium", "low"];
42739
43701
  async function locateCommandsIndex() {
42740
43702
  const cleoHome = getCleoHome();
42741
43703
  const paths = [
42742
- join61(cleoHome, "docs", "commands", "COMMANDS-INDEX.json"),
42743
- join61(process.cwd(), "docs", "commands", "COMMANDS-INDEX.json")
43704
+ join63(cleoHome, "docs", "commands", "COMMANDS-INDEX.json"),
43705
+ join63(process.cwd(), "docs", "commands", "COMMANDS-INDEX.json")
42744
43706
  ];
42745
43707
  for (const p of paths) {
42746
43708
  const data = await readJson(p);
@@ -42821,9 +43783,9 @@ init_errors();
42821
43783
  init_json();
42822
43784
  init_paths();
42823
43785
  import { readdir as readdir2, readFile as readFile16 } from "node:fs/promises";
42824
- import { join as join62 } from "node:path";
43786
+ import { join as join64 } from "node:path";
42825
43787
  async function getScriptNames(projectRoot) {
42826
- const scriptsDir = join62(projectRoot, "scripts");
43788
+ const scriptsDir = join64(projectRoot, "scripts");
42827
43789
  try {
42828
43790
  const files = await readdir2(scriptsDir);
42829
43791
  return files.filter((f) => f.endsWith(".sh")).map((f) => f.replace(".sh", "")).sort();
@@ -42832,7 +43794,7 @@ async function getScriptNames(projectRoot) {
42832
43794
  }
42833
43795
  }
42834
43796
  async function getIndexedCommands(projectRoot) {
42835
- const indexPath = join62(projectRoot, "docs", "commands", "COMMANDS-INDEX.json");
43797
+ const indexPath = join64(projectRoot, "docs", "commands", "COMMANDS-INDEX.json");
42836
43798
  const index5 = await readJson(indexPath);
42837
43799
  if (!index5) return [];
42838
43800
  return index5.commands.map((c) => c.name).sort();
@@ -42865,7 +43827,7 @@ async function runGapCheck(_projectRoot, filterId) {
42865
43827
  const reviewFiles = files.filter((f) => f.endsWith(".md"));
42866
43828
  for (const file of reviewFiles) {
42867
43829
  if (filterId && !file.includes(filterId)) continue;
42868
- const filePath = join62(reviewDir, file);
43830
+ const filePath = join64(reviewDir, file);
42869
43831
  const content = await readFile16(filePath, "utf-8");
42870
43832
  const taskMatch = file.match(/^(T\d+)/);
42871
43833
  const taskId = taskMatch ? taskMatch[1] : "UNKNOWN";
@@ -42969,7 +43931,7 @@ init_paths();
42969
43931
  init_json();
42970
43932
  init_data_accessor();
42971
43933
  import { readFile as readFile17, rm as rm3, stat as stat3 } from "node:fs/promises";
42972
- import { join as join63 } from "node:path";
43934
+ import { join as join65 } from "node:path";
42973
43935
  function parseTaskId(content) {
42974
43936
  const match = content.match(/^\[T(\d+)\]/);
42975
43937
  return match ? `T${match[1]}` : null;
@@ -43025,7 +43987,7 @@ function registerExtractCommand(program2) {
43025
43987
  const accessor = await getAccessor();
43026
43988
  const taskData = await accessor.loadTaskFile();
43027
43989
  const cleoDir = getCleoDir();
43028
- const stateFile = join63(cleoDir, "sync", "todowrite-session.json");
43990
+ const stateFile = join65(cleoDir, "sync", "todowrite-session.json");
43029
43991
  let sessionState = null;
43030
43992
  try {
43031
43993
  sessionState = await readJson(stateFile);
@@ -43211,17 +44173,17 @@ init_renderers();
43211
44173
  init_errors();
43212
44174
  init_exit_codes();
43213
44175
  init_paths();
43214
- import { spawn as spawn2, execFileSync as execFileSync10 } from "node:child_process";
44176
+ import { spawn as spawn2, execFileSync as execFileSync12 } from "node:child_process";
43215
44177
  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";
44178
+ import { join as join66 } from "node:path";
43217
44179
  var DEFAULT_PORT = 3456;
43218
44180
  var DEFAULT_HOST = "127.0.0.1";
43219
44181
  function getWebPaths() {
43220
44182
  const cleoHome = getCleoHome();
43221
44183
  return {
43222
- pidFile: join64(cleoHome, "web-server.pid"),
43223
- configFile: join64(cleoHome, "web-server.json"),
43224
- logFile: join64(cleoHome, "logs", "web-server.log")
44184
+ pidFile: join66(cleoHome, "web-server.pid"),
44185
+ configFile: join66(cleoHome, "web-server.json"),
44186
+ logFile: join66(cleoHome, "logs", "web-server.log")
43225
44187
  };
43226
44188
  }
43227
44189
  function isProcessRunning(pid) {
@@ -43265,19 +44227,19 @@ function registerWebCommand(program2) {
43265
44227
  throw new CleoError(1 /* GENERAL_ERROR */, `Server already running (PID: ${status.pid})`);
43266
44228
  }
43267
44229
  const projectRoot = process.env["CLEO_ROOT"] ?? process.cwd();
43268
- const distMcpDir = join64(projectRoot, "dist", "mcp");
43269
- await mkdir12(join64(getCleoHome(), "logs"), { recursive: true });
44230
+ const distMcpDir = join66(projectRoot, "dist", "mcp");
44231
+ await mkdir12(join66(getCleoHome(), "logs"), { recursive: true });
43270
44232
  await writeFile12(configFile, JSON.stringify({
43271
44233
  port,
43272
44234
  host,
43273
44235
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
43274
44236
  }));
43275
- const webIndexPath = join64(distMcpDir, "index.js");
44237
+ const webIndexPath = join66(distMcpDir, "index.js");
43276
44238
  try {
43277
44239
  await stat4(webIndexPath);
43278
44240
  } catch {
43279
44241
  try {
43280
- execFileSync10("npm", ["run", "build"], { cwd: projectRoot, stdio: "ignore" });
44242
+ execFileSync12("npm", ["run", "build"], { cwd: projectRoot, stdio: "ignore" });
43281
44243
  } catch {
43282
44244
  throw new CleoError(1 /* GENERAL_ERROR */, `Build failed. Check logs: ${logFile}`);
43283
44245
  }
@@ -43496,13 +44458,13 @@ init_renderers();
43496
44458
  init_errors();
43497
44459
  init_exit_codes();
43498
44460
  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";
44461
+ import { existsSync as existsSync64, readFileSync as readFileSync47, writeFileSync as writeFileSync11, mkdirSync as mkdirSync15 } from "node:fs";
44462
+ import { execFileSync as execFileSync13 } from "node:child_process";
44463
+ import { join as join67, dirname as dirname15 } from "node:path";
43502
44464
  function getChangelogSource(cwd) {
43503
44465
  const configPath = getConfigPath(cwd);
43504
44466
  try {
43505
- const config = JSON.parse(readFileSync45(configPath, "utf-8"));
44467
+ const config = JSON.parse(readFileSync47(configPath, "utf-8"));
43506
44468
  return config?.release?.changelog?.source ?? "CHANGELOG.md";
43507
44469
  } catch {
43508
44470
  return "CHANGELOG.md";
@@ -43511,7 +44473,7 @@ function getChangelogSource(cwd) {
43511
44473
  function getEnabledPlatforms(cwd) {
43512
44474
  const configPath = getConfigPath(cwd);
43513
44475
  try {
43514
- const config = JSON.parse(readFileSync45(configPath, "utf-8"));
44476
+ const config = JSON.parse(readFileSync47(configPath, "utf-8"));
43515
44477
  const outputs = config?.release?.changelog?.outputs ?? [];
43516
44478
  return outputs.filter((o) => o.enabled);
43517
44479
  } catch {
@@ -43533,7 +44495,7 @@ function getDefaultOutputPath(platform2) {
43533
44495
  function getGitHubRepoSlug(cwd) {
43534
44496
  const projectRoot = getProjectRoot(cwd);
43535
44497
  try {
43536
- const remoteUrl = execFileSync11("git", ["remote", "get-url", "origin"], {
44498
+ const remoteUrl = execFileSync13("git", ["remote", "get-url", "origin"], {
43537
44499
  cwd: projectRoot,
43538
44500
  encoding: "utf-8",
43539
44501
  stdio: ["pipe", "pipe", "pipe"]
@@ -43638,11 +44600,11 @@ function registerGenerateChangelogCommand(program2) {
43638
44600
  const targetPlatform = opts["platform"];
43639
44601
  const dryRun = !!opts["dryRun"];
43640
44602
  const sourceFile = getChangelogSource();
43641
- const sourcePath = join65(getProjectRoot(), sourceFile);
43642
- if (!existsSync62(sourcePath)) {
44603
+ const sourcePath = join67(getProjectRoot(), sourceFile);
44604
+ if (!existsSync64(sourcePath)) {
43643
44605
  throw new CleoError(4 /* NOT_FOUND */, `Changelog source not found: ${sourcePath}`);
43644
44606
  }
43645
- const sourceContent = readFileSync45(sourcePath, "utf-8");
44607
+ const sourceContent = readFileSync47(sourcePath, "utf-8");
43646
44608
  const repoSlug = getGitHubRepoSlug();
43647
44609
  const results = [];
43648
44610
  if (targetPlatform) {
@@ -43651,9 +44613,9 @@ function registerGenerateChangelogCommand(program2) {
43651
44613
  const outputPath = platformConfig?.path ?? getDefaultOutputPath(targetPlatform);
43652
44614
  const content = generateForPlatform(targetPlatform, sourceContent, repoSlug, limit);
43653
44615
  if (!dryRun) {
43654
- const fullPath = join65(getProjectRoot(), outputPath);
44616
+ const fullPath = join67(getProjectRoot(), outputPath);
43655
44617
  mkdirSync15(dirname15(fullPath), { recursive: true });
43656
- writeFileSync10(fullPath, content, "utf-8");
44618
+ writeFileSync11(fullPath, content, "utf-8");
43657
44619
  }
43658
44620
  results.push({ platform: targetPlatform, path: outputPath, written: !dryRun });
43659
44621
  } else {
@@ -43672,9 +44634,9 @@ function registerGenerateChangelogCommand(program2) {
43672
44634
  limit
43673
44635
  );
43674
44636
  if (!dryRun) {
43675
- const fullPath = join65(getProjectRoot(), platformConfig.path);
44637
+ const fullPath = join67(getProjectRoot(), platformConfig.path);
43676
44638
  mkdirSync15(dirname15(fullPath), { recursive: true });
43677
- writeFileSync10(fullPath, content, "utf-8");
44639
+ writeFileSync11(fullPath, content, "utf-8");
43678
44640
  }
43679
44641
  results.push({
43680
44642
  platform: platformConfig.platform,
@@ -43703,7 +44665,7 @@ function registerGenerateChangelogCommand(program2) {
43703
44665
  init_cli();
43704
44666
  init_renderers();
43705
44667
  init_build_config();
43706
- import { execFileSync as execFileSync12 } from "node:child_process";
44668
+ import { execFileSync as execFileSync14 } from "node:child_process";
43707
44669
  var CLEO_REPO2 = BUILD_CONFIG.repository.fullName;
43708
44670
  function registerIssueCommand(program2) {
43709
44671
  const issueCmd = program2.command("issue").description("File bug reports, feature requests, or questions to CLEO GitHub repo");
@@ -43742,7 +44704,7 @@ async function handleIssueType(issueType, opts) {
43742
44704
  if (opts["open"] && typeof result["url"] === "string" && result["url"].startsWith("https://")) {
43743
44705
  const issueNumber = result["url"].match(/(\d+)$/)?.[1] ?? "unknown";
43744
44706
  try {
43745
- execFileSync12("gh", ["issue", "view", issueNumber, "--repo", CLEO_REPO2, "--web"], {
44707
+ execFileSync14("gh", ["issue", "view", issueNumber, "--repo", CLEO_REPO2, "--web"], {
43746
44708
  stdio: ["pipe", "pipe", "pipe"]
43747
44709
  });
43748
44710
  } catch {
@@ -44427,22 +45389,22 @@ function registerPlanCommand(program2) {
44427
45389
  }
44428
45390
 
44429
45391
  // 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";
45392
+ import { readFileSync as readFileSync48, existsSync as existsSync65, writeFileSync as writeFileSync12, copyFileSync as copyFileSync4 } from "node:fs";
45393
+ import { join as join68 } from "node:path";
44432
45394
  function getProjectRoot2() {
44433
45395
  let dir = process.cwd();
44434
45396
  while (dir !== "/") {
44435
- if (existsSync63(join66(dir, ".cleo", "config.json"))) return dir;
44436
- dir = join66(dir, "..");
45397
+ if (existsSync65(join68(dir, ".cleo", "config.json"))) return dir;
45398
+ dir = join68(dir, "..");
44437
45399
  }
44438
45400
  return process.cwd();
44439
45401
  }
44440
45402
  function getTokenFilePath() {
44441
- return join66(getProjectRoot2(), ".cleo", "metrics", "TOKEN_USAGE.jsonl");
45403
+ return join68(getProjectRoot2(), ".cleo", "metrics", "TOKEN_USAGE.jsonl");
44442
45404
  }
44443
45405
  function readJsonlFile(filePath) {
44444
- if (!existsSync63(filePath)) return [];
44445
- const content = readFileSync46(filePath, "utf-8").trim();
45406
+ if (!existsSync65(filePath)) return [];
45407
+ const content = readFileSync48(filePath, "utf-8").trim();
44446
45408
  if (!content) return [];
44447
45409
  return content.split("\n").map((line) => JSON.parse(line));
44448
45410
  }
@@ -44524,10 +45486,10 @@ async function getRealTokenUsage(_opts) {
44524
45486
  }
44525
45487
  async function clearOtelData() {
44526
45488
  const tokenFile = getTokenFilePath();
44527
- if (existsSync63(tokenFile)) {
45489
+ if (existsSync65(tokenFile)) {
44528
45490
  const backup = `${tokenFile}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
44529
45491
  copyFileSync4(tokenFile, backup);
44530
- writeFileSync11(tokenFile, "");
45492
+ writeFileSync12(tokenFile, "");
44531
45493
  return { message: "Token tracking cleared", backup };
44532
45494
  }
44533
45495
  return { message: "No token file to clear" };
@@ -44889,8 +45851,8 @@ init_storage_preflight();
44889
45851
  init_paths();
44890
45852
  init_storage_preflight();
44891
45853
  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";
45854
+ import { existsSync as existsSync69, readFileSync as readFileSync50, writeFileSync as writeFileSync13, mkdirSync as mkdirSync18, readdirSync as readdirSync19, copyFileSync as copyFileSync5 } from "node:fs";
45855
+ import { join as join72 } from "node:path";
44894
45856
 
44895
45857
  // src/store/index.ts
44896
45858
  init_atomic();
@@ -44926,8 +45888,8 @@ async function runUpgrade(options = {}) {
44926
45888
  return { success: false, upToDate: false, dryRun: isDryRun, actions, applied: 0, errors: [String(err)] };
44927
45889
  }
44928
45890
  const cleoDir = getCleoDirAbsolute(options.cwd);
44929
- const dbPath = join70(cleoDir, "tasks.db");
44930
- const dbExists2 = existsSync67(dbPath);
45891
+ const dbPath = join72(cleoDir, "tasks.db");
45892
+ const dbExists2 = existsSync69(dbPath);
44931
45893
  const legacyRecordCount = preflight.details.todoJsonTaskCount + preflight.details.archiveJsonTaskCount + preflight.details.sessionsJsonCount;
44932
45894
  const needsMigration = !dbExists2 && legacyRecordCount > 0;
44933
45895
  const needsCleanup = dbExists2 && preflight.migrationNeeded;
@@ -44943,7 +45905,7 @@ async function runUpgrade(options = {}) {
44943
45905
  let migrationLock = null;
44944
45906
  try {
44945
45907
  const cleoDir2 = getCleoDirAbsolute(options.cwd);
44946
- const dbPath2 = join70(cleoDir2, "tasks.db");
45908
+ const dbPath2 = join72(cleoDir2, "tasks.db");
44947
45909
  try {
44948
45910
  migrationLock = await acquireLock(dbPath2, { stale: 3e4, retries: 0 });
44949
45911
  } catch {
@@ -44968,23 +45930,23 @@ async function runUpgrade(options = {}) {
44968
45930
  } = await Promise.resolve().then(() => (init_state(), state_exports));
44969
45931
  const logger = new MigrationLogger2(cleoDir2);
44970
45932
  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: "" }
45933
+ todoJson: { path: join72(cleoDir2, "todo.json"), checksum: "" },
45934
+ sessionsJson: { path: join72(cleoDir2, "sessions.json"), checksum: "" },
45935
+ archiveJson: { path: join72(cleoDir2, "todo-archive.json"), checksum: "" }
44974
45936
  });
44975
45937
  await updateMigrationPhase2(cleoDir2, "backup");
44976
45938
  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)) {
45939
+ const dbBackupPath = join72(cleoDir2, "backups", "safety", `tasks.db.pre-migration.${Date.now()}`);
45940
+ const dbTempPath = join72(cleoDir2, "tasks.db.migrating");
45941
+ if (existsSync69(dbPath2)) {
45942
+ const backupDir = join72(cleoDir2, "backups", "safety");
45943
+ if (!existsSync69(backupDir)) {
44982
45944
  mkdirSync18(backupDir, { recursive: true });
44983
45945
  }
44984
45946
  copyFileSync5(dbPath2, dbBackupPath);
44985
45947
  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");
45948
+ const origChecksum = createHash9("sha256").update(readFileSync50(dbPath2)).digest("hex");
45949
+ const backupChecksum = createHash9("sha256").update(readFileSync50(dbBackupPath)).digest("hex");
44988
45950
  if (origChecksum !== backupChecksum) {
44989
45951
  throw new Error(
44990
45952
  `Backup verification failed: checksum mismatch. Aborting migration to prevent data loss.`
@@ -44999,14 +45961,14 @@ async function runUpgrade(options = {}) {
44999
45961
  }
45000
45962
  logger.info("backup", "verified", "Backup integrity verified", { checksum: origChecksum });
45001
45963
  }
45002
- if (existsSync67(dbTempPath)) {
45964
+ if (existsSync69(dbTempPath)) {
45003
45965
  const { unlinkSync: unlinkSync5 } = await import("node:fs");
45004
45966
  unlinkSync5(dbTempPath);
45005
45967
  }
45006
- const configPath = join70(cleoDir2, "config.json");
45968
+ const configPath = join72(cleoDir2, "config.json");
45007
45969
  let configBackup = null;
45008
- if (existsSync67(configPath)) {
45009
- configBackup = readFileSync48(configPath, "utf-8");
45970
+ if (existsSync69(configPath)) {
45971
+ configBackup = readFileSync50(configPath, "utf-8");
45010
45972
  }
45011
45973
  const { resetDbState: resetDbState2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
45012
45974
  resetDbState2();
@@ -45032,10 +45994,10 @@ async function runUpgrade(options = {}) {
45032
45994
  resetDbState2();
45033
45995
  if (result.success) {
45034
45996
  const totalImported = result.tasksImported + result.archivedImported;
45035
- if (totalImported === 0 && existsSync67(dbBackupPath)) {
45997
+ if (totalImported === 0 && existsSync69(dbBackupPath)) {
45036
45998
  copyFileSync5(dbBackupPath, dbPath2);
45037
45999
  if (configBackup) {
45038
- writeFileSync12(configPath, configBackup);
46000
+ writeFileSync13(configPath, configBackup);
45039
46001
  }
45040
46002
  actions.push({
45041
46003
  action: "storage_migration",
@@ -45046,9 +46008,9 @@ async function runUpgrade(options = {}) {
45046
46008
  errors.push("Migration imported 0 tasks \u2014 restored from backup to prevent data loss");
45047
46009
  } else {
45048
46010
  let config = {};
45049
- if (existsSync67(configPath)) {
46011
+ if (existsSync69(configPath)) {
45050
46012
  try {
45051
- config = JSON.parse(readFileSync48(configPath, "utf-8"));
46013
+ config = JSON.parse(readFileSync50(configPath, "utf-8"));
45052
46014
  } catch {
45053
46015
  }
45054
46016
  }
@@ -45056,7 +46018,7 @@ async function runUpgrade(options = {}) {
45056
46018
  config.storage = {};
45057
46019
  }
45058
46020
  config.storage.engine = "sqlite";
45059
- writeFileSync12(configPath, JSON.stringify(config, null, 2));
46021
+ writeFileSync13(configPath, JSON.stringify(config, null, 2));
45060
46022
  actions.push({
45061
46023
  action: "storage_migration",
45062
46024
  status: "applied",
@@ -45074,11 +46036,11 @@ async function runUpgrade(options = {}) {
45074
46036
  logger.info("complete", "finish", "Migration completed successfully");
45075
46037
  }
45076
46038
  } else {
45077
- if (existsSync67(dbBackupPath)) {
46039
+ if (existsSync69(dbBackupPath)) {
45078
46040
  copyFileSync5(dbBackupPath, dbPath2);
45079
46041
  }
45080
46042
  if (configBackup) {
45081
- writeFileSync12(configPath, configBackup);
46043
+ writeFileSync13(configPath, configBackup);
45082
46044
  }
45083
46045
  await updateMigrationPhase2(cleoDir2, "failed");
45084
46046
  for (const error of result.errors) {
@@ -45097,12 +46059,12 @@ async function runUpgrade(options = {}) {
45097
46059
  } catch (err) {
45098
46060
  try {
45099
46061
  const cleoDir2 = getCleoDirAbsolute(options.cwd);
45100
- const dbPath2 = join70(cleoDir2, "tasks.db");
45101
- const safetyDir = join70(cleoDir2, "backups", "safety");
45102
- if (existsSync67(safetyDir)) {
46062
+ const dbPath2 = join72(cleoDir2, "tasks.db");
46063
+ const safetyDir = join72(cleoDir2, "backups", "safety");
46064
+ if (existsSync69(safetyDir)) {
45103
46065
  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);
46066
+ if (backups.length > 0 && !existsSync69(dbPath2)) {
46067
+ copyFileSync5(join72(safetyDir, backups[0]), dbPath2);
45106
46068
  }
45107
46069
  }
45108
46070
  } catch {
@@ -45137,7 +46099,7 @@ async function runUpgrade(options = {}) {
45137
46099
  details: preflight.summary
45138
46100
  });
45139
46101
  }
45140
- if (existsSync67(dbPath)) {
46102
+ if (existsSync69(dbPath)) {
45141
46103
  try {
45142
46104
  const { runAllRepairs: runAllRepairs2 } = await Promise.resolve().then(() => (init_repair(), repair_exports));
45143
46105
  const repairActions = await runAllRepairs2(options.cwd, isDryRun);
@@ -45147,8 +46109,8 @@ async function runUpgrade(options = {}) {
45147
46109
  } catch {
45148
46110
  }
45149
46111
  }
45150
- if (existsSync67(dbPath)) {
45151
- const legacySequenceFiles = [".sequence", ".sequence.json"].filter((f) => existsSync67(join70(cleoDir, f)));
46112
+ if (existsSync69(dbPath)) {
46113
+ const legacySequenceFiles = [".sequence", ".sequence.json"].filter((f) => existsSync69(join72(cleoDir, f)));
45152
46114
  if (legacySequenceFiles.length > 0) {
45153
46115
  if (isDryRun) {
45154
46116
  actions.push({
@@ -45177,7 +46139,7 @@ async function runUpgrade(options = {}) {
45177
46139
  }
45178
46140
  if (needsCleanup) {
45179
46141
  const staleJsonFiles = ["todo.json", "sessions.json", "todo-archive.json", ".sequence", ".sequence.json"];
45180
- const foundStale = staleJsonFiles.filter((f) => existsSync67(join70(cleoDir, f)));
46142
+ const foundStale = staleJsonFiles.filter((f) => existsSync69(join72(cleoDir, f)));
45181
46143
  if (foundStale.length > 0) {
45182
46144
  if (isDryRun) {
45183
46145
  actions.push({
@@ -45187,15 +46149,15 @@ async function runUpgrade(options = {}) {
45187
46149
  });
45188
46150
  } else {
45189
46151
  try {
45190
- const backupDir = join70(cleoDir, ".backups", `legacy-json-${Date.now()}`);
46152
+ const backupDir = join72(cleoDir, ".backups", `legacy-json-${Date.now()}`);
45191
46153
  mkdirSync18(backupDir, { recursive: true });
45192
46154
  for (const f of foundStale) {
45193
- const src = join70(cleoDir, f);
45194
- copyFileSync5(src, join70(backupDir, f));
46155
+ const src = join72(cleoDir, f);
46156
+ copyFileSync5(src, join72(backupDir, f));
45195
46157
  }
45196
46158
  const { unlinkSync: unlinkSync5 } = await import("node:fs");
45197
46159
  for (const f of foundStale) {
45198
- unlinkSync5(join70(cleoDir, f));
46160
+ unlinkSync5(join72(cleoDir, f));
45199
46161
  }
45200
46162
  actions.push({
45201
46163
  action: "stale_json_cleanup",
@@ -45215,7 +46177,7 @@ async function runUpgrade(options = {}) {
45215
46177
  if (options.includeGlobal) {
45216
46178
  try {
45217
46179
  const globalDir = getCleoHome();
45218
- const globalPreflight = checkStorageMigration(join70(globalDir, ".."));
46180
+ const globalPreflight = checkStorageMigration(join72(globalDir, ".."));
45219
46181
  if (globalPreflight.migrationNeeded) {
45220
46182
  actions.push({
45221
46183
  action: "global_storage_check",
@@ -45236,8 +46198,8 @@ async function runUpgrade(options = {}) {
45236
46198
  try {
45237
46199
  const projectRoot = getProjectRoot(options.cwd);
45238
46200
  if (isDryRun) {
45239
- const gitignorePath = join70(cleoDir, ".gitignore");
45240
- if (!existsSync67(gitignorePath)) {
46201
+ const gitignorePath = join72(cleoDir, ".gitignore");
46202
+ if (!existsSync69(gitignorePath)) {
45241
46203
  actions.push({ action: "gitignore_integrity", status: "preview", details: "Would create .cleo/.gitignore from template" });
45242
46204
  } else {
45243
46205
  actions.push({ action: "gitignore_integrity", status: "preview", details: "Would verify .cleo/.gitignore matches template" });
@@ -45280,12 +46242,12 @@ async function runUpgrade(options = {}) {
45280
46242
  try {
45281
46243
  const projectRootForContext = getProjectRoot(options.cwd);
45282
46244
  if (isDryRun) {
45283
- const contextPath = join70(cleoDir, "project-context.json");
45284
- if (!existsSync67(contextPath)) {
46245
+ const contextPath = join72(cleoDir, "project-context.json");
46246
+ if (!existsSync69(contextPath)) {
45285
46247
  actions.push({ action: "project_context_detection", status: "preview", details: "Would detect and create project-context.json" });
45286
46248
  } else {
45287
46249
  try {
45288
- const context = JSON.parse(readFileSync48(contextPath, "utf-8"));
46250
+ const context = JSON.parse(readFileSync50(contextPath, "utf-8"));
45289
46251
  if (context.detectedAt) {
45290
46252
  const daysSince = (Date.now() - new Date(context.detectedAt).getTime()) / (1e3 * 60 * 60 * 24);
45291
46253
  if (daysSince > 30) {
@@ -45446,7 +46408,7 @@ async function runUpgrade(options = {}) {
45446
46408
  init_runtime();
45447
46409
  import { readFile as readFile20 } from "node:fs/promises";
45448
46410
  import * as readline from "node:readline";
45449
- import { join as join71 } from "node:path";
46411
+ import { join as join73 } from "node:path";
45450
46412
  import { execFile as execFile7 } from "node:child_process";
45451
46413
  import { promisify as promisify9 } from "node:util";
45452
46414
  init_build_config();
@@ -45455,7 +46417,7 @@ var GITHUB_REPO = BUILD_CONFIG.repository.fullName;
45455
46417
  async function getCurrentVersion() {
45456
46418
  const cleoHome = getCleoHome();
45457
46419
  try {
45458
- const content = await readFile20(join71(cleoHome, "VERSION"), "utf-8");
46420
+ const content = await readFile20(join73(cleoHome, "VERSION"), "utf-8");
45459
46421
  return (content.split("\n")[0] ?? "unknown").trim();
45460
46422
  } catch {
45461
46423
  return "unknown";
@@ -45502,7 +46464,7 @@ async function writeRuntimeVersionMetadata(mode, source, version) {
45502
46464
  `installed=${(/* @__PURE__ */ new Date()).toISOString()}`
45503
46465
  ];
45504
46466
  await import("node:fs/promises").then(
45505
- ({ writeFile: writeFile15, mkdir: mkdir14 }) => mkdir14(cleoHome, { recursive: true }).then(() => writeFile15(join71(cleoHome, "VERSION"), `${lines.join("\n")}
46467
+ ({ writeFile: writeFile15, mkdir: mkdir14 }) => mkdir14(cleoHome, { recursive: true }).then(() => writeFile15(join73(cleoHome, "VERSION"), `${lines.join("\n")}
45506
46468
  `, "utf-8"))
45507
46469
  );
45508
46470
  }
@@ -45861,14 +46823,14 @@ function registerVerifyCommand(program2) {
45861
46823
 
45862
46824
  // src/cli/commands/detect-drift.ts
45863
46825
  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";
46826
+ import { readFileSync as readFileSync51, existsSync as existsSync70, readdirSync as readdirSync20 } from "node:fs";
46827
+ import { join as join74, dirname as dirname18 } from "node:path";
45866
46828
  import { fileURLToPath as fileURLToPath5 } from "node:url";
45867
46829
  function findProjectRoot() {
45868
46830
  const currentFile = fileURLToPath5(import.meta.url);
45869
46831
  let currentDir = dirname18(currentFile);
45870
46832
  while (currentDir !== "/") {
45871
- if (existsSync68(join72(currentDir, "package.json"))) {
46833
+ if (existsSync70(join74(currentDir, "package.json"))) {
45872
46834
  return currentDir;
45873
46835
  }
45874
46836
  const parent = dirname18(currentDir);
@@ -45906,16 +46868,16 @@ function registerDetectDriftCommand(program2) {
45906
46868
  };
45907
46869
  const safeRead = (path) => {
45908
46870
  try {
45909
- return readFileSync49(path, "utf-8");
46871
+ return readFileSync51(path, "utf-8");
45910
46872
  } catch {
45911
46873
  return "";
45912
46874
  }
45913
46875
  };
45914
46876
  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)) {
46877
+ const specPath = join74(projectRoot, "docs", "specs", "CLEO-OPERATIONS-REFERENCE.md");
46878
+ const queryPath = join74(projectRoot, "src", "mcp", "gateways", "query.ts");
46879
+ const mutatePath = join74(projectRoot, "src", "mcp", "gateways", "mutate.ts");
46880
+ if (!existsSync70(specPath)) {
45919
46881
  addCheck("Gateway-to-spec sync", "fail", "CLEO-OPERATIONS-REFERENCE.md missing", [{
45920
46882
  severity: "error",
45921
46883
  category: "spec",
@@ -45923,7 +46885,7 @@ function registerDetectDriftCommand(program2) {
45923
46885
  file: specPath,
45924
46886
  recommendation: "Create docs/specs/CLEO-OPERATIONS-REFERENCE.md with canonical operation definitions"
45925
46887
  }]);
45926
- } else if (!existsSync68(queryPath) || !existsSync68(mutatePath)) {
46888
+ } else if (!existsSync70(queryPath) || !existsSync70(mutatePath)) {
45927
46889
  addCheck("Gateway-to-spec sync", "fail", "MCP gateway files missing", [{
45928
46890
  severity: "error",
45929
46891
  category: "implementation",
@@ -45977,16 +46939,16 @@ function registerDetectDriftCommand(program2) {
45977
46939
  }]);
45978
46940
  }
45979
46941
  try {
45980
- const cliDir = join72(projectRoot, "src", "cli", "commands");
45981
- const coreDir = join72(projectRoot, "src", "core");
45982
- if (!existsSync68(cliDir)) {
46942
+ const cliDir = join74(projectRoot, "src", "cli", "commands");
46943
+ const coreDir = join74(projectRoot, "src", "core");
46944
+ if (!existsSync70(cliDir)) {
45983
46945
  addCheck("CLI-to-core sync", "fail", "CLI commands directory missing", [{
45984
46946
  severity: "error",
45985
46947
  category: "structure",
45986
46948
  message: "src/cli/commands/ directory not found",
45987
46949
  recommendation: "Verify TypeScript source structure is intact"
45988
46950
  }]);
45989
- } else if (!existsSync68(coreDir)) {
46951
+ } else if (!existsSync70(coreDir)) {
45990
46952
  addCheck("CLI-to-core sync", "fail", "Core directory missing", [{
45991
46953
  severity: "error",
45992
46954
  category: "structure",
@@ -46001,8 +46963,8 @@ function registerDetectDriftCommand(program2) {
46001
46963
  addCheck("CLI-to-core sync", "fail", `Error: ${e.message}`);
46002
46964
  }
46003
46965
  try {
46004
- const domainsDir = join72(projectRoot, "src", "mcp", "domains");
46005
- if (!existsSync68(domainsDir)) {
46966
+ const domainsDir = join74(projectRoot, "src", "mcp", "domains");
46967
+ if (!existsSync70(domainsDir)) {
46006
46968
  addCheck("Domain handler coverage", "fail", "MCP domains directory missing", [{
46007
46969
  severity: "error",
46008
46970
  category: "structure",
@@ -46017,8 +46979,8 @@ function registerDetectDriftCommand(program2) {
46017
46979
  addCheck("Domain handler coverage", "fail", `Error: ${e.message}`);
46018
46980
  }
46019
46981
  try {
46020
- const matrixPath = join72(projectRoot, "src", "dispatch", "lib", "capability-matrix.ts");
46021
- if (!existsSync68(matrixPath)) {
46982
+ const matrixPath = join74(projectRoot, "src", "dispatch", "lib", "capability-matrix.ts");
46983
+ if (!existsSync70(matrixPath)) {
46022
46984
  addCheck("Capability matrix", "fail", "Capability matrix missing", [{
46023
46985
  severity: "error",
46024
46986
  category: "configuration",
@@ -46032,8 +46994,8 @@ function registerDetectDriftCommand(program2) {
46032
46994
  addCheck("Capability matrix", "fail", `Error: ${e.message}`);
46033
46995
  }
46034
46996
  try {
46035
- const schemaPath = join72(projectRoot, "src", "store", "schema.ts");
46036
- if (!existsSync68(schemaPath)) {
46997
+ const schemaPath = join74(projectRoot, "src", "store", "schema.ts");
46998
+ if (!existsSync70(schemaPath)) {
46037
46999
  addCheck("Schema validation", "fail", "Schema definition missing", [{
46038
47000
  severity: "error",
46039
47001
  category: "data-model",
@@ -46058,10 +47020,10 @@ function registerDetectDriftCommand(program2) {
46058
47020
  addCheck("Schema validation", "fail", `Error: ${e.message}`);
46059
47021
  }
46060
47022
  try {
46061
- const visionPath = join72(projectRoot, "docs", "concepts", "CLEO-VISION.md");
46062
- const specPath = join72(projectRoot, "docs", "specs", "PORTABLE-BRAIN-SPEC.md");
47023
+ const visionPath = join74(projectRoot, "docs", "concepts", "CLEO-VISION.md");
47024
+ const specPath = join74(projectRoot, "docs", "specs", "PORTABLE-BRAIN-SPEC.md");
46063
47025
  const issues = [];
46064
- if (!existsSync68(visionPath)) {
47026
+ if (!existsSync70(visionPath)) {
46065
47027
  issues.push({
46066
47028
  severity: "error",
46067
47029
  category: "vision",
@@ -46070,7 +47032,7 @@ function registerDetectDriftCommand(program2) {
46070
47032
  recommendation: "Create docs/concepts/CLEO-VISION.md with project vision"
46071
47033
  });
46072
47034
  }
46073
- if (!existsSync68(specPath)) {
47035
+ if (!existsSync70(specPath)) {
46074
47036
  issues.push({
46075
47037
  severity: "error",
46076
47038
  category: "spec",
@@ -46107,8 +47069,8 @@ function registerDetectDriftCommand(program2) {
46107
47069
  addCheck("Canonical identity", "fail", `Error: ${e.message}`);
46108
47070
  }
46109
47071
  try {
46110
- const injectionPath = join72(projectRoot, ".cleo", "templates", "CLEO-INJECTION.md");
46111
- if (!existsSync68(injectionPath)) {
47072
+ const injectionPath = join74(projectRoot, ".cleo", "templates", "CLEO-INJECTION.md");
47073
+ if (!existsSync70(injectionPath)) {
46112
47074
  addCheck("Agent injection", "fail", "Agent injection template missing", [{
46113
47075
  severity: "error",
46114
47076
  category: "agent-support",
@@ -46133,8 +47095,8 @@ function registerDetectDriftCommand(program2) {
46133
47095
  addCheck("Agent injection", "fail", `Error: ${e.message}`);
46134
47096
  }
46135
47097
  try {
46136
- const exitCodesPath = join72(projectRoot, "src", "types", "exit-codes.ts");
46137
- if (!existsSync68(exitCodesPath)) {
47098
+ const exitCodesPath = join74(projectRoot, "src", "types", "exit-codes.ts");
47099
+ if (!existsSync70(exitCodesPath)) {
46138
47100
  addCheck("Exit codes", "fail", "Exit codes definition missing", [{
46139
47101
  severity: "error",
46140
47102
  category: "protocol",
@@ -46373,8 +47335,8 @@ function registerRemoteCommand(program2) {
46373
47335
  init_renderers();
46374
47336
  init_paths();
46375
47337
  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";
47338
+ import { existsSync as existsSync71, readFileSync as readFileSync52 } from "node:fs";
47339
+ import { join as join75, resolve as resolve10, dirname as dirname19 } from "node:path";
46378
47340
  import { homedir as homedir4 } from "node:os";
46379
47341
  import { fileURLToPath as fileURLToPath6 } from "node:url";
46380
47342
  function registerInstallGlobalCommand(program2) {
@@ -46384,17 +47346,17 @@ function registerInstallGlobalCommand(program2) {
46384
47346
  const warnings = [];
46385
47347
  try {
46386
47348
  const cleoHome = getCleoHome();
46387
- const globalTemplatesDir = join73(cleoHome, "templates");
47349
+ const globalTemplatesDir = join75(cleoHome, "templates");
46388
47350
  if (!isDryRun) {
46389
47351
  await mkdir13(globalTemplatesDir, { recursive: true });
46390
47352
  }
46391
47353
  try {
46392
47354
  const thisFile = fileURLToPath6(import.meta.url);
46393
47355
  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");
47356
+ const templatePath = join75(packageRoot, "templates", "CLEO-INJECTION.md");
47357
+ if (existsSync71(templatePath)) {
47358
+ const content = readFileSync52(templatePath, "utf-8");
47359
+ const globalPath = join75(globalTemplatesDir, "CLEO-INJECTION.md");
46398
47360
  if (!isDryRun) {
46399
47361
  await writeFile14(globalPath, content);
46400
47362
  }
@@ -46404,12 +47366,12 @@ function registerInstallGlobalCommand(program2) {
46404
47366
  warnings.push("Could not refresh CLEO-INJECTION.md template");
46405
47367
  }
46406
47368
  const globalAgentsDir = getAgentsHome();
46407
- const globalAgentsMd = join73(globalAgentsDir, "AGENTS.md");
47369
+ const globalAgentsMd = join75(globalAgentsDir, "AGENTS.md");
46408
47370
  try {
46409
47371
  const { inject, getInstalledProviders: getInstalledProviders3, injectAll: injectAll2, buildInjectionContent: buildInjectionContent2 } = await import("@cleocode/caamp");
46410
47372
  if (!isDryRun) {
46411
47373
  await mkdir13(globalAgentsDir, { recursive: true });
46412
- if (existsSync69(globalAgentsMd)) {
47374
+ if (existsSync71(globalAgentsMd)) {
46413
47375
  const content = await readFile21(globalAgentsMd, "utf8");
46414
47376
  const stripped = content.replace(/\n?<!-- CLEO:START -->[\s\S]*?<!-- CLEO:END -->\n?/g, "");
46415
47377
  if (stripped !== content) {
@@ -46428,8 +47390,8 @@ function registerInstallGlobalCommand(program2) {
46428
47390
  const injectionContent = buildInjectionContent2({ references: ["@~/.agents/AGENTS.md"] });
46429
47391
  if (!isDryRun) {
46430
47392
  for (const provider of providers) {
46431
- const instructFilePath = join73(provider.pathGlobal, provider.instructFile);
46432
- if (existsSync69(instructFilePath)) {
47393
+ const instructFilePath = join75(provider.pathGlobal, provider.instructFile);
47394
+ if (existsSync71(instructFilePath)) {
46433
47395
  const fileContent = await readFile21(instructFilePath, "utf8");
46434
47396
  const stripped = fileContent.replace(/\n?<!-- CLEO:START -->[\s\S]*?<!-- CLEO:END -->\n?/g, "");
46435
47397
  if (stripped !== fileContent) {
@@ -46444,7 +47406,7 @@ function registerInstallGlobalCommand(program2) {
46444
47406
  }
46445
47407
  } else {
46446
47408
  for (const p of providers) {
46447
- const displayPath = join73(p.pathGlobal, p.instructFile).replace(homedir4(), "~");
47409
+ const displayPath = join75(p.pathGlobal, p.instructFile).replace(homedir4(), "~");
46448
47410
  created.push(`${displayPath} (would update CAAMP block)`);
46449
47411
  }
46450
47412
  }
@@ -46650,7 +47612,7 @@ init_paths();
46650
47612
  init_paths();
46651
47613
  init_brain_sqlite();
46652
47614
  init_brain_search();
46653
- import { existsSync as existsSync70 } from "node:fs";
47615
+ import { existsSync as existsSync72 } from "node:fs";
46654
47616
  import { createRequire as createRequire5 } from "node:module";
46655
47617
  var _require5 = createRequire5(import.meta.url);
46656
47618
  var { DatabaseSync: DatabaseSync3 } = _require5("node:sqlite");
@@ -46694,7 +47656,7 @@ async function migrateClaudeMem(projectRoot, options = {}) {
46694
47656
  errors: [],
46695
47657
  dryRun
46696
47658
  };
46697
- if (!existsSync70(sourcePath)) {
47659
+ if (!existsSync72(sourcePath)) {
46698
47660
  throw new Error(
46699
47661
  `claude-mem database not found at: ${sourcePath}
46700
47662
  Expected location: ~/.claude-mem/claude-mem.db
@@ -47096,10 +48058,10 @@ init_config();
47096
48058
  // src/cli/logger-bootstrap.ts
47097
48059
  init_logger();
47098
48060
  init_project_info();
47099
- import { join as join74 } from "node:path";
48061
+ import { join as join76 } from "node:path";
47100
48062
  function initCliLogger(cwd, loggingConfig) {
47101
48063
  const projectInfo = getProjectInfoSync(cwd);
47102
- initLogger(join74(cwd, ".cleo"), loggingConfig, projectInfo?.projectHash);
48064
+ initLogger(join76(cwd, ".cleo"), loggingConfig, projectInfo?.projectHash);
47103
48065
  }
47104
48066
 
47105
48067
  // src/cli/index.ts
@@ -47332,8 +48294,8 @@ Upgrade options:
47332
48294
  }
47333
48295
  function getPackageVersion() {
47334
48296
  try {
47335
- const moduleRoot = join75(import.meta.dirname ?? "", "..", "..");
47336
- const pkg = JSON.parse(readFileSync51(join75(moduleRoot, "package.json"), "utf-8"));
48297
+ const moduleRoot = join77(import.meta.dirname ?? "", "..", "..");
48298
+ const pkg = JSON.parse(readFileSync53(join77(moduleRoot, "package.json"), "utf-8"));
47337
48299
  return pkg.version ?? "0.0.0";
47338
48300
  } catch {
47339
48301
  return "0.0.0";
@@ -47444,7 +48406,7 @@ program.hook("preAction", async () => {
47444
48406
  const config = await loadConfig();
47445
48407
  initCliLogger(process.cwd(), config.logging);
47446
48408
  const { pruneAuditLog: pruneAuditLog2 } = await Promise.resolve().then(() => (init_audit_prune(), audit_prune_exports));
47447
- pruneAuditLog2(join75(process.cwd(), ".cleo"), config.logging).catch(() => {
48409
+ pruneAuditLog2(join77(process.cwd(), ".cleo"), config.logging).catch(() => {
47448
48410
  });
47449
48411
  } catch {
47450
48412
  }
@@ -47484,7 +48446,7 @@ program.hook("preAction", (thisCommand) => {
47484
48446
  }
47485
48447
  });
47486
48448
  if (process.argv[2] === "mcp") {
47487
- const mcpPath = join75(import.meta.dirname ?? "", "..", "mcp", "index.js");
48449
+ const mcpPath = join77(import.meta.dirname ?? "", "..", "mcp", "index.js");
47488
48450
  const { spawn: spawn3 } = await import("node:child_process");
47489
48451
  const child = spawn3(
47490
48452
  process.execPath,