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