@cleocode/core 2026.3.44 → 2026.3.45
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/admin/import-tasks.d.ts +10 -2
- package/dist/admin/import-tasks.d.ts.map +1 -1
- package/dist/index.js +415 -132
- package/dist/index.js.map +4 -4
- package/dist/internal.d.ts +3 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/nexus/index.d.ts +2 -0
- package/dist/nexus/index.d.ts.map +1 -1
- package/dist/nexus/transfer-types.d.ts +123 -0
- package/dist/nexus/transfer-types.d.ts.map +1 -0
- package/dist/nexus/transfer.d.ts +31 -0
- package/dist/nexus/transfer.d.ts.map +1 -0
- package/dist/store/brain-sqlite.d.ts +4 -1
- package/dist/store/brain-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.d.ts +4 -1
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.d.ts +4 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/tasks-schema.d.ts +3 -3
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/validation-schemas.d.ts +5 -4
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/admin/import-tasks.ts +53 -29
- package/src/internal.ts +7 -1
- package/src/nexus/__tests__/transfer.test.ts +446 -0
- package/src/nexus/index.ts +14 -0
- package/src/nexus/transfer-types.ts +129 -0
- package/src/nexus/transfer.ts +314 -0
- package/src/store/brain-sqlite.ts +7 -3
- package/src/store/nexus-sqlite.ts +7 -3
- package/src/store/sqlite.ts +9 -3
- package/src/store/tasks-schema.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -9122,7 +9122,7 @@ var init_tasks_schema = __esm({
|
|
|
9122
9122
|
"supersedes"
|
|
9123
9123
|
];
|
|
9124
9124
|
LIFECYCLE_TRANSITION_TYPES = ["automatic", "manual", "forced"];
|
|
9125
|
-
EXTERNAL_LINK_TYPES = ["created", "matched", "manual"];
|
|
9125
|
+
EXTERNAL_LINK_TYPES = ["created", "matched", "manual", "transferred"];
|
|
9126
9126
|
SYNC_DIRECTIONS = ["inbound", "outbound", "bidirectional"];
|
|
9127
9127
|
tasks = sqliteTable(
|
|
9128
9128
|
"tasks",
|
|
@@ -10579,7 +10579,9 @@ function getBrainDbPath(cwd) {
|
|
|
10579
10579
|
function resolveBrainMigrationsFolder() {
|
|
10580
10580
|
const __filename = fileURLToPath(import.meta.url);
|
|
10581
10581
|
const __dirname = dirname3(__filename);
|
|
10582
|
-
|
|
10582
|
+
const isBundled = __dirname.endsWith("/dist") || __dirname.endsWith("\\dist");
|
|
10583
|
+
const pkgRoot = isBundled ? join7(__dirname, "..") : join7(__dirname, "..", "..");
|
|
10584
|
+
return join7(pkgRoot, "migrations", "drizzle-brain");
|
|
10583
10585
|
}
|
|
10584
10586
|
function tableExists(nativeDb, tableName) {
|
|
10585
10587
|
const result = nativeDb.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
|
|
@@ -10823,7 +10825,9 @@ function getNexusDbPath() {
|
|
|
10823
10825
|
function resolveNexusMigrationsFolder() {
|
|
10824
10826
|
const __filename = fileURLToPath2(import.meta.url);
|
|
10825
10827
|
const __dirname = dirname4(__filename);
|
|
10826
|
-
|
|
10828
|
+
const isBundled = __dirname.endsWith("/dist") || __dirname.endsWith("\\dist");
|
|
10829
|
+
const pkgRoot = isBundled ? join8(__dirname, "..") : join8(__dirname, "..", "..");
|
|
10830
|
+
return join8(pkgRoot, "migrations", "drizzle-nexus");
|
|
10827
10831
|
}
|
|
10828
10832
|
function tableExists2(nativeDb, tableName) {
|
|
10829
10833
|
const result = nativeDb.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
|
|
@@ -11014,7 +11018,7 @@ function getDbPath(cwd) {
|
|
|
11014
11018
|
return join9(getCleoDirAbsolute(cwd), DB_FILENAME3);
|
|
11015
11019
|
}
|
|
11016
11020
|
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
11017
|
-
const
|
|
11021
|
+
const log8 = getLogger("sqlite");
|
|
11018
11022
|
try {
|
|
11019
11023
|
const countResult = nativeDb.prepare("SELECT COUNT(*) as cnt FROM tasks").get();
|
|
11020
11024
|
const taskCount = countResult?.cnt ?? 0;
|
|
@@ -11035,7 +11039,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
11035
11039
|
if (backupTaskCount < MIN_BACKUP_TASK_COUNT) {
|
|
11036
11040
|
return;
|
|
11037
11041
|
}
|
|
11038
|
-
|
|
11042
|
+
log8.warn(
|
|
11039
11043
|
{ dbPath, backupPath: newestBackup.path, backupTasks: backupTaskCount },
|
|
11040
11044
|
`Empty database detected with ${backupTaskCount}-task backup available. Auto-recovering from backup. This likely happened because git-tracked WAL/SHM files were overwritten during a branch switch (T5188).`
|
|
11041
11045
|
);
|
|
@@ -11053,7 +11057,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
11053
11057
|
const tempPath = dbPath + ".recovery-tmp";
|
|
11054
11058
|
copyFileSync3(newestBackup.path, tempPath);
|
|
11055
11059
|
renameSync(tempPath, dbPath);
|
|
11056
|
-
|
|
11060
|
+
log8.info(
|
|
11057
11061
|
{ dbPath, backupPath: newestBackup.path, restoredTasks: backupTaskCount },
|
|
11058
11062
|
"Database auto-recovered from backup successfully."
|
|
11059
11063
|
);
|
|
@@ -11063,7 +11067,7 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
11063
11067
|
runMigrations(restoredNativeDb, restoredDb);
|
|
11064
11068
|
_db2 = restoredDb;
|
|
11065
11069
|
} catch (err) {
|
|
11066
|
-
|
|
11070
|
+
log8.error({ err, dbPath }, "Auto-recovery from backup failed. Continuing with empty database.");
|
|
11067
11071
|
}
|
|
11068
11072
|
}
|
|
11069
11073
|
async function getDb(cwd) {
|
|
@@ -11094,7 +11098,7 @@ async function getDb(cwd) {
|
|
|
11094
11098
|
const { execFileSync: execFileSync11 } = await import("node:child_process");
|
|
11095
11099
|
const gitCwd = resolve3(dbPath, "..", "..");
|
|
11096
11100
|
const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
|
|
11097
|
-
const
|
|
11101
|
+
const log8 = getLogger("sqlite");
|
|
11098
11102
|
for (const fileToCheck of filesToCheck) {
|
|
11099
11103
|
try {
|
|
11100
11104
|
execFileSync11("git", ["ls-files", "--error-unmatch", fileToCheck], {
|
|
@@ -11102,7 +11106,7 @@ async function getDb(cwd) {
|
|
|
11102
11106
|
stdio: "pipe"
|
|
11103
11107
|
});
|
|
11104
11108
|
const basename18 = fileToCheck.split(/[\\/]/).pop();
|
|
11105
|
-
|
|
11109
|
+
log8.warn(
|
|
11106
11110
|
{ path: fileToCheck },
|
|
11107
11111
|
`${basename18} is tracked by project git \u2014 this risks data loss on branch switch. Run: git rm --cached ${fileToCheck.replace(gitCwd + sep, "")} (see ADR-013, T5188)`
|
|
11108
11112
|
);
|
|
@@ -11124,7 +11128,9 @@ async function getDb(cwd) {
|
|
|
11124
11128
|
function resolveMigrationsFolder() {
|
|
11125
11129
|
const __filename = fileURLToPath3(import.meta.url);
|
|
11126
11130
|
const __dirname = dirname5(__filename);
|
|
11127
|
-
|
|
11131
|
+
const isBundled = __dirname.endsWith("/dist") || __dirname.endsWith("\\dist");
|
|
11132
|
+
const pkgRoot = isBundled ? join9(__dirname, "..") : join9(__dirname, "..", "..");
|
|
11133
|
+
return join9(pkgRoot, "migrations", "drizzle-tasks");
|
|
11128
11134
|
}
|
|
11129
11135
|
function tableExists3(nativeDb, tableName) {
|
|
11130
11136
|
const result = nativeDb.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
|
|
@@ -24246,6 +24252,37 @@ function buildExportPackage(tasks2, options) {
|
|
|
24246
24252
|
relationshipGraph
|
|
24247
24253
|
};
|
|
24248
24254
|
}
|
|
24255
|
+
function exportSingle(taskId, allTasks, projectName) {
|
|
24256
|
+
const task = allTasks.find((t) => t.id === taskId);
|
|
24257
|
+
if (!task) return null;
|
|
24258
|
+
return buildExportPackage([task], {
|
|
24259
|
+
mode: "single",
|
|
24260
|
+
rootTaskIds: [taskId],
|
|
24261
|
+
includeChildren: false,
|
|
24262
|
+
projectName
|
|
24263
|
+
});
|
|
24264
|
+
}
|
|
24265
|
+
function exportSubtree(rootId, allTasks, projectName) {
|
|
24266
|
+
const root = allTasks.find((t) => t.id === rootId);
|
|
24267
|
+
if (!root) return null;
|
|
24268
|
+
const collected = /* @__PURE__ */ new Map();
|
|
24269
|
+
const queue = [rootId];
|
|
24270
|
+
while (queue.length > 0) {
|
|
24271
|
+
const id = queue.shift();
|
|
24272
|
+
const task = allTasks.find((t) => t.id === id);
|
|
24273
|
+
if (!task || collected.has(id)) continue;
|
|
24274
|
+
collected.set(id, task);
|
|
24275
|
+
const children = allTasks.filter((t) => t.parentId === id);
|
|
24276
|
+
queue.push(...children.map((c) => c.id));
|
|
24277
|
+
}
|
|
24278
|
+
const tasks2 = [...collected.values()];
|
|
24279
|
+
return buildExportPackage(tasks2, {
|
|
24280
|
+
mode: "subtree",
|
|
24281
|
+
rootTaskIds: [rootId],
|
|
24282
|
+
includeChildren: true,
|
|
24283
|
+
projectName
|
|
24284
|
+
});
|
|
24285
|
+
}
|
|
24249
24286
|
|
|
24250
24287
|
// packages/core/src/admin/export-tasks.ts
|
|
24251
24288
|
function parseFilter(filter) {
|
|
@@ -24678,20 +24715,7 @@ function topologicalSort(tasks2) {
|
|
|
24678
24715
|
}
|
|
24679
24716
|
return sorted;
|
|
24680
24717
|
}
|
|
24681
|
-
async function
|
|
24682
|
-
const { file: file2 } = params;
|
|
24683
|
-
try {
|
|
24684
|
-
await access2(file2, fsConstants2.R_OK);
|
|
24685
|
-
} catch {
|
|
24686
|
-
throw new Error(`Export file not found: ${file2}`);
|
|
24687
|
-
}
|
|
24688
|
-
const content = await readFile4(file2, "utf-8");
|
|
24689
|
-
let exportPkg;
|
|
24690
|
-
try {
|
|
24691
|
-
exportPkg = JSON.parse(content);
|
|
24692
|
-
} catch {
|
|
24693
|
-
throw new Error(`Invalid JSON in: ${file2}`);
|
|
24694
|
-
}
|
|
24718
|
+
async function importFromPackage(exportPkg, options = {}) {
|
|
24695
24719
|
if (exportPkg._meta?.format !== "cleo-export") {
|
|
24696
24720
|
throw new Error(
|
|
24697
24721
|
`Invalid export format (expected 'cleo-export', got '${exportPkg._meta?.format}')`
|
|
@@ -24700,16 +24724,16 @@ async function importTasksPackage(params) {
|
|
|
24700
24724
|
if (!exportPkg.tasks?.length) {
|
|
24701
24725
|
throw new Error("Export package contains no tasks");
|
|
24702
24726
|
}
|
|
24703
|
-
const accessor = await getAccessor(
|
|
24727
|
+
const accessor = await getAccessor(options.cwd);
|
|
24704
24728
|
const { tasks: existingTasks } = await accessor.queryTasks({});
|
|
24705
|
-
const onConflict =
|
|
24706
|
-
const onMissingDep =
|
|
24707
|
-
const force =
|
|
24708
|
-
const parentId =
|
|
24709
|
-
const phaseOverride =
|
|
24710
|
-
const addLabel =
|
|
24711
|
-
const resetStatus =
|
|
24712
|
-
const addProvenance =
|
|
24729
|
+
const onConflict = options.onConflict ?? "fail";
|
|
24730
|
+
const onMissingDep = options.onMissingDep === "fail" ? "fail" : "strip";
|
|
24731
|
+
const force = options.force ?? false;
|
|
24732
|
+
const parentId = options.parent;
|
|
24733
|
+
const phaseOverride = options.phase;
|
|
24734
|
+
const addLabel = options.addLabel;
|
|
24735
|
+
const resetStatus = options.resetStatus;
|
|
24736
|
+
const addProvenance = options.provenance !== false;
|
|
24713
24737
|
if (parentId) {
|
|
24714
24738
|
const parentExists = existingTasks.some((t) => t.id === parentId);
|
|
24715
24739
|
if (!parentExists) {
|
|
@@ -24778,7 +24802,7 @@ async function importTasksPackage(params) {
|
|
|
24778
24802
|
existingTitles.add(remapped.title.toLowerCase());
|
|
24779
24803
|
existingIds.add(remapped.id);
|
|
24780
24804
|
}
|
|
24781
|
-
if (
|
|
24805
|
+
if (options.dryRun) {
|
|
24782
24806
|
return {
|
|
24783
24807
|
imported: transformed.length,
|
|
24784
24808
|
skipped: skipped.length,
|
|
@@ -24798,6 +24822,33 @@ async function importTasksPackage(params) {
|
|
|
24798
24822
|
idRemap: idRemapJson
|
|
24799
24823
|
};
|
|
24800
24824
|
}
|
|
24825
|
+
async function importTasksPackage(params) {
|
|
24826
|
+
const { file: file2 } = params;
|
|
24827
|
+
try {
|
|
24828
|
+
await access2(file2, fsConstants2.R_OK);
|
|
24829
|
+
} catch {
|
|
24830
|
+
throw new Error(`Export file not found: ${file2}`);
|
|
24831
|
+
}
|
|
24832
|
+
const content = await readFile4(file2, "utf-8");
|
|
24833
|
+
let exportPkg;
|
|
24834
|
+
try {
|
|
24835
|
+
exportPkg = JSON.parse(content);
|
|
24836
|
+
} catch {
|
|
24837
|
+
throw new Error(`Invalid JSON in: ${file2}`);
|
|
24838
|
+
}
|
|
24839
|
+
return importFromPackage(exportPkg, {
|
|
24840
|
+
cwd: params.cwd,
|
|
24841
|
+
dryRun: params.dryRun,
|
|
24842
|
+
parent: params.parent,
|
|
24843
|
+
phase: params.phase,
|
|
24844
|
+
addLabel: params.addLabel,
|
|
24845
|
+
provenance: params.provenance,
|
|
24846
|
+
resetStatus: params.resetStatus,
|
|
24847
|
+
onConflict: params.onConflict,
|
|
24848
|
+
onMissingDep: params.onMissingDep === "placeholder" ? "strip" : params.onMissingDep,
|
|
24849
|
+
force: params.force
|
|
24850
|
+
});
|
|
24851
|
+
}
|
|
24801
24852
|
|
|
24802
24853
|
// packages/core/src/adrs/index.ts
|
|
24803
24854
|
var adrs_exports = {};
|
|
@@ -43851,6 +43902,7 @@ __export(nexus_exports, {
|
|
|
43851
43902
|
checkPermissionDetail: () => checkPermissionDetail,
|
|
43852
43903
|
criticalPath: () => criticalPath,
|
|
43853
43904
|
discoverRelated: () => discoverRelated,
|
|
43905
|
+
executeTransfer: () => executeTransfer,
|
|
43854
43906
|
extractKeywords: () => extractKeywords2,
|
|
43855
43907
|
generateProjectHash: () => generateProjectHash,
|
|
43856
43908
|
getCurrentProject: () => getCurrentProject,
|
|
@@ -43874,6 +43926,7 @@ __export(nexus_exports, {
|
|
|
43874
43926
|
orphanDetection: () => orphanDetection,
|
|
43875
43927
|
parseQuery: () => parseQuery,
|
|
43876
43928
|
permissionLevel: () => permissionLevel,
|
|
43929
|
+
previewTransfer: () => previewTransfer,
|
|
43877
43930
|
readRegistry: () => readRegistry,
|
|
43878
43931
|
readRegistryRequired: () => readRegistryRequired,
|
|
43879
43932
|
requirePermission: () => requirePermission,
|
|
@@ -44751,6 +44804,325 @@ async function syncGitignore(cwd) {
|
|
|
44751
44804
|
return { updated: true, entriesCount: entries.length };
|
|
44752
44805
|
}
|
|
44753
44806
|
|
|
44807
|
+
// packages/core/src/nexus/transfer.ts
|
|
44808
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
44809
|
+
init_logger();
|
|
44810
|
+
|
|
44811
|
+
// packages/core/src/reconciliation/link-store.ts
|
|
44812
|
+
init_sqlite2();
|
|
44813
|
+
init_tasks_schema();
|
|
44814
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
44815
|
+
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
44816
|
+
async function getLinksByProvider(providerId, cwd) {
|
|
44817
|
+
const db = await getDb(cwd);
|
|
44818
|
+
const rows = await db.select().from(externalTaskLinks).where(eq8(externalTaskLinks.providerId, providerId));
|
|
44819
|
+
return rows.map(rowToLink);
|
|
44820
|
+
}
|
|
44821
|
+
async function getLinkByExternalId(providerId, externalId, cwd) {
|
|
44822
|
+
const db = await getDb(cwd);
|
|
44823
|
+
const rows = await db.select().from(externalTaskLinks).where(
|
|
44824
|
+
and5(
|
|
44825
|
+
eq8(externalTaskLinks.providerId, providerId),
|
|
44826
|
+
eq8(externalTaskLinks.externalId, externalId)
|
|
44827
|
+
)
|
|
44828
|
+
);
|
|
44829
|
+
return rows.length > 0 ? rowToLink(rows[0]) : null;
|
|
44830
|
+
}
|
|
44831
|
+
async function getLinksByTaskId(taskId, cwd) {
|
|
44832
|
+
const db = await getDb(cwd);
|
|
44833
|
+
const rows = await db.select().from(externalTaskLinks).where(eq8(externalTaskLinks.taskId, taskId));
|
|
44834
|
+
return rows.map(rowToLink);
|
|
44835
|
+
}
|
|
44836
|
+
async function createLink(params, cwd) {
|
|
44837
|
+
const db = await getDb(cwd);
|
|
44838
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
44839
|
+
const id = randomUUID4();
|
|
44840
|
+
await db.insert(externalTaskLinks).values({
|
|
44841
|
+
id,
|
|
44842
|
+
taskId: params.taskId,
|
|
44843
|
+
providerId: params.providerId,
|
|
44844
|
+
externalId: params.externalId,
|
|
44845
|
+
externalUrl: params.externalUrl ?? null,
|
|
44846
|
+
externalTitle: params.externalTitle ?? null,
|
|
44847
|
+
linkType: params.linkType,
|
|
44848
|
+
syncDirection: params.syncDirection ?? "inbound",
|
|
44849
|
+
metadataJson: params.metadata ? JSON.stringify(params.metadata) : "{}",
|
|
44850
|
+
linkedAt: now,
|
|
44851
|
+
lastSyncAt: now
|
|
44852
|
+
});
|
|
44853
|
+
return {
|
|
44854
|
+
id,
|
|
44855
|
+
taskId: params.taskId,
|
|
44856
|
+
providerId: params.providerId,
|
|
44857
|
+
externalId: params.externalId,
|
|
44858
|
+
externalUrl: params.externalUrl ?? null,
|
|
44859
|
+
externalTitle: params.externalTitle ?? null,
|
|
44860
|
+
linkType: params.linkType,
|
|
44861
|
+
syncDirection: params.syncDirection ?? "inbound",
|
|
44862
|
+
metadata: params.metadata,
|
|
44863
|
+
linkedAt: now,
|
|
44864
|
+
lastSyncAt: now
|
|
44865
|
+
};
|
|
44866
|
+
}
|
|
44867
|
+
async function touchLink(linkId, updates, cwd) {
|
|
44868
|
+
const db = await getDb(cwd);
|
|
44869
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
44870
|
+
const values = { lastSyncAt: now };
|
|
44871
|
+
if (updates?.externalTitle !== void 0) {
|
|
44872
|
+
values.externalTitle = updates.externalTitle;
|
|
44873
|
+
}
|
|
44874
|
+
if (updates?.metadata !== void 0) {
|
|
44875
|
+
values.metadataJson = JSON.stringify(updates.metadata);
|
|
44876
|
+
}
|
|
44877
|
+
await db.update(externalTaskLinks).set(values).where(eq8(externalTaskLinks.id, linkId));
|
|
44878
|
+
}
|
|
44879
|
+
async function removeLinksByProvider(providerId, cwd) {
|
|
44880
|
+
const db = await getDb(cwd);
|
|
44881
|
+
const result = await db.delete(externalTaskLinks).where(eq8(externalTaskLinks.providerId, providerId));
|
|
44882
|
+
return Number(result.changes);
|
|
44883
|
+
}
|
|
44884
|
+
function rowToLink(row) {
|
|
44885
|
+
return {
|
|
44886
|
+
id: row.id,
|
|
44887
|
+
taskId: row.taskId,
|
|
44888
|
+
providerId: row.providerId,
|
|
44889
|
+
externalId: row.externalId,
|
|
44890
|
+
externalUrl: row.externalUrl,
|
|
44891
|
+
externalTitle: row.externalTitle,
|
|
44892
|
+
linkType: row.linkType,
|
|
44893
|
+
syncDirection: row.syncDirection,
|
|
44894
|
+
metadata: row.metadataJson ? JSON.parse(row.metadataJson) : void 0,
|
|
44895
|
+
linkedAt: row.linkedAt,
|
|
44896
|
+
lastSyncAt: row.lastSyncAt
|
|
44897
|
+
};
|
|
44898
|
+
}
|
|
44899
|
+
|
|
44900
|
+
// packages/core/src/nexus/transfer.ts
|
|
44901
|
+
init_brain_accessor();
|
|
44902
|
+
init_brain_sqlite();
|
|
44903
|
+
init_data_accessor();
|
|
44904
|
+
init_registry3();
|
|
44905
|
+
var log6 = getLogger("nexus:transfer");
|
|
44906
|
+
async function previewTransfer(params) {
|
|
44907
|
+
return executeTransferInternal({ ...params, dryRun: true });
|
|
44908
|
+
}
|
|
44909
|
+
async function executeTransfer(params) {
|
|
44910
|
+
return executeTransferInternal(params);
|
|
44911
|
+
}
|
|
44912
|
+
async function executeTransferInternal(params) {
|
|
44913
|
+
const {
|
|
44914
|
+
taskIds,
|
|
44915
|
+
sourceProject: sourceProjectRef,
|
|
44916
|
+
targetProject: targetProjectRef,
|
|
44917
|
+
mode = "copy",
|
|
44918
|
+
scope = "subtree",
|
|
44919
|
+
onConflict = "rename",
|
|
44920
|
+
onMissingDep = "strip",
|
|
44921
|
+
provenance = true,
|
|
44922
|
+
targetParent,
|
|
44923
|
+
transferBrain = false,
|
|
44924
|
+
dryRun = false
|
|
44925
|
+
} = params;
|
|
44926
|
+
if (!taskIds.length) {
|
|
44927
|
+
throw new Error("No task IDs specified for transfer");
|
|
44928
|
+
}
|
|
44929
|
+
const sourceProject = await nexusGetProject(sourceProjectRef);
|
|
44930
|
+
if (!sourceProject) {
|
|
44931
|
+
throw new Error(`Source project not found: ${sourceProjectRef}`);
|
|
44932
|
+
}
|
|
44933
|
+
const targetProject = await nexusGetProject(targetProjectRef);
|
|
44934
|
+
if (!targetProject) {
|
|
44935
|
+
throw new Error(`Target project not found: ${targetProjectRef}`);
|
|
44936
|
+
}
|
|
44937
|
+
if (sourceProject.hash === targetProject.hash) {
|
|
44938
|
+
throw new Error("Source and target projects must be different");
|
|
44939
|
+
}
|
|
44940
|
+
await requirePermission(sourceProject.hash, "read", "nexus.transfer");
|
|
44941
|
+
await requirePermission(targetProject.hash, "write", "nexus.transfer");
|
|
44942
|
+
const sourceAccessor = await getAccessor(sourceProject.path);
|
|
44943
|
+
const { tasks: allSourceTasks } = await sourceAccessor.queryTasks({});
|
|
44944
|
+
const exportPackages = [];
|
|
44945
|
+
for (const taskId of taskIds) {
|
|
44946
|
+
const pkg = scope === "subtree" ? exportSubtree(taskId, allSourceTasks, sourceProject.name) : exportSingle(taskId, allSourceTasks, sourceProject.name);
|
|
44947
|
+
if (!pkg) {
|
|
44948
|
+
throw new Error(`Task not found in source project: ${taskId}`);
|
|
44949
|
+
}
|
|
44950
|
+
exportPackages.push(pkg);
|
|
44951
|
+
}
|
|
44952
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
44953
|
+
const mergedTasks = [];
|
|
44954
|
+
for (const pkg of exportPackages) {
|
|
44955
|
+
for (const task of pkg.tasks) {
|
|
44956
|
+
if (!seenIds.has(task.id)) {
|
|
44957
|
+
seenIds.add(task.id);
|
|
44958
|
+
mergedTasks.push(task);
|
|
44959
|
+
}
|
|
44960
|
+
}
|
|
44961
|
+
}
|
|
44962
|
+
const mergedPkg = { ...exportPackages[0] };
|
|
44963
|
+
mergedPkg.tasks = mergedTasks;
|
|
44964
|
+
mergedPkg._meta = { ...mergedPkg._meta, taskCount: mergedTasks.length };
|
|
44965
|
+
const importResult = await importFromPackage(mergedPkg, {
|
|
44966
|
+
cwd: targetProject.path,
|
|
44967
|
+
dryRun,
|
|
44968
|
+
parent: targetParent,
|
|
44969
|
+
provenance,
|
|
44970
|
+
onConflict,
|
|
44971
|
+
onMissingDep
|
|
44972
|
+
});
|
|
44973
|
+
const entries = mergedTasks.map((t) => ({
|
|
44974
|
+
sourceId: t.id,
|
|
44975
|
+
targetId: importResult.idRemap[t.id] ?? t.id,
|
|
44976
|
+
title: t.title,
|
|
44977
|
+
type: t.type ?? "task"
|
|
44978
|
+
}));
|
|
44979
|
+
const manifest = {
|
|
44980
|
+
sourceProject: sourceProject.name,
|
|
44981
|
+
targetProject: targetProject.name,
|
|
44982
|
+
mode,
|
|
44983
|
+
scope,
|
|
44984
|
+
entries,
|
|
44985
|
+
idRemap: importResult.idRemap,
|
|
44986
|
+
brainObservationsTransferred: 0
|
|
44987
|
+
};
|
|
44988
|
+
const result = {
|
|
44989
|
+
dryRun,
|
|
44990
|
+
transferred: importResult.imported,
|
|
44991
|
+
skipped: importResult.skipped,
|
|
44992
|
+
archived: 0,
|
|
44993
|
+
linksCreated: 0,
|
|
44994
|
+
brainObservationsTransferred: 0,
|
|
44995
|
+
manifest
|
|
44996
|
+
};
|
|
44997
|
+
if (dryRun) {
|
|
44998
|
+
return result;
|
|
44999
|
+
}
|
|
45000
|
+
let linksCreated = 0;
|
|
45001
|
+
const targetAccessor = await getAccessor(targetProject.path);
|
|
45002
|
+
const { tasks: targetTasks } = await targetAccessor.queryTasks({});
|
|
45003
|
+
const targetTaskIds = new Set(targetTasks.map((t) => t.id));
|
|
45004
|
+
for (const entry of entries) {
|
|
45005
|
+
if (importResult.idRemap[entry.sourceId] && targetTaskIds.has(entry.targetId)) {
|
|
45006
|
+
await createLink(
|
|
45007
|
+
{
|
|
45008
|
+
taskId: entry.targetId,
|
|
45009
|
+
providerId: `nexus:${sourceProject.name}`,
|
|
45010
|
+
externalId: entry.sourceId,
|
|
45011
|
+
externalTitle: entry.title,
|
|
45012
|
+
linkType: "transferred",
|
|
45013
|
+
syncDirection: "inbound",
|
|
45014
|
+
metadata: {
|
|
45015
|
+
transferMode: mode,
|
|
45016
|
+
transferScope: scope,
|
|
45017
|
+
sourceProject: sourceProject.name,
|
|
45018
|
+
transferredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
45019
|
+
}
|
|
45020
|
+
},
|
|
45021
|
+
targetProject.path
|
|
45022
|
+
);
|
|
45023
|
+
linksCreated++;
|
|
45024
|
+
await createLink(
|
|
45025
|
+
{
|
|
45026
|
+
taskId: entry.sourceId,
|
|
45027
|
+
providerId: `nexus:${targetProject.name}`,
|
|
45028
|
+
externalId: entry.targetId,
|
|
45029
|
+
externalTitle: entry.title,
|
|
45030
|
+
linkType: "transferred",
|
|
45031
|
+
syncDirection: "outbound",
|
|
45032
|
+
metadata: {
|
|
45033
|
+
transferMode: mode,
|
|
45034
|
+
transferScope: scope,
|
|
45035
|
+
targetProject: targetProject.name,
|
|
45036
|
+
transferredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
45037
|
+
}
|
|
45038
|
+
},
|
|
45039
|
+
sourceProject.path
|
|
45040
|
+
);
|
|
45041
|
+
linksCreated++;
|
|
45042
|
+
}
|
|
45043
|
+
}
|
|
45044
|
+
result.linksCreated = linksCreated;
|
|
45045
|
+
try {
|
|
45046
|
+
const { getNexusDb: getNexusDb2 } = await Promise.resolve().then(() => (init_nexus_sqlite(), nexus_sqlite_exports));
|
|
45047
|
+
const { nexusAuditLog: nexusAuditLog2 } = await Promise.resolve().then(() => (init_nexus_schema(), nexus_schema_exports));
|
|
45048
|
+
const db = await getNexusDb2();
|
|
45049
|
+
await db.insert(nexusAuditLog2).values({
|
|
45050
|
+
id: randomUUID5(),
|
|
45051
|
+
action: "transfer",
|
|
45052
|
+
projectHash: sourceProject.hash,
|
|
45053
|
+
projectId: sourceProject.projectId,
|
|
45054
|
+
domain: "nexus",
|
|
45055
|
+
operation: "transfer",
|
|
45056
|
+
success: 1,
|
|
45057
|
+
detailsJson: JSON.stringify({
|
|
45058
|
+
sourceProject: sourceProject.name,
|
|
45059
|
+
targetProject: targetProject.name,
|
|
45060
|
+
mode,
|
|
45061
|
+
scope,
|
|
45062
|
+
taskCount: result.transferred,
|
|
45063
|
+
idRemap: importResult.idRemap
|
|
45064
|
+
})
|
|
45065
|
+
});
|
|
45066
|
+
} catch (err) {
|
|
45067
|
+
log6.warn({ err }, "nexus transfer audit write failed");
|
|
45068
|
+
}
|
|
45069
|
+
if (mode === "move") {
|
|
45070
|
+
let archived = 0;
|
|
45071
|
+
for (const entry of entries) {
|
|
45072
|
+
if (importResult.idRemap[entry.sourceId]) {
|
|
45073
|
+
try {
|
|
45074
|
+
await sourceAccessor.archiveSingleTask(entry.sourceId, {
|
|
45075
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45076
|
+
archiveReason: `Transferred to ${targetProject.name} as ${entry.targetId}`
|
|
45077
|
+
});
|
|
45078
|
+
archived++;
|
|
45079
|
+
} catch (err) {
|
|
45080
|
+
log6.warn({ err, taskId: entry.sourceId }, "failed to archive source task after transfer");
|
|
45081
|
+
}
|
|
45082
|
+
}
|
|
45083
|
+
}
|
|
45084
|
+
result.archived = archived;
|
|
45085
|
+
}
|
|
45086
|
+
if (transferBrain) {
|
|
45087
|
+
let brainTransferred = 0;
|
|
45088
|
+
try {
|
|
45089
|
+
const sourceBrainDb = await getBrainDb(sourceProject.path);
|
|
45090
|
+
const targetBrainDb = await getBrainDb(targetProject.path);
|
|
45091
|
+
const sourceBrain = new BrainDataAccessor(sourceBrainDb);
|
|
45092
|
+
const targetBrain = new BrainDataAccessor(targetBrainDb);
|
|
45093
|
+
for (const entry of entries) {
|
|
45094
|
+
if (!importResult.idRemap[entry.sourceId]) continue;
|
|
45095
|
+
const links = await sourceBrain.getLinksForTask(entry.sourceId);
|
|
45096
|
+
for (const link of links) {
|
|
45097
|
+
if (link.memoryType !== "observation") continue;
|
|
45098
|
+
const observation = await sourceBrain.getObservation(link.memoryId);
|
|
45099
|
+
if (!observation) continue;
|
|
45100
|
+
const newObsId = `O-${randomUUID5().slice(0, 8)}`;
|
|
45101
|
+
await targetBrain.addObservation({
|
|
45102
|
+
...observation,
|
|
45103
|
+
id: newObsId,
|
|
45104
|
+
createdAt: observation.createdAt,
|
|
45105
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
|
|
45106
|
+
});
|
|
45107
|
+
await targetBrain.addLink({
|
|
45108
|
+
memoryType: "observation",
|
|
45109
|
+
memoryId: newObsId,
|
|
45110
|
+
taskId: entry.targetId,
|
|
45111
|
+
linkType: "applies_to",
|
|
45112
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
|
|
45113
|
+
});
|
|
45114
|
+
brainTransferred++;
|
|
45115
|
+
}
|
|
45116
|
+
}
|
|
45117
|
+
} catch (err) {
|
|
45118
|
+
log6.warn({ err }, "brain observation transfer failed");
|
|
45119
|
+
}
|
|
45120
|
+
result.brainObservationsTransferred = brainTransferred;
|
|
45121
|
+
result.manifest.brainObservationsTransferred = brainTransferred;
|
|
45122
|
+
}
|
|
45123
|
+
return result;
|
|
45124
|
+
}
|
|
45125
|
+
|
|
44754
45126
|
// packages/core/src/observability/index.ts
|
|
44755
45127
|
var observability_exports = {};
|
|
44756
45128
|
__export(observability_exports, {
|
|
@@ -46149,95 +46521,6 @@ __export(reconciliation_exports, {
|
|
|
46149
46521
|
touchLink: () => touchLink
|
|
46150
46522
|
});
|
|
46151
46523
|
|
|
46152
|
-
// packages/core/src/reconciliation/link-store.ts
|
|
46153
|
-
init_sqlite2();
|
|
46154
|
-
init_tasks_schema();
|
|
46155
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
46156
|
-
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
46157
|
-
async function getLinksByProvider(providerId, cwd) {
|
|
46158
|
-
const db = await getDb(cwd);
|
|
46159
|
-
const rows = await db.select().from(externalTaskLinks).where(eq8(externalTaskLinks.providerId, providerId));
|
|
46160
|
-
return rows.map(rowToLink);
|
|
46161
|
-
}
|
|
46162
|
-
async function getLinkByExternalId(providerId, externalId, cwd) {
|
|
46163
|
-
const db = await getDb(cwd);
|
|
46164
|
-
const rows = await db.select().from(externalTaskLinks).where(
|
|
46165
|
-
and5(
|
|
46166
|
-
eq8(externalTaskLinks.providerId, providerId),
|
|
46167
|
-
eq8(externalTaskLinks.externalId, externalId)
|
|
46168
|
-
)
|
|
46169
|
-
);
|
|
46170
|
-
return rows.length > 0 ? rowToLink(rows[0]) : null;
|
|
46171
|
-
}
|
|
46172
|
-
async function getLinksByTaskId(taskId, cwd) {
|
|
46173
|
-
const db = await getDb(cwd);
|
|
46174
|
-
const rows = await db.select().from(externalTaskLinks).where(eq8(externalTaskLinks.taskId, taskId));
|
|
46175
|
-
return rows.map(rowToLink);
|
|
46176
|
-
}
|
|
46177
|
-
async function createLink(params, cwd) {
|
|
46178
|
-
const db = await getDb(cwd);
|
|
46179
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
46180
|
-
const id = randomUUID4();
|
|
46181
|
-
await db.insert(externalTaskLinks).values({
|
|
46182
|
-
id,
|
|
46183
|
-
taskId: params.taskId,
|
|
46184
|
-
providerId: params.providerId,
|
|
46185
|
-
externalId: params.externalId,
|
|
46186
|
-
externalUrl: params.externalUrl ?? null,
|
|
46187
|
-
externalTitle: params.externalTitle ?? null,
|
|
46188
|
-
linkType: params.linkType,
|
|
46189
|
-
syncDirection: params.syncDirection ?? "inbound",
|
|
46190
|
-
metadataJson: params.metadata ? JSON.stringify(params.metadata) : "{}",
|
|
46191
|
-
linkedAt: now,
|
|
46192
|
-
lastSyncAt: now
|
|
46193
|
-
});
|
|
46194
|
-
return {
|
|
46195
|
-
id,
|
|
46196
|
-
taskId: params.taskId,
|
|
46197
|
-
providerId: params.providerId,
|
|
46198
|
-
externalId: params.externalId,
|
|
46199
|
-
externalUrl: params.externalUrl ?? null,
|
|
46200
|
-
externalTitle: params.externalTitle ?? null,
|
|
46201
|
-
linkType: params.linkType,
|
|
46202
|
-
syncDirection: params.syncDirection ?? "inbound",
|
|
46203
|
-
metadata: params.metadata,
|
|
46204
|
-
linkedAt: now,
|
|
46205
|
-
lastSyncAt: now
|
|
46206
|
-
};
|
|
46207
|
-
}
|
|
46208
|
-
async function touchLink(linkId, updates, cwd) {
|
|
46209
|
-
const db = await getDb(cwd);
|
|
46210
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
46211
|
-
const values = { lastSyncAt: now };
|
|
46212
|
-
if (updates?.externalTitle !== void 0) {
|
|
46213
|
-
values.externalTitle = updates.externalTitle;
|
|
46214
|
-
}
|
|
46215
|
-
if (updates?.metadata !== void 0) {
|
|
46216
|
-
values.metadataJson = JSON.stringify(updates.metadata);
|
|
46217
|
-
}
|
|
46218
|
-
await db.update(externalTaskLinks).set(values).where(eq8(externalTaskLinks.id, linkId));
|
|
46219
|
-
}
|
|
46220
|
-
async function removeLinksByProvider(providerId, cwd) {
|
|
46221
|
-
const db = await getDb(cwd);
|
|
46222
|
-
const result = await db.delete(externalTaskLinks).where(eq8(externalTaskLinks.providerId, providerId));
|
|
46223
|
-
return Number(result.changes);
|
|
46224
|
-
}
|
|
46225
|
-
function rowToLink(row) {
|
|
46226
|
-
return {
|
|
46227
|
-
id: row.id,
|
|
46228
|
-
taskId: row.taskId,
|
|
46229
|
-
providerId: row.providerId,
|
|
46230
|
-
externalId: row.externalId,
|
|
46231
|
-
externalUrl: row.externalUrl,
|
|
46232
|
-
externalTitle: row.externalTitle,
|
|
46233
|
-
linkType: row.linkType,
|
|
46234
|
-
syncDirection: row.syncDirection,
|
|
46235
|
-
metadata: row.metadataJson ? JSON.parse(row.metadataJson) : void 0,
|
|
46236
|
-
linkedAt: row.linkedAt,
|
|
46237
|
-
lastSyncAt: row.lastSyncAt
|
|
46238
|
-
};
|
|
46239
|
-
}
|
|
46240
|
-
|
|
46241
46524
|
// packages/core/src/reconciliation/reconciliation-engine.ts
|
|
46242
46525
|
init_data_accessor();
|
|
46243
46526
|
init_add();
|
|
@@ -53945,11 +54228,11 @@ import { join as join84 } from "node:path";
|
|
|
53945
54228
|
import { Readable } from "node:stream";
|
|
53946
54229
|
import { pipeline } from "node:stream/promises";
|
|
53947
54230
|
import { createGzip } from "node:zlib";
|
|
53948
|
-
var
|
|
54231
|
+
var log7 = getLogger("prune");
|
|
53949
54232
|
async function pruneAuditLog(cleoDir, config2) {
|
|
53950
54233
|
try {
|
|
53951
54234
|
if (!config2.auditRetentionDays || config2.auditRetentionDays <= 0) {
|
|
53952
|
-
|
|
54235
|
+
log7.debug("auditRetentionDays is 0 or unset; skipping audit prune");
|
|
53953
54236
|
return { rowsArchived: 0, rowsDeleted: 0 };
|
|
53954
54237
|
}
|
|
53955
54238
|
const cutoff = new Date(Date.now() - config2.auditRetentionDays * 864e5).toISOString();
|
|
@@ -53960,7 +54243,7 @@ async function pruneAuditLog(cleoDir, config2) {
|
|
|
53960
54243
|
const db = await getDb3(projectRoot);
|
|
53961
54244
|
const oldRows = await db.select().from(auditLog2).where(lt3(auditLog2.timestamp, cutoff));
|
|
53962
54245
|
if (oldRows.length === 0) {
|
|
53963
|
-
|
|
54246
|
+
log7.debug("No audit_log rows older than cutoff; nothing to prune");
|
|
53964
54247
|
return { rowsArchived: 0, rowsDeleted: 0 };
|
|
53965
54248
|
}
|
|
53966
54249
|
let archivePath;
|
|
@@ -53978,17 +54261,17 @@ async function pruneAuditLog(cleoDir, config2) {
|
|
|
53978
54261
|
const inStream = Readable.from([jsonlContent]);
|
|
53979
54262
|
await pipeline(inStream, gzip, outStream);
|
|
53980
54263
|
rowsArchived = oldRows.length;
|
|
53981
|
-
|
|
54264
|
+
log7.info(
|
|
53982
54265
|
{ archivePath, rowsArchived },
|
|
53983
54266
|
`Archived ${rowsArchived} audit rows to ${archivePath}`
|
|
53984
54267
|
);
|
|
53985
54268
|
} catch (archiveErr) {
|
|
53986
|
-
|
|
54269
|
+
log7.warn({ err: archiveErr }, "Failed to archive audit rows; continuing with deletion");
|
|
53987
54270
|
archivePath = void 0;
|
|
53988
54271
|
}
|
|
53989
54272
|
}
|
|
53990
54273
|
await db.delete(auditLog2).where(lt3(auditLog2.timestamp, cutoff)).run();
|
|
53991
|
-
|
|
54274
|
+
log7.info(
|
|
53992
54275
|
{ rowsDeleted: oldRows.length, cutoff },
|
|
53993
54276
|
`Pruned ${oldRows.length} audit_log rows older than ${cutoff}`
|
|
53994
54277
|
);
|
|
@@ -53998,7 +54281,7 @@ async function pruneAuditLog(cleoDir, config2) {
|
|
|
53998
54281
|
archivePath
|
|
53999
54282
|
};
|
|
54000
54283
|
} catch (err) {
|
|
54001
|
-
|
|
54284
|
+
log7.warn({ err }, "audit log pruning failed");
|
|
54002
54285
|
return { rowsArchived: 0, rowsDeleted: 0 };
|
|
54003
54286
|
}
|
|
54004
54287
|
}
|
|
@@ -60816,7 +61099,7 @@ init_logger();
|
|
|
60816
61099
|
|
|
60817
61100
|
// packages/core/src/output.ts
|
|
60818
61101
|
init_errors3();
|
|
60819
|
-
import { randomUUID as
|
|
61102
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
60820
61103
|
|
|
60821
61104
|
// packages/core/src/sessions/context-alert.ts
|
|
60822
61105
|
init_paths();
|
|
@@ -60849,7 +61132,7 @@ function createCliMeta(operation, mvi = "standard") {
|
|
|
60849
61132
|
schemaVersion: "2026.2.1",
|
|
60850
61133
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
60851
61134
|
operation,
|
|
60852
|
-
requestId:
|
|
61135
|
+
requestId: randomUUID6(),
|
|
60853
61136
|
transport: "cli",
|
|
60854
61137
|
strict: true,
|
|
60855
61138
|
mvi,
|