@cleocode/cleo 2026.2.9 → 2026.3.1
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/README.md +14 -13
- package/dist/cli/index.js +2113 -1295
- package/dist/cli/index.js.map +4 -4
- package/dist/mcp/index.js +1796 -865
- package/dist/mcp/index.js.map +4 -4
- package/drizzle/20260301053344_careless_changeling/migration.sql +27 -0
- package/drizzle/20260301053344_careless_changeling/snapshot.json +2598 -0
- package/package.json +2 -2
- package/packages/ct-skills/skills/ct-cleo/SKILL.md +49 -22
- package/templates/CLEO-INJECTION.md +32 -138
- package/templates/cleo-gitignore +66 -49
- package/templates/git-hooks/pre-commit +24 -6
- package/schemas/archive/agent-configs.schema.json +0 -120
- package/schemas/archive/agent-registry.schema.json +0 -132
- package/schemas/archive/archive.schema.json +0 -450
- package/schemas/archive/claudedocs-frontmatter.schema.json +0 -162
- package/schemas/archive/commands-index.schema.json +0 -158
- package/schemas/archive/contribution.schema.json +0 -722
- package/schemas/archive/critical-path.schema.json +0 -246
- package/schemas/archive/deps-cache.schema.json +0 -97
- package/schemas/archive/doctor-output.schema.json +0 -283
- package/schemas/archive/error.schema.json +0 -161
- package/schemas/archive/export-package.schema.json +0 -375
- package/schemas/archive/global-config.schema.json +0 -219
- package/schemas/archive/log.schema.json +0 -250
- package/schemas/archive/metrics.schema.json +0 -328
- package/schemas/archive/migrations.schema.json +0 -150
- package/schemas/archive/nexus-registry.schema.json +0 -90
- package/schemas/archive/output.schema.json +0 -164
- package/schemas/archive/rcsd-consensus-report.schema.json +0 -491
- package/schemas/archive/rcsd-hitl-resolution.schema.json +0 -216
- package/schemas/archive/rcsd-index.schema.json +0 -384
- package/schemas/archive/rcsd-manifest.schema.json +0 -264
- package/schemas/archive/rcsd-research-output.schema.json +0 -564
- package/schemas/archive/rcsd-spec-frontmatter.schema.json +0 -225
- package/schemas/archive/releases.schema.json +0 -267
- package/schemas/archive/skills-manifest.schema.json +0 -91
- package/schemas/archive/skillsmp.schema.json +0 -208
- package/schemas/archive/spec-index.schema.json +0 -196
- package/schemas/archive/todo.schema.json +0 -995
- package/schemas/claudedocs-frontmatter.schema.json +0 -162
- package/schemas/commands-index.schema.json +0 -158
- package/schemas/rcsd-consensus-report.schema.json +0 -494
- package/schemas/rcsd-hitl-resolution.schema.json +0 -219
- package/schemas/rcsd-index.schema.json +0 -387
- package/schemas/rcsd-manifest.schema.json +0 -267
- package/schemas/rcsd-research-output.schema.json +0 -567
- package/schemas/rcsd-spec-frontmatter.schema.json +0 -225
- package/schemas/todo.schema.json +0 -994
- package/skills/_shared/cleo-style-guide.md +0 -84
- package/skills/_shared/manifest-operations.md +0 -810
- package/skills/_shared/placeholders.json +0 -433
- package/skills/_shared/skill-chaining-patterns.md +0 -240
- package/skills/_shared/subagent-protocol-base.md +0 -221
- package/skills/_shared/task-system-integration.md +0 -232
- package/skills/_shared/testing-framework-config.md +0 -110
- package/skills/agentskills-integrate.md +0 -104
- package/skills/agentskills-specs.md +0 -255
- package/skills/agentskills-what-are-skills.md +0 -75
- package/skills/manifest.json +0 -510
- package/templates/AGENT-INJECTION.md +0 -166
- /package/schemas/{research-manifest.schema.json → archive/research-manifest.schema.json} +0 -0
package/dist/mcp/index.js
CHANGED
|
@@ -501,6 +501,7 @@ __export(schema_exports, {
|
|
|
501
501
|
lifecyclePipelines: () => lifecyclePipelines,
|
|
502
502
|
lifecycleStages: () => lifecycleStages,
|
|
503
503
|
lifecycleTransitions: () => lifecycleTransitions,
|
|
504
|
+
manifestEntries: () => manifestEntries,
|
|
504
505
|
schemaMeta: () => schemaMeta,
|
|
505
506
|
sessions: () => sessions,
|
|
506
507
|
statusRegistryTable: () => statusRegistryTable,
|
|
@@ -517,7 +518,7 @@ import {
|
|
|
517
518
|
primaryKey
|
|
518
519
|
} from "drizzle-orm/sqlite-core";
|
|
519
520
|
import { sql } from "drizzle-orm";
|
|
520
|
-
var TASK_PRIORITIES, TASK_TYPES, TASK_SIZES, LIFECYCLE_STAGE_NAMES, LIFECYCLE_GATE_RESULTS, LIFECYCLE_EVIDENCE_TYPES, tasks, taskDependencies, taskRelations, sessions, taskWorkHistory, lifecyclePipelines, lifecycleStages, lifecycleGateResults, lifecycleEvidence, lifecycleTransitions, schemaMeta, auditLog, architectureDecisions, adrTaskLinks, adrRelations, statusRegistryTable;
|
|
521
|
+
var TASK_PRIORITIES, TASK_TYPES, TASK_SIZES, LIFECYCLE_STAGE_NAMES, LIFECYCLE_GATE_RESULTS, LIFECYCLE_EVIDENCE_TYPES, tasks, taskDependencies, taskRelations, sessions, taskWorkHistory, lifecyclePipelines, lifecycleStages, lifecycleGateResults, lifecycleEvidence, lifecycleTransitions, manifestEntries, schemaMeta, auditLog, architectureDecisions, adrTaskLinks, adrRelations, statusRegistryTable;
|
|
521
522
|
var init_schema = __esm({
|
|
522
523
|
"src/store/schema.ts"() {
|
|
523
524
|
"use strict";
|
|
@@ -613,8 +614,9 @@ var init_schema = __esm({
|
|
|
613
614
|
taskId: text("task_id").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
614
615
|
relatedTo: text("related_to").notNull().references(() => tasks.id, { onDelete: "cascade" }),
|
|
615
616
|
relationType: text("relation_type", {
|
|
616
|
-
enum: ["related", "blocks", "duplicates"]
|
|
617
|
-
}).notNull().default("related")
|
|
617
|
+
enum: ["related", "blocks", "duplicates", "absorbs", "fixes", "extends", "supersedes"]
|
|
618
|
+
}).notNull().default("related"),
|
|
619
|
+
reason: text("reason")
|
|
618
620
|
}, (table) => [
|
|
619
621
|
primaryKey({ columns: [table.taskId, table.relatedTo] })
|
|
620
622
|
]);
|
|
@@ -687,7 +689,16 @@ var init_schema = __esm({
|
|
|
687
689
|
skippedAt: text("skipped_at"),
|
|
688
690
|
skipReason: text("skip_reason"),
|
|
689
691
|
notesJson: text("notes_json").default("[]"),
|
|
690
|
-
metadataJson: text("metadata_json").default("{}")
|
|
692
|
+
metadataJson: text("metadata_json").default("{}"),
|
|
693
|
+
// RCASD provenance tracking columns (T5100)
|
|
694
|
+
outputFile: text("output_file"),
|
|
695
|
+
createdBy: text("created_by"),
|
|
696
|
+
validatedBy: text("validated_by"),
|
|
697
|
+
validatedAt: text("validated_at"),
|
|
698
|
+
validationStatus: text("validation_status", {
|
|
699
|
+
enum: ["pending", "in_review", "approved", "rejected", "needs_revision"]
|
|
700
|
+
}),
|
|
701
|
+
provenanceChainJson: text("provenance_chain_json")
|
|
691
702
|
}, (table) => [
|
|
692
703
|
index("idx_lifecycle_stages_pipeline_id").on(table.pipelineId),
|
|
693
704
|
index("idx_lifecycle_stages_stage_name").on(table.stageName),
|
|
@@ -732,6 +743,25 @@ var init_schema = __esm({
|
|
|
732
743
|
}, (table) => [
|
|
733
744
|
index("idx_lifecycle_transitions_pipeline_id").on(table.pipelineId)
|
|
734
745
|
]);
|
|
746
|
+
manifestEntries = sqliteTable("manifest_entries", {
|
|
747
|
+
id: text("id").primaryKey(),
|
|
748
|
+
pipelineId: text("pipeline_id").references(() => lifecyclePipelines.id, { onDelete: "cascade" }),
|
|
749
|
+
stageId: text("stage_id").references(() => lifecycleStages.id, { onDelete: "cascade" }),
|
|
750
|
+
title: text("title").notNull(),
|
|
751
|
+
date: text("date").notNull(),
|
|
752
|
+
status: text("status", { enum: MANIFEST_STATUSES }).notNull(),
|
|
753
|
+
agentType: text("agent_type"),
|
|
754
|
+
outputFile: text("output_file"),
|
|
755
|
+
topicsJson: text("topics_json").default("[]"),
|
|
756
|
+
findingsJson: text("findings_json").default("[]"),
|
|
757
|
+
linkedTasksJson: text("linked_tasks_json").default("[]"),
|
|
758
|
+
createdBy: text("created_by"),
|
|
759
|
+
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`)
|
|
760
|
+
}, (table) => [
|
|
761
|
+
index("idx_manifest_entries_pipeline_id").on(table.pipelineId),
|
|
762
|
+
index("idx_manifest_entries_stage_id").on(table.stageId),
|
|
763
|
+
index("idx_manifest_entries_status").on(table.status)
|
|
764
|
+
]);
|
|
735
765
|
schemaMeta = sqliteTable("schema_meta", {
|
|
736
766
|
key: text("key").primaryKey(),
|
|
737
767
|
value: text("value").notNull()
|
|
@@ -829,11 +859,36 @@ function openNativeDatabase(path, options) {
|
|
|
829
859
|
readOnly: options?.readonly ?? false,
|
|
830
860
|
timeout: options?.timeout ?? 5e3
|
|
831
861
|
});
|
|
862
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
832
863
|
if (options?.enableWal !== false) {
|
|
833
|
-
|
|
864
|
+
const MAX_WAL_RETRIES = 3;
|
|
865
|
+
const RETRY_DELAY_MS = 200;
|
|
866
|
+
let walSet = false;
|
|
867
|
+
for (let attempt = 1; attempt <= MAX_WAL_RETRIES; attempt++) {
|
|
868
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
869
|
+
const result = db.prepare("PRAGMA journal_mode").get();
|
|
870
|
+
const currentMode = result?.journal_mode?.toLowerCase?.() ?? "unknown";
|
|
871
|
+
if (currentMode === "wal") {
|
|
872
|
+
walSet = true;
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
if (attempt < MAX_WAL_RETRIES) {
|
|
876
|
+
const buf = new SharedArrayBuffer(4);
|
|
877
|
+
Atomics.wait(new Int32Array(buf), 0, 0, RETRY_DELAY_MS * attempt);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (!walSet) {
|
|
881
|
+
const finalResult = db.prepare("PRAGMA journal_mode").get();
|
|
882
|
+
const finalMode = finalResult?.journal_mode?.toLowerCase?.() ?? "unknown";
|
|
883
|
+
if (finalMode !== "wal") {
|
|
884
|
+
db.close();
|
|
885
|
+
throw new Error(
|
|
886
|
+
`CRITICAL: Failed to set WAL journal mode after ${MAX_WAL_RETRIES} attempts. Database is in '${finalMode}' mode. Another process likely holds an EXCLUSIVE lock on ${path}. Refusing to open \u2014 concurrent writes in DELETE mode cause data loss. Kill other cleo/MCP processes and retry. (T5173)`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
834
890
|
}
|
|
835
891
|
db.exec("PRAGMA foreign_keys=ON");
|
|
836
|
-
db.exec("PRAGMA busy_timeout=5000");
|
|
837
892
|
return db;
|
|
838
893
|
}
|
|
839
894
|
function createDrizzleCallback(db) {
|
|
@@ -884,6 +939,65 @@ var init_node_sqlite_adapter = __esm({
|
|
|
884
939
|
}
|
|
885
940
|
});
|
|
886
941
|
|
|
942
|
+
// src/store/sqlite-backup.ts
|
|
943
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
944
|
+
import { join as join3 } from "node:path";
|
|
945
|
+
function formatTimestamp(d) {
|
|
946
|
+
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
947
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
948
|
+
}
|
|
949
|
+
function rotateSnapshots(backupDir) {
|
|
950
|
+
try {
|
|
951
|
+
const files = readdirSync(backupDir).filter((f) => f.match(/^tasks-\d{8}-\d{6}\.db$/)).map((f) => ({ name: f, path: join3(backupDir, f), mtimeMs: statSync(join3(backupDir, f)).mtimeMs })).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
952
|
+
while (files.length >= MAX_SNAPSHOTS) {
|
|
953
|
+
const oldest = files.shift();
|
|
954
|
+
unlinkSync(oldest.path);
|
|
955
|
+
}
|
|
956
|
+
} catch {
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async function vacuumIntoBackup(opts = {}) {
|
|
960
|
+
const now = Date.now();
|
|
961
|
+
if (!opts.force && now - _lastBackupEpoch < DEBOUNCE_MS) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
try {
|
|
965
|
+
const cleoDir = getCleoDir(opts.cwd);
|
|
966
|
+
const backupDir = join3(cleoDir, "backups", "sqlite");
|
|
967
|
+
mkdirSync(backupDir, { recursive: true });
|
|
968
|
+
const db = getNativeDb();
|
|
969
|
+
if (!db) return;
|
|
970
|
+
const dest = join3(backupDir, `tasks-${formatTimestamp(/* @__PURE__ */ new Date())}.db`);
|
|
971
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
972
|
+
rotateSnapshots(backupDir);
|
|
973
|
+
const safeDest = dest.replace(/'/g, "''");
|
|
974
|
+
db.exec(`VACUUM INTO '${safeDest}'`);
|
|
975
|
+
_lastBackupEpoch = Date.now();
|
|
976
|
+
} catch {
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function listSqliteBackups(cwd) {
|
|
980
|
+
try {
|
|
981
|
+
const cleoDir = getCleoDir(cwd);
|
|
982
|
+
const backupDir = join3(cleoDir, "backups", "sqlite");
|
|
983
|
+
if (!existsSync3(backupDir)) return [];
|
|
984
|
+
return readdirSync(backupDir).filter((f) => f.match(/^tasks-\d{8}-\d{6}\.db$/)).map((f) => ({ name: f, path: join3(backupDir, f), mtimeMs: statSync(join3(backupDir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
985
|
+
} catch {
|
|
986
|
+
return [];
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
var MAX_SNAPSHOTS, DEBOUNCE_MS, _lastBackupEpoch;
|
|
990
|
+
var init_sqlite_backup = __esm({
|
|
991
|
+
"src/store/sqlite-backup.ts"() {
|
|
992
|
+
"use strict";
|
|
993
|
+
init_paths();
|
|
994
|
+
init_sqlite();
|
|
995
|
+
MAX_SNAPSHOTS = 10;
|
|
996
|
+
DEBOUNCE_MS = 3e4;
|
|
997
|
+
_lastBackupEpoch = 0;
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
|
|
887
1001
|
// src/store/sqlite.ts
|
|
888
1002
|
var sqlite_exports = {};
|
|
889
1003
|
__export(sqlite_exports, {
|
|
@@ -894,20 +1008,79 @@ __export(sqlite_exports, {
|
|
|
894
1008
|
getDbPath: () => getDbPath,
|
|
895
1009
|
getNativeDb: () => getNativeDb,
|
|
896
1010
|
getSchemaVersion: () => getSchemaVersion,
|
|
1011
|
+
isSqliteBusy: () => isSqliteBusy,
|
|
897
1012
|
resetDbState: () => resetDbState,
|
|
898
1013
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
899
1014
|
schema: () => schema_exports
|
|
900
1015
|
});
|
|
901
|
-
import { existsSync as
|
|
1016
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, copyFileSync, unlinkSync as unlinkSync2, renameSync as renameSync2 } from "node:fs";
|
|
902
1017
|
import { createRequire as createRequire2 } from "node:module";
|
|
903
|
-
import { dirname as dirname2, join as
|
|
1018
|
+
import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
|
|
904
1019
|
import { fileURLToPath } from "node:url";
|
|
905
1020
|
import { eq } from "drizzle-orm";
|
|
906
1021
|
import { readMigrationFiles } from "drizzle-orm/migrator";
|
|
907
1022
|
import { drizzle } from "drizzle-orm/sqlite-proxy";
|
|
908
1023
|
import { migrate } from "drizzle-orm/sqlite-proxy/migrator";
|
|
909
1024
|
function getDbPath(cwd) {
|
|
910
|
-
return
|
|
1025
|
+
return join4(getCleoDirAbsolute(cwd), DB_FILENAME);
|
|
1026
|
+
}
|
|
1027
|
+
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
1028
|
+
const log5 = getLogger("sqlite");
|
|
1029
|
+
try {
|
|
1030
|
+
const countResult = nativeDb.prepare("SELECT COUNT(*) as cnt FROM tasks").get();
|
|
1031
|
+
const taskCount = countResult?.cnt ?? 0;
|
|
1032
|
+
if (taskCount > 0) return;
|
|
1033
|
+
const backups = listSqliteBackups(cwd);
|
|
1034
|
+
if (backups.length === 0) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const newestBackup = backups[0];
|
|
1038
|
+
const backupDb = new DatabaseSync2(newestBackup.path, { readOnly: true });
|
|
1039
|
+
let backupTaskCount = 0;
|
|
1040
|
+
try {
|
|
1041
|
+
const backupCount = backupDb.prepare("SELECT COUNT(*) as cnt FROM tasks").get();
|
|
1042
|
+
backupTaskCount = backupCount?.cnt ?? 0;
|
|
1043
|
+
} finally {
|
|
1044
|
+
backupDb.close();
|
|
1045
|
+
}
|
|
1046
|
+
if (backupTaskCount < MIN_BACKUP_TASK_COUNT) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
log5.warn(
|
|
1050
|
+
{ dbPath, backupPath: newestBackup.path, backupTasks: backupTaskCount },
|
|
1051
|
+
`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).`
|
|
1052
|
+
);
|
|
1053
|
+
nativeDb.close();
|
|
1054
|
+
const walPath = dbPath + "-wal";
|
|
1055
|
+
const shmPath = dbPath + "-shm";
|
|
1056
|
+
try {
|
|
1057
|
+
unlinkSync2(walPath);
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
unlinkSync2(shmPath);
|
|
1062
|
+
} catch {
|
|
1063
|
+
}
|
|
1064
|
+
const tempPath = dbPath + ".recovery-tmp";
|
|
1065
|
+
copyFileSync(newestBackup.path, tempPath);
|
|
1066
|
+
renameSync2(tempPath, dbPath);
|
|
1067
|
+
log5.info(
|
|
1068
|
+
{ dbPath, backupPath: newestBackup.path, restoredTasks: backupTaskCount },
|
|
1069
|
+
"Database auto-recovered from backup successfully."
|
|
1070
|
+
);
|
|
1071
|
+
const restoredNativeDb = openNativeDatabase(dbPath);
|
|
1072
|
+
_nativeDb = restoredNativeDb;
|
|
1073
|
+
const callback = createDrizzleCallback(restoredNativeDb);
|
|
1074
|
+
const batchCb = createBatchCallback(restoredNativeDb);
|
|
1075
|
+
const restoredDb = drizzle(callback, batchCb, { schema: schema_exports });
|
|
1076
|
+
await runMigrations(restoredNativeDb, restoredDb);
|
|
1077
|
+
_db = restoredDb;
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
log5.error(
|
|
1080
|
+
{ err, dbPath },
|
|
1081
|
+
"Auto-recovery from backup failed. Continuing with empty database."
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
911
1084
|
}
|
|
912
1085
|
async function getDb(cwd) {
|
|
913
1086
|
const requestedPath = getDbPath(cwd);
|
|
@@ -919,7 +1092,7 @@ async function getDb(cwd) {
|
|
|
919
1092
|
_initPromise = (async () => {
|
|
920
1093
|
const dbPath = requestedPath;
|
|
921
1094
|
_dbPath = dbPath;
|
|
922
|
-
|
|
1095
|
+
mkdirSync2(dirname2(dbPath), { recursive: true });
|
|
923
1096
|
const nativeDb = openNativeDatabase(dbPath);
|
|
924
1097
|
_nativeDb = nativeDb;
|
|
925
1098
|
const callback = createDrizzleCallback(nativeDb);
|
|
@@ -932,6 +1105,31 @@ async function getDb(cwd) {
|
|
|
932
1105
|
nativeDb.exec(
|
|
933
1106
|
`INSERT OR IGNORE INTO schema_meta (key, value) VALUES ('task_id_sequence', '{"counter":0,"lastId":"T000","checksum":"seed"}')`
|
|
934
1107
|
);
|
|
1108
|
+
await autoRecoverFromBackup(nativeDb, dbPath, cwd);
|
|
1109
|
+
if (!_gitTrackingChecked) {
|
|
1110
|
+
_gitTrackingChecked = true;
|
|
1111
|
+
try {
|
|
1112
|
+
const { execFileSync: execFileSync6 } = await import("node:child_process");
|
|
1113
|
+
const gitCwd = resolve3(dbPath, "..", "..");
|
|
1114
|
+
const filesToCheck = [dbPath, dbPath + "-wal", dbPath + "-shm"];
|
|
1115
|
+
const log5 = getLogger("sqlite");
|
|
1116
|
+
for (const fileToCheck of filesToCheck) {
|
|
1117
|
+
try {
|
|
1118
|
+
execFileSync6("git", ["ls-files", "--error-unmatch", fileToCheck], {
|
|
1119
|
+
cwd: gitCwd,
|
|
1120
|
+
stdio: "pipe"
|
|
1121
|
+
});
|
|
1122
|
+
const basename7 = fileToCheck.split("/").pop();
|
|
1123
|
+
log5.warn(
|
|
1124
|
+
{ path: fileToCheck },
|
|
1125
|
+
`${basename7} is tracked by project git \u2014 this risks data loss on branch switch. Run: git rm --cached ${fileToCheck.replace(gitCwd + "/", "")} (see ADR-013, T5188)`
|
|
1126
|
+
);
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
} catch {
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
935
1133
|
_db = db;
|
|
936
1134
|
return db;
|
|
937
1135
|
})();
|
|
@@ -944,7 +1142,7 @@ async function getDb(cwd) {
|
|
|
944
1142
|
function resolveMigrationsFolder() {
|
|
945
1143
|
const __filename = fileURLToPath(import.meta.url);
|
|
946
1144
|
const __dirname2 = dirname2(__filename);
|
|
947
|
-
return
|
|
1145
|
+
return join4(__dirname2, "..", "..", "drizzle");
|
|
948
1146
|
}
|
|
949
1147
|
function tableExists(nativeDb, tableName) {
|
|
950
1148
|
const result = nativeDb.prepare(
|
|
@@ -952,6 +1150,11 @@ function tableExists(nativeDb, tableName) {
|
|
|
952
1150
|
).get(tableName);
|
|
953
1151
|
return !!result;
|
|
954
1152
|
}
|
|
1153
|
+
function isSqliteBusy(err) {
|
|
1154
|
+
if (!(err instanceof Error)) return false;
|
|
1155
|
+
const msg = err.message.toLowerCase();
|
|
1156
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1157
|
+
}
|
|
955
1158
|
async function runMigrations(nativeDb, db) {
|
|
956
1159
|
const migrationsFolder = resolveMigrationsFolder();
|
|
957
1160
|
if (tableExists(nativeDb, "tasks") && !tableExists(nativeDb, "__drizzle_migrations")) {
|
|
@@ -971,16 +1174,33 @@ async function runMigrations(nativeDb, db) {
|
|
|
971
1174
|
}
|
|
972
1175
|
}
|
|
973
1176
|
await migrate(db, async (queries) => {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
nativeDb.prepare(
|
|
1177
|
+
let lastError;
|
|
1178
|
+
for (let attempt = 1; attempt <= MAX_MIGRATION_RETRIES; attempt++) {
|
|
1179
|
+
try {
|
|
1180
|
+
nativeDb.prepare("BEGIN IMMEDIATE").run();
|
|
1181
|
+
try {
|
|
1182
|
+
for (const query of queries) {
|
|
1183
|
+
nativeDb.prepare(query).run();
|
|
1184
|
+
}
|
|
1185
|
+
nativeDb.prepare("COMMIT").run();
|
|
1186
|
+
return;
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
nativeDb.prepare("ROLLBACK").run();
|
|
1189
|
+
throw err;
|
|
1190
|
+
}
|
|
1191
|
+
} catch (err) {
|
|
1192
|
+
if (!isSqliteBusy(err) || attempt === MAX_MIGRATION_RETRIES) {
|
|
1193
|
+
throw err;
|
|
1194
|
+
}
|
|
1195
|
+
lastError = err;
|
|
1196
|
+
const exponentialDelay = MIGRATION_RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1197
|
+
const jitter = Math.random() * exponentialDelay * 0.5;
|
|
1198
|
+
const delay = Math.min(exponentialDelay + jitter, MIGRATION_RETRY_MAX_DELAY_MS);
|
|
1199
|
+
const buf = new SharedArrayBuffer(4);
|
|
1200
|
+
Atomics.wait(new Int32Array(buf), 0, 0, Math.round(delay));
|
|
978
1201
|
}
|
|
979
|
-
nativeDb.prepare("COMMIT").run();
|
|
980
|
-
} catch (err) {
|
|
981
|
-
nativeDb.prepare("ROLLBACK").run();
|
|
982
|
-
throw err;
|
|
983
1202
|
}
|
|
1203
|
+
throw lastError;
|
|
984
1204
|
}, { migrationsFolder });
|
|
985
1205
|
}
|
|
986
1206
|
function closeDb() {
|
|
@@ -1016,18 +1236,20 @@ async function getSchemaVersion(cwd) {
|
|
|
1016
1236
|
return result[0]?.value ?? null;
|
|
1017
1237
|
}
|
|
1018
1238
|
function dbExists(cwd) {
|
|
1019
|
-
return
|
|
1239
|
+
return existsSync4(getDbPath(cwd));
|
|
1020
1240
|
}
|
|
1021
1241
|
function getNativeDb() {
|
|
1022
1242
|
return _nativeDb;
|
|
1023
1243
|
}
|
|
1024
|
-
var _require2, DatabaseSync2, DB_FILENAME, SQLITE_SCHEMA_VERSION, SCHEMA_VERSION, _db, _nativeDb, _dbPath, _initPromise;
|
|
1244
|
+
var _require2, DatabaseSync2, DB_FILENAME, SQLITE_SCHEMA_VERSION, SCHEMA_VERSION, _db, _nativeDb, _dbPath, _initPromise, _gitTrackingChecked, MIN_BACKUP_TASK_COUNT, MAX_MIGRATION_RETRIES, MIGRATION_RETRY_BASE_DELAY_MS, MIGRATION_RETRY_MAX_DELAY_MS;
|
|
1025
1245
|
var init_sqlite = __esm({
|
|
1026
1246
|
"src/store/sqlite.ts"() {
|
|
1027
1247
|
"use strict";
|
|
1028
1248
|
init_schema();
|
|
1029
1249
|
init_paths();
|
|
1030
1250
|
init_node_sqlite_adapter();
|
|
1251
|
+
init_logger();
|
|
1252
|
+
init_sqlite_backup();
|
|
1031
1253
|
_require2 = createRequire2(import.meta.url);
|
|
1032
1254
|
({ DatabaseSync: DatabaseSync2 } = _require2("node:sqlite"));
|
|
1033
1255
|
DB_FILENAME = "tasks.db";
|
|
@@ -1037,6 +1259,11 @@ var init_sqlite = __esm({
|
|
|
1037
1259
|
_nativeDb = null;
|
|
1038
1260
|
_dbPath = null;
|
|
1039
1261
|
_initPromise = null;
|
|
1262
|
+
_gitTrackingChecked = false;
|
|
1263
|
+
MIN_BACKUP_TASK_COUNT = 10;
|
|
1264
|
+
MAX_MIGRATION_RETRIES = 5;
|
|
1265
|
+
MIGRATION_RETRY_BASE_DELAY_MS = 100;
|
|
1266
|
+
MIGRATION_RETRY_MAX_DELAY_MS = 2e3;
|
|
1040
1267
|
}
|
|
1041
1268
|
});
|
|
1042
1269
|
|
|
@@ -1298,7 +1525,7 @@ var init_atomic = __esm({
|
|
|
1298
1525
|
|
|
1299
1526
|
// src/store/backup.ts
|
|
1300
1527
|
import { copyFile, rename as fsRename, readdir, unlink as unlink2, stat, mkdir as mkdir2 } from "node:fs/promises";
|
|
1301
|
-
import { join as
|
|
1528
|
+
import { join as join5, basename } from "node:path";
|
|
1302
1529
|
async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUPS) {
|
|
1303
1530
|
try {
|
|
1304
1531
|
await mkdir2(backupDir, { recursive: true });
|
|
@@ -1312,14 +1539,14 @@ async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUP
|
|
|
1312
1539
|
);
|
|
1313
1540
|
}
|
|
1314
1541
|
for (let i = maxBackups; i >= 1; i--) {
|
|
1315
|
-
const current =
|
|
1542
|
+
const current = join5(backupDir, `${fileName}.${i}`);
|
|
1316
1543
|
if (i === maxBackups) {
|
|
1317
1544
|
try {
|
|
1318
1545
|
await unlink2(current);
|
|
1319
1546
|
} catch {
|
|
1320
1547
|
}
|
|
1321
1548
|
} else {
|
|
1322
|
-
const next =
|
|
1549
|
+
const next = join5(backupDir, `${fileName}.${i + 1}`);
|
|
1323
1550
|
try {
|
|
1324
1551
|
await stat(current);
|
|
1325
1552
|
await fsRename(current, next);
|
|
@@ -1327,7 +1554,7 @@ async function createBackup(filePath, backupDir, maxBackups = DEFAULT_MAX_BACKUP
|
|
|
1327
1554
|
}
|
|
1328
1555
|
}
|
|
1329
1556
|
}
|
|
1330
|
-
const backupPath =
|
|
1557
|
+
const backupPath = join5(backupDir, `${fileName}.1`);
|
|
1331
1558
|
await copyFile(filePath, backupPath);
|
|
1332
1559
|
return backupPath;
|
|
1333
1560
|
} catch (err) {
|
|
@@ -1803,6 +2030,30 @@ async function loadDependenciesForTasks(db, tasks2, validationIds) {
|
|
|
1803
2030
|
}
|
|
1804
2031
|
}
|
|
1805
2032
|
}
|
|
2033
|
+
async function loadRelationsForTasks(db, tasks2) {
|
|
2034
|
+
if (tasks2.length === 0) return;
|
|
2035
|
+
const taskIds = tasks2.map((t) => t.id);
|
|
2036
|
+
const allRels = await db.select().from(taskRelations).where(inArray(taskRelations.taskId, taskIds)).all();
|
|
2037
|
+
const relMap = /* @__PURE__ */ new Map();
|
|
2038
|
+
for (const rel of allRels) {
|
|
2039
|
+
let arr = relMap.get(rel.taskId);
|
|
2040
|
+
if (!arr) {
|
|
2041
|
+
arr = [];
|
|
2042
|
+
relMap.set(rel.taskId, arr);
|
|
2043
|
+
}
|
|
2044
|
+
arr.push({
|
|
2045
|
+
taskId: rel.relatedTo,
|
|
2046
|
+
type: rel.relationType,
|
|
2047
|
+
reason: rel.reason ?? void 0
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
for (const task of tasks2) {
|
|
2051
|
+
const relations = relMap.get(task.id);
|
|
2052
|
+
if (relations && relations.length > 0) {
|
|
2053
|
+
task.relates = relations;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
1806
2057
|
var init_db_helpers = __esm({
|
|
1807
2058
|
"src/store/db-helpers.ts"() {
|
|
1808
2059
|
"use strict";
|
|
@@ -1851,6 +2102,7 @@ async function createSqliteDataAccessor(cwd) {
|
|
|
1851
2102
|
const tasks2 = taskRows.map(rowToTask);
|
|
1852
2103
|
if (tasks2.length > 0) {
|
|
1853
2104
|
await loadDependenciesForTasks(db, tasks2);
|
|
2105
|
+
await loadRelationsForTasks(db, tasks2);
|
|
1854
2106
|
}
|
|
1855
2107
|
const projectMeta = await getMetaValue(cwd, "project_meta") ?? DEFAULT_PROJECT_META;
|
|
1856
2108
|
const workState = await getMetaValue(cwd, "focus_state") ?? DEFAULT_WORK_STATE;
|
|
@@ -1926,6 +2178,7 @@ async function createSqliteDataAccessor(cwd) {
|
|
|
1926
2178
|
...activeRows.map((r) => r.id)
|
|
1927
2179
|
]);
|
|
1928
2180
|
await loadDependenciesForTasks(db, archivedTasks, allKnownIds);
|
|
2181
|
+
await loadRelationsForTasks(db, archivedTasks);
|
|
1929
2182
|
}
|
|
1930
2183
|
return {
|
|
1931
2184
|
archivedTasks,
|
|
@@ -1992,6 +2245,14 @@ async function createSqliteDataAccessor(cwd) {
|
|
|
1992
2245
|
await upsertTask(db, row);
|
|
1993
2246
|
await updateDependencies(db, task.id, task.depends ?? []);
|
|
1994
2247
|
},
|
|
2248
|
+
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
2249
|
+
const db = await getDb(cwd);
|
|
2250
|
+
const validTypes = ["related", "blocks", "duplicates", "absorbs", "fixes", "extends", "supersedes"];
|
|
2251
|
+
if (!validTypes.includes(relationType)) {
|
|
2252
|
+
throw new Error(`Invalid relation type: ${relationType}. Valid types: ${validTypes.join(", ")}`);
|
|
2253
|
+
}
|
|
2254
|
+
await db.insert(taskRelations).values({ taskId, relatedTo, relationType, reason: reason ?? null }).onConflictDoNothing().run();
|
|
2255
|
+
},
|
|
1995
2256
|
async archiveSingleTask(taskId, fields) {
|
|
1996
2257
|
const db = await getDb(cwd);
|
|
1997
2258
|
const rows = await db.select({ id: tasks.id }).from(tasks).where(eq3(tasks.id, taskId)).all();
|
|
@@ -2063,22 +2324,22 @@ var init_sqlite_data_accessor = __esm({
|
|
|
2063
2324
|
|
|
2064
2325
|
// src/store/git-checkpoint.ts
|
|
2065
2326
|
import { readFile as readFile2, writeFile } from "node:fs/promises";
|
|
2066
|
-
import { existsSync as
|
|
2327
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2067
2328
|
import { execFile } from "node:child_process";
|
|
2068
2329
|
import { promisify } from "node:util";
|
|
2069
|
-
import { join as
|
|
2330
|
+
import { join as join6, resolve as resolve4 } from "node:path";
|
|
2070
2331
|
function makeCleoGitEnv(cleoDir) {
|
|
2071
|
-
const abs =
|
|
2332
|
+
const abs = resolve4(cleoDir);
|
|
2072
2333
|
return {
|
|
2073
2334
|
...process.env,
|
|
2074
|
-
GIT_DIR:
|
|
2335
|
+
GIT_DIR: join6(abs, ".git"),
|
|
2075
2336
|
GIT_WORK_TREE: abs
|
|
2076
2337
|
};
|
|
2077
2338
|
}
|
|
2078
2339
|
async function cleoGitCommand(args, cleoDir) {
|
|
2079
2340
|
try {
|
|
2080
2341
|
const result = await execFileAsync("git", args, {
|
|
2081
|
-
cwd:
|
|
2342
|
+
cwd: resolve4(cleoDir),
|
|
2082
2343
|
// absolute cwd so relative paths in args resolve correctly
|
|
2083
2344
|
env: makeCleoGitEnv(cleoDir),
|
|
2084
2345
|
timeout: 1e4
|
|
@@ -2089,7 +2350,7 @@ async function cleoGitCommand(args, cleoDir) {
|
|
|
2089
2350
|
}
|
|
2090
2351
|
}
|
|
2091
2352
|
function isCleoGitInitialized(cleoDir) {
|
|
2092
|
-
return
|
|
2353
|
+
return existsSync5(join6(cleoDir, ".git", "HEAD"));
|
|
2093
2354
|
}
|
|
2094
2355
|
async function loadCheckpointConfig(cwd) {
|
|
2095
2356
|
try {
|
|
@@ -2116,25 +2377,25 @@ async function isCleoGitRepo(cleoDir) {
|
|
|
2116
2377
|
return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
|
|
2117
2378
|
}
|
|
2118
2379
|
function isMergeInProgress(cleoDir) {
|
|
2119
|
-
return
|
|
2380
|
+
return existsSync5(join6(cleoDir, ".git", "MERGE_HEAD"));
|
|
2120
2381
|
}
|
|
2121
2382
|
async function isDetachedHead(cleoDir) {
|
|
2122
2383
|
const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
|
|
2123
2384
|
return !result.success;
|
|
2124
2385
|
}
|
|
2125
2386
|
function isRebaseInProgress(cleoDir) {
|
|
2126
|
-
return
|
|
2387
|
+
return existsSync5(join6(cleoDir, ".git", "rebase-merge")) || existsSync5(join6(cleoDir, ".git", "rebase-apply"));
|
|
2127
2388
|
}
|
|
2128
2389
|
async function recordCheckpointTime(cleoDir) {
|
|
2129
2390
|
try {
|
|
2130
|
-
const stateFile =
|
|
2391
|
+
const stateFile = join6(cleoDir, CHECKPOINT_STATE_FILE);
|
|
2131
2392
|
await writeFile(stateFile, String(Math.floor(Date.now() / 1e3)));
|
|
2132
2393
|
} catch {
|
|
2133
2394
|
}
|
|
2134
2395
|
}
|
|
2135
2396
|
async function getLastCheckpointTime(cleoDir) {
|
|
2136
2397
|
try {
|
|
2137
|
-
const stateFile =
|
|
2398
|
+
const stateFile = join6(cleoDir, CHECKPOINT_STATE_FILE);
|
|
2138
2399
|
const content = await readFile2(stateFile, "utf-8");
|
|
2139
2400
|
const epoch = parseInt(content.trim(), 10);
|
|
2140
2401
|
return isNaN(epoch) ? 0 : epoch;
|
|
@@ -2145,8 +2406,8 @@ async function getLastCheckpointTime(cleoDir) {
|
|
|
2145
2406
|
async function getChangedStateFiles(cleoDir) {
|
|
2146
2407
|
const changed = [];
|
|
2147
2408
|
for (const stateFile of STATE_FILES) {
|
|
2148
|
-
const fullPath =
|
|
2149
|
-
if (!
|
|
2409
|
+
const fullPath = join6(cleoDir, stateFile);
|
|
2410
|
+
if (!existsSync5(fullPath)) continue;
|
|
2150
2411
|
const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
|
|
2151
2412
|
const cachedResult = await cleoGitCommand(["diff", "--cached", "--quiet", "--", stateFile], cleoDir);
|
|
2152
2413
|
const untrackedResult = await cleoGitCommand(
|
|
@@ -2170,7 +2431,7 @@ async function shouldCheckpoint(options) {
|
|
|
2170
2431
|
const config = await loadCheckpointConfig(cwd);
|
|
2171
2432
|
if (!config.enabled) return false;
|
|
2172
2433
|
const cleoDir = getCleoDir(cwd);
|
|
2173
|
-
if (!
|
|
2434
|
+
if (!existsSync5(cleoDir)) return false;
|
|
2174
2435
|
if (!isCleoGitInitialized(cleoDir)) return false;
|
|
2175
2436
|
if (!await isCleoGitRepo(cleoDir)) return false;
|
|
2176
2437
|
if (isMergeInProgress(cleoDir)) return false;
|
|
@@ -2242,63 +2503,15 @@ var init_git_checkpoint = __esm({
|
|
|
2242
2503
|
}
|
|
2243
2504
|
});
|
|
2244
2505
|
|
|
2245
|
-
// src/store/sqlite-backup.ts
|
|
2246
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
2247
|
-
import { join as join6 } from "node:path";
|
|
2248
|
-
function formatTimestamp(d) {
|
|
2249
|
-
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
2250
|
-
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
2251
|
-
}
|
|
2252
|
-
function rotateSnapshots(backupDir) {
|
|
2253
|
-
try {
|
|
2254
|
-
const files = readdirSync(backupDir).filter((f) => f.match(/^tasks-\d{8}-\d{6}\.db$/)).map((f) => ({ name: f, path: join6(backupDir, f), mtimeMs: statSync(join6(backupDir, f)).mtimeMs })).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
2255
|
-
while (files.length >= MAX_SNAPSHOTS) {
|
|
2256
|
-
const oldest = files.shift();
|
|
2257
|
-
unlinkSync(oldest.path);
|
|
2258
|
-
}
|
|
2259
|
-
} catch {
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
async function vacuumIntoBackup(opts = {}) {
|
|
2263
|
-
const now = Date.now();
|
|
2264
|
-
if (!opts.force && now - _lastBackupEpoch < DEBOUNCE_MS) {
|
|
2265
|
-
return;
|
|
2266
|
-
}
|
|
2267
|
-
try {
|
|
2268
|
-
const cleoDir = getCleoDir(opts.cwd);
|
|
2269
|
-
const backupDir = join6(cleoDir, "backups", "sqlite");
|
|
2270
|
-
mkdirSync2(backupDir, { recursive: true });
|
|
2271
|
-
const db = getNativeDb();
|
|
2272
|
-
if (!db) return;
|
|
2273
|
-
const dest = join6(backupDir, `tasks-${formatTimestamp(/* @__PURE__ */ new Date())}.db`);
|
|
2274
|
-
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
2275
|
-
rotateSnapshots(backupDir);
|
|
2276
|
-
const safeDest = dest.replace(/'/g, "''");
|
|
2277
|
-
db.exec(`VACUUM INTO '${safeDest}'`);
|
|
2278
|
-
_lastBackupEpoch = Date.now();
|
|
2279
|
-
} catch {
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
var MAX_SNAPSHOTS, DEBOUNCE_MS, _lastBackupEpoch;
|
|
2283
|
-
var init_sqlite_backup = __esm({
|
|
2284
|
-
"src/store/sqlite-backup.ts"() {
|
|
2285
|
-
"use strict";
|
|
2286
|
-
init_paths();
|
|
2287
|
-
init_sqlite();
|
|
2288
|
-
MAX_SNAPSHOTS = 10;
|
|
2289
|
-
DEBOUNCE_MS = 3e4;
|
|
2290
|
-
_lastBackupEpoch = 0;
|
|
2291
|
-
}
|
|
2292
|
-
});
|
|
2293
|
-
|
|
2294
2506
|
// src/core/sequence/index.ts
|
|
2295
2507
|
var sequence_exports = {};
|
|
2296
2508
|
__export(sequence_exports, {
|
|
2509
|
+
allocateNextTaskId: () => allocateNextTaskId,
|
|
2297
2510
|
checkSequence: () => checkSequence,
|
|
2298
2511
|
repairSequence: () => repairSequence,
|
|
2299
2512
|
showSequence: () => showSequence
|
|
2300
2513
|
});
|
|
2301
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3, renameSync as
|
|
2514
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, renameSync as renameSync3 } from "node:fs";
|
|
2302
2515
|
import { join as join7 } from "node:path";
|
|
2303
2516
|
import { eq as eq4 } from "drizzle-orm";
|
|
2304
2517
|
function getLegacySequenceJsonPath(cwd) {
|
|
@@ -2329,10 +2542,10 @@ function renameLegacyFile(path) {
|
|
|
2329
2542
|
const migratedPath = `${path}.migrated`;
|
|
2330
2543
|
try {
|
|
2331
2544
|
if (!existsSync6(migratedPath)) {
|
|
2332
|
-
|
|
2545
|
+
renameSync3(path, migratedPath);
|
|
2333
2546
|
return;
|
|
2334
2547
|
}
|
|
2335
|
-
|
|
2548
|
+
renameSync3(path, `${migratedPath}.${Date.now()}`);
|
|
2336
2549
|
} catch {
|
|
2337
2550
|
}
|
|
2338
2551
|
}
|
|
@@ -2480,7 +2693,56 @@ async function repairSequence(cwd, accessor) {
|
|
|
2480
2693
|
message: `Sequence repaired: ${oldCounter} -> ${newCounter}`
|
|
2481
2694
|
};
|
|
2482
2695
|
}
|
|
2483
|
-
|
|
2696
|
+
async function allocateNextTaskId(cwd, retryCount = 0) {
|
|
2697
|
+
await getDb(cwd);
|
|
2698
|
+
const nativeDb = getNativeDb();
|
|
2699
|
+
if (!nativeDb) {
|
|
2700
|
+
throw new CleoError(3 /* FILE_ERROR */, "Native database not available for atomic ID allocation");
|
|
2701
|
+
}
|
|
2702
|
+
nativeDb.prepare("BEGIN IMMEDIATE").run();
|
|
2703
|
+
try {
|
|
2704
|
+
nativeDb.prepare(`
|
|
2705
|
+
UPDATE schema_meta
|
|
2706
|
+
SET value = json_set(value,
|
|
2707
|
+
'$.counter', json_extract(value, '$.counter') + 1,
|
|
2708
|
+
'$.lastId', 'T' || printf('%03d', json_extract(value, '$.counter') + 1),
|
|
2709
|
+
'$.checksum', 'alloc-' || strftime('%s','now')
|
|
2710
|
+
)
|
|
2711
|
+
WHERE key = 'task_id_sequence'
|
|
2712
|
+
`).run();
|
|
2713
|
+
const row = nativeDb.prepare(`
|
|
2714
|
+
SELECT json_extract(value, '$.counter') AS counter
|
|
2715
|
+
FROM schema_meta WHERE key = 'task_id_sequence'
|
|
2716
|
+
`).get();
|
|
2717
|
+
if (!row) {
|
|
2718
|
+
throw new CleoError(3 /* FILE_ERROR */, "Sequence counter not found after increment");
|
|
2719
|
+
}
|
|
2720
|
+
const newId = `T${String(row.counter).padStart(3, "0")}`;
|
|
2721
|
+
const existing = nativeDb.prepare(
|
|
2722
|
+
"SELECT id FROM tasks WHERE id = ?"
|
|
2723
|
+
).get(newId);
|
|
2724
|
+
if (existing) {
|
|
2725
|
+
nativeDb.prepare("ROLLBACK").run();
|
|
2726
|
+
if (retryCount >= MAX_ALLOC_RETRIES) {
|
|
2727
|
+
throw new CleoError(
|
|
2728
|
+
22 /* ID_COLLISION */,
|
|
2729
|
+
`Failed to allocate unique task ID after ${MAX_ALLOC_RETRIES} retries (last attempted: ${newId})`
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
await repairSequence(cwd);
|
|
2733
|
+
return allocateNextTaskId(cwd, retryCount + 1);
|
|
2734
|
+
}
|
|
2735
|
+
nativeDb.prepare("COMMIT").run();
|
|
2736
|
+
return newId;
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
try {
|
|
2739
|
+
nativeDb.prepare("ROLLBACK").run();
|
|
2740
|
+
} catch {
|
|
2741
|
+
}
|
|
2742
|
+
throw err;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
var SEQUENCE_META_KEY, MAX_ALLOC_RETRIES;
|
|
2484
2746
|
var init_sequence = __esm({
|
|
2485
2747
|
"src/core/sequence/index.ts"() {
|
|
2486
2748
|
"use strict";
|
|
@@ -2491,6 +2753,7 @@ var init_sequence = __esm({
|
|
|
2491
2753
|
init_sqlite_data_accessor();
|
|
2492
2754
|
init_data_accessor();
|
|
2493
2755
|
SEQUENCE_META_KEY = "task_id_sequence";
|
|
2756
|
+
MAX_ALLOC_RETRIES = 3;
|
|
2494
2757
|
}
|
|
2495
2758
|
});
|
|
2496
2759
|
|
|
@@ -2803,6 +3066,11 @@ var init_safety_data_accessor = __esm({
|
|
|
2803
3066
|
this.getSafetyOptions()
|
|
2804
3067
|
);
|
|
2805
3068
|
}
|
|
3069
|
+
// ---- Relations (pass-through to inner, T5168) ----
|
|
3070
|
+
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
3071
|
+
if (!this.inner.addRelation) return;
|
|
3072
|
+
await this.inner.addRelation(taskId, relatedTo, relationType, reason);
|
|
3073
|
+
}
|
|
2806
3074
|
// ---- Metadata (pass-through to inner) ----
|
|
2807
3075
|
async getMetaValue(key) {
|
|
2808
3076
|
return this.inner.getMetaValue?.(key) ?? null;
|
|
@@ -2838,10 +3106,57 @@ var init_data_accessor = __esm({
|
|
|
2838
3106
|
}
|
|
2839
3107
|
});
|
|
2840
3108
|
|
|
2841
|
-
// src/
|
|
2842
|
-
import { readFileSync as readFileSync4, writeFileSync, renameSync as
|
|
2843
|
-
import { join as join8, dirname as dirname5, basename as basename2 } from "path";
|
|
3109
|
+
// src/store/file-utils.ts
|
|
3110
|
+
import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync4, existsSync as existsSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
3111
|
+
import { join as join8, dirname as dirname5, basename as basename2 } from "node:path";
|
|
3112
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2844
3113
|
import * as lockfile2 from "proper-lockfile";
|
|
3114
|
+
function rotateBackup(filePath) {
|
|
3115
|
+
const dir = dirname5(filePath);
|
|
3116
|
+
const name = basename2(filePath);
|
|
3117
|
+
const backupDir = join8(dir, ".backups");
|
|
3118
|
+
if (!existsSync8(backupDir)) {
|
|
3119
|
+
mkdirSync3(backupDir, { recursive: true });
|
|
3120
|
+
}
|
|
3121
|
+
for (let i = MAX_BACKUPS; i >= 1; i--) {
|
|
3122
|
+
const current = join8(backupDir, `${name}.${i}`);
|
|
3123
|
+
if (i === MAX_BACKUPS) {
|
|
3124
|
+
try {
|
|
3125
|
+
unlinkSync3(current);
|
|
3126
|
+
} catch {
|
|
3127
|
+
}
|
|
3128
|
+
} else {
|
|
3129
|
+
const next = join8(backupDir, `${name}.${i + 1}`);
|
|
3130
|
+
try {
|
|
3131
|
+
if (existsSync8(current)) renameSync4(current, next);
|
|
3132
|
+
} catch {
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
try {
|
|
3137
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3138
|
+
writeFileSync(join8(backupDir, `${name}.1`), content, "utf-8");
|
|
3139
|
+
} catch {
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function writeJsonFileAtomic(filePath, data, indent = 2) {
|
|
3143
|
+
const dir = dirname5(filePath);
|
|
3144
|
+
const tempPath = join8(dir, `.${basename2(filePath)}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
3145
|
+
const content = JSON.stringify(data, null, indent) + "\n";
|
|
3146
|
+
writeFileSync(tempPath, content, "utf-8");
|
|
3147
|
+
try {
|
|
3148
|
+
if (existsSync8(filePath)) {
|
|
3149
|
+
rotateBackup(filePath);
|
|
3150
|
+
}
|
|
3151
|
+
renameSync4(tempPath, filePath);
|
|
3152
|
+
} catch (error) {
|
|
3153
|
+
try {
|
|
3154
|
+
unlinkSync3(tempPath);
|
|
3155
|
+
} catch {
|
|
3156
|
+
}
|
|
3157
|
+
throw error;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
2845
3160
|
function readJsonFile(filePath) {
|
|
2846
3161
|
try {
|
|
2847
3162
|
const content = readFileSync4(filePath, "utf-8");
|
|
@@ -2853,12 +3168,95 @@ function readJsonFile(filePath) {
|
|
|
2853
3168
|
throw error;
|
|
2854
3169
|
}
|
|
2855
3170
|
}
|
|
3171
|
+
function readLogFileEntries(filePath) {
|
|
3172
|
+
let content;
|
|
3173
|
+
try {
|
|
3174
|
+
content = readFileSync4(filePath, "utf-8").trim();
|
|
3175
|
+
} catch (error) {
|
|
3176
|
+
if (error.code === "ENOENT") return [];
|
|
3177
|
+
throw error;
|
|
3178
|
+
}
|
|
3179
|
+
if (!content) return [];
|
|
3180
|
+
try {
|
|
3181
|
+
const parsed = JSON.parse(content);
|
|
3182
|
+
if (Array.isArray(parsed)) return parsed;
|
|
3183
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.entries)) {
|
|
3184
|
+
return parsed.entries;
|
|
3185
|
+
}
|
|
3186
|
+
return [parsed];
|
|
3187
|
+
} catch {
|
|
3188
|
+
}
|
|
3189
|
+
const entries = [];
|
|
3190
|
+
if (content.startsWith("{")) {
|
|
3191
|
+
let depth = 0;
|
|
3192
|
+
let inString = false;
|
|
3193
|
+
let escaped = false;
|
|
3194
|
+
let jsonEnd = -1;
|
|
3195
|
+
for (let i = 0; i < content.length; i++) {
|
|
3196
|
+
const ch = content[i];
|
|
3197
|
+
if (escaped) {
|
|
3198
|
+
escaped = false;
|
|
3199
|
+
continue;
|
|
3200
|
+
}
|
|
3201
|
+
if (ch === "\\" && inString) {
|
|
3202
|
+
escaped = true;
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
if (ch === '"') {
|
|
3206
|
+
inString = !inString;
|
|
3207
|
+
continue;
|
|
3208
|
+
}
|
|
3209
|
+
if (inString) continue;
|
|
3210
|
+
if (ch === "{") depth++;
|
|
3211
|
+
else if (ch === "}") {
|
|
3212
|
+
depth--;
|
|
3213
|
+
if (depth === 0) {
|
|
3214
|
+
jsonEnd = i + 1;
|
|
3215
|
+
break;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
if (jsonEnd > 0) {
|
|
3220
|
+
try {
|
|
3221
|
+
const initial = JSON.parse(content.substring(0, jsonEnd));
|
|
3222
|
+
if (initial && Array.isArray(initial.entries)) entries.push(...initial.entries);
|
|
3223
|
+
} catch {
|
|
3224
|
+
}
|
|
3225
|
+
const remainder = content.substring(jsonEnd).trim();
|
|
3226
|
+
if (remainder) {
|
|
3227
|
+
for (const line of remainder.split("\n")) {
|
|
3228
|
+
const l = line.trim();
|
|
3229
|
+
if (!l || !l.startsWith("{")) continue;
|
|
3230
|
+
try {
|
|
3231
|
+
entries.push(JSON.parse(l));
|
|
3232
|
+
} catch {
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
} else {
|
|
3238
|
+
for (const line of content.split("\n")) {
|
|
3239
|
+
const l = line.trim();
|
|
3240
|
+
if (!l || !l.startsWith("{")) continue;
|
|
3241
|
+
try {
|
|
3242
|
+
entries.push(JSON.parse(l));
|
|
3243
|
+
} catch {
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
return entries;
|
|
3248
|
+
}
|
|
2856
3249
|
function getDataPath(projectRoot, filename) {
|
|
2857
3250
|
return join8(projectRoot, ".cleo", filename);
|
|
2858
3251
|
}
|
|
2859
|
-
|
|
2860
|
-
"
|
|
3252
|
+
function resolveProjectRoot() {
|
|
3253
|
+
return process.env["CLEO_ROOT"] || process.cwd();
|
|
3254
|
+
}
|
|
3255
|
+
var MAX_BACKUPS;
|
|
3256
|
+
var init_file_utils = __esm({
|
|
3257
|
+
"src/store/file-utils.ts"() {
|
|
2861
3258
|
"use strict";
|
|
3259
|
+
MAX_BACKUPS = 10;
|
|
2862
3260
|
}
|
|
2863
3261
|
});
|
|
2864
3262
|
|
|
@@ -3102,7 +3500,7 @@ var init_plan = __esm({
|
|
|
3102
3500
|
"src/core/tasks/plan.ts"() {
|
|
3103
3501
|
"use strict";
|
|
3104
3502
|
init_data_accessor();
|
|
3105
|
-
|
|
3503
|
+
init_file_utils();
|
|
3106
3504
|
init_deps_ready();
|
|
3107
3505
|
PRIORITY_SCORE2 = {
|
|
3108
3506
|
critical: 100,
|
|
@@ -3114,7 +3512,7 @@ var init_plan = __esm({
|
|
|
3114
3512
|
});
|
|
3115
3513
|
|
|
3116
3514
|
// src/core/sessions/decisions.ts
|
|
3117
|
-
import { randomBytes as
|
|
3515
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3118
3516
|
import { readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "node:fs";
|
|
3119
3517
|
import { join as join9 } from "node:path";
|
|
3120
3518
|
async function recordDecision(projectRoot, params) {
|
|
@@ -3130,7 +3528,7 @@ async function recordDecision(projectRoot, params) {
|
|
|
3130
3528
|
}
|
|
3131
3529
|
const decisionPath = join9(auditDir, "decisions.jsonl");
|
|
3132
3530
|
const record = {
|
|
3133
|
-
id: `dec-${
|
|
3531
|
+
id: `dec-${randomBytes3(8).toString("hex")}`,
|
|
3134
3532
|
sessionId: params.sessionId,
|
|
3135
3533
|
taskId: params.taskId,
|
|
3136
3534
|
decision: params.decision,
|
|
@@ -3416,189 +3814,36 @@ async function computeChainPosition(projectRoot, sessionId) {
|
|
|
3416
3814
|
let current = sessionId;
|
|
3417
3815
|
let position = 1;
|
|
3418
3816
|
const visited = /* @__PURE__ */ new Set();
|
|
3419
|
-
while (true) {
|
|
3420
|
-
visited.add(current);
|
|
3421
|
-
const session = sessionMap.get(current);
|
|
3422
|
-
if (!session?.previousSessionId || visited.has(session.previousSessionId)) break;
|
|
3423
|
-
current = session.previousSessionId;
|
|
3424
|
-
position++;
|
|
3425
|
-
}
|
|
3426
|
-
let length = position;
|
|
3427
|
-
const startSession = sessionMap.get(sessionId);
|
|
3428
|
-
let fwd = startSession?.nextSessionId;
|
|
3429
|
-
while (fwd && !visited.has(fwd)) {
|
|
3430
|
-
visited.add(fwd);
|
|
3431
|
-
length++;
|
|
3432
|
-
const s = sessionMap.get(fwd);
|
|
3433
|
-
fwd = s?.nextSessionId ?? void 0;
|
|
3434
|
-
}
|
|
3435
|
-
return { position, length };
|
|
3436
|
-
} catch {
|
|
3437
|
-
return { position: 1, length: 1 };
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
var execFileAsync2;
|
|
3441
|
-
var init_handoff = __esm({
|
|
3442
|
-
"src/core/sessions/handoff.ts"() {
|
|
3443
|
-
"use strict";
|
|
3444
|
-
init_data_accessor();
|
|
3445
|
-
init_errors();
|
|
3446
|
-
init_exit_codes();
|
|
3447
|
-
init_decisions();
|
|
3448
|
-
execFileAsync2 = promisify2(execFile2);
|
|
3449
|
-
}
|
|
3450
|
-
});
|
|
3451
|
-
|
|
3452
|
-
// src/store/file-utils.ts
|
|
3453
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync as renameSync4, existsSync as existsSync11, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3454
|
-
import { join as join11, dirname as dirname6, basename as basename3 } from "node:path";
|
|
3455
|
-
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
3456
|
-
function rotateBackup(filePath) {
|
|
3457
|
-
const dir = dirname6(filePath);
|
|
3458
|
-
const name = basename3(filePath);
|
|
3459
|
-
const backupDir = join11(dir, ".backups");
|
|
3460
|
-
if (!existsSync11(backupDir)) {
|
|
3461
|
-
mkdirSync6(backupDir, { recursive: true });
|
|
3462
|
-
}
|
|
3463
|
-
for (let i = MAX_BACKUPS; i >= 1; i--) {
|
|
3464
|
-
const current = join11(backupDir, `${name}.${i}`);
|
|
3465
|
-
if (i === MAX_BACKUPS) {
|
|
3466
|
-
try {
|
|
3467
|
-
unlinkSync3(current);
|
|
3468
|
-
} catch {
|
|
3469
|
-
}
|
|
3470
|
-
} else {
|
|
3471
|
-
const next = join11(backupDir, `${name}.${i + 1}`);
|
|
3472
|
-
try {
|
|
3473
|
-
if (existsSync11(current)) renameSync4(current, next);
|
|
3474
|
-
} catch {
|
|
3475
|
-
}
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
try {
|
|
3479
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
3480
|
-
writeFileSync2(join11(backupDir, `${name}.1`), content, "utf-8");
|
|
3481
|
-
} catch {
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
function writeJsonFileAtomic(filePath, data, indent = 2) {
|
|
3485
|
-
const dir = dirname6(filePath);
|
|
3486
|
-
const tempPath = join11(dir, `.${basename3(filePath)}.${randomBytes5(6).toString("hex")}.tmp`);
|
|
3487
|
-
const content = JSON.stringify(data, null, indent) + "\n";
|
|
3488
|
-
writeFileSync2(tempPath, content, "utf-8");
|
|
3489
|
-
try {
|
|
3490
|
-
if (existsSync11(filePath)) {
|
|
3491
|
-
rotateBackup(filePath);
|
|
3492
|
-
}
|
|
3493
|
-
renameSync4(tempPath, filePath);
|
|
3494
|
-
} catch (error) {
|
|
3495
|
-
try {
|
|
3496
|
-
unlinkSync3(tempPath);
|
|
3497
|
-
} catch {
|
|
3498
|
-
}
|
|
3499
|
-
throw error;
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3502
|
-
function readJsonFile2(filePath) {
|
|
3503
|
-
try {
|
|
3504
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
3505
|
-
return JSON.parse(content);
|
|
3506
|
-
} catch (error) {
|
|
3507
|
-
if (error.code === "ENOENT") {
|
|
3508
|
-
return null;
|
|
3509
|
-
}
|
|
3510
|
-
throw error;
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
function readLogFileEntries2(filePath) {
|
|
3514
|
-
let content;
|
|
3515
|
-
try {
|
|
3516
|
-
content = readFileSync6(filePath, "utf-8").trim();
|
|
3517
|
-
} catch (error) {
|
|
3518
|
-
if (error.code === "ENOENT") return [];
|
|
3519
|
-
throw error;
|
|
3520
|
-
}
|
|
3521
|
-
if (!content) return [];
|
|
3522
|
-
try {
|
|
3523
|
-
const parsed = JSON.parse(content);
|
|
3524
|
-
if (Array.isArray(parsed)) return parsed;
|
|
3525
|
-
if (parsed && typeof parsed === "object" && Array.isArray(parsed.entries)) {
|
|
3526
|
-
return parsed.entries;
|
|
3527
|
-
}
|
|
3528
|
-
return [parsed];
|
|
3529
|
-
} catch {
|
|
3530
|
-
}
|
|
3531
|
-
const entries = [];
|
|
3532
|
-
if (content.startsWith("{")) {
|
|
3533
|
-
let depth = 0;
|
|
3534
|
-
let inString = false;
|
|
3535
|
-
let escaped = false;
|
|
3536
|
-
let jsonEnd = -1;
|
|
3537
|
-
for (let i = 0; i < content.length; i++) {
|
|
3538
|
-
const ch = content[i];
|
|
3539
|
-
if (escaped) {
|
|
3540
|
-
escaped = false;
|
|
3541
|
-
continue;
|
|
3542
|
-
}
|
|
3543
|
-
if (ch === "\\" && inString) {
|
|
3544
|
-
escaped = true;
|
|
3545
|
-
continue;
|
|
3546
|
-
}
|
|
3547
|
-
if (ch === '"') {
|
|
3548
|
-
inString = !inString;
|
|
3549
|
-
continue;
|
|
3550
|
-
}
|
|
3551
|
-
if (inString) continue;
|
|
3552
|
-
if (ch === "{") depth++;
|
|
3553
|
-
else if (ch === "}") {
|
|
3554
|
-
depth--;
|
|
3555
|
-
if (depth === 0) {
|
|
3556
|
-
jsonEnd = i + 1;
|
|
3557
|
-
break;
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
if (jsonEnd > 0) {
|
|
3562
|
-
try {
|
|
3563
|
-
const initial = JSON.parse(content.substring(0, jsonEnd));
|
|
3564
|
-
if (initial && Array.isArray(initial.entries)) entries.push(...initial.entries);
|
|
3565
|
-
} catch {
|
|
3566
|
-
}
|
|
3567
|
-
const remainder = content.substring(jsonEnd).trim();
|
|
3568
|
-
if (remainder) {
|
|
3569
|
-
for (const line of remainder.split("\n")) {
|
|
3570
|
-
const l = line.trim();
|
|
3571
|
-
if (!l || !l.startsWith("{")) continue;
|
|
3572
|
-
try {
|
|
3573
|
-
entries.push(JSON.parse(l));
|
|
3574
|
-
} catch {
|
|
3575
|
-
}
|
|
3576
|
-
}
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
} else {
|
|
3580
|
-
for (const line of content.split("\n")) {
|
|
3581
|
-
const l = line.trim();
|
|
3582
|
-
if (!l || !l.startsWith("{")) continue;
|
|
3583
|
-
try {
|
|
3584
|
-
entries.push(JSON.parse(l));
|
|
3585
|
-
} catch {
|
|
3586
|
-
}
|
|
3817
|
+
while (true) {
|
|
3818
|
+
visited.add(current);
|
|
3819
|
+
const session = sessionMap.get(current);
|
|
3820
|
+
if (!session?.previousSessionId || visited.has(session.previousSessionId)) break;
|
|
3821
|
+
current = session.previousSessionId;
|
|
3822
|
+
position++;
|
|
3823
|
+
}
|
|
3824
|
+
let length = position;
|
|
3825
|
+
const startSession = sessionMap.get(sessionId);
|
|
3826
|
+
let fwd = startSession?.nextSessionId;
|
|
3827
|
+
while (fwd && !visited.has(fwd)) {
|
|
3828
|
+
visited.add(fwd);
|
|
3829
|
+
length++;
|
|
3830
|
+
const s = sessionMap.get(fwd);
|
|
3831
|
+
fwd = s?.nextSessionId ?? void 0;
|
|
3587
3832
|
}
|
|
3833
|
+
return { position, length };
|
|
3834
|
+
} catch {
|
|
3835
|
+
return { position: 1, length: 1 };
|
|
3588
3836
|
}
|
|
3589
|
-
return entries;
|
|
3590
|
-
}
|
|
3591
|
-
function getDataPath2(projectRoot, filename) {
|
|
3592
|
-
return join11(projectRoot, ".cleo", filename);
|
|
3593
3837
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
var MAX_BACKUPS;
|
|
3598
|
-
var init_file_utils = __esm({
|
|
3599
|
-
"src/store/file-utils.ts"() {
|
|
3838
|
+
var execFileAsync2;
|
|
3839
|
+
var init_handoff = __esm({
|
|
3840
|
+
"src/core/sessions/handoff.ts"() {
|
|
3600
3841
|
"use strict";
|
|
3601
|
-
|
|
3842
|
+
init_data_accessor();
|
|
3843
|
+
init_errors();
|
|
3844
|
+
init_exit_codes();
|
|
3845
|
+
init_decisions();
|
|
3846
|
+
execFileAsync2 = promisify2(execFile2);
|
|
3602
3847
|
}
|
|
3603
3848
|
});
|
|
3604
3849
|
|
|
@@ -3613,15 +3858,15 @@ __export(platform_exports, {
|
|
|
3613
3858
|
dateDaysAgo: () => dateDaysAgo,
|
|
3614
3859
|
detectPlatform: () => detectPlatform,
|
|
3615
3860
|
generateRandomHex: () => generateRandomHex,
|
|
3616
|
-
getDataPath: () =>
|
|
3861
|
+
getDataPath: () => getDataPath,
|
|
3617
3862
|
getFileMtime: () => getFileMtime,
|
|
3618
3863
|
getFileSize: () => getFileSize,
|
|
3619
3864
|
getIsoTimestamp: () => getIsoTimestamp,
|
|
3620
3865
|
getNodeUpgradeInstructions: () => getNodeUpgradeInstructions,
|
|
3621
3866
|
getNodeVersionInfo: () => getNodeVersionInfo,
|
|
3622
3867
|
isoToEpoch: () => isoToEpoch,
|
|
3623
|
-
readJsonFile: () =>
|
|
3624
|
-
readLogFileEntries: () =>
|
|
3868
|
+
readJsonFile: () => readJsonFile,
|
|
3869
|
+
readLogFileEntries: () => readLogFileEntries,
|
|
3625
3870
|
requireTool: () => requireTool,
|
|
3626
3871
|
resolveProjectRoot: () => resolveProjectRoot,
|
|
3627
3872
|
sha256: () => sha256,
|
|
@@ -3629,8 +3874,8 @@ __export(platform_exports, {
|
|
|
3629
3874
|
});
|
|
3630
3875
|
import { execFileSync } from "node:child_process";
|
|
3631
3876
|
import { tmpdir } from "node:os";
|
|
3632
|
-
import { join as
|
|
3633
|
-
import { existsSync as
|
|
3877
|
+
import { join as join11 } from "node:path";
|
|
3878
|
+
import { existsSync as existsSync11, statSync as statSync2 } from "node:fs";
|
|
3634
3879
|
import { createHash as createHash3, randomBytes as randomBytes6 } from "node:crypto";
|
|
3635
3880
|
function detectPlatform() {
|
|
3636
3881
|
switch (process.platform) {
|
|
@@ -3689,11 +3934,11 @@ function dateDaysAgo(days) {
|
|
|
3689
3934
|
return d.toISOString();
|
|
3690
3935
|
}
|
|
3691
3936
|
function getFileSize(filePath) {
|
|
3692
|
-
if (!
|
|
3937
|
+
if (!existsSync11(filePath)) return 0;
|
|
3693
3938
|
return statSync2(filePath).size;
|
|
3694
3939
|
}
|
|
3695
3940
|
function getFileMtime(filePath) {
|
|
3696
|
-
if (!
|
|
3941
|
+
if (!existsSync11(filePath)) return null;
|
|
3697
3942
|
return statSync2(filePath).mtime.toISOString();
|
|
3698
3943
|
}
|
|
3699
3944
|
function generateRandomHex(bytes = 6) {
|
|
@@ -3704,7 +3949,7 @@ function sha256(data) {
|
|
|
3704
3949
|
}
|
|
3705
3950
|
function createTempFilePath(prefix = "cleo-", suffix = ".tmp") {
|
|
3706
3951
|
const random = generateRandomHex(8);
|
|
3707
|
-
return
|
|
3952
|
+
return join11(tmpdir(), `${prefix}${random}${suffix}`);
|
|
3708
3953
|
}
|
|
3709
3954
|
function getNodeVersionInfo() {
|
|
3710
3955
|
const version = process.version.replace("v", "");
|
|
@@ -3834,20 +4079,20 @@ __export(schema_integrity_exports, {
|
|
|
3834
4079
|
checkSchemaIntegrity: () => checkSchemaIntegrity,
|
|
3835
4080
|
readSchemaVersionFromFile: () => readSchemaVersionFromFile
|
|
3836
4081
|
});
|
|
3837
|
-
import { existsSync as
|
|
3838
|
-
import { join as
|
|
4082
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14 } from "node:fs";
|
|
4083
|
+
import { join as join21, dirname as dirname6 } from "node:path";
|
|
3839
4084
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3840
4085
|
function resolveSchemaPath(schemaName) {
|
|
3841
|
-
const __dirname2 =
|
|
4086
|
+
const __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
3842
4087
|
const candidates = [
|
|
3843
4088
|
// dist/core/validation/ → schemas/
|
|
3844
|
-
|
|
4089
|
+
join21(__dirname2, "..", "..", "..", "schemas", schemaName),
|
|
3845
4090
|
// src/core/validation/ → schemas/ (ts-node / vitest)
|
|
3846
|
-
|
|
4091
|
+
join21(__dirname2, "..", "..", "schemas", schemaName),
|
|
3847
4092
|
// fallback: look relative to cwd
|
|
3848
|
-
|
|
4093
|
+
join21(process.cwd(), "schemas", schemaName)
|
|
3849
4094
|
];
|
|
3850
|
-
return candidates.find(
|
|
4095
|
+
return candidates.find(existsSync21) ?? null;
|
|
3851
4096
|
}
|
|
3852
4097
|
function readSchemaVersionFromFile(schemaName) {
|
|
3853
4098
|
const path = resolveSchemaPath(schemaName);
|
|
@@ -3856,7 +4101,7 @@ function readSchemaVersionFromFile(schemaName) {
|
|
|
3856
4101
|
}
|
|
3857
4102
|
function readSchemaVersion(schemaPath) {
|
|
3858
4103
|
try {
|
|
3859
|
-
const raw =
|
|
4104
|
+
const raw = readFileSync14(schemaPath, "utf-8");
|
|
3860
4105
|
const schema = JSON.parse(raw);
|
|
3861
4106
|
const version = schema["schemaVersion"] ?? schema["_meta"]?.["schemaVersion"];
|
|
3862
4107
|
return typeof version === "string" ? version : null;
|
|
@@ -3866,7 +4111,7 @@ function readSchemaVersion(schemaPath) {
|
|
|
3866
4111
|
}
|
|
3867
4112
|
function checkFile(target, cleoDir) {
|
|
3868
4113
|
const filePath = target.filePath(cleoDir);
|
|
3869
|
-
if (!
|
|
4114
|
+
if (!existsSync21(filePath)) {
|
|
3870
4115
|
if (target.required) {
|
|
3871
4116
|
return {
|
|
3872
4117
|
label: target.label,
|
|
@@ -3878,7 +4123,7 @@ function checkFile(target, cleoDir) {
|
|
|
3878
4123
|
}
|
|
3879
4124
|
let data;
|
|
3880
4125
|
try {
|
|
3881
|
-
data = JSON.parse(
|
|
4126
|
+
data = JSON.parse(readFileSync14(filePath, "utf-8"));
|
|
3882
4127
|
} catch (err) {
|
|
3883
4128
|
return {
|
|
3884
4129
|
label: target.label,
|
|
@@ -3896,7 +4141,7 @@ function checkFile(target, cleoDir) {
|
|
|
3896
4141
|
}
|
|
3897
4142
|
let schema;
|
|
3898
4143
|
try {
|
|
3899
|
-
schema = JSON.parse(
|
|
4144
|
+
schema = JSON.parse(readFileSync14(schemaPath, "utf-8"));
|
|
3900
4145
|
} catch (err) {
|
|
3901
4146
|
return {
|
|
3902
4147
|
label: target.label,
|
|
@@ -3960,28 +4205,28 @@ var init_schema_integrity = __esm({
|
|
|
3960
4205
|
INTEGRITY_TARGETS = [
|
|
3961
4206
|
{
|
|
3962
4207
|
label: "config.json",
|
|
3963
|
-
filePath: (d) =>
|
|
4208
|
+
filePath: (d) => join21(d, "config.json"),
|
|
3964
4209
|
schemaName: "config.schema.json",
|
|
3965
4210
|
required: true,
|
|
3966
4211
|
versionKey: "schemaVersion"
|
|
3967
4212
|
},
|
|
3968
4213
|
{
|
|
3969
4214
|
label: "project-info.json",
|
|
3970
|
-
filePath: (d) =>
|
|
4215
|
+
filePath: (d) => join21(d, "project-info.json"),
|
|
3971
4216
|
schemaName: "project-info.schema.json",
|
|
3972
4217
|
required: false,
|
|
3973
4218
|
versionKey: "schemaVersion"
|
|
3974
4219
|
},
|
|
3975
4220
|
{
|
|
3976
4221
|
label: "project-context.json",
|
|
3977
|
-
filePath: (d) =>
|
|
4222
|
+
filePath: (d) => join21(d, "project-context.json"),
|
|
3978
4223
|
schemaName: "project-context.schema.json",
|
|
3979
4224
|
required: false,
|
|
3980
4225
|
versionKey: "schemaVersion"
|
|
3981
4226
|
},
|
|
3982
4227
|
{
|
|
3983
4228
|
label: ".context-state.json",
|
|
3984
|
-
filePath: (d) =>
|
|
4229
|
+
filePath: (d) => join21(d, ".context-state.json"),
|
|
3985
4230
|
schemaName: "context-state.schema.json",
|
|
3986
4231
|
required: false,
|
|
3987
4232
|
versionKey: "version"
|
|
@@ -3996,17 +4241,17 @@ __export(mcp_exports, {
|
|
|
3996
4241
|
detectEnvMode: () => detectEnvMode,
|
|
3997
4242
|
generateMcpServerEntry: () => generateMcpServerEntry
|
|
3998
4243
|
});
|
|
3999
|
-
import { readFileSync as
|
|
4000
|
-
import { join as
|
|
4244
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
4245
|
+
import { join as join22 } from "node:path";
|
|
4001
4246
|
import { homedir as homedir3 } from "node:os";
|
|
4002
4247
|
function detectEnvMode() {
|
|
4003
|
-
const versionPath =
|
|
4004
|
-
process.env["CLEO_HOME"] ??
|
|
4248
|
+
const versionPath = join22(
|
|
4249
|
+
process.env["CLEO_HOME"] ?? join22(homedir3(), ".cleo"),
|
|
4005
4250
|
"VERSION"
|
|
4006
4251
|
);
|
|
4007
4252
|
let content;
|
|
4008
4253
|
try {
|
|
4009
|
-
content =
|
|
4254
|
+
content = readFileSync15(versionPath, "utf-8");
|
|
4010
4255
|
} catch {
|
|
4011
4256
|
return { mode: "unknown", source: null };
|
|
4012
4257
|
}
|
|
@@ -4029,7 +4274,7 @@ function generateMcpServerEntry(env) {
|
|
|
4029
4274
|
if (env.mode === "dev-ts" && env.source) {
|
|
4030
4275
|
return {
|
|
4031
4276
|
command: "node",
|
|
4032
|
-
args: [
|
|
4277
|
+
args: [join22(env.source, "dist", "mcp", "index.js")],
|
|
4033
4278
|
env: {}
|
|
4034
4279
|
};
|
|
4035
4280
|
}
|
|
@@ -4068,17 +4313,17 @@ __export(registry_exports, {
|
|
|
4068
4313
|
readRegistryRequired: () => readRegistryRequired
|
|
4069
4314
|
});
|
|
4070
4315
|
import { createHash as createHash4 } from "node:crypto";
|
|
4071
|
-
import { join as
|
|
4316
|
+
import { join as join23 } from "node:path";
|
|
4072
4317
|
import { mkdir as mkdir4, access, readFile as readFile4 } from "node:fs/promises";
|
|
4073
4318
|
import { z } from "zod";
|
|
4074
4319
|
function getNexusHome() {
|
|
4075
|
-
return process.env["NEXUS_HOME"] ??
|
|
4320
|
+
return process.env["NEXUS_HOME"] ?? join23(getCleoHome(), "nexus");
|
|
4076
4321
|
}
|
|
4077
4322
|
function getNexusCacheDir() {
|
|
4078
|
-
return process.env["NEXUS_CACHE_DIR"] ??
|
|
4323
|
+
return process.env["NEXUS_CACHE_DIR"] ?? join23(getNexusHome(), "cache");
|
|
4079
4324
|
}
|
|
4080
4325
|
function getRegistryPath() {
|
|
4081
|
-
return process.env["NEXUS_REGISTRY_FILE"] ??
|
|
4326
|
+
return process.env["NEXUS_REGISTRY_FILE"] ?? join23(getCleoHome(), "projects-registry.json");
|
|
4082
4327
|
}
|
|
4083
4328
|
function generateProjectHash(projectPath) {
|
|
4084
4329
|
const hash = createHash4("sha256").update(projectPath).digest("hex");
|
|
@@ -4120,7 +4365,7 @@ async function nexusInit() {
|
|
|
4120
4365
|
}
|
|
4121
4366
|
async function isCleoProject(projectPath) {
|
|
4122
4367
|
try {
|
|
4123
|
-
await access(
|
|
4368
|
+
await access(join23(projectPath, ".cleo", "todo.json"));
|
|
4124
4369
|
return true;
|
|
4125
4370
|
} catch {
|
|
4126
4371
|
return false;
|
|
@@ -4130,9 +4375,9 @@ async function readProjectMeta(projectPath) {
|
|
|
4130
4375
|
try {
|
|
4131
4376
|
let raw;
|
|
4132
4377
|
try {
|
|
4133
|
-
raw = await readFile4(
|
|
4378
|
+
raw = await readFile4(join23(projectPath, ".cleo", "tasks.json"), "utf-8");
|
|
4134
4379
|
} catch {
|
|
4135
|
-
raw = await readFile4(
|
|
4380
|
+
raw = await readFile4(join23(projectPath, ".cleo", "todo.json"), "utf-8");
|
|
4136
4381
|
}
|
|
4137
4382
|
const data = JSON.parse(raw);
|
|
4138
4383
|
const tasks2 = data.tasks ?? [];
|
|
@@ -4318,10 +4563,10 @@ var project_detect_exports = {};
|
|
|
4318
4563
|
__export(project_detect_exports, {
|
|
4319
4564
|
detectProjectType: () => detectProjectType
|
|
4320
4565
|
});
|
|
4321
|
-
import { existsSync as
|
|
4322
|
-
import { join as
|
|
4566
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6 } from "node:fs";
|
|
4567
|
+
import { join as join24 } from "node:path";
|
|
4323
4568
|
function detectProjectType(projectDir) {
|
|
4324
|
-
const exists = (f) =>
|
|
4569
|
+
const exists = (f) => existsSync22(join24(projectDir, f));
|
|
4325
4570
|
const info = {
|
|
4326
4571
|
type: "unknown",
|
|
4327
4572
|
testFramework: "unknown",
|
|
@@ -4392,6 +4637,7 @@ __export(validation_schemas_exports, {
|
|
|
4392
4637
|
insertLifecyclePipelineSchema: () => insertLifecyclePipelineSchema,
|
|
4393
4638
|
insertLifecycleStageSchema: () => insertLifecycleStageSchema,
|
|
4394
4639
|
insertLifecycleTransitionSchema: () => insertLifecycleTransitionSchema,
|
|
4640
|
+
insertManifestEntrySchema: () => insertManifestEntrySchema,
|
|
4395
4641
|
insertSchemaMetaSchema: () => insertSchemaMetaSchema,
|
|
4396
4642
|
insertSessionSchema: () => insertSessionSchema,
|
|
4397
4643
|
insertTaskDependencySchema: () => insertTaskDependencySchema,
|
|
@@ -4405,6 +4651,7 @@ __export(validation_schemas_exports, {
|
|
|
4405
4651
|
selectLifecyclePipelineSchema: () => selectLifecyclePipelineSchema,
|
|
4406
4652
|
selectLifecycleStageSchema: () => selectLifecycleStageSchema,
|
|
4407
4653
|
selectLifecycleTransitionSchema: () => selectLifecycleTransitionSchema,
|
|
4654
|
+
selectManifestEntrySchema: () => selectManifestEntrySchema,
|
|
4408
4655
|
selectSchemaMetaSchema: () => selectSchemaMetaSchema,
|
|
4409
4656
|
selectSessionSchema: () => selectSessionSchema,
|
|
4410
4657
|
selectTaskDependencySchema: () => selectTaskDependencySchema,
|
|
@@ -4418,7 +4665,7 @@ __export(validation_schemas_exports, {
|
|
|
4418
4665
|
});
|
|
4419
4666
|
import { createInsertSchema, createSelectSchema } from "drizzle-orm/zod";
|
|
4420
4667
|
import { z as z2 } from "zod/v4";
|
|
4421
|
-
var taskRefinements, insertTaskSchema, selectTaskSchema, insertTaskDependencySchema, selectTaskDependencySchema, insertTaskRelationSchema, selectTaskRelationSchema, insertSessionSchema, selectSessionSchema, sessionScopeSchema, sessionStatsSchema, sessionTaskWorkSchema, sessionSchema, insertWorkHistorySchema, selectWorkHistorySchema, insertLifecyclePipelineSchema, selectLifecyclePipelineSchema, insertLifecycleStageSchema, selectLifecycleStageSchema, insertLifecycleGateResultSchema, selectLifecycleGateResultSchema, insertLifecycleEvidenceSchema, selectLifecycleEvidenceSchema, insertLifecycleTransitionSchema, selectLifecycleTransitionSchema, insertSchemaMetaSchema, selectSchemaMetaSchema, insertAuditLogSchema, AuditLogInsertSchema, selectAuditLogSchema, AuditLogSelectSchema, insertArchitectureDecisionSchema, selectArchitectureDecisionSchema;
|
|
4668
|
+
var taskRefinements, insertTaskSchema, selectTaskSchema, insertTaskDependencySchema, selectTaskDependencySchema, insertTaskRelationSchema, selectTaskRelationSchema, insertSessionSchema, selectSessionSchema, sessionScopeSchema, sessionStatsSchema, sessionTaskWorkSchema, sessionSchema, insertWorkHistorySchema, selectWorkHistorySchema, insertLifecyclePipelineSchema, selectLifecyclePipelineSchema, insertLifecycleStageSchema, selectLifecycleStageSchema, insertLifecycleGateResultSchema, selectLifecycleGateResultSchema, insertLifecycleEvidenceSchema, selectLifecycleEvidenceSchema, insertLifecycleTransitionSchema, selectLifecycleTransitionSchema, insertSchemaMetaSchema, selectSchemaMetaSchema, insertAuditLogSchema, AuditLogInsertSchema, selectAuditLogSchema, AuditLogSelectSchema, insertArchitectureDecisionSchema, selectArchitectureDecisionSchema, insertManifestEntrySchema, selectManifestEntrySchema;
|
|
4422
4669
|
var init_validation_schemas = __esm({
|
|
4423
4670
|
"src/store/validation-schemas.ts"() {
|
|
4424
4671
|
"use strict";
|
|
@@ -4511,6 +4758,8 @@ var init_validation_schemas = __esm({
|
|
|
4511
4758
|
AuditLogSelectSchema = selectAuditLogSchema;
|
|
4512
4759
|
insertArchitectureDecisionSchema = createInsertSchema(architectureDecisions);
|
|
4513
4760
|
selectArchitectureDecisionSchema = createSelectSchema(architectureDecisions);
|
|
4761
|
+
insertManifestEntrySchema = createInsertSchema(manifestEntries);
|
|
4762
|
+
selectManifestEntrySchema = createSelectSchema(manifestEntries);
|
|
4514
4763
|
}
|
|
4515
4764
|
});
|
|
4516
4765
|
|
|
@@ -4684,8 +4933,8 @@ __export(session_grade_exports, {
|
|
|
4684
4933
|
gradeSession: () => gradeSession,
|
|
4685
4934
|
readGrades: () => readGrades
|
|
4686
4935
|
});
|
|
4687
|
-
import { join as
|
|
4688
|
-
import { existsSync as
|
|
4936
|
+
import { join as join41 } from "node:path";
|
|
4937
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
4689
4938
|
import { readFile as readFile6, appendFile, mkdir as mkdir6 } from "node:fs/promises";
|
|
4690
4939
|
async function gradeSession(sessionId, cwd) {
|
|
4691
4940
|
const sessionEntries = await queryAudit({ sessionId });
|
|
@@ -4865,9 +5114,9 @@ function detectDuplicateCreates(entries) {
|
|
|
4865
5114
|
async function appendGradeResult(result, cwd) {
|
|
4866
5115
|
try {
|
|
4867
5116
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
4868
|
-
const metricsDir =
|
|
5117
|
+
const metricsDir = join41(cleoDir, "metrics");
|
|
4869
5118
|
await mkdir6(metricsDir, { recursive: true });
|
|
4870
|
-
const gradesPath =
|
|
5119
|
+
const gradesPath = join41(metricsDir, "GRADES.jsonl");
|
|
4871
5120
|
const line = JSON.stringify({ ...result, evaluator: "auto" }) + "\n";
|
|
4872
5121
|
await appendFile(gradesPath, line, "utf8");
|
|
4873
5122
|
} catch {
|
|
@@ -4876,8 +5125,8 @@ async function appendGradeResult(result, cwd) {
|
|
|
4876
5125
|
async function readGrades(sessionId, cwd) {
|
|
4877
5126
|
try {
|
|
4878
5127
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
4879
|
-
const gradesPath =
|
|
4880
|
-
if (!
|
|
5128
|
+
const gradesPath = join41(cleoDir, "metrics", "GRADES.jsonl");
|
|
5129
|
+
if (!existsSync40(gradesPath)) return [];
|
|
4881
5130
|
const content = await readFile6(gradesPath, "utf8");
|
|
4882
5131
|
const results = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
4883
5132
|
return sessionId ? results.filter((r) => r.sessionId === sessionId) : results;
|
|
@@ -5241,7 +5490,7 @@ if (actualQueryCount < 1) {
|
|
|
5241
5490
|
function registerQueryTool() {
|
|
5242
5491
|
return {
|
|
5243
5492
|
name: "cleo_query",
|
|
5244
|
-
description:
|
|
5493
|
+
description: 'CLEO read operations: task discovery, status checks, analysis, validation, and compliance metrics. Never modifies state. First call: use domain "admin", operation "help" to discover all available operations.',
|
|
5245
5494
|
inputSchema: {
|
|
5246
5495
|
type: "object",
|
|
5247
5496
|
required: ["domain", "operation"],
|
|
@@ -5253,7 +5502,7 @@ function registerQueryTool() {
|
|
|
5253
5502
|
},
|
|
5254
5503
|
operation: {
|
|
5255
5504
|
type: "string",
|
|
5256
|
-
description: "Domain-specific read operation
|
|
5505
|
+
description: "Domain-specific read operation. Call admin.help to see the full operation matrix. Common: tasks.find, tasks.show, tasks.next, session.status, admin.dash"
|
|
5257
5506
|
},
|
|
5258
5507
|
params: {
|
|
5259
5508
|
type: "object",
|
|
@@ -5563,7 +5812,7 @@ if (actualMutateCount < 1) {
|
|
|
5563
5812
|
function registerMutateTool() {
|
|
5564
5813
|
return {
|
|
5565
5814
|
name: "cleo_mutate",
|
|
5566
|
-
description:
|
|
5815
|
+
description: 'CLEO write operations: create, update, complete tasks; manage sessions; spawn agents; progress lifecycle; execute releases. Modifies state with validation. Use cleo_query with domain "admin", operation "help" first to discover available operations.',
|
|
5567
5816
|
inputSchema: {
|
|
5568
5817
|
type: "object",
|
|
5569
5818
|
required: ["domain", "operation"],
|
|
@@ -5575,7 +5824,7 @@ function registerMutateTool() {
|
|
|
5575
5824
|
},
|
|
5576
5825
|
operation: {
|
|
5577
5826
|
type: "string",
|
|
5578
|
-
description: "Domain-specific write operation
|
|
5827
|
+
description: "Domain-specific write operation. Call cleo_query admin.help to see the full operation matrix. Common: tasks.add, tasks.update, tasks.complete, session.start, session.end"
|
|
5579
5828
|
},
|
|
5580
5829
|
params: {
|
|
5581
5830
|
type: "object",
|
|
@@ -7813,7 +8062,6 @@ init_data_accessor();
|
|
|
7813
8062
|
|
|
7814
8063
|
// src/dispatch/engines/_error.ts
|
|
7815
8064
|
init_logger();
|
|
7816
|
-
var logger = getLogger("engine");
|
|
7817
8065
|
var STRING_TO_EXIT = {
|
|
7818
8066
|
// General Errors (1-9)
|
|
7819
8067
|
E_GENERAL: 1,
|
|
@@ -7942,6 +8190,7 @@ function logLevel(exitCode) {
|
|
|
7942
8190
|
function engineError(code, message, options) {
|
|
7943
8191
|
const exitCode = STRING_TO_EXIT[code] ?? 1;
|
|
7944
8192
|
const level = logLevel(exitCode);
|
|
8193
|
+
const logger = getLogger("engine");
|
|
7945
8194
|
logger[level]({ code, exitCode, ...options?.details && { details: options.details } }, message);
|
|
7946
8195
|
return {
|
|
7947
8196
|
success: false,
|
|
@@ -8564,6 +8813,7 @@ function validateHierarchyPlacement(parentId, tasks2, policy) {
|
|
|
8564
8813
|
}
|
|
8565
8814
|
|
|
8566
8815
|
// src/core/tasks/add.ts
|
|
8816
|
+
init_sequence();
|
|
8567
8817
|
function validateTitle(title) {
|
|
8568
8818
|
if (!title || title.trim().length === 0) {
|
|
8569
8819
|
throw new CleoError(2 /* INVALID_INPUT */, "Task title is required");
|
|
@@ -8746,20 +8996,23 @@ async function addTask(options, cwd, accessor) {
|
|
|
8746
8996
|
validateTitle(options.title);
|
|
8747
8997
|
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(taskPath);
|
|
8748
8998
|
let archivedTasks = [];
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
if (
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8999
|
+
const useSqliteAlloc = accessor?.engine === "sqlite" || !accessor && (await Promise.resolve().then(() => (init_sqlite(), sqlite_exports))).getNativeDb() !== null;
|
|
9000
|
+
if (!useSqliteAlloc) {
|
|
9001
|
+
try {
|
|
9002
|
+
if (accessor) {
|
|
9003
|
+
const archive = await accessor.loadArchive();
|
|
9004
|
+
if (archive?.archivedTasks) {
|
|
9005
|
+
archivedTasks = archive.archivedTasks;
|
|
9006
|
+
}
|
|
9007
|
+
} else {
|
|
9008
|
+
const { readJson: readJson2 } = await Promise.resolve().then(() => (init_json(), json_exports));
|
|
9009
|
+
const archive = await readJson2(archivePath);
|
|
9010
|
+
if (archive?.archivedTasks) {
|
|
9011
|
+
archivedTasks = archive.archivedTasks;
|
|
9012
|
+
}
|
|
8760
9013
|
}
|
|
9014
|
+
} catch {
|
|
8761
9015
|
}
|
|
8762
|
-
} catch {
|
|
8763
9016
|
}
|
|
8764
9017
|
const status = options.status ?? "pending";
|
|
8765
9018
|
const priority = normalizePriority(options.priority ?? "medium");
|
|
@@ -8837,9 +9090,14 @@ async function addTask(options, cwd, accessor) {
|
|
|
8837
9090
|
if (duplicate) {
|
|
8838
9091
|
return { task: duplicate, duplicate: true };
|
|
8839
9092
|
}
|
|
8840
|
-
|
|
8841
|
-
if (
|
|
8842
|
-
|
|
9093
|
+
let taskId;
|
|
9094
|
+
if (useSqliteAlloc) {
|
|
9095
|
+
taskId = await allocateNextTaskId(cwd);
|
|
9096
|
+
} else {
|
|
9097
|
+
taskId = generateTaskId(data.tasks, archivedTasks);
|
|
9098
|
+
if (data.tasks.some((t) => t.id === taskId)) {
|
|
9099
|
+
throw new CleoError(22 /* ID_COLLISION */, `Generated ID ${taskId} already exists`);
|
|
9100
|
+
}
|
|
8843
9101
|
}
|
|
8844
9102
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8845
9103
|
const position = options.position ?? getNextPosition(parentId, data.tasks);
|
|
@@ -9193,6 +9451,291 @@ async function updateTask2(options, cwd, accessor) {
|
|
|
9193
9451
|
return { task, changes };
|
|
9194
9452
|
}
|
|
9195
9453
|
|
|
9454
|
+
// src/core/tasks/complete.ts
|
|
9455
|
+
init_json();
|
|
9456
|
+
init_errors();
|
|
9457
|
+
init_exit_codes();
|
|
9458
|
+
init_paths();
|
|
9459
|
+
|
|
9460
|
+
// src/core/tasks/dependency-check.ts
|
|
9461
|
+
function detectCircularDeps(taskId, tasks2) {
|
|
9462
|
+
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
9463
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9464
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
9465
|
+
const path = [];
|
|
9466
|
+
function dfs(id) {
|
|
9467
|
+
visited.add(id);
|
|
9468
|
+
recursionStack.add(id);
|
|
9469
|
+
path.push(id);
|
|
9470
|
+
const task = taskMap.get(id);
|
|
9471
|
+
if (task?.depends) {
|
|
9472
|
+
for (const depId of task.depends) {
|
|
9473
|
+
if (!visited.has(depId)) {
|
|
9474
|
+
const cycle = dfs(depId);
|
|
9475
|
+
if (cycle.length > 0) return cycle;
|
|
9476
|
+
} else if (recursionStack.has(depId)) {
|
|
9477
|
+
const cycleStart = path.indexOf(depId);
|
|
9478
|
+
return [...path.slice(cycleStart), depId];
|
|
9479
|
+
}
|
|
9480
|
+
}
|
|
9481
|
+
}
|
|
9482
|
+
path.pop();
|
|
9483
|
+
recursionStack.delete(id);
|
|
9484
|
+
return [];
|
|
9485
|
+
}
|
|
9486
|
+
return dfs(taskId);
|
|
9487
|
+
}
|
|
9488
|
+
function getBlockedTasks(tasks2) {
|
|
9489
|
+
const completedIds = new Set(
|
|
9490
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9491
|
+
);
|
|
9492
|
+
return tasks2.filter((t) => {
|
|
9493
|
+
if (!t.depends?.length) return false;
|
|
9494
|
+
if (t.status === "done" || t.status === "cancelled") return false;
|
|
9495
|
+
return t.depends.some((depId) => !completedIds.has(depId));
|
|
9496
|
+
});
|
|
9497
|
+
}
|
|
9498
|
+
function getReadyTasks(tasks2) {
|
|
9499
|
+
const completedIds = new Set(
|
|
9500
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9501
|
+
);
|
|
9502
|
+
return tasks2.filter((t) => {
|
|
9503
|
+
if (t.status === "done" || t.status === "cancelled") return false;
|
|
9504
|
+
if (!t.depends?.length) return true;
|
|
9505
|
+
return t.depends.every((depId) => completedIds.has(depId));
|
|
9506
|
+
});
|
|
9507
|
+
}
|
|
9508
|
+
function getDependents(taskId, tasks2) {
|
|
9509
|
+
return tasks2.filter((t) => t.depends?.includes(taskId));
|
|
9510
|
+
}
|
|
9511
|
+
function getUnresolvedDeps(taskId, tasks2) {
|
|
9512
|
+
const task = tasks2.find((t) => t.id === taskId);
|
|
9513
|
+
if (!task?.depends?.length) return [];
|
|
9514
|
+
const completedIds = new Set(
|
|
9515
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9516
|
+
);
|
|
9517
|
+
return task.depends.filter((depId) => !completedIds.has(depId));
|
|
9518
|
+
}
|
|
9519
|
+
function validateDependencyRefs(tasks2) {
|
|
9520
|
+
const taskIds = new Set(tasks2.map((t) => t.id));
|
|
9521
|
+
const errors = [];
|
|
9522
|
+
for (const task of tasks2) {
|
|
9523
|
+
if (!task.depends?.length) continue;
|
|
9524
|
+
for (const depId of task.depends) {
|
|
9525
|
+
if (!taskIds.has(depId)) {
|
|
9526
|
+
errors.push({
|
|
9527
|
+
code: "E_DEP_NOT_FOUND",
|
|
9528
|
+
taskId: task.id,
|
|
9529
|
+
message: `Task ${task.id} depends on ${depId}, which does not exist`,
|
|
9530
|
+
relatedIds: [depId]
|
|
9531
|
+
});
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9534
|
+
}
|
|
9535
|
+
return errors;
|
|
9536
|
+
}
|
|
9537
|
+
function validateDependencies(tasks2) {
|
|
9538
|
+
const errors = [];
|
|
9539
|
+
const warnings = [];
|
|
9540
|
+
errors.push(...validateDependencyRefs(tasks2));
|
|
9541
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9542
|
+
for (const task of tasks2) {
|
|
9543
|
+
if (visited.has(task.id)) continue;
|
|
9544
|
+
if (!task.depends?.length) continue;
|
|
9545
|
+
const cycle = detectCircularDeps(task.id, tasks2);
|
|
9546
|
+
if (cycle.length > 0) {
|
|
9547
|
+
errors.push({
|
|
9548
|
+
code: "E_CIRCULAR_DEP",
|
|
9549
|
+
taskId: task.id,
|
|
9550
|
+
message: `Circular dependency detected: ${cycle.join(" -> ")}`,
|
|
9551
|
+
relatedIds: cycle
|
|
9552
|
+
});
|
|
9553
|
+
cycle.forEach((id) => visited.add(id));
|
|
9554
|
+
}
|
|
9555
|
+
}
|
|
9556
|
+
for (const task of tasks2) {
|
|
9557
|
+
if (task.depends?.includes(task.id)) {
|
|
9558
|
+
errors.push({
|
|
9559
|
+
code: "E_SELF_DEP",
|
|
9560
|
+
taskId: task.id,
|
|
9561
|
+
message: `Task ${task.id} depends on itself`
|
|
9562
|
+
});
|
|
9563
|
+
}
|
|
9564
|
+
}
|
|
9565
|
+
for (const task of tasks2) {
|
|
9566
|
+
if (task.status === "done" && task.depends?.length) {
|
|
9567
|
+
const unresolved = getUnresolvedDeps(task.id, tasks2);
|
|
9568
|
+
if (unresolved.length > 0) {
|
|
9569
|
+
warnings.push({
|
|
9570
|
+
code: "W_COMPLETED_WITH_UNMET_DEPS",
|
|
9571
|
+
taskId: task.id,
|
|
9572
|
+
message: `Completed task ${task.id} has unmet dependencies: ${unresolved.join(", ")}`
|
|
9573
|
+
});
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
return {
|
|
9578
|
+
valid: errors.length === 0,
|
|
9579
|
+
errors,
|
|
9580
|
+
warnings
|
|
9581
|
+
};
|
|
9582
|
+
}
|
|
9583
|
+
function getTransitiveBlockers(taskId, tasks2) {
|
|
9584
|
+
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
9585
|
+
const task = taskMap.get(taskId);
|
|
9586
|
+
if (!task?.depends?.length) return [];
|
|
9587
|
+
const blockers = /* @__PURE__ */ new Set();
|
|
9588
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9589
|
+
function walk(id) {
|
|
9590
|
+
if (visited.has(id)) return;
|
|
9591
|
+
visited.add(id);
|
|
9592
|
+
const t = taskMap.get(id);
|
|
9593
|
+
if (!t?.depends?.length) return;
|
|
9594
|
+
for (const depId of t.depends) {
|
|
9595
|
+
const dep = taskMap.get(depId);
|
|
9596
|
+
if (!dep) continue;
|
|
9597
|
+
if (dep.status === "done" || dep.status === "cancelled") continue;
|
|
9598
|
+
blockers.add(depId);
|
|
9599
|
+
walk(depId);
|
|
9600
|
+
}
|
|
9601
|
+
}
|
|
9602
|
+
walk(taskId);
|
|
9603
|
+
return [...blockers];
|
|
9604
|
+
}
|
|
9605
|
+
function getLeafBlockers(taskId, tasks2) {
|
|
9606
|
+
const blockerIds = getTransitiveBlockers(taskId, tasks2);
|
|
9607
|
+
if (blockerIds.length === 0) return [];
|
|
9608
|
+
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
9609
|
+
const completedIds = new Set(
|
|
9610
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9611
|
+
);
|
|
9612
|
+
return blockerIds.filter((id) => {
|
|
9613
|
+
const t = taskMap.get(id);
|
|
9614
|
+
if (!t?.depends?.length) return true;
|
|
9615
|
+
return t.depends.every((depId) => completedIds.has(depId));
|
|
9616
|
+
});
|
|
9617
|
+
}
|
|
9618
|
+
|
|
9619
|
+
// src/core/tasks/complete.ts
|
|
9620
|
+
init_data_safety_central();
|
|
9621
|
+
async function completeTask(options, cwd, accessor) {
|
|
9622
|
+
const taskPath = getTaskPath(cwd);
|
|
9623
|
+
const logPath = getLogPath(cwd);
|
|
9624
|
+
const backupDir = getBackupDir(cwd);
|
|
9625
|
+
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(taskPath);
|
|
9626
|
+
const taskIdx = data.tasks.findIndex((t) => t.id === options.taskId);
|
|
9627
|
+
if (taskIdx === -1) {
|
|
9628
|
+
throw new CleoError(
|
|
9629
|
+
4 /* NOT_FOUND */,
|
|
9630
|
+
`Task not found: ${options.taskId}`,
|
|
9631
|
+
{ fix: `Use 'cleo find "${options.taskId}"' to search` }
|
|
9632
|
+
);
|
|
9633
|
+
}
|
|
9634
|
+
const task = data.tasks[taskIdx];
|
|
9635
|
+
if (task.status === "done") {
|
|
9636
|
+
throw new CleoError(
|
|
9637
|
+
17 /* TASK_COMPLETED */,
|
|
9638
|
+
`Task ${options.taskId} is already completed`
|
|
9639
|
+
);
|
|
9640
|
+
}
|
|
9641
|
+
if (task.depends?.length) {
|
|
9642
|
+
const incompleteDeps = task.depends.filter((depId) => {
|
|
9643
|
+
const dep = data.tasks.find((t) => t.id === depId);
|
|
9644
|
+
return dep && dep.status !== "done";
|
|
9645
|
+
});
|
|
9646
|
+
if (incompleteDeps.length > 0) {
|
|
9647
|
+
throw new CleoError(
|
|
9648
|
+
5 /* DEPENDENCY_ERROR */,
|
|
9649
|
+
`Task ${options.taskId} has incomplete dependencies: ${incompleteDeps.join(", ")}`,
|
|
9650
|
+
{ fix: `Complete dependencies first: ${incompleteDeps.map((d) => `cleo complete ${d}`).join(", ")}` }
|
|
9651
|
+
);
|
|
9652
|
+
}
|
|
9653
|
+
}
|
|
9654
|
+
const children = data.tasks.filter((t) => t.parentId === options.taskId);
|
|
9655
|
+
const incompleteChildren = children.filter((c) => c.status !== "done" && c.status !== "cancelled");
|
|
9656
|
+
if (incompleteChildren.length > 0 && task.type === "epic") {
|
|
9657
|
+
if (!task.noAutoComplete) {
|
|
9658
|
+
throw new CleoError(
|
|
9659
|
+
16 /* HAS_CHILDREN */,
|
|
9660
|
+
`Epic ${options.taskId} has ${incompleteChildren.length} incomplete children: ${incompleteChildren.map((c) => c.id).join(", ")}`,
|
|
9661
|
+
{ fix: `Complete children first or use 'cleo update ${options.taskId} --no-auto-complete'` }
|
|
9662
|
+
);
|
|
9663
|
+
}
|
|
9664
|
+
}
|
|
9665
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9666
|
+
const before = { ...task };
|
|
9667
|
+
task.status = "done";
|
|
9668
|
+
task.completedAt = now;
|
|
9669
|
+
task.updatedAt = now;
|
|
9670
|
+
if (options.notes) {
|
|
9671
|
+
const timestampedNote = `${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}: ${options.notes}`;
|
|
9672
|
+
if (!task.notes) task.notes = [];
|
|
9673
|
+
task.notes.push(timestampedNote);
|
|
9674
|
+
}
|
|
9675
|
+
if (options.changeset) {
|
|
9676
|
+
if (!task.notes) task.notes = [];
|
|
9677
|
+
task.notes.push(`Changeset: ${options.changeset}`);
|
|
9678
|
+
}
|
|
9679
|
+
data.tasks[taskIdx] = task;
|
|
9680
|
+
const autoCompleted = [];
|
|
9681
|
+
if (task.parentId) {
|
|
9682
|
+
const parent = data.tasks.find((t) => t.id === task.parentId);
|
|
9683
|
+
if (parent && parent.type === "epic" && !parent.noAutoComplete) {
|
|
9684
|
+
const parentChildren = data.tasks.filter((t) => t.parentId === parent.id);
|
|
9685
|
+
const allDone = parentChildren.every((c) => c.status === "done" || c.status === "cancelled");
|
|
9686
|
+
if (allDone) {
|
|
9687
|
+
parent.status = "done";
|
|
9688
|
+
parent.completedAt = now;
|
|
9689
|
+
parent.updatedAt = now;
|
|
9690
|
+
autoCompleted.push(parent.id);
|
|
9691
|
+
}
|
|
9692
|
+
}
|
|
9693
|
+
}
|
|
9694
|
+
data._meta.checksum = computeChecksum(data.tasks);
|
|
9695
|
+
data.lastUpdated = now;
|
|
9696
|
+
if (accessor) {
|
|
9697
|
+
if (accessor.upsertSingleTask) {
|
|
9698
|
+
await accessor.upsertSingleTask(task);
|
|
9699
|
+
for (const parentId of autoCompleted) {
|
|
9700
|
+
const parent = data.tasks.find((t) => t.id === parentId);
|
|
9701
|
+
if (parent) await accessor.upsertSingleTask(parent);
|
|
9702
|
+
}
|
|
9703
|
+
} else {
|
|
9704
|
+
await safeSaveTaskFile(accessor, data, cwd);
|
|
9705
|
+
}
|
|
9706
|
+
await safeAppendLog(accessor, {
|
|
9707
|
+
id: `log-${Math.floor(Date.now() / 1e3)}-${(await import("node:crypto")).randomBytes(3).toString("hex")}`,
|
|
9708
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9709
|
+
action: "task_completed",
|
|
9710
|
+
taskId: options.taskId,
|
|
9711
|
+
actor: "system",
|
|
9712
|
+
details: { title: task.title, previousStatus: before.status },
|
|
9713
|
+
before: null,
|
|
9714
|
+
after: { title: task.title, previousStatus: before.status }
|
|
9715
|
+
}, cwd);
|
|
9716
|
+
} else {
|
|
9717
|
+
await saveJson(taskPath, data, { backupDir });
|
|
9718
|
+
await logOperation(logPath, "task_completed", options.taskId, {
|
|
9719
|
+
title: task.title,
|
|
9720
|
+
previousStatus: before.status
|
|
9721
|
+
});
|
|
9722
|
+
}
|
|
9723
|
+
const dependents = getDependents(options.taskId, data.tasks);
|
|
9724
|
+
const unblockedTasks = [];
|
|
9725
|
+
for (const dep of dependents) {
|
|
9726
|
+
if (dep.status === "done" || dep.status === "cancelled") continue;
|
|
9727
|
+
const stillUnresolved = getUnresolvedDeps(dep.id, data.tasks);
|
|
9728
|
+
if (stillUnresolved.length === 0) {
|
|
9729
|
+
unblockedTasks.push({ id: dep.id, title: dep.title });
|
|
9730
|
+
}
|
|
9731
|
+
}
|
|
9732
|
+
return {
|
|
9733
|
+
task,
|
|
9734
|
+
...autoCompleted.length > 0 && { autoCompleted },
|
|
9735
|
+
...unblockedTasks.length > 0 && { unblockedTasks }
|
|
9736
|
+
};
|
|
9737
|
+
}
|
|
9738
|
+
|
|
9196
9739
|
// src/core/tasks/delete.ts
|
|
9197
9740
|
init_json();
|
|
9198
9741
|
init_errors();
|
|
@@ -9498,6 +10041,16 @@ async function showTask(taskId, cwd, accessor) {
|
|
|
9498
10041
|
title: dep?.title ?? "Unknown task"
|
|
9499
10042
|
};
|
|
9500
10043
|
});
|
|
10044
|
+
const unresolved = detail.dependencyStatus.filter(
|
|
10045
|
+
(d) => d.status !== "done" && d.status !== "cancelled"
|
|
10046
|
+
);
|
|
10047
|
+
if (unresolved.length > 0) {
|
|
10048
|
+
detail.unresolvedDeps = unresolved;
|
|
10049
|
+
}
|
|
10050
|
+
}
|
|
10051
|
+
const dependents = data.tasks.filter((t) => t.depends?.includes(taskId)).map((t) => t.id);
|
|
10052
|
+
if (dependents.length > 0) {
|
|
10053
|
+
detail.dependents = dependents;
|
|
9501
10054
|
}
|
|
9502
10055
|
const path = [taskId];
|
|
9503
10056
|
let currentId = task.parentId;
|
|
@@ -9518,6 +10071,16 @@ async function showTask(taskId, cwd, accessor) {
|
|
|
9518
10071
|
// src/core/tasks/list.ts
|
|
9519
10072
|
init_json();
|
|
9520
10073
|
init_paths();
|
|
10074
|
+
function toCompact(task) {
|
|
10075
|
+
return {
|
|
10076
|
+
id: task.id,
|
|
10077
|
+
title: task.title,
|
|
10078
|
+
status: task.status,
|
|
10079
|
+
priority: task.priority,
|
|
10080
|
+
type: task.type,
|
|
10081
|
+
parentId: task.parentId
|
|
10082
|
+
};
|
|
10083
|
+
}
|
|
9521
10084
|
async function listTasks(options = {}, cwd, accessor) {
|
|
9522
10085
|
const taskPath = getTaskPath(cwd);
|
|
9523
10086
|
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(taskPath);
|
|
@@ -9694,34 +10257,19 @@ async function findTasks(options, cwd, accessor) {
|
|
|
9694
10257
|
|
|
9695
10258
|
// src/core/tasks/task-ops.ts
|
|
9696
10259
|
init_data_accessor();
|
|
9697
|
-
|
|
10260
|
+
init_file_utils();
|
|
9698
10261
|
init_status_registry();
|
|
9699
10262
|
init_deps_ready();
|
|
9700
10263
|
var PRIORITY_SCORE = {
|
|
9701
10264
|
critical: 100,
|
|
9702
10265
|
high: 75,
|
|
9703
|
-
medium: 50,
|
|
9704
|
-
low: 25
|
|
9705
|
-
};
|
|
9706
|
-
async function loadAllTasks2(projectRoot) {
|
|
9707
|
-
const accessor = await getAccessor(projectRoot);
|
|
9708
|
-
const data = await accessor.loadTaskFile();
|
|
9709
|
-
return data.tasks;
|
|
9710
|
-
}
|
|
9711
|
-
function buildBlockingChain(task, taskMap, visited = /* @__PURE__ */ new Set()) {
|
|
9712
|
-
const chain = [];
|
|
9713
|
-
if (visited.has(task.id)) return chain;
|
|
9714
|
-
visited.add(task.id);
|
|
9715
|
-
if (task.depends) {
|
|
9716
|
-
for (const depId of task.depends) {
|
|
9717
|
-
const dep = taskMap.get(depId);
|
|
9718
|
-
if (dep && dep.status !== "done" && dep.status !== "cancelled") {
|
|
9719
|
-
chain.push(depId);
|
|
9720
|
-
chain.push(...buildBlockingChain(dep, taskMap, visited));
|
|
9721
|
-
}
|
|
9722
|
-
}
|
|
9723
|
-
}
|
|
9724
|
-
return chain;
|
|
10266
|
+
medium: 50,
|
|
10267
|
+
low: 25
|
|
10268
|
+
};
|
|
10269
|
+
async function loadAllTasks2(projectRoot) {
|
|
10270
|
+
const accessor = await getAccessor(projectRoot);
|
|
10271
|
+
const data = await accessor.loadTaskFile();
|
|
10272
|
+
return data.tasks;
|
|
9725
10273
|
}
|
|
9726
10274
|
function buildTreeNode(task, childrenMap) {
|
|
9727
10275
|
const children = (childrenMap.get(task.id) ?? []).map(
|
|
@@ -9735,6 +10283,25 @@ function buildTreeNode(task, childrenMap) {
|
|
|
9735
10283
|
children
|
|
9736
10284
|
};
|
|
9737
10285
|
}
|
|
10286
|
+
function buildUpstreamTree(taskId, taskMap, visited = /* @__PURE__ */ new Set()) {
|
|
10287
|
+
const task = taskMap.get(taskId);
|
|
10288
|
+
if (!task?.depends?.length) return [];
|
|
10289
|
+
const nodes = [];
|
|
10290
|
+
for (const depId of task.depends) {
|
|
10291
|
+
if (visited.has(depId)) continue;
|
|
10292
|
+
visited.add(depId);
|
|
10293
|
+
const dep = taskMap.get(depId);
|
|
10294
|
+
if (!dep) continue;
|
|
10295
|
+
nodes.push({
|
|
10296
|
+
id: dep.id,
|
|
10297
|
+
title: dep.title,
|
|
10298
|
+
status: dep.status,
|
|
10299
|
+
type: dep.type,
|
|
10300
|
+
children: buildUpstreamTree(depId, taskMap, visited)
|
|
10301
|
+
});
|
|
10302
|
+
}
|
|
10303
|
+
return nodes;
|
|
10304
|
+
}
|
|
9738
10305
|
function countNodes(nodes) {
|
|
9739
10306
|
let count2 = nodes.length;
|
|
9740
10307
|
for (const node of nodes) {
|
|
@@ -9827,20 +10394,21 @@ async function coreTaskBlockers(projectRoot, params) {
|
|
|
9827
10394
|
return dep && dep.status !== "done" && dep.status !== "cancelled";
|
|
9828
10395
|
})
|
|
9829
10396
|
);
|
|
10397
|
+
const tasksAsTask = allTasks;
|
|
9830
10398
|
const blockerInfos = [
|
|
9831
10399
|
...blockedTasks.map((t) => ({
|
|
9832
10400
|
id: t.id,
|
|
9833
10401
|
title: t.title,
|
|
9834
10402
|
status: t.status,
|
|
9835
10403
|
depends: t.depends,
|
|
9836
|
-
blockingChain: analyze ?
|
|
10404
|
+
blockingChain: analyze ? getTransitiveBlockers(t.id, tasksAsTask) : []
|
|
9837
10405
|
})),
|
|
9838
10406
|
...depBlockedTasks.filter((t) => !blockedTasks.some((bt) => bt.id === t.id)).map((t) => ({
|
|
9839
10407
|
id: t.id,
|
|
9840
10408
|
title: t.title,
|
|
9841
10409
|
status: t.status,
|
|
9842
10410
|
depends: t.depends,
|
|
9843
|
-
blockingChain: analyze ?
|
|
10411
|
+
blockingChain: analyze ? getTransitiveBlockers(t.id, tasksAsTask) : []
|
|
9844
10412
|
}))
|
|
9845
10413
|
];
|
|
9846
10414
|
const blockerCounts = /* @__PURE__ */ new Map();
|
|
@@ -9922,7 +10490,10 @@ async function coreTaskRelatesAdd(projectRoot, taskId, relatedId, type, reason)
|
|
|
9922
10490
|
} else {
|
|
9923
10491
|
await accessor.saveTaskFile(current);
|
|
9924
10492
|
}
|
|
9925
|
-
|
|
10493
|
+
if (accessor.addRelation) {
|
|
10494
|
+
await accessor.addRelation(taskId, relatedId, type, reason);
|
|
10495
|
+
}
|
|
10496
|
+
return { from: taskId, to: relatedId, type, reason, added: true };
|
|
9926
10497
|
}
|
|
9927
10498
|
async function coreTaskAnalyze(projectRoot, taskId) {
|
|
9928
10499
|
const allTasks = await loadAllTasks2(projectRoot);
|
|
@@ -10242,7 +10813,63 @@ async function coreTaskComplexityEstimate(projectRoot, params) {
|
|
|
10242
10813
|
else size = "large";
|
|
10243
10814
|
return { size, score, factors, dependencyDepth, subtaskCount, fileCount };
|
|
10244
10815
|
}
|
|
10245
|
-
async function
|
|
10816
|
+
async function coreTaskDepsOverview(projectRoot) {
|
|
10817
|
+
const allTasks = await loadAllTasks2(projectRoot);
|
|
10818
|
+
const tasksAsTask = allTasks;
|
|
10819
|
+
const tasksWithDeps = allTasks.filter((t) => t.depends && t.depends.length > 0);
|
|
10820
|
+
const blocked = getBlockedTasks(tasksAsTask);
|
|
10821
|
+
const ready = getReadyTasks(tasksAsTask);
|
|
10822
|
+
const validation = validateDependencies(tasksAsTask);
|
|
10823
|
+
return {
|
|
10824
|
+
totalTasks: allTasks.length,
|
|
10825
|
+
tasksWithDeps: tasksWithDeps.length,
|
|
10826
|
+
blockedTasks: blocked.map((t) => ({
|
|
10827
|
+
id: t.id,
|
|
10828
|
+
title: t.title,
|
|
10829
|
+
status: t.status,
|
|
10830
|
+
unblockedBy: (t.depends ?? []).filter((depId) => {
|
|
10831
|
+
const dep = allTasks.find((x) => x.id === depId);
|
|
10832
|
+
return dep && dep.status !== "done" && dep.status !== "cancelled";
|
|
10833
|
+
})
|
|
10834
|
+
})),
|
|
10835
|
+
readyTasks: ready.filter((t) => t.status !== "done" && t.status !== "cancelled").map((t) => ({
|
|
10836
|
+
id: t.id,
|
|
10837
|
+
title: t.title,
|
|
10838
|
+
status: t.status
|
|
10839
|
+
})),
|
|
10840
|
+
validation: {
|
|
10841
|
+
valid: validation.valid,
|
|
10842
|
+
errorCount: validation.errors.length,
|
|
10843
|
+
warningCount: validation.warnings.length
|
|
10844
|
+
}
|
|
10845
|
+
};
|
|
10846
|
+
}
|
|
10847
|
+
async function coreTaskDepsCycles(projectRoot) {
|
|
10848
|
+
const allTasks = await loadAllTasks2(projectRoot);
|
|
10849
|
+
const tasksAsTask = allTasks;
|
|
10850
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
10851
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10852
|
+
const cycles = [];
|
|
10853
|
+
for (const task of allTasks) {
|
|
10854
|
+
if (visited.has(task.id)) continue;
|
|
10855
|
+
if (!task.depends?.length) continue;
|
|
10856
|
+
const cycle = detectCircularDeps(task.id, tasksAsTask);
|
|
10857
|
+
if (cycle.length > 0) {
|
|
10858
|
+
cycles.push({
|
|
10859
|
+
path: cycle,
|
|
10860
|
+
// Deduplicate: detectCircularDeps returns [A,B,C,A] where
|
|
10861
|
+
// last element closes the cycle. Use Set for robustness.
|
|
10862
|
+
tasks: [...new Set(cycle)].map((id) => {
|
|
10863
|
+
const t = taskMap.get(id);
|
|
10864
|
+
return { id, title: t?.title ?? "unknown" };
|
|
10865
|
+
})
|
|
10866
|
+
});
|
|
10867
|
+
cycle.forEach((id) => visited.add(id));
|
|
10868
|
+
}
|
|
10869
|
+
}
|
|
10870
|
+
return { hasCycles: cycles.length > 0, cycles };
|
|
10871
|
+
}
|
|
10872
|
+
async function coreTaskDepends(projectRoot, taskId, direction = "both", options) {
|
|
10246
10873
|
const allTasks = await loadAllTasks2(projectRoot);
|
|
10247
10874
|
const task = allTasks.find((t) => t.id === taskId);
|
|
10248
10875
|
if (!task) {
|
|
@@ -10266,7 +10893,31 @@ async function coreTaskDepends(projectRoot, taskId, direction = "both") {
|
|
|
10266
10893
|
}
|
|
10267
10894
|
}
|
|
10268
10895
|
}
|
|
10269
|
-
|
|
10896
|
+
const tasksAsTask = allTasks;
|
|
10897
|
+
const transitiveIds = getTransitiveBlockers(taskId, tasksAsTask);
|
|
10898
|
+
const unresolvedChain = transitiveIds.length;
|
|
10899
|
+
const leafIds = getLeafBlockers(taskId, tasksAsTask);
|
|
10900
|
+
const leafBlockers = leafIds.map((id) => {
|
|
10901
|
+
const t = taskMap.get(id);
|
|
10902
|
+
return { id: t.id, title: t.title, status: t.status };
|
|
10903
|
+
});
|
|
10904
|
+
const allDepsReady = unresolvedChain === 0;
|
|
10905
|
+
const hint = unresolvedChain > 0 ? `Run 'ct deps show ${taskId} --tree' for full dependency graph` : void 0;
|
|
10906
|
+
let upstreamTree;
|
|
10907
|
+
if (options?.tree) {
|
|
10908
|
+
upstreamTree = buildUpstreamTree(taskId, taskMap);
|
|
10909
|
+
}
|
|
10910
|
+
return {
|
|
10911
|
+
taskId,
|
|
10912
|
+
direction,
|
|
10913
|
+
upstream,
|
|
10914
|
+
downstream,
|
|
10915
|
+
unresolvedChain,
|
|
10916
|
+
leafBlockers,
|
|
10917
|
+
allDepsReady,
|
|
10918
|
+
...hint && { hint },
|
|
10919
|
+
...upstreamTree && { upstreamTree }
|
|
10920
|
+
};
|
|
10270
10921
|
}
|
|
10271
10922
|
|
|
10272
10923
|
// src/dispatch/engines/task-engine.ts
|
|
@@ -10300,17 +10951,25 @@ async function taskList(projectRoot, params) {
|
|
|
10300
10951
|
status: params?.status,
|
|
10301
10952
|
limit: params?.limit
|
|
10302
10953
|
}, projectRoot, accessor);
|
|
10954
|
+
if (params?.compact) {
|
|
10955
|
+
return { success: true, data: { tasks: result.tasks.map((t) => toCompact(t)), total: result.total } };
|
|
10956
|
+
}
|
|
10303
10957
|
return { success: true, data: { tasks: tasksToRecords(result.tasks), total: result.total } };
|
|
10304
10958
|
} catch {
|
|
10305
10959
|
return engineError("E_NOT_INITIALIZED", "Task database not initialized");
|
|
10306
10960
|
}
|
|
10307
10961
|
}
|
|
10308
|
-
async function taskFind(projectRoot, query, limit) {
|
|
10962
|
+
async function taskFind(projectRoot, query, limit, options) {
|
|
10309
10963
|
try {
|
|
10310
10964
|
const accessor = await getAccessor(projectRoot);
|
|
10311
10965
|
const findResult = await findTasks({
|
|
10312
10966
|
query,
|
|
10313
|
-
|
|
10967
|
+
id: options?.id,
|
|
10968
|
+
exact: options?.exact,
|
|
10969
|
+
status: options?.status,
|
|
10970
|
+
includeArchive: options?.includeArchive,
|
|
10971
|
+
limit: limit ?? 20,
|
|
10972
|
+
offset: options?.offset
|
|
10314
10973
|
}, projectRoot, accessor);
|
|
10315
10974
|
const results = findResult.results.map((r) => ({
|
|
10316
10975
|
id: r.id,
|
|
@@ -10413,14 +11072,33 @@ async function taskUpdate(projectRoot, taskId, updates) {
|
|
|
10413
11072
|
}
|
|
10414
11073
|
}
|
|
10415
11074
|
async function taskComplete(projectRoot, taskId, notes) {
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
11075
|
+
try {
|
|
11076
|
+
const accessor = await getAccessor(projectRoot);
|
|
11077
|
+
const result = await completeTask({ taskId, notes }, void 0, accessor);
|
|
11078
|
+
return {
|
|
11079
|
+
success: true,
|
|
11080
|
+
data: {
|
|
11081
|
+
task: result.task,
|
|
11082
|
+
...result.autoCompleted && { autoCompleted: result.autoCompleted },
|
|
11083
|
+
...result.unblockedTasks && { unblockedTasks: result.unblockedTasks }
|
|
11084
|
+
}
|
|
11085
|
+
};
|
|
11086
|
+
} catch (err) {
|
|
11087
|
+
const message = err.message;
|
|
11088
|
+
if (message.includes("already completed")) {
|
|
11089
|
+
return engineError("E_TASK_COMPLETED", message);
|
|
11090
|
+
}
|
|
11091
|
+
if (message.includes("not found")) {
|
|
11092
|
+
return engineError("E_NOT_FOUND", message);
|
|
11093
|
+
}
|
|
11094
|
+
if (message.includes("incomplete dependencies")) {
|
|
11095
|
+
return engineError("E_DEPENDENCY_ERROR", message);
|
|
11096
|
+
}
|
|
11097
|
+
if (message.includes("incomplete children")) {
|
|
11098
|
+
return engineError("E_HAS_CHILDREN", message);
|
|
11099
|
+
}
|
|
11100
|
+
return engineError("E_INTERNAL", message);
|
|
10419
11101
|
}
|
|
10420
|
-
return taskUpdate(projectRoot, taskId, {
|
|
10421
|
-
status: "done",
|
|
10422
|
-
notes: notes || void 0
|
|
10423
|
-
});
|
|
10424
11102
|
}
|
|
10425
11103
|
async function taskDelete(projectRoot, taskId, force) {
|
|
10426
11104
|
try {
|
|
@@ -10625,9 +11303,9 @@ async function taskComplexityEstimate(projectRoot, params) {
|
|
|
10625
11303
|
return engineError("E_NOT_INITIALIZED", "Task database not initialized");
|
|
10626
11304
|
}
|
|
10627
11305
|
}
|
|
10628
|
-
async function taskDepends(projectRoot, taskId, direction = "both") {
|
|
11306
|
+
async function taskDepends(projectRoot, taskId, direction = "both", tree) {
|
|
10629
11307
|
try {
|
|
10630
|
-
const result = await coreTaskDepends(projectRoot, taskId, direction);
|
|
11308
|
+
const result = await coreTaskDepends(projectRoot, taskId, direction, tree ? { tree } : void 0);
|
|
10631
11309
|
return { success: true, data: result };
|
|
10632
11310
|
} catch (err) {
|
|
10633
11311
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10637,6 +11315,24 @@ async function taskDepends(projectRoot, taskId, direction = "both") {
|
|
|
10637
11315
|
return engineError("E_NOT_INITIALIZED", "Task database not initialized");
|
|
10638
11316
|
}
|
|
10639
11317
|
}
|
|
11318
|
+
async function taskDepsOverview(projectRoot) {
|
|
11319
|
+
try {
|
|
11320
|
+
const result = await coreTaskDepsOverview(projectRoot);
|
|
11321
|
+
return { success: true, data: result };
|
|
11322
|
+
} catch (err) {
|
|
11323
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11324
|
+
return engineError("E_NOT_INITIALIZED", message);
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
async function taskDepsCycles(projectRoot) {
|
|
11328
|
+
try {
|
|
11329
|
+
const result = await coreTaskDepsCycles(projectRoot);
|
|
11330
|
+
return { success: true, data: result };
|
|
11331
|
+
} catch (err) {
|
|
11332
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11333
|
+
return engineError("E_NOT_INITIALIZED", message);
|
|
11334
|
+
}
|
|
11335
|
+
}
|
|
10640
11336
|
async function taskPlan(projectRoot) {
|
|
10641
11337
|
const { coreTaskPlan: coreTaskPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
10642
11338
|
try {
|
|
@@ -10894,7 +11590,7 @@ init_decisions();
|
|
|
10894
11590
|
init_data_accessor();
|
|
10895
11591
|
init_errors();
|
|
10896
11592
|
init_exit_codes();
|
|
10897
|
-
import { randomBytes as
|
|
11593
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
10898
11594
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "node:fs";
|
|
10899
11595
|
import { join as join10 } from "node:path";
|
|
10900
11596
|
async function recordAssumption(projectRoot, params) {
|
|
@@ -10914,7 +11610,7 @@ async function recordAssumption(projectRoot, params) {
|
|
|
10914
11610
|
const taskData = await accessor.loadTaskFile();
|
|
10915
11611
|
const current = taskData;
|
|
10916
11612
|
const sessionId = params.sessionId || current._meta?.activeSession || "default";
|
|
10917
|
-
const id = `asm-${
|
|
11613
|
+
const id = `asm-${randomBytes4(8).toString("hex")}`;
|
|
10918
11614
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10919
11615
|
const record = {
|
|
10920
11616
|
id,
|
|
@@ -10981,6 +11677,12 @@ async function computeBriefing(projectRoot, options = {}) {
|
|
|
10981
11677
|
scopeTaskIds
|
|
10982
11678
|
});
|
|
10983
11679
|
const pipelineStage = computePipelineStage(current);
|
|
11680
|
+
const warnings = [];
|
|
11681
|
+
if (currentTaskInfo?.blockedBy?.length) {
|
|
11682
|
+
warnings.push(
|
|
11683
|
+
`Focused task ${currentTaskInfo.id} is blocked by: ${currentTaskInfo.blockedBy.join(", ")}`
|
|
11684
|
+
);
|
|
11685
|
+
}
|
|
10984
11686
|
return {
|
|
10985
11687
|
lastSession,
|
|
10986
11688
|
currentTask: currentTaskInfo,
|
|
@@ -10988,7 +11690,8 @@ async function computeBriefing(projectRoot, options = {}) {
|
|
|
10988
11690
|
openBugs,
|
|
10989
11691
|
blockedTasks,
|
|
10990
11692
|
activeEpics,
|
|
10991
|
-
...pipelineStage && { pipelineStage }
|
|
11693
|
+
...pipelineStage && { pipelineStage },
|
|
11694
|
+
...warnings.length > 0 && { warnings }
|
|
10992
11695
|
};
|
|
10993
11696
|
}
|
|
10994
11697
|
function parseScope(scopeStr, current) {
|
|
@@ -11076,11 +11779,21 @@ function computeCurrentTask(current, taskMap) {
|
|
|
11076
11779
|
if (!focusTaskId) return null;
|
|
11077
11780
|
const task = taskMap.get(focusTaskId);
|
|
11078
11781
|
if (!task) return null;
|
|
11079
|
-
|
|
11782
|
+
const info = {
|
|
11080
11783
|
id: task.id,
|
|
11081
11784
|
title: task.title,
|
|
11082
11785
|
status: task.status
|
|
11083
11786
|
};
|
|
11787
|
+
if (task.depends?.length) {
|
|
11788
|
+
const unresolved = task.depends.filter((depId) => {
|
|
11789
|
+
const dep = taskMap.get(depId);
|
|
11790
|
+
return dep && dep.status !== "done" && dep.status !== "cancelled";
|
|
11791
|
+
});
|
|
11792
|
+
if (unresolved.length > 0) {
|
|
11793
|
+
info.blockedBy = unresolved;
|
|
11794
|
+
}
|
|
11795
|
+
}
|
|
11796
|
+
return info;
|
|
11084
11797
|
}
|
|
11085
11798
|
function calculateLeverage2(taskId, taskMap) {
|
|
11086
11799
|
let leverage = 0;
|
|
@@ -11227,11 +11940,11 @@ function computePipelineStage(current) {
|
|
|
11227
11940
|
}
|
|
11228
11941
|
|
|
11229
11942
|
// src/core/sessions/session-id.ts
|
|
11230
|
-
import { randomBytes as
|
|
11943
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
11231
11944
|
function generateSessionId() {
|
|
11232
11945
|
const now = /* @__PURE__ */ new Date();
|
|
11233
11946
|
const ts = now.toISOString().replace(/[-:T]/g, "").substring(0, 14);
|
|
11234
|
-
const hex =
|
|
11947
|
+
const hex = randomBytes5(3).toString("hex");
|
|
11235
11948
|
return `ses_${ts}_${hex}`;
|
|
11236
11949
|
}
|
|
11237
11950
|
|
|
@@ -11269,6 +11982,14 @@ async function startTask(taskId, cwd, accessor) {
|
|
|
11269
11982
|
{ fix: `Use 'cleo find "${taskId}"' to search` }
|
|
11270
11983
|
);
|
|
11271
11984
|
}
|
|
11985
|
+
const unresolvedDeps = getUnresolvedDeps(taskId, data.tasks);
|
|
11986
|
+
if (unresolvedDeps.length > 0) {
|
|
11987
|
+
throw new CleoError(
|
|
11988
|
+
5 /* DEPENDENCY_ERROR */,
|
|
11989
|
+
`Task ${taskId} is blocked by unresolved dependencies: ${unresolvedDeps.join(", ")}`,
|
|
11990
|
+
{ fix: `Complete blockers first: ${unresolvedDeps.map((d) => `cleo complete ${d}`).join(", ")}` }
|
|
11991
|
+
);
|
|
11992
|
+
}
|
|
11272
11993
|
const previousTask = data.focus?.currentTask ?? null;
|
|
11273
11994
|
if (!data.focus) {
|
|
11274
11995
|
data.focus = {};
|
|
@@ -11327,6 +12048,8 @@ async function stopTask(cwd, accessor) {
|
|
|
11327
12048
|
}
|
|
11328
12049
|
|
|
11329
12050
|
// src/dispatch/engines/session-engine.ts
|
|
12051
|
+
init_errors();
|
|
12052
|
+
init_exit_codes();
|
|
11330
12053
|
async function sessionStatus(projectRoot) {
|
|
11331
12054
|
try {
|
|
11332
12055
|
const accessor = await getAccessor(projectRoot);
|
|
@@ -11396,6 +12119,12 @@ async function taskStart(projectRoot, taskId) {
|
|
|
11396
12119
|
data: { taskId: result.taskId, previousTask: result.previousTask }
|
|
11397
12120
|
};
|
|
11398
12121
|
} catch (err) {
|
|
12122
|
+
if (err instanceof CleoError) {
|
|
12123
|
+
const stringCode = `E_${getExitCodeName(err.code)}`;
|
|
12124
|
+
return engineError(stringCode, err.message, {
|
|
12125
|
+
...err.fix && { fix: err.fix }
|
|
12126
|
+
});
|
|
12127
|
+
}
|
|
11399
12128
|
const message = err.message;
|
|
11400
12129
|
const code = message.includes("not found") ? "E_NOT_FOUND" : "E_NOT_INITIALIZED";
|
|
11401
12130
|
return engineError(code, message);
|
|
@@ -11860,8 +12589,8 @@ async function sessionChainShow(projectRoot, sessionId) {
|
|
|
11860
12589
|
// src/dispatch/engines/system-engine.ts
|
|
11861
12590
|
init_platform();
|
|
11862
12591
|
init_data_accessor();
|
|
11863
|
-
import { readFileSync as
|
|
11864
|
-
import { join as
|
|
12592
|
+
import { readFileSync as readFileSync13, existsSync as existsSync20, readdirSync as readdirSync5 } from "node:fs";
|
|
12593
|
+
import { join as join20, basename as basename4 } from "node:path";
|
|
11865
12594
|
|
|
11866
12595
|
// src/core/stats/index.ts
|
|
11867
12596
|
init_json();
|
|
@@ -11989,14 +12718,14 @@ init_paths();
|
|
|
11989
12718
|
// src/core/system/inject-generate.ts
|
|
11990
12719
|
init_json();
|
|
11991
12720
|
init_paths();
|
|
11992
|
-
import { readFileSync as
|
|
11993
|
-
import { join as
|
|
12721
|
+
import { readFileSync as readFileSync6, existsSync as existsSync12 } from "node:fs";
|
|
12722
|
+
import { join as join12 } from "node:path";
|
|
11994
12723
|
async function generateInjection(projectRoot, accessor) {
|
|
11995
12724
|
let version = "unknown";
|
|
11996
12725
|
try {
|
|
11997
|
-
const pkgPath =
|
|
11998
|
-
if (
|
|
11999
|
-
const pkg = JSON.parse(
|
|
12726
|
+
const pkgPath = join12(projectRoot, "package.json");
|
|
12727
|
+
if (existsSync12(pkgPath)) {
|
|
12728
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
12000
12729
|
version = pkg.version || "unknown";
|
|
12001
12730
|
}
|
|
12002
12731
|
} catch {
|
|
@@ -12011,8 +12740,8 @@ async function generateInjection(projectRoot, accessor) {
|
|
|
12011
12740
|
}
|
|
12012
12741
|
try {
|
|
12013
12742
|
const sessionsPath = getSessionsPath(projectRoot);
|
|
12014
|
-
if (
|
|
12015
|
-
const sessionsData = JSON.parse(
|
|
12743
|
+
if (existsSync12(sessionsPath)) {
|
|
12744
|
+
const sessionsData = JSON.parse(readFileSync6(sessionsPath, "utf-8"));
|
|
12016
12745
|
const active = sessionsData.sessions?.find((s) => s.status === "active");
|
|
12017
12746
|
if (active) {
|
|
12018
12747
|
activeSessionName = active.name || active.id;
|
|
@@ -12093,8 +12822,8 @@ For full protocol details, load the **ct-cleo** skill: \`cleo_query({ domain: "s
|
|
|
12093
12822
|
}
|
|
12094
12823
|
|
|
12095
12824
|
// src/core/system/health.ts
|
|
12096
|
-
import { existsSync as
|
|
12097
|
-
import { join as
|
|
12825
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
|
|
12826
|
+
import { join as join14 } from "node:path";
|
|
12098
12827
|
import { execFile as execFile3 } from "node:child_process";
|
|
12099
12828
|
import { promisify as promisify3 } from "node:util";
|
|
12100
12829
|
|
|
@@ -12106,25 +12835,25 @@ init_platform();
|
|
|
12106
12835
|
|
|
12107
12836
|
// src/core/migration/agent-outputs.ts
|
|
12108
12837
|
import {
|
|
12109
|
-
existsSync as
|
|
12110
|
-
readFileSync as
|
|
12111
|
-
writeFileSync as
|
|
12112
|
-
mkdirSync as
|
|
12838
|
+
existsSync as existsSync13,
|
|
12839
|
+
readFileSync as readFileSync7,
|
|
12840
|
+
writeFileSync as writeFileSync2,
|
|
12841
|
+
mkdirSync as mkdirSync6,
|
|
12113
12842
|
readdirSync as readdirSync3,
|
|
12114
|
-
copyFileSync,
|
|
12843
|
+
copyFileSync as copyFileSync2,
|
|
12115
12844
|
statSync as statSync3,
|
|
12116
12845
|
rmSync
|
|
12117
12846
|
} from "node:fs";
|
|
12118
|
-
import { join as
|
|
12847
|
+
import { join as join13 } from "node:path";
|
|
12119
12848
|
var CANONICAL_DIR = ".cleo/agent-outputs";
|
|
12120
12849
|
var MANIFEST_PATH_REWRITES = [
|
|
12121
12850
|
[/claudedocs\/research-outputs\//g, `${CANONICAL_DIR}/`],
|
|
12122
12851
|
[/claudedocs\/agent-outputs\//g, `${CANONICAL_DIR}/`]
|
|
12123
12852
|
];
|
|
12124
12853
|
function detectLegacyAgentOutputs(projectRoot, cleoDir) {
|
|
12125
|
-
const hasResearchOutputs =
|
|
12126
|
-
const hasLegacyAgentOutputs =
|
|
12127
|
-
const hasCanonical =
|
|
12854
|
+
const hasResearchOutputs = existsSync13(join13(projectRoot, "claudedocs", "research-outputs"));
|
|
12855
|
+
const hasLegacyAgentOutputs = existsSync13(join13(projectRoot, "claudedocs", "agent-outputs"));
|
|
12856
|
+
const hasCanonical = existsSync13(join13(cleoDir, "agent-outputs"));
|
|
12128
12857
|
const legacyPaths = [];
|
|
12129
12858
|
if (hasResearchOutputs) legacyPaths.push("claudedocs/research-outputs/");
|
|
12130
12859
|
if (hasLegacyAgentOutputs) legacyPaths.push("claudedocs/agent-outputs/");
|
|
@@ -12147,30 +12876,30 @@ function migrateAgentOutputs(projectRoot, cleoDir) {
|
|
|
12147
12876
|
summary: "No legacy output directories found"
|
|
12148
12877
|
};
|
|
12149
12878
|
}
|
|
12150
|
-
const newDir =
|
|
12879
|
+
const newDir = join13(cleoDir, "agent-outputs");
|
|
12151
12880
|
const hadCanonical = detection.hasCanonical;
|
|
12152
|
-
|
|
12881
|
+
mkdirSync6(newDir, { recursive: true });
|
|
12153
12882
|
let totalCopied = 0;
|
|
12154
12883
|
const mergedManifestLines = [];
|
|
12155
12884
|
const copiedFiles = /* @__PURE__ */ new Set();
|
|
12156
12885
|
const legacySources = [
|
|
12157
|
-
{ path:
|
|
12158
|
-
{ path:
|
|
12886
|
+
{ path: join13(projectRoot, "claudedocs", "research-outputs"), exists: detection.hasResearchOutputs },
|
|
12887
|
+
{ path: join13(projectRoot, "claudedocs", "agent-outputs"), exists: detection.hasLegacyAgentOutputs }
|
|
12159
12888
|
];
|
|
12160
12889
|
for (const source of legacySources) {
|
|
12161
12890
|
if (!source.exists) continue;
|
|
12162
12891
|
totalCopied += copyDirContents(source.path, newDir, mergedManifestLines, copiedFiles);
|
|
12163
12892
|
}
|
|
12164
|
-
const
|
|
12893
|
+
const manifestEntries2 = mergeManifests(newDir, hadCanonical, mergedManifestLines);
|
|
12165
12894
|
updateConfigPaths(cleoDir);
|
|
12166
12895
|
const removed = removeLegacyDirs(projectRoot, detection);
|
|
12167
12896
|
const parts = [`Migrated ${totalCopied} files \u2192 ${CANONICAL_DIR}/`];
|
|
12168
|
-
if (
|
|
12897
|
+
if (manifestEntries2 > 0) parts.push(`merged ${manifestEntries2} manifest entries`);
|
|
12169
12898
|
if (removed.length > 0) parts.push(`removed: ${removed.join(", ")}`);
|
|
12170
12899
|
return {
|
|
12171
12900
|
migrated: true,
|
|
12172
12901
|
filesCopied: totalCopied,
|
|
12173
|
-
manifestEntries,
|
|
12902
|
+
manifestEntries: manifestEntries2,
|
|
12174
12903
|
removed,
|
|
12175
12904
|
summary: parts.join("; ")
|
|
12176
12905
|
};
|
|
@@ -12180,19 +12909,19 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
12180
12909
|
const entries = readdirSync3(srcDir);
|
|
12181
12910
|
for (const entry of entries) {
|
|
12182
12911
|
if (entry === "MANIFEST.jsonl") {
|
|
12183
|
-
collectManifestLines(
|
|
12912
|
+
collectManifestLines(join13(srcDir, entry), manifestLines);
|
|
12184
12913
|
continue;
|
|
12185
12914
|
}
|
|
12186
|
-
const srcPath =
|
|
12187
|
-
const dstPath =
|
|
12915
|
+
const srcPath = join13(srcDir, entry);
|
|
12916
|
+
const dstPath = join13(dstDir, entry);
|
|
12188
12917
|
try {
|
|
12189
12918
|
const st = statSync3(srcPath);
|
|
12190
12919
|
if (st.isDirectory()) {
|
|
12191
|
-
|
|
12920
|
+
mkdirSync6(dstPath, { recursive: true });
|
|
12192
12921
|
for (const sf of readdirSync3(srcPath)) {
|
|
12193
12922
|
if (!copiedFiles.has(sf)) {
|
|
12194
12923
|
try {
|
|
12195
|
-
|
|
12924
|
+
copyFileSync2(join13(srcPath, sf), join13(dstPath, sf));
|
|
12196
12925
|
copiedFiles.add(sf);
|
|
12197
12926
|
count2++;
|
|
12198
12927
|
} catch {
|
|
@@ -12200,7 +12929,7 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
12200
12929
|
}
|
|
12201
12930
|
}
|
|
12202
12931
|
} else if (!copiedFiles.has(entry)) {
|
|
12203
|
-
|
|
12932
|
+
copyFileSync2(srcPath, dstPath);
|
|
12204
12933
|
copiedFiles.add(entry);
|
|
12205
12934
|
count2++;
|
|
12206
12935
|
}
|
|
@@ -12211,7 +12940,7 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
12211
12940
|
}
|
|
12212
12941
|
function collectManifestLines(manifestPath, out) {
|
|
12213
12942
|
try {
|
|
12214
|
-
const content =
|
|
12943
|
+
const content = readFileSync7(manifestPath, "utf-8");
|
|
12215
12944
|
for (const line of content.split("\n")) {
|
|
12216
12945
|
if (!line.trim()) continue;
|
|
12217
12946
|
let rewritten = line;
|
|
@@ -12224,11 +12953,11 @@ function collectManifestLines(manifestPath, out) {
|
|
|
12224
12953
|
}
|
|
12225
12954
|
}
|
|
12226
12955
|
function mergeManifests(newDir, hadCanonical, legacyLines) {
|
|
12227
|
-
const manifestPath =
|
|
12956
|
+
const manifestPath = join13(newDir, "MANIFEST.jsonl");
|
|
12228
12957
|
const existingLines = [];
|
|
12229
|
-
if (hadCanonical &&
|
|
12958
|
+
if (hadCanonical && existsSync13(manifestPath)) {
|
|
12230
12959
|
try {
|
|
12231
|
-
const existing =
|
|
12960
|
+
const existing = readFileSync7(manifestPath, "utf-8");
|
|
12232
12961
|
for (const line of existing.split("\n")) {
|
|
12233
12962
|
if (line.trim()) existingLines.push(line);
|
|
12234
12963
|
}
|
|
@@ -12255,15 +12984,15 @@ function mergeManifests(newDir, hadCanonical, legacyLines) {
|
|
|
12255
12984
|
finalLines.push(line);
|
|
12256
12985
|
}
|
|
12257
12986
|
if (finalLines.length > 0) {
|
|
12258
|
-
|
|
12987
|
+
writeFileSync2(manifestPath, finalLines.join("\n") + "\n");
|
|
12259
12988
|
}
|
|
12260
12989
|
return finalLines.length;
|
|
12261
12990
|
}
|
|
12262
12991
|
function updateConfigPaths(cleoDir) {
|
|
12263
|
-
const configPath =
|
|
12264
|
-
if (!
|
|
12992
|
+
const configPath = join13(cleoDir, "config.json");
|
|
12993
|
+
if (!existsSync13(configPath)) return;
|
|
12265
12994
|
try {
|
|
12266
|
-
const config = JSON.parse(
|
|
12995
|
+
const config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
12267
12996
|
const currentDir = config.agentOutputs?.directory ?? config.agentOutputs ?? config.research?.outputDir;
|
|
12268
12997
|
if (currentDir && currentDir !== CANONICAL_DIR) {
|
|
12269
12998
|
if (typeof config.agentOutputs === "object") {
|
|
@@ -12277,7 +13006,7 @@ function updateConfigPaths(cleoDir) {
|
|
|
12277
13006
|
delete config.research;
|
|
12278
13007
|
}
|
|
12279
13008
|
}
|
|
12280
|
-
|
|
13009
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
12281
13010
|
}
|
|
12282
13011
|
} catch {
|
|
12283
13012
|
}
|
|
@@ -12286,20 +13015,20 @@ function removeLegacyDirs(projectRoot, detection) {
|
|
|
12286
13015
|
const removed = [];
|
|
12287
13016
|
if (detection.hasResearchOutputs) {
|
|
12288
13017
|
try {
|
|
12289
|
-
rmSync(
|
|
13018
|
+
rmSync(join13(projectRoot, "claudedocs", "research-outputs"), { recursive: true, force: true });
|
|
12290
13019
|
removed.push("claudedocs/research-outputs/");
|
|
12291
13020
|
} catch {
|
|
12292
13021
|
}
|
|
12293
13022
|
}
|
|
12294
13023
|
if (detection.hasLegacyAgentOutputs) {
|
|
12295
13024
|
try {
|
|
12296
|
-
rmSync(
|
|
13025
|
+
rmSync(join13(projectRoot, "claudedocs", "agent-outputs"), { recursive: true, force: true });
|
|
12297
13026
|
removed.push("claudedocs/agent-outputs/");
|
|
12298
13027
|
} catch {
|
|
12299
13028
|
}
|
|
12300
13029
|
}
|
|
12301
|
-
const claudedocsDir =
|
|
12302
|
-
if (
|
|
13030
|
+
const claudedocsDir = join13(projectRoot, "claudedocs");
|
|
13031
|
+
if (existsSync13(claudedocsDir)) {
|
|
12303
13032
|
try {
|
|
12304
13033
|
if (readdirSync3(claudedocsDir).length === 0) {
|
|
12305
13034
|
rmSync(claudedocsDir, { recursive: true, force: true });
|
|
@@ -12316,15 +13045,15 @@ init_data_accessor();
|
|
|
12316
13045
|
var execAsync = promisify3(execFile3);
|
|
12317
13046
|
var STALE_JSON_FILES = ["todo.json", "sessions.json", "todo-archive.json"];
|
|
12318
13047
|
function getSystemHealth(projectRoot, opts) {
|
|
12319
|
-
const cleoDir =
|
|
13048
|
+
const cleoDir = join14(projectRoot, ".cleo");
|
|
12320
13049
|
const checks = [];
|
|
12321
|
-
if (
|
|
13050
|
+
if (existsSync14(cleoDir)) {
|
|
12322
13051
|
checks.push({ name: "cleo_dir", status: "pass", message: ".cleo directory exists" });
|
|
12323
13052
|
} else {
|
|
12324
13053
|
checks.push({ name: "cleo_dir", status: "fail", message: ".cleo directory not found" });
|
|
12325
13054
|
}
|
|
12326
|
-
const dbPath =
|
|
12327
|
-
if (
|
|
13055
|
+
const dbPath = join14(cleoDir, "tasks.db");
|
|
13056
|
+
if (existsSync14(dbPath)) {
|
|
12328
13057
|
try {
|
|
12329
13058
|
const dbSize = statSync4(dbPath).size;
|
|
12330
13059
|
if (dbSize > 0) {
|
|
@@ -12338,10 +13067,10 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12338
13067
|
} else {
|
|
12339
13068
|
checks.push({ name: "tasks_db", status: "fail", message: "tasks.db not found" });
|
|
12340
13069
|
}
|
|
12341
|
-
const configPath =
|
|
12342
|
-
if (
|
|
13070
|
+
const configPath = join14(cleoDir, "config.json");
|
|
13071
|
+
if (existsSync14(configPath)) {
|
|
12343
13072
|
try {
|
|
12344
|
-
JSON.parse(
|
|
13073
|
+
JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
12345
13074
|
checks.push({ name: "config_json", status: "pass", message: "config.json is valid JSON" });
|
|
12346
13075
|
} catch {
|
|
12347
13076
|
checks.push({ name: "config_json", status: "warn", message: "config.json is not valid JSON" });
|
|
@@ -12349,8 +13078,8 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12349
13078
|
} else {
|
|
12350
13079
|
checks.push({ name: "config_json", status: "warn", message: "config.json not found" });
|
|
12351
13080
|
}
|
|
12352
|
-
if (
|
|
12353
|
-
const staleFiles = STALE_JSON_FILES.filter((f) =>
|
|
13081
|
+
if (existsSync14(dbPath)) {
|
|
13082
|
+
const staleFiles = STALE_JSON_FILES.filter((f) => existsSync14(join14(cleoDir, f)));
|
|
12354
13083
|
if (staleFiles.length > 0) {
|
|
12355
13084
|
checks.push({
|
|
12356
13085
|
name: "stale_json",
|
|
@@ -12360,14 +13089,14 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12360
13089
|
}
|
|
12361
13090
|
}
|
|
12362
13091
|
if (opts?.detailed) {
|
|
12363
|
-
const logPath =
|
|
12364
|
-
if (
|
|
13092
|
+
const logPath = join14(cleoDir, "todo-log.jsonl");
|
|
13093
|
+
if (existsSync14(logPath)) {
|
|
12365
13094
|
checks.push({ name: "log_file", status: "pass", message: "todo-log.jsonl exists" });
|
|
12366
13095
|
} else {
|
|
12367
13096
|
checks.push({ name: "log_file", status: "warn", message: "todo-log.jsonl not found" });
|
|
12368
13097
|
}
|
|
12369
|
-
const backupDir =
|
|
12370
|
-
if (
|
|
13098
|
+
const backupDir = join14(cleoDir, ".backups");
|
|
13099
|
+
if (existsSync14(backupDir)) {
|
|
12371
13100
|
checks.push({ name: "backups_dir", status: "pass", message: ".backups directory exists" });
|
|
12372
13101
|
} else {
|
|
12373
13102
|
checks.push({ name: "backups_dir", status: "pass", message: "No backups directory (created on first write)" });
|
|
@@ -12375,9 +13104,9 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12375
13104
|
}
|
|
12376
13105
|
let version = "unknown";
|
|
12377
13106
|
try {
|
|
12378
|
-
const pkgPath =
|
|
12379
|
-
if (
|
|
12380
|
-
const pkg = JSON.parse(
|
|
13107
|
+
const pkgPath = join14(projectRoot, "package.json");
|
|
13108
|
+
if (existsSync14(pkgPath)) {
|
|
13109
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
12381
13110
|
version = pkg.version || "unknown";
|
|
12382
13111
|
}
|
|
12383
13112
|
} catch {
|
|
@@ -12396,34 +13125,34 @@ init_paths();
|
|
|
12396
13125
|
// src/core/system/backup.ts
|
|
12397
13126
|
init_errors();
|
|
12398
13127
|
init_exit_codes();
|
|
12399
|
-
import { readFileSync as
|
|
12400
|
-
import { join as
|
|
13128
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "node:fs";
|
|
13129
|
+
import { join as join15 } from "node:path";
|
|
12401
13130
|
function createBackup2(projectRoot, opts) {
|
|
12402
|
-
const cleoDir =
|
|
13131
|
+
const cleoDir = join15(projectRoot, ".cleo");
|
|
12403
13132
|
const btype = opts?.type || "snapshot";
|
|
12404
13133
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
12405
13134
|
const backupId = `${btype}-${timestamp.replace(/[:.]/g, "-")}`;
|
|
12406
|
-
const backupDir =
|
|
12407
|
-
if (!
|
|
12408
|
-
|
|
13135
|
+
const backupDir = join15(cleoDir, "backups", btype);
|
|
13136
|
+
if (!existsSync15(backupDir)) {
|
|
13137
|
+
mkdirSync7(backupDir, { recursive: true });
|
|
12409
13138
|
}
|
|
12410
13139
|
const filesToBackup = ["todo.json", "todo-archive.json", "sessions.json", "config.json", "todo-log.jsonl"];
|
|
12411
13140
|
const backedUp = [];
|
|
12412
13141
|
for (const file of filesToBackup) {
|
|
12413
|
-
const src =
|
|
12414
|
-
if (
|
|
12415
|
-
const dest =
|
|
13142
|
+
const src = join15(cleoDir, file);
|
|
13143
|
+
if (existsSync15(src)) {
|
|
13144
|
+
const dest = join15(backupDir, `${file}.${backupId}`);
|
|
12416
13145
|
try {
|
|
12417
|
-
const content =
|
|
12418
|
-
|
|
13146
|
+
const content = readFileSync9(src, "utf-8");
|
|
13147
|
+
writeFileSync3(dest, content, "utf-8");
|
|
12419
13148
|
backedUp.push(file);
|
|
12420
13149
|
} catch {
|
|
12421
13150
|
}
|
|
12422
13151
|
}
|
|
12423
13152
|
}
|
|
12424
|
-
const metaPath =
|
|
13153
|
+
const metaPath = join15(backupDir, `${backupId}.meta.json`);
|
|
12425
13154
|
try {
|
|
12426
|
-
|
|
13155
|
+
writeFileSync3(metaPath, JSON.stringify({
|
|
12427
13156
|
backupId,
|
|
12428
13157
|
type: btype,
|
|
12429
13158
|
timestamp,
|
|
@@ -12438,15 +13167,15 @@ function restoreBackup(projectRoot, params) {
|
|
|
12438
13167
|
if (!params.backupId) {
|
|
12439
13168
|
throw new CleoError(2 /* INVALID_INPUT */, "backupId is required");
|
|
12440
13169
|
}
|
|
12441
|
-
const cleoDir =
|
|
13170
|
+
const cleoDir = join15(projectRoot, ".cleo");
|
|
12442
13171
|
const backupTypes = ["snapshot", "safety", "migration"];
|
|
12443
13172
|
let metaPath = null;
|
|
12444
13173
|
let backupDir = null;
|
|
12445
13174
|
for (const btype of backupTypes) {
|
|
12446
|
-
const candidateMeta =
|
|
12447
|
-
if (
|
|
13175
|
+
const candidateMeta = join15(cleoDir, "backups", btype, `${params.backupId}.meta.json`);
|
|
13176
|
+
if (existsSync15(candidateMeta)) {
|
|
12448
13177
|
metaPath = candidateMeta;
|
|
12449
|
-
backupDir =
|
|
13178
|
+
backupDir = join15(cleoDir, "backups", btype);
|
|
12450
13179
|
break;
|
|
12451
13180
|
}
|
|
12452
13181
|
}
|
|
@@ -12455,17 +13184,17 @@ function restoreBackup(projectRoot, params) {
|
|
|
12455
13184
|
}
|
|
12456
13185
|
let meta;
|
|
12457
13186
|
try {
|
|
12458
|
-
meta = JSON.parse(
|
|
13187
|
+
meta = JSON.parse(readFileSync9(metaPath, "utf-8"));
|
|
12459
13188
|
} catch {
|
|
12460
13189
|
throw new CleoError(3 /* FILE_ERROR */, "Failed to read backup metadata");
|
|
12461
13190
|
}
|
|
12462
13191
|
const restored = [];
|
|
12463
13192
|
for (const file of meta.files ?? []) {
|
|
12464
|
-
const backupFile =
|
|
12465
|
-
if (
|
|
13193
|
+
const backupFile = join15(backupDir, `${file}.${params.backupId}`);
|
|
13194
|
+
if (existsSync15(backupFile)) {
|
|
12466
13195
|
try {
|
|
12467
|
-
const content =
|
|
12468
|
-
|
|
13196
|
+
const content = readFileSync9(backupFile, "utf-8");
|
|
13197
|
+
writeFileSync3(join15(cleoDir, file), content, "utf-8");
|
|
12469
13198
|
restored.push(file);
|
|
12470
13199
|
} catch {
|
|
12471
13200
|
}
|
|
@@ -12482,14 +13211,14 @@ function restoreBackup(projectRoot, params) {
|
|
|
12482
13211
|
// src/core/system/migrate.ts
|
|
12483
13212
|
init_errors();
|
|
12484
13213
|
init_exit_codes();
|
|
12485
|
-
import { readFileSync as
|
|
12486
|
-
import { join as
|
|
13214
|
+
import { readFileSync as readFileSync10, existsSync as existsSync16 } from "node:fs";
|
|
13215
|
+
import { join as join16 } from "node:path";
|
|
12487
13216
|
function getMigrationStatus(projectRoot, opts) {
|
|
12488
|
-
const taskPath =
|
|
13217
|
+
const taskPath = join16(projectRoot, ".cleo", "tasks.json");
|
|
12489
13218
|
let currentVersion = "unknown";
|
|
12490
|
-
if (
|
|
13219
|
+
if (existsSync16(taskPath)) {
|
|
12491
13220
|
try {
|
|
12492
|
-
const taskFile = JSON.parse(
|
|
13221
|
+
const taskFile = JSON.parse(readFileSync10(taskPath, "utf-8"));
|
|
12493
13222
|
currentVersion = taskFile._meta?.schemaVersion ?? taskFile.version ?? "unknown";
|
|
12494
13223
|
} catch {
|
|
12495
13224
|
throw new CleoError(3 /* FILE_ERROR */, "Failed to read tasks.json");
|
|
@@ -12509,21 +13238,21 @@ function getMigrationStatus(projectRoot, opts) {
|
|
|
12509
13238
|
// src/core/system/cleanup.ts
|
|
12510
13239
|
init_errors();
|
|
12511
13240
|
init_exit_codes();
|
|
12512
|
-
import { readFileSync as
|
|
12513
|
-
import { join as
|
|
13241
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync17, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "node:fs";
|
|
13242
|
+
import { join as join17 } from "node:path";
|
|
12514
13243
|
function cleanupSystem(projectRoot, params) {
|
|
12515
13244
|
if (!params.target) {
|
|
12516
13245
|
throw new CleoError(2 /* INVALID_INPUT */, "target is required (sessions|backups|logs|archive)");
|
|
12517
13246
|
}
|
|
12518
|
-
const cleoDir =
|
|
13247
|
+
const cleoDir = join17(projectRoot, ".cleo");
|
|
12519
13248
|
const dryRun = params.dryRun ?? false;
|
|
12520
13249
|
const items = [];
|
|
12521
13250
|
switch (params.target) {
|
|
12522
13251
|
case "sessions": {
|
|
12523
|
-
const sessPath =
|
|
12524
|
-
if (
|
|
13252
|
+
const sessPath = join17(cleoDir, "sessions.json");
|
|
13253
|
+
if (existsSync17(sessPath)) {
|
|
12525
13254
|
try {
|
|
12526
|
-
const data = JSON.parse(
|
|
13255
|
+
const data = JSON.parse(readFileSync11(sessPath, "utf-8"));
|
|
12527
13256
|
const sessions2 = data.sessions ?? [];
|
|
12528
13257
|
const stale = sessions2.filter((s) => {
|
|
12529
13258
|
if (s.status !== "active") return false;
|
|
@@ -12539,7 +13268,7 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12539
13268
|
if (!dryRun && stale.length > 0) {
|
|
12540
13269
|
const staleIds = new Set(stale.map((s) => s.id));
|
|
12541
13270
|
data.sessions = sessions2.filter((s) => !staleIds.has(s.id));
|
|
12542
|
-
|
|
13271
|
+
writeFileSync4(sessPath, JSON.stringify(data, null, 2), "utf-8");
|
|
12543
13272
|
}
|
|
12544
13273
|
} catch {
|
|
12545
13274
|
}
|
|
@@ -12547,16 +13276,16 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12547
13276
|
break;
|
|
12548
13277
|
}
|
|
12549
13278
|
case "backups": {
|
|
12550
|
-
const backupBaseDir =
|
|
12551
|
-
if (
|
|
13279
|
+
const backupBaseDir = join17(cleoDir, "backups");
|
|
13280
|
+
if (existsSync17(backupBaseDir)) {
|
|
12552
13281
|
for (const typeDir of readdirSync4(backupBaseDir)) {
|
|
12553
|
-
const fullDir =
|
|
13282
|
+
const fullDir = join17(backupBaseDir, typeDir);
|
|
12554
13283
|
try {
|
|
12555
13284
|
for (const file of readdirSync4(fullDir)) {
|
|
12556
13285
|
if (file.endsWith(".meta.json")) {
|
|
12557
|
-
const metaFilePath =
|
|
13286
|
+
const metaFilePath = join17(fullDir, file);
|
|
12558
13287
|
try {
|
|
12559
|
-
const meta = JSON.parse(
|
|
13288
|
+
const meta = JSON.parse(readFileSync11(metaFilePath, "utf-8"));
|
|
12560
13289
|
if (params.olderThan && meta.timestamp < params.olderThan) {
|
|
12561
13290
|
items.push(file.replace(".meta.json", ""));
|
|
12562
13291
|
if (!dryRun) {
|
|
@@ -12564,7 +13293,7 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12564
13293
|
for (const bf of readdirSync4(fullDir)) {
|
|
12565
13294
|
if (bf.includes(meta.backupId)) {
|
|
12566
13295
|
try {
|
|
12567
|
-
unlinkSync4(
|
|
13296
|
+
unlinkSync4(join17(fullDir, bf));
|
|
12568
13297
|
} catch {
|
|
12569
13298
|
}
|
|
12570
13299
|
}
|
|
@@ -12583,13 +13312,13 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12583
13312
|
}
|
|
12584
13313
|
case "logs": {
|
|
12585
13314
|
const auditPattern = /^audit-log-.*\.json$/;
|
|
12586
|
-
if (
|
|
13315
|
+
if (existsSync17(cleoDir)) {
|
|
12587
13316
|
for (const file of readdirSync4(cleoDir)) {
|
|
12588
13317
|
if (auditPattern.test(file)) {
|
|
12589
13318
|
items.push(file);
|
|
12590
13319
|
if (!dryRun) {
|
|
12591
13320
|
try {
|
|
12592
|
-
unlinkSync4(
|
|
13321
|
+
unlinkSync4(join17(cleoDir, file));
|
|
12593
13322
|
} catch {
|
|
12594
13323
|
}
|
|
12595
13324
|
}
|
|
@@ -12607,17 +13336,17 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12607
13336
|
// src/core/system/safestop.ts
|
|
12608
13337
|
init_errors();
|
|
12609
13338
|
init_exit_codes();
|
|
12610
|
-
import { readFileSync as
|
|
12611
|
-
import { join as
|
|
13339
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync5, existsSync as existsSync18 } from "node:fs";
|
|
13340
|
+
import { join as join18 } from "node:path";
|
|
12612
13341
|
function safestop(projectRoot, opts) {
|
|
12613
13342
|
const dryRun = opts?.dryRun ?? false;
|
|
12614
13343
|
const reason = opts?.reason ?? "Manual safestop";
|
|
12615
13344
|
let sessionEnded = false;
|
|
12616
13345
|
if (!dryRun && !opts?.noSessionEnd) {
|
|
12617
|
-
const sessPath =
|
|
12618
|
-
if (
|
|
13346
|
+
const sessPath = join18(projectRoot, ".cleo", "sessions.json");
|
|
13347
|
+
if (existsSync18(sessPath)) {
|
|
12619
13348
|
try {
|
|
12620
|
-
const data = JSON.parse(
|
|
13349
|
+
const data = JSON.parse(readFileSync12(sessPath, "utf-8"));
|
|
12621
13350
|
const sessions2 = data.sessions ?? [];
|
|
12622
13351
|
let changed = false;
|
|
12623
13352
|
for (const s of sessions2) {
|
|
@@ -12630,7 +13359,7 @@ function safestop(projectRoot, opts) {
|
|
|
12630
13359
|
}
|
|
12631
13360
|
}
|
|
12632
13361
|
if (changed) {
|
|
12633
|
-
|
|
13362
|
+
writeFileSync5(sessPath, JSON.stringify(data, null, 2), "utf-8");
|
|
12634
13363
|
}
|
|
12635
13364
|
} catch {
|
|
12636
13365
|
}
|
|
@@ -12643,9 +13372,9 @@ function safestop(projectRoot, opts) {
|
|
|
12643
13372
|
init_sequence();
|
|
12644
13373
|
|
|
12645
13374
|
// src/core/system/runtime.ts
|
|
12646
|
-
import { existsSync as
|
|
13375
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
12647
13376
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
12648
|
-
import { basename as
|
|
13377
|
+
import { basename as basename3, join as join19 } from "node:path";
|
|
12649
13378
|
import { homedir as homedir2 } from "node:os";
|
|
12650
13379
|
import { execFile as execFile4 } from "node:child_process";
|
|
12651
13380
|
import { promisify as promisify4 } from "node:util";
|
|
@@ -12679,8 +13408,8 @@ function getExpectedNaming(channel) {
|
|
|
12679
13408
|
}
|
|
12680
13409
|
}
|
|
12681
13410
|
async function parseVersionFile(dataRoot) {
|
|
12682
|
-
const versionPath =
|
|
12683
|
-
if (!
|
|
13411
|
+
const versionPath = join19(dataRoot, "VERSION");
|
|
13412
|
+
if (!existsSync19(versionPath)) return null;
|
|
12684
13413
|
let content;
|
|
12685
13414
|
try {
|
|
12686
13415
|
content = await readFile3(versionPath, "utf-8");
|
|
@@ -12709,9 +13438,9 @@ async function parseVersionFile(dataRoot) {
|
|
|
12709
13438
|
async function getPackageInfo(sourceDir) {
|
|
12710
13439
|
const candidates = [];
|
|
12711
13440
|
if (sourceDir && sourceDir !== "unknown" && sourceDir !== "npm") {
|
|
12712
|
-
candidates.push(
|
|
13441
|
+
candidates.push(join19(sourceDir, "package.json"));
|
|
12713
13442
|
}
|
|
12714
|
-
candidates.push(
|
|
13443
|
+
candidates.push(join19(process.cwd(), "package.json"));
|
|
12715
13444
|
for (const candidate of candidates) {
|
|
12716
13445
|
try {
|
|
12717
13446
|
const raw = await readFile3(candidate, "utf-8");
|
|
@@ -12735,9 +13464,9 @@ async function resolveBinaryPath(name) {
|
|
|
12735
13464
|
}
|
|
12736
13465
|
async function getRuntimeDiagnostics(options) {
|
|
12737
13466
|
const scriptPath = process.argv[1] ?? "";
|
|
12738
|
-
const invocationName =
|
|
13467
|
+
const invocationName = basename3(scriptPath || process.argv0 || "cleo");
|
|
12739
13468
|
const envChannel = normalizeChannel(process.env["CLEO_CHANNEL"]);
|
|
12740
|
-
const dataRoot = process.env["CLEO_HOME"] ??
|
|
13469
|
+
const dataRoot = process.env["CLEO_HOME"] ?? join19(homedir2(), ".cleo");
|
|
12741
13470
|
const versionInfo = await parseVersionFile(dataRoot);
|
|
12742
13471
|
const packageInfo = await getPackageInfo(versionInfo?.source);
|
|
12743
13472
|
const channel = envChannel ?? detectFromInvocation(invocationName) ?? normalizeChannel(versionInfo?.version.includes("-beta") ? "beta" : void 0) ?? detectFromDataRoot(dataRoot) ?? normalizeChannel(versionInfo?.mode.startsWith("dev") ? "dev" : void 0) ?? "stable";
|
|
@@ -12952,10 +13681,10 @@ async function systemLog(projectRoot, filters) {
|
|
|
12952
13681
|
}
|
|
12953
13682
|
async function queryAuditLogSqlite(projectRoot, filters) {
|
|
12954
13683
|
try {
|
|
12955
|
-
const { join:
|
|
12956
|
-
const { existsSync:
|
|
12957
|
-
const dbPath =
|
|
12958
|
-
if (!
|
|
13684
|
+
const { join: join45 } = await import("node:path");
|
|
13685
|
+
const { existsSync: existsSync44 } = await import("node:fs");
|
|
13686
|
+
const dbPath = join45(projectRoot, ".cleo", "tasks.db");
|
|
13687
|
+
if (!existsSync44(dbPath)) return null;
|
|
12959
13688
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
12960
13689
|
const { auditLog: auditLog2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
12961
13690
|
const { sql: sql4 } = await import("drizzle-orm");
|
|
@@ -13020,8 +13749,8 @@ async function queryAuditLogSqlite(projectRoot, filters) {
|
|
|
13020
13749
|
}
|
|
13021
13750
|
}
|
|
13022
13751
|
function queryAuditLogJsonl(projectRoot, filters) {
|
|
13023
|
-
const logPath =
|
|
13024
|
-
const raw =
|
|
13752
|
+
const logPath = getDataPath(projectRoot, "todo-log.jsonl");
|
|
13753
|
+
const raw = readLogFileEntries(logPath);
|
|
13025
13754
|
let entries = raw;
|
|
13026
13755
|
if (filters?.operation) {
|
|
13027
13756
|
entries = entries.filter((e) => e.operation === filters.operation);
|
|
@@ -13050,34 +13779,34 @@ function queryAuditLogJsonl(projectRoot, filters) {
|
|
|
13050
13779
|
}
|
|
13051
13780
|
function systemContext(projectRoot, params) {
|
|
13052
13781
|
try {
|
|
13053
|
-
const cleoDir =
|
|
13782
|
+
const cleoDir = join20(projectRoot, ".cleo");
|
|
13054
13783
|
let stateFile;
|
|
13055
13784
|
if (params?.session) {
|
|
13056
|
-
const sessionFile =
|
|
13057
|
-
stateFile =
|
|
13785
|
+
const sessionFile = join20(cleoDir, "context-states", `context-state-${params.session}.json`);
|
|
13786
|
+
stateFile = existsSync20(sessionFile) ? sessionFile : join20(cleoDir, ".context-state.json");
|
|
13058
13787
|
} else {
|
|
13059
|
-
const currentSessionPath =
|
|
13060
|
-
if (
|
|
13061
|
-
const currentSession =
|
|
13788
|
+
const currentSessionPath = join20(cleoDir, ".current-session");
|
|
13789
|
+
if (existsSync20(currentSessionPath)) {
|
|
13790
|
+
const currentSession = readFileSync13(currentSessionPath, "utf-8").trim();
|
|
13062
13791
|
if (currentSession) {
|
|
13063
|
-
const sessionFile =
|
|
13064
|
-
stateFile =
|
|
13792
|
+
const sessionFile = join20(cleoDir, "context-states", `context-state-${currentSession}.json`);
|
|
13793
|
+
stateFile = existsSync20(sessionFile) ? sessionFile : join20(cleoDir, ".context-state.json");
|
|
13065
13794
|
} else {
|
|
13066
|
-
stateFile =
|
|
13795
|
+
stateFile = join20(cleoDir, ".context-state.json");
|
|
13067
13796
|
}
|
|
13068
13797
|
} else {
|
|
13069
|
-
stateFile =
|
|
13798
|
+
stateFile = join20(cleoDir, ".context-state.json");
|
|
13070
13799
|
}
|
|
13071
13800
|
}
|
|
13072
13801
|
const sessions2 = [];
|
|
13073
|
-
const statesDir =
|
|
13074
|
-
if (
|
|
13802
|
+
const statesDir = join20(cleoDir, "context-states");
|
|
13803
|
+
if (existsSync20(statesDir)) {
|
|
13075
13804
|
for (const file of readdirSync5(statesDir)) {
|
|
13076
13805
|
if (file.startsWith("context-state-") && file.endsWith(".json")) {
|
|
13077
13806
|
try {
|
|
13078
|
-
const state = JSON.parse(
|
|
13807
|
+
const state = JSON.parse(readFileSync13(join20(statesDir, file), "utf-8"));
|
|
13079
13808
|
sessions2.push({
|
|
13080
|
-
file:
|
|
13809
|
+
file: basename4(file),
|
|
13081
13810
|
sessionId: state.sessionId ?? null,
|
|
13082
13811
|
percentage: state.contextWindow?.percentage ?? 0,
|
|
13083
13812
|
status: state.status ?? "unknown",
|
|
@@ -13088,10 +13817,10 @@ function systemContext(projectRoot, params) {
|
|
|
13088
13817
|
}
|
|
13089
13818
|
}
|
|
13090
13819
|
}
|
|
13091
|
-
const singletonFile =
|
|
13092
|
-
if (
|
|
13820
|
+
const singletonFile = join20(cleoDir, ".context-state.json");
|
|
13821
|
+
if (existsSync20(singletonFile)) {
|
|
13093
13822
|
try {
|
|
13094
|
-
const state = JSON.parse(
|
|
13823
|
+
const state = JSON.parse(readFileSync13(singletonFile, "utf-8"));
|
|
13095
13824
|
sessions2.push({
|
|
13096
13825
|
file: ".context-state.json",
|
|
13097
13826
|
sessionId: state.sessionId ?? "global",
|
|
@@ -13102,7 +13831,7 @@ function systemContext(projectRoot, params) {
|
|
|
13102
13831
|
} catch {
|
|
13103
13832
|
}
|
|
13104
13833
|
}
|
|
13105
|
-
if (!
|
|
13834
|
+
if (!existsSync20(stateFile)) {
|
|
13106
13835
|
return {
|
|
13107
13836
|
success: true,
|
|
13108
13837
|
data: {
|
|
@@ -13118,7 +13847,7 @@ function systemContext(projectRoot, params) {
|
|
|
13118
13847
|
};
|
|
13119
13848
|
}
|
|
13120
13849
|
try {
|
|
13121
|
-
const state = JSON.parse(
|
|
13850
|
+
const state = JSON.parse(readFileSync13(stateFile, "utf-8"));
|
|
13122
13851
|
const timestamp = state.timestamp;
|
|
13123
13852
|
const staleMs = state.staleAfterMs ?? 5e3;
|
|
13124
13853
|
const percentage = state.contextWindow?.percentage ?? 0;
|
|
@@ -13313,8 +14042,8 @@ init_json();
|
|
|
13313
14042
|
init_json();
|
|
13314
14043
|
init_paths();
|
|
13315
14044
|
import { chmod, mkdir as mkdir5, access as access2, writeFile as writeFile3, readFile as readFile5, copyFile as copyFile2, symlink, lstat, unlink as unlink3 } from "node:fs/promises";
|
|
13316
|
-
import { constants as fsConstants, existsSync as
|
|
13317
|
-
import { join as
|
|
14045
|
+
import { constants as fsConstants, existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync7 } from "node:fs";
|
|
14046
|
+
import { join as join25, resolve as resolve5, basename as basename5, dirname as dirname7 } from "node:path";
|
|
13318
14047
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13319
14048
|
import { createHash as createHash5 } from "node:crypto";
|
|
13320
14049
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -13330,7 +14059,7 @@ async function fileExists(path) {
|
|
|
13330
14059
|
}
|
|
13331
14060
|
}
|
|
13332
14061
|
async function stripCLEOBlocks(filePath) {
|
|
13333
|
-
if (!
|
|
14062
|
+
if (!existsSync23(filePath)) return;
|
|
13334
14063
|
const content = await readFile5(filePath, "utf8");
|
|
13335
14064
|
const stripped = content.replace(
|
|
13336
14065
|
/\n?<!-- CLEO:START -->[\s\S]*?<!-- CLEO:END -->\n?/g,
|
|
@@ -13366,31 +14095,67 @@ function createDefaultConfig() {
|
|
|
13366
14095
|
}
|
|
13367
14096
|
function getPackageRoot() {
|
|
13368
14097
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
13369
|
-
return
|
|
14098
|
+
return resolve5(dirname7(thisFile), "..", "..");
|
|
13370
14099
|
}
|
|
13371
14100
|
function getGitignoreContent() {
|
|
13372
14101
|
try {
|
|
13373
14102
|
const packageRoot = getPackageRoot();
|
|
13374
|
-
const templatePath =
|
|
13375
|
-
if (
|
|
13376
|
-
return
|
|
14103
|
+
const templatePath = join25(packageRoot, "templates", "cleo-gitignore");
|
|
14104
|
+
if (existsSync23(templatePath)) {
|
|
14105
|
+
return readFileSync16(templatePath, "utf-8");
|
|
13377
14106
|
}
|
|
13378
14107
|
} catch {
|
|
13379
14108
|
}
|
|
13380
14109
|
return CLEO_GITIGNORE_FALLBACK;
|
|
13381
14110
|
}
|
|
13382
|
-
var CLEO_GITIGNORE_FALLBACK = `#
|
|
13383
|
-
#
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
.
|
|
14111
|
+
var CLEO_GITIGNORE_FALLBACK = `# .cleo/.gitignore \u2014 Deny-by-default for CLEO project data
|
|
14112
|
+
# Ignore everything, then explicitly allow only tracked files.
|
|
14113
|
+
|
|
14114
|
+
# Step 1: Ignore everything
|
|
14115
|
+
*
|
|
14116
|
+
|
|
14117
|
+
# Allow list
|
|
14118
|
+
!.gitignore
|
|
14119
|
+
!config.json
|
|
14120
|
+
!project-context.json
|
|
14121
|
+
!project-info.json
|
|
14122
|
+
!setup-otel.sh
|
|
14123
|
+
!DATA-SAFETY-IMPLEMENTATION-SUMMARY.md
|
|
14124
|
+
!schemas/
|
|
14125
|
+
!schemas/**
|
|
14126
|
+
!templates/
|
|
14127
|
+
!templates/**
|
|
14128
|
+
!adrs/
|
|
14129
|
+
!adrs/**
|
|
14130
|
+
!rcasd/
|
|
14131
|
+
!rcasd/**
|
|
14132
|
+
!agent-outputs/
|
|
14133
|
+
!agent-outputs/**
|
|
14134
|
+
|
|
14135
|
+
# Explicit deny safety net
|
|
14136
|
+
*.db
|
|
14137
|
+
*.db-shm
|
|
14138
|
+
*.db-wal
|
|
14139
|
+
*.db-journal
|
|
14140
|
+
log.json
|
|
14141
|
+
tasks-log.jsonl
|
|
14142
|
+
todo-log.jsonl
|
|
14143
|
+
bypass-log.json
|
|
14144
|
+
qa-log.json
|
|
14145
|
+
.deps-cache/
|
|
14146
|
+
.context-alert-state.json
|
|
14147
|
+
.context-state*.json
|
|
14148
|
+
context-states/
|
|
14149
|
+
.git-checkpoint-state
|
|
14150
|
+
.migration-state.json
|
|
14151
|
+
migrations.json
|
|
14152
|
+
sync/
|
|
14153
|
+
metrics/
|
|
14154
|
+
.backups/
|
|
14155
|
+
backups/
|
|
13391
14156
|
`;
|
|
13392
14157
|
async function removeCleoFromRootGitignore(projectRoot) {
|
|
13393
|
-
const rootGitignorePath =
|
|
14158
|
+
const rootGitignorePath = join25(projectRoot, ".gitignore");
|
|
13394
14159
|
if (!await fileExists(rootGitignorePath)) {
|
|
13395
14160
|
return false;
|
|
13396
14161
|
}
|
|
@@ -13411,8 +14176,8 @@ function generateProjectHash2(projectPath) {
|
|
|
13411
14176
|
}
|
|
13412
14177
|
function getCleoVersion() {
|
|
13413
14178
|
try {
|
|
13414
|
-
const pkgPath =
|
|
13415
|
-
const pkg = JSON.parse(
|
|
14179
|
+
const pkgPath = join25(getPackageRoot(), "package.json");
|
|
14180
|
+
const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
13416
14181
|
return pkg.version ?? "0.0.0";
|
|
13417
14182
|
} catch {
|
|
13418
14183
|
return "0.0.0";
|
|
@@ -13420,9 +14185,9 @@ function getCleoVersion() {
|
|
|
13420
14185
|
}
|
|
13421
14186
|
function getInjectionTemplateContent() {
|
|
13422
14187
|
const packageRoot = getPackageRoot();
|
|
13423
|
-
const packageTemplatePath =
|
|
13424
|
-
if (
|
|
13425
|
-
return
|
|
14188
|
+
const packageTemplatePath = join25(packageRoot, "templates", "CLEO-INJECTION.md");
|
|
14189
|
+
if (existsSync23(packageTemplatePath)) {
|
|
14190
|
+
return readFileSync16(packageTemplatePath, "utf-8");
|
|
13426
14191
|
}
|
|
13427
14192
|
return null;
|
|
13428
14193
|
}
|
|
@@ -13435,27 +14200,27 @@ async function initCoreFiles(cleoDir, _projectName, force, created, skipped) {
|
|
|
13435
14200
|
await saveJson(configPath, createDefaultConfig());
|
|
13436
14201
|
created.push("config.json");
|
|
13437
14202
|
}
|
|
13438
|
-
const legacySequencePath =
|
|
14203
|
+
const legacySequencePath = join25(cleoDir, ".sequence");
|
|
13439
14204
|
try {
|
|
13440
14205
|
await unlink3(legacySequencePath);
|
|
13441
14206
|
} catch {
|
|
13442
14207
|
}
|
|
13443
|
-
const legacySequenceJsonPath =
|
|
14208
|
+
const legacySequenceJsonPath = join25(cleoDir, ".sequence.json");
|
|
13444
14209
|
try {
|
|
13445
14210
|
await unlink3(legacySequenceJsonPath);
|
|
13446
14211
|
} catch {
|
|
13447
14212
|
}
|
|
13448
|
-
const backupDir =
|
|
13449
|
-
await mkdir5(
|
|
13450
|
-
await mkdir5(
|
|
14213
|
+
const backupDir = join25(cleoDir, "backups");
|
|
14214
|
+
await mkdir5(join25(backupDir, "operational"), { recursive: true });
|
|
14215
|
+
await mkdir5(join25(backupDir, "safety"), { recursive: true });
|
|
13451
14216
|
try {
|
|
13452
14217
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
13453
|
-
await getDb2(
|
|
14218
|
+
await getDb2(join25(cleoDir, ".."));
|
|
13454
14219
|
created.push("tasks.db");
|
|
13455
14220
|
} catch (err) {
|
|
13456
14221
|
created.push(`tasks.db (deferred: ${err instanceof Error ? err.message : String(err)})`);
|
|
13457
14222
|
}
|
|
13458
|
-
const gitignorePath =
|
|
14223
|
+
const gitignorePath = join25(cleoDir, ".gitignore");
|
|
13459
14224
|
if (await fileExists(gitignorePath) && !force) {
|
|
13460
14225
|
skipped.push(".gitignore");
|
|
13461
14226
|
} else {
|
|
@@ -13464,8 +14229,8 @@ async function initCoreFiles(cleoDir, _projectName, force, created, skipped) {
|
|
|
13464
14229
|
}
|
|
13465
14230
|
}
|
|
13466
14231
|
async function initCleoGitRepo(cleoDir, created, warnings) {
|
|
13467
|
-
const cleoGitDir =
|
|
13468
|
-
if (
|
|
14232
|
+
const cleoGitDir = join25(cleoDir, ".git");
|
|
14233
|
+
if (existsSync23(cleoGitDir)) {
|
|
13469
14234
|
return;
|
|
13470
14235
|
}
|
|
13471
14236
|
const gitEnv = {
|
|
@@ -13483,11 +14248,11 @@ async function initCleoGitRepo(cleoDir, created, warnings) {
|
|
|
13483
14248
|
}
|
|
13484
14249
|
}
|
|
13485
14250
|
async function initSchemas(cleoDir, force, created, warnings) {
|
|
13486
|
-
const schemasDir =
|
|
14251
|
+
const schemasDir = join25(cleoDir, "schemas");
|
|
13487
14252
|
await mkdir5(schemasDir, { recursive: true });
|
|
13488
14253
|
const packageRoot = getPackageRoot();
|
|
13489
|
-
const sourceSchemaDir =
|
|
13490
|
-
if (!
|
|
14254
|
+
const sourceSchemaDir = join25(packageRoot, "schemas");
|
|
14255
|
+
if (!existsSync23(sourceSchemaDir)) {
|
|
13491
14256
|
warnings.push("schemas/ directory not found in package root, skipping schema installation");
|
|
13492
14257
|
return;
|
|
13493
14258
|
}
|
|
@@ -13498,9 +14263,9 @@ async function initSchemas(cleoDir, force, created, warnings) {
|
|
|
13498
14263
|
];
|
|
13499
14264
|
let copiedCount = 0;
|
|
13500
14265
|
for (const schemaFile of coreSchemas) {
|
|
13501
|
-
const sourcePath =
|
|
13502
|
-
const destPath =
|
|
13503
|
-
if (!
|
|
14266
|
+
const sourcePath = join25(sourceSchemaDir, schemaFile);
|
|
14267
|
+
const destPath = join25(schemasDir, schemaFile);
|
|
14268
|
+
if (!existsSync23(sourcePath)) {
|
|
13504
14269
|
continue;
|
|
13505
14270
|
}
|
|
13506
14271
|
if (await fileExists(destPath) && !force) {
|
|
@@ -13518,27 +14283,27 @@ async function initSchemas(cleoDir, force, created, warnings) {
|
|
|
13518
14283
|
}
|
|
13519
14284
|
}
|
|
13520
14285
|
async function initGitHooks(projRoot, force, created, warnings) {
|
|
13521
|
-
const gitHooksDir =
|
|
13522
|
-
if (!
|
|
14286
|
+
const gitHooksDir = join25(projRoot, ".git", "hooks");
|
|
14287
|
+
if (!existsSync23(join25(projRoot, ".git"))) {
|
|
13523
14288
|
warnings.push("No .git/ directory found, skipping git hook installation");
|
|
13524
14289
|
return;
|
|
13525
14290
|
}
|
|
13526
14291
|
await mkdir5(gitHooksDir, { recursive: true });
|
|
13527
14292
|
const packageRoot = getPackageRoot();
|
|
13528
|
-
const sourceDir =
|
|
13529
|
-
if (!
|
|
14293
|
+
const sourceDir = join25(packageRoot, "templates", "git-hooks");
|
|
14294
|
+
if (!existsSync23(sourceDir)) {
|
|
13530
14295
|
warnings.push("templates/git-hooks/ not found in package root, skipping git hook installation");
|
|
13531
14296
|
return;
|
|
13532
14297
|
}
|
|
13533
14298
|
const hooks = ["commit-msg", "pre-commit"];
|
|
13534
14299
|
let installedCount = 0;
|
|
13535
14300
|
for (const hook of hooks) {
|
|
13536
|
-
const sourcePath =
|
|
13537
|
-
const destPath =
|
|
13538
|
-
if (!
|
|
14301
|
+
const sourcePath = join25(sourceDir, hook);
|
|
14302
|
+
const destPath = join25(gitHooksDir, hook);
|
|
14303
|
+
if (!existsSync23(sourcePath)) {
|
|
13539
14304
|
continue;
|
|
13540
14305
|
}
|
|
13541
|
-
if (
|
|
14306
|
+
if (existsSync23(destPath) && !force) {
|
|
13542
14307
|
continue;
|
|
13543
14308
|
}
|
|
13544
14309
|
try {
|
|
@@ -13554,7 +14319,7 @@ async function initGitHooks(projRoot, force, created, warnings) {
|
|
|
13554
14319
|
}
|
|
13555
14320
|
}
|
|
13556
14321
|
async function initProjectInfo(cleoDir, projectRoot, force, created, skipped) {
|
|
13557
|
-
const projectInfoPath =
|
|
14322
|
+
const projectInfoPath = join25(cleoDir, "project-info.json");
|
|
13558
14323
|
if (await fileExists(projectInfoPath) && !force) {
|
|
13559
14324
|
skipped.push("project-info.json");
|
|
13560
14325
|
return;
|
|
@@ -13603,40 +14368,40 @@ async function initInjection(projectRoot, created, warnings) {
|
|
|
13603
14368
|
return;
|
|
13604
14369
|
}
|
|
13605
14370
|
for (const provider of providers) {
|
|
13606
|
-
const instructFile =
|
|
14371
|
+
const instructFile = join25(projectRoot, provider.pathProject, provider.instructFile);
|
|
13607
14372
|
await stripCLEOBlocks(instructFile);
|
|
13608
14373
|
}
|
|
13609
|
-
await stripCLEOBlocks(
|
|
14374
|
+
await stripCLEOBlocks(join25(projectRoot, "AGENTS.md"));
|
|
13610
14375
|
const injectionContent = buildInjectionContent2({ references: ["@AGENTS.md"] });
|
|
13611
14376
|
const results = await injectAll2(providers, projectRoot, "project", injectionContent);
|
|
13612
14377
|
const injected = [];
|
|
13613
14378
|
for (const [filePath, action] of results) {
|
|
13614
|
-
const fileName =
|
|
14379
|
+
const fileName = basename5(filePath);
|
|
13615
14380
|
injected.push(`${fileName} (${action})`);
|
|
13616
14381
|
}
|
|
13617
14382
|
if (injected.length > 0) {
|
|
13618
14383
|
created.push(`injection: ${injected.join(", ")}`);
|
|
13619
14384
|
}
|
|
13620
|
-
const agentsMdPath =
|
|
14385
|
+
const agentsMdPath = join25(projectRoot, "AGENTS.md");
|
|
13621
14386
|
const agentsMdLines = ["@~/.cleo/templates/CLEO-INJECTION.md"];
|
|
13622
|
-
const projectContextPath =
|
|
13623
|
-
if (
|
|
14387
|
+
const projectContextPath = join25(projectRoot, ".cleo", "project-context.json");
|
|
14388
|
+
if (existsSync23(projectContextPath)) {
|
|
13624
14389
|
agentsMdLines.push("@.cleo/project-context.json");
|
|
13625
14390
|
}
|
|
13626
14391
|
const agentsAction = await inject(agentsMdPath, agentsMdLines.join("\n"));
|
|
13627
14392
|
created.push(`AGENTS.md CLEO content (${agentsAction})`);
|
|
13628
14393
|
const content = getInjectionTemplateContent();
|
|
13629
14394
|
if (content) {
|
|
13630
|
-
const globalTemplatesDir =
|
|
14395
|
+
const globalTemplatesDir = join25(getCleoHome(), "templates");
|
|
13631
14396
|
await mkdir5(globalTemplatesDir, { recursive: true });
|
|
13632
|
-
const globalPath =
|
|
13633
|
-
if (!
|
|
14397
|
+
const globalPath = join25(globalTemplatesDir, "CLEO-INJECTION.md");
|
|
14398
|
+
if (!existsSync23(globalPath)) {
|
|
13634
14399
|
await writeFile3(globalPath, content);
|
|
13635
14400
|
}
|
|
13636
14401
|
}
|
|
13637
14402
|
try {
|
|
13638
|
-
const globalAgentsDir =
|
|
13639
|
-
const globalAgentsMd =
|
|
14403
|
+
const globalAgentsDir = join25(homedir4(), ".agents");
|
|
14404
|
+
const globalAgentsMd = join25(globalAgentsDir, "AGENTS.md");
|
|
13640
14405
|
await mkdir5(globalAgentsDir, { recursive: true });
|
|
13641
14406
|
await inject(globalAgentsMd, "@~/.cleo/templates/CLEO-INJECTION.md");
|
|
13642
14407
|
} catch {
|
|
@@ -13647,13 +14412,13 @@ async function initInjection(projectRoot, created, warnings) {
|
|
|
13647
14412
|
}
|
|
13648
14413
|
async function initAgentDefinition(created, warnings) {
|
|
13649
14414
|
const packageRoot = getPackageRoot();
|
|
13650
|
-
const agentSourceDir =
|
|
13651
|
-
if (!
|
|
14415
|
+
const agentSourceDir = join25(packageRoot, "agents", "cleo-subagent");
|
|
14416
|
+
if (!existsSync23(agentSourceDir)) {
|
|
13652
14417
|
warnings.push("agents/cleo-subagent/ not found in package, skipping agent definition install");
|
|
13653
14418
|
return;
|
|
13654
14419
|
}
|
|
13655
|
-
const globalAgentsDir =
|
|
13656
|
-
await mkdir5(
|
|
14420
|
+
const globalAgentsDir = join25(homedir4(), ".agents", "agents", "cleo-subagent");
|
|
14421
|
+
await mkdir5(dirname7(globalAgentsDir), { recursive: true });
|
|
13657
14422
|
try {
|
|
13658
14423
|
try {
|
|
13659
14424
|
const stat2 = await lstat(globalAgentsDir);
|
|
@@ -13669,7 +14434,7 @@ async function initAgentDefinition(created, warnings) {
|
|
|
13669
14434
|
await mkdir5(globalAgentsDir, { recursive: true });
|
|
13670
14435
|
const files = readdirSync7(agentSourceDir);
|
|
13671
14436
|
for (const file of files) {
|
|
13672
|
-
await copyFile2(
|
|
14437
|
+
await copyFile2(join25(agentSourceDir, file), join25(globalAgentsDir, file));
|
|
13673
14438
|
}
|
|
13674
14439
|
created.push("agent: cleo-subagent (copied)");
|
|
13675
14440
|
} catch (copyErr) {
|
|
@@ -13721,12 +14486,12 @@ async function initCoreSkills(created, warnings) {
|
|
|
13721
14486
|
let ctSkillsRoot = null;
|
|
13722
14487
|
try {
|
|
13723
14488
|
const packageRoot = getPackageRoot();
|
|
13724
|
-
const bundledPath =
|
|
13725
|
-
if (
|
|
14489
|
+
const bundledPath = join25(packageRoot, "packages", "ct-skills");
|
|
14490
|
+
if (existsSync23(join25(bundledPath, "skills.json"))) {
|
|
13726
14491
|
ctSkillsRoot = bundledPath;
|
|
13727
14492
|
} else {
|
|
13728
|
-
const ctSkillsPath =
|
|
13729
|
-
if (
|
|
14493
|
+
const ctSkillsPath = join25(packageRoot, "node_modules", "@cleocode", "ct-skills");
|
|
14494
|
+
if (existsSync23(join25(ctSkillsPath, "skills.json"))) {
|
|
13730
14495
|
ctSkillsRoot = ctSkillsPath;
|
|
13731
14496
|
}
|
|
13732
14497
|
}
|
|
@@ -13741,14 +14506,14 @@ async function initCoreSkills(created, warnings) {
|
|
|
13741
14506
|
} catch {
|
|
13742
14507
|
warnings.push("Failed to register skill library with CAAMP");
|
|
13743
14508
|
}
|
|
13744
|
-
const catalogPath =
|
|
13745
|
-
const catalog2 = JSON.parse(
|
|
14509
|
+
const catalogPath = join25(ctSkillsRoot, "skills.json");
|
|
14510
|
+
const catalog2 = JSON.parse(readFileSync16(catalogPath, "utf-8"));
|
|
13746
14511
|
const skills = catalog2.skills ?? [];
|
|
13747
14512
|
const coreSkills = skills.filter((s) => s.tier <= 2);
|
|
13748
14513
|
const installed = [];
|
|
13749
14514
|
for (const skill of coreSkills) {
|
|
13750
|
-
const skillSourceDir =
|
|
13751
|
-
if (!
|
|
14515
|
+
const skillSourceDir = dirname7(join25(ctSkillsRoot, skill.path));
|
|
14516
|
+
if (!existsSync23(skillSourceDir)) {
|
|
13752
14517
|
continue;
|
|
13753
14518
|
}
|
|
13754
14519
|
try {
|
|
@@ -13783,7 +14548,7 @@ async function initProjectDetect(cleoDir, projectRoot, created, warnings) {
|
|
|
13783
14548
|
try {
|
|
13784
14549
|
const { detectProjectType: detectProjectType2 } = await Promise.resolve().then(() => (init_project_detect(), project_detect_exports));
|
|
13785
14550
|
const info = detectProjectType2(projectRoot);
|
|
13786
|
-
const contextPath =
|
|
14551
|
+
const contextPath = join25(cleoDir, "project-context.json");
|
|
13787
14552
|
const context = {
|
|
13788
14553
|
...info,
|
|
13789
14554
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -13856,8 +14621,8 @@ async function initProject(opts = {}) {
|
|
|
13856
14621
|
async function getVersion(projectRoot) {
|
|
13857
14622
|
const root = projectRoot ?? getProjectRoot();
|
|
13858
14623
|
const versionPaths = [
|
|
13859
|
-
|
|
13860
|
-
|
|
14624
|
+
join25(root, "VERSION"),
|
|
14625
|
+
join25(root, "..", "VERSION")
|
|
13861
14626
|
];
|
|
13862
14627
|
for (const versionPath of versionPaths) {
|
|
13863
14628
|
try {
|
|
@@ -13869,7 +14634,7 @@ async function getVersion(projectRoot) {
|
|
|
13869
14634
|
} catch {
|
|
13870
14635
|
}
|
|
13871
14636
|
}
|
|
13872
|
-
const pkg = await readJson(
|
|
14637
|
+
const pkg = await readJson(join25(root, "package.json"));
|
|
13873
14638
|
if (pkg?.version) {
|
|
13874
14639
|
return { version: pkg.version };
|
|
13875
14640
|
}
|
|
@@ -13912,8 +14677,8 @@ async function getVersion2(projectRoot) {
|
|
|
13912
14677
|
init_paths();
|
|
13913
14678
|
init_file_utils();
|
|
13914
14679
|
init_schema();
|
|
13915
|
-
import { existsSync as
|
|
13916
|
-
import { join as
|
|
14680
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "node:fs";
|
|
14681
|
+
import { join as join26 } from "node:path";
|
|
13917
14682
|
|
|
13918
14683
|
// src/core/lifecycle/stages.ts
|
|
13919
14684
|
var PIPELINE_STAGES = [
|
|
@@ -14059,7 +14824,7 @@ var DEFAULT_LIFECYCLE_DATA_DIR = "rcasd";
|
|
|
14059
14824
|
function resolveLifecycleDir(epicId, cwd) {
|
|
14060
14825
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
14061
14826
|
for (const dirName of LIFECYCLE_DATA_DIRS) {
|
|
14062
|
-
if (
|
|
14827
|
+
if (existsSync24(join26(cleoDir, dirName, epicId))) {
|
|
14063
14828
|
return dirName;
|
|
14064
14829
|
}
|
|
14065
14830
|
}
|
|
@@ -14068,18 +14833,18 @@ function resolveLifecycleDir(epicId, cwd) {
|
|
|
14068
14833
|
function getRcsdDir(epicId, cwd) {
|
|
14069
14834
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
14070
14835
|
const dirName = resolveLifecycleDir(epicId, cwd);
|
|
14071
|
-
return
|
|
14836
|
+
return join26(cleoDir, dirName, epicId);
|
|
14072
14837
|
}
|
|
14073
14838
|
function getRcsdManifestPath(epicId, cwd) {
|
|
14074
|
-
return
|
|
14839
|
+
return join26(getRcsdDir(epicId, cwd), "_manifest.json");
|
|
14075
14840
|
}
|
|
14076
14841
|
function readManifestSync(epicId, cwd) {
|
|
14077
|
-
return
|
|
14842
|
+
return readJsonFile(getRcsdManifestPath(epicId, cwd));
|
|
14078
14843
|
}
|
|
14079
14844
|
function writeManifestSync(epicId, manifest, cwd) {
|
|
14080
14845
|
const dir = getRcsdDir(epicId, cwd);
|
|
14081
|
-
if (!
|
|
14082
|
-
|
|
14846
|
+
if (!existsSync24(dir)) {
|
|
14847
|
+
mkdirSync8(dir, { recursive: true });
|
|
14083
14848
|
}
|
|
14084
14849
|
writeJsonFileAtomic(getRcsdManifestPath(epicId, cwd), manifest);
|
|
14085
14850
|
}
|
|
@@ -14533,16 +15298,16 @@ init_platform();
|
|
|
14533
15298
|
// src/core/validation/validate-ops.ts
|
|
14534
15299
|
init_paths();
|
|
14535
15300
|
init_data_accessor();
|
|
14536
|
-
import { readFileSync as
|
|
15301
|
+
import { readFileSync as readFileSync18, existsSync as existsSync26, appendFileSync as appendFileSync3, mkdirSync as mkdirSync9 } from "node:fs";
|
|
14537
15302
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
14538
|
-
import { join as
|
|
15303
|
+
import { join as join28, dirname as dirname8, resolve as resolve6 } from "node:path";
|
|
14539
15304
|
|
|
14540
|
-
// src/
|
|
15305
|
+
// src/core/validation/schema-validator.ts
|
|
14541
15306
|
init_validation_schemas();
|
|
14542
15307
|
import AjvModule2 from "ajv";
|
|
14543
15308
|
import addFormatsModule2 from "ajv-formats";
|
|
14544
|
-
import { readFileSync as
|
|
14545
|
-
import { join as
|
|
15309
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
15310
|
+
import { join as join27 } from "path";
|
|
14546
15311
|
var Ajv2 = AjvModule2.default || AjvModule2;
|
|
14547
15312
|
var addFormats2 = addFormatsModule2.default || addFormatsModule2;
|
|
14548
15313
|
var schemaCache2 = /* @__PURE__ */ new Map();
|
|
@@ -14566,14 +15331,14 @@ function resolveSchemaPath2(schemaType) {
|
|
|
14566
15331
|
const filename = `${schemaType}.schema.json`;
|
|
14567
15332
|
const projectRoot = process.env.CLEO_ROOT || process.cwd();
|
|
14568
15333
|
const paths = [
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
// relative from dist/
|
|
14572
|
-
|
|
14573
|
-
// relative from dist/
|
|
15334
|
+
join27(projectRoot, "schemas", filename),
|
|
15335
|
+
join27(__dirname, "..", "..", "..", "schemas", filename),
|
|
15336
|
+
// relative from dist/core/validation/
|
|
15337
|
+
join27(__dirname, "..", "..", "schemas", filename)
|
|
15338
|
+
// relative from dist/core/
|
|
14574
15339
|
];
|
|
14575
15340
|
for (const p of paths) {
|
|
14576
|
-
if (
|
|
15341
|
+
if (existsSync25(p)) {
|
|
14577
15342
|
return p;
|
|
14578
15343
|
}
|
|
14579
15344
|
}
|
|
@@ -14589,7 +15354,7 @@ function getValidator(schemaType) {
|
|
|
14589
15354
|
return null;
|
|
14590
15355
|
}
|
|
14591
15356
|
try {
|
|
14592
|
-
const schemaContent =
|
|
15357
|
+
const schemaContent = readFileSync17(schemaPath, "utf-8");
|
|
14593
15358
|
const schema = JSON.parse(schemaContent);
|
|
14594
15359
|
const ajv = getAjv2();
|
|
14595
15360
|
const validate = ajv.compile(schema);
|
|
@@ -14627,7 +15392,7 @@ function validateSchema(schemaType, data) {
|
|
|
14627
15392
|
return { valid: false, errors };
|
|
14628
15393
|
}
|
|
14629
15394
|
|
|
14630
|
-
// src/
|
|
15395
|
+
// src/core/validation/validation-rules.ts
|
|
14631
15396
|
function validateTitleDescription(title, description) {
|
|
14632
15397
|
const violations = [];
|
|
14633
15398
|
if (!title || title.trim().length === 0) {
|
|
@@ -14761,35 +15526,6 @@ function hasErrors(violations) {
|
|
|
14761
15526
|
return violations.some((v) => v.severity === "error");
|
|
14762
15527
|
}
|
|
14763
15528
|
|
|
14764
|
-
// src/core/tasks/dependency-check.ts
|
|
14765
|
-
function detectCircularDeps(taskId, tasks2) {
|
|
14766
|
-
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
14767
|
-
const visited = /* @__PURE__ */ new Set();
|
|
14768
|
-
const recursionStack = /* @__PURE__ */ new Set();
|
|
14769
|
-
const path = [];
|
|
14770
|
-
function dfs(id) {
|
|
14771
|
-
visited.add(id);
|
|
14772
|
-
recursionStack.add(id);
|
|
14773
|
-
path.push(id);
|
|
14774
|
-
const task = taskMap.get(id);
|
|
14775
|
-
if (task?.depends) {
|
|
14776
|
-
for (const depId of task.depends) {
|
|
14777
|
-
if (!visited.has(depId)) {
|
|
14778
|
-
const cycle = dfs(depId);
|
|
14779
|
-
if (cycle.length > 0) return cycle;
|
|
14780
|
-
} else if (recursionStack.has(depId)) {
|
|
14781
|
-
const cycleStart = path.indexOf(depId);
|
|
14782
|
-
return [...path.slice(cycleStart), depId];
|
|
14783
|
-
}
|
|
14784
|
-
}
|
|
14785
|
-
}
|
|
14786
|
-
path.pop();
|
|
14787
|
-
recursionStack.delete(id);
|
|
14788
|
-
return [];
|
|
14789
|
-
}
|
|
14790
|
-
return dfs(taskId);
|
|
14791
|
-
}
|
|
14792
|
-
|
|
14793
15529
|
// src/core/validation/validate-ops.ts
|
|
14794
15530
|
init_json();
|
|
14795
15531
|
init_status_registry();
|
|
@@ -14798,9 +15534,9 @@ init_status_registry();
|
|
|
14798
15534
|
import { isNull as isNull2, sql as sql3 } from "drizzle-orm";
|
|
14799
15535
|
|
|
14800
15536
|
// src/core/validation/validate-ops.ts
|
|
14801
|
-
function
|
|
15537
|
+
function readJsonFile2(filePath) {
|
|
14802
15538
|
try {
|
|
14803
|
-
const raw =
|
|
15539
|
+
const raw = readFileSync18(filePath, "utf-8");
|
|
14804
15540
|
return JSON.parse(raw);
|
|
14805
15541
|
} catch {
|
|
14806
15542
|
return null;
|
|
@@ -14815,11 +15551,11 @@ async function coreValidateSchema(type, data, projectRoot) {
|
|
|
14815
15551
|
throw new Error(`Unknown schema type: ${type}. Valid types: ${validTypes.join(", ")}`);
|
|
14816
15552
|
}
|
|
14817
15553
|
if (type === "config") {
|
|
14818
|
-
const filePath =
|
|
14819
|
-
if (!
|
|
15554
|
+
const filePath = join28(projectRoot, ".cleo", "config.json");
|
|
15555
|
+
if (!existsSync26(filePath)) {
|
|
14820
15556
|
throw new Error("File not found: .cleo/config.json");
|
|
14821
15557
|
}
|
|
14822
|
-
const configData = data ??
|
|
15558
|
+
const configData = data ?? readJsonFile2(filePath);
|
|
14823
15559
|
const result = validateSchema("config", configData);
|
|
14824
15560
|
return { type, valid: result.valid, errors: result.errors, errorCount: result.errors.length };
|
|
14825
15561
|
}
|
|
@@ -14953,7 +15689,7 @@ async function coreValidateProtocol(taskId, protocolType, projectRoot) {
|
|
|
14953
15689
|
}
|
|
14954
15690
|
function coreValidateManifest(projectRoot) {
|
|
14955
15691
|
const manifestPath = getManifestPath(projectRoot);
|
|
14956
|
-
if (!
|
|
15692
|
+
if (!existsSync26(manifestPath)) {
|
|
14957
15693
|
return {
|
|
14958
15694
|
valid: true,
|
|
14959
15695
|
totalEntries: 0,
|
|
@@ -14963,7 +15699,7 @@ function coreValidateManifest(projectRoot) {
|
|
|
14963
15699
|
message: "No manifest file found"
|
|
14964
15700
|
};
|
|
14965
15701
|
}
|
|
14966
|
-
const content =
|
|
15702
|
+
const content = readFileSync18(manifestPath, "utf-8");
|
|
14967
15703
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
14968
15704
|
let validCount = 0;
|
|
14969
15705
|
let invalidCount = 0;
|
|
@@ -15003,11 +15739,11 @@ function coreValidateOutput(filePath, taskId, projectRoot) {
|
|
|
15003
15739
|
if (!filePath) {
|
|
15004
15740
|
throw new Error("filePath is required");
|
|
15005
15741
|
}
|
|
15006
|
-
const fullPath =
|
|
15007
|
-
if (!
|
|
15742
|
+
const fullPath = resolve6(projectRoot, filePath);
|
|
15743
|
+
if (!existsSync26(fullPath)) {
|
|
15008
15744
|
throw new Error(`Output file not found: ${filePath}`);
|
|
15009
15745
|
}
|
|
15010
|
-
const content =
|
|
15746
|
+
const content = readFileSync18(fullPath, "utf-8");
|
|
15011
15747
|
const issues = [];
|
|
15012
15748
|
if (!content.includes("# ")) {
|
|
15013
15749
|
issues.push({ code: "O_MISSING_TITLE", message: "Output file should have a markdown title", severity: "warning" });
|
|
@@ -15027,11 +15763,11 @@ function coreValidateOutput(filePath, taskId, projectRoot) {
|
|
|
15027
15763
|
};
|
|
15028
15764
|
}
|
|
15029
15765
|
function parseComplianceEntries(projectRoot) {
|
|
15030
|
-
const compliancePath =
|
|
15031
|
-
if (!
|
|
15766
|
+
const compliancePath = join28(projectRoot, ".cleo", "metrics", "COMPLIANCE.jsonl");
|
|
15767
|
+
if (!existsSync26(compliancePath)) {
|
|
15032
15768
|
return [];
|
|
15033
15769
|
}
|
|
15034
|
-
const content =
|
|
15770
|
+
const content = readFileSync18(compliancePath, "utf-8");
|
|
15035
15771
|
const entries = [];
|
|
15036
15772
|
for (const line of content.split("\n")) {
|
|
15037
15773
|
const trimmed = line.trim();
|
|
@@ -15091,10 +15827,10 @@ function coreComplianceRecord(taskId, result, protocol, violations, projectRoot)
|
|
|
15091
15827
|
if (!validResults.includes(result)) {
|
|
15092
15828
|
throw new Error(`Invalid result: ${result}. Valid: ${validResults.join(", ")}`);
|
|
15093
15829
|
}
|
|
15094
|
-
const compliancePath =
|
|
15095
|
-
const dir =
|
|
15096
|
-
if (!
|
|
15097
|
-
|
|
15830
|
+
const compliancePath = join28(projectRoot, ".cleo", "metrics", "COMPLIANCE.jsonl");
|
|
15831
|
+
const dir = dirname8(compliancePath);
|
|
15832
|
+
if (!existsSync26(dir)) {
|
|
15833
|
+
mkdirSync9(dir, { recursive: true });
|
|
15098
15834
|
}
|
|
15099
15835
|
const entry = {
|
|
15100
15836
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15113,10 +15849,10 @@ function coreComplianceRecord(taskId, result, protocol, violations, projectRoot)
|
|
|
15113
15849
|
};
|
|
15114
15850
|
}
|
|
15115
15851
|
function coreTestStatus(projectRoot) {
|
|
15116
|
-
const testDir =
|
|
15117
|
-
const mcpTestDir =
|
|
15118
|
-
const hasBatsTests =
|
|
15119
|
-
const hasMcpTests =
|
|
15852
|
+
const testDir = join28(projectRoot, "tests");
|
|
15853
|
+
const mcpTestDir = join28(projectRoot, "src", "mcp", "__tests__");
|
|
15854
|
+
const hasBatsTests = existsSync26(testDir);
|
|
15855
|
+
const hasMcpTests = existsSync26(mcpTestDir);
|
|
15120
15856
|
return {
|
|
15121
15857
|
batsTests: {
|
|
15122
15858
|
available: hasBatsTests,
|
|
@@ -15258,8 +15994,8 @@ async function coreCoherenceCheck(projectRoot) {
|
|
|
15258
15994
|
};
|
|
15259
15995
|
}
|
|
15260
15996
|
function coreTestRun(params, projectRoot) {
|
|
15261
|
-
const hasVitest =
|
|
15262
|
-
const hasBats =
|
|
15997
|
+
const hasVitest = existsSync26(join28(projectRoot, "node_modules", ".bin", "vitest"));
|
|
15998
|
+
const hasBats = existsSync26(join28(projectRoot, "tests"));
|
|
15263
15999
|
if (!hasVitest && !hasBats) {
|
|
15264
16000
|
return {
|
|
15265
16001
|
ran: false,
|
|
@@ -15305,14 +16041,14 @@ function coreTestRun(params, projectRoot) {
|
|
|
15305
16041
|
}
|
|
15306
16042
|
}
|
|
15307
16043
|
function coreTestCoverage(projectRoot) {
|
|
15308
|
-
const coveragePath =
|
|
15309
|
-
if (!
|
|
16044
|
+
const coveragePath = join28(projectRoot, "coverage", "coverage-summary.json");
|
|
16045
|
+
if (!existsSync26(coveragePath)) {
|
|
15310
16046
|
return {
|
|
15311
16047
|
available: false,
|
|
15312
16048
|
message: "No coverage data found. Run tests with coverage first."
|
|
15313
16049
|
};
|
|
15314
16050
|
}
|
|
15315
|
-
const coverageData =
|
|
16051
|
+
const coverageData = readJsonFile2(coveragePath);
|
|
15316
16052
|
if (!coverageData) {
|
|
15317
16053
|
throw new Error("Failed to read coverage data");
|
|
15318
16054
|
}
|
|
@@ -15444,7 +16180,7 @@ function validateTestCoverage(projectRoot) {
|
|
|
15444
16180
|
init_platform();
|
|
15445
16181
|
init_paths();
|
|
15446
16182
|
init_data_accessor();
|
|
15447
|
-
import { readFileSync as
|
|
16183
|
+
import { readFileSync as readFileSync21, existsSync as existsSync29 } from "node:fs";
|
|
15448
16184
|
|
|
15449
16185
|
// src/core/orchestration/index.ts
|
|
15450
16186
|
init_json();
|
|
@@ -15573,7 +16309,7 @@ async function analyzeEpic(epicId, cwd, accessor) {
|
|
|
15573
16309
|
completedTasks
|
|
15574
16310
|
};
|
|
15575
16311
|
}
|
|
15576
|
-
async function
|
|
16312
|
+
async function getReadyTasks2(epicId, cwd, accessor) {
|
|
15577
16313
|
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(getTaskPath(cwd));
|
|
15578
16314
|
const childTasks = data.tasks.filter((t) => t.parentId === epicId);
|
|
15579
16315
|
const completedIds = new Set(
|
|
@@ -15593,7 +16329,7 @@ async function getReadyTasks(epicId, cwd, accessor) {
|
|
|
15593
16329
|
});
|
|
15594
16330
|
}
|
|
15595
16331
|
async function getNextTask(epicId, cwd, accessor) {
|
|
15596
|
-
const readyTasks = await
|
|
16332
|
+
const readyTasks = await getReadyTasks2(epicId, cwd, accessor);
|
|
15597
16333
|
const ready = readyTasks.filter((t) => t.ready);
|
|
15598
16334
|
if (ready.length === 0) return null;
|
|
15599
16335
|
return ready[0];
|
|
@@ -15954,27 +16690,27 @@ async function getUnblockOpportunities(cwd, accessor) {
|
|
|
15954
16690
|
// src/core/orchestration/parallel.ts
|
|
15955
16691
|
init_errors();
|
|
15956
16692
|
init_exit_codes();
|
|
15957
|
-
import { readFileSync as
|
|
15958
|
-
import { join as
|
|
16693
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync6, existsSync as existsSync27, mkdirSync as mkdirSync10 } from "node:fs";
|
|
16694
|
+
import { join as join29, dirname as dirname9 } from "node:path";
|
|
15959
16695
|
init_json();
|
|
15960
16696
|
init_paths();
|
|
15961
16697
|
function getParallelStatePath(projectRoot) {
|
|
15962
|
-
return
|
|
16698
|
+
return join29(projectRoot, ".cleo", "parallel-state.json");
|
|
15963
16699
|
}
|
|
15964
16700
|
function readParallelState(projectRoot) {
|
|
15965
16701
|
const statePath = getParallelStatePath(projectRoot);
|
|
15966
|
-
if (!
|
|
16702
|
+
if (!existsSync27(statePath)) return { active: false };
|
|
15967
16703
|
try {
|
|
15968
|
-
return JSON.parse(
|
|
16704
|
+
return JSON.parse(readFileSync19(statePath, "utf-8"));
|
|
15969
16705
|
} catch {
|
|
15970
16706
|
return { active: false };
|
|
15971
16707
|
}
|
|
15972
16708
|
}
|
|
15973
16709
|
function writeParallelState(state, projectRoot) {
|
|
15974
16710
|
const statePath = getParallelStatePath(projectRoot);
|
|
15975
|
-
const dir =
|
|
15976
|
-
if (!
|
|
15977
|
-
|
|
16711
|
+
const dir = dirname9(statePath);
|
|
16712
|
+
if (!existsSync27(dir)) mkdirSync10(dir, { recursive: true });
|
|
16713
|
+
writeFileSync6(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
15978
16714
|
}
|
|
15979
16715
|
async function startParallelExecution(epicId, wave, cwd, accessor) {
|
|
15980
16716
|
const projectRoot = cwd ?? process.cwd();
|
|
@@ -16109,8 +16845,8 @@ async function validateSpawnReadiness(taskId, cwd, accessor) {
|
|
|
16109
16845
|
// src/core/orchestration/bootstrap.ts
|
|
16110
16846
|
init_json();
|
|
16111
16847
|
init_paths();
|
|
16112
|
-
import { readFileSync as
|
|
16113
|
-
import { join as
|
|
16848
|
+
import { readFileSync as readFileSync20, existsSync as existsSync28 } from "node:fs";
|
|
16849
|
+
import { join as join30 } from "node:path";
|
|
16114
16850
|
async function buildBrainState(projectRoot, opts, accessor) {
|
|
16115
16851
|
const speed = opts?.speed || "fast";
|
|
16116
16852
|
const brain = {
|
|
@@ -16122,8 +16858,8 @@ async function buildBrainState(projectRoot, opts, accessor) {
|
|
|
16122
16858
|
};
|
|
16123
16859
|
try {
|
|
16124
16860
|
const sessionsPath = getSessionsPath(projectRoot);
|
|
16125
|
-
if (
|
|
16126
|
-
const sessionsData = JSON.parse(
|
|
16861
|
+
if (existsSync28(sessionsPath)) {
|
|
16862
|
+
const sessionsData = JSON.parse(readFileSync20(sessionsPath, "utf-8"));
|
|
16127
16863
|
const activeSession = (sessionsData.sessions ?? []).find(
|
|
16128
16864
|
(s) => s.status === "active"
|
|
16129
16865
|
);
|
|
@@ -16178,9 +16914,9 @@ async function buildBrainState(projectRoot, opts, accessor) {
|
|
|
16178
16914
|
blockedBy: b.depends || []
|
|
16179
16915
|
}));
|
|
16180
16916
|
try {
|
|
16181
|
-
const decisionLogPath =
|
|
16182
|
-
if (
|
|
16183
|
-
const content =
|
|
16917
|
+
const decisionLogPath = join30(projectRoot, ".cleo", "decision-log.jsonl");
|
|
16918
|
+
if (existsSync28(decisionLogPath)) {
|
|
16919
|
+
const content = readFileSync20(decisionLogPath, "utf-8").trim();
|
|
16184
16920
|
if (content) {
|
|
16185
16921
|
const entries = content.split("\n").filter((l) => l.trim()).map((l) => {
|
|
16186
16922
|
try {
|
|
@@ -16200,9 +16936,9 @@ async function buildBrainState(projectRoot, opts, accessor) {
|
|
|
16200
16936
|
} catch {
|
|
16201
16937
|
}
|
|
16202
16938
|
try {
|
|
16203
|
-
const contextStatePath =
|
|
16204
|
-
if (
|
|
16205
|
-
const state = JSON.parse(
|
|
16939
|
+
const contextStatePath = join30(projectRoot, ".cleo", ".context-state.json");
|
|
16940
|
+
if (existsSync28(contextStatePath)) {
|
|
16941
|
+
const state = JSON.parse(readFileSync20(contextStatePath, "utf-8"));
|
|
16206
16942
|
const percentage = state.contextWindow?.percentage ?? 0;
|
|
16207
16943
|
const factors = [];
|
|
16208
16944
|
if (percentage > 80) factors.push("high_context_usage");
|
|
@@ -16351,7 +17087,7 @@ async function orchestrateReady(epicId, projectRoot) {
|
|
|
16351
17087
|
try {
|
|
16352
17088
|
const root = projectRoot || resolveProjectRoot();
|
|
16353
17089
|
const accessor = await getAccessor(root);
|
|
16354
|
-
const readyTasks = await
|
|
17090
|
+
const readyTasks = await getReadyTasks2(epicId, root, accessor);
|
|
16355
17091
|
const ready = readyTasks.filter((t) => t.ready);
|
|
16356
17092
|
return {
|
|
16357
17093
|
success: true,
|
|
@@ -16390,7 +17126,7 @@ async function orchestrateNext(epicId, projectRoot) {
|
|
|
16390
17126
|
}
|
|
16391
17127
|
};
|
|
16392
17128
|
}
|
|
16393
|
-
const readyTasks = await
|
|
17129
|
+
const readyTasks = await getReadyTasks2(epicId, root, accessor);
|
|
16394
17130
|
const ready = readyTasks.filter((t) => t.ready);
|
|
16395
17131
|
return {
|
|
16396
17132
|
success: true,
|
|
@@ -16430,11 +17166,11 @@ async function orchestrateContext(epicId, projectRoot) {
|
|
|
16430
17166
|
}
|
|
16431
17167
|
const estimatedTokens = taskCount * 100;
|
|
16432
17168
|
const manifestPath = getManifestPath(root);
|
|
16433
|
-
let
|
|
16434
|
-
if (
|
|
17169
|
+
let manifestEntries2 = 0;
|
|
17170
|
+
if (existsSync29(manifestPath)) {
|
|
16435
17171
|
try {
|
|
16436
|
-
const content =
|
|
16437
|
-
|
|
17172
|
+
const content = readFileSync21(manifestPath, "utf-8");
|
|
17173
|
+
manifestEntries2 = content.split("\n").filter((l) => l.trim()).length;
|
|
16438
17174
|
} catch {
|
|
16439
17175
|
}
|
|
16440
17176
|
}
|
|
@@ -16443,7 +17179,7 @@ async function orchestrateContext(epicId, projectRoot) {
|
|
|
16443
17179
|
data: {
|
|
16444
17180
|
epicId: epicId || null,
|
|
16445
17181
|
taskCount,
|
|
16446
|
-
manifestEntries,
|
|
17182
|
+
manifestEntries: manifestEntries2,
|
|
16447
17183
|
estimatedTokens,
|
|
16448
17184
|
recommendation: estimatedTokens > 5e3 ? "Consider using manifest summaries instead of full task details" : "Context usage is within recommended limits",
|
|
16449
17185
|
limits: {
|
|
@@ -16470,7 +17206,7 @@ async function orchestrateValidate(taskId, projectRoot) {
|
|
|
16470
17206
|
return engineError("E_VALIDATION", err.message);
|
|
16471
17207
|
}
|
|
16472
17208
|
}
|
|
16473
|
-
async function orchestrateSpawn(taskId, protocolType, projectRoot) {
|
|
17209
|
+
async function orchestrateSpawn(taskId, protocolType, projectRoot, tier) {
|
|
16474
17210
|
if (!taskId) {
|
|
16475
17211
|
return engineError("E_INVALID_INPUT", "taskId is required");
|
|
16476
17212
|
}
|
|
@@ -16491,9 +17227,11 @@ async function orchestrateSpawn(taskId, protocolType, projectRoot) {
|
|
|
16491
17227
|
spawnContext: {
|
|
16492
17228
|
taskId: spawnContext.taskId,
|
|
16493
17229
|
protocol: spawnContext.protocol,
|
|
16494
|
-
protocolType: protocolType || spawnContext.protocol
|
|
17230
|
+
protocolType: protocolType || spawnContext.protocol,
|
|
17231
|
+
tier: tier ?? null
|
|
16495
17232
|
},
|
|
16496
17233
|
protocolType: protocolType || spawnContext.protocol,
|
|
17234
|
+
tier: tier ?? null,
|
|
16497
17235
|
tokenResolution: spawnContext.tokenResolution
|
|
16498
17236
|
}
|
|
16499
17237
|
};
|
|
@@ -16516,7 +17254,7 @@ async function orchestrateStartup(epicId, projectRoot) {
|
|
|
16516
17254
|
}
|
|
16517
17255
|
const children = tasks2.filter((t) => t.parentId === epicId);
|
|
16518
17256
|
const waves = computeWaves(children);
|
|
16519
|
-
const readyTasks = await
|
|
17257
|
+
const readyTasks = await getReadyTasks2(epicId, root, accessor);
|
|
16520
17258
|
const ready = readyTasks.filter((t) => t.ready);
|
|
16521
17259
|
return {
|
|
16522
17260
|
success: true,
|
|
@@ -16652,8 +17390,8 @@ async function orchestrateCheck(projectRoot) {
|
|
|
16652
17390
|
|
|
16653
17391
|
// src/core/memory/engine-compat.ts
|
|
16654
17392
|
init_paths();
|
|
16655
|
-
import { readFileSync as
|
|
16656
|
-
import { resolve as
|
|
17393
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync9, appendFileSync as appendFileSync6, existsSync as existsSync32, mkdirSync as mkdirSync13 } from "node:fs";
|
|
17394
|
+
import { resolve as resolve7, dirname as dirname10 } from "node:path";
|
|
16657
17395
|
|
|
16658
17396
|
// src/core/memory/index.ts
|
|
16659
17397
|
init_json();
|
|
@@ -16695,25 +17433,25 @@ function filterManifestEntries(entries, filter) {
|
|
|
16695
17433
|
|
|
16696
17434
|
// src/core/memory/patterns.ts
|
|
16697
17435
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
16698
|
-
import { readFileSync as
|
|
16699
|
-
import { join as
|
|
17436
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync7, appendFileSync as appendFileSync4, mkdirSync as mkdirSync11, existsSync as existsSync30 } from "node:fs";
|
|
17437
|
+
import { join as join31 } from "node:path";
|
|
16700
17438
|
function getMemoryDir(projectRoot) {
|
|
16701
|
-
const memDir =
|
|
16702
|
-
if (!
|
|
16703
|
-
|
|
17439
|
+
const memDir = join31(projectRoot, ".cleo", "memory");
|
|
17440
|
+
if (!existsSync30(memDir)) {
|
|
17441
|
+
mkdirSync11(memDir, { recursive: true });
|
|
16704
17442
|
}
|
|
16705
17443
|
return memDir;
|
|
16706
17444
|
}
|
|
16707
17445
|
function getPatternsPath(projectRoot) {
|
|
16708
|
-
return
|
|
17446
|
+
return join31(getMemoryDir(projectRoot), "patterns.jsonl");
|
|
16709
17447
|
}
|
|
16710
17448
|
function generatePatternId() {
|
|
16711
17449
|
return `P${randomBytes7(4).toString("hex")}`;
|
|
16712
17450
|
}
|
|
16713
17451
|
function readPatterns(projectRoot) {
|
|
16714
17452
|
const path = getPatternsPath(projectRoot);
|
|
16715
|
-
if (!
|
|
16716
|
-
const content =
|
|
17453
|
+
if (!existsSync30(path)) return [];
|
|
17454
|
+
const content = readFileSync22(path, "utf-8").trim();
|
|
16717
17455
|
if (!content) return [];
|
|
16718
17456
|
const entries = [];
|
|
16719
17457
|
for (const line of content.split("\n")) {
|
|
@@ -16750,7 +17488,7 @@ function storePattern(projectRoot, params) {
|
|
|
16750
17488
|
}
|
|
16751
17489
|
const path2 = getPatternsPath(projectRoot);
|
|
16752
17490
|
const updated = existing.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
16753
|
-
|
|
17491
|
+
writeFileSync7(path2, updated, "utf-8");
|
|
16754
17492
|
return duplicate;
|
|
16755
17493
|
}
|
|
16756
17494
|
const entry = {
|
|
@@ -16822,25 +17560,25 @@ function patternStats(projectRoot) {
|
|
|
16822
17560
|
|
|
16823
17561
|
// src/core/memory/learnings.ts
|
|
16824
17562
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
16825
|
-
import { readFileSync as
|
|
16826
|
-
import { join as
|
|
17563
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync8, appendFileSync as appendFileSync5, mkdirSync as mkdirSync12, existsSync as existsSync31 } from "node:fs";
|
|
17564
|
+
import { join as join32 } from "node:path";
|
|
16827
17565
|
function getMemoryDir2(projectRoot) {
|
|
16828
|
-
const memDir =
|
|
16829
|
-
if (!
|
|
16830
|
-
|
|
17566
|
+
const memDir = join32(projectRoot, ".cleo", "memory");
|
|
17567
|
+
if (!existsSync31(memDir)) {
|
|
17568
|
+
mkdirSync12(memDir, { recursive: true });
|
|
16831
17569
|
}
|
|
16832
17570
|
return memDir;
|
|
16833
17571
|
}
|
|
16834
17572
|
function getLearningsPath(projectRoot) {
|
|
16835
|
-
return
|
|
17573
|
+
return join32(getMemoryDir2(projectRoot), "learnings.jsonl");
|
|
16836
17574
|
}
|
|
16837
17575
|
function generateLearningId() {
|
|
16838
17576
|
return `L${randomBytes8(4).toString("hex")}`;
|
|
16839
17577
|
}
|
|
16840
17578
|
function readLearnings(projectRoot) {
|
|
16841
17579
|
const path = getLearningsPath(projectRoot);
|
|
16842
|
-
if (!
|
|
16843
|
-
const content =
|
|
17580
|
+
if (!existsSync31(path)) return [];
|
|
17581
|
+
const content = readFileSync23(path, "utf-8").trim();
|
|
16844
17582
|
if (!content) return [];
|
|
16845
17583
|
const entries = [];
|
|
16846
17584
|
for (const line of content.split("\n")) {
|
|
@@ -16879,7 +17617,7 @@ function storeLearning(projectRoot, params) {
|
|
|
16879
17617
|
}
|
|
16880
17618
|
const path2 = getLearningsPath(projectRoot);
|
|
16881
17619
|
const updated = existing.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
16882
|
-
|
|
17620
|
+
writeFileSync8(path2, updated, "utf-8");
|
|
16883
17621
|
return duplicate;
|
|
16884
17622
|
}
|
|
16885
17623
|
const entry = {
|
|
@@ -16954,7 +17692,7 @@ function resolveRoot(projectRoot) {
|
|
|
16954
17692
|
function readManifestEntries(projectRoot) {
|
|
16955
17693
|
const manifestPath = getManifestPath2(projectRoot);
|
|
16956
17694
|
try {
|
|
16957
|
-
const content =
|
|
17695
|
+
const content = readFileSync24(manifestPath, "utf-8");
|
|
16958
17696
|
const entries = [];
|
|
16959
17697
|
const lines = content.split("\n");
|
|
16960
17698
|
for (const line of lines) {
|
|
@@ -16989,9 +17727,9 @@ function memoryShow(researchId, projectRoot) {
|
|
|
16989
17727
|
const root = resolveRoot(projectRoot);
|
|
16990
17728
|
let fileContent = null;
|
|
16991
17729
|
try {
|
|
16992
|
-
const filePath =
|
|
16993
|
-
if (
|
|
16994
|
-
fileContent =
|
|
17730
|
+
const filePath = resolve7(root, entry.file);
|
|
17731
|
+
if (existsSync32(filePath)) {
|
|
17732
|
+
fileContent = readFileSync24(filePath, "utf-8");
|
|
16995
17733
|
}
|
|
16996
17734
|
} catch {
|
|
16997
17735
|
}
|
|
@@ -17119,7 +17857,7 @@ function memoryLink(taskId, researchId, notes, projectRoot) {
|
|
|
17119
17857
|
}
|
|
17120
17858
|
entry.linked_tasks.push(taskId);
|
|
17121
17859
|
const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
17122
|
-
|
|
17860
|
+
writeFileSync9(manifestPath, content, "utf-8");
|
|
17123
17861
|
return { success: true, data: { taskId, researchId, linked: true, notes: notes || null } };
|
|
17124
17862
|
}
|
|
17125
17863
|
function memoryManifestAppend(entry, projectRoot) {
|
|
@@ -17139,9 +17877,9 @@ function memoryManifestAppend(entry, projectRoot) {
|
|
|
17139
17877
|
return { success: false, error: { code: "E_VALIDATION_FAILED", message: `Invalid manifest entry: ${errors.join(", ")}` } };
|
|
17140
17878
|
}
|
|
17141
17879
|
const manifestPath = getManifestPath2(projectRoot);
|
|
17142
|
-
const dir =
|
|
17143
|
-
if (!
|
|
17144
|
-
|
|
17880
|
+
const dir = dirname10(manifestPath);
|
|
17881
|
+
if (!existsSync32(dir)) {
|
|
17882
|
+
mkdirSync13(dir, { recursive: true });
|
|
17145
17883
|
}
|
|
17146
17884
|
const serialized = JSON.stringify(entry);
|
|
17147
17885
|
appendFileSync6(manifestPath, serialized + "\n", "utf-8");
|
|
@@ -17160,14 +17898,14 @@ function memoryManifestArchive(beforeDate, projectRoot) {
|
|
|
17160
17898
|
if (toArchive.length === 0) {
|
|
17161
17899
|
return { success: true, data: { archived: 0, remaining: entries.length, message: "No entries found before the specified date" } };
|
|
17162
17900
|
}
|
|
17163
|
-
const archiveDir =
|
|
17164
|
-
if (!
|
|
17165
|
-
|
|
17901
|
+
const archiveDir = dirname10(archivePath);
|
|
17902
|
+
if (!existsSync32(archiveDir)) {
|
|
17903
|
+
mkdirSync13(archiveDir, { recursive: true });
|
|
17166
17904
|
}
|
|
17167
17905
|
const archiveContent = toArchive.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
17168
17906
|
appendFileSync6(archivePath, archiveContent, "utf-8");
|
|
17169
17907
|
const remainingContent = toKeep.length > 0 ? toKeep.map((e) => JSON.stringify(e)).join("\n") + "\n" : "";
|
|
17170
|
-
|
|
17908
|
+
writeFileSync9(manifestPath, remainingContent, "utf-8");
|
|
17171
17909
|
return { success: true, data: { archived: toArchive.length, remaining: toKeep.length, archiveFile: getManifestArchivePath() } };
|
|
17172
17910
|
}
|
|
17173
17911
|
function memoryContradictions(projectRoot, params) {
|
|
@@ -17249,16 +17987,16 @@ function memoryInject(protocolType, params, projectRoot) {
|
|
|
17249
17987
|
}
|
|
17250
17988
|
const root = resolveRoot(projectRoot);
|
|
17251
17989
|
const protocolLocations = [
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17990
|
+
resolve7(root, "protocols", `${protocolType}.md`),
|
|
17991
|
+
resolve7(root, "skills", "_shared", `${protocolType}.md`),
|
|
17992
|
+
resolve7(root, "agents", "cleo-subagent", "protocols", `${protocolType}.md`)
|
|
17255
17993
|
];
|
|
17256
17994
|
let protocolContent = null;
|
|
17257
17995
|
let protocolPath = null;
|
|
17258
17996
|
for (const loc of protocolLocations) {
|
|
17259
|
-
if (
|
|
17997
|
+
if (existsSync32(loc)) {
|
|
17260
17998
|
try {
|
|
17261
|
-
protocolContent =
|
|
17999
|
+
protocolContent = readFileSync24(loc, "utf-8");
|
|
17262
18000
|
protocolPath = loc.replace(root + "/", "");
|
|
17263
18001
|
break;
|
|
17264
18002
|
} catch {
|
|
@@ -17347,11 +18085,11 @@ init_data_accessor();
|
|
|
17347
18085
|
// src/core/release/release-manifest.ts
|
|
17348
18086
|
init_json();
|
|
17349
18087
|
init_paths();
|
|
17350
|
-
import { existsSync as
|
|
18088
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync14 } from "node:fs";
|
|
17351
18089
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
17352
|
-
import { dirname as
|
|
18090
|
+
import { dirname as dirname11, join as join33 } from "node:path";
|
|
17353
18091
|
function getReleasesPath(cwd) {
|
|
17354
|
-
return
|
|
18092
|
+
return join33(getCleoDirAbsolute(cwd), "releases.json");
|
|
17355
18093
|
}
|
|
17356
18094
|
async function readReleases(cwd) {
|
|
17357
18095
|
const data = await readJson(getReleasesPath(cwd));
|
|
@@ -17359,9 +18097,9 @@ async function readReleases(cwd) {
|
|
|
17359
18097
|
}
|
|
17360
18098
|
async function writeReleases(index2, cwd) {
|
|
17361
18099
|
const releasesPath = getReleasesPath(cwd);
|
|
17362
|
-
const dir =
|
|
17363
|
-
if (!
|
|
17364
|
-
|
|
18100
|
+
const dir = dirname11(releasesPath);
|
|
18101
|
+
if (!existsSync33(dir)) {
|
|
18102
|
+
mkdirSync14(dir, { recursive: true });
|
|
17365
18103
|
}
|
|
17366
18104
|
await saveJson(releasesPath, index2);
|
|
17367
18105
|
}
|
|
@@ -17628,7 +18366,7 @@ async function rollbackRelease(version, reason, cwd) {
|
|
|
17628
18366
|
};
|
|
17629
18367
|
}
|
|
17630
18368
|
async function readPushPolicy(cwd) {
|
|
17631
|
-
const configPath =
|
|
18369
|
+
const configPath = join33(getCleoDirAbsolute(cwd), "config.json");
|
|
17632
18370
|
const config = await readJson(configPath);
|
|
17633
18371
|
if (!config) return void 0;
|
|
17634
18372
|
const release2 = config.release;
|
|
@@ -17712,8 +18450,8 @@ async function hasManifestEntry(version, projectRoot) {
|
|
|
17712
18450
|
}
|
|
17713
18451
|
async function loadTasks2(projectRoot) {
|
|
17714
18452
|
if (projectRoot) {
|
|
17715
|
-
const taskPath =
|
|
17716
|
-
const taskData =
|
|
18453
|
+
const taskPath = getDataPath(projectRoot, "todo.json");
|
|
18454
|
+
const taskData = readJsonFile(taskPath);
|
|
17717
18455
|
return taskData?.tasks ?? [];
|
|
17718
18456
|
}
|
|
17719
18457
|
try {
|
|
@@ -17722,8 +18460,8 @@ async function loadTasks2(projectRoot) {
|
|
|
17722
18460
|
return taskFile?.tasks ?? [];
|
|
17723
18461
|
} catch {
|
|
17724
18462
|
const root = resolveProjectRoot();
|
|
17725
|
-
const taskPath =
|
|
17726
|
-
const taskData =
|
|
18463
|
+
const taskPath = getDataPath(root, "todo.json");
|
|
18464
|
+
const taskData = readJsonFile(taskPath);
|
|
17727
18465
|
return taskData?.tasks ?? [];
|
|
17728
18466
|
}
|
|
17729
18467
|
}
|
|
@@ -17843,8 +18581,8 @@ async function releasePush(version, remote, projectRoot, opts) {
|
|
|
17843
18581
|
|
|
17844
18582
|
// src/dispatch/engines/template-parser.ts
|
|
17845
18583
|
init_platform();
|
|
17846
|
-
import { readFileSync as
|
|
17847
|
-
import { join as
|
|
18584
|
+
import { readFileSync as readFileSync25, readdirSync as readdirSync9, existsSync as existsSync34 } from "fs";
|
|
18585
|
+
import { join as join34 } from "path";
|
|
17848
18586
|
import { parse as parseYaml } from "yaml";
|
|
17849
18587
|
var SUFFIX_PATTERNS = ["_report", "_request", "_question"];
|
|
17850
18588
|
function deriveSubcommand(filename) {
|
|
@@ -17859,8 +18597,8 @@ function deriveSubcommand(filename) {
|
|
|
17859
18597
|
return firstWord.toLowerCase();
|
|
17860
18598
|
}
|
|
17861
18599
|
function parseTemplateFile(templateDir, filename) {
|
|
17862
|
-
const filePath =
|
|
17863
|
-
const raw =
|
|
18600
|
+
const filePath = join34(templateDir, filename);
|
|
18601
|
+
const raw = readFileSync25(filePath, "utf-8");
|
|
17864
18602
|
const parsed = parseYaml(raw);
|
|
17865
18603
|
const name = typeof parsed.name === "string" ? parsed.name : filename;
|
|
17866
18604
|
const titlePrefix = typeof parsed.title === "string" ? parsed.title : "";
|
|
@@ -17909,8 +18647,8 @@ function parseTemplateFile(templateDir, filename) {
|
|
|
17909
18647
|
};
|
|
17910
18648
|
}
|
|
17911
18649
|
function parseIssueTemplates(projectRoot) {
|
|
17912
|
-
const templateDir =
|
|
17913
|
-
if (!
|
|
18650
|
+
const templateDir = join34(projectRoot, ".github", "ISSUE_TEMPLATE");
|
|
18651
|
+
if (!existsSync34(templateDir)) {
|
|
17914
18652
|
return engineError("E_NOT_FOUND", `Issue template directory not found: ${templateDir}`);
|
|
17915
18653
|
}
|
|
17916
18654
|
let files;
|
|
@@ -17962,7 +18700,7 @@ async function generateTemplateConfig(projectRoot) {
|
|
|
17962
18700
|
if (!result.success || !result.data) {
|
|
17963
18701
|
return result.error ? { success: false, error: result.error } : engineError("E_PARSE_ERROR", "Failed to parse issue templates");
|
|
17964
18702
|
}
|
|
17965
|
-
const outputPath =
|
|
18703
|
+
const outputPath = getDataPath(projectRoot, "issue-templates.json");
|
|
17966
18704
|
try {
|
|
17967
18705
|
writeJsonFileAtomic(outputPath, result.data);
|
|
17968
18706
|
} catch (error) {
|
|
@@ -17990,7 +18728,6 @@ function validateLabels2(labels, repoLabels) {
|
|
|
17990
18728
|
}
|
|
17991
18729
|
|
|
17992
18730
|
// src/dispatch/domains/tasks.ts
|
|
17993
|
-
var logger2 = getLogger("domain:tasks");
|
|
17994
18731
|
var TasksHandler = class {
|
|
17995
18732
|
projectRoot;
|
|
17996
18733
|
constructor() {
|
|
@@ -18012,14 +18749,26 @@ var TasksHandler = class {
|
|
|
18012
18749
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18013
18750
|
}
|
|
18014
18751
|
case "list": {
|
|
18015
|
-
const result = await taskList(this.projectRoot,
|
|
18752
|
+
const result = await taskList(this.projectRoot, {
|
|
18753
|
+
parent: params?.parent,
|
|
18754
|
+
status: params?.status,
|
|
18755
|
+
limit: params?.limit,
|
|
18756
|
+
compact: params?.compact
|
|
18757
|
+
});
|
|
18016
18758
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18017
18759
|
}
|
|
18018
18760
|
case "find": {
|
|
18019
18761
|
const result = await taskFind(
|
|
18020
18762
|
this.projectRoot,
|
|
18021
18763
|
params?.query,
|
|
18022
|
-
params?.limit
|
|
18764
|
+
params?.limit,
|
|
18765
|
+
{
|
|
18766
|
+
id: params?.id,
|
|
18767
|
+
exact: params?.exact,
|
|
18768
|
+
status: params?.status,
|
|
18769
|
+
includeArchive: params?.includeArchive,
|
|
18770
|
+
offset: params?.offset
|
|
18771
|
+
}
|
|
18023
18772
|
);
|
|
18024
18773
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18025
18774
|
}
|
|
@@ -18041,12 +18790,22 @@ var TasksHandler = class {
|
|
|
18041
18790
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18042
18791
|
}
|
|
18043
18792
|
case "depends": {
|
|
18793
|
+
const action = params?.action;
|
|
18794
|
+
if (action === "overview") {
|
|
18795
|
+
const result2 = await taskDepsOverview(this.projectRoot);
|
|
18796
|
+
return this.wrapEngineResult(result2, "query", "tasks", operation, startTime);
|
|
18797
|
+
}
|
|
18798
|
+
if (action === "cycles") {
|
|
18799
|
+
const result2 = await taskDepsCycles(this.projectRoot);
|
|
18800
|
+
return this.wrapEngineResult(result2, "query", "tasks", operation, startTime);
|
|
18801
|
+
}
|
|
18044
18802
|
const taskId = params?.taskId;
|
|
18045
18803
|
if (!taskId) {
|
|
18046
|
-
return this.errorResponse("query", "tasks", operation, "E_INVALID_INPUT", "taskId is required", startTime);
|
|
18804
|
+
return this.errorResponse("query", "tasks", operation, "E_INVALID_INPUT", "taskId is required (or use action: overview|cycles)", startTime);
|
|
18047
18805
|
}
|
|
18048
18806
|
const direction = params?.direction;
|
|
18049
|
-
const
|
|
18807
|
+
const tree = params?.tree;
|
|
18808
|
+
const result = await taskDepends(this.projectRoot, taskId, direction, tree);
|
|
18050
18809
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18051
18810
|
}
|
|
18052
18811
|
case "analyze": {
|
|
@@ -18216,10 +18975,10 @@ var TasksHandler = class {
|
|
|
18216
18975
|
}
|
|
18217
18976
|
case "relates.add": {
|
|
18218
18977
|
const taskId = params?.taskId;
|
|
18219
|
-
const relatedId = params?.relatedId;
|
|
18978
|
+
const relatedId = params?.relatedId ?? params?.targetId;
|
|
18220
18979
|
const type = params?.type;
|
|
18221
18980
|
if (!taskId || !relatedId || !type) {
|
|
18222
|
-
return this.errorResponse("mutate", "tasks", operation, "E_INVALID_INPUT", "taskId, relatedId, and type are required", startTime);
|
|
18981
|
+
return this.errorResponse("mutate", "tasks", operation, "E_INVALID_INPUT", "taskId, relatedId (or targetId), and type are required", startTime);
|
|
18223
18982
|
}
|
|
18224
18983
|
const result = await taskRelatesAdd(
|
|
18225
18984
|
this.projectRoot,
|
|
@@ -18313,7 +19072,7 @@ var TasksHandler = class {
|
|
|
18313
19072
|
}
|
|
18314
19073
|
handleError(gateway, domain, operation, error, startTime) {
|
|
18315
19074
|
const message = error instanceof Error ? error.message : String(error);
|
|
18316
|
-
|
|
19075
|
+
getLogger("domain:tasks").error({ gateway, domain, operation, err: error }, message);
|
|
18317
19076
|
return {
|
|
18318
19077
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
18319
19078
|
success: false,
|
|
@@ -18351,7 +19110,6 @@ function unbindSession() {
|
|
|
18351
19110
|
}
|
|
18352
19111
|
|
|
18353
19112
|
// src/dispatch/domains/session.ts
|
|
18354
|
-
var logger3 = getLogger("domain:session");
|
|
18355
19113
|
var SessionHandler = class {
|
|
18356
19114
|
projectRoot;
|
|
18357
19115
|
constructor() {
|
|
@@ -18472,7 +19230,7 @@ var SessionHandler = class {
|
|
|
18472
19230
|
gradeMode: params?.grade ?? false
|
|
18473
19231
|
});
|
|
18474
19232
|
} catch {
|
|
18475
|
-
|
|
19233
|
+
getLogger("domain:session").warn({ sessionId: session.id }, "Session context already bound, skipping bindSession");
|
|
18476
19234
|
}
|
|
18477
19235
|
}
|
|
18478
19236
|
return this.wrapEngineResult(result, "mutate", "session", operation, startTime);
|
|
@@ -18583,7 +19341,7 @@ var SessionHandler = class {
|
|
|
18583
19341
|
}
|
|
18584
19342
|
handleError(gateway, domain, operation, error, startTime) {
|
|
18585
19343
|
const message = error instanceof Error ? error.message : String(error);
|
|
18586
|
-
|
|
19344
|
+
getLogger("domain:session").error({ gateway, domain, operation, err: error }, message);
|
|
18587
19345
|
return {
|
|
18588
19346
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
18589
19347
|
success: false,
|
|
@@ -18595,7 +19353,6 @@ var SessionHandler = class {
|
|
|
18595
19353
|
// src/dispatch/domains/check.ts
|
|
18596
19354
|
init_paths();
|
|
18597
19355
|
init_logger();
|
|
18598
|
-
var logger4 = getLogger("domain:check");
|
|
18599
19356
|
var CheckHandler = class {
|
|
18600
19357
|
projectRoot;
|
|
18601
19358
|
constructor() {
|
|
@@ -18754,7 +19511,7 @@ var CheckHandler = class {
|
|
|
18754
19511
|
}
|
|
18755
19512
|
handleError(gateway, domain, operation, error, startTime) {
|
|
18756
19513
|
const message = error instanceof Error ? error.message : String(error);
|
|
18757
|
-
|
|
19514
|
+
getLogger("domain:check").error({ gateway, domain, operation, err: error }, message);
|
|
18758
19515
|
return {
|
|
18759
19516
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
18760
19517
|
success: false,
|
|
@@ -18768,8 +19525,8 @@ init_paths();
|
|
|
18768
19525
|
init_logger();
|
|
18769
19526
|
|
|
18770
19527
|
// src/core/adrs/parse.ts
|
|
18771
|
-
import { readFileSync as
|
|
18772
|
-
import { join as
|
|
19528
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
19529
|
+
import { join as join35 } from "node:path";
|
|
18773
19530
|
function extractAdrId(filename) {
|
|
18774
19531
|
const match = filename.match(/^(ADR-\d+)/);
|
|
18775
19532
|
return match ? match[1] : filename.replace(".md", "");
|
|
@@ -18791,8 +19548,8 @@ function extractTitle(content) {
|
|
|
18791
19548
|
return match ? match[1].trim() : "Untitled";
|
|
18792
19549
|
}
|
|
18793
19550
|
function parseAdrFile(filePath, projectRoot) {
|
|
18794
|
-
const absolutePath = filePath.startsWith("/") ? filePath :
|
|
18795
|
-
const content =
|
|
19551
|
+
const absolutePath = filePath.startsWith("/") ? filePath : join35(projectRoot, filePath);
|
|
19552
|
+
const content = readFileSync26(absolutePath, "utf-8");
|
|
18796
19553
|
const filename = filePath.split("/").pop();
|
|
18797
19554
|
return {
|
|
18798
19555
|
id: extractAdrId(filename),
|
|
@@ -18803,27 +19560,27 @@ function parseAdrFile(filePath, projectRoot) {
|
|
|
18803
19560
|
}
|
|
18804
19561
|
|
|
18805
19562
|
// src/core/adrs/validate.ts
|
|
18806
|
-
import { readFileSync as
|
|
18807
|
-
import { join as
|
|
19563
|
+
import { readFileSync as readFileSync27, readdirSync as readdirSync10, existsSync as existsSync35 } from "node:fs";
|
|
19564
|
+
import { join as join36 } from "node:path";
|
|
18808
19565
|
import AjvModule3 from "ajv";
|
|
18809
19566
|
var Ajv3 = AjvModule3.default ?? AjvModule3;
|
|
18810
19567
|
async function validateAllAdrs(projectRoot) {
|
|
18811
|
-
const adrsDir =
|
|
18812
|
-
const schemaPath =
|
|
18813
|
-
if (!
|
|
19568
|
+
const adrsDir = join36(projectRoot, ".cleo", "adrs");
|
|
19569
|
+
const schemaPath = join36(projectRoot, "schemas", "adr-frontmatter.schema.json");
|
|
19570
|
+
if (!existsSync35(schemaPath)) {
|
|
18814
19571
|
return {
|
|
18815
19572
|
valid: false,
|
|
18816
19573
|
errors: [{ file: "schemas/adr-frontmatter.schema.json", field: "schema", message: "Schema file not found" }],
|
|
18817
19574
|
checked: 0
|
|
18818
19575
|
};
|
|
18819
19576
|
}
|
|
18820
|
-
if (!
|
|
19577
|
+
if (!existsSync35(adrsDir)) {
|
|
18821
19578
|
return { valid: true, errors: [], checked: 0 };
|
|
18822
19579
|
}
|
|
18823
|
-
const schema = JSON.parse(
|
|
19580
|
+
const schema = JSON.parse(readFileSync27(schemaPath, "utf-8"));
|
|
18824
19581
|
const ajv = new Ajv3({ allErrors: true });
|
|
18825
19582
|
const validate = ajv.compile(schema);
|
|
18826
|
-
const files = readdirSync10(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) =>
|
|
19583
|
+
const files = readdirSync10(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join36(adrsDir, f));
|
|
18827
19584
|
const errors = [];
|
|
18828
19585
|
for (const filePath of files) {
|
|
18829
19586
|
const record = parseAdrFile(filePath, projectRoot);
|
|
@@ -18844,8 +19601,8 @@ async function validateAllAdrs(projectRoot) {
|
|
|
18844
19601
|
// src/core/adrs/sync.ts
|
|
18845
19602
|
init_sqlite();
|
|
18846
19603
|
init_schema();
|
|
18847
|
-
import { readFileSync as
|
|
18848
|
-
import { join as
|
|
19604
|
+
import { readFileSync as readFileSync28, readdirSync as readdirSync11, writeFileSync as writeFileSync10, existsSync as existsSync36 } from "node:fs";
|
|
19605
|
+
import { join as join37 } from "node:path";
|
|
18849
19606
|
import { eq as eq7 } from "drizzle-orm";
|
|
18850
19607
|
function extractAdrIdFromRef(ref) {
|
|
18851
19608
|
const m = ref.match(/^(ADR-\d+)/);
|
|
@@ -18858,7 +19615,7 @@ function collectAdrFiles(dir) {
|
|
|
18858
19615
|
const results = [];
|
|
18859
19616
|
for (const entry of readdirSync11(dir, { withFileTypes: true })) {
|
|
18860
19617
|
if (entry.isDirectory()) {
|
|
18861
|
-
const sub =
|
|
19618
|
+
const sub = join37(dir, entry.name);
|
|
18862
19619
|
for (const f of readdirSync11(sub)) {
|
|
18863
19620
|
if (f.endsWith(".md") && /^ADR-\d+/.test(f)) {
|
|
18864
19621
|
results.push({ file: f, relPath: `${entry.name}/${f}` });
|
|
@@ -18871,23 +19628,23 @@ function collectAdrFiles(dir) {
|
|
|
18871
19628
|
return results.sort((a, b) => a.file.localeCompare(b.file));
|
|
18872
19629
|
}
|
|
18873
19630
|
async function syncAdrsToDb(projectRoot) {
|
|
18874
|
-
const adrsDir =
|
|
19631
|
+
const adrsDir = join37(projectRoot, ".cleo", "adrs");
|
|
18875
19632
|
const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
|
|
18876
|
-
if (!
|
|
19633
|
+
if (!existsSync36(adrsDir)) {
|
|
18877
19634
|
return result;
|
|
18878
19635
|
}
|
|
18879
19636
|
const db = await getDb(projectRoot);
|
|
18880
19637
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18881
19638
|
const allFiles = collectAdrFiles(adrsDir);
|
|
18882
19639
|
const activeFiles = allFiles.filter((f) => !f.relPath.includes("/"));
|
|
18883
|
-
const
|
|
19640
|
+
const manifestEntries2 = [];
|
|
18884
19641
|
for (const { file, relPath } of activeFiles) {
|
|
18885
19642
|
try {
|
|
18886
|
-
const filePath =
|
|
19643
|
+
const filePath = join37(adrsDir, relPath);
|
|
18887
19644
|
const record = parseAdrFile(filePath, projectRoot);
|
|
18888
19645
|
const fm = record.frontmatter;
|
|
18889
19646
|
const dbRelPath = `.cleo/adrs/${relPath}`;
|
|
18890
|
-
const content =
|
|
19647
|
+
const content = readFileSync28(filePath, "utf-8");
|
|
18891
19648
|
const supersedesId = fm.Supersedes ? extractAdrIdFromRef(fm.Supersedes) : null;
|
|
18892
19649
|
const supersededById = fm["Superseded By"] ? extractAdrIdFromRef(fm["Superseded By"]) : null;
|
|
18893
19650
|
const amendsId = fm.Amends ? extractAdrIdFromRef(fm.Amends) : null;
|
|
@@ -18929,7 +19686,7 @@ async function syncAdrsToDb(projectRoot) {
|
|
|
18929
19686
|
}
|
|
18930
19687
|
for (const { relPath } of allFiles) {
|
|
18931
19688
|
try {
|
|
18932
|
-
const filePath =
|
|
19689
|
+
const filePath = join37(adrsDir, relPath);
|
|
18933
19690
|
const record = parseAdrFile(filePath, projectRoot);
|
|
18934
19691
|
const fm = record.frontmatter;
|
|
18935
19692
|
const entry = {
|
|
@@ -18952,28 +19709,28 @@ async function syncAdrsToDb(projectRoot) {
|
|
|
18952
19709
|
if (fm.Summary) entry["summary"] = fm.Summary;
|
|
18953
19710
|
if (fm.Keywords) entry["keywords"] = fm.Keywords.split(",").map((s) => s.trim()).filter(Boolean);
|
|
18954
19711
|
if (fm.Topics) entry["topics"] = fm.Topics.split(",").map((s) => s.trim()).filter(Boolean);
|
|
18955
|
-
|
|
19712
|
+
manifestEntries2.push(entry);
|
|
18956
19713
|
} catch {
|
|
18957
19714
|
}
|
|
18958
19715
|
}
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
19716
|
+
writeFileSync10(
|
|
19717
|
+
join37(adrsDir, "MANIFEST.jsonl"),
|
|
19718
|
+
manifestEntries2.map((e) => JSON.stringify(e)).join("\n") + "\n",
|
|
18962
19719
|
"utf-8"
|
|
18963
19720
|
);
|
|
18964
19721
|
return result;
|
|
18965
19722
|
}
|
|
18966
19723
|
|
|
18967
19724
|
// src/core/adrs/list.ts
|
|
18968
|
-
import { readdirSync as readdirSync12, existsSync as
|
|
18969
|
-
import { join as
|
|
19725
|
+
import { readdirSync as readdirSync12, existsSync as existsSync37 } from "node:fs";
|
|
19726
|
+
import { join as join38 } from "node:path";
|
|
18970
19727
|
async function listAdrs(projectRoot, opts) {
|
|
18971
|
-
const adrsDir =
|
|
18972
|
-
if (!
|
|
19728
|
+
const adrsDir = join38(projectRoot, ".cleo", "adrs");
|
|
19729
|
+
if (!existsSync37(adrsDir)) {
|
|
18973
19730
|
return { adrs: [], total: 0 };
|
|
18974
19731
|
}
|
|
18975
19732
|
const files = readdirSync12(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
|
|
18976
|
-
const records = files.map((f) => parseAdrFile(
|
|
19733
|
+
const records = files.map((f) => parseAdrFile(join38(adrsDir, f), projectRoot));
|
|
18977
19734
|
const filtered = records.filter((r) => {
|
|
18978
19735
|
if (opts?.status && r.frontmatter.Status !== opts.status) return false;
|
|
18979
19736
|
if (opts?.since && r.frontmatter.Date < opts.since) return false;
|
|
@@ -18992,20 +19749,20 @@ async function listAdrs(projectRoot, opts) {
|
|
|
18992
19749
|
}
|
|
18993
19750
|
|
|
18994
19751
|
// src/core/adrs/show.ts
|
|
18995
|
-
import { existsSync as
|
|
18996
|
-
import { join as
|
|
19752
|
+
import { existsSync as existsSync38, readdirSync as readdirSync13 } from "node:fs";
|
|
19753
|
+
import { join as join39 } from "node:path";
|
|
18997
19754
|
async function showAdr(projectRoot, adrId) {
|
|
18998
|
-
const adrsDir =
|
|
18999
|
-
if (!
|
|
19755
|
+
const adrsDir = join39(projectRoot, ".cleo", "adrs");
|
|
19756
|
+
if (!existsSync38(adrsDir)) return null;
|
|
19000
19757
|
const files = readdirSync13(adrsDir).filter((f) => f.startsWith(adrId) && f.endsWith(".md"));
|
|
19001
19758
|
if (files.length === 0) return null;
|
|
19002
|
-
const filePath =
|
|
19759
|
+
const filePath = join39(adrsDir, files[0]);
|
|
19003
19760
|
return parseAdrFile(filePath, projectRoot);
|
|
19004
19761
|
}
|
|
19005
19762
|
|
|
19006
19763
|
// src/core/adrs/find.ts
|
|
19007
|
-
import { readdirSync as readdirSync14, existsSync as
|
|
19008
|
-
import { join as
|
|
19764
|
+
import { readdirSync as readdirSync14, existsSync as existsSync39 } from "node:fs";
|
|
19765
|
+
import { join as join40 } from "node:path";
|
|
19009
19766
|
function normalise(s) {
|
|
19010
19767
|
return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
19011
19768
|
}
|
|
@@ -19022,8 +19779,8 @@ function matchedTerms(target, terms) {
|
|
|
19022
19779
|
return terms.filter((term) => t.includes(term));
|
|
19023
19780
|
}
|
|
19024
19781
|
async function findAdrs(projectRoot, query, opts) {
|
|
19025
|
-
const adrsDir =
|
|
19026
|
-
if (!
|
|
19782
|
+
const adrsDir = join40(projectRoot, ".cleo", "adrs");
|
|
19783
|
+
if (!existsSync39(adrsDir)) {
|
|
19027
19784
|
return { adrs: [], query, total: 0 };
|
|
19028
19785
|
}
|
|
19029
19786
|
const files = readdirSync14(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
|
|
@@ -19032,7 +19789,7 @@ async function findAdrs(projectRoot, query, opts) {
|
|
|
19032
19789
|
const filterKeywords = opts?.keywords ? parseTags(opts.keywords) : null;
|
|
19033
19790
|
const results = [];
|
|
19034
19791
|
for (const file of files) {
|
|
19035
|
-
const record = parseAdrFile(
|
|
19792
|
+
const record = parseAdrFile(join40(adrsDir, file), projectRoot);
|
|
19036
19793
|
const fm = record.frontmatter;
|
|
19037
19794
|
if (opts?.status && fm.Status !== opts.status) continue;
|
|
19038
19795
|
if (filterTopics && filterTopics.length > 0) {
|
|
@@ -19095,7 +19852,6 @@ init_schema();
|
|
|
19095
19852
|
import { eq as eq8, and as and2 } from "drizzle-orm";
|
|
19096
19853
|
|
|
19097
19854
|
// src/dispatch/domains/admin.ts
|
|
19098
|
-
var logger5 = getLogger("domain:admin");
|
|
19099
19855
|
var AdminHandler = class {
|
|
19100
19856
|
projectRoot;
|
|
19101
19857
|
constructor() {
|
|
@@ -19178,6 +19934,14 @@ var AdminHandler = class {
|
|
|
19178
19934
|
case "help": {
|
|
19179
19935
|
const tier = typeof params?.tier === "number" ? params.tier : 0;
|
|
19180
19936
|
const ops = OPERATIONS.filter((op) => op.tier <= tier);
|
|
19937
|
+
const getCostHint = (domain, op) => {
|
|
19938
|
+
const key = `${domain}.${op}`;
|
|
19939
|
+
const heavyOps = ["tasks.list", "tasks.tree", "admin.log", "admin.stats", "tasks.analyze"];
|
|
19940
|
+
const moderateOps = ["tasks.show", "tasks.blockers", "tasks.depends", "admin.health", "admin.dash", "admin.help"];
|
|
19941
|
+
if (heavyOps.includes(key)) return "heavy";
|
|
19942
|
+
if (moderateOps.includes(key)) return "moderate";
|
|
19943
|
+
return "minimal";
|
|
19944
|
+
};
|
|
19181
19945
|
const tierGuidance = {
|
|
19182
19946
|
0: "Tier 0: Core task and session operations (tasks, session, admin). 80% of use cases.",
|
|
19183
19947
|
1: "Tier 1: + memory/research and check/validate operations. 15% of use cases.",
|
|
@@ -19189,14 +19953,22 @@ var AdminHandler = class {
|
|
|
19189
19953
|
data: {
|
|
19190
19954
|
tier,
|
|
19191
19955
|
operationCount: ops.length,
|
|
19956
|
+
quickStart: tier === 0 ? [
|
|
19957
|
+
"cleo_query tasks.current \u2014 check active task (~100 tokens)",
|
|
19958
|
+
"cleo_query tasks.next \u2014 get suggestion (~300 tokens)",
|
|
19959
|
+
"cleo_query tasks.find {query} \u2014 search tasks (~200 tokens)",
|
|
19960
|
+
"cleo_mutate tasks.start {taskId} \u2014 begin work (~100 tokens)",
|
|
19961
|
+
"cleo_mutate tasks.complete {taskId} \u2014 finish task (~200 tokens)"
|
|
19962
|
+
] : void 0,
|
|
19192
19963
|
operations: ops.map((op) => ({
|
|
19193
19964
|
gateway: op.gateway,
|
|
19194
19965
|
domain: op.domain,
|
|
19195
19966
|
operation: op.operation,
|
|
19196
|
-
description: op.description
|
|
19967
|
+
description: op.description,
|
|
19968
|
+
costHint: getCostHint(op.domain, op.operation)
|
|
19197
19969
|
})),
|
|
19198
19970
|
guidance: tierGuidance[tier] ?? tierGuidance[0],
|
|
19199
|
-
escalation: tier < 2 ? `For more operations:
|
|
19971
|
+
escalation: tier < 2 ? `For more operations: cleo_query({domain:"admin",operation:"help",params:{tier:${tier + 1}}})` : "Full operation set displayed."
|
|
19200
19972
|
}
|
|
19201
19973
|
};
|
|
19202
19974
|
}
|
|
@@ -19447,7 +20219,7 @@ var AdminHandler = class {
|
|
|
19447
20219
|
}
|
|
19448
20220
|
handleError(gateway, domain, operation, error, startTime) {
|
|
19449
20221
|
const message = error instanceof Error ? error.message : String(error);
|
|
19450
|
-
|
|
20222
|
+
getLogger("domain:admin").error({ gateway, domain, operation, err: error }, message);
|
|
19451
20223
|
return {
|
|
19452
20224
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
19453
20225
|
success: false,
|
|
@@ -19459,7 +20231,6 @@ var AdminHandler = class {
|
|
|
19459
20231
|
// src/dispatch/domains/memory.ts
|
|
19460
20232
|
init_paths();
|
|
19461
20233
|
init_logger();
|
|
19462
|
-
var logger6 = getLogger("domain:memory");
|
|
19463
20234
|
var MemoryHandler = class {
|
|
19464
20235
|
projectRoot;
|
|
19465
20236
|
constructor() {
|
|
@@ -19692,7 +20463,7 @@ var MemoryHandler = class {
|
|
|
19692
20463
|
}
|
|
19693
20464
|
handleError(gateway, domain, operation, error, startTime) {
|
|
19694
20465
|
const message = error instanceof Error ? error.message : String(error);
|
|
19695
|
-
|
|
20466
|
+
getLogger("domain:memory").error({ gateway, domain, operation, err: error }, message);
|
|
19696
20467
|
return {
|
|
19697
20468
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
19698
20469
|
success: false,
|
|
@@ -19704,7 +20475,6 @@ var MemoryHandler = class {
|
|
|
19704
20475
|
// src/dispatch/domains/orchestrate.ts
|
|
19705
20476
|
init_paths();
|
|
19706
20477
|
init_logger();
|
|
19707
|
-
var logger7 = getLogger("domain:orchestrate");
|
|
19708
20478
|
var OrchestrateHandler = class {
|
|
19709
20479
|
projectRoot;
|
|
19710
20480
|
constructor() {
|
|
@@ -19839,7 +20609,8 @@ var OrchestrateHandler = class {
|
|
|
19839
20609
|
);
|
|
19840
20610
|
}
|
|
19841
20611
|
const protocolType = params?.protocolType;
|
|
19842
|
-
const
|
|
20612
|
+
const tier = params?.tier;
|
|
20613
|
+
const result = await orchestrateSpawn(taskId, protocolType, this.projectRoot, tier);
|
|
19843
20614
|
return this.wrapEngineResult(result, "mutate", operation, startTime);
|
|
19844
20615
|
}
|
|
19845
20616
|
case "validate": {
|
|
@@ -19973,7 +20744,7 @@ var OrchestrateHandler = class {
|
|
|
19973
20744
|
}
|
|
19974
20745
|
handleError(gateway, operation, error, startTime) {
|
|
19975
20746
|
const message = error instanceof Error ? error.message : String(error);
|
|
19976
|
-
|
|
20747
|
+
getLogger("domain:orchestrate").error({ gateway, domain: "orchestrate", operation, err: error }, message);
|
|
19977
20748
|
return this.errorResponse(
|
|
19978
20749
|
gateway,
|
|
19979
20750
|
operation,
|
|
@@ -19987,7 +20758,6 @@ var OrchestrateHandler = class {
|
|
|
19987
20758
|
// src/dispatch/domains/pipeline.ts
|
|
19988
20759
|
init_paths();
|
|
19989
20760
|
init_logger();
|
|
19990
|
-
var logger8 = getLogger("domain:pipeline");
|
|
19991
20761
|
var PipelineHandler = class {
|
|
19992
20762
|
projectRoot;
|
|
19993
20763
|
constructor() {
|
|
@@ -20388,7 +21158,7 @@ var PipelineHandler = class {
|
|
|
20388
21158
|
}
|
|
20389
21159
|
handleError(gateway, operation, error, startTime) {
|
|
20390
21160
|
const message = error instanceof Error ? error.message : String(error);
|
|
20391
|
-
|
|
21161
|
+
getLogger("domain:pipeline").error({ gateway, domain: "pipeline", operation, err: error }, message);
|
|
20392
21162
|
return this.errorResponse(
|
|
20393
21163
|
gateway,
|
|
20394
21164
|
operation,
|
|
@@ -20451,8 +21221,8 @@ import { execFileSync as execFileSync5 } from "node:child_process";
|
|
|
20451
21221
|
|
|
20452
21222
|
// src/core/issue/template-parser.ts
|
|
20453
21223
|
init_paths();
|
|
20454
|
-
import { existsSync as
|
|
20455
|
-
import { join as
|
|
21224
|
+
import { existsSync as existsSync41, readFileSync as readFileSync29, readdirSync as readdirSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
21225
|
+
import { join as join42, basename as basename6 } from "node:path";
|
|
20456
21226
|
var TEMPLATE_DIR = ".github/ISSUE_TEMPLATE";
|
|
20457
21227
|
var CACHE_FILE = "issue-templates.json";
|
|
20458
21228
|
var SUBCOMMAND_MAP = {
|
|
@@ -20509,10 +21279,10 @@ function extractYamlArray(content, field) {
|
|
|
20509
21279
|
return items;
|
|
20510
21280
|
}
|
|
20511
21281
|
function parseTemplateFile2(filePath) {
|
|
20512
|
-
if (!
|
|
21282
|
+
if (!existsSync41(filePath)) return null;
|
|
20513
21283
|
try {
|
|
20514
|
-
const content =
|
|
20515
|
-
const fileName =
|
|
21284
|
+
const content = readFileSync29(filePath, "utf-8");
|
|
21285
|
+
const fileName = basename6(filePath);
|
|
20516
21286
|
const stem = fileName.replace(/\.ya?ml$/, "");
|
|
20517
21287
|
const name = extractYamlField(content, "name");
|
|
20518
21288
|
const description = extractYamlField(content, "description");
|
|
@@ -20527,12 +21297,12 @@ function parseTemplateFile2(filePath) {
|
|
|
20527
21297
|
}
|
|
20528
21298
|
function parseIssueTemplates2(projectDir) {
|
|
20529
21299
|
const dir = projectDir ?? getProjectRoot();
|
|
20530
|
-
const templateDir =
|
|
20531
|
-
if (!
|
|
21300
|
+
const templateDir = join42(dir, TEMPLATE_DIR);
|
|
21301
|
+
if (!existsSync41(templateDir)) return [];
|
|
20532
21302
|
const templates = [];
|
|
20533
21303
|
for (const file of readdirSync15(templateDir)) {
|
|
20534
21304
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
20535
|
-
const template = parseTemplateFile2(
|
|
21305
|
+
const template = parseTemplateFile2(join42(templateDir, file));
|
|
20536
21306
|
if (template) templates.push(template);
|
|
20537
21307
|
}
|
|
20538
21308
|
return templates;
|
|
@@ -20541,10 +21311,10 @@ function getTemplateConfig(cwd) {
|
|
|
20541
21311
|
const projectDir = cwd ?? getProjectRoot();
|
|
20542
21312
|
const liveTemplates = parseIssueTemplates2(projectDir);
|
|
20543
21313
|
if (liveTemplates.length > 0) return liveTemplates;
|
|
20544
|
-
const cachePath =
|
|
20545
|
-
if (
|
|
21314
|
+
const cachePath = join42(getCleoDir(cwd), CACHE_FILE);
|
|
21315
|
+
if (existsSync41(cachePath)) {
|
|
20546
21316
|
try {
|
|
20547
|
-
const cached = JSON.parse(
|
|
21317
|
+
const cached = JSON.parse(readFileSync29(cachePath, "utf-8"));
|
|
20548
21318
|
if (cached.templates?.length > 0) return cached.templates;
|
|
20549
21319
|
} catch {
|
|
20550
21320
|
}
|
|
@@ -20669,7 +21439,6 @@ import {
|
|
|
20669
21439
|
injectAll,
|
|
20670
21440
|
buildInjectionContent
|
|
20671
21441
|
} from "@cleocode/caamp";
|
|
20672
|
-
var logger9 = getLogger("domain:tools");
|
|
20673
21442
|
var ToolsHandler = class {
|
|
20674
21443
|
projectRoot;
|
|
20675
21444
|
constructor() {
|
|
@@ -21304,7 +22073,7 @@ var ToolsHandler = class {
|
|
|
21304
22073
|
}
|
|
21305
22074
|
handleError(gateway, domain, operation, error, startTime) {
|
|
21306
22075
|
const message = error instanceof Error ? error.message : String(error);
|
|
21307
|
-
|
|
22076
|
+
getLogger("domain:tools").error({ gateway, domain, operation, err: error }, message);
|
|
21308
22077
|
return this.errorResponse(
|
|
21309
22078
|
gateway,
|
|
21310
22079
|
domain,
|
|
@@ -21318,11 +22087,10 @@ var ToolsHandler = class {
|
|
|
21318
22087
|
|
|
21319
22088
|
// src/dispatch/domains/nexus.ts
|
|
21320
22089
|
init_logger();
|
|
21321
|
-
var logger10 = getLogger("domain:nexus");
|
|
21322
22090
|
var NexusHandler = class {
|
|
21323
22091
|
async query(operation, _params) {
|
|
21324
22092
|
const startTime = Date.now();
|
|
21325
|
-
|
|
22093
|
+
getLogger("domain:nexus").warn({ operation }, `Nexus domain not yet implemented: ${operation}`);
|
|
21326
22094
|
return {
|
|
21327
22095
|
_meta: dispatchMeta("query", "nexus", operation, startTime),
|
|
21328
22096
|
success: false,
|
|
@@ -21331,7 +22099,7 @@ var NexusHandler = class {
|
|
|
21331
22099
|
}
|
|
21332
22100
|
async mutate(operation, _params) {
|
|
21333
22101
|
const startTime = Date.now();
|
|
21334
|
-
|
|
22102
|
+
getLogger("domain:nexus").warn({ operation }, `Nexus domain not yet implemented: ${operation}`);
|
|
21335
22103
|
return {
|
|
21336
22104
|
_meta: dispatchMeta("mutate", "nexus", operation, startTime),
|
|
21337
22105
|
success: false,
|
|
@@ -21349,8 +22117,8 @@ init_logger();
|
|
|
21349
22117
|
|
|
21350
22118
|
// src/core/sharing/index.ts
|
|
21351
22119
|
import { readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
21352
|
-
import { existsSync as
|
|
21353
|
-
import { join as
|
|
22120
|
+
import { existsSync as existsSync42, readdirSync as readdirSync16, statSync as statSync5 } from "node:fs";
|
|
22121
|
+
import { join as join43, relative } from "node:path";
|
|
21354
22122
|
init_paths();
|
|
21355
22123
|
var GITIGNORE_START = "# CLEO:SHARING:START - Auto-managed by cleo sharing sync";
|
|
21356
22124
|
var GITIGNORE_END = "# CLEO:SHARING:END";
|
|
@@ -21378,7 +22146,7 @@ function collectCleoFiles(cleoDir) {
|
|
|
21378
22146
|
const entries = readdirSync16(dir);
|
|
21379
22147
|
for (const entry of entries) {
|
|
21380
22148
|
if (entry === ".git") continue;
|
|
21381
|
-
const fullPath =
|
|
22149
|
+
const fullPath = join43(dir, entry);
|
|
21382
22150
|
const relPath = relative(cleoDir, fullPath);
|
|
21383
22151
|
try {
|
|
21384
22152
|
const stat2 = statSync5(fullPath);
|
|
@@ -21438,7 +22206,7 @@ function generateGitignoreEntries(sharing) {
|
|
|
21438
22206
|
async function syncGitignore(cwd) {
|
|
21439
22207
|
const config = await loadConfig2(cwd);
|
|
21440
22208
|
const projectRoot = getProjectRoot(cwd);
|
|
21441
|
-
const gitignorePath =
|
|
22209
|
+
const gitignorePath = join43(projectRoot, ".gitignore");
|
|
21442
22210
|
const entries = generateGitignoreEntries(config.sharing);
|
|
21443
22211
|
const managedSection = [
|
|
21444
22212
|
"",
|
|
@@ -21448,7 +22216,7 @@ async function syncGitignore(cwd) {
|
|
|
21448
22216
|
""
|
|
21449
22217
|
].join("\n");
|
|
21450
22218
|
let content = "";
|
|
21451
|
-
if (
|
|
22219
|
+
if (existsSync42(gitignorePath)) {
|
|
21452
22220
|
content = await readFile7(gitignorePath, "utf-8");
|
|
21453
22221
|
}
|
|
21454
22222
|
const startIdx = content.indexOf(GITIGNORE_START);
|
|
@@ -21473,8 +22241,8 @@ init_data_accessor();
|
|
|
21473
22241
|
init_paths();
|
|
21474
22242
|
import { createHash as createHash6 } from "node:crypto";
|
|
21475
22243
|
import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir7 } from "node:fs/promises";
|
|
21476
|
-
import { existsSync as
|
|
21477
|
-
import { join as
|
|
22244
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
22245
|
+
import { join as join44, dirname as dirname12 } from "node:path";
|
|
21478
22246
|
var SNAPSHOT_FORMAT_VERSION = "1.0.0";
|
|
21479
22247
|
function toSnapshotTask(task) {
|
|
21480
22248
|
return {
|
|
@@ -21527,8 +22295,8 @@ async function exportSnapshot(cwd) {
|
|
|
21527
22295
|
};
|
|
21528
22296
|
}
|
|
21529
22297
|
async function writeSnapshot(snapshot, outputPath) {
|
|
21530
|
-
const dir =
|
|
21531
|
-
if (!
|
|
22298
|
+
const dir = dirname12(outputPath);
|
|
22299
|
+
if (!existsSync43(dir)) {
|
|
21532
22300
|
await mkdir7(dir, { recursive: true });
|
|
21533
22301
|
}
|
|
21534
22302
|
await writeFile5(outputPath, JSON.stringify(snapshot, null, 2) + "\n");
|
|
@@ -21544,7 +22312,7 @@ async function readSnapshot(inputPath) {
|
|
|
21544
22312
|
function getDefaultSnapshotPath(cwd) {
|
|
21545
22313
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
21546
22314
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
21547
|
-
return
|
|
22315
|
+
return join44(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
|
|
21548
22316
|
}
|
|
21549
22317
|
async function importSnapshot(snapshot, cwd) {
|
|
21550
22318
|
const accessor = await getAccessor(cwd);
|
|
@@ -21611,10 +22379,10 @@ init_paths();
|
|
|
21611
22379
|
init_git_checkpoint();
|
|
21612
22380
|
import { execFile as execFile6 } from "node:child_process";
|
|
21613
22381
|
import { promisify as promisify6 } from "node:util";
|
|
21614
|
-
import { resolve as
|
|
22382
|
+
import { resolve as resolve8 } from "node:path";
|
|
21615
22383
|
var execFileAsync4 = promisify6(execFile6);
|
|
21616
22384
|
async function cleoGitExec(args, cleoDir) {
|
|
21617
|
-
const abs =
|
|
22385
|
+
const abs = resolve8(cleoDir);
|
|
21618
22386
|
const result = await execFileAsync4("git", args, {
|
|
21619
22387
|
cwd: abs,
|
|
21620
22388
|
env: makeCleoGitEnv(cleoDir),
|
|
@@ -21801,7 +22569,6 @@ async function getSyncStatus(remote = "origin", cwd) {
|
|
|
21801
22569
|
}
|
|
21802
22570
|
|
|
21803
22571
|
// src/dispatch/domains/sharing.ts
|
|
21804
|
-
var logger11 = getLogger("domain:sharing");
|
|
21805
22572
|
var SharingHandler = class {
|
|
21806
22573
|
projectRoot;
|
|
21807
22574
|
constructor() {
|
|
@@ -21986,7 +22753,7 @@ var SharingHandler = class {
|
|
|
21986
22753
|
}
|
|
21987
22754
|
handleError(gateway, operation, error, startTime) {
|
|
21988
22755
|
const message = error instanceof Error ? error.message : String(error);
|
|
21989
|
-
|
|
22756
|
+
getLogger("domain:sharing").error({ gateway, operation, err: error }, message);
|
|
21990
22757
|
return {
|
|
21991
22758
|
_meta: dispatchMeta(gateway, "sharing", operation, startTime),
|
|
21992
22759
|
success: false,
|
|
@@ -22049,7 +22816,7 @@ function createSessionResolver(cliSessionLookup) {
|
|
|
22049
22816
|
// src/dispatch/lib/security.ts
|
|
22050
22817
|
init_schema();
|
|
22051
22818
|
init_status_registry();
|
|
22052
|
-
import { resolve as
|
|
22819
|
+
import { resolve as resolve9, normalize, relative as relative2, isAbsolute } from "path";
|
|
22053
22820
|
var SecurityError = class extends Error {
|
|
22054
22821
|
constructor(message, code = "E_SECURITY_VIOLATION", field) {
|
|
22055
22822
|
super(message);
|
|
@@ -22125,12 +22892,12 @@ function sanitizePath(path, projectRoot) {
|
|
|
22125
22892
|
"path"
|
|
22126
22893
|
);
|
|
22127
22894
|
}
|
|
22128
|
-
const normalizedRoot =
|
|
22895
|
+
const normalizedRoot = resolve9(projectRoot);
|
|
22129
22896
|
let resolvedPath;
|
|
22130
22897
|
if (isAbsolute(trimmedPath)) {
|
|
22131
22898
|
resolvedPath = normalize(trimmedPath);
|
|
22132
22899
|
} else {
|
|
22133
|
-
resolvedPath =
|
|
22900
|
+
resolvedPath = resolve9(normalizedRoot, trimmedPath);
|
|
22134
22901
|
}
|
|
22135
22902
|
const relativePath = relative2(normalizedRoot, resolvedPath);
|
|
22136
22903
|
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
@@ -22179,6 +22946,27 @@ function validateEnum(value, allowed, fieldName) {
|
|
|
22179
22946
|
}
|
|
22180
22947
|
var ALL_VALID_STATUSES = [...TASK_STATUSES, ...MANIFEST_STATUSES];
|
|
22181
22948
|
var VALID_PRIORITIES2 = TASK_PRIORITIES;
|
|
22949
|
+
var ARRAY_PARAMS = /* @__PURE__ */ new Set([
|
|
22950
|
+
"labels",
|
|
22951
|
+
"addLabels",
|
|
22952
|
+
"removeLabels",
|
|
22953
|
+
"depends",
|
|
22954
|
+
"addDepends",
|
|
22955
|
+
"removeDepends",
|
|
22956
|
+
"files",
|
|
22957
|
+
"acceptance",
|
|
22958
|
+
"findings",
|
|
22959
|
+
"sources",
|
|
22960
|
+
"tasks",
|
|
22961
|
+
"removeTasks",
|
|
22962
|
+
"taskIds"
|
|
22963
|
+
]);
|
|
22964
|
+
function ensureArray(value, separator = ",") {
|
|
22965
|
+
if (value === void 0 || value === null) return void 0;
|
|
22966
|
+
if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : String(v));
|
|
22967
|
+
if (typeof value === "string") return value.split(separator).map((s) => s.trim()).filter(Boolean);
|
|
22968
|
+
return [String(value)];
|
|
22969
|
+
}
|
|
22182
22970
|
function sanitizeParams(params, projectRoot, context) {
|
|
22183
22971
|
if (!params) {
|
|
22184
22972
|
return params;
|
|
@@ -22238,6 +23026,11 @@ function sanitizeParams(params, projectRoot, context) {
|
|
|
22238
23026
|
continue;
|
|
22239
23027
|
}
|
|
22240
23028
|
}
|
|
23029
|
+
for (const key of Object.keys(sanitized)) {
|
|
23030
|
+
if (ARRAY_PARAMS.has(key) && sanitized[key] !== void 0) {
|
|
23031
|
+
sanitized[key] = ensureArray(sanitized[key]);
|
|
23032
|
+
}
|
|
23033
|
+
}
|
|
22241
23034
|
return sanitized;
|
|
22242
23035
|
}
|
|
22243
23036
|
|
|
@@ -24172,6 +24965,142 @@ function createProtocolEnforcement(strictMode = true) {
|
|
|
24172
24965
|
|
|
24173
24966
|
// src/dispatch/adapters/mcp.ts
|
|
24174
24967
|
init_audit();
|
|
24968
|
+
|
|
24969
|
+
// src/dispatch/lib/projections.ts
|
|
24970
|
+
var PROJECTIONS = {
|
|
24971
|
+
minimal: {
|
|
24972
|
+
allowedDomains: ["tasks", "session", "admin"],
|
|
24973
|
+
excludeFields: ["notes", "history", "metadata._internal", "auditLog"],
|
|
24974
|
+
maxDepth: 2
|
|
24975
|
+
},
|
|
24976
|
+
standard: {
|
|
24977
|
+
allowedDomains: ["tasks", "session", "admin", "memory", "check", "pipeline", "tools", "validate"],
|
|
24978
|
+
excludeFields: ["metadata._internal", "auditLog"],
|
|
24979
|
+
maxDepth: 4
|
|
24980
|
+
},
|
|
24981
|
+
orchestrator: {
|
|
24982
|
+
allowedDomains: [
|
|
24983
|
+
"tasks",
|
|
24984
|
+
"session",
|
|
24985
|
+
"admin",
|
|
24986
|
+
"memory",
|
|
24987
|
+
"check",
|
|
24988
|
+
"pipeline",
|
|
24989
|
+
"orchestrate",
|
|
24990
|
+
"tools",
|
|
24991
|
+
"sharing",
|
|
24992
|
+
"nexus",
|
|
24993
|
+
"validate",
|
|
24994
|
+
"lifecycle",
|
|
24995
|
+
"release",
|
|
24996
|
+
"system"
|
|
24997
|
+
],
|
|
24998
|
+
maxDepth: 8
|
|
24999
|
+
}
|
|
25000
|
+
};
|
|
25001
|
+
var VALID_TIERS = /* @__PURE__ */ new Set(["minimal", "standard", "orchestrator"]);
|
|
25002
|
+
function resolveTier(params, sessionScope) {
|
|
25003
|
+
const mvi = params?._mviTier;
|
|
25004
|
+
if (typeof mvi === "string" && VALID_TIERS.has(mvi)) return mvi;
|
|
25005
|
+
if (sessionScope?.type === "epic") return "orchestrator";
|
|
25006
|
+
return "standard";
|
|
25007
|
+
}
|
|
25008
|
+
|
|
25009
|
+
// src/dispatch/middleware/projection.ts
|
|
25010
|
+
function pruneDepth(data, maxDepth, currentDepth = 0) {
|
|
25011
|
+
if (data === null || data === void 0 || typeof data !== "object") {
|
|
25012
|
+
return data;
|
|
25013
|
+
}
|
|
25014
|
+
if (currentDepth >= maxDepth) {
|
|
25015
|
+
if (Array.isArray(data)) {
|
|
25016
|
+
return `[Array(${data.length})]`;
|
|
25017
|
+
}
|
|
25018
|
+
return "[Object]";
|
|
25019
|
+
}
|
|
25020
|
+
if (Array.isArray(data)) {
|
|
25021
|
+
return data.map((item) => pruneDepth(item, maxDepth, currentDepth + 1));
|
|
25022
|
+
}
|
|
25023
|
+
const result = {};
|
|
25024
|
+
for (const [key, value] of Object.entries(data)) {
|
|
25025
|
+
result[key] = pruneDepth(value, maxDepth, currentDepth + 1);
|
|
25026
|
+
}
|
|
25027
|
+
return result;
|
|
25028
|
+
}
|
|
25029
|
+
function applyProjection(data, config) {
|
|
25030
|
+
if (!data || typeof data !== "object") return data;
|
|
25031
|
+
let result = data;
|
|
25032
|
+
if (config.excludeFields?.length) {
|
|
25033
|
+
const obj = { ...data };
|
|
25034
|
+
for (const field of config.excludeFields) {
|
|
25035
|
+
const parts = field.split(".");
|
|
25036
|
+
if (parts.length === 1) {
|
|
25037
|
+
delete obj[parts[0]];
|
|
25038
|
+
} else {
|
|
25039
|
+
let current = obj;
|
|
25040
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
25041
|
+
const val = current?.[parts[i]];
|
|
25042
|
+
if (val && typeof val === "object") {
|
|
25043
|
+
current[parts[i]] = { ...val };
|
|
25044
|
+
current = current[parts[i]];
|
|
25045
|
+
} else {
|
|
25046
|
+
current = void 0;
|
|
25047
|
+
break;
|
|
25048
|
+
}
|
|
25049
|
+
}
|
|
25050
|
+
if (current) {
|
|
25051
|
+
delete current[parts[parts.length - 1]];
|
|
25052
|
+
}
|
|
25053
|
+
}
|
|
25054
|
+
}
|
|
25055
|
+
result = obj;
|
|
25056
|
+
}
|
|
25057
|
+
if (config.maxDepth !== void 0) {
|
|
25058
|
+
result = pruneDepth(result, config.maxDepth);
|
|
25059
|
+
}
|
|
25060
|
+
return result;
|
|
25061
|
+
}
|
|
25062
|
+
function createProjectionMiddleware() {
|
|
25063
|
+
return async (req, next) => {
|
|
25064
|
+
const session = getBoundSession();
|
|
25065
|
+
const tier = resolveTier(req.params, session?.scope ?? null);
|
|
25066
|
+
const config = PROJECTIONS[tier];
|
|
25067
|
+
if (req.params) {
|
|
25068
|
+
delete req.params["_mviTier"];
|
|
25069
|
+
}
|
|
25070
|
+
if (req.source === "mcp" && req.domain === "tasks" && req.operation === "list") {
|
|
25071
|
+
if (req.params && req.params["compact"] === void 0) {
|
|
25072
|
+
req.params["compact"] = true;
|
|
25073
|
+
}
|
|
25074
|
+
}
|
|
25075
|
+
if (!config.allowedDomains.includes(req.domain)) {
|
|
25076
|
+
return {
|
|
25077
|
+
_meta: {
|
|
25078
|
+
gateway: req.gateway,
|
|
25079
|
+
domain: req.domain,
|
|
25080
|
+
operation: req.operation,
|
|
25081
|
+
version: "1.0.0",
|
|
25082
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25083
|
+
duration_ms: 0,
|
|
25084
|
+
source: req.source,
|
|
25085
|
+
requestId: req.requestId
|
|
25086
|
+
},
|
|
25087
|
+
success: false,
|
|
25088
|
+
error: {
|
|
25089
|
+
code: "E_INVALID_OPERATION",
|
|
25090
|
+
exitCode: 2,
|
|
25091
|
+
message: `Operation not available at '${tier}' tier. Domain '${req.domain}' requires a higher tier.`
|
|
25092
|
+
}
|
|
25093
|
+
};
|
|
25094
|
+
}
|
|
25095
|
+
const response = await next();
|
|
25096
|
+
if (response.success && response.data !== void 0) {
|
|
25097
|
+
response.data = applyProjection(response.data, config);
|
|
25098
|
+
}
|
|
25099
|
+
return response;
|
|
25100
|
+
};
|
|
25101
|
+
}
|
|
25102
|
+
|
|
25103
|
+
// src/dispatch/adapters/mcp.ts
|
|
24175
25104
|
init_paths();
|
|
24176
25105
|
var _dispatcher = null;
|
|
24177
25106
|
function initMcpDispatcher(config = {}) {
|
|
@@ -24184,6 +25113,8 @@ function initMcpDispatcher(config = {}) {
|
|
|
24184
25113
|
createSessionResolver(),
|
|
24185
25114
|
// T4959: session identity first
|
|
24186
25115
|
createSanitizer(() => getProjectRoot()),
|
|
25116
|
+
createProjectionMiddleware(),
|
|
25117
|
+
// T5096: MVI tier-based domain/field projection
|
|
24187
25118
|
createFieldFilter(),
|
|
24188
25119
|
createRateLimiter(config.rateLimiting),
|
|
24189
25120
|
createVerificationGates(strictMode),
|