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