@cleocode/cleo 2026.3.16 → 2026.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -4895,13 +4895,13 @@ async function getDb(cwd) {
4895
4895
  if (!_gitTrackingChecked) {
4896
4896
  _gitTrackingChecked = true;
4897
4897
  try {
4898
- const { execFileSync: execFileSync10 } = await import("node:child_process");
4898
+ const { execFileSync: execFileSync12 } = await import("node:child_process");
4899
4899
  const gitCwd = resolve3(dbPath, "..", "..");
4900
4900
  const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
4901
4901
  const log7 = getLogger("sqlite");
4902
4902
  for (const fileToCheck of filesToCheck) {
4903
4903
  try {
4904
- execFileSync10("git", ["ls-files", "--error-unmatch", fileToCheck], {
4904
+ execFileSync12("git", ["ls-files", "--error-unmatch", fileToCheck], {
4905
4905
  cwd: gitCwd,
4906
4906
  stdio: "pipe"
4907
4907
  });
@@ -15585,6 +15585,7 @@ async function getProjectStats(opts, accessor) {
15585
15585
  const active = tasks2.filter((t) => t.status === "active").length;
15586
15586
  const done = tasks2.filter((t) => t.status === "done").length;
15587
15587
  const blocked = tasks2.filter((t) => t.status === "blocked").length;
15588
+ const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
15588
15589
  const totalActive = tasks2.length;
15589
15590
  const cutoff = new Date(Date.now() - periodDays * 864e5).toISOString();
15590
15591
  const entries = await queryAuditEntries(opts.cwd);
@@ -15601,11 +15602,45 @@ async function getProjectStats(opts, accessor) {
15601
15602
  (e) => isArchive(e) && e.timestamp >= cutoff
15602
15603
  ).length;
15603
15604
  const completionRate = createdInPeriod > 0 ? Math.round(completedInPeriod / createdInPeriod * 1e4) / 100 : 0;
15604
- const totalCreated = entries.filter(isCreate).length;
15605
- const totalCompleted = entries.filter(isComplete).length;
15606
- const totalArchived = entries.filter(isArchive).length;
15605
+ let totalCreated = 0;
15606
+ let totalCompleted = 0;
15607
+ let totalCancelled = 0;
15608
+ let totalArchived = 0;
15609
+ let archivedCompleted = 0;
15610
+ let archivedCount = 0;
15611
+ try {
15612
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
15613
+ const { count: dbCount, eq: dbEq, and: dbAnd } = await import("drizzle-orm");
15614
+ const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
15615
+ const db = await getDb2(opts.cwd);
15616
+ const statusRows = await db.select({ status: tasksTable.status, c: dbCount() }).from(tasksTable).groupBy(tasksTable.status).all();
15617
+ const statusMap = {};
15618
+ for (const row of statusRows) {
15619
+ statusMap[row.status] = row.c;
15620
+ }
15621
+ archivedCount = statusMap["archived"] ?? 0;
15622
+ totalCreated = Object.values(statusMap).reduce((sum, n) => sum + n, 0);
15623
+ totalCancelled = statusMap["cancelled"] ?? 0;
15624
+ totalArchived = archivedCount;
15625
+ const archivedDoneRow = await db.select({ c: dbCount() }).from(tasksTable).where(dbAnd(dbEq(tasksTable.status, "archived"), dbEq(tasksTable.archiveReason, "completed"))).get();
15626
+ archivedCompleted = archivedDoneRow?.c ?? 0;
15627
+ totalCompleted = (statusMap["done"] ?? 0) + archivedCompleted;
15628
+ } catch {
15629
+ totalCreated = entries.filter(isCreate).length;
15630
+ totalCompleted = entries.filter(isComplete).length;
15631
+ totalArchived = entries.filter(isArchive).length;
15632
+ }
15607
15633
  return {
15608
- currentState: { pending, active, done, blocked, totalActive },
15634
+ currentState: {
15635
+ pending,
15636
+ active,
15637
+ done,
15638
+ blocked,
15639
+ cancelled,
15640
+ totalActive,
15641
+ archived: archivedCount,
15642
+ grandTotal: totalActive + archivedCount
15643
+ },
15609
15644
  completionMetrics: {
15610
15645
  periodDays,
15611
15646
  completedInPeriod,
@@ -15617,7 +15652,7 @@ async function getProjectStats(opts, accessor) {
15617
15652
  completedInPeriod,
15618
15653
  archivedInPeriod
15619
15654
  },
15620
- allTime: { totalCreated, totalCompleted, totalArchived }
15655
+ allTime: { totalCreated, totalCompleted, totalCancelled, totalArchived, archivedCompleted }
15621
15656
  };
15622
15657
  }
15623
15658
  function rankBlockedTask(task, allTasks, focusTask) {
@@ -15661,7 +15696,18 @@ async function getDashboard(opts, accessor) {
15661
15696
  const active = tasks2.filter((t) => t.status === "active").length;
15662
15697
  const done = tasks2.filter((t) => t.status === "done").length;
15663
15698
  const blocked = tasks2.filter((t) => t.status === "blocked").length;
15699
+ const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
15664
15700
  const total = tasks2.length;
15701
+ let archived = 0;
15702
+ try {
15703
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
15704
+ const { count: dbCount, eq: dbEq } = await import("drizzle-orm");
15705
+ const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
15706
+ const db = await getDb2(opts.cwd);
15707
+ const row = await db.select({ c: dbCount() }).from(tasksTable).where(dbEq(tasksTable.status, "archived")).get();
15708
+ archived = row?.c ?? 0;
15709
+ } catch {
15710
+ }
15665
15711
  const project = data.project?.name ?? "Unknown Project";
15666
15712
  const currentPhase = data.project?.currentPhase ?? null;
15667
15713
  const focusId = data.focus?.currentTask ?? null;
@@ -15669,7 +15715,7 @@ async function getDashboard(opts, accessor) {
15669
15715
  if (focusId) {
15670
15716
  focusTask = tasks2.find((t) => t.id === focusId) ?? null;
15671
15717
  }
15672
- const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done").sort((a, b) => {
15718
+ const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done" && t.status !== "cancelled").sort((a, b) => {
15673
15719
  const pDiff = (PRIORITY_ORDER[a.priority ?? "low"] ?? 9) - (PRIORITY_ORDER[b.priority ?? "low"] ?? 9);
15674
15720
  if (pDiff !== 0) return pDiff;
15675
15721
  return (a.createdAt ?? "").localeCompare(b.createdAt ?? "");
@@ -15682,6 +15728,7 @@ async function getDashboard(opts, accessor) {
15682
15728
  }).map((r) => r.task);
15683
15729
  const labelMap = {};
15684
15730
  for (const t of tasks2) {
15731
+ if (t.status === "cancelled") continue;
15685
15732
  for (const label of t.labels ?? []) {
15686
15733
  labelMap[label] = (labelMap[label] ?? 0) + 1;
15687
15734
  }
@@ -15690,7 +15737,7 @@ async function getDashboard(opts, accessor) {
15690
15737
  return {
15691
15738
  project,
15692
15739
  currentPhase,
15693
- summary: { pending, active, blocked, done, total },
15740
+ summary: { pending, active, blocked, done, cancelled, total, archived, grandTotal: total + archived },
15694
15741
  focus: { currentTask: focusId, task: focusTask },
15695
15742
  highPriority: { count: highPriority.length, tasks: highPriority.slice(0, 5) },
15696
15743
  blockedTasks: {
@@ -19811,7 +19858,9 @@ async function systemDash(projectRoot, params) {
19811
19858
  blocked: summary.blocked,
19812
19859
  done: summary.done,
19813
19860
  cancelled: summary.cancelled ?? 0,
19814
- total: summary.total
19861
+ total: summary.total,
19862
+ archived: summary.archived ?? 0,
19863
+ grandTotal: summary.grandTotal ?? summary.total
19815
19864
  },
19816
19865
  taskWork: data.focus ?? data.taskWork,
19817
19866
  activeSession: data.activeSession ?? null,
@@ -19831,17 +19880,18 @@ async function systemStats(projectRoot, params) {
19831
19880
  const result = await getProjectStats({ period: String(params?.period ?? 30), cwd: projectRoot }, accessor);
19832
19881
  const taskData = await accessor.loadTaskFile();
19833
19882
  const tasks2 = taskData?.tasks ?? [];
19883
+ const activeTasks = tasks2.filter((t) => t.status !== "cancelled");
19834
19884
  const byPriority = {};
19835
- for (const t of tasks2) {
19885
+ for (const t of activeTasks) {
19836
19886
  byPriority[t.priority] = (byPriority[t.priority] ?? 0) + 1;
19837
19887
  }
19838
19888
  const byType = {};
19839
- for (const t of tasks2) {
19889
+ for (const t of activeTasks) {
19840
19890
  const type = t.type || "task";
19841
19891
  byType[type] = (byType[type] ?? 0) + 1;
19842
19892
  }
19843
19893
  const byPhase = {};
19844
- for (const t of tasks2) {
19894
+ for (const t of activeTasks) {
19845
19895
  const phase = t.phase || "unassigned";
19846
19896
  byPhase[phase] = (byPhase[phase] ?? 0) + 1;
19847
19897
  }
@@ -19871,7 +19921,9 @@ async function systemStats(projectRoot, params) {
19871
19921
  done: currentState.done,
19872
19922
  blocked: currentState.blocked,
19873
19923
  cancelled: tasks2.filter((t) => t.status === "cancelled").length,
19874
- totalActive: currentState.totalActive
19924
+ totalActive: currentState.totalActive,
19925
+ archived: currentState.archived ?? 0,
19926
+ grandTotal: currentState.grandTotal ?? currentState.totalActive
19875
19927
  },
19876
19928
  byPriority,
19877
19929
  byType,
@@ -19896,10 +19948,10 @@ async function systemLog(projectRoot, filters) {
19896
19948
  }
19897
19949
  async function queryAuditLogSqlite(projectRoot, filters) {
19898
19950
  try {
19899
- const { join: join62 } = await import("node:path");
19900
- const { existsSync: existsSync62 } = await import("node:fs");
19901
- const dbPath = join62(projectRoot, ".cleo", "tasks.db");
19902
- if (!existsSync62(dbPath)) {
19951
+ const { join: join63 } = await import("node:path");
19952
+ const { existsSync: existsSync63 } = await import("node:fs");
19953
+ const dbPath = join63(projectRoot, ".cleo", "tasks.db");
19954
+ if (!existsSync63(dbPath)) {
19903
19955
  const offset = filters?.offset ?? 0;
19904
19956
  const limit = filters?.limit ?? 20;
19905
19957
  return {
@@ -20643,10 +20695,10 @@ async function readProjectMeta(projectPath) {
20643
20695
  }
20644
20696
  async function readProjectId(projectPath) {
20645
20697
  try {
20646
- const { readFileSync: readFileSync44, existsSync: existsSync62 } = await import("node:fs");
20698
+ const { readFileSync: readFileSync45, existsSync: existsSync63 } = await import("node:fs");
20647
20699
  const infoPath = join34(projectPath, ".cleo", "project-info.json");
20648
- if (!existsSync62(infoPath)) return "";
20649
- const data = JSON.parse(readFileSync44(infoPath, "utf-8"));
20700
+ if (!existsSync63(infoPath)) return "";
20701
+ const data = JSON.parse(readFileSync45(infoPath, "utf-8"));
20650
20702
  return typeof data.projectId === "string" ? data.projectId : "";
20651
20703
  } catch {
20652
20704
  return "";
@@ -27991,11 +28043,353 @@ var init_changelog_writer = __esm({
27991
28043
  }
27992
28044
  });
27993
28045
 
27994
- // src/core/release/release-manifest.ts
27995
- import { existsSync as existsSync49, renameSync as renameSync7 } from "node:fs";
27996
- import { readFile as readFile10 } from "node:fs/promises";
28046
+ // src/core/release/github-pr.ts
27997
28047
  import { execFileSync as execFileSync6 } from "node:child_process";
28048
+ function isGhCliAvailable() {
28049
+ try {
28050
+ execFileSync6("gh", ["--version"], { stdio: "pipe" });
28051
+ return true;
28052
+ } catch {
28053
+ return false;
28054
+ }
28055
+ }
28056
+ function extractRepoOwnerAndName(remote) {
28057
+ const trimmed = remote.trim();
28058
+ const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
28059
+ if (httpsMatch) {
28060
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
28061
+ }
28062
+ const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
28063
+ if (sshMatch) {
28064
+ return { owner: sshMatch[1], repo: sshMatch[2] };
28065
+ }
28066
+ return null;
28067
+ }
28068
+ async function detectBranchProtection(branch, remote, projectRoot) {
28069
+ const cwdOpts = projectRoot ? { cwd: projectRoot } : {};
28070
+ if (isGhCliAvailable()) {
28071
+ try {
28072
+ const remoteUrl = execFileSync6("git", ["remote", "get-url", remote], {
28073
+ encoding: "utf-8",
28074
+ stdio: "pipe",
28075
+ ...cwdOpts
28076
+ }).trim();
28077
+ const identity = extractRepoOwnerAndName(remoteUrl);
28078
+ if (identity) {
28079
+ const { owner, repo } = identity;
28080
+ try {
28081
+ execFileSync6(
28082
+ "gh",
28083
+ ["api", `/repos/${owner}/${repo}/branches/${branch}/protection`],
28084
+ {
28085
+ encoding: "utf-8",
28086
+ stdio: "pipe",
28087
+ ...cwdOpts
28088
+ }
28089
+ );
28090
+ return { protected: true, detectionMethod: "gh-api" };
28091
+ } catch (apiErr) {
28092
+ const stderr = apiErr instanceof Error && "stderr" in apiErr ? String(apiErr.stderr ?? "") : "";
28093
+ if (stderr.includes("404") || stderr.includes("Not Found")) {
28094
+ return { protected: false, detectionMethod: "gh-api" };
28095
+ }
28096
+ }
28097
+ }
28098
+ } catch {
28099
+ }
28100
+ }
28101
+ try {
28102
+ const result = execFileSync6(
28103
+ "git",
28104
+ ["push", "--dry-run", remote, `HEAD:${branch}`],
28105
+ {
28106
+ encoding: "utf-8",
28107
+ stdio: "pipe",
28108
+ ...cwdOpts
28109
+ }
28110
+ );
28111
+ const output = typeof result === "string" ? result : "";
28112
+ if (output.includes("protected branch") || output.includes("GH006") || output.includes("refusing to allow")) {
28113
+ return { protected: true, detectionMethod: "push-dry-run" };
28114
+ }
28115
+ return { protected: false, detectionMethod: "push-dry-run" };
28116
+ } catch (pushErr) {
28117
+ const stderr = pushErr instanceof Error && "stderr" in pushErr ? String(pushErr.stderr ?? "") : pushErr instanceof Error ? pushErr.message : String(pushErr);
28118
+ if (stderr.includes("protected branch") || stderr.includes("GH006") || stderr.includes("refusing to allow")) {
28119
+ return { protected: true, detectionMethod: "push-dry-run" };
28120
+ }
28121
+ return {
28122
+ protected: false,
28123
+ detectionMethod: "unknown",
28124
+ error: stderr
28125
+ };
28126
+ }
28127
+ }
28128
+ function buildPRBody(opts) {
28129
+ const epicLine = opts.epicId ? `**Epic**: ${opts.epicId}
28130
+
28131
+ ` : "";
28132
+ return [
28133
+ `## Release v${opts.version}`,
28134
+ "",
28135
+ `${epicLine}This PR merges the ${opts.head} branch into ${opts.base} to publish the release.`,
28136
+ "",
28137
+ "### Checklist",
28138
+ "- [ ] CHANGELOG.md updated",
28139
+ "- [ ] All release tasks complete",
28140
+ "- [ ] Version bump committed",
28141
+ "",
28142
+ "---",
28143
+ "*Created by CLEO release pipeline*"
28144
+ ].join("\n");
28145
+ }
28146
+ function formatManualPRInstructions(opts) {
28147
+ const epicSuffix = opts.epicId ? ` (${opts.epicId})` : "";
28148
+ return [
28149
+ "Branch protection detected or gh CLI unavailable. Create the PR manually:",
28150
+ "",
28151
+ ` gh pr create \\`,
28152
+ ` --base ${opts.base} \\`,
28153
+ ` --head ${opts.head} \\`,
28154
+ ` --title "${opts.title}" \\`,
28155
+ ` --body "Release v${opts.version}${epicSuffix}"`,
28156
+ "",
28157
+ `Or visit: https://github.com/[owner]/[repo]/compare/${opts.base}...${opts.head}`,
28158
+ "",
28159
+ "After merging, CI will automatically publish to npm."
28160
+ ].join("\n");
28161
+ }
28162
+ async function createPullRequest(opts) {
28163
+ if (!isGhCliAvailable()) {
28164
+ return {
28165
+ mode: "manual",
28166
+ instructions: formatManualPRInstructions(opts)
28167
+ };
28168
+ }
28169
+ const body = buildPRBody(opts);
28170
+ const args = [
28171
+ "pr",
28172
+ "create",
28173
+ "--base",
28174
+ opts.base,
28175
+ "--head",
28176
+ opts.head,
28177
+ "--title",
28178
+ opts.title,
28179
+ "--body",
28180
+ body
28181
+ ];
28182
+ if (opts.labels && opts.labels.length > 0) {
28183
+ for (const label of opts.labels) {
28184
+ args.push("--label", label);
28185
+ }
28186
+ }
28187
+ try {
28188
+ const output = execFileSync6("gh", args, {
28189
+ encoding: "utf-8",
28190
+ stdio: "pipe",
28191
+ ...opts.projectRoot ? { cwd: opts.projectRoot } : {}
28192
+ });
28193
+ const prUrl = output.trim();
28194
+ const numberMatch = prUrl.match(/\/pull\/(\d+)$/);
28195
+ const prNumber = numberMatch ? parseInt(numberMatch[1], 10) : void 0;
28196
+ return {
28197
+ mode: "created",
28198
+ prUrl,
28199
+ prNumber
28200
+ };
28201
+ } catch (err) {
28202
+ const stderr = err instanceof Error && "stderr" in err ? String(err.stderr ?? "") : err instanceof Error ? err.message : String(err);
28203
+ if (stderr.includes("already exists")) {
28204
+ const urlMatch = stderr.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
28205
+ const existingUrl = urlMatch ? urlMatch[0] : void 0;
28206
+ return {
28207
+ mode: "skipped",
28208
+ prUrl: existingUrl,
28209
+ instructions: "PR already exists"
28210
+ };
28211
+ }
28212
+ return {
28213
+ mode: "manual",
28214
+ instructions: formatManualPRInstructions(opts),
28215
+ error: stderr
28216
+ };
28217
+ }
28218
+ }
28219
+ var init_github_pr = __esm({
28220
+ "src/core/release/github-pr.ts"() {
28221
+ "use strict";
28222
+ }
28223
+ });
28224
+
28225
+ // src/core/release/channel.ts
28226
+ function getDefaultChannelConfig() {
28227
+ return {
28228
+ main: "main",
28229
+ develop: "develop",
28230
+ feature: "feature/"
28231
+ };
28232
+ }
28233
+ function resolveChannelFromBranch(branch, config) {
28234
+ const cfg = config ?? getDefaultChannelConfig();
28235
+ if (cfg.custom) {
28236
+ if (Object.prototype.hasOwnProperty.call(cfg.custom, branch)) {
28237
+ return cfg.custom[branch];
28238
+ }
28239
+ let bestPrefix = "";
28240
+ let bestChannel;
28241
+ for (const [key, channel] of Object.entries(cfg.custom)) {
28242
+ if (branch.startsWith(key) && key.length > bestPrefix.length) {
28243
+ bestPrefix = key;
28244
+ bestChannel = channel;
28245
+ }
28246
+ }
28247
+ if (bestChannel !== void 0) {
28248
+ return bestChannel;
28249
+ }
28250
+ }
28251
+ if (branch === cfg.main) {
28252
+ return "latest";
28253
+ }
28254
+ if (branch === cfg.develop) {
28255
+ return "beta";
28256
+ }
28257
+ const alphaPrefixes = ["feature/", "hotfix/", "release/"];
28258
+ if (cfg.feature && !alphaPrefixes.includes(cfg.feature)) {
28259
+ alphaPrefixes.push(cfg.feature);
28260
+ }
28261
+ for (const prefix of alphaPrefixes) {
28262
+ if (branch.startsWith(prefix)) {
28263
+ return "alpha";
28264
+ }
28265
+ }
28266
+ return "alpha";
28267
+ }
28268
+ function channelToDistTag(channel) {
28269
+ const tags = {
28270
+ latest: "latest",
28271
+ beta: "beta",
28272
+ alpha: "alpha"
28273
+ };
28274
+ return tags[channel];
28275
+ }
28276
+ function describeChannel(channel) {
28277
+ const descriptions = {
28278
+ latest: "stable release published to npm @latest",
28279
+ beta: "pre-release published to npm @beta (develop branch)",
28280
+ alpha: "early pre-release published to npm @alpha (feature/hotfix branches)"
28281
+ };
28282
+ return descriptions[channel];
28283
+ }
28284
+ var init_channel = __esm({
28285
+ "src/core/release/channel.ts"() {
28286
+ "use strict";
28287
+ }
28288
+ });
28289
+
28290
+ // src/core/release/release-config.ts
28291
+ import { existsSync as existsSync49, readFileSync as readFileSync38 } from "node:fs";
27998
28292
  import { join as join47 } from "node:path";
28293
+ function readConfigValueSync(path, defaultValue, cwd) {
28294
+ try {
28295
+ const configPath = join47(getCleoDir(cwd), "config.json");
28296
+ if (!existsSync49(configPath)) return defaultValue;
28297
+ const config = JSON.parse(readFileSync38(configPath, "utf-8"));
28298
+ const keys = path.split(".");
28299
+ let value = config;
28300
+ for (const key of keys) {
28301
+ if (value == null || typeof value !== "object") return defaultValue;
28302
+ value = value[key];
28303
+ }
28304
+ return value ?? defaultValue;
28305
+ } catch {
28306
+ return defaultValue;
28307
+ }
28308
+ }
28309
+ function loadReleaseConfig(cwd) {
28310
+ return {
28311
+ versioningScheme: readConfigValueSync("release.versioning.scheme", DEFAULTS2.versioningScheme, cwd),
28312
+ tagPrefix: readConfigValueSync("release.versioning.tagPrefix", DEFAULTS2.tagPrefix, cwd),
28313
+ changelogFormat: readConfigValueSync("release.changelog.format", DEFAULTS2.changelogFormat, cwd),
28314
+ changelogFile: readConfigValueSync("release.changelog.file", DEFAULTS2.changelogFile, cwd),
28315
+ artifactType: readConfigValueSync("release.artifact.type", DEFAULTS2.artifactType, cwd),
28316
+ gates: readConfigValueSync("release.gates", [], cwd),
28317
+ versionBump: {
28318
+ files: readConfigValueSync("release.versionBump.files", [], cwd)
28319
+ },
28320
+ security: {
28321
+ enableProvenance: readConfigValueSync("release.security.enableProvenance", false, cwd),
28322
+ slsaLevel: readConfigValueSync("release.security.slsaLevel", 3, cwd),
28323
+ requireSignedCommits: readConfigValueSync("release.security.requireSignedCommits", false, cwd)
28324
+ }
28325
+ };
28326
+ }
28327
+ function getDefaultGitFlowConfig() {
28328
+ return {
28329
+ enabled: true,
28330
+ branches: {
28331
+ main: "main",
28332
+ develop: "develop",
28333
+ featurePrefix: "feature/",
28334
+ hotfixPrefix: "hotfix/",
28335
+ releasePrefix: "release/"
28336
+ }
28337
+ };
28338
+ }
28339
+ function getGitFlowConfig(config) {
28340
+ const defaults = getDefaultGitFlowConfig();
28341
+ if (!config.gitflow) return defaults;
28342
+ return {
28343
+ enabled: config.gitflow.enabled ?? defaults.enabled,
28344
+ branches: {
28345
+ main: config.gitflow.branches?.main ?? defaults.branches.main,
28346
+ develop: config.gitflow.branches?.develop ?? defaults.branches.develop,
28347
+ featurePrefix: config.gitflow.branches?.featurePrefix ?? defaults.branches.featurePrefix,
28348
+ hotfixPrefix: config.gitflow.branches?.hotfixPrefix ?? defaults.branches.hotfixPrefix,
28349
+ releasePrefix: config.gitflow.branches?.releasePrefix ?? defaults.branches.releasePrefix
28350
+ }
28351
+ };
28352
+ }
28353
+ function getDefaultChannelConfig2() {
28354
+ return {
28355
+ main: "latest",
28356
+ develop: "beta",
28357
+ feature: "alpha"
28358
+ };
28359
+ }
28360
+ function getChannelConfig(config) {
28361
+ const defaults = getDefaultChannelConfig2();
28362
+ if (!config.channels) return defaults;
28363
+ return {
28364
+ main: config.channels.main ?? defaults.main,
28365
+ develop: config.channels.develop ?? defaults.develop,
28366
+ feature: config.channels.feature ?? defaults.feature,
28367
+ custom: config.channels.custom
28368
+ };
28369
+ }
28370
+ function getPushMode(config) {
28371
+ return config.push?.mode ?? "auto";
28372
+ }
28373
+ var DEFAULTS2;
28374
+ var init_release_config = __esm({
28375
+ "src/core/release/release-config.ts"() {
28376
+ "use strict";
28377
+ init_paths();
28378
+ DEFAULTS2 = {
28379
+ versioningScheme: "calver",
28380
+ tagPrefix: "v",
28381
+ changelogFormat: "keepachangelog",
28382
+ changelogFile: "CHANGELOG.md",
28383
+ artifactType: "generic-tarball"
28384
+ };
28385
+ }
28386
+ });
28387
+
28388
+ // src/core/release/release-manifest.ts
28389
+ import { existsSync as existsSync50, renameSync as renameSync7 } from "node:fs";
28390
+ import { readFile as readFile10 } from "node:fs/promises";
28391
+ import { execFileSync as execFileSync7 } from "node:child_process";
28392
+ import { join as join48 } from "node:path";
27999
28393
  import { eq as eq14, desc as desc4 } from "drizzle-orm";
28000
28394
  function isValidVersion(version) {
28001
28395
  return /^v?\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/.test(version);
@@ -28087,25 +28481,67 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28087
28481
  const chores = [];
28088
28482
  const docs = [];
28089
28483
  const tests = [];
28090
- const other = [];
28484
+ const changes = [];
28485
+ function stripConventionalPrefix(title) {
28486
+ return title.replace(/^(feat|fix|docs?|test|chore|refactor|style|ci|build|perf)(\([^)]+\))?:\s*/i, "");
28487
+ }
28488
+ function capitalize(s) {
28489
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
28490
+ }
28491
+ function buildEntry(task) {
28492
+ const cleanTitle = capitalize(stripConventionalPrefix(task.title));
28493
+ const desc6 = task.description?.trim();
28494
+ const shouldIncludeDesc = (() => {
28495
+ if (!desc6 || desc6.length === 0) return false;
28496
+ const titleNorm = cleanTitle.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
28497
+ const descNorm = desc6.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
28498
+ if (titleNorm === descNorm) return false;
28499
+ if (descNorm.startsWith(titleNorm) && descNorm.length < titleNorm.length * 1.3) return false;
28500
+ return desc6.length >= 20;
28501
+ })();
28502
+ if (shouldIncludeDesc) {
28503
+ const descDisplay = desc6.length > 150 ? desc6.slice(0, 147) + "..." : desc6;
28504
+ return `- **${cleanTitle}**: ${descDisplay} (${task.id})`;
28505
+ }
28506
+ return `- ${cleanTitle} (${task.id})`;
28507
+ }
28508
+ function categorizeTask(task) {
28509
+ if (task.type === "epic") return "changes";
28510
+ const labels = task.labels ?? [];
28511
+ const titleLower = stripConventionalPrefix(task.title).toLowerCase();
28512
+ const rawTitleLower = task.title.toLowerCase();
28513
+ if (/^feat(\([^)]+\))?:/.test(task.title.toLowerCase())) return "features";
28514
+ if (/^fix(\([^)]+\))?:/.test(task.title.toLowerCase())) return "fixes";
28515
+ if (/^docs?(\([^)]+\))?:/.test(task.title.toLowerCase())) return "docs";
28516
+ if (/^test(\([^)]+\))?:/.test(task.title.toLowerCase())) return "tests";
28517
+ if (/^(chore|refactor|style|ci|build|perf)(\([^)]+\))?:/.test(task.title.toLowerCase())) return "chores";
28518
+ if (labels.some((l) => ["feat", "feature", "enhancement", "add"].includes(l.toLowerCase()))) return "features";
28519
+ if (labels.some((l) => ["fix", "bug", "bugfix", "regression"].includes(l.toLowerCase()))) return "fixes";
28520
+ if (labels.some((l) => ["docs", "documentation"].includes(l.toLowerCase()))) return "docs";
28521
+ if (labels.some((l) => ["test", "testing"].includes(l.toLowerCase()))) return "tests";
28522
+ if (labels.some((l) => ["chore", "refactor", "cleanup", "maintenance"].includes(l.toLowerCase()))) return "chores";
28523
+ if (titleLower.startsWith("add ") || titleLower.includes("implement") || titleLower.startsWith("create ") || titleLower.startsWith("introduce ")) return "features";
28524
+ if (titleLower.includes("bug") || titleLower.startsWith("fix") || titleLower.includes("regression") || titleLower.includes("broken")) return "fixes";
28525
+ if (titleLower.startsWith("doc") || titleLower.includes("documentation") || titleLower.includes("readme") || titleLower.includes("changelog")) return "docs";
28526
+ if (titleLower.startsWith("test") || titleLower.includes("test") && titleLower.includes("add")) return "tests";
28527
+ if (titleLower.startsWith("chore") || titleLower.includes("refactor") || titleLower.includes("cleanup") || titleLower.includes("migrate") || titleLower.includes("upgrade") || titleLower.includes("remove ") || titleLower.startsWith("audit")) return "chores";
28528
+ if (rawTitleLower.startsWith("feat")) return "features";
28529
+ return "changes";
28530
+ }
28091
28531
  for (const taskId of releaseTasks) {
28092
28532
  const task = taskMap.get(taskId);
28093
28533
  if (!task) continue;
28094
- const titleLower = task.title.toLowerCase();
28095
- const entry = `- ${task.title} (${task.id})`;
28096
- if (titleLower.startsWith("feat") || titleLower.includes("add ") || titleLower.includes("implement")) {
28097
- features.push(entry);
28098
- } else if (titleLower.startsWith("fix") || titleLower.includes("bug")) {
28099
- fixes.push(entry);
28100
- } else if (titleLower.startsWith("doc") || titleLower.includes("documentation")) {
28101
- docs.push(entry);
28102
- } else if (titleLower.startsWith("test") || titleLower.includes("test")) {
28103
- tests.push(entry);
28104
- } else if (titleLower.startsWith("chore") || titleLower.includes("refactor")) {
28105
- chores.push(entry);
28106
- } else {
28107
- other.push(entry);
28108
- }
28534
+ if (task.type === "epic") continue;
28535
+ if (task.labels?.some((l) => l.toLowerCase() === "epic")) continue;
28536
+ if (/^epic:/i.test(task.title.trim())) continue;
28537
+ const category = categorizeTask(task);
28538
+ const entry = buildEntry(task);
28539
+ if (category === "features") features.push(entry);
28540
+ else if (category === "fixes") fixes.push(entry);
28541
+ else if (category === "docs") docs.push(entry);
28542
+ else if (category === "tests") tests.push(entry);
28543
+ else if (category === "chores") chores.push(entry);
28544
+ else changes.push(entry);
28109
28545
  }
28110
28546
  const sections = [];
28111
28547
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -28140,14 +28576,14 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28140
28576
  sections.push(...chores);
28141
28577
  sections.push("");
28142
28578
  }
28143
- if (other.length > 0) {
28144
- sections.push("### Other");
28145
- sections.push(...other);
28579
+ if (changes.length > 0) {
28580
+ sections.push("### Changes");
28581
+ sections.push(...changes);
28146
28582
  sections.push("");
28147
28583
  }
28148
28584
  const changelog = sections.join("\n");
28149
28585
  await db.update(releaseManifests).set({ changelog }).where(eq14(releaseManifests.version, normalizedVersion)).run();
28150
- const changelogPath = join47(cwd ?? process.cwd(), "CHANGELOG.md");
28586
+ const changelogPath = join48(cwd ?? process.cwd(), "CHANGELOG.md");
28151
28587
  let existingChangelogContent = "";
28152
28588
  try {
28153
28589
  existingChangelogContent = await readFile10(changelogPath, "utf8");
@@ -28166,7 +28602,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
28166
28602
  docs: docs.length,
28167
28603
  tests: tests.length,
28168
28604
  chores: chores.length,
28169
- other: other.length
28605
+ changes: changes.length
28170
28606
  }
28171
28607
  };
28172
28608
  }
@@ -28267,19 +28703,19 @@ async function runReleaseGates(version, loadTasksFn, cwd) {
28267
28703
  message: incompleteTasks.length === 0 ? "All tasks completed" : `${incompleteTasks.length} tasks not completed: ${incompleteTasks.join(", ")}`
28268
28704
  });
28269
28705
  const projectRoot = cwd ?? getProjectRoot();
28270
- const distPath = join47(projectRoot, "dist", "cli", "index.js");
28271
- const isNodeProject = existsSync49(join47(projectRoot, "package.json"));
28706
+ const distPath = join48(projectRoot, "dist", "cli", "index.js");
28707
+ const isNodeProject = existsSync50(join48(projectRoot, "package.json"));
28272
28708
  if (isNodeProject) {
28273
28709
  gates.push({
28274
28710
  name: "build_artifact",
28275
- status: existsSync49(distPath) ? "passed" : "failed",
28276
- message: existsSync49(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
28711
+ status: existsSync50(distPath) ? "passed" : "failed",
28712
+ message: existsSync50(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
28277
28713
  });
28278
28714
  }
28279
28715
  let workingTreeClean = true;
28280
28716
  let dirtyFiles = [];
28281
28717
  try {
28282
- const porcelain = execFileSync6("git", ["status", "--porcelain"], {
28718
+ const porcelain = execFileSync7("git", ["status", "--porcelain"], {
28283
28719
  cwd: projectRoot,
28284
28720
  encoding: "utf-8",
28285
28721
  stdio: "pipe"
@@ -28296,27 +28732,60 @@ async function runReleaseGates(version, loadTasksFn, cwd) {
28296
28732
  const isPreRelease = normalizedVersion.includes("-");
28297
28733
  let currentBranch = "";
28298
28734
  try {
28299
- currentBranch = execFileSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28735
+ currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28300
28736
  cwd: projectRoot,
28301
28737
  encoding: "utf-8",
28302
28738
  stdio: "pipe"
28303
28739
  }).trim();
28304
28740
  } catch {
28305
28741
  }
28306
- const expectedBranch = isPreRelease ? "develop" : "main";
28307
- const branchOk = !currentBranch || currentBranch === expectedBranch || currentBranch === "HEAD";
28742
+ const releaseConfig = loadReleaseConfig(cwd);
28743
+ const gitFlowCfg = getGitFlowConfig(releaseConfig);
28744
+ const channelCfg = getChannelConfig(releaseConfig);
28745
+ const expectedBranch = isPreRelease ? gitFlowCfg.branches.develop : gitFlowCfg.branches.main;
28746
+ const isFeatureBranch = currentBranch.startsWith(gitFlowCfg.branches.featurePrefix) || currentBranch.startsWith(gitFlowCfg.branches.hotfixPrefix) || currentBranch.startsWith(gitFlowCfg.branches.releasePrefix);
28747
+ const branchOk = !currentBranch || currentBranch === "HEAD" || currentBranch === expectedBranch || isPreRelease && isFeatureBranch;
28748
+ const detectedChannel = currentBranch ? resolveChannelFromBranch(currentBranch, channelCfg) : isPreRelease ? "beta" : "latest";
28308
28749
  gates.push({
28309
28750
  name: "branch_target",
28310
28751
  status: branchOk ? "passed" : "failed",
28311
- message: branchOk ? `On correct branch: ${currentBranch}` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
28752
+ message: branchOk ? `On correct branch: ${currentBranch} (channel: ${detectedChannel})` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
28753
+ });
28754
+ const pushMode = getPushMode(releaseConfig);
28755
+ let requiresPR = false;
28756
+ if (pushMode === "pr") {
28757
+ requiresPR = true;
28758
+ } else if (pushMode === "auto") {
28759
+ try {
28760
+ const protectionResult = await detectBranchProtection(
28761
+ expectedBranch,
28762
+ "origin",
28763
+ projectRoot
28764
+ );
28765
+ requiresPR = protectionResult.protected;
28766
+ } catch {
28767
+ requiresPR = false;
28768
+ }
28769
+ }
28770
+ gates.push({
28771
+ name: "branch_protection",
28772
+ status: "passed",
28773
+ message: requiresPR ? `Branch '${expectedBranch}' is protected \u2014 release.ship will create a PR` : `Branch '${expectedBranch}' allows direct push`
28312
28774
  });
28313
28775
  const allPassed = gates.every((g) => g.status === "passed");
28776
+ const metadata = {
28777
+ channel: detectedChannel,
28778
+ requiresPR,
28779
+ targetBranch: expectedBranch,
28780
+ currentBranch
28781
+ };
28314
28782
  return {
28315
28783
  version: normalizedVersion,
28316
28784
  allPassed,
28317
28785
  gates,
28318
28786
  passedCount: gates.filter((g) => g.status === "passed").length,
28319
- failedCount: gates.filter((g) => g.status === "failed").length
28787
+ failedCount: gates.filter((g) => g.status === "failed").length,
28788
+ metadata
28320
28789
  };
28321
28790
  }
28322
28791
  async function rollbackRelease(version, reason, cwd) {
@@ -28339,7 +28808,7 @@ async function rollbackRelease(version, reason, cwd) {
28339
28808
  };
28340
28809
  }
28341
28810
  async function readPushPolicy(cwd) {
28342
- const configPath = join47(getCleoDirAbsolute(cwd), "config.json");
28811
+ const configPath = join48(getCleoDirAbsolute(cwd), "config.json");
28343
28812
  const config = await readJson(configPath);
28344
28813
  if (!config) return void 0;
28345
28814
  const release2 = config.release;
@@ -28353,6 +28822,33 @@ async function pushRelease(version, remote, cwd, opts) {
28353
28822
  const normalizedVersion = normalizeVersion(version);
28354
28823
  const projectRoot = getProjectRoot(cwd);
28355
28824
  const pushPolicy = await readPushPolicy(cwd);
28825
+ const configPushMode = getPushMode(loadReleaseConfig(cwd));
28826
+ const effectivePushMode = opts?.mode ?? pushPolicy?.mode ?? configPushMode;
28827
+ if (effectivePushMode === "pr" || effectivePushMode === "auto") {
28828
+ const targetRemoteForCheck = remote ?? pushPolicy?.remote ?? "origin";
28829
+ let branchIsProtected = effectivePushMode === "pr";
28830
+ if (effectivePushMode === "auto") {
28831
+ try {
28832
+ const protection = await detectBranchProtection(
28833
+ pushPolicy?.allowedBranches?.[0] ?? "main",
28834
+ targetRemoteForCheck,
28835
+ projectRoot
28836
+ );
28837
+ branchIsProtected = protection.protected;
28838
+ } catch {
28839
+ branchIsProtected = false;
28840
+ }
28841
+ }
28842
+ if (branchIsProtected) {
28843
+ return {
28844
+ version: normalizedVersion,
28845
+ status: "requires_pr",
28846
+ remote: targetRemoteForCheck,
28847
+ pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
28848
+ requiresPR: true
28849
+ };
28850
+ }
28851
+ }
28356
28852
  if (pushPolicy && pushPolicy.enabled === false && !opts?.explicitPush) {
28357
28853
  throw new Error(
28358
28854
  "Push is disabled by config (release.push.enabled=false). Use --push to override."
@@ -28360,7 +28856,7 @@ async function pushRelease(version, remote, cwd, opts) {
28360
28856
  }
28361
28857
  const targetRemote = remote ?? pushPolicy?.remote ?? "origin";
28362
28858
  if (pushPolicy?.requireCleanTree) {
28363
- const statusOutput = execFileSync6("git", ["status", "--porcelain"], {
28859
+ const statusOutput = execFileSync7("git", ["status", "--porcelain"], {
28364
28860
  cwd: projectRoot,
28365
28861
  timeout: 1e4,
28366
28862
  encoding: "utf-8",
@@ -28373,7 +28869,7 @@ async function pushRelease(version, remote, cwd, opts) {
28373
28869
  }
28374
28870
  }
28375
28871
  if (pushPolicy?.allowedBranches && pushPolicy.allowedBranches.length > 0) {
28376
- const currentBranch = execFileSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28872
+ const currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
28377
28873
  cwd: projectRoot,
28378
28874
  timeout: 1e4,
28379
28875
  encoding: "utf-8",
@@ -28385,7 +28881,7 @@ async function pushRelease(version, remote, cwd, opts) {
28385
28881
  );
28386
28882
  }
28387
28883
  }
28388
- execFileSync6("git", ["push", targetRemote, "--follow-tags"], {
28884
+ execFileSync7("git", ["push", targetRemote, "--follow-tags"], {
28389
28885
  cwd: projectRoot,
28390
28886
  timeout: 6e4,
28391
28887
  encoding: "utf-8",
@@ -28416,6 +28912,9 @@ var init_release_manifest = __esm({
28416
28912
  init_paths();
28417
28913
  init_json();
28418
28914
  init_changelog_writer();
28915
+ init_github_pr();
28916
+ init_channel();
28917
+ init_release_config();
28419
28918
  }
28420
28919
  });
28421
28920
 
@@ -28496,7 +28995,7 @@ var init_guards = __esm({
28496
28995
  });
28497
28996
 
28498
28997
  // src/dispatch/engines/release-engine.ts
28499
- import { execFileSync as execFileSync7 } from "node:child_process";
28998
+ import { execFileSync as execFileSync8 } from "node:child_process";
28500
28999
  function isAgentContext() {
28501
29000
  return !!(process.env["CLEO_SESSION_ID"] || process.env["CLAUDE_AGENT_TYPE"]);
28502
29001
  }
@@ -28638,7 +29137,7 @@ async function releasePush(version, remote, projectRoot, opts) {
28638
29137
  const result = await pushRelease(version, remote, projectRoot, opts);
28639
29138
  let commitSha;
28640
29139
  try {
28641
- commitSha = execFileSync7("git", ["rev-parse", "HEAD"], {
29140
+ commitSha = execFileSync8("git", ["rev-parse", "HEAD"], {
28642
29141
  cwd: projectRoot ?? process.cwd(),
28643
29142
  encoding: "utf-8",
28644
29143
  stdio: "pipe"
@@ -28668,7 +29167,17 @@ async function releaseShip(params, projectRoot) {
28668
29167
  return engineError("E_INVALID_INPUT", "epicId is required");
28669
29168
  }
28670
29169
  const cwd = projectRoot ?? resolveProjectRoot();
29170
+ const logStep = (n, total, label, done, error) => {
29171
+ if (done === void 0) {
29172
+ console.log(`[Step ${n}/${total}] ${label}...`);
29173
+ } else if (done) {
29174
+ console.log(` \u2713 ${label}`);
29175
+ } else {
29176
+ console.log(` \u2717 ${label}: ${error ?? "failed"}`);
29177
+ }
29178
+ };
28671
29179
  try {
29180
+ logStep(1, 7, "Validate release gates");
28672
29181
  const gatesResult = await runReleaseGates(
28673
29182
  version,
28674
29183
  () => loadTasks2(projectRoot),
@@ -28676,10 +29185,32 @@ async function releaseShip(params, projectRoot) {
28676
29185
  );
28677
29186
  if (gatesResult && !gatesResult.allPassed) {
28678
29187
  const failedGates = gatesResult.gates.filter((g) => g.status === "failed");
29188
+ logStep(1, 7, "Validate release gates", false, failedGates.map((g) => g.name).join(", "));
28679
29189
  return engineError("E_LIFECYCLE_GATE_FAILED", `Release gates failed for ${version}: ${failedGates.map((g) => g.name).join(", ")}`, {
28680
29190
  details: { gates: gatesResult.gates, failedCount: gatesResult.failedCount }
28681
29191
  });
28682
29192
  }
29193
+ logStep(1, 7, "Validate release gates", true);
29194
+ let resolvedChannel = "latest";
29195
+ let currentBranchForPR = "HEAD";
29196
+ try {
29197
+ const branchName = execFileSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
29198
+ cwd,
29199
+ encoding: "utf-8",
29200
+ stdio: "pipe"
29201
+ }).trim();
29202
+ currentBranchForPR = branchName;
29203
+ const channelEnum = resolveChannelFromBranch(branchName);
29204
+ resolvedChannel = channelToDistTag(channelEnum);
29205
+ } catch {
29206
+ }
29207
+ const gateMetadata = gatesResult.metadata;
29208
+ const requiresPRFromGates = gateMetadata?.requiresPR ?? false;
29209
+ const targetBranchFromGates = gateMetadata?.targetBranch;
29210
+ if (gateMetadata?.currentBranch) {
29211
+ currentBranchForPR = gateMetadata.currentBranch;
29212
+ }
29213
+ logStep(2, 7, "Check epic completeness");
28683
29214
  let releaseTaskIds = [];
28684
29215
  try {
28685
29216
  const manifest = await showManifestRelease(version, projectRoot);
@@ -28690,10 +29221,13 @@ async function releaseShip(params, projectRoot) {
28690
29221
  const epicCheck = await checkEpicCompleteness(releaseTaskIds, projectRoot, epicAccessor);
28691
29222
  if (epicCheck.hasIncomplete) {
28692
29223
  const incomplete = epicCheck.epics.filter((e) => e.missingChildren.length > 0).map((e) => `${e.epicId}: missing ${e.missingChildren.map((c) => c.id).join(", ")}`).join("; ");
29224
+ logStep(2, 7, "Check epic completeness", false, incomplete);
28693
29225
  return engineError("E_LIFECYCLE_GATE_FAILED", `Epic completeness check failed: ${incomplete}`, {
28694
29226
  details: { epics: epicCheck.epics }
28695
29227
  });
28696
29228
  }
29229
+ logStep(2, 7, "Check epic completeness", true);
29230
+ logStep(3, 7, "Check task double-listing");
28697
29231
  const allReleases = await listManifestReleases(projectRoot);
28698
29232
  const existingReleases = (allReleases.releases ?? []).filter((r) => r.version !== version);
28699
29233
  const doubleCheck = checkDoubleListing(
@@ -28702,10 +29236,13 @@ async function releaseShip(params, projectRoot) {
28702
29236
  );
28703
29237
  if (doubleCheck.hasDoubleListing) {
28704
29238
  const dupes = doubleCheck.duplicates.map((d) => `${d.taskId} (in ${d.releases.join(", ")})`).join("; ");
29239
+ logStep(3, 7, "Check task double-listing", false, dupes);
28705
29240
  return engineError("E_VALIDATION", `Double-listing detected: ${dupes}`, {
28706
29241
  details: { duplicates: doubleCheck.duplicates }
28707
29242
  });
28708
29243
  }
29244
+ logStep(3, 7, "Check task double-listing", true);
29245
+ logStep(4, 7, "Generate CHANGELOG");
28709
29246
  const changelogResult = await generateReleaseChangelog(
28710
29247
  version,
28711
29248
  () => loadTasks2(projectRoot),
@@ -28713,62 +29250,128 @@ async function releaseShip(params, projectRoot) {
28713
29250
  );
28714
29251
  const changelogPath = `${cwd}/CHANGELOG.md`;
28715
29252
  const generatedContent = changelogResult.changelog ?? "";
29253
+ logStep(4, 7, "Generate CHANGELOG", true);
29254
+ const loadedConfig = loadReleaseConfig(cwd);
29255
+ const pushMode = getPushMode(loadedConfig);
29256
+ const gitflowCfg = getGitFlowConfig(loadedConfig);
29257
+ const targetBranch = targetBranchFromGates ?? gitflowCfg.branches.main;
28716
29258
  if (dryRun) {
28717
- return {
28718
- success: true,
28719
- data: {
28720
- version,
28721
- epicId,
28722
- dryRun: true,
28723
- wouldDo: [
28724
- `write CHANGELOG section for ${version} (${generatedContent.length} chars)`,
28725
- "git add CHANGELOG.md",
28726
- `git commit -m "release: ship v${version} (${epicId})"`,
28727
- `git tag -a v${version} -m "Release v${version}"`,
28728
- `git push ${remote ?? "origin"} --follow-tags`,
28729
- "markReleasePushed(...)"
28730
- ]
28731
- }
29259
+ const wouldCreatePR = requiresPRFromGates || pushMode === "pr";
29260
+ const dryRunOutput = {
29261
+ version,
29262
+ epicId,
29263
+ dryRun: true,
29264
+ channel: resolvedChannel,
29265
+ pushMode,
29266
+ wouldDo: [
29267
+ `write CHANGELOG section for ${version} (${generatedContent.length} chars)`,
29268
+ "git add CHANGELOG.md",
29269
+ `git commit -m "release: ship v${version} (${epicId})"`,
29270
+ `git tag -a v${version} -m "Release v${version}"`
29271
+ ]
28732
29272
  };
29273
+ if (wouldCreatePR) {
29274
+ const ghAvailable = isGhCliAvailable();
29275
+ dryRunOutput["wouldDo"].push(
29276
+ ghAvailable ? `gh pr create --base ${targetBranch} --head ${currentBranchForPR} --title "release: ship v${version}"` : `manual PR: ${currentBranchForPR} \u2192 ${targetBranch} (gh CLI not available)`
29277
+ );
29278
+ dryRunOutput["wouldCreatePR"] = true;
29279
+ dryRunOutput["prTitle"] = `release: ship v${version}`;
29280
+ dryRunOutput["prTargetBranch"] = targetBranch;
29281
+ } else {
29282
+ dryRunOutput["wouldDo"].push(
29283
+ `git push ${remote ?? "origin"} --follow-tags`
29284
+ );
29285
+ dryRunOutput["wouldCreatePR"] = false;
29286
+ }
29287
+ dryRunOutput["wouldDo"].push("markReleasePushed(...)");
29288
+ return { success: true, data: dryRunOutput };
28733
29289
  }
28734
- await writeChangelogSection(version, generatedContent, [], changelogPath);
29290
+ logStep(5, 7, "Commit release");
28735
29291
  const gitCwd = { cwd, encoding: "utf-8", stdio: "pipe" };
28736
29292
  try {
28737
- execFileSync7("git", ["add", "CHANGELOG.md"], gitCwd);
29293
+ execFileSync8("git", ["add", "CHANGELOG.md"], gitCwd);
28738
29294
  } catch (err) {
28739
29295
  const msg = err.message ?? String(err);
29296
+ logStep(5, 7, "Commit release", false, `git add failed: ${msg}`);
28740
29297
  return engineError("E_GENERAL", `git add failed: ${msg}`);
28741
29298
  }
28742
29299
  try {
28743
- execFileSync7(
29300
+ execFileSync8(
28744
29301
  "git",
28745
29302
  ["commit", "-m", `release: ship v${version} (${epicId})`],
28746
29303
  gitCwd
28747
29304
  );
28748
29305
  } catch (err) {
28749
29306
  const msg = err.stderr ?? err.message ?? String(err);
29307
+ logStep(5, 7, "Commit release", false, `git commit failed: ${msg}`);
28750
29308
  return engineError("E_GENERAL", `git commit failed: ${msg}`);
28751
29309
  }
29310
+ logStep(5, 7, "Commit release", true);
28752
29311
  let commitSha;
28753
29312
  try {
28754
- commitSha = execFileSync7("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
29313
+ commitSha = execFileSync8("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
28755
29314
  } catch {
28756
29315
  }
29316
+ logStep(6, 7, "Tag release");
28757
29317
  const gitTag = `v${version.replace(/^v/, "")}`;
28758
29318
  try {
28759
- execFileSync7("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
29319
+ execFileSync8("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
28760
29320
  } catch (err) {
28761
29321
  const msg = err.stderr ?? err.message ?? String(err);
29322
+ logStep(6, 7, "Tag release", false, `git tag failed: ${msg}`);
28762
29323
  return engineError("E_GENERAL", `git tag failed: ${msg}`);
28763
29324
  }
28764
- try {
28765
- execFileSync7("git", ["push", remote ?? "origin", "--follow-tags"], gitCwd);
28766
- } catch (err) {
28767
- const execError = err;
28768
- const msg = (execError.stderr ?? execError.message ?? "").slice(0, 500);
28769
- return engineError("E_GENERAL", `git push failed: ${msg}`, {
28770
- details: { exitCode: execError.status }
29325
+ logStep(6, 7, "Tag release", true);
29326
+ logStep(7, 7, "Push / create PR");
29327
+ let prResult = null;
29328
+ const pushResult = await pushRelease(version, remote, projectRoot, {
29329
+ explicitPush: true,
29330
+ mode: pushMode
29331
+ });
29332
+ if (pushResult.requiresPR || requiresPRFromGates) {
29333
+ const prBody = buildPRBody({
29334
+ base: targetBranch,
29335
+ head: currentBranchForPR,
29336
+ title: `release: ship v${version}`,
29337
+ body: "",
29338
+ version,
29339
+ epicId,
29340
+ projectRoot: cwd
29341
+ });
29342
+ prResult = await createPullRequest({
29343
+ base: targetBranch,
29344
+ head: currentBranchForPR,
29345
+ title: `release: ship v${version}`,
29346
+ body: prBody,
29347
+ labels: ["release", resolvedChannel],
29348
+ version,
29349
+ epicId,
29350
+ projectRoot: cwd
28771
29351
  });
29352
+ if (prResult.mode === "created") {
29353
+ console.log(` \u2713 Push / create PR`);
29354
+ console.log(` PR created: ${prResult.prUrl}`);
29355
+ console.log(` \u2192 Next: merge the PR, then CI will publish to npm @${resolvedChannel}`);
29356
+ } else if (prResult.mode === "skipped") {
29357
+ console.log(` \u2713 Push / create PR`);
29358
+ console.log(` PR already exists: ${prResult.prUrl}`);
29359
+ } else {
29360
+ console.log(` ! Push / create PR \u2014 manual PR required:`);
29361
+ console.log(prResult.instructions);
29362
+ }
29363
+ } else {
29364
+ try {
29365
+ execFileSync8("git", ["push", remote ?? "origin", "--follow-tags"], gitCwd);
29366
+ logStep(7, 7, "Push / create PR", true);
29367
+ } catch (err) {
29368
+ const execError = err;
29369
+ const msg = (execError.stderr ?? execError.message ?? "").slice(0, 500);
29370
+ logStep(7, 7, "Push / create PR", false, `git push failed: ${msg}`);
29371
+ return engineError("E_GENERAL", `git push failed: ${msg}`, {
29372
+ details: { exitCode: execError.status }
29373
+ });
29374
+ }
28772
29375
  }
28773
29376
  const pushedAt = (/* @__PURE__ */ new Date()).toISOString();
28774
29377
  await markReleasePushed(version, pushedAt, projectRoot, { commitSha, gitTag });
@@ -28780,7 +29383,16 @@ async function releaseShip(params, projectRoot) {
28780
29383
  commitSha,
28781
29384
  gitTag,
28782
29385
  pushedAt,
28783
- changelog: changelogPath
29386
+ changelog: changelogPath,
29387
+ channel: resolvedChannel,
29388
+ ...prResult ? {
29389
+ pr: {
29390
+ mode: prResult.mode,
29391
+ prUrl: prResult.prUrl,
29392
+ prNumber: prResult.prNumber,
29393
+ instructions: prResult.instructions
29394
+ }
29395
+ } : {}
28784
29396
  }
28785
29397
  };
28786
29398
  } catch (err) {
@@ -28793,15 +29405,17 @@ var init_release_engine = __esm({
28793
29405
  init_platform();
28794
29406
  init_data_accessor();
28795
29407
  init_release_manifest();
28796
- init_changelog_writer();
28797
29408
  init_guards();
29409
+ init_github_pr();
29410
+ init_channel();
29411
+ init_release_config();
28798
29412
  init_error();
28799
29413
  }
28800
29414
  });
28801
29415
 
28802
29416
  // src/dispatch/engines/template-parser.ts
28803
- import { readFileSync as readFileSync38, readdirSync as readdirSync12, existsSync as existsSync50 } from "fs";
28804
- import { join as join48 } from "path";
29417
+ import { readFileSync as readFileSync39, readdirSync as readdirSync12, existsSync as existsSync51 } from "fs";
29418
+ import { join as join49 } from "path";
28805
29419
  import { parse as parseYaml } from "yaml";
28806
29420
  function deriveSubcommand(filename) {
28807
29421
  let stem = filename.replace(/\.ya?ml$/i, "");
@@ -28815,8 +29429,8 @@ function deriveSubcommand(filename) {
28815
29429
  return firstWord.toLowerCase();
28816
29430
  }
28817
29431
  function parseTemplateFile(templateDir, filename) {
28818
- const filePath = join48(templateDir, filename);
28819
- const raw = readFileSync38(filePath, "utf-8");
29432
+ const filePath = join49(templateDir, filename);
29433
+ const raw = readFileSync39(filePath, "utf-8");
28820
29434
  const parsed = parseYaml(raw);
28821
29435
  const name = typeof parsed.name === "string" ? parsed.name : filename;
28822
29436
  const titlePrefix = typeof parsed.title === "string" ? parsed.title : "";
@@ -28865,8 +29479,8 @@ function parseTemplateFile(templateDir, filename) {
28865
29479
  };
28866
29480
  }
28867
29481
  function parseIssueTemplates(projectRoot) {
28868
- const templateDir = join48(projectRoot, ".github", "ISSUE_TEMPLATE");
28869
- if (!existsSync50(templateDir)) {
29482
+ const templateDir = join49(projectRoot, ".github", "ISSUE_TEMPLATE");
29483
+ if (!existsSync51(templateDir)) {
28870
29484
  return engineError("E_NOT_FOUND", `Issue template directory not found: ${templateDir}`);
28871
29485
  }
28872
29486
  let files;
@@ -30545,26 +31159,26 @@ var init_check = __esm({
30545
31159
  });
30546
31160
 
30547
31161
  // src/core/adrs/validate.ts
30548
- import { readFileSync as readFileSync39, readdirSync as readdirSync13, existsSync as existsSync51 } from "node:fs";
30549
- import { join as join49 } from "node:path";
31162
+ import { readFileSync as readFileSync40, readdirSync as readdirSync13, existsSync as existsSync52 } from "node:fs";
31163
+ import { join as join50 } from "node:path";
30550
31164
  import AjvModule3 from "ajv";
30551
31165
  async function validateAllAdrs(projectRoot) {
30552
- const adrsDir = join49(projectRoot, ".cleo", "adrs");
30553
- const schemaPath = join49(projectRoot, "schemas", "adr-frontmatter.schema.json");
30554
- if (!existsSync51(schemaPath)) {
31166
+ const adrsDir = join50(projectRoot, ".cleo", "adrs");
31167
+ const schemaPath = join50(projectRoot, "schemas", "adr-frontmatter.schema.json");
31168
+ if (!existsSync52(schemaPath)) {
30555
31169
  return {
30556
31170
  valid: false,
30557
31171
  errors: [{ file: "schemas/adr-frontmatter.schema.json", field: "schema", message: "Schema file not found" }],
30558
31172
  checked: 0
30559
31173
  };
30560
31174
  }
30561
- if (!existsSync51(adrsDir)) {
31175
+ if (!existsSync52(adrsDir)) {
30562
31176
  return { valid: true, errors: [], checked: 0 };
30563
31177
  }
30564
- const schema = JSON.parse(readFileSync39(schemaPath, "utf-8"));
31178
+ const schema = JSON.parse(readFileSync40(schemaPath, "utf-8"));
30565
31179
  const ajv = new Ajv3({ allErrors: true });
30566
31180
  const validate = ajv.compile(schema);
30567
- const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join49(adrsDir, f));
31181
+ const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join50(adrsDir, f));
30568
31182
  const errors = [];
30569
31183
  for (const filePath of files) {
30570
31184
  const record = parseAdrFile(filePath, projectRoot);
@@ -30591,15 +31205,15 @@ var init_validate = __esm({
30591
31205
  });
30592
31206
 
30593
31207
  // src/core/adrs/list.ts
30594
- import { readdirSync as readdirSync14, existsSync as existsSync52 } from "node:fs";
30595
- import { join as join50 } from "node:path";
31208
+ import { readdirSync as readdirSync14, existsSync as existsSync53 } from "node:fs";
31209
+ import { join as join51 } from "node:path";
30596
31210
  async function listAdrs(projectRoot, opts) {
30597
- const adrsDir = join50(projectRoot, ".cleo", "adrs");
30598
- if (!existsSync52(adrsDir)) {
31211
+ const adrsDir = join51(projectRoot, ".cleo", "adrs");
31212
+ if (!existsSync53(adrsDir)) {
30599
31213
  return { adrs: [], total: 0 };
30600
31214
  }
30601
31215
  const files = readdirSync14(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
30602
- const records = files.map((f) => parseAdrFile(join50(adrsDir, f), projectRoot));
31216
+ const records = files.map((f) => parseAdrFile(join51(adrsDir, f), projectRoot));
30603
31217
  const filtered = records.filter((r) => {
30604
31218
  if (opts?.status && r.frontmatter.Status !== opts.status) return false;
30605
31219
  if (opts?.since && r.frontmatter.Date < opts.since) return false;
@@ -30624,14 +31238,14 @@ var init_list2 = __esm({
30624
31238
  });
30625
31239
 
30626
31240
  // src/core/adrs/show.ts
30627
- import { existsSync as existsSync53, readdirSync as readdirSync15 } from "node:fs";
30628
- import { join as join51 } from "node:path";
31241
+ import { existsSync as existsSync54, readdirSync as readdirSync15 } from "node:fs";
31242
+ import { join as join52 } from "node:path";
30629
31243
  async function showAdr(projectRoot, adrId) {
30630
- const adrsDir = join51(projectRoot, ".cleo", "adrs");
30631
- if (!existsSync53(adrsDir)) return null;
31244
+ const adrsDir = join52(projectRoot, ".cleo", "adrs");
31245
+ if (!existsSync54(adrsDir)) return null;
30632
31246
  const files = readdirSync15(adrsDir).filter((f) => f.startsWith(adrId) && f.endsWith(".md"));
30633
31247
  if (files.length === 0) return null;
30634
- const filePath = join51(adrsDir, files[0]);
31248
+ const filePath = join52(adrsDir, files[0]);
30635
31249
  return parseAdrFile(filePath, projectRoot);
30636
31250
  }
30637
31251
  var init_show2 = __esm({
@@ -30642,8 +31256,8 @@ var init_show2 = __esm({
30642
31256
  });
30643
31257
 
30644
31258
  // src/core/adrs/find.ts
30645
- import { readdirSync as readdirSync16, existsSync as existsSync54 } from "node:fs";
30646
- import { join as join52 } from "node:path";
31259
+ import { readdirSync as readdirSync16, existsSync as existsSync55 } from "node:fs";
31260
+ import { join as join53 } from "node:path";
30647
31261
  function normalise(s) {
30648
31262
  return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
30649
31263
  }
@@ -30660,8 +31274,8 @@ function matchedTerms(target, terms) {
30660
31274
  return terms.filter((term) => t.includes(term));
30661
31275
  }
30662
31276
  async function findAdrs(projectRoot, query, opts) {
30663
- const adrsDir = join52(projectRoot, ".cleo", "adrs");
30664
- if (!existsSync54(adrsDir)) {
31277
+ const adrsDir = join53(projectRoot, ".cleo", "adrs");
31278
+ if (!existsSync55(adrsDir)) {
30665
31279
  return { adrs: [], query, total: 0 };
30666
31280
  }
30667
31281
  const files = readdirSync16(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
@@ -30670,7 +31284,7 @@ async function findAdrs(projectRoot, query, opts) {
30670
31284
  const filterKeywords = opts?.keywords ? parseTags(opts.keywords) : null;
30671
31285
  const results = [];
30672
31286
  for (const file of files) {
30673
- const record = parseAdrFile(join52(adrsDir, file), projectRoot);
31287
+ const record = parseAdrFile(join53(adrsDir, file), projectRoot);
30674
31288
  const fm = record.frontmatter;
30675
31289
  if (opts?.status && fm.Status !== opts.status) continue;
30676
31290
  if (filterTopics && filterTopics.length > 0) {
@@ -30748,12 +31362,12 @@ var init_adrs = __esm({
30748
31362
  });
30749
31363
 
30750
31364
  // src/core/admin/sync.ts
30751
- import { join as join53 } from "node:path";
31365
+ import { join as join54 } from "node:path";
30752
31366
  import { rm as rm2, rmdir, stat as stat2 } from "node:fs/promises";
30753
31367
  async function getSyncStatus(projectRoot) {
30754
31368
  try {
30755
31369
  const cleoDir = getCleoDir(projectRoot);
30756
- const stateFile = join53(cleoDir, "sync", "todowrite-session.json");
31370
+ const stateFile = join54(cleoDir, "sync", "todowrite-session.json");
30757
31371
  const sessionState = await readJson(stateFile);
30758
31372
  if (!sessionState) {
30759
31373
  return {
@@ -30797,8 +31411,8 @@ async function getSyncStatus(projectRoot) {
30797
31411
  async function clearSyncState(projectRoot, dryRun) {
30798
31412
  try {
30799
31413
  const cleoDir = getCleoDir(projectRoot);
30800
- const syncDir = join53(cleoDir, "sync");
30801
- const stateFile = join53(syncDir, "todowrite-session.json");
31414
+ const syncDir = join54(cleoDir, "sync");
31415
+ const stateFile = join54(syncDir, "todowrite-session.json");
30802
31416
  let exists = false;
30803
31417
  try {
30804
31418
  await stat2(stateFile);
@@ -31565,8 +32179,8 @@ var init_import_tasks = __esm({
31565
32179
  // src/core/snapshot/index.ts
31566
32180
  import { createHash as createHash8 } from "node:crypto";
31567
32181
  import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir10 } from "node:fs/promises";
31568
- import { existsSync as existsSync55 } from "node:fs";
31569
- import { join as join54, dirname as dirname14 } from "node:path";
32182
+ import { existsSync as existsSync56 } from "node:fs";
32183
+ import { join as join55, dirname as dirname14 } from "node:path";
31570
32184
  function toSnapshotTask(task) {
31571
32185
  return {
31572
32186
  id: task.id,
@@ -31619,7 +32233,7 @@ async function exportSnapshot(cwd) {
31619
32233
  }
31620
32234
  async function writeSnapshot(snapshot, outputPath) {
31621
32235
  const dir = dirname14(outputPath);
31622
- if (!existsSync55(dir)) {
32236
+ if (!existsSync56(dir)) {
31623
32237
  await mkdir10(dir, { recursive: true });
31624
32238
  }
31625
32239
  await writeFile10(outputPath, JSON.stringify(snapshot, null, 2) + "\n");
@@ -31635,7 +32249,7 @@ async function readSnapshot(inputPath) {
31635
32249
  function getDefaultSnapshotPath(cwd) {
31636
32250
  const cleoDir = getCleoDirAbsolute(cwd);
31637
32251
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
31638
- return join54(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
32252
+ return join55(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
31639
32253
  }
31640
32254
  async function importSnapshot(snapshot, cwd) {
31641
32255
  const accessor = await getAccessor(cwd);
@@ -31767,40 +32381,49 @@ var init_session_resolver = __esm({
31767
32381
  }
31768
32382
  });
31769
32383
 
32384
+ // src/core/tasks/id-generator.ts
32385
+ function normalizeTaskId(input) {
32386
+ if (typeof input !== "string") return null;
32387
+ const trimmed = input.trim();
32388
+ if (trimmed === "") return null;
32389
+ const match = trimmed.match(/^[Tt]?(\d+)(?:_.*)?$/);
32390
+ if (!match) return null;
32391
+ return `T${match[1]}`;
32392
+ }
32393
+ var init_id_generator = __esm({
32394
+ "src/core/tasks/id-generator.ts"() {
32395
+ "use strict";
32396
+ init_data_accessor();
32397
+ }
32398
+ });
32399
+
31770
32400
  // src/dispatch/lib/security.ts
31771
32401
  import { resolve as resolve8, normalize, relative as relative4, isAbsolute as isAbsolute2 } from "path";
31772
- function sanitizeTaskId(id) {
31773
- if (typeof id !== "string") {
32402
+ function sanitizeTaskId(value) {
32403
+ if (typeof value !== "string") {
31774
32404
  throw new SecurityError(
31775
32405
  "Task ID must be a string",
31776
32406
  "E_INVALID_TASK_ID",
31777
32407
  "taskId"
31778
32408
  );
31779
32409
  }
31780
- const trimmed = id.trim();
31781
- if (trimmed.length === 0) {
31782
- throw new SecurityError(
31783
- "Task ID cannot be empty",
31784
- "E_INVALID_TASK_ID",
31785
- "taskId"
31786
- );
31787
- }
31788
- if (!TASK_ID_PATTERN.test(trimmed)) {
32410
+ const normalized = normalizeTaskId(value);
32411
+ if (normalized === null) {
31789
32412
  throw new SecurityError(
31790
- `Invalid task ID format: "${trimmed}". Must match pattern T[0-9]+ (e.g., T123)`,
32413
+ `Invalid task ID format: ${value}`,
31791
32414
  "E_INVALID_TASK_ID",
31792
32415
  "taskId"
31793
32416
  );
31794
32417
  }
31795
- const numericPart = parseInt(trimmed.slice(1), 10);
32418
+ const numericPart = parseInt(normalized.slice(1), 10);
31796
32419
  if (numericPart > MAX_TASK_ID_NUMBER) {
31797
32420
  throw new SecurityError(
31798
- `Task ID numeric value exceeds maximum (${MAX_TASK_ID_NUMBER}): ${trimmed}`,
32421
+ `Task ID exceeds maximum value: ${value}`,
31799
32422
  "E_INVALID_TASK_ID",
31800
32423
  "taskId"
31801
32424
  );
31802
32425
  }
31803
- return trimmed;
32426
+ return normalized;
31804
32427
  }
31805
32428
  function sanitizePath(path, projectRoot) {
31806
32429
  if (typeof path !== "string") {
@@ -31899,14 +32522,14 @@ function sanitizeParams(params, projectRoot, context) {
31899
32522
  if (value === void 0 || value === null) {
31900
32523
  continue;
31901
32524
  }
31902
- if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId")) {
32525
+ if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId" || key === "parentId" || key === "newParentId" || key === "relatedId" || key === "targetId")) {
31903
32526
  if (key === "parent" && value === "") {
31904
32527
  continue;
31905
32528
  }
31906
32529
  sanitized[key] = sanitizeTaskId(value);
31907
32530
  continue;
31908
32531
  }
31909
- if (key === "depends" && Array.isArray(value)) {
32532
+ if ((key === "depends" || key === "addDepends" || key === "removeDepends") && Array.isArray(value)) {
31910
32533
  sanitized[key] = value.map((v) => {
31911
32534
  if (typeof v === "string") {
31912
32535
  return sanitizeTaskId(v);
@@ -31956,10 +32579,11 @@ function sanitizeParams(params, projectRoot, context) {
31956
32579
  }
31957
32580
  return sanitized;
31958
32581
  }
31959
- var SecurityError, TASK_ID_PATTERN, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
32582
+ var SecurityError, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
31960
32583
  var init_security = __esm({
31961
32584
  "src/dispatch/lib/security.ts"() {
31962
32585
  "use strict";
32586
+ init_id_generator();
31963
32587
  init_schema();
31964
32588
  init_status_registry();
31965
32589
  SecurityError = class extends Error {
@@ -31970,7 +32594,6 @@ var init_security = __esm({
31970
32594
  this.name = "SecurityError";
31971
32595
  }
31972
32596
  };
31973
- TASK_ID_PATTERN = /^T[0-9]+$/;
31974
32597
  MAX_TASK_ID_NUMBER = 999999;
31975
32598
  CONTROL_CHAR_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
31976
32599
  DEFAULT_MAX_CONTENT_LENGTH = 64 * 1024;
@@ -32069,15 +32692,15 @@ var init_field_filter = __esm({
32069
32692
  });
32070
32693
 
32071
32694
  // src/core/project-info.ts
32072
- import { readFileSync as readFileSync40, existsSync as existsSync56 } from "node:fs";
32073
- import { join as join55 } from "node:path";
32695
+ import { readFileSync as readFileSync41, existsSync as existsSync57 } from "node:fs";
32696
+ import { join as join56 } from "node:path";
32074
32697
  function getProjectInfoSync(cwd) {
32075
32698
  const projectRoot = cwd ?? process.cwd();
32076
32699
  const cleoDir = getCleoDirAbsolute(projectRoot);
32077
- const infoPath = join55(cleoDir, "project-info.json");
32078
- if (!existsSync56(infoPath)) return null;
32700
+ const infoPath = join56(cleoDir, "project-info.json");
32701
+ if (!existsSync57(infoPath)) return null;
32079
32702
  try {
32080
- const raw = readFileSync40(infoPath, "utf-8");
32703
+ const raw = readFileSync41(infoPath, "utf-8");
32081
32704
  const data = JSON.parse(raw);
32082
32705
  if (typeof data.projectHash !== "string" || data.projectHash.length === 0) {
32083
32706
  return null;
@@ -32305,13 +32928,13 @@ var init_field_context = __esm({
32305
32928
  });
32306
32929
 
32307
32930
  // src/core/sessions/context-alert.ts
32308
- import { existsSync as existsSync57, readFileSync as readFileSync41, writeFileSync as writeFileSync8 } from "node:fs";
32309
- import { join as join56 } from "node:path";
32931
+ import { existsSync as existsSync58, readFileSync as readFileSync42, writeFileSync as writeFileSync8 } from "node:fs";
32932
+ import { join as join57 } from "node:path";
32310
32933
  function getCurrentSessionId(cwd) {
32311
32934
  if (process.env.CLEO_SESSION) return process.env.CLEO_SESSION;
32312
- const sessionFile = join56(getCleoDir(cwd), ".current-session");
32313
- if (existsSync57(sessionFile)) {
32314
- return readFileSync41(sessionFile, "utf-8").trim() || null;
32935
+ const sessionFile = join57(getCleoDir(cwd), ".current-session");
32936
+ if (existsSync58(sessionFile)) {
32937
+ return readFileSync42(sessionFile, "utf-8").trim() || null;
32315
32938
  }
32316
32939
  return null;
32317
32940
  }
@@ -33712,8 +34335,8 @@ __export(session_grade_exports, {
33712
34335
  gradeSession: () => gradeSession,
33713
34336
  readGrades: () => readGrades
33714
34337
  });
33715
- import { join as join57 } from "node:path";
33716
- import { existsSync as existsSync58 } from "node:fs";
34338
+ import { join as join58 } from "node:path";
34339
+ import { existsSync as existsSync59 } from "node:fs";
33717
34340
  import { readFile as readFile14, appendFile, mkdir as mkdir11 } from "node:fs/promises";
33718
34341
  async function gradeSession(sessionId, cwd) {
33719
34342
  const sessionEntries = await queryAudit({ sessionId });
@@ -33893,9 +34516,9 @@ function detectDuplicateCreates(entries) {
33893
34516
  async function appendGradeResult(result, cwd) {
33894
34517
  try {
33895
34518
  const cleoDir = getCleoDirAbsolute(cwd);
33896
- const metricsDir = join57(cleoDir, "metrics");
34519
+ const metricsDir = join58(cleoDir, "metrics");
33897
34520
  await mkdir11(metricsDir, { recursive: true });
33898
- const gradesPath = join57(metricsDir, "GRADES.jsonl");
34521
+ const gradesPath = join58(metricsDir, "GRADES.jsonl");
33899
34522
  const line = JSON.stringify({ ...result, evaluator: "auto" }) + "\n";
33900
34523
  await appendFile(gradesPath, line, "utf8");
33901
34524
  } catch {
@@ -33904,8 +34527,8 @@ async function appendGradeResult(result, cwd) {
33904
34527
  async function readGrades(sessionId, cwd) {
33905
34528
  try {
33906
34529
  const cleoDir = getCleoDirAbsolute(cwd);
33907
- const gradesPath = join57(cleoDir, "metrics", "GRADES.jsonl");
33908
- if (!existsSync58(gradesPath)) return [];
34530
+ const gradesPath = join58(cleoDir, "metrics", "GRADES.jsonl");
34531
+ if (!existsSync59(gradesPath)) return [];
33909
34532
  const content = await readFile14(gradesPath, "utf8");
33910
34533
  const results = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
33911
34534
  return sessionId ? results.filter((r) => r.sessionId === sessionId) : results;
@@ -35113,7 +35736,7 @@ function validateVariableType(name, varDef, value) {
35113
35736
  if (typeof value !== "string") {
35114
35737
  throw new Error(`Invalid variable type for "${name}": expected ${varDef.type}, got ${valueType(value)}`);
35115
35738
  }
35116
- if (!TASK_ID_PATTERN2.test(value)) {
35739
+ if (!TASK_ID_PATTERN.test(value)) {
35117
35740
  throw new Error(`Invalid variable format for "${name}": expected ${varDef.type} like "T1234", got "${value}"`);
35118
35741
  }
35119
35742
  return;
@@ -35271,7 +35894,7 @@ function showTessera(id) {
35271
35894
  ensureDefaults();
35272
35895
  return templates.get(id) ?? null;
35273
35896
  }
35274
- var DEFAULT_TESSERA_ID, TASK_ID_PATTERN2, PLACEHOLDER_EXACT, PLACEHOLDER_GLOBAL, templates;
35897
+ var DEFAULT_TESSERA_ID, TASK_ID_PATTERN, PLACEHOLDER_EXACT, PLACEHOLDER_GLOBAL, templates;
35275
35898
  var init_tessera_engine = __esm({
35276
35899
  "src/core/lifecycle/tessera-engine.ts"() {
35277
35900
  "use strict";
@@ -35279,7 +35902,7 @@ var init_tessera_engine = __esm({
35279
35902
  init_chain_validation();
35280
35903
  init_chain_store();
35281
35904
  DEFAULT_TESSERA_ID = "tessera-rcasd";
35282
- TASK_ID_PATTERN2 = /^T\d+$/;
35905
+ TASK_ID_PATTERN = /^T\d+$/;
35283
35906
  PLACEHOLDER_EXACT = /^\{\{\s*([A-Za-z0-9_]+)\s*\}\}$/;
35284
35907
  PLACEHOLDER_GLOBAL = /\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g;
35285
35908
  templates = /* @__PURE__ */ new Map();
@@ -36142,6 +36765,7 @@ var init_phase = __esm({
36142
36765
  });
36143
36766
 
36144
36767
  // src/dispatch/domains/pipeline.ts
36768
+ import { execFileSync as execFileSync9 } from "node:child_process";
36145
36769
  var PipelineHandler;
36146
36770
  var init_pipeline2 = __esm({
36147
36771
  "src/dispatch/domains/pipeline.ts"() {
@@ -36152,6 +36776,7 @@ var init_pipeline2 = __esm({
36152
36776
  init_data_accessor();
36153
36777
  init_engine();
36154
36778
  init_release_engine();
36779
+ init_channel();
36155
36780
  init_phase();
36156
36781
  init_phases();
36157
36782
  init_pipeline_manifest_sqlite();
@@ -36237,6 +36862,7 @@ var init_pipeline2 = __esm({
36237
36862
  "manifest.stats",
36238
36863
  "release.list",
36239
36864
  "release.show",
36865
+ "release.channel.show",
36240
36866
  "phase.show",
36241
36867
  "phase.list",
36242
36868
  "chain.show",
@@ -36479,6 +37105,29 @@ var init_pipeline2 = __esm({
36479
37105
  const result = await releaseShow(version, this.projectRoot);
36480
37106
  return this.wrapEngineResult(result, "query", "release.show", startTime);
36481
37107
  }
37108
+ case "channel.show": {
37109
+ let currentBranch = "unknown";
37110
+ try {
37111
+ currentBranch = execFileSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
37112
+ encoding: "utf-8",
37113
+ stdio: "pipe",
37114
+ cwd: this.projectRoot
37115
+ }).trim();
37116
+ } catch {
37117
+ }
37118
+ const resolvedChannel = resolveChannelFromBranch(currentBranch);
37119
+ const distTag = channelToDistTag(resolvedChannel);
37120
+ const description = describeChannel(resolvedChannel);
37121
+ return this.wrapEngineResult({
37122
+ success: true,
37123
+ data: {
37124
+ branch: currentBranch,
37125
+ channel: resolvedChannel,
37126
+ distTag,
37127
+ description
37128
+ }
37129
+ }, "query", "release.channel.show", startTime);
37130
+ }
36482
37131
  default:
36483
37132
  return this.errorResponse(
36484
37133
  "query",
@@ -37083,11 +37732,11 @@ var init_pipeline2 = __esm({
37083
37732
  });
37084
37733
 
37085
37734
  // src/core/issue/diagnostics.ts
37086
- import { execFileSync as execFileSync8 } from "node:child_process";
37735
+ import { execFileSync as execFileSync10 } from "node:child_process";
37087
37736
  function collectDiagnostics() {
37088
37737
  const getVersion3 = (cmd, args) => {
37089
37738
  try {
37090
- return execFileSync8(cmd, args, {
37739
+ return execFileSync10(cmd, args, {
37091
37740
  encoding: "utf-8",
37092
37741
  stdio: ["pipe", "pipe", "pipe"]
37093
37742
  }).trim();
@@ -37138,7 +37787,7 @@ var init_build_config = __esm({
37138
37787
  "use strict";
37139
37788
  BUILD_CONFIG = {
37140
37789
  "name": "@cleocode/cleo",
37141
- "version": "2026.3.16",
37790
+ "version": "2026.3.17",
37142
37791
  "description": "CLEO V2 - TypeScript task management CLI for AI coding agents",
37143
37792
  "repository": {
37144
37793
  "owner": "kryptobaseddev",
@@ -37147,7 +37796,7 @@ var init_build_config = __esm({
37147
37796
  "url": "https://github.com/kryptobaseddev/cleo.git",
37148
37797
  "issuesUrl": "https://github.com/kryptobaseddev/cleo/issues"
37149
37798
  },
37150
- "buildDate": "2026-03-07T05:25:13.762Z",
37799
+ "buildDate": "2026-03-07T07:08:25.337Z",
37151
37800
  "templates": {
37152
37801
  "issueTemplatesDir": "templates/issue-templates"
37153
37802
  }
@@ -37156,8 +37805,8 @@ var init_build_config = __esm({
37156
37805
  });
37157
37806
 
37158
37807
  // src/core/issue/template-parser.ts
37159
- import { existsSync as existsSync59, readFileSync as readFileSync42, readdirSync as readdirSync17, writeFileSync as writeFileSync9 } from "node:fs";
37160
- import { join as join58, basename as basename10 } from "node:path";
37808
+ import { existsSync as existsSync60, readFileSync as readFileSync43, readdirSync as readdirSync17, writeFileSync as writeFileSync9 } from "node:fs";
37809
+ import { join as join59, basename as basename10 } from "node:path";
37161
37810
  function extractYamlField(content, field) {
37162
37811
  const regex = new RegExp(`^${field}:\\s*["']?(.+?)["']?\\s*$`, "m");
37163
37812
  const match = content.match(regex);
@@ -37189,9 +37838,9 @@ function extractYamlArray(content, field) {
37189
37838
  return items;
37190
37839
  }
37191
37840
  function parseTemplateFile2(filePath) {
37192
- if (!existsSync59(filePath)) return null;
37841
+ if (!existsSync60(filePath)) return null;
37193
37842
  try {
37194
- const content = readFileSync42(filePath, "utf-8");
37843
+ const content = readFileSync43(filePath, "utf-8");
37195
37844
  const fileName = basename10(filePath);
37196
37845
  const stem = fileName.replace(/\.ya?ml$/, "");
37197
37846
  const name = extractYamlField(content, "name");
@@ -37208,12 +37857,12 @@ function parseTemplateFile2(filePath) {
37208
37857
  function parseIssueTemplates2(projectDir) {
37209
37858
  try {
37210
37859
  const packageRoot = getPackageRoot();
37211
- const packagedTemplateDir = join58(packageRoot, PACKAGED_TEMPLATE_DIR);
37212
- if (existsSync59(packagedTemplateDir)) {
37860
+ const packagedTemplateDir = join59(packageRoot, PACKAGED_TEMPLATE_DIR);
37861
+ if (existsSync60(packagedTemplateDir)) {
37213
37862
  const templates3 = [];
37214
37863
  for (const file of readdirSync17(packagedTemplateDir)) {
37215
37864
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
37216
- const template = parseTemplateFile2(join58(packagedTemplateDir, file));
37865
+ const template = parseTemplateFile2(join59(packagedTemplateDir, file));
37217
37866
  if (template) templates3.push(template);
37218
37867
  }
37219
37868
  if (templates3.length > 0) return templates3;
@@ -37221,12 +37870,12 @@ function parseIssueTemplates2(projectDir) {
37221
37870
  } catch {
37222
37871
  }
37223
37872
  const dir = projectDir ?? getProjectRoot();
37224
- const templateDir = join58(dir, TEMPLATE_DIR);
37225
- if (!existsSync59(templateDir)) return [];
37873
+ const templateDir = join59(dir, TEMPLATE_DIR);
37874
+ if (!existsSync60(templateDir)) return [];
37226
37875
  const templates2 = [];
37227
37876
  for (const file of readdirSync17(templateDir)) {
37228
37877
  if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
37229
- const template = parseTemplateFile2(join58(templateDir, file));
37878
+ const template = parseTemplateFile2(join59(templateDir, file));
37230
37879
  if (template) templates2.push(template);
37231
37880
  }
37232
37881
  return templates2;
@@ -37235,10 +37884,10 @@ function getTemplateConfig(cwd) {
37235
37884
  const projectDir = cwd ?? getProjectRoot();
37236
37885
  const liveTemplates = parseIssueTemplates2(projectDir);
37237
37886
  if (liveTemplates.length > 0) return liveTemplates;
37238
- const cachePath = join58(getCleoDir(cwd), CACHE_FILE);
37239
- if (existsSync59(cachePath)) {
37887
+ const cachePath = join59(getCleoDir(cwd), CACHE_FILE);
37888
+ if (existsSync60(cachePath)) {
37240
37889
  try {
37241
- const cached = JSON.parse(readFileSync42(cachePath, "utf-8"));
37890
+ const cached = JSON.parse(readFileSync43(cachePath, "utf-8"));
37242
37891
  if (cached.templates?.length > 0) return cached.templates;
37243
37892
  } catch {
37244
37893
  }
@@ -37294,7 +37943,7 @@ var init_template_parser2 = __esm({
37294
37943
  });
37295
37944
 
37296
37945
  // src/core/issue/create.ts
37297
- import { execFileSync as execFileSync9 } from "node:child_process";
37946
+ import { execFileSync as execFileSync11 } from "node:child_process";
37298
37947
  function buildIssueBody(subcommand, rawBody, severity, area) {
37299
37948
  const template = getTemplateForSubcommand2(subcommand);
37300
37949
  const sectionLabel = template?.name ?? "Description";
@@ -37313,7 +37962,7 @@ function buildIssueBody(subcommand, rawBody, severity, area) {
37313
37962
  }
37314
37963
  function checkGhCli() {
37315
37964
  try {
37316
- execFileSync9("gh", ["--version"], {
37965
+ execFileSync11("gh", ["--version"], {
37317
37966
  encoding: "utf-8",
37318
37967
  stdio: ["pipe", "pipe", "pipe"]
37319
37968
  });
@@ -37323,7 +37972,7 @@ function checkGhCli() {
37323
37972
  });
37324
37973
  }
37325
37974
  try {
37326
- execFileSync9("gh", ["auth", "status", "--hostname", "github.com"], {
37975
+ execFileSync11("gh", ["auth", "status", "--hostname", "github.com"], {
37327
37976
  encoding: "utf-8",
37328
37977
  stdio: ["pipe", "pipe", "pipe"]
37329
37978
  });
@@ -37335,7 +37984,7 @@ function checkGhCli() {
37335
37984
  }
37336
37985
  function addGhIssue(title, body, labels) {
37337
37986
  try {
37338
- const result = execFileSync9("gh", [
37987
+ const result = execFileSync11("gh", [
37339
37988
  "issue",
37340
37989
  "create",
37341
37990
  "--repo",
@@ -38297,8 +38946,8 @@ var init_tools = __esm({
38297
38946
  });
38298
38947
 
38299
38948
  // src/core/nexus/query.ts
38300
- import { join as join59, basename as basename11 } from "node:path";
38301
- import { existsSync as existsSync60, readFileSync as readFileSync43 } from "node:fs";
38949
+ import { join as join60, basename as basename11 } from "node:path";
38950
+ import { existsSync as existsSync61, readFileSync as readFileSync44 } from "node:fs";
38302
38951
  import { z as z3 } from "zod";
38303
38952
  function validateSyntax(query) {
38304
38953
  if (!query) return false;
@@ -38334,9 +38983,9 @@ function getCurrentProject() {
38334
38983
  return process.env["NEXUS_CURRENT_PROJECT"];
38335
38984
  }
38336
38985
  try {
38337
- const infoPath = join59(process.cwd(), ".cleo", "project-info.json");
38338
- if (existsSync60(infoPath)) {
38339
- const data = JSON.parse(readFileSync43(infoPath, "utf-8"));
38986
+ const infoPath = join60(process.cwd(), ".cleo", "project-info.json");
38987
+ if (existsSync61(infoPath)) {
38988
+ const data = JSON.parse(readFileSync44(infoPath, "utf-8"));
38340
38989
  if (typeof data.name === "string" && data.name.length > 0) {
38341
38990
  return data.name;
38342
38991
  }
@@ -38372,7 +39021,7 @@ async function resolveProjectPath2(projectName) {
38372
39021
  return project.path;
38373
39022
  }
38374
39023
  async function readProjectTasks(projectPath) {
38375
- const tasksDbPath = join59(projectPath, ".cleo", "tasks.db");
39024
+ const tasksDbPath = join60(projectPath, ".cleo", "tasks.db");
38376
39025
  try {
38377
39026
  const accessor = await getAccessor(projectPath);
38378
39027
  const taskFile = await accessor.loadTaskFile();
@@ -38786,8 +39435,8 @@ var init_deps2 = __esm({
38786
39435
 
38787
39436
  // src/core/nexus/sharing/index.ts
38788
39437
  import { readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
38789
- import { existsSync as existsSync61, readdirSync as readdirSync18, statSync as statSync9 } from "node:fs";
38790
- import { join as join60, relative as relative5 } from "node:path";
39438
+ import { existsSync as existsSync62, readdirSync as readdirSync18, statSync as statSync9 } from "node:fs";
39439
+ import { join as join61, relative as relative5 } from "node:path";
38791
39440
  function matchesPattern(filePath, pattern) {
38792
39441
  const normalizedPath = filePath.replace(/^\/+|\/+$/g, "");
38793
39442
  const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
@@ -38812,7 +39461,7 @@ function collectCleoFiles(cleoDir) {
38812
39461
  const entries = readdirSync18(dir);
38813
39462
  for (const entry of entries) {
38814
39463
  if (entry === ".git") continue;
38815
- const fullPath = join60(dir, entry);
39464
+ const fullPath = join61(dir, entry);
38816
39465
  const relPath = relative5(cleoDir, fullPath);
38817
39466
  try {
38818
39467
  const stat3 = statSync9(fullPath);
@@ -38872,7 +39521,7 @@ function generateGitignoreEntries(sharing) {
38872
39521
  async function syncGitignore(cwd) {
38873
39522
  const config = await loadConfig2(cwd);
38874
39523
  const projectRoot = getProjectRoot(cwd);
38875
- const gitignorePath = join60(projectRoot, ".gitignore");
39524
+ const gitignorePath = join61(projectRoot, ".gitignore");
38876
39525
  const entries = generateGitignoreEntries(config.sharing);
38877
39526
  const managedSection = [
38878
39527
  "",
@@ -38882,7 +39531,7 @@ async function syncGitignore(cwd) {
38882
39531
  ""
38883
39532
  ].join("\n");
38884
39533
  let content = "";
38885
- if (existsSync61(gitignorePath)) {
39534
+ if (existsSync62(gitignorePath)) {
38886
39535
  content = await readFile15(gitignorePath, "utf-8");
38887
39536
  }
38888
39537
  const startIdx = content.indexOf(GITIGNORE_START);
@@ -42075,7 +42724,22 @@ async function validateLayer1Schema(context) {
42075
42724
  }
42076
42725
  if (context.params?.status) {
42077
42726
  const status = context.params.status;
42078
- if (!TASK_STATUSES.includes(status)) {
42727
+ const validStatuses = (() => {
42728
+ if (context.domain === "pipeline" && context.operation === "stage.record") {
42729
+ return LIFECYCLE_STAGE_STATUSES;
42730
+ }
42731
+ if (context.domain === "admin" && context.operation?.startsWith("adr.")) {
42732
+ return ADR_STATUSES;
42733
+ }
42734
+ if (context.domain === "session") {
42735
+ return SESSION_STATUSES;
42736
+ }
42737
+ if (context.domain === "pipeline" && context.operation?.startsWith("manifest.")) {
42738
+ return MANIFEST_STATUSES;
42739
+ }
42740
+ return TASK_STATUSES;
42741
+ })();
42742
+ if (!validStatuses.includes(status)) {
42079
42743
  violations.push({
42080
42744
  layer: 1 /* SCHEMA */,
42081
42745
  severity: "error" /* ERROR */,
@@ -42083,8 +42747,8 @@ async function validateLayer1Schema(context) {
42083
42747
  message: `Invalid status: ${status}`,
42084
42748
  field: "status",
42085
42749
  value: status,
42086
- constraint: `Must be one of: ${TASK_STATUSES.join(", ")}`,
42087
- fix: `Use one of: ${TASK_STATUSES.join(", ")}`
42750
+ constraint: `Must be one of: ${validStatuses.join(", ")}`,
42751
+ fix: `Use one of: ${validStatuses.join(", ")}`
42088
42752
  });
42089
42753
  }
42090
42754
  }
@@ -42985,7 +43649,7 @@ init_logger();
42985
43649
  init_project_info();
42986
43650
  init_scaffold();
42987
43651
  init_audit_prune();
42988
- import { join as join61 } from "node:path";
43652
+ import { join as join62 } from "node:path";
42989
43653
  var serverState = null;
42990
43654
  var startupLog = getLogger("mcp:startup");
42991
43655
  async function main() {
@@ -43055,7 +43719,7 @@ async function main() {
43055
43719
  }
43056
43720
  const config = loadConfig();
43057
43721
  const projectInfo = getProjectInfoSync();
43058
- const cleoDir = join61(process.cwd(), ".cleo");
43722
+ const cleoDir = join62(process.cwd(), ".cleo");
43059
43723
  initLogger(cleoDir, {
43060
43724
  level: config.logLevel ?? "info",
43061
43725
  filePath: "logs/cleo.log",