@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 +1384 -422
- package/dist/cli/index.js.map +4 -4
- package/dist/mcp/index.js +1201 -228
- package/dist/mcp/index.js.map +4 -4
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1809,6 +1809,16 @@ var init_registry = __esm({
|
|
|
1809
1809
|
sessionRequired: false,
|
|
1810
1810
|
requiredParams: ["version"]
|
|
1811
1811
|
},
|
|
1812
|
+
{
|
|
1813
|
+
gateway: "query",
|
|
1814
|
+
domain: "pipeline",
|
|
1815
|
+
operation: "release.channel.show",
|
|
1816
|
+
description: "Show the current release channel based on git branch (latest/beta/alpha)",
|
|
1817
|
+
tier: 0,
|
|
1818
|
+
idempotent: true,
|
|
1819
|
+
sessionRequired: false,
|
|
1820
|
+
requiredParams: []
|
|
1821
|
+
},
|
|
1812
1822
|
{
|
|
1813
1823
|
gateway: "mutate",
|
|
1814
1824
|
domain: "pipeline",
|
|
@@ -1869,6 +1879,16 @@ var init_registry = __esm({
|
|
|
1869
1879
|
sessionRequired: false,
|
|
1870
1880
|
requiredParams: []
|
|
1871
1881
|
},
|
|
1882
|
+
{
|
|
1883
|
+
gateway: "mutate",
|
|
1884
|
+
domain: "pipeline",
|
|
1885
|
+
operation: "release.cancel",
|
|
1886
|
+
description: "pipeline.release.cancel (mutate)",
|
|
1887
|
+
tier: 0,
|
|
1888
|
+
idempotent: false,
|
|
1889
|
+
sessionRequired: false,
|
|
1890
|
+
requiredParams: ["version"]
|
|
1891
|
+
},
|
|
1872
1892
|
{
|
|
1873
1893
|
gateway: "mutate",
|
|
1874
1894
|
domain: "pipeline",
|
|
@@ -4895,13 +4915,13 @@ async function getDb(cwd) {
|
|
|
4895
4915
|
if (!_gitTrackingChecked) {
|
|
4896
4916
|
_gitTrackingChecked = true;
|
|
4897
4917
|
try {
|
|
4898
|
-
const { execFileSync:
|
|
4918
|
+
const { execFileSync: execFileSync12 } = await import("node:child_process");
|
|
4899
4919
|
const gitCwd = resolve3(dbPath, "..", "..");
|
|
4900
4920
|
const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
|
|
4901
4921
|
const log7 = getLogger("sqlite");
|
|
4902
4922
|
for (const fileToCheck of filesToCheck) {
|
|
4903
4923
|
try {
|
|
4904
|
-
|
|
4924
|
+
execFileSync12("git", ["ls-files", "--error-unmatch", fileToCheck], {
|
|
4905
4925
|
cwd: gitCwd,
|
|
4906
4926
|
stdio: "pipe"
|
|
4907
4927
|
});
|
|
@@ -15585,6 +15605,7 @@ async function getProjectStats(opts, accessor) {
|
|
|
15585
15605
|
const active = tasks2.filter((t) => t.status === "active").length;
|
|
15586
15606
|
const done = tasks2.filter((t) => t.status === "done").length;
|
|
15587
15607
|
const blocked = tasks2.filter((t) => t.status === "blocked").length;
|
|
15608
|
+
const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
|
|
15588
15609
|
const totalActive = tasks2.length;
|
|
15589
15610
|
const cutoff = new Date(Date.now() - periodDays * 864e5).toISOString();
|
|
15590
15611
|
const entries = await queryAuditEntries(opts.cwd);
|
|
@@ -15601,11 +15622,45 @@ async function getProjectStats(opts, accessor) {
|
|
|
15601
15622
|
(e) => isArchive(e) && e.timestamp >= cutoff
|
|
15602
15623
|
).length;
|
|
15603
15624
|
const completionRate = createdInPeriod > 0 ? Math.round(completedInPeriod / createdInPeriod * 1e4) / 100 : 0;
|
|
15604
|
-
|
|
15605
|
-
|
|
15606
|
-
|
|
15625
|
+
let totalCreated = 0;
|
|
15626
|
+
let totalCompleted = 0;
|
|
15627
|
+
let totalCancelled = 0;
|
|
15628
|
+
let totalArchived = 0;
|
|
15629
|
+
let archivedCompleted = 0;
|
|
15630
|
+
let archivedCount = 0;
|
|
15631
|
+
try {
|
|
15632
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
15633
|
+
const { count: dbCount, eq: dbEq, and: dbAnd } = await import("drizzle-orm");
|
|
15634
|
+
const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
15635
|
+
const db = await getDb2(opts.cwd);
|
|
15636
|
+
const statusRows = await db.select({ status: tasksTable.status, c: dbCount() }).from(tasksTable).groupBy(tasksTable.status).all();
|
|
15637
|
+
const statusMap = {};
|
|
15638
|
+
for (const row of statusRows) {
|
|
15639
|
+
statusMap[row.status] = row.c;
|
|
15640
|
+
}
|
|
15641
|
+
archivedCount = statusMap["archived"] ?? 0;
|
|
15642
|
+
totalCreated = Object.values(statusMap).reduce((sum, n) => sum + n, 0);
|
|
15643
|
+
totalCancelled = statusMap["cancelled"] ?? 0;
|
|
15644
|
+
totalArchived = archivedCount;
|
|
15645
|
+
const archivedDoneRow = await db.select({ c: dbCount() }).from(tasksTable).where(dbAnd(dbEq(tasksTable.status, "archived"), dbEq(tasksTable.archiveReason, "completed"))).get();
|
|
15646
|
+
archivedCompleted = archivedDoneRow?.c ?? 0;
|
|
15647
|
+
totalCompleted = (statusMap["done"] ?? 0) + archivedCompleted;
|
|
15648
|
+
} catch {
|
|
15649
|
+
totalCreated = entries.filter(isCreate).length;
|
|
15650
|
+
totalCompleted = entries.filter(isComplete).length;
|
|
15651
|
+
totalArchived = entries.filter(isArchive).length;
|
|
15652
|
+
}
|
|
15607
15653
|
return {
|
|
15608
|
-
currentState: {
|
|
15654
|
+
currentState: {
|
|
15655
|
+
pending,
|
|
15656
|
+
active,
|
|
15657
|
+
done,
|
|
15658
|
+
blocked,
|
|
15659
|
+
cancelled,
|
|
15660
|
+
totalActive,
|
|
15661
|
+
archived: archivedCount,
|
|
15662
|
+
grandTotal: totalActive + archivedCount
|
|
15663
|
+
},
|
|
15609
15664
|
completionMetrics: {
|
|
15610
15665
|
periodDays,
|
|
15611
15666
|
completedInPeriod,
|
|
@@ -15617,7 +15672,7 @@ async function getProjectStats(opts, accessor) {
|
|
|
15617
15672
|
completedInPeriod,
|
|
15618
15673
|
archivedInPeriod
|
|
15619
15674
|
},
|
|
15620
|
-
allTime: { totalCreated, totalCompleted, totalArchived }
|
|
15675
|
+
allTime: { totalCreated, totalCompleted, totalCancelled, totalArchived, archivedCompleted }
|
|
15621
15676
|
};
|
|
15622
15677
|
}
|
|
15623
15678
|
function rankBlockedTask(task, allTasks, focusTask) {
|
|
@@ -15661,7 +15716,18 @@ async function getDashboard(opts, accessor) {
|
|
|
15661
15716
|
const active = tasks2.filter((t) => t.status === "active").length;
|
|
15662
15717
|
const done = tasks2.filter((t) => t.status === "done").length;
|
|
15663
15718
|
const blocked = tasks2.filter((t) => t.status === "blocked").length;
|
|
15719
|
+
const cancelled = tasks2.filter((t) => t.status === "cancelled").length;
|
|
15664
15720
|
const total = tasks2.length;
|
|
15721
|
+
let archived = 0;
|
|
15722
|
+
try {
|
|
15723
|
+
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
15724
|
+
const { count: dbCount, eq: dbEq } = await import("drizzle-orm");
|
|
15725
|
+
const { tasks: tasksTable } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
15726
|
+
const db = await getDb2(opts.cwd);
|
|
15727
|
+
const row = await db.select({ c: dbCount() }).from(tasksTable).where(dbEq(tasksTable.status, "archived")).get();
|
|
15728
|
+
archived = row?.c ?? 0;
|
|
15729
|
+
} catch {
|
|
15730
|
+
}
|
|
15665
15731
|
const project = data.project?.name ?? "Unknown Project";
|
|
15666
15732
|
const currentPhase = data.project?.currentPhase ?? null;
|
|
15667
15733
|
const focusId = data.focus?.currentTask ?? null;
|
|
@@ -15669,7 +15735,7 @@ async function getDashboard(opts, accessor) {
|
|
|
15669
15735
|
if (focusId) {
|
|
15670
15736
|
focusTask = tasks2.find((t) => t.id === focusId) ?? null;
|
|
15671
15737
|
}
|
|
15672
|
-
const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done").sort((a, b) => {
|
|
15738
|
+
const highPriority = tasks2.filter((t) => (t.priority === "critical" || t.priority === "high") && t.status !== "done" && t.status !== "cancelled").sort((a, b) => {
|
|
15673
15739
|
const pDiff = (PRIORITY_ORDER[a.priority ?? "low"] ?? 9) - (PRIORITY_ORDER[b.priority ?? "low"] ?? 9);
|
|
15674
15740
|
if (pDiff !== 0) return pDiff;
|
|
15675
15741
|
return (a.createdAt ?? "").localeCompare(b.createdAt ?? "");
|
|
@@ -15682,6 +15748,7 @@ async function getDashboard(opts, accessor) {
|
|
|
15682
15748
|
}).map((r) => r.task);
|
|
15683
15749
|
const labelMap = {};
|
|
15684
15750
|
for (const t of tasks2) {
|
|
15751
|
+
if (t.status === "cancelled") continue;
|
|
15685
15752
|
for (const label of t.labels ?? []) {
|
|
15686
15753
|
labelMap[label] = (labelMap[label] ?? 0) + 1;
|
|
15687
15754
|
}
|
|
@@ -15690,7 +15757,7 @@ async function getDashboard(opts, accessor) {
|
|
|
15690
15757
|
return {
|
|
15691
15758
|
project,
|
|
15692
15759
|
currentPhase,
|
|
15693
|
-
summary: { pending, active, blocked, done, total },
|
|
15760
|
+
summary: { pending, active, blocked, done, cancelled, total, archived, grandTotal: total + archived },
|
|
15694
15761
|
focus: { currentTask: focusId, task: focusTask },
|
|
15695
15762
|
highPriority: { count: highPriority.length, tasks: highPriority.slice(0, 5) },
|
|
15696
15763
|
blockedTasks: {
|
|
@@ -19811,7 +19878,9 @@ async function systemDash(projectRoot, params) {
|
|
|
19811
19878
|
blocked: summary.blocked,
|
|
19812
19879
|
done: summary.done,
|
|
19813
19880
|
cancelled: summary.cancelled ?? 0,
|
|
19814
|
-
total: summary.total
|
|
19881
|
+
total: summary.total,
|
|
19882
|
+
archived: summary.archived ?? 0,
|
|
19883
|
+
grandTotal: summary.grandTotal ?? summary.total
|
|
19815
19884
|
},
|
|
19816
19885
|
taskWork: data.focus ?? data.taskWork,
|
|
19817
19886
|
activeSession: data.activeSession ?? null,
|
|
@@ -19831,17 +19900,18 @@ async function systemStats(projectRoot, params) {
|
|
|
19831
19900
|
const result = await getProjectStats({ period: String(params?.period ?? 30), cwd: projectRoot }, accessor);
|
|
19832
19901
|
const taskData = await accessor.loadTaskFile();
|
|
19833
19902
|
const tasks2 = taskData?.tasks ?? [];
|
|
19903
|
+
const activeTasks = tasks2.filter((t) => t.status !== "cancelled");
|
|
19834
19904
|
const byPriority = {};
|
|
19835
|
-
for (const t of
|
|
19905
|
+
for (const t of activeTasks) {
|
|
19836
19906
|
byPriority[t.priority] = (byPriority[t.priority] ?? 0) + 1;
|
|
19837
19907
|
}
|
|
19838
19908
|
const byType = {};
|
|
19839
|
-
for (const t of
|
|
19909
|
+
for (const t of activeTasks) {
|
|
19840
19910
|
const type = t.type || "task";
|
|
19841
19911
|
byType[type] = (byType[type] ?? 0) + 1;
|
|
19842
19912
|
}
|
|
19843
19913
|
const byPhase = {};
|
|
19844
|
-
for (const t of
|
|
19914
|
+
for (const t of activeTasks) {
|
|
19845
19915
|
const phase = t.phase || "unassigned";
|
|
19846
19916
|
byPhase[phase] = (byPhase[phase] ?? 0) + 1;
|
|
19847
19917
|
}
|
|
@@ -19871,7 +19941,9 @@ async function systemStats(projectRoot, params) {
|
|
|
19871
19941
|
done: currentState.done,
|
|
19872
19942
|
blocked: currentState.blocked,
|
|
19873
19943
|
cancelled: tasks2.filter((t) => t.status === "cancelled").length,
|
|
19874
|
-
totalActive: currentState.totalActive
|
|
19944
|
+
totalActive: currentState.totalActive,
|
|
19945
|
+
archived: currentState.archived ?? 0,
|
|
19946
|
+
grandTotal: currentState.grandTotal ?? currentState.totalActive
|
|
19875
19947
|
},
|
|
19876
19948
|
byPriority,
|
|
19877
19949
|
byType,
|
|
@@ -19896,10 +19968,10 @@ async function systemLog(projectRoot, filters) {
|
|
|
19896
19968
|
}
|
|
19897
19969
|
async function queryAuditLogSqlite(projectRoot, filters) {
|
|
19898
19970
|
try {
|
|
19899
|
-
const { join:
|
|
19900
|
-
const { existsSync:
|
|
19901
|
-
const dbPath =
|
|
19902
|
-
if (!
|
|
19971
|
+
const { join: join64 } = await import("node:path");
|
|
19972
|
+
const { existsSync: existsSync64 } = await import("node:fs");
|
|
19973
|
+
const dbPath = join64(projectRoot, ".cleo", "tasks.db");
|
|
19974
|
+
if (!existsSync64(dbPath)) {
|
|
19903
19975
|
const offset = filters?.offset ?? 0;
|
|
19904
19976
|
const limit = filters?.limit ?? 20;
|
|
19905
19977
|
return {
|
|
@@ -20643,10 +20715,10 @@ async function readProjectMeta(projectPath) {
|
|
|
20643
20715
|
}
|
|
20644
20716
|
async function readProjectId(projectPath) {
|
|
20645
20717
|
try {
|
|
20646
|
-
const { readFileSync:
|
|
20718
|
+
const { readFileSync: readFileSync46, existsSync: existsSync64 } = await import("node:fs");
|
|
20647
20719
|
const infoPath = join34(projectPath, ".cleo", "project-info.json");
|
|
20648
|
-
if (!
|
|
20649
|
-
const data = JSON.parse(
|
|
20720
|
+
if (!existsSync64(infoPath)) return "";
|
|
20721
|
+
const data = JSON.parse(readFileSync46(infoPath, "utf-8"));
|
|
20650
20722
|
return typeof data.projectId === "string" ? data.projectId : "";
|
|
20651
20723
|
} catch {
|
|
20652
20724
|
return "";
|
|
@@ -27991,11 +28063,353 @@ var init_changelog_writer = __esm({
|
|
|
27991
28063
|
}
|
|
27992
28064
|
});
|
|
27993
28065
|
|
|
27994
|
-
// src/core/release/
|
|
27995
|
-
import { existsSync as existsSync49, renameSync as renameSync7 } from "node:fs";
|
|
27996
|
-
import { readFile as readFile10 } from "node:fs/promises";
|
|
28066
|
+
// src/core/release/github-pr.ts
|
|
27997
28067
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
28068
|
+
function isGhCliAvailable() {
|
|
28069
|
+
try {
|
|
28070
|
+
execFileSync6("gh", ["--version"], { stdio: "pipe" });
|
|
28071
|
+
return true;
|
|
28072
|
+
} catch {
|
|
28073
|
+
return false;
|
|
28074
|
+
}
|
|
28075
|
+
}
|
|
28076
|
+
function extractRepoOwnerAndName(remote) {
|
|
28077
|
+
const trimmed = remote.trim();
|
|
28078
|
+
const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
28079
|
+
if (httpsMatch) {
|
|
28080
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
28081
|
+
}
|
|
28082
|
+
const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
28083
|
+
if (sshMatch) {
|
|
28084
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
28085
|
+
}
|
|
28086
|
+
return null;
|
|
28087
|
+
}
|
|
28088
|
+
async function detectBranchProtection(branch, remote, projectRoot) {
|
|
28089
|
+
const cwdOpts = projectRoot ? { cwd: projectRoot } : {};
|
|
28090
|
+
if (isGhCliAvailable()) {
|
|
28091
|
+
try {
|
|
28092
|
+
const remoteUrl = execFileSync6("git", ["remote", "get-url", remote], {
|
|
28093
|
+
encoding: "utf-8",
|
|
28094
|
+
stdio: "pipe",
|
|
28095
|
+
...cwdOpts
|
|
28096
|
+
}).trim();
|
|
28097
|
+
const identity = extractRepoOwnerAndName(remoteUrl);
|
|
28098
|
+
if (identity) {
|
|
28099
|
+
const { owner, repo } = identity;
|
|
28100
|
+
try {
|
|
28101
|
+
execFileSync6(
|
|
28102
|
+
"gh",
|
|
28103
|
+
["api", `/repos/${owner}/${repo}/branches/${branch}/protection`],
|
|
28104
|
+
{
|
|
28105
|
+
encoding: "utf-8",
|
|
28106
|
+
stdio: "pipe",
|
|
28107
|
+
...cwdOpts
|
|
28108
|
+
}
|
|
28109
|
+
);
|
|
28110
|
+
return { protected: true, detectionMethod: "gh-api" };
|
|
28111
|
+
} catch (apiErr) {
|
|
28112
|
+
const stderr = apiErr instanceof Error && "stderr" in apiErr ? String(apiErr.stderr ?? "") : "";
|
|
28113
|
+
if (stderr.includes("404") || stderr.includes("Not Found")) {
|
|
28114
|
+
return { protected: false, detectionMethod: "gh-api" };
|
|
28115
|
+
}
|
|
28116
|
+
}
|
|
28117
|
+
}
|
|
28118
|
+
} catch {
|
|
28119
|
+
}
|
|
28120
|
+
}
|
|
28121
|
+
try {
|
|
28122
|
+
const result = execFileSync6(
|
|
28123
|
+
"git",
|
|
28124
|
+
["push", "--dry-run", remote, `HEAD:${branch}`],
|
|
28125
|
+
{
|
|
28126
|
+
encoding: "utf-8",
|
|
28127
|
+
stdio: "pipe",
|
|
28128
|
+
...cwdOpts
|
|
28129
|
+
}
|
|
28130
|
+
);
|
|
28131
|
+
const output = typeof result === "string" ? result : "";
|
|
28132
|
+
if (output.includes("protected branch") || output.includes("GH006") || output.includes("refusing to allow")) {
|
|
28133
|
+
return { protected: true, detectionMethod: "push-dry-run" };
|
|
28134
|
+
}
|
|
28135
|
+
return { protected: false, detectionMethod: "push-dry-run" };
|
|
28136
|
+
} catch (pushErr) {
|
|
28137
|
+
const stderr = pushErr instanceof Error && "stderr" in pushErr ? String(pushErr.stderr ?? "") : pushErr instanceof Error ? pushErr.message : String(pushErr);
|
|
28138
|
+
if (stderr.includes("protected branch") || stderr.includes("GH006") || stderr.includes("refusing to allow")) {
|
|
28139
|
+
return { protected: true, detectionMethod: "push-dry-run" };
|
|
28140
|
+
}
|
|
28141
|
+
return {
|
|
28142
|
+
protected: false,
|
|
28143
|
+
detectionMethod: "unknown",
|
|
28144
|
+
error: stderr
|
|
28145
|
+
};
|
|
28146
|
+
}
|
|
28147
|
+
}
|
|
28148
|
+
function buildPRBody(opts) {
|
|
28149
|
+
const epicLine = opts.epicId ? `**Epic**: ${opts.epicId}
|
|
28150
|
+
|
|
28151
|
+
` : "";
|
|
28152
|
+
return [
|
|
28153
|
+
`## Release v${opts.version}`,
|
|
28154
|
+
"",
|
|
28155
|
+
`${epicLine}This PR merges the ${opts.head} branch into ${opts.base} to publish the release.`,
|
|
28156
|
+
"",
|
|
28157
|
+
"### Checklist",
|
|
28158
|
+
"- [ ] CHANGELOG.md updated",
|
|
28159
|
+
"- [ ] All release tasks complete",
|
|
28160
|
+
"- [ ] Version bump committed",
|
|
28161
|
+
"",
|
|
28162
|
+
"---",
|
|
28163
|
+
"*Created by CLEO release pipeline*"
|
|
28164
|
+
].join("\n");
|
|
28165
|
+
}
|
|
28166
|
+
function formatManualPRInstructions(opts) {
|
|
28167
|
+
const epicSuffix = opts.epicId ? ` (${opts.epicId})` : "";
|
|
28168
|
+
return [
|
|
28169
|
+
"Branch protection detected or gh CLI unavailable. Create the PR manually:",
|
|
28170
|
+
"",
|
|
28171
|
+
` gh pr create \\`,
|
|
28172
|
+
` --base ${opts.base} \\`,
|
|
28173
|
+
` --head ${opts.head} \\`,
|
|
28174
|
+
` --title "${opts.title}" \\`,
|
|
28175
|
+
` --body "Release v${opts.version}${epicSuffix}"`,
|
|
28176
|
+
"",
|
|
28177
|
+
`Or visit: https://github.com/[owner]/[repo]/compare/${opts.base}...${opts.head}`,
|
|
28178
|
+
"",
|
|
28179
|
+
"After merging, CI will automatically publish to npm."
|
|
28180
|
+
].join("\n");
|
|
28181
|
+
}
|
|
28182
|
+
async function createPullRequest(opts) {
|
|
28183
|
+
if (!isGhCliAvailable()) {
|
|
28184
|
+
return {
|
|
28185
|
+
mode: "manual",
|
|
28186
|
+
instructions: formatManualPRInstructions(opts)
|
|
28187
|
+
};
|
|
28188
|
+
}
|
|
28189
|
+
const body = buildPRBody(opts);
|
|
28190
|
+
const args = [
|
|
28191
|
+
"pr",
|
|
28192
|
+
"create",
|
|
28193
|
+
"--base",
|
|
28194
|
+
opts.base,
|
|
28195
|
+
"--head",
|
|
28196
|
+
opts.head,
|
|
28197
|
+
"--title",
|
|
28198
|
+
opts.title,
|
|
28199
|
+
"--body",
|
|
28200
|
+
body
|
|
28201
|
+
];
|
|
28202
|
+
if (opts.labels && opts.labels.length > 0) {
|
|
28203
|
+
for (const label of opts.labels) {
|
|
28204
|
+
args.push("--label", label);
|
|
28205
|
+
}
|
|
28206
|
+
}
|
|
28207
|
+
try {
|
|
28208
|
+
const output = execFileSync6("gh", args, {
|
|
28209
|
+
encoding: "utf-8",
|
|
28210
|
+
stdio: "pipe",
|
|
28211
|
+
...opts.projectRoot ? { cwd: opts.projectRoot } : {}
|
|
28212
|
+
});
|
|
28213
|
+
const prUrl = output.trim();
|
|
28214
|
+
const numberMatch = prUrl.match(/\/pull\/(\d+)$/);
|
|
28215
|
+
const prNumber = numberMatch ? parseInt(numberMatch[1], 10) : void 0;
|
|
28216
|
+
return {
|
|
28217
|
+
mode: "created",
|
|
28218
|
+
prUrl,
|
|
28219
|
+
prNumber
|
|
28220
|
+
};
|
|
28221
|
+
} catch (err) {
|
|
28222
|
+
const stderr = err instanceof Error && "stderr" in err ? String(err.stderr ?? "") : err instanceof Error ? err.message : String(err);
|
|
28223
|
+
if (stderr.includes("already exists")) {
|
|
28224
|
+
const urlMatch = stderr.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/);
|
|
28225
|
+
const existingUrl = urlMatch ? urlMatch[0] : void 0;
|
|
28226
|
+
return {
|
|
28227
|
+
mode: "skipped",
|
|
28228
|
+
prUrl: existingUrl,
|
|
28229
|
+
instructions: "PR already exists"
|
|
28230
|
+
};
|
|
28231
|
+
}
|
|
28232
|
+
return {
|
|
28233
|
+
mode: "manual",
|
|
28234
|
+
instructions: formatManualPRInstructions(opts),
|
|
28235
|
+
error: stderr
|
|
28236
|
+
};
|
|
28237
|
+
}
|
|
28238
|
+
}
|
|
28239
|
+
var init_github_pr = __esm({
|
|
28240
|
+
"src/core/release/github-pr.ts"() {
|
|
28241
|
+
"use strict";
|
|
28242
|
+
}
|
|
28243
|
+
});
|
|
28244
|
+
|
|
28245
|
+
// src/core/release/channel.ts
|
|
28246
|
+
function getDefaultChannelConfig() {
|
|
28247
|
+
return {
|
|
28248
|
+
main: "main",
|
|
28249
|
+
develop: "develop",
|
|
28250
|
+
feature: "feature/"
|
|
28251
|
+
};
|
|
28252
|
+
}
|
|
28253
|
+
function resolveChannelFromBranch(branch, config) {
|
|
28254
|
+
const cfg = config ?? getDefaultChannelConfig();
|
|
28255
|
+
if (cfg.custom) {
|
|
28256
|
+
if (Object.prototype.hasOwnProperty.call(cfg.custom, branch)) {
|
|
28257
|
+
return cfg.custom[branch];
|
|
28258
|
+
}
|
|
28259
|
+
let bestPrefix = "";
|
|
28260
|
+
let bestChannel;
|
|
28261
|
+
for (const [key, channel] of Object.entries(cfg.custom)) {
|
|
28262
|
+
if (branch.startsWith(key) && key.length > bestPrefix.length) {
|
|
28263
|
+
bestPrefix = key;
|
|
28264
|
+
bestChannel = channel;
|
|
28265
|
+
}
|
|
28266
|
+
}
|
|
28267
|
+
if (bestChannel !== void 0) {
|
|
28268
|
+
return bestChannel;
|
|
28269
|
+
}
|
|
28270
|
+
}
|
|
28271
|
+
if (branch === cfg.main) {
|
|
28272
|
+
return "latest";
|
|
28273
|
+
}
|
|
28274
|
+
if (branch === cfg.develop) {
|
|
28275
|
+
return "beta";
|
|
28276
|
+
}
|
|
28277
|
+
const alphaPrefixes = ["feature/", "hotfix/", "release/"];
|
|
28278
|
+
if (cfg.feature && !alphaPrefixes.includes(cfg.feature)) {
|
|
28279
|
+
alphaPrefixes.push(cfg.feature);
|
|
28280
|
+
}
|
|
28281
|
+
for (const prefix of alphaPrefixes) {
|
|
28282
|
+
if (branch.startsWith(prefix)) {
|
|
28283
|
+
return "alpha";
|
|
28284
|
+
}
|
|
28285
|
+
}
|
|
28286
|
+
return "alpha";
|
|
28287
|
+
}
|
|
28288
|
+
function channelToDistTag(channel) {
|
|
28289
|
+
const tags = {
|
|
28290
|
+
latest: "latest",
|
|
28291
|
+
beta: "beta",
|
|
28292
|
+
alpha: "alpha"
|
|
28293
|
+
};
|
|
28294
|
+
return tags[channel];
|
|
28295
|
+
}
|
|
28296
|
+
function describeChannel(channel) {
|
|
28297
|
+
const descriptions = {
|
|
28298
|
+
latest: "stable release published to npm @latest",
|
|
28299
|
+
beta: "pre-release published to npm @beta (develop branch)",
|
|
28300
|
+
alpha: "early pre-release published to npm @alpha (feature/hotfix branches)"
|
|
28301
|
+
};
|
|
28302
|
+
return descriptions[channel];
|
|
28303
|
+
}
|
|
28304
|
+
var init_channel = __esm({
|
|
28305
|
+
"src/core/release/channel.ts"() {
|
|
28306
|
+
"use strict";
|
|
28307
|
+
}
|
|
28308
|
+
});
|
|
28309
|
+
|
|
28310
|
+
// src/core/release/release-config.ts
|
|
28311
|
+
import { existsSync as existsSync49, readFileSync as readFileSync38 } from "node:fs";
|
|
27998
28312
|
import { join as join47 } from "node:path";
|
|
28313
|
+
function readConfigValueSync(path, defaultValue, cwd) {
|
|
28314
|
+
try {
|
|
28315
|
+
const configPath = join47(getCleoDir(cwd), "config.json");
|
|
28316
|
+
if (!existsSync49(configPath)) return defaultValue;
|
|
28317
|
+
const config = JSON.parse(readFileSync38(configPath, "utf-8"));
|
|
28318
|
+
const keys = path.split(".");
|
|
28319
|
+
let value = config;
|
|
28320
|
+
for (const key of keys) {
|
|
28321
|
+
if (value == null || typeof value !== "object") return defaultValue;
|
|
28322
|
+
value = value[key];
|
|
28323
|
+
}
|
|
28324
|
+
return value ?? defaultValue;
|
|
28325
|
+
} catch {
|
|
28326
|
+
return defaultValue;
|
|
28327
|
+
}
|
|
28328
|
+
}
|
|
28329
|
+
function loadReleaseConfig(cwd) {
|
|
28330
|
+
return {
|
|
28331
|
+
versioningScheme: readConfigValueSync("release.versioning.scheme", DEFAULTS2.versioningScheme, cwd),
|
|
28332
|
+
tagPrefix: readConfigValueSync("release.versioning.tagPrefix", DEFAULTS2.tagPrefix, cwd),
|
|
28333
|
+
changelogFormat: readConfigValueSync("release.changelog.format", DEFAULTS2.changelogFormat, cwd),
|
|
28334
|
+
changelogFile: readConfigValueSync("release.changelog.file", DEFAULTS2.changelogFile, cwd),
|
|
28335
|
+
artifactType: readConfigValueSync("release.artifact.type", DEFAULTS2.artifactType, cwd),
|
|
28336
|
+
gates: readConfigValueSync("release.gates", [], cwd),
|
|
28337
|
+
versionBump: {
|
|
28338
|
+
files: readConfigValueSync("release.versionBump.files", [], cwd)
|
|
28339
|
+
},
|
|
28340
|
+
security: {
|
|
28341
|
+
enableProvenance: readConfigValueSync("release.security.enableProvenance", false, cwd),
|
|
28342
|
+
slsaLevel: readConfigValueSync("release.security.slsaLevel", 3, cwd),
|
|
28343
|
+
requireSignedCommits: readConfigValueSync("release.security.requireSignedCommits", false, cwd)
|
|
28344
|
+
}
|
|
28345
|
+
};
|
|
28346
|
+
}
|
|
28347
|
+
function getDefaultGitFlowConfig() {
|
|
28348
|
+
return {
|
|
28349
|
+
enabled: true,
|
|
28350
|
+
branches: {
|
|
28351
|
+
main: "main",
|
|
28352
|
+
develop: "develop",
|
|
28353
|
+
featurePrefix: "feature/",
|
|
28354
|
+
hotfixPrefix: "hotfix/",
|
|
28355
|
+
releasePrefix: "release/"
|
|
28356
|
+
}
|
|
28357
|
+
};
|
|
28358
|
+
}
|
|
28359
|
+
function getGitFlowConfig(config) {
|
|
28360
|
+
const defaults = getDefaultGitFlowConfig();
|
|
28361
|
+
if (!config.gitflow) return defaults;
|
|
28362
|
+
return {
|
|
28363
|
+
enabled: config.gitflow.enabled ?? defaults.enabled,
|
|
28364
|
+
branches: {
|
|
28365
|
+
main: config.gitflow.branches?.main ?? defaults.branches.main,
|
|
28366
|
+
develop: config.gitflow.branches?.develop ?? defaults.branches.develop,
|
|
28367
|
+
featurePrefix: config.gitflow.branches?.featurePrefix ?? defaults.branches.featurePrefix,
|
|
28368
|
+
hotfixPrefix: config.gitflow.branches?.hotfixPrefix ?? defaults.branches.hotfixPrefix,
|
|
28369
|
+
releasePrefix: config.gitflow.branches?.releasePrefix ?? defaults.branches.releasePrefix
|
|
28370
|
+
}
|
|
28371
|
+
};
|
|
28372
|
+
}
|
|
28373
|
+
function getDefaultChannelConfig2() {
|
|
28374
|
+
return {
|
|
28375
|
+
main: "latest",
|
|
28376
|
+
develop: "beta",
|
|
28377
|
+
feature: "alpha"
|
|
28378
|
+
};
|
|
28379
|
+
}
|
|
28380
|
+
function getChannelConfig(config) {
|
|
28381
|
+
const defaults = getDefaultChannelConfig2();
|
|
28382
|
+
if (!config.channels) return defaults;
|
|
28383
|
+
return {
|
|
28384
|
+
main: config.channels.main ?? defaults.main,
|
|
28385
|
+
develop: config.channels.develop ?? defaults.develop,
|
|
28386
|
+
feature: config.channels.feature ?? defaults.feature,
|
|
28387
|
+
custom: config.channels.custom
|
|
28388
|
+
};
|
|
28389
|
+
}
|
|
28390
|
+
function getPushMode(config) {
|
|
28391
|
+
return config.push?.mode ?? "auto";
|
|
28392
|
+
}
|
|
28393
|
+
var DEFAULTS2;
|
|
28394
|
+
var init_release_config = __esm({
|
|
28395
|
+
"src/core/release/release-config.ts"() {
|
|
28396
|
+
"use strict";
|
|
28397
|
+
init_paths();
|
|
28398
|
+
DEFAULTS2 = {
|
|
28399
|
+
versioningScheme: "calver",
|
|
28400
|
+
tagPrefix: "v",
|
|
28401
|
+
changelogFormat: "keepachangelog",
|
|
28402
|
+
changelogFile: "CHANGELOG.md",
|
|
28403
|
+
artifactType: "generic-tarball"
|
|
28404
|
+
};
|
|
28405
|
+
}
|
|
28406
|
+
});
|
|
28407
|
+
|
|
28408
|
+
// src/core/release/release-manifest.ts
|
|
28409
|
+
import { existsSync as existsSync50, renameSync as renameSync7 } from "node:fs";
|
|
28410
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
28411
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
28412
|
+
import { join as join48 } from "node:path";
|
|
27999
28413
|
import { eq as eq14, desc as desc4 } from "drizzle-orm";
|
|
28000
28414
|
function isValidVersion(version) {
|
|
28001
28415
|
return /^v?\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/.test(version);
|
|
@@ -28087,25 +28501,78 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
|
|
|
28087
28501
|
const chores = [];
|
|
28088
28502
|
const docs = [];
|
|
28089
28503
|
const tests = [];
|
|
28090
|
-
const
|
|
28504
|
+
const changes = [];
|
|
28505
|
+
function stripConventionalPrefix(title) {
|
|
28506
|
+
return title.replace(/^(feat|fix|docs?|test|chore|refactor|style|ci|build|perf)(\([^)]+\))?:\s*/i, "");
|
|
28507
|
+
}
|
|
28508
|
+
function capitalize(s) {
|
|
28509
|
+
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
28510
|
+
}
|
|
28511
|
+
function buildEntry(task) {
|
|
28512
|
+
const cleanTitle = capitalize(stripConventionalPrefix(task.title));
|
|
28513
|
+
const safeDesc = task.description?.replace(/\r?\n/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
28514
|
+
const desc6 = safeDesc;
|
|
28515
|
+
const shouldIncludeDesc = (() => {
|
|
28516
|
+
if (!desc6 || desc6.length === 0) return false;
|
|
28517
|
+
const titleNorm = cleanTitle.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
28518
|
+
const descNorm = desc6.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
28519
|
+
if (titleNorm === descNorm) return false;
|
|
28520
|
+
if (descNorm.startsWith(titleNorm) && descNorm.length < titleNorm.length * 1.3) return false;
|
|
28521
|
+
return desc6.length >= 20;
|
|
28522
|
+
})();
|
|
28523
|
+
if (shouldIncludeDesc) {
|
|
28524
|
+
const descDisplay = desc6.length > 150 ? desc6.slice(0, 147) + "..." : desc6;
|
|
28525
|
+
return `- **${cleanTitle}**: ${descDisplay} (${task.id})`;
|
|
28526
|
+
}
|
|
28527
|
+
return `- ${cleanTitle} (${task.id})`;
|
|
28528
|
+
}
|
|
28529
|
+
function categorizeTask(task) {
|
|
28530
|
+
if (task.type === "epic") return "changes";
|
|
28531
|
+
const taskType = (task.type ?? "").toLowerCase();
|
|
28532
|
+
if (taskType === "test") return "tests";
|
|
28533
|
+
if (taskType === "fix" || taskType === "bugfix") return "fixes";
|
|
28534
|
+
if (taskType === "feat" || taskType === "feature") return "features";
|
|
28535
|
+
if (taskType === "docs" || taskType === "doc") return "docs";
|
|
28536
|
+
if (taskType === "chore" || taskType === "refactor") return "chores";
|
|
28537
|
+
if (/^feat(\([^)]+\))?:/.test(task.title.toLowerCase())) return "features";
|
|
28538
|
+
if (/^fix(\([^)]+\))?:/.test(task.title.toLowerCase())) return "fixes";
|
|
28539
|
+
if (/^docs?(\([^)]+\))?:/.test(task.title.toLowerCase())) return "docs";
|
|
28540
|
+
if (/^test(\([^)]+\))?:/.test(task.title.toLowerCase())) return "tests";
|
|
28541
|
+
if (/^(chore|refactor|style|ci|build|perf)(\([^)]+\))?:/.test(task.title.toLowerCase())) return "chores";
|
|
28542
|
+
const labels = task.labels ?? [];
|
|
28543
|
+
if (labels.some((l) => ["test", "testing"].includes(l.toLowerCase()))) return "tests";
|
|
28544
|
+
if (labels.some((l) => ["fix", "bug", "bugfix", "regression"].includes(l.toLowerCase()))) return "fixes";
|
|
28545
|
+
if (labels.some((l) => ["feat", "feature", "enhancement", "add"].includes(l.toLowerCase()))) return "features";
|
|
28546
|
+
if (labels.some((l) => ["docs", "documentation"].includes(l.toLowerCase()))) return "docs";
|
|
28547
|
+
if (labels.some((l) => ["chore", "refactor", "cleanup", "maintenance"].includes(l.toLowerCase()))) return "chores";
|
|
28548
|
+
const titleLower = stripConventionalPrefix(task.title).toLowerCase();
|
|
28549
|
+
const rawTitleLower = task.title.toLowerCase();
|
|
28550
|
+
if (titleLower.startsWith("test") || titleLower.includes("test") && titleLower.includes("add")) return "tests";
|
|
28551
|
+
if (titleLower.includes("bug") || titleLower.startsWith("fix") || titleLower.includes("regression") || titleLower.includes("broken")) return "fixes";
|
|
28552
|
+
if (titleLower.startsWith("add ") || titleLower.includes("implement") || titleLower.startsWith("create ") || titleLower.startsWith("introduce ")) return "features";
|
|
28553
|
+
if (titleLower.startsWith("doc") || titleLower.includes("documentation") || titleLower.includes("readme") || titleLower.includes("changelog")) return "docs";
|
|
28554
|
+
if (titleLower.startsWith("chore") || titleLower.includes("refactor") || titleLower.includes("cleanup") || titleLower.includes("migrate") || titleLower.includes("upgrade") || titleLower.includes("remove ") || titleLower.startsWith("audit")) return "chores";
|
|
28555
|
+
if (rawTitleLower.startsWith("feat")) return "features";
|
|
28556
|
+
return "changes";
|
|
28557
|
+
}
|
|
28091
28558
|
for (const taskId of releaseTasks) {
|
|
28092
28559
|
const task = taskMap.get(taskId);
|
|
28093
28560
|
if (!task) continue;
|
|
28094
|
-
|
|
28095
|
-
|
|
28096
|
-
if (
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
|
|
28100
|
-
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
|
|
28108
|
-
|
|
28561
|
+
if (task.type === "epic") continue;
|
|
28562
|
+
if (task.labels?.some((l) => l.toLowerCase() === "epic")) continue;
|
|
28563
|
+
if (/^epic:/i.test(task.title.trim())) continue;
|
|
28564
|
+
const labelsLower = (task.labels ?? []).map((l) => l.toLowerCase());
|
|
28565
|
+
if (labelsLower.some((l) => ["research", "internal", "spike", "audit"].includes(l))) continue;
|
|
28566
|
+
if (["spike", "research"].includes((task.type ?? "").toLowerCase())) continue;
|
|
28567
|
+
if (/^(research|investigate|audit|spike)\s/i.test(task.title.trim())) continue;
|
|
28568
|
+
const category = categorizeTask(task);
|
|
28569
|
+
const entry = buildEntry(task);
|
|
28570
|
+
if (category === "features") features.push(entry);
|
|
28571
|
+
else if (category === "fixes") fixes.push(entry);
|
|
28572
|
+
else if (category === "docs") docs.push(entry);
|
|
28573
|
+
else if (category === "tests") tests.push(entry);
|
|
28574
|
+
else if (category === "chores") chores.push(entry);
|
|
28575
|
+
else changes.push(entry);
|
|
28109
28576
|
}
|
|
28110
28577
|
const sections = [];
|
|
28111
28578
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -28140,14 +28607,14 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
|
|
|
28140
28607
|
sections.push(...chores);
|
|
28141
28608
|
sections.push("");
|
|
28142
28609
|
}
|
|
28143
|
-
if (
|
|
28144
|
-
sections.push("###
|
|
28145
|
-
sections.push(...
|
|
28610
|
+
if (changes.length > 0) {
|
|
28611
|
+
sections.push("### Changes");
|
|
28612
|
+
sections.push(...changes);
|
|
28146
28613
|
sections.push("");
|
|
28147
28614
|
}
|
|
28148
28615
|
const changelog = sections.join("\n");
|
|
28149
28616
|
await db.update(releaseManifests).set({ changelog }).where(eq14(releaseManifests.version, normalizedVersion)).run();
|
|
28150
|
-
const changelogPath =
|
|
28617
|
+
const changelogPath = join48(cwd ?? process.cwd(), "CHANGELOG.md");
|
|
28151
28618
|
let existingChangelogContent = "";
|
|
28152
28619
|
try {
|
|
28153
28620
|
existingChangelogContent = await readFile10(changelogPath, "utf8");
|
|
@@ -28155,7 +28622,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
|
|
|
28155
28622
|
}
|
|
28156
28623
|
const { customBlocks } = parseChangelogBlocks(existingChangelogContent);
|
|
28157
28624
|
const changelogBody = sections.slice(2).join("\n");
|
|
28158
|
-
await writeChangelogSection(normalizedVersion, changelogBody, customBlocks, changelogPath);
|
|
28625
|
+
await writeChangelogSection(normalizedVersion.replace(/^v/, ""), changelogBody, customBlocks, changelogPath);
|
|
28159
28626
|
return {
|
|
28160
28627
|
version: normalizedVersion,
|
|
28161
28628
|
changelog,
|
|
@@ -28166,7 +28633,7 @@ async function generateReleaseChangelog(version, loadTasksFn, cwd) {
|
|
|
28166
28633
|
docs: docs.length,
|
|
28167
28634
|
tests: tests.length,
|
|
28168
28635
|
chores: chores.length,
|
|
28169
|
-
|
|
28636
|
+
changes: changes.length
|
|
28170
28637
|
}
|
|
28171
28638
|
};
|
|
28172
28639
|
}
|
|
@@ -28228,7 +28695,7 @@ async function tagRelease(version, cwd) {
|
|
|
28228
28695
|
await db.update(releaseManifests).set({ status: "tagged", taggedAt }).where(eq14(releaseManifests.version, normalizedVersion)).run();
|
|
28229
28696
|
return { version: normalizedVersion, status: "tagged", taggedAt };
|
|
28230
28697
|
}
|
|
28231
|
-
async function runReleaseGates(version, loadTasksFn, cwd) {
|
|
28698
|
+
async function runReleaseGates(version, loadTasksFn, cwd, opts) {
|
|
28232
28699
|
if (!version) {
|
|
28233
28700
|
throw new Error("version is required");
|
|
28234
28701
|
}
|
|
@@ -28267,58 +28734,121 @@ async function runReleaseGates(version, loadTasksFn, cwd) {
|
|
|
28267
28734
|
message: incompleteTasks.length === 0 ? "All tasks completed" : `${incompleteTasks.length} tasks not completed: ${incompleteTasks.join(", ")}`
|
|
28268
28735
|
});
|
|
28269
28736
|
const projectRoot = cwd ?? getProjectRoot();
|
|
28270
|
-
const distPath =
|
|
28271
|
-
const isNodeProject =
|
|
28737
|
+
const distPath = join48(projectRoot, "dist", "cli", "index.js");
|
|
28738
|
+
const isNodeProject = existsSync50(join48(projectRoot, "package.json"));
|
|
28272
28739
|
if (isNodeProject) {
|
|
28273
28740
|
gates.push({
|
|
28274
28741
|
name: "build_artifact",
|
|
28275
|
-
status:
|
|
28276
|
-
message:
|
|
28742
|
+
status: existsSync50(distPath) ? "passed" : "failed",
|
|
28743
|
+
message: existsSync50(distPath) ? "dist/cli/index.js present" : "dist/ not built \u2014 run: npm run build"
|
|
28277
28744
|
});
|
|
28278
28745
|
}
|
|
28279
|
-
|
|
28280
|
-
|
|
28281
|
-
|
|
28282
|
-
|
|
28283
|
-
|
|
28284
|
-
|
|
28285
|
-
|
|
28746
|
+
if (opts?.dryRun) {
|
|
28747
|
+
gates.push({
|
|
28748
|
+
name: "clean_working_tree",
|
|
28749
|
+
status: "passed",
|
|
28750
|
+
message: "Skipped in dry-run mode"
|
|
28751
|
+
});
|
|
28752
|
+
} else {
|
|
28753
|
+
let workingTreeClean = true;
|
|
28754
|
+
let dirtyFiles = [];
|
|
28755
|
+
try {
|
|
28756
|
+
const porcelain = execFileSync7("git", ["status", "--porcelain"], {
|
|
28757
|
+
cwd: projectRoot,
|
|
28758
|
+
encoding: "utf-8",
|
|
28759
|
+
stdio: "pipe"
|
|
28760
|
+
});
|
|
28761
|
+
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");
|
|
28762
|
+
workingTreeClean = dirtyFiles.length === 0;
|
|
28763
|
+
} catch {
|
|
28764
|
+
}
|
|
28765
|
+
gates.push({
|
|
28766
|
+
name: "clean_working_tree",
|
|
28767
|
+
status: workingTreeClean ? "passed" : "failed",
|
|
28768
|
+
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)` : ""}`
|
|
28286
28769
|
});
|
|
28287
|
-
dirtyFiles = porcelain.split("\n").filter((l) => l.trim()).map((l) => l.slice(3).trim()).filter((f) => f !== "CHANGELOG.md" && f !== "VERSION" && f !== "package.json");
|
|
28288
|
-
workingTreeClean = dirtyFiles.length === 0;
|
|
28289
|
-
} catch {
|
|
28290
28770
|
}
|
|
28291
|
-
gates.push({
|
|
28292
|
-
name: "clean_working_tree",
|
|
28293
|
-
status: workingTreeClean ? "passed" : "failed",
|
|
28294
|
-
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)` : ""}`
|
|
28295
|
-
});
|
|
28296
28771
|
const isPreRelease = normalizedVersion.includes("-");
|
|
28297
28772
|
let currentBranch = "";
|
|
28298
28773
|
try {
|
|
28299
|
-
currentBranch =
|
|
28774
|
+
currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
28300
28775
|
cwd: projectRoot,
|
|
28301
28776
|
encoding: "utf-8",
|
|
28302
28777
|
stdio: "pipe"
|
|
28303
28778
|
}).trim();
|
|
28304
28779
|
} catch {
|
|
28305
28780
|
}
|
|
28306
|
-
const
|
|
28307
|
-
const
|
|
28781
|
+
const releaseConfig = loadReleaseConfig(cwd);
|
|
28782
|
+
const gitFlowCfg = getGitFlowConfig(releaseConfig);
|
|
28783
|
+
const channelCfg = getChannelConfig(releaseConfig);
|
|
28784
|
+
const expectedBranch = isPreRelease ? gitFlowCfg.branches.develop : gitFlowCfg.branches.main;
|
|
28785
|
+
const isFeatureBranch = currentBranch.startsWith(gitFlowCfg.branches.featurePrefix) || currentBranch.startsWith(gitFlowCfg.branches.hotfixPrefix) || currentBranch.startsWith(gitFlowCfg.branches.releasePrefix);
|
|
28786
|
+
const branchOk = !currentBranch || currentBranch === "HEAD" || currentBranch === expectedBranch || isPreRelease && isFeatureBranch;
|
|
28787
|
+
const detectedChannel = currentBranch ? resolveChannelFromBranch(currentBranch, channelCfg) : isPreRelease ? "beta" : "latest";
|
|
28308
28788
|
gates.push({
|
|
28309
28789
|
name: "branch_target",
|
|
28310
28790
|
status: branchOk ? "passed" : "failed",
|
|
28311
|
-
message: branchOk ? `On correct branch: ${currentBranch}` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
|
|
28791
|
+
message: branchOk ? `On correct branch: ${currentBranch} (channel: ${detectedChannel})` : `Expected branch '${expectedBranch}' for ${isPreRelease ? "pre-release" : "stable"} release, but on '${currentBranch}'`
|
|
28792
|
+
});
|
|
28793
|
+
const pushMode = getPushMode(releaseConfig);
|
|
28794
|
+
let requiresPR = false;
|
|
28795
|
+
if (pushMode === "pr") {
|
|
28796
|
+
requiresPR = true;
|
|
28797
|
+
} else if (pushMode === "auto") {
|
|
28798
|
+
try {
|
|
28799
|
+
const protectionResult = await detectBranchProtection(
|
|
28800
|
+
expectedBranch,
|
|
28801
|
+
"origin",
|
|
28802
|
+
projectRoot
|
|
28803
|
+
);
|
|
28804
|
+
requiresPR = protectionResult.protected;
|
|
28805
|
+
} catch {
|
|
28806
|
+
requiresPR = false;
|
|
28807
|
+
}
|
|
28808
|
+
}
|
|
28809
|
+
gates.push({
|
|
28810
|
+
name: "branch_protection",
|
|
28811
|
+
status: "passed",
|
|
28812
|
+
message: requiresPR ? `Branch '${expectedBranch}' is protected \u2014 release.ship will create a PR` : `Branch '${expectedBranch}' allows direct push`
|
|
28312
28813
|
});
|
|
28313
28814
|
const allPassed = gates.every((g) => g.status === "passed");
|
|
28815
|
+
const metadata = {
|
|
28816
|
+
channel: detectedChannel,
|
|
28817
|
+
requiresPR,
|
|
28818
|
+
targetBranch: expectedBranch,
|
|
28819
|
+
currentBranch
|
|
28820
|
+
};
|
|
28314
28821
|
return {
|
|
28315
28822
|
version: normalizedVersion,
|
|
28316
28823
|
allPassed,
|
|
28317
28824
|
gates,
|
|
28318
28825
|
passedCount: gates.filter((g) => g.status === "passed").length,
|
|
28319
|
-
failedCount: gates.filter((g) => g.status === "failed").length
|
|
28826
|
+
failedCount: gates.filter((g) => g.status === "failed").length,
|
|
28827
|
+
metadata
|
|
28320
28828
|
};
|
|
28321
28829
|
}
|
|
28830
|
+
async function cancelRelease(version, projectRoot) {
|
|
28831
|
+
if (!version) {
|
|
28832
|
+
throw new Error("version is required");
|
|
28833
|
+
}
|
|
28834
|
+
const normalizedVersion = normalizeVersion(version);
|
|
28835
|
+
const db = await getDb(projectRoot);
|
|
28836
|
+
const rows = await db.select().from(releaseManifests).where(eq14(releaseManifests.version, normalizedVersion)).limit(1).all();
|
|
28837
|
+
if (rows.length === 0) {
|
|
28838
|
+
return { success: false, message: `Release ${normalizedVersion} not found`, version: normalizedVersion };
|
|
28839
|
+
}
|
|
28840
|
+
const status = rows[0].status;
|
|
28841
|
+
const cancellableStates = ["draft", "prepared"];
|
|
28842
|
+
if (!cancellableStates.includes(status)) {
|
|
28843
|
+
return {
|
|
28844
|
+
success: false,
|
|
28845
|
+
message: `Cannot cancel a release in '${status}' state. Use 'release rollback' instead.`,
|
|
28846
|
+
version: normalizedVersion
|
|
28847
|
+
};
|
|
28848
|
+
}
|
|
28849
|
+
await db.delete(releaseManifests).where(eq14(releaseManifests.version, normalizedVersion)).run();
|
|
28850
|
+
return { success: true, message: `Release ${normalizedVersion} cancelled and removed`, version: normalizedVersion };
|
|
28851
|
+
}
|
|
28322
28852
|
async function rollbackRelease(version, reason, cwd) {
|
|
28323
28853
|
if (!version) {
|
|
28324
28854
|
throw new Error("version is required");
|
|
@@ -28339,7 +28869,7 @@ async function rollbackRelease(version, reason, cwd) {
|
|
|
28339
28869
|
};
|
|
28340
28870
|
}
|
|
28341
28871
|
async function readPushPolicy(cwd) {
|
|
28342
|
-
const configPath =
|
|
28872
|
+
const configPath = join48(getCleoDirAbsolute(cwd), "config.json");
|
|
28343
28873
|
const config = await readJson(configPath);
|
|
28344
28874
|
if (!config) return void 0;
|
|
28345
28875
|
const release2 = config.release;
|
|
@@ -28353,6 +28883,33 @@ async function pushRelease(version, remote, cwd, opts) {
|
|
|
28353
28883
|
const normalizedVersion = normalizeVersion(version);
|
|
28354
28884
|
const projectRoot = getProjectRoot(cwd);
|
|
28355
28885
|
const pushPolicy = await readPushPolicy(cwd);
|
|
28886
|
+
const configPushMode = getPushMode(loadReleaseConfig(cwd));
|
|
28887
|
+
const effectivePushMode = opts?.mode ?? pushPolicy?.mode ?? configPushMode;
|
|
28888
|
+
if (effectivePushMode === "pr" || effectivePushMode === "auto") {
|
|
28889
|
+
const targetRemoteForCheck = remote ?? pushPolicy?.remote ?? "origin";
|
|
28890
|
+
let branchIsProtected = effectivePushMode === "pr";
|
|
28891
|
+
if (effectivePushMode === "auto") {
|
|
28892
|
+
try {
|
|
28893
|
+
const protection = await detectBranchProtection(
|
|
28894
|
+
pushPolicy?.allowedBranches?.[0] ?? "main",
|
|
28895
|
+
targetRemoteForCheck,
|
|
28896
|
+
projectRoot
|
|
28897
|
+
);
|
|
28898
|
+
branchIsProtected = protection.protected;
|
|
28899
|
+
} catch {
|
|
28900
|
+
branchIsProtected = false;
|
|
28901
|
+
}
|
|
28902
|
+
}
|
|
28903
|
+
if (branchIsProtected) {
|
|
28904
|
+
return {
|
|
28905
|
+
version: normalizedVersion,
|
|
28906
|
+
status: "requires_pr",
|
|
28907
|
+
remote: targetRemoteForCheck,
|
|
28908
|
+
pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28909
|
+
requiresPR: true
|
|
28910
|
+
};
|
|
28911
|
+
}
|
|
28912
|
+
}
|
|
28356
28913
|
if (pushPolicy && pushPolicy.enabled === false && !opts?.explicitPush) {
|
|
28357
28914
|
throw new Error(
|
|
28358
28915
|
"Push is disabled by config (release.push.enabled=false). Use --push to override."
|
|
@@ -28360,20 +28917,21 @@ async function pushRelease(version, remote, cwd, opts) {
|
|
|
28360
28917
|
}
|
|
28361
28918
|
const targetRemote = remote ?? pushPolicy?.remote ?? "origin";
|
|
28362
28919
|
if (pushPolicy?.requireCleanTree) {
|
|
28363
|
-
const statusOutput =
|
|
28920
|
+
const statusOutput = execFileSync7("git", ["status", "--porcelain"], {
|
|
28364
28921
|
cwd: projectRoot,
|
|
28365
28922
|
timeout: 1e4,
|
|
28366
28923
|
encoding: "utf-8",
|
|
28367
28924
|
stdio: ["pipe", "pipe", "pipe"]
|
|
28368
28925
|
});
|
|
28369
|
-
|
|
28926
|
+
const trackedDirty = statusOutput.split("\n").filter((l) => l.trim() && !l.startsWith("?? ")).join("\n");
|
|
28927
|
+
if (trackedDirty.trim().length > 0) {
|
|
28370
28928
|
throw new Error(
|
|
28371
28929
|
"Git working tree is not clean. Commit or stash changes before pushing (config: release.push.requireCleanTree=true)."
|
|
28372
28930
|
);
|
|
28373
28931
|
}
|
|
28374
28932
|
}
|
|
28375
28933
|
if (pushPolicy?.allowedBranches && pushPolicy.allowedBranches.length > 0) {
|
|
28376
|
-
const currentBranch =
|
|
28934
|
+
const currentBranch = execFileSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
28377
28935
|
cwd: projectRoot,
|
|
28378
28936
|
timeout: 1e4,
|
|
28379
28937
|
encoding: "utf-8",
|
|
@@ -28385,7 +28943,7 @@ async function pushRelease(version, remote, cwd, opts) {
|
|
|
28385
28943
|
);
|
|
28386
28944
|
}
|
|
28387
28945
|
}
|
|
28388
|
-
|
|
28946
|
+
execFileSync7("git", ["push", targetRemote, "--follow-tags"], {
|
|
28389
28947
|
cwd: projectRoot,
|
|
28390
28948
|
timeout: 6e4,
|
|
28391
28949
|
encoding: "utf-8",
|
|
@@ -28416,6 +28974,9 @@ var init_release_manifest = __esm({
|
|
|
28416
28974
|
init_paths();
|
|
28417
28975
|
init_json();
|
|
28418
28976
|
init_changelog_writer();
|
|
28977
|
+
init_github_pr();
|
|
28978
|
+
init_channel();
|
|
28979
|
+
init_release_config();
|
|
28419
28980
|
}
|
|
28420
28981
|
});
|
|
28421
28982
|
|
|
@@ -28495,8 +29056,181 @@ var init_guards = __esm({
|
|
|
28495
29056
|
}
|
|
28496
29057
|
});
|
|
28497
29058
|
|
|
29059
|
+
// src/core/release/version-bump.ts
|
|
29060
|
+
import { existsSync as existsSync51, readFileSync as readFileSync39, writeFileSync as writeFileSync8 } from "node:fs";
|
|
29061
|
+
import { join as join49 } from "node:path";
|
|
29062
|
+
function readConfigValueSync2(path, defaultValue, cwd) {
|
|
29063
|
+
try {
|
|
29064
|
+
const configPath = join49(getCleoDir(cwd), "config.json");
|
|
29065
|
+
if (!existsSync51(configPath)) return defaultValue;
|
|
29066
|
+
const config = JSON.parse(readFileSync39(configPath, "utf-8"));
|
|
29067
|
+
const keys = path.split(".");
|
|
29068
|
+
let value = config;
|
|
29069
|
+
for (const key of keys) {
|
|
29070
|
+
if (value == null || typeof value !== "object") return defaultValue;
|
|
29071
|
+
value = value[key];
|
|
29072
|
+
}
|
|
29073
|
+
return value ?? defaultValue;
|
|
29074
|
+
} catch {
|
|
29075
|
+
return defaultValue;
|
|
29076
|
+
}
|
|
29077
|
+
}
|
|
29078
|
+
function validateVersionFormat(version) {
|
|
29079
|
+
return VERSION_WITH_PRERELEASE.test(version);
|
|
29080
|
+
}
|
|
29081
|
+
function getVersionBumpConfig(cwd) {
|
|
29082
|
+
try {
|
|
29083
|
+
const raw = readConfigValueSync2("release.versionBump.files", [], cwd);
|
|
29084
|
+
return raw.map((entry) => ({
|
|
29085
|
+
file: entry.path ?? entry.file ?? "",
|
|
29086
|
+
strategy: entry.strategy,
|
|
29087
|
+
field: entry.jsonPath?.replace(/^\./, "") ?? entry.field,
|
|
29088
|
+
key: entry.key,
|
|
29089
|
+
section: entry.section,
|
|
29090
|
+
pattern: entry.sedPattern ?? entry.pattern
|
|
29091
|
+
})).filter((t) => t.file !== "");
|
|
29092
|
+
} catch {
|
|
29093
|
+
return [];
|
|
29094
|
+
}
|
|
29095
|
+
}
|
|
29096
|
+
function bumpFile(target, newVersion, projectRoot) {
|
|
29097
|
+
const filePath = join49(projectRoot, target.file);
|
|
29098
|
+
if (!existsSync51(filePath)) {
|
|
29099
|
+
return {
|
|
29100
|
+
file: target.file,
|
|
29101
|
+
strategy: target.strategy,
|
|
29102
|
+
success: false,
|
|
29103
|
+
error: `File not found: ${target.file}`
|
|
29104
|
+
};
|
|
29105
|
+
}
|
|
29106
|
+
try {
|
|
29107
|
+
const content = readFileSync39(filePath, "utf-8");
|
|
29108
|
+
let previousVersion;
|
|
29109
|
+
let newContent;
|
|
29110
|
+
switch (target.strategy) {
|
|
29111
|
+
case "plain": {
|
|
29112
|
+
previousVersion = content.trim();
|
|
29113
|
+
newContent = newVersion + "\n";
|
|
29114
|
+
break;
|
|
29115
|
+
}
|
|
29116
|
+
case "json": {
|
|
29117
|
+
const field = target.field ?? "version";
|
|
29118
|
+
const json = JSON.parse(content);
|
|
29119
|
+
previousVersion = getNestedField(json, field);
|
|
29120
|
+
setNestedField(json, field, newVersion);
|
|
29121
|
+
newContent = JSON.stringify(json, null, 2) + "\n";
|
|
29122
|
+
break;
|
|
29123
|
+
}
|
|
29124
|
+
case "toml": {
|
|
29125
|
+
const key = target.key ?? "version";
|
|
29126
|
+
const versionRegex = new RegExp(`^(${key}\\s*=\\s*")([^"]+)(")`, "m");
|
|
29127
|
+
const match = content.match(versionRegex);
|
|
29128
|
+
previousVersion = match?.[2];
|
|
29129
|
+
newContent = content.replace(versionRegex, `$1${newVersion}$3`);
|
|
29130
|
+
break;
|
|
29131
|
+
}
|
|
29132
|
+
case "sed": {
|
|
29133
|
+
const pattern = target.pattern ?? "";
|
|
29134
|
+
if (!pattern.includes("{{VERSION}}")) {
|
|
29135
|
+
return {
|
|
29136
|
+
file: target.file,
|
|
29137
|
+
strategy: target.strategy,
|
|
29138
|
+
success: false,
|
|
29139
|
+
error: "sed strategy requires {{VERSION}} placeholder in pattern"
|
|
29140
|
+
};
|
|
29141
|
+
}
|
|
29142
|
+
const regex = new RegExp(pattern.replace("{{VERSION}}", "([\\d.]+)"));
|
|
29143
|
+
const match = content.match(regex);
|
|
29144
|
+
previousVersion = match?.[1];
|
|
29145
|
+
newContent = content.replace(
|
|
29146
|
+
regex,
|
|
29147
|
+
pattern.replace("{{VERSION}}", newVersion)
|
|
29148
|
+
);
|
|
29149
|
+
break;
|
|
29150
|
+
}
|
|
29151
|
+
default:
|
|
29152
|
+
return {
|
|
29153
|
+
file: target.file,
|
|
29154
|
+
strategy: target.strategy,
|
|
29155
|
+
success: false,
|
|
29156
|
+
error: `Unknown strategy: ${target.strategy}`
|
|
29157
|
+
};
|
|
29158
|
+
}
|
|
29159
|
+
writeFileSync8(filePath, newContent, "utf-8");
|
|
29160
|
+
return {
|
|
29161
|
+
file: target.file,
|
|
29162
|
+
strategy: target.strategy,
|
|
29163
|
+
success: true,
|
|
29164
|
+
previousVersion,
|
|
29165
|
+
newVersion
|
|
29166
|
+
};
|
|
29167
|
+
} catch (err) {
|
|
29168
|
+
return {
|
|
29169
|
+
file: target.file,
|
|
29170
|
+
strategy: target.strategy,
|
|
29171
|
+
success: false,
|
|
29172
|
+
error: String(err)
|
|
29173
|
+
};
|
|
29174
|
+
}
|
|
29175
|
+
}
|
|
29176
|
+
function bumpVersionFromConfig(newVersion, options = {}, cwd) {
|
|
29177
|
+
if (!validateVersionFormat(newVersion)) {
|
|
29178
|
+
throw new CleoError(
|
|
29179
|
+
6 /* VALIDATION_ERROR */,
|
|
29180
|
+
`Invalid version: '${newVersion}' (expected X.Y.Z or YYYY.M.patch)`
|
|
29181
|
+
);
|
|
29182
|
+
}
|
|
29183
|
+
const targets = getVersionBumpConfig(cwd);
|
|
29184
|
+
if (targets.length === 0) {
|
|
29185
|
+
throw new CleoError(
|
|
29186
|
+
1 /* GENERAL_ERROR */,
|
|
29187
|
+
"No version bump targets configured. Add release.versionBump.files to .cleo/config.json"
|
|
29188
|
+
);
|
|
29189
|
+
}
|
|
29190
|
+
const projectRoot = getProjectRoot(cwd);
|
|
29191
|
+
const results = [];
|
|
29192
|
+
if (options.dryRun) {
|
|
29193
|
+
for (const target of targets) {
|
|
29194
|
+
results.push({
|
|
29195
|
+
file: target.file,
|
|
29196
|
+
strategy: target.strategy,
|
|
29197
|
+
success: true,
|
|
29198
|
+
newVersion
|
|
29199
|
+
});
|
|
29200
|
+
}
|
|
29201
|
+
return { results, allSuccess: true };
|
|
29202
|
+
}
|
|
29203
|
+
for (const target of targets) {
|
|
29204
|
+
results.push(bumpFile(target, newVersion, projectRoot));
|
|
29205
|
+
}
|
|
29206
|
+
const allSuccess = results.every((r) => r.success);
|
|
29207
|
+
return { results, allSuccess };
|
|
29208
|
+
}
|
|
29209
|
+
function getNestedField(obj, path) {
|
|
29210
|
+
return path.split(".").reduce((acc, key) => acc?.[key], obj);
|
|
29211
|
+
}
|
|
29212
|
+
function setNestedField(obj, path, value) {
|
|
29213
|
+
const parts = path.split(".");
|
|
29214
|
+
let current = obj;
|
|
29215
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
29216
|
+
if (typeof current[parts[i]] !== "object") current[parts[i]] = {};
|
|
29217
|
+
current = current[parts[i]];
|
|
29218
|
+
}
|
|
29219
|
+
current[parts[parts.length - 1]] = value;
|
|
29220
|
+
}
|
|
29221
|
+
var VERSION_WITH_PRERELEASE;
|
|
29222
|
+
var init_version_bump = __esm({
|
|
29223
|
+
"src/core/release/version-bump.ts"() {
|
|
29224
|
+
"use strict";
|
|
29225
|
+
init_paths();
|
|
29226
|
+
init_errors();
|
|
29227
|
+
init_exit_codes();
|
|
29228
|
+
VERSION_WITH_PRERELEASE = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
|
|
29229
|
+
}
|
|
29230
|
+
});
|
|
29231
|
+
|
|
28498
29232
|
// src/dispatch/engines/release-engine.ts
|
|
28499
|
-
import { execFileSync as
|
|
29233
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
28500
29234
|
function isAgentContext() {
|
|
28501
29235
|
return !!(process.env["CLEO_SESSION_ID"] || process.env["CLAUDE_AGENT_TYPE"]);
|
|
28502
29236
|
}
|
|
@@ -28617,6 +29351,23 @@ async function releaseRollback(version, reason, projectRoot) {
|
|
|
28617
29351
|
return engineError(code, message);
|
|
28618
29352
|
}
|
|
28619
29353
|
}
|
|
29354
|
+
async function releaseCancel(version, projectRoot) {
|
|
29355
|
+
if (!version) {
|
|
29356
|
+
return engineError("E_INVALID_INPUT", "version is required");
|
|
29357
|
+
}
|
|
29358
|
+
try {
|
|
29359
|
+
const result = await cancelRelease(version, projectRoot);
|
|
29360
|
+
if (!result.success) {
|
|
29361
|
+
const code = result.message.includes("not found") ? "E_NOT_FOUND" : "E_INVALID_STATE";
|
|
29362
|
+
return engineError(code, result.message);
|
|
29363
|
+
}
|
|
29364
|
+
return { success: true, data: result };
|
|
29365
|
+
} catch (err) {
|
|
29366
|
+
const message = err.message;
|
|
29367
|
+
const code = message.includes("not found") ? "E_NOT_FOUND" : "E_CANCEL_FAILED";
|
|
29368
|
+
return engineError(code, message);
|
|
29369
|
+
}
|
|
29370
|
+
}
|
|
28620
29371
|
async function releasePush(version, remote, projectRoot, opts) {
|
|
28621
29372
|
if (isAgentContext()) {
|
|
28622
29373
|
const hasEntry = await hasManifestEntry(version, projectRoot);
|
|
@@ -28638,7 +29389,7 @@ async function releasePush(version, remote, projectRoot, opts) {
|
|
|
28638
29389
|
const result = await pushRelease(version, remote, projectRoot, opts);
|
|
28639
29390
|
let commitSha;
|
|
28640
29391
|
try {
|
|
28641
|
-
commitSha =
|
|
29392
|
+
commitSha = execFileSync8("git", ["rev-parse", "HEAD"], {
|
|
28642
29393
|
cwd: projectRoot ?? process.cwd(),
|
|
28643
29394
|
encoding: "utf-8",
|
|
28644
29395
|
stdio: "pipe"
|
|
@@ -28660,7 +29411,7 @@ async function releasePush(version, remote, projectRoot, opts) {
|
|
|
28660
29411
|
}
|
|
28661
29412
|
}
|
|
28662
29413
|
async function releaseShip(params, projectRoot) {
|
|
28663
|
-
const { version, epicId, remote, dryRun = false } = params;
|
|
29414
|
+
const { version, epicId, remote, dryRun = false, bump = true } = params;
|
|
28664
29415
|
if (!version) {
|
|
28665
29416
|
return engineError("E_INVALID_INPUT", "version is required");
|
|
28666
29417
|
}
|
|
@@ -28668,18 +29419,71 @@ async function releaseShip(params, projectRoot) {
|
|
|
28668
29419
|
return engineError("E_INVALID_INPUT", "epicId is required");
|
|
28669
29420
|
}
|
|
28670
29421
|
const cwd = projectRoot ?? resolveProjectRoot();
|
|
29422
|
+
const steps = [];
|
|
29423
|
+
const logStep = (n, total, label, done, error) => {
|
|
29424
|
+
let msg;
|
|
29425
|
+
if (done === void 0) {
|
|
29426
|
+
msg = `[Step ${n}/${total}] ${label}...`;
|
|
29427
|
+
} else if (done) {
|
|
29428
|
+
msg = ` \u2713 ${label}`;
|
|
29429
|
+
} else {
|
|
29430
|
+
msg = ` \u2717 ${label}: ${error ?? "failed"}`;
|
|
29431
|
+
}
|
|
29432
|
+
steps.push(msg);
|
|
29433
|
+
console.log(msg);
|
|
29434
|
+
};
|
|
29435
|
+
const bumpTargets = getVersionBumpConfig(cwd);
|
|
29436
|
+
const shouldBump = bump && bumpTargets.length > 0;
|
|
28671
29437
|
try {
|
|
29438
|
+
if (shouldBump) {
|
|
29439
|
+
logStep(0, 8, "Bump version files");
|
|
29440
|
+
if (!dryRun) {
|
|
29441
|
+
const bumpResults = bumpVersionFromConfig(version, { dryRun: false }, cwd);
|
|
29442
|
+
if (!bumpResults.allSuccess) {
|
|
29443
|
+
const failed = bumpResults.results.filter((r) => !r.success).map((r) => r.file);
|
|
29444
|
+
steps.push(` ! Version bump partial: failed for ${failed.join(", ")}`);
|
|
29445
|
+
} else {
|
|
29446
|
+
logStep(0, 8, "Bump version files", true);
|
|
29447
|
+
}
|
|
29448
|
+
} else {
|
|
29449
|
+
logStep(0, 8, "Bump version files", true);
|
|
29450
|
+
}
|
|
29451
|
+
}
|
|
29452
|
+
logStep(1, 8, "Validate release gates");
|
|
28672
29453
|
const gatesResult = await runReleaseGates(
|
|
28673
29454
|
version,
|
|
28674
29455
|
() => loadTasks2(projectRoot),
|
|
28675
|
-
projectRoot
|
|
29456
|
+
projectRoot,
|
|
29457
|
+
{ dryRun }
|
|
28676
29458
|
);
|
|
28677
29459
|
if (gatesResult && !gatesResult.allPassed) {
|
|
28678
29460
|
const failedGates = gatesResult.gates.filter((g) => g.status === "failed");
|
|
29461
|
+
logStep(1, 8, "Validate release gates", false, failedGates.map((g) => g.name).join(", "));
|
|
28679
29462
|
return engineError("E_LIFECYCLE_GATE_FAILED", `Release gates failed for ${version}: ${failedGates.map((g) => g.name).join(", ")}`, {
|
|
28680
29463
|
details: { gates: gatesResult.gates, failedCount: gatesResult.failedCount }
|
|
28681
29464
|
});
|
|
28682
29465
|
}
|
|
29466
|
+
logStep(1, 8, "Validate release gates", true);
|
|
29467
|
+
let resolvedChannel = "latest";
|
|
29468
|
+
let currentBranchForPR = "HEAD";
|
|
29469
|
+
try {
|
|
29470
|
+
const branchName = execFileSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
29471
|
+
cwd,
|
|
29472
|
+
encoding: "utf-8",
|
|
29473
|
+
stdio: "pipe"
|
|
29474
|
+
}).trim();
|
|
29475
|
+
currentBranchForPR = branchName;
|
|
29476
|
+
const channelEnum = resolveChannelFromBranch(branchName);
|
|
29477
|
+
resolvedChannel = channelToDistTag(channelEnum);
|
|
29478
|
+
} catch {
|
|
29479
|
+
}
|
|
29480
|
+
const gateMetadata = gatesResult.metadata;
|
|
29481
|
+
const requiresPRFromGates = gateMetadata?.requiresPR ?? false;
|
|
29482
|
+
const targetBranchFromGates = gateMetadata?.targetBranch;
|
|
29483
|
+
if (gateMetadata?.currentBranch) {
|
|
29484
|
+
currentBranchForPR = gateMetadata.currentBranch;
|
|
29485
|
+
}
|
|
29486
|
+
logStep(2, 8, "Check epic completeness");
|
|
28683
29487
|
let releaseTaskIds = [];
|
|
28684
29488
|
try {
|
|
28685
29489
|
const manifest = await showManifestRelease(version, projectRoot);
|
|
@@ -28690,10 +29494,13 @@ async function releaseShip(params, projectRoot) {
|
|
|
28690
29494
|
const epicCheck = await checkEpicCompleteness(releaseTaskIds, projectRoot, epicAccessor);
|
|
28691
29495
|
if (epicCheck.hasIncomplete) {
|
|
28692
29496
|
const incomplete = epicCheck.epics.filter((e) => e.missingChildren.length > 0).map((e) => `${e.epicId}: missing ${e.missingChildren.map((c) => c.id).join(", ")}`).join("; ");
|
|
29497
|
+
logStep(2, 8, "Check epic completeness", false, incomplete);
|
|
28693
29498
|
return engineError("E_LIFECYCLE_GATE_FAILED", `Epic completeness check failed: ${incomplete}`, {
|
|
28694
29499
|
details: { epics: epicCheck.epics }
|
|
28695
29500
|
});
|
|
28696
29501
|
}
|
|
29502
|
+
logStep(2, 8, "Check epic completeness", true);
|
|
29503
|
+
logStep(3, 8, "Check task double-listing");
|
|
28697
29504
|
const allReleases = await listManifestReleases(projectRoot);
|
|
28698
29505
|
const existingReleases = (allReleases.releases ?? []).filter((r) => r.version !== version);
|
|
28699
29506
|
const doubleCheck = checkDoubleListing(
|
|
@@ -28702,73 +29509,160 @@ async function releaseShip(params, projectRoot) {
|
|
|
28702
29509
|
);
|
|
28703
29510
|
if (doubleCheck.hasDoubleListing) {
|
|
28704
29511
|
const dupes = doubleCheck.duplicates.map((d) => `${d.taskId} (in ${d.releases.join(", ")})`).join("; ");
|
|
29512
|
+
logStep(3, 8, "Check task double-listing", false, dupes);
|
|
28705
29513
|
return engineError("E_VALIDATION", `Double-listing detected: ${dupes}`, {
|
|
28706
29514
|
details: { duplicates: doubleCheck.duplicates }
|
|
28707
29515
|
});
|
|
28708
29516
|
}
|
|
28709
|
-
|
|
29517
|
+
logStep(3, 8, "Check task double-listing", true);
|
|
29518
|
+
const loadedConfig = loadReleaseConfig(cwd);
|
|
29519
|
+
const pushMode = getPushMode(loadedConfig);
|
|
29520
|
+
const gitflowCfg = getGitFlowConfig(loadedConfig);
|
|
29521
|
+
const targetBranch = targetBranchFromGates ?? gitflowCfg.branches.main;
|
|
29522
|
+
if (dryRun) {
|
|
29523
|
+
logStep(4, 8, "Generate CHANGELOG");
|
|
29524
|
+
logStep(4, 8, "Generate CHANGELOG", true);
|
|
29525
|
+
const wouldCreatePR = requiresPRFromGates || pushMode === "pr";
|
|
29526
|
+
const filesToStagePreview = ["CHANGELOG.md", ...shouldBump ? bumpTargets.map((t) => t.file) : []];
|
|
29527
|
+
const wouldDo = [];
|
|
29528
|
+
if (shouldBump) {
|
|
29529
|
+
wouldDo.push(`bump version files: ${bumpTargets.map((t) => t.file).join(", ")} \u2192 ${version}`);
|
|
29530
|
+
}
|
|
29531
|
+
wouldDo.push(
|
|
29532
|
+
`write CHANGELOG.md: ## [${version}] - ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} (preview only, not written in dry-run)`,
|
|
29533
|
+
`git add ${filesToStagePreview.join(" ")}`,
|
|
29534
|
+
`git commit -m "release: ship v${version} (${epicId})"`,
|
|
29535
|
+
`git tag -a v${version} -m "Release v${version}"`
|
|
29536
|
+
);
|
|
29537
|
+
const dryRunOutput = {
|
|
29538
|
+
version,
|
|
29539
|
+
epicId,
|
|
29540
|
+
dryRun: true,
|
|
29541
|
+
channel: resolvedChannel,
|
|
29542
|
+
pushMode,
|
|
29543
|
+
wouldDo
|
|
29544
|
+
};
|
|
29545
|
+
if (wouldCreatePR) {
|
|
29546
|
+
const ghAvailable = isGhCliAvailable();
|
|
29547
|
+
dryRunOutput["wouldDo"].push(
|
|
29548
|
+
ghAvailable ? `gh pr create --base ${targetBranch} --head ${currentBranchForPR} --title "release: ship v${version}"` : `manual PR: ${currentBranchForPR} \u2192 ${targetBranch} (gh CLI not available)`
|
|
29549
|
+
);
|
|
29550
|
+
dryRunOutput["wouldCreatePR"] = true;
|
|
29551
|
+
dryRunOutput["prTitle"] = `release: ship v${version}`;
|
|
29552
|
+
dryRunOutput["prTargetBranch"] = targetBranch;
|
|
29553
|
+
} else {
|
|
29554
|
+
dryRunOutput["wouldDo"].push(
|
|
29555
|
+
`git push ${remote ?? "origin"} --follow-tags`
|
|
29556
|
+
);
|
|
29557
|
+
dryRunOutput["wouldCreatePR"] = false;
|
|
29558
|
+
}
|
|
29559
|
+
dryRunOutput["wouldDo"].push("markReleasePushed(...)");
|
|
29560
|
+
return { success: true, data: { ...dryRunOutput, steps } };
|
|
29561
|
+
}
|
|
29562
|
+
logStep(4, 8, "Generate CHANGELOG");
|
|
29563
|
+
await generateReleaseChangelog(
|
|
28710
29564
|
version,
|
|
28711
29565
|
() => loadTasks2(projectRoot),
|
|
28712
29566
|
projectRoot
|
|
28713
29567
|
);
|
|
28714
29568
|
const changelogPath = `${cwd}/CHANGELOG.md`;
|
|
28715
|
-
|
|
28716
|
-
|
|
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
|
-
}
|
|
28732
|
-
};
|
|
28733
|
-
}
|
|
28734
|
-
await writeChangelogSection(version, generatedContent, [], changelogPath);
|
|
29569
|
+
logStep(4, 8, "Generate CHANGELOG", true);
|
|
29570
|
+
logStep(5, 8, "Commit release");
|
|
28735
29571
|
const gitCwd = { cwd, encoding: "utf-8", stdio: "pipe" };
|
|
29572
|
+
const filesToStage = ["CHANGELOG.md", ...shouldBump ? bumpTargets.map((t) => t.file) : []];
|
|
28736
29573
|
try {
|
|
28737
|
-
|
|
29574
|
+
execFileSync8("git", ["add", ...filesToStage], gitCwd);
|
|
28738
29575
|
} catch (err) {
|
|
28739
29576
|
const msg = err.message ?? String(err);
|
|
29577
|
+
logStep(5, 8, "Commit release", false, `git add failed: ${msg}`);
|
|
28740
29578
|
return engineError("E_GENERAL", `git add failed: ${msg}`);
|
|
28741
29579
|
}
|
|
28742
29580
|
try {
|
|
28743
|
-
|
|
29581
|
+
execFileSync8(
|
|
28744
29582
|
"git",
|
|
28745
29583
|
["commit", "-m", `release: ship v${version} (${epicId})`],
|
|
28746
29584
|
gitCwd
|
|
28747
29585
|
);
|
|
28748
29586
|
} catch (err) {
|
|
28749
29587
|
const msg = err.stderr ?? err.message ?? String(err);
|
|
29588
|
+
logStep(5, 8, "Commit release", false, `git commit failed: ${msg}`);
|
|
28750
29589
|
return engineError("E_GENERAL", `git commit failed: ${msg}`);
|
|
28751
29590
|
}
|
|
29591
|
+
logStep(5, 8, "Commit release", true);
|
|
28752
29592
|
let commitSha;
|
|
28753
29593
|
try {
|
|
28754
|
-
commitSha =
|
|
29594
|
+
commitSha = execFileSync8("git", ["rev-parse", "HEAD"], gitCwd).toString().trim();
|
|
28755
29595
|
} catch {
|
|
28756
29596
|
}
|
|
29597
|
+
logStep(6, 8, "Tag release");
|
|
28757
29598
|
const gitTag = `v${version.replace(/^v/, "")}`;
|
|
28758
29599
|
try {
|
|
28759
|
-
|
|
29600
|
+
execFileSync8("git", ["tag", "-a", gitTag, "-m", `Release ${gitTag}`], gitCwd);
|
|
28760
29601
|
} catch (err) {
|
|
28761
29602
|
const msg = err.stderr ?? err.message ?? String(err);
|
|
29603
|
+
logStep(6, 8, "Tag release", false, `git tag failed: ${msg}`);
|
|
28762
29604
|
return engineError("E_GENERAL", `git tag failed: ${msg}`);
|
|
28763
29605
|
}
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28770
|
-
|
|
29606
|
+
logStep(6, 8, "Tag release", true);
|
|
29607
|
+
logStep(7, 8, "Push / create PR");
|
|
29608
|
+
let prResult = null;
|
|
29609
|
+
const pushResult = await pushRelease(version, remote, projectRoot, {
|
|
29610
|
+
explicitPush: true,
|
|
29611
|
+
mode: pushMode
|
|
29612
|
+
});
|
|
29613
|
+
if (pushResult.requiresPR || requiresPRFromGates) {
|
|
29614
|
+
const prBody = buildPRBody({
|
|
29615
|
+
base: targetBranch,
|
|
29616
|
+
head: currentBranchForPR,
|
|
29617
|
+
title: `release: ship v${version}`,
|
|
29618
|
+
body: "",
|
|
29619
|
+
version,
|
|
29620
|
+
epicId,
|
|
29621
|
+
projectRoot: cwd
|
|
29622
|
+
});
|
|
29623
|
+
prResult = await createPullRequest({
|
|
29624
|
+
base: targetBranch,
|
|
29625
|
+
head: currentBranchForPR,
|
|
29626
|
+
title: `release: ship v${version}`,
|
|
29627
|
+
body: prBody,
|
|
29628
|
+
labels: ["release", resolvedChannel],
|
|
29629
|
+
version,
|
|
29630
|
+
epicId,
|
|
29631
|
+
projectRoot: cwd
|
|
28771
29632
|
});
|
|
29633
|
+
if (prResult.mode === "created") {
|
|
29634
|
+
const m1 = ` \u2713 Push / create PR`;
|
|
29635
|
+
const m2 = ` PR created: ${prResult.prUrl}`;
|
|
29636
|
+
const m3 = ` \u2192 Next: merge the PR, then CI will publish to npm @${resolvedChannel}`;
|
|
29637
|
+
steps.push(m1, m2, m3);
|
|
29638
|
+
console.log(m1);
|
|
29639
|
+
console.log(m2);
|
|
29640
|
+
console.log(m3);
|
|
29641
|
+
} else if (prResult.mode === "skipped") {
|
|
29642
|
+
const m1 = ` \u2713 Push / create PR`;
|
|
29643
|
+
const m2 = ` PR already exists: ${prResult.prUrl}`;
|
|
29644
|
+
steps.push(m1, m2);
|
|
29645
|
+
console.log(m1);
|
|
29646
|
+
console.log(m2);
|
|
29647
|
+
} else {
|
|
29648
|
+
const m1 = ` ! Push / create PR \u2014 manual PR required:`;
|
|
29649
|
+
const m2 = prResult.instructions ?? "";
|
|
29650
|
+
steps.push(m1, m2);
|
|
29651
|
+
console.log(m1);
|
|
29652
|
+
console.log(m2);
|
|
29653
|
+
}
|
|
29654
|
+
} else {
|
|
29655
|
+
try {
|
|
29656
|
+
execFileSync8("git", ["push", remote ?? "origin", "--follow-tags"], gitCwd);
|
|
29657
|
+
logStep(7, 8, "Push / create PR", true);
|
|
29658
|
+
} catch (err) {
|
|
29659
|
+
const execError = err;
|
|
29660
|
+
const msg = (execError.stderr ?? execError.message ?? "").slice(0, 500);
|
|
29661
|
+
logStep(7, 8, "Push / create PR", false, `git push failed: ${msg}`);
|
|
29662
|
+
return engineError("E_GENERAL", `git push failed: ${msg}`, {
|
|
29663
|
+
details: { exitCode: execError.status }
|
|
29664
|
+
});
|
|
29665
|
+
}
|
|
28772
29666
|
}
|
|
28773
29667
|
const pushedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28774
29668
|
await markReleasePushed(version, pushedAt, projectRoot, { commitSha, gitTag });
|
|
@@ -28780,7 +29674,17 @@ async function releaseShip(params, projectRoot) {
|
|
|
28780
29674
|
commitSha,
|
|
28781
29675
|
gitTag,
|
|
28782
29676
|
pushedAt,
|
|
28783
|
-
changelog: changelogPath
|
|
29677
|
+
changelog: changelogPath,
|
|
29678
|
+
channel: resolvedChannel,
|
|
29679
|
+
steps,
|
|
29680
|
+
...prResult ? {
|
|
29681
|
+
pr: {
|
|
29682
|
+
mode: prResult.mode,
|
|
29683
|
+
prUrl: prResult.prUrl,
|
|
29684
|
+
prNumber: prResult.prNumber,
|
|
29685
|
+
instructions: prResult.instructions
|
|
29686
|
+
}
|
|
29687
|
+
} : {}
|
|
28784
29688
|
}
|
|
28785
29689
|
};
|
|
28786
29690
|
} catch (err) {
|
|
@@ -28793,15 +29697,18 @@ var init_release_engine = __esm({
|
|
|
28793
29697
|
init_platform();
|
|
28794
29698
|
init_data_accessor();
|
|
28795
29699
|
init_release_manifest();
|
|
28796
|
-
init_changelog_writer();
|
|
28797
29700
|
init_guards();
|
|
29701
|
+
init_github_pr();
|
|
29702
|
+
init_channel();
|
|
29703
|
+
init_release_config();
|
|
29704
|
+
init_version_bump();
|
|
28798
29705
|
init_error();
|
|
28799
29706
|
}
|
|
28800
29707
|
});
|
|
28801
29708
|
|
|
28802
29709
|
// src/dispatch/engines/template-parser.ts
|
|
28803
|
-
import { readFileSync as
|
|
28804
|
-
import { join as
|
|
29710
|
+
import { readFileSync as readFileSync40, readdirSync as readdirSync12, existsSync as existsSync52 } from "fs";
|
|
29711
|
+
import { join as join50 } from "path";
|
|
28805
29712
|
import { parse as parseYaml } from "yaml";
|
|
28806
29713
|
function deriveSubcommand(filename) {
|
|
28807
29714
|
let stem = filename.replace(/\.ya?ml$/i, "");
|
|
@@ -28815,8 +29722,8 @@ function deriveSubcommand(filename) {
|
|
|
28815
29722
|
return firstWord.toLowerCase();
|
|
28816
29723
|
}
|
|
28817
29724
|
function parseTemplateFile(templateDir, filename) {
|
|
28818
|
-
const filePath =
|
|
28819
|
-
const raw =
|
|
29725
|
+
const filePath = join50(templateDir, filename);
|
|
29726
|
+
const raw = readFileSync40(filePath, "utf-8");
|
|
28820
29727
|
const parsed = parseYaml(raw);
|
|
28821
29728
|
const name = typeof parsed.name === "string" ? parsed.name : filename;
|
|
28822
29729
|
const titlePrefix = typeof parsed.title === "string" ? parsed.title : "";
|
|
@@ -28865,8 +29772,8 @@ function parseTemplateFile(templateDir, filename) {
|
|
|
28865
29772
|
};
|
|
28866
29773
|
}
|
|
28867
29774
|
function parseIssueTemplates(projectRoot) {
|
|
28868
|
-
const templateDir =
|
|
28869
|
-
if (!
|
|
29775
|
+
const templateDir = join50(projectRoot, ".github", "ISSUE_TEMPLATE");
|
|
29776
|
+
if (!existsSync52(templateDir)) {
|
|
28870
29777
|
return engineError("E_NOT_FOUND", `Issue template directory not found: ${templateDir}`);
|
|
28871
29778
|
}
|
|
28872
29779
|
let files;
|
|
@@ -30545,26 +31452,26 @@ var init_check = __esm({
|
|
|
30545
31452
|
});
|
|
30546
31453
|
|
|
30547
31454
|
// src/core/adrs/validate.ts
|
|
30548
|
-
import { readFileSync as
|
|
30549
|
-
import { join as
|
|
31455
|
+
import { readFileSync as readFileSync41, readdirSync as readdirSync13, existsSync as existsSync53 } from "node:fs";
|
|
31456
|
+
import { join as join51 } from "node:path";
|
|
30550
31457
|
import AjvModule3 from "ajv";
|
|
30551
31458
|
async function validateAllAdrs(projectRoot) {
|
|
30552
|
-
const adrsDir =
|
|
30553
|
-
const schemaPath =
|
|
30554
|
-
if (!
|
|
31459
|
+
const adrsDir = join51(projectRoot, ".cleo", "adrs");
|
|
31460
|
+
const schemaPath = join51(projectRoot, "schemas", "adr-frontmatter.schema.json");
|
|
31461
|
+
if (!existsSync53(schemaPath)) {
|
|
30555
31462
|
return {
|
|
30556
31463
|
valid: false,
|
|
30557
31464
|
errors: [{ file: "schemas/adr-frontmatter.schema.json", field: "schema", message: "Schema file not found" }],
|
|
30558
31465
|
checked: 0
|
|
30559
31466
|
};
|
|
30560
31467
|
}
|
|
30561
|
-
if (!
|
|
31468
|
+
if (!existsSync53(adrsDir)) {
|
|
30562
31469
|
return { valid: true, errors: [], checked: 0 };
|
|
30563
31470
|
}
|
|
30564
|
-
const schema = JSON.parse(
|
|
31471
|
+
const schema = JSON.parse(readFileSync41(schemaPath, "utf-8"));
|
|
30565
31472
|
const ajv = new Ajv3({ allErrors: true });
|
|
30566
31473
|
const validate = ajv.compile(schema);
|
|
30567
|
-
const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) =>
|
|
31474
|
+
const files = readdirSync13(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join51(adrsDir, f));
|
|
30568
31475
|
const errors = [];
|
|
30569
31476
|
for (const filePath of files) {
|
|
30570
31477
|
const record = parseAdrFile(filePath, projectRoot);
|
|
@@ -30591,15 +31498,15 @@ var init_validate = __esm({
|
|
|
30591
31498
|
});
|
|
30592
31499
|
|
|
30593
31500
|
// src/core/adrs/list.ts
|
|
30594
|
-
import { readdirSync as readdirSync14, existsSync as
|
|
30595
|
-
import { join as
|
|
31501
|
+
import { readdirSync as readdirSync14, existsSync as existsSync54 } from "node:fs";
|
|
31502
|
+
import { join as join52 } from "node:path";
|
|
30596
31503
|
async function listAdrs(projectRoot, opts) {
|
|
30597
|
-
const adrsDir =
|
|
30598
|
-
if (!
|
|
31504
|
+
const adrsDir = join52(projectRoot, ".cleo", "adrs");
|
|
31505
|
+
if (!existsSync54(adrsDir)) {
|
|
30599
31506
|
return { adrs: [], total: 0 };
|
|
30600
31507
|
}
|
|
30601
31508
|
const files = readdirSync14(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
|
|
30602
|
-
const records = files.map((f) => parseAdrFile(
|
|
31509
|
+
const records = files.map((f) => parseAdrFile(join52(adrsDir, f), projectRoot));
|
|
30603
31510
|
const filtered = records.filter((r) => {
|
|
30604
31511
|
if (opts?.status && r.frontmatter.Status !== opts.status) return false;
|
|
30605
31512
|
if (opts?.since && r.frontmatter.Date < opts.since) return false;
|
|
@@ -30624,14 +31531,14 @@ var init_list2 = __esm({
|
|
|
30624
31531
|
});
|
|
30625
31532
|
|
|
30626
31533
|
// src/core/adrs/show.ts
|
|
30627
|
-
import { existsSync as
|
|
30628
|
-
import { join as
|
|
31534
|
+
import { existsSync as existsSync55, readdirSync as readdirSync15 } from "node:fs";
|
|
31535
|
+
import { join as join53 } from "node:path";
|
|
30629
31536
|
async function showAdr(projectRoot, adrId) {
|
|
30630
|
-
const adrsDir =
|
|
30631
|
-
if (!
|
|
31537
|
+
const adrsDir = join53(projectRoot, ".cleo", "adrs");
|
|
31538
|
+
if (!existsSync55(adrsDir)) return null;
|
|
30632
31539
|
const files = readdirSync15(adrsDir).filter((f) => f.startsWith(adrId) && f.endsWith(".md"));
|
|
30633
31540
|
if (files.length === 0) return null;
|
|
30634
|
-
const filePath =
|
|
31541
|
+
const filePath = join53(adrsDir, files[0]);
|
|
30635
31542
|
return parseAdrFile(filePath, projectRoot);
|
|
30636
31543
|
}
|
|
30637
31544
|
var init_show2 = __esm({
|
|
@@ -30642,8 +31549,8 @@ var init_show2 = __esm({
|
|
|
30642
31549
|
});
|
|
30643
31550
|
|
|
30644
31551
|
// src/core/adrs/find.ts
|
|
30645
|
-
import { readdirSync as readdirSync16, existsSync as
|
|
30646
|
-
import { join as
|
|
31552
|
+
import { readdirSync as readdirSync16, existsSync as existsSync56 } from "node:fs";
|
|
31553
|
+
import { join as join54 } from "node:path";
|
|
30647
31554
|
function normalise(s) {
|
|
30648
31555
|
return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
30649
31556
|
}
|
|
@@ -30660,8 +31567,8 @@ function matchedTerms(target, terms) {
|
|
|
30660
31567
|
return terms.filter((term) => t.includes(term));
|
|
30661
31568
|
}
|
|
30662
31569
|
async function findAdrs(projectRoot, query, opts) {
|
|
30663
|
-
const adrsDir =
|
|
30664
|
-
if (!
|
|
31570
|
+
const adrsDir = join54(projectRoot, ".cleo", "adrs");
|
|
31571
|
+
if (!existsSync56(adrsDir)) {
|
|
30665
31572
|
return { adrs: [], query, total: 0 };
|
|
30666
31573
|
}
|
|
30667
31574
|
const files = readdirSync16(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
|
|
@@ -30670,7 +31577,7 @@ async function findAdrs(projectRoot, query, opts) {
|
|
|
30670
31577
|
const filterKeywords = opts?.keywords ? parseTags(opts.keywords) : null;
|
|
30671
31578
|
const results = [];
|
|
30672
31579
|
for (const file of files) {
|
|
30673
|
-
const record = parseAdrFile(
|
|
31580
|
+
const record = parseAdrFile(join54(adrsDir, file), projectRoot);
|
|
30674
31581
|
const fm = record.frontmatter;
|
|
30675
31582
|
if (opts?.status && fm.Status !== opts.status) continue;
|
|
30676
31583
|
if (filterTopics && filterTopics.length > 0) {
|
|
@@ -30748,12 +31655,12 @@ var init_adrs = __esm({
|
|
|
30748
31655
|
});
|
|
30749
31656
|
|
|
30750
31657
|
// src/core/admin/sync.ts
|
|
30751
|
-
import { join as
|
|
31658
|
+
import { join as join55 } from "node:path";
|
|
30752
31659
|
import { rm as rm2, rmdir, stat as stat2 } from "node:fs/promises";
|
|
30753
31660
|
async function getSyncStatus(projectRoot) {
|
|
30754
31661
|
try {
|
|
30755
31662
|
const cleoDir = getCleoDir(projectRoot);
|
|
30756
|
-
const stateFile =
|
|
31663
|
+
const stateFile = join55(cleoDir, "sync", "todowrite-session.json");
|
|
30757
31664
|
const sessionState = await readJson(stateFile);
|
|
30758
31665
|
if (!sessionState) {
|
|
30759
31666
|
return {
|
|
@@ -30797,8 +31704,8 @@ async function getSyncStatus(projectRoot) {
|
|
|
30797
31704
|
async function clearSyncState(projectRoot, dryRun) {
|
|
30798
31705
|
try {
|
|
30799
31706
|
const cleoDir = getCleoDir(projectRoot);
|
|
30800
|
-
const syncDir =
|
|
30801
|
-
const stateFile =
|
|
31707
|
+
const syncDir = join55(cleoDir, "sync");
|
|
31708
|
+
const stateFile = join55(syncDir, "todowrite-session.json");
|
|
30802
31709
|
let exists = false;
|
|
30803
31710
|
try {
|
|
30804
31711
|
await stat2(stateFile);
|
|
@@ -31565,8 +32472,8 @@ var init_import_tasks = __esm({
|
|
|
31565
32472
|
// src/core/snapshot/index.ts
|
|
31566
32473
|
import { createHash as createHash8 } from "node:crypto";
|
|
31567
32474
|
import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir10 } from "node:fs/promises";
|
|
31568
|
-
import { existsSync as
|
|
31569
|
-
import { join as
|
|
32475
|
+
import { existsSync as existsSync57 } from "node:fs";
|
|
32476
|
+
import { join as join56, dirname as dirname14 } from "node:path";
|
|
31570
32477
|
function toSnapshotTask(task) {
|
|
31571
32478
|
return {
|
|
31572
32479
|
id: task.id,
|
|
@@ -31619,7 +32526,7 @@ async function exportSnapshot(cwd) {
|
|
|
31619
32526
|
}
|
|
31620
32527
|
async function writeSnapshot(snapshot, outputPath) {
|
|
31621
32528
|
const dir = dirname14(outputPath);
|
|
31622
|
-
if (!
|
|
32529
|
+
if (!existsSync57(dir)) {
|
|
31623
32530
|
await mkdir10(dir, { recursive: true });
|
|
31624
32531
|
}
|
|
31625
32532
|
await writeFile10(outputPath, JSON.stringify(snapshot, null, 2) + "\n");
|
|
@@ -31635,7 +32542,7 @@ async function readSnapshot(inputPath) {
|
|
|
31635
32542
|
function getDefaultSnapshotPath(cwd) {
|
|
31636
32543
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
31637
32544
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
31638
|
-
return
|
|
32545
|
+
return join56(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
|
|
31639
32546
|
}
|
|
31640
32547
|
async function importSnapshot(snapshot, cwd) {
|
|
31641
32548
|
const accessor = await getAccessor(cwd);
|
|
@@ -31767,40 +32674,49 @@ var init_session_resolver = __esm({
|
|
|
31767
32674
|
}
|
|
31768
32675
|
});
|
|
31769
32676
|
|
|
32677
|
+
// src/core/tasks/id-generator.ts
|
|
32678
|
+
function normalizeTaskId(input) {
|
|
32679
|
+
if (typeof input !== "string") return null;
|
|
32680
|
+
const trimmed = input.trim();
|
|
32681
|
+
if (trimmed === "") return null;
|
|
32682
|
+
const match = trimmed.match(/^[Tt]?(\d+)(?:_.*)?$/);
|
|
32683
|
+
if (!match) return null;
|
|
32684
|
+
return `T${match[1]}`;
|
|
32685
|
+
}
|
|
32686
|
+
var init_id_generator = __esm({
|
|
32687
|
+
"src/core/tasks/id-generator.ts"() {
|
|
32688
|
+
"use strict";
|
|
32689
|
+
init_data_accessor();
|
|
32690
|
+
}
|
|
32691
|
+
});
|
|
32692
|
+
|
|
31770
32693
|
// src/dispatch/lib/security.ts
|
|
31771
32694
|
import { resolve as resolve8, normalize, relative as relative4, isAbsolute as isAbsolute2 } from "path";
|
|
31772
|
-
function sanitizeTaskId(
|
|
31773
|
-
if (typeof
|
|
32695
|
+
function sanitizeTaskId(value) {
|
|
32696
|
+
if (typeof value !== "string") {
|
|
31774
32697
|
throw new SecurityError(
|
|
31775
32698
|
"Task ID must be a string",
|
|
31776
32699
|
"E_INVALID_TASK_ID",
|
|
31777
32700
|
"taskId"
|
|
31778
32701
|
);
|
|
31779
32702
|
}
|
|
31780
|
-
const
|
|
31781
|
-
if (
|
|
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)) {
|
|
32703
|
+
const normalized = normalizeTaskId(value);
|
|
32704
|
+
if (normalized === null) {
|
|
31789
32705
|
throw new SecurityError(
|
|
31790
|
-
`Invalid task ID format:
|
|
32706
|
+
`Invalid task ID format: ${value}`,
|
|
31791
32707
|
"E_INVALID_TASK_ID",
|
|
31792
32708
|
"taskId"
|
|
31793
32709
|
);
|
|
31794
32710
|
}
|
|
31795
|
-
const numericPart = parseInt(
|
|
32711
|
+
const numericPart = parseInt(normalized.slice(1), 10);
|
|
31796
32712
|
if (numericPart > MAX_TASK_ID_NUMBER) {
|
|
31797
32713
|
throw new SecurityError(
|
|
31798
|
-
`Task ID
|
|
32714
|
+
`Task ID exceeds maximum value: ${value}`,
|
|
31799
32715
|
"E_INVALID_TASK_ID",
|
|
31800
32716
|
"taskId"
|
|
31801
32717
|
);
|
|
31802
32718
|
}
|
|
31803
|
-
return
|
|
32719
|
+
return normalized;
|
|
31804
32720
|
}
|
|
31805
32721
|
function sanitizePath(path, projectRoot) {
|
|
31806
32722
|
if (typeof path !== "string") {
|
|
@@ -31899,14 +32815,14 @@ function sanitizeParams(params, projectRoot, context) {
|
|
|
31899
32815
|
if (value === void 0 || value === null) {
|
|
31900
32816
|
continue;
|
|
31901
32817
|
}
|
|
31902
|
-
if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId")) {
|
|
32818
|
+
if (typeof value === "string" && (key === "taskId" || key === "parent" || key === "epicId" || key === "parentId" || key === "newParentId" || key === "relatedId" || key === "targetId")) {
|
|
31903
32819
|
if (key === "parent" && value === "") {
|
|
31904
32820
|
continue;
|
|
31905
32821
|
}
|
|
31906
32822
|
sanitized[key] = sanitizeTaskId(value);
|
|
31907
32823
|
continue;
|
|
31908
32824
|
}
|
|
31909
|
-
if (key === "depends" && Array.isArray(value)) {
|
|
32825
|
+
if ((key === "depends" || key === "addDepends" || key === "removeDepends") && Array.isArray(value)) {
|
|
31910
32826
|
sanitized[key] = value.map((v) => {
|
|
31911
32827
|
if (typeof v === "string") {
|
|
31912
32828
|
return sanitizeTaskId(v);
|
|
@@ -31956,10 +32872,11 @@ function sanitizeParams(params, projectRoot, context) {
|
|
|
31956
32872
|
}
|
|
31957
32873
|
return sanitized;
|
|
31958
32874
|
}
|
|
31959
|
-
var SecurityError,
|
|
32875
|
+
var SecurityError, MAX_TASK_ID_NUMBER, CONTROL_CHAR_PATTERN, DEFAULT_MAX_CONTENT_LENGTH, ALL_VALID_STATUSES, VALID_PRIORITIES2, ARRAY_PARAMS;
|
|
31960
32876
|
var init_security = __esm({
|
|
31961
32877
|
"src/dispatch/lib/security.ts"() {
|
|
31962
32878
|
"use strict";
|
|
32879
|
+
init_id_generator();
|
|
31963
32880
|
init_schema();
|
|
31964
32881
|
init_status_registry();
|
|
31965
32882
|
SecurityError = class extends Error {
|
|
@@ -31970,7 +32887,6 @@ var init_security = __esm({
|
|
|
31970
32887
|
this.name = "SecurityError";
|
|
31971
32888
|
}
|
|
31972
32889
|
};
|
|
31973
|
-
TASK_ID_PATTERN = /^T[0-9]+$/;
|
|
31974
32890
|
MAX_TASK_ID_NUMBER = 999999;
|
|
31975
32891
|
CONTROL_CHAR_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
|
|
31976
32892
|
DEFAULT_MAX_CONTENT_LENGTH = 64 * 1024;
|
|
@@ -32069,15 +32985,15 @@ var init_field_filter = __esm({
|
|
|
32069
32985
|
});
|
|
32070
32986
|
|
|
32071
32987
|
// src/core/project-info.ts
|
|
32072
|
-
import { readFileSync as
|
|
32073
|
-
import { join as
|
|
32988
|
+
import { readFileSync as readFileSync42, existsSync as existsSync58 } from "node:fs";
|
|
32989
|
+
import { join as join57 } from "node:path";
|
|
32074
32990
|
function getProjectInfoSync(cwd) {
|
|
32075
32991
|
const projectRoot = cwd ?? process.cwd();
|
|
32076
32992
|
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
32077
|
-
const infoPath =
|
|
32078
|
-
if (!
|
|
32993
|
+
const infoPath = join57(cleoDir, "project-info.json");
|
|
32994
|
+
if (!existsSync58(infoPath)) return null;
|
|
32079
32995
|
try {
|
|
32080
|
-
const raw =
|
|
32996
|
+
const raw = readFileSync42(infoPath, "utf-8");
|
|
32081
32997
|
const data = JSON.parse(raw);
|
|
32082
32998
|
if (typeof data.projectHash !== "string" || data.projectHash.length === 0) {
|
|
32083
32999
|
return null;
|
|
@@ -32305,13 +33221,13 @@ var init_field_context = __esm({
|
|
|
32305
33221
|
});
|
|
32306
33222
|
|
|
32307
33223
|
// src/core/sessions/context-alert.ts
|
|
32308
|
-
import { existsSync as
|
|
32309
|
-
import { join as
|
|
33224
|
+
import { existsSync as existsSync59, readFileSync as readFileSync43, writeFileSync as writeFileSync9 } from "node:fs";
|
|
33225
|
+
import { join as join58 } from "node:path";
|
|
32310
33226
|
function getCurrentSessionId(cwd) {
|
|
32311
33227
|
if (process.env.CLEO_SESSION) return process.env.CLEO_SESSION;
|
|
32312
|
-
const sessionFile =
|
|
32313
|
-
if (
|
|
32314
|
-
return
|
|
33228
|
+
const sessionFile = join58(getCleoDir(cwd), ".current-session");
|
|
33229
|
+
if (existsSync59(sessionFile)) {
|
|
33230
|
+
return readFileSync43(sessionFile, "utf-8").trim() || null;
|
|
32315
33231
|
}
|
|
32316
33232
|
return null;
|
|
32317
33233
|
}
|
|
@@ -33359,7 +34275,7 @@ async function stopTask2(sessionId, cwd) {
|
|
|
33359
34275
|
}
|
|
33360
34276
|
async function workHistory(sessionId, limit = 50, cwd) {
|
|
33361
34277
|
const db = await getDb(cwd);
|
|
33362
|
-
const rows = await db.select().from(taskWorkHistory).where(eq16(taskWorkHistory.sessionId, sessionId)).orderBy(desc5(taskWorkHistory.setAt)).limit(limit).all();
|
|
34278
|
+
const rows = await db.select().from(taskWorkHistory).where(eq16(taskWorkHistory.sessionId, sessionId)).orderBy(desc5(taskWorkHistory.setAt), desc5(taskWorkHistory.id)).limit(limit).all();
|
|
33363
34279
|
return rows.map((r) => ({
|
|
33364
34280
|
taskId: r.taskId,
|
|
33365
34281
|
setAt: r.setAt,
|
|
@@ -33712,8 +34628,8 @@ __export(session_grade_exports, {
|
|
|
33712
34628
|
gradeSession: () => gradeSession,
|
|
33713
34629
|
readGrades: () => readGrades
|
|
33714
34630
|
});
|
|
33715
|
-
import { join as
|
|
33716
|
-
import { existsSync as
|
|
34631
|
+
import { join as join59 } from "node:path";
|
|
34632
|
+
import { existsSync as existsSync60 } from "node:fs";
|
|
33717
34633
|
import { readFile as readFile14, appendFile, mkdir as mkdir11 } from "node:fs/promises";
|
|
33718
34634
|
async function gradeSession(sessionId, cwd) {
|
|
33719
34635
|
const sessionEntries = await queryAudit({ sessionId });
|
|
@@ -33893,9 +34809,9 @@ function detectDuplicateCreates(entries) {
|
|
|
33893
34809
|
async function appendGradeResult(result, cwd) {
|
|
33894
34810
|
try {
|
|
33895
34811
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
33896
|
-
const metricsDir =
|
|
34812
|
+
const metricsDir = join59(cleoDir, "metrics");
|
|
33897
34813
|
await mkdir11(metricsDir, { recursive: true });
|
|
33898
|
-
const gradesPath =
|
|
34814
|
+
const gradesPath = join59(metricsDir, "GRADES.jsonl");
|
|
33899
34815
|
const line = JSON.stringify({ ...result, evaluator: "auto" }) + "\n";
|
|
33900
34816
|
await appendFile(gradesPath, line, "utf8");
|
|
33901
34817
|
} catch {
|
|
@@ -33904,8 +34820,8 @@ async function appendGradeResult(result, cwd) {
|
|
|
33904
34820
|
async function readGrades(sessionId, cwd) {
|
|
33905
34821
|
try {
|
|
33906
34822
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
33907
|
-
const gradesPath =
|
|
33908
|
-
if (!
|
|
34823
|
+
const gradesPath = join59(cleoDir, "metrics", "GRADES.jsonl");
|
|
34824
|
+
if (!existsSync60(gradesPath)) return [];
|
|
33909
34825
|
const content = await readFile14(gradesPath, "utf8");
|
|
33910
34826
|
const results = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
33911
34827
|
return sessionId ? results.filter((r) => r.sessionId === sessionId) : results;
|
|
@@ -35113,7 +36029,7 @@ function validateVariableType(name, varDef, value) {
|
|
|
35113
36029
|
if (typeof value !== "string") {
|
|
35114
36030
|
throw new Error(`Invalid variable type for "${name}": expected ${varDef.type}, got ${valueType(value)}`);
|
|
35115
36031
|
}
|
|
35116
|
-
if (!
|
|
36032
|
+
if (!TASK_ID_PATTERN.test(value)) {
|
|
35117
36033
|
throw new Error(`Invalid variable format for "${name}": expected ${varDef.type} like "T1234", got "${value}"`);
|
|
35118
36034
|
}
|
|
35119
36035
|
return;
|
|
@@ -35271,7 +36187,7 @@ function showTessera(id) {
|
|
|
35271
36187
|
ensureDefaults();
|
|
35272
36188
|
return templates.get(id) ?? null;
|
|
35273
36189
|
}
|
|
35274
|
-
var DEFAULT_TESSERA_ID,
|
|
36190
|
+
var DEFAULT_TESSERA_ID, TASK_ID_PATTERN, PLACEHOLDER_EXACT, PLACEHOLDER_GLOBAL, templates;
|
|
35275
36191
|
var init_tessera_engine = __esm({
|
|
35276
36192
|
"src/core/lifecycle/tessera-engine.ts"() {
|
|
35277
36193
|
"use strict";
|
|
@@ -35279,7 +36195,7 @@ var init_tessera_engine = __esm({
|
|
|
35279
36195
|
init_chain_validation();
|
|
35280
36196
|
init_chain_store();
|
|
35281
36197
|
DEFAULT_TESSERA_ID = "tessera-rcasd";
|
|
35282
|
-
|
|
36198
|
+
TASK_ID_PATTERN = /^T\d+$/;
|
|
35283
36199
|
PLACEHOLDER_EXACT = /^\{\{\s*([A-Za-z0-9_]+)\s*\}\}$/;
|
|
35284
36200
|
PLACEHOLDER_GLOBAL = /\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g;
|
|
35285
36201
|
templates = /* @__PURE__ */ new Map();
|
|
@@ -36142,6 +37058,7 @@ var init_phase = __esm({
|
|
|
36142
37058
|
});
|
|
36143
37059
|
|
|
36144
37060
|
// src/dispatch/domains/pipeline.ts
|
|
37061
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
36145
37062
|
var PipelineHandler;
|
|
36146
37063
|
var init_pipeline2 = __esm({
|
|
36147
37064
|
"src/dispatch/domains/pipeline.ts"() {
|
|
@@ -36152,6 +37069,7 @@ var init_pipeline2 = __esm({
|
|
|
36152
37069
|
init_data_accessor();
|
|
36153
37070
|
init_engine();
|
|
36154
37071
|
init_release_engine();
|
|
37072
|
+
init_channel();
|
|
36155
37073
|
init_phase();
|
|
36156
37074
|
init_phases();
|
|
36157
37075
|
init_pipeline_manifest_sqlite();
|
|
@@ -36237,6 +37155,7 @@ var init_pipeline2 = __esm({
|
|
|
36237
37155
|
"manifest.stats",
|
|
36238
37156
|
"release.list",
|
|
36239
37157
|
"release.show",
|
|
37158
|
+
"release.channel.show",
|
|
36240
37159
|
"phase.show",
|
|
36241
37160
|
"phase.list",
|
|
36242
37161
|
"chain.show",
|
|
@@ -36256,6 +37175,7 @@ var init_pipeline2 = __esm({
|
|
|
36256
37175
|
"release.push",
|
|
36257
37176
|
"release.gates.run",
|
|
36258
37177
|
"release.rollback",
|
|
37178
|
+
"release.cancel",
|
|
36259
37179
|
"release.ship",
|
|
36260
37180
|
"manifest.append",
|
|
36261
37181
|
"manifest.archive",
|
|
@@ -36479,6 +37399,29 @@ var init_pipeline2 = __esm({
|
|
|
36479
37399
|
const result = await releaseShow(version, this.projectRoot);
|
|
36480
37400
|
return this.wrapEngineResult(result, "query", "release.show", startTime);
|
|
36481
37401
|
}
|
|
37402
|
+
case "channel.show": {
|
|
37403
|
+
let currentBranch = "unknown";
|
|
37404
|
+
try {
|
|
37405
|
+
currentBranch = execFileSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
37406
|
+
encoding: "utf-8",
|
|
37407
|
+
stdio: "pipe",
|
|
37408
|
+
cwd: this.projectRoot
|
|
37409
|
+
}).trim();
|
|
37410
|
+
} catch {
|
|
37411
|
+
}
|
|
37412
|
+
const resolvedChannel = resolveChannelFromBranch(currentBranch);
|
|
37413
|
+
const distTag = channelToDistTag(resolvedChannel);
|
|
37414
|
+
const description = describeChannel(resolvedChannel);
|
|
37415
|
+
return this.wrapEngineResult({
|
|
37416
|
+
success: true,
|
|
37417
|
+
data: {
|
|
37418
|
+
branch: currentBranch,
|
|
37419
|
+
channel: resolvedChannel,
|
|
37420
|
+
distTag,
|
|
37421
|
+
description
|
|
37422
|
+
}
|
|
37423
|
+
}, "query", "release.channel.show", startTime);
|
|
37424
|
+
}
|
|
36482
37425
|
default:
|
|
36483
37426
|
return this.errorResponse(
|
|
36484
37427
|
"query",
|
|
@@ -36597,6 +37540,20 @@ var init_pipeline2 = __esm({
|
|
|
36597
37540
|
const result = await releaseRollback(version, reason, this.projectRoot);
|
|
36598
37541
|
return this.wrapEngineResult(result, "mutate", "release.rollback", startTime);
|
|
36599
37542
|
}
|
|
37543
|
+
case "cancel": {
|
|
37544
|
+
const version = params?.version;
|
|
37545
|
+
if (!version) {
|
|
37546
|
+
return this.errorResponse(
|
|
37547
|
+
"mutate",
|
|
37548
|
+
"release.cancel",
|
|
37549
|
+
"E_INVALID_INPUT",
|
|
37550
|
+
"version is required",
|
|
37551
|
+
startTime
|
|
37552
|
+
);
|
|
37553
|
+
}
|
|
37554
|
+
const result = await releaseCancel(version, this.projectRoot);
|
|
37555
|
+
return this.wrapEngineResult(result, "mutate", "release.cancel", startTime);
|
|
37556
|
+
}
|
|
36600
37557
|
case "ship": {
|
|
36601
37558
|
const version = params?.version;
|
|
36602
37559
|
const epicId = params?.epicId;
|
|
@@ -36611,8 +37568,9 @@ var init_pipeline2 = __esm({
|
|
|
36611
37568
|
}
|
|
36612
37569
|
const remote = params?.remote;
|
|
36613
37570
|
const dryRun = params?.dryRun;
|
|
37571
|
+
const bump = params?.bump;
|
|
36614
37572
|
const result = await releaseShip(
|
|
36615
|
-
{ version, epicId, remote, dryRun },
|
|
37573
|
+
{ version, epicId, remote, dryRun, bump },
|
|
36616
37574
|
this.projectRoot
|
|
36617
37575
|
);
|
|
36618
37576
|
return this.wrapEngineResult(result, "mutate", "release.ship", startTime);
|
|
@@ -37083,11 +38041,11 @@ var init_pipeline2 = __esm({
|
|
|
37083
38041
|
});
|
|
37084
38042
|
|
|
37085
38043
|
// src/core/issue/diagnostics.ts
|
|
37086
|
-
import { execFileSync as
|
|
38044
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
37087
38045
|
function collectDiagnostics() {
|
|
37088
38046
|
const getVersion3 = (cmd, args) => {
|
|
37089
38047
|
try {
|
|
37090
|
-
return
|
|
38048
|
+
return execFileSync10(cmd, args, {
|
|
37091
38049
|
encoding: "utf-8",
|
|
37092
38050
|
stdio: ["pipe", "pipe", "pipe"]
|
|
37093
38051
|
}).trim();
|
|
@@ -37138,7 +38096,7 @@ var init_build_config = __esm({
|
|
|
37138
38096
|
"use strict";
|
|
37139
38097
|
BUILD_CONFIG = {
|
|
37140
38098
|
"name": "@cleocode/cleo",
|
|
37141
|
-
"version": "2026.3.
|
|
38099
|
+
"version": "2026.3.18",
|
|
37142
38100
|
"description": "CLEO V2 - TypeScript task management CLI for AI coding agents",
|
|
37143
38101
|
"repository": {
|
|
37144
38102
|
"owner": "kryptobaseddev",
|
|
@@ -37147,7 +38105,7 @@ var init_build_config = __esm({
|
|
|
37147
38105
|
"url": "https://github.com/kryptobaseddev/cleo.git",
|
|
37148
38106
|
"issuesUrl": "https://github.com/kryptobaseddev/cleo/issues"
|
|
37149
38107
|
},
|
|
37150
|
-
"buildDate": "2026-03-
|
|
38108
|
+
"buildDate": "2026-03-07T20:24:31.815Z",
|
|
37151
38109
|
"templates": {
|
|
37152
38110
|
"issueTemplatesDir": "templates/issue-templates"
|
|
37153
38111
|
}
|
|
@@ -37156,8 +38114,8 @@ var init_build_config = __esm({
|
|
|
37156
38114
|
});
|
|
37157
38115
|
|
|
37158
38116
|
// src/core/issue/template-parser.ts
|
|
37159
|
-
import { existsSync as
|
|
37160
|
-
import { join as
|
|
38117
|
+
import { existsSync as existsSync61, readFileSync as readFileSync44, readdirSync as readdirSync17, writeFileSync as writeFileSync10 } from "node:fs";
|
|
38118
|
+
import { join as join60, basename as basename10 } from "node:path";
|
|
37161
38119
|
function extractYamlField(content, field) {
|
|
37162
38120
|
const regex = new RegExp(`^${field}:\\s*["']?(.+?)["']?\\s*$`, "m");
|
|
37163
38121
|
const match = content.match(regex);
|
|
@@ -37189,9 +38147,9 @@ function extractYamlArray(content, field) {
|
|
|
37189
38147
|
return items;
|
|
37190
38148
|
}
|
|
37191
38149
|
function parseTemplateFile2(filePath) {
|
|
37192
|
-
if (!
|
|
38150
|
+
if (!existsSync61(filePath)) return null;
|
|
37193
38151
|
try {
|
|
37194
|
-
const content =
|
|
38152
|
+
const content = readFileSync44(filePath, "utf-8");
|
|
37195
38153
|
const fileName = basename10(filePath);
|
|
37196
38154
|
const stem = fileName.replace(/\.ya?ml$/, "");
|
|
37197
38155
|
const name = extractYamlField(content, "name");
|
|
@@ -37208,12 +38166,12 @@ function parseTemplateFile2(filePath) {
|
|
|
37208
38166
|
function parseIssueTemplates2(projectDir) {
|
|
37209
38167
|
try {
|
|
37210
38168
|
const packageRoot = getPackageRoot();
|
|
37211
|
-
const packagedTemplateDir =
|
|
37212
|
-
if (
|
|
38169
|
+
const packagedTemplateDir = join60(packageRoot, PACKAGED_TEMPLATE_DIR);
|
|
38170
|
+
if (existsSync61(packagedTemplateDir)) {
|
|
37213
38171
|
const templates3 = [];
|
|
37214
38172
|
for (const file of readdirSync17(packagedTemplateDir)) {
|
|
37215
38173
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
37216
|
-
const template = parseTemplateFile2(
|
|
38174
|
+
const template = parseTemplateFile2(join60(packagedTemplateDir, file));
|
|
37217
38175
|
if (template) templates3.push(template);
|
|
37218
38176
|
}
|
|
37219
38177
|
if (templates3.length > 0) return templates3;
|
|
@@ -37221,12 +38179,12 @@ function parseIssueTemplates2(projectDir) {
|
|
|
37221
38179
|
} catch {
|
|
37222
38180
|
}
|
|
37223
38181
|
const dir = projectDir ?? getProjectRoot();
|
|
37224
|
-
const templateDir =
|
|
37225
|
-
if (!
|
|
38182
|
+
const templateDir = join60(dir, TEMPLATE_DIR);
|
|
38183
|
+
if (!existsSync61(templateDir)) return [];
|
|
37226
38184
|
const templates2 = [];
|
|
37227
38185
|
for (const file of readdirSync17(templateDir)) {
|
|
37228
38186
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
37229
|
-
const template = parseTemplateFile2(
|
|
38187
|
+
const template = parseTemplateFile2(join60(templateDir, file));
|
|
37230
38188
|
if (template) templates2.push(template);
|
|
37231
38189
|
}
|
|
37232
38190
|
return templates2;
|
|
@@ -37235,10 +38193,10 @@ function getTemplateConfig(cwd) {
|
|
|
37235
38193
|
const projectDir = cwd ?? getProjectRoot();
|
|
37236
38194
|
const liveTemplates = parseIssueTemplates2(projectDir);
|
|
37237
38195
|
if (liveTemplates.length > 0) return liveTemplates;
|
|
37238
|
-
const cachePath =
|
|
37239
|
-
if (
|
|
38196
|
+
const cachePath = join60(getCleoDir(cwd), CACHE_FILE);
|
|
38197
|
+
if (existsSync61(cachePath)) {
|
|
37240
38198
|
try {
|
|
37241
|
-
const cached = JSON.parse(
|
|
38199
|
+
const cached = JSON.parse(readFileSync44(cachePath, "utf-8"));
|
|
37242
38200
|
if (cached.templates?.length > 0) return cached.templates;
|
|
37243
38201
|
} catch {
|
|
37244
38202
|
}
|
|
@@ -37294,7 +38252,7 @@ var init_template_parser2 = __esm({
|
|
|
37294
38252
|
});
|
|
37295
38253
|
|
|
37296
38254
|
// src/core/issue/create.ts
|
|
37297
|
-
import { execFileSync as
|
|
38255
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
37298
38256
|
function buildIssueBody(subcommand, rawBody, severity, area) {
|
|
37299
38257
|
const template = getTemplateForSubcommand2(subcommand);
|
|
37300
38258
|
const sectionLabel = template?.name ?? "Description";
|
|
@@ -37313,7 +38271,7 @@ function buildIssueBody(subcommand, rawBody, severity, area) {
|
|
|
37313
38271
|
}
|
|
37314
38272
|
function checkGhCli() {
|
|
37315
38273
|
try {
|
|
37316
|
-
|
|
38274
|
+
execFileSync11("gh", ["--version"], {
|
|
37317
38275
|
encoding: "utf-8",
|
|
37318
38276
|
stdio: ["pipe", "pipe", "pipe"]
|
|
37319
38277
|
});
|
|
@@ -37323,7 +38281,7 @@ function checkGhCli() {
|
|
|
37323
38281
|
});
|
|
37324
38282
|
}
|
|
37325
38283
|
try {
|
|
37326
|
-
|
|
38284
|
+
execFileSync11("gh", ["auth", "status", "--hostname", "github.com"], {
|
|
37327
38285
|
encoding: "utf-8",
|
|
37328
38286
|
stdio: ["pipe", "pipe", "pipe"]
|
|
37329
38287
|
});
|
|
@@ -37335,7 +38293,7 @@ function checkGhCli() {
|
|
|
37335
38293
|
}
|
|
37336
38294
|
function addGhIssue(title, body, labels) {
|
|
37337
38295
|
try {
|
|
37338
|
-
const result =
|
|
38296
|
+
const result = execFileSync11("gh", [
|
|
37339
38297
|
"issue",
|
|
37340
38298
|
"create",
|
|
37341
38299
|
"--repo",
|
|
@@ -38297,8 +39255,8 @@ var init_tools = __esm({
|
|
|
38297
39255
|
});
|
|
38298
39256
|
|
|
38299
39257
|
// src/core/nexus/query.ts
|
|
38300
|
-
import { join as
|
|
38301
|
-
import { existsSync as
|
|
39258
|
+
import { join as join61, basename as basename11 } from "node:path";
|
|
39259
|
+
import { existsSync as existsSync62, readFileSync as readFileSync45 } from "node:fs";
|
|
38302
39260
|
import { z as z3 } from "zod";
|
|
38303
39261
|
function validateSyntax(query) {
|
|
38304
39262
|
if (!query) return false;
|
|
@@ -38334,9 +39292,9 @@ function getCurrentProject() {
|
|
|
38334
39292
|
return process.env["NEXUS_CURRENT_PROJECT"];
|
|
38335
39293
|
}
|
|
38336
39294
|
try {
|
|
38337
|
-
const infoPath =
|
|
38338
|
-
if (
|
|
38339
|
-
const data = JSON.parse(
|
|
39295
|
+
const infoPath = join61(process.cwd(), ".cleo", "project-info.json");
|
|
39296
|
+
if (existsSync62(infoPath)) {
|
|
39297
|
+
const data = JSON.parse(readFileSync45(infoPath, "utf-8"));
|
|
38340
39298
|
if (typeof data.name === "string" && data.name.length > 0) {
|
|
38341
39299
|
return data.name;
|
|
38342
39300
|
}
|
|
@@ -38372,7 +39330,7 @@ async function resolveProjectPath2(projectName) {
|
|
|
38372
39330
|
return project.path;
|
|
38373
39331
|
}
|
|
38374
39332
|
async function readProjectTasks(projectPath) {
|
|
38375
|
-
const tasksDbPath =
|
|
39333
|
+
const tasksDbPath = join61(projectPath, ".cleo", "tasks.db");
|
|
38376
39334
|
try {
|
|
38377
39335
|
const accessor = await getAccessor(projectPath);
|
|
38378
39336
|
const taskFile = await accessor.loadTaskFile();
|
|
@@ -38786,8 +39744,8 @@ var init_deps2 = __esm({
|
|
|
38786
39744
|
|
|
38787
39745
|
// src/core/nexus/sharing/index.ts
|
|
38788
39746
|
import { readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
|
|
38789
|
-
import { existsSync as
|
|
38790
|
-
import { join as
|
|
39747
|
+
import { existsSync as existsSync63, readdirSync as readdirSync18, statSync as statSync9 } from "node:fs";
|
|
39748
|
+
import { join as join62, relative as relative5 } from "node:path";
|
|
38791
39749
|
function matchesPattern(filePath, pattern) {
|
|
38792
39750
|
const normalizedPath = filePath.replace(/^\/+|\/+$/g, "");
|
|
38793
39751
|
const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
|
|
@@ -38812,7 +39770,7 @@ function collectCleoFiles(cleoDir) {
|
|
|
38812
39770
|
const entries = readdirSync18(dir);
|
|
38813
39771
|
for (const entry of entries) {
|
|
38814
39772
|
if (entry === ".git") continue;
|
|
38815
|
-
const fullPath =
|
|
39773
|
+
const fullPath = join62(dir, entry);
|
|
38816
39774
|
const relPath = relative5(cleoDir, fullPath);
|
|
38817
39775
|
try {
|
|
38818
39776
|
const stat3 = statSync9(fullPath);
|
|
@@ -38872,7 +39830,7 @@ function generateGitignoreEntries(sharing) {
|
|
|
38872
39830
|
async function syncGitignore(cwd) {
|
|
38873
39831
|
const config = await loadConfig2(cwd);
|
|
38874
39832
|
const projectRoot = getProjectRoot(cwd);
|
|
38875
|
-
const gitignorePath =
|
|
39833
|
+
const gitignorePath = join62(projectRoot, ".gitignore");
|
|
38876
39834
|
const entries = generateGitignoreEntries(config.sharing);
|
|
38877
39835
|
const managedSection = [
|
|
38878
39836
|
"",
|
|
@@ -38882,7 +39840,7 @@ async function syncGitignore(cwd) {
|
|
|
38882
39840
|
""
|
|
38883
39841
|
].join("\n");
|
|
38884
39842
|
let content = "";
|
|
38885
|
-
if (
|
|
39843
|
+
if (existsSync63(gitignorePath)) {
|
|
38886
39844
|
content = await readFile15(gitignorePath, "utf-8");
|
|
38887
39845
|
}
|
|
38888
39846
|
const startIdx = content.indexOf(GITIGNORE_START);
|
|
@@ -42075,7 +43033,22 @@ async function validateLayer1Schema(context) {
|
|
|
42075
43033
|
}
|
|
42076
43034
|
if (context.params?.status) {
|
|
42077
43035
|
const status = context.params.status;
|
|
42078
|
-
|
|
43036
|
+
const validStatuses = (() => {
|
|
43037
|
+
if (context.domain === "pipeline" && context.operation === "stage.record") {
|
|
43038
|
+
return LIFECYCLE_STAGE_STATUSES;
|
|
43039
|
+
}
|
|
43040
|
+
if (context.domain === "admin" && context.operation?.startsWith("adr.")) {
|
|
43041
|
+
return ADR_STATUSES;
|
|
43042
|
+
}
|
|
43043
|
+
if (context.domain === "session") {
|
|
43044
|
+
return SESSION_STATUSES;
|
|
43045
|
+
}
|
|
43046
|
+
if (context.domain === "pipeline" && context.operation?.startsWith("manifest.")) {
|
|
43047
|
+
return MANIFEST_STATUSES;
|
|
43048
|
+
}
|
|
43049
|
+
return TASK_STATUSES;
|
|
43050
|
+
})();
|
|
43051
|
+
if (!validStatuses.includes(status)) {
|
|
42079
43052
|
violations.push({
|
|
42080
43053
|
layer: 1 /* SCHEMA */,
|
|
42081
43054
|
severity: "error" /* ERROR */,
|
|
@@ -42083,8 +43056,8 @@ async function validateLayer1Schema(context) {
|
|
|
42083
43056
|
message: `Invalid status: ${status}`,
|
|
42084
43057
|
field: "status",
|
|
42085
43058
|
value: status,
|
|
42086
|
-
constraint: `Must be one of: ${
|
|
42087
|
-
fix: `Use one of: ${
|
|
43059
|
+
constraint: `Must be one of: ${validStatuses.join(", ")}`,
|
|
43060
|
+
fix: `Use one of: ${validStatuses.join(", ")}`
|
|
42088
43061
|
});
|
|
42089
43062
|
}
|
|
42090
43063
|
}
|
|
@@ -42985,7 +43958,7 @@ init_logger();
|
|
|
42985
43958
|
init_project_info();
|
|
42986
43959
|
init_scaffold();
|
|
42987
43960
|
init_audit_prune();
|
|
42988
|
-
import { join as
|
|
43961
|
+
import { join as join63 } from "node:path";
|
|
42989
43962
|
var serverState = null;
|
|
42990
43963
|
var startupLog = getLogger("mcp:startup");
|
|
42991
43964
|
async function main() {
|
|
@@ -43055,7 +44028,7 @@ async function main() {
|
|
|
43055
44028
|
}
|
|
43056
44029
|
const config = loadConfig();
|
|
43057
44030
|
const projectInfo = getProjectInfoSync();
|
|
43058
|
-
const cleoDir =
|
|
44031
|
+
const cleoDir = join63(process.cwd(), ".cleo");
|
|
43059
44032
|
initLogger(cleoDir, {
|
|
43060
44033
|
level: config.logLevel ?? "info",
|
|
43061
44034
|
filePath: "logs/cleo.log",
|