@cleocode/cleo 2026.2.8 → 2026.3.0
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 +22676 -26012
- package/dist/cli/index.js.map +4 -4
- package/dist/mcp/index.js +1651 -802
- 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 +3 -4
- 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) {
|
|
@@ -894,13 +949,14 @@ __export(sqlite_exports, {
|
|
|
894
949
|
getDbPath: () => getDbPath,
|
|
895
950
|
getNativeDb: () => getNativeDb,
|
|
896
951
|
getSchemaVersion: () => getSchemaVersion,
|
|
952
|
+
isSqliteBusy: () => isSqliteBusy,
|
|
897
953
|
resetDbState: () => resetDbState,
|
|
898
954
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
899
955
|
schema: () => schema_exports
|
|
900
956
|
});
|
|
901
957
|
import { existsSync as existsSync3, mkdirSync } from "node:fs";
|
|
902
958
|
import { createRequire as createRequire2 } from "node:module";
|
|
903
|
-
import { dirname as dirname2, join as join3 } from "node:path";
|
|
959
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
|
|
904
960
|
import { fileURLToPath } from "node:url";
|
|
905
961
|
import { eq } from "drizzle-orm";
|
|
906
962
|
import { readMigrationFiles } from "drizzle-orm/migrator";
|
|
@@ -932,6 +988,22 @@ async function getDb(cwd) {
|
|
|
932
988
|
nativeDb.exec(
|
|
933
989
|
`INSERT OR IGNORE INTO schema_meta (key, value) VALUES ('task_id_sequence', '{"counter":0,"lastId":"T000","checksum":"seed"}')`
|
|
934
990
|
);
|
|
991
|
+
if (!_gitTrackingChecked) {
|
|
992
|
+
_gitTrackingChecked = true;
|
|
993
|
+
try {
|
|
994
|
+
const { execFileSync: execFileSync6 } = await import("node:child_process");
|
|
995
|
+
execFileSync6("git", ["ls-files", "--error-unmatch", dbPath], {
|
|
996
|
+
cwd: resolve3(dbPath, "..", ".."),
|
|
997
|
+
stdio: "pipe"
|
|
998
|
+
});
|
|
999
|
+
const log5 = getLogger("sqlite");
|
|
1000
|
+
log5.warn(
|
|
1001
|
+
{ dbPath },
|
|
1002
|
+
"tasks.db is tracked by project git \u2014 this risks data loss on git operations. Run: git rm --cached .cleo/tasks.db (see ADR-013)"
|
|
1003
|
+
);
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
935
1007
|
_db = db;
|
|
936
1008
|
return db;
|
|
937
1009
|
})();
|
|
@@ -952,6 +1024,11 @@ function tableExists(nativeDb, tableName) {
|
|
|
952
1024
|
).get(tableName);
|
|
953
1025
|
return !!result;
|
|
954
1026
|
}
|
|
1027
|
+
function isSqliteBusy(err) {
|
|
1028
|
+
if (!(err instanceof Error)) return false;
|
|
1029
|
+
const msg = err.message.toLowerCase();
|
|
1030
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1031
|
+
}
|
|
955
1032
|
async function runMigrations(nativeDb, db) {
|
|
956
1033
|
const migrationsFolder = resolveMigrationsFolder();
|
|
957
1034
|
if (tableExists(nativeDb, "tasks") && !tableExists(nativeDb, "__drizzle_migrations")) {
|
|
@@ -971,16 +1048,33 @@ async function runMigrations(nativeDb, db) {
|
|
|
971
1048
|
}
|
|
972
1049
|
}
|
|
973
1050
|
await migrate(db, async (queries) => {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
nativeDb.prepare(
|
|
1051
|
+
let lastError;
|
|
1052
|
+
for (let attempt = 1; attempt <= MAX_MIGRATION_RETRIES; attempt++) {
|
|
1053
|
+
try {
|
|
1054
|
+
nativeDb.prepare("BEGIN IMMEDIATE").run();
|
|
1055
|
+
try {
|
|
1056
|
+
for (const query of queries) {
|
|
1057
|
+
nativeDb.prepare(query).run();
|
|
1058
|
+
}
|
|
1059
|
+
nativeDb.prepare("COMMIT").run();
|
|
1060
|
+
return;
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
nativeDb.prepare("ROLLBACK").run();
|
|
1063
|
+
throw err;
|
|
1064
|
+
}
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
if (!isSqliteBusy(err) || attempt === MAX_MIGRATION_RETRIES) {
|
|
1067
|
+
throw err;
|
|
1068
|
+
}
|
|
1069
|
+
lastError = err;
|
|
1070
|
+
const exponentialDelay = MIGRATION_RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
1071
|
+
const jitter = Math.random() * exponentialDelay * 0.5;
|
|
1072
|
+
const delay = Math.min(exponentialDelay + jitter, MIGRATION_RETRY_MAX_DELAY_MS);
|
|
1073
|
+
const buf = new SharedArrayBuffer(4);
|
|
1074
|
+
Atomics.wait(new Int32Array(buf), 0, 0, Math.round(delay));
|
|
978
1075
|
}
|
|
979
|
-
nativeDb.prepare("COMMIT").run();
|
|
980
|
-
} catch (err) {
|
|
981
|
-
nativeDb.prepare("ROLLBACK").run();
|
|
982
|
-
throw err;
|
|
983
1076
|
}
|
|
1077
|
+
throw lastError;
|
|
984
1078
|
}, { migrationsFolder });
|
|
985
1079
|
}
|
|
986
1080
|
function closeDb() {
|
|
@@ -1021,13 +1115,14 @@ function dbExists(cwd) {
|
|
|
1021
1115
|
function getNativeDb() {
|
|
1022
1116
|
return _nativeDb;
|
|
1023
1117
|
}
|
|
1024
|
-
var _require2, DatabaseSync2, DB_FILENAME, SQLITE_SCHEMA_VERSION, SCHEMA_VERSION, _db, _nativeDb, _dbPath, _initPromise;
|
|
1118
|
+
var _require2, DatabaseSync2, DB_FILENAME, SQLITE_SCHEMA_VERSION, SCHEMA_VERSION, _db, _nativeDb, _dbPath, _initPromise, _gitTrackingChecked, MAX_MIGRATION_RETRIES, MIGRATION_RETRY_BASE_DELAY_MS, MIGRATION_RETRY_MAX_DELAY_MS;
|
|
1025
1119
|
var init_sqlite = __esm({
|
|
1026
1120
|
"src/store/sqlite.ts"() {
|
|
1027
1121
|
"use strict";
|
|
1028
1122
|
init_schema();
|
|
1029
1123
|
init_paths();
|
|
1030
1124
|
init_node_sqlite_adapter();
|
|
1125
|
+
init_logger();
|
|
1031
1126
|
_require2 = createRequire2(import.meta.url);
|
|
1032
1127
|
({ DatabaseSync: DatabaseSync2 } = _require2("node:sqlite"));
|
|
1033
1128
|
DB_FILENAME = "tasks.db";
|
|
@@ -1037,6 +1132,10 @@ var init_sqlite = __esm({
|
|
|
1037
1132
|
_nativeDb = null;
|
|
1038
1133
|
_dbPath = null;
|
|
1039
1134
|
_initPromise = null;
|
|
1135
|
+
_gitTrackingChecked = false;
|
|
1136
|
+
MAX_MIGRATION_RETRIES = 5;
|
|
1137
|
+
MIGRATION_RETRY_BASE_DELAY_MS = 100;
|
|
1138
|
+
MIGRATION_RETRY_MAX_DELAY_MS = 2e3;
|
|
1040
1139
|
}
|
|
1041
1140
|
});
|
|
1042
1141
|
|
|
@@ -1803,6 +1902,30 @@ async function loadDependenciesForTasks(db, tasks2, validationIds) {
|
|
|
1803
1902
|
}
|
|
1804
1903
|
}
|
|
1805
1904
|
}
|
|
1905
|
+
async function loadRelationsForTasks(db, tasks2) {
|
|
1906
|
+
if (tasks2.length === 0) return;
|
|
1907
|
+
const taskIds = tasks2.map((t) => t.id);
|
|
1908
|
+
const allRels = await db.select().from(taskRelations).where(inArray(taskRelations.taskId, taskIds)).all();
|
|
1909
|
+
const relMap = /* @__PURE__ */ new Map();
|
|
1910
|
+
for (const rel of allRels) {
|
|
1911
|
+
let arr = relMap.get(rel.taskId);
|
|
1912
|
+
if (!arr) {
|
|
1913
|
+
arr = [];
|
|
1914
|
+
relMap.set(rel.taskId, arr);
|
|
1915
|
+
}
|
|
1916
|
+
arr.push({
|
|
1917
|
+
taskId: rel.relatedTo,
|
|
1918
|
+
type: rel.relationType,
|
|
1919
|
+
reason: rel.reason ?? void 0
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
for (const task of tasks2) {
|
|
1923
|
+
const relations = relMap.get(task.id);
|
|
1924
|
+
if (relations && relations.length > 0) {
|
|
1925
|
+
task.relates = relations;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1806
1929
|
var init_db_helpers = __esm({
|
|
1807
1930
|
"src/store/db-helpers.ts"() {
|
|
1808
1931
|
"use strict";
|
|
@@ -1851,6 +1974,7 @@ async function createSqliteDataAccessor(cwd) {
|
|
|
1851
1974
|
const tasks2 = taskRows.map(rowToTask);
|
|
1852
1975
|
if (tasks2.length > 0) {
|
|
1853
1976
|
await loadDependenciesForTasks(db, tasks2);
|
|
1977
|
+
await loadRelationsForTasks(db, tasks2);
|
|
1854
1978
|
}
|
|
1855
1979
|
const projectMeta = await getMetaValue(cwd, "project_meta") ?? DEFAULT_PROJECT_META;
|
|
1856
1980
|
const workState = await getMetaValue(cwd, "focus_state") ?? DEFAULT_WORK_STATE;
|
|
@@ -1926,6 +2050,7 @@ async function createSqliteDataAccessor(cwd) {
|
|
|
1926
2050
|
...activeRows.map((r) => r.id)
|
|
1927
2051
|
]);
|
|
1928
2052
|
await loadDependenciesForTasks(db, archivedTasks, allKnownIds);
|
|
2053
|
+
await loadRelationsForTasks(db, archivedTasks);
|
|
1929
2054
|
}
|
|
1930
2055
|
return {
|
|
1931
2056
|
archivedTasks,
|
|
@@ -1992,6 +2117,14 @@ async function createSqliteDataAccessor(cwd) {
|
|
|
1992
2117
|
await upsertTask(db, row);
|
|
1993
2118
|
await updateDependencies(db, task.id, task.depends ?? []);
|
|
1994
2119
|
},
|
|
2120
|
+
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
2121
|
+
const db = await getDb(cwd);
|
|
2122
|
+
const validTypes = ["related", "blocks", "duplicates", "absorbs", "fixes", "extends", "supersedes"];
|
|
2123
|
+
if (!validTypes.includes(relationType)) {
|
|
2124
|
+
throw new Error(`Invalid relation type: ${relationType}. Valid types: ${validTypes.join(", ")}`);
|
|
2125
|
+
}
|
|
2126
|
+
await db.insert(taskRelations).values({ taskId, relatedTo, relationType, reason: reason ?? null }).onConflictDoNothing().run();
|
|
2127
|
+
},
|
|
1995
2128
|
async archiveSingleTask(taskId, fields) {
|
|
1996
2129
|
const db = await getDb(cwd);
|
|
1997
2130
|
const rows = await db.select({ id: tasks.id }).from(tasks).where(eq3(tasks.id, taskId)).all();
|
|
@@ -2066,9 +2199,9 @@ import { readFile as readFile2, writeFile } from "node:fs/promises";
|
|
|
2066
2199
|
import { existsSync as existsSync4 } from "node:fs";
|
|
2067
2200
|
import { execFile } from "node:child_process";
|
|
2068
2201
|
import { promisify } from "node:util";
|
|
2069
|
-
import { join as join5, resolve as
|
|
2202
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
2070
2203
|
function makeCleoGitEnv(cleoDir) {
|
|
2071
|
-
const abs =
|
|
2204
|
+
const abs = resolve4(cleoDir);
|
|
2072
2205
|
return {
|
|
2073
2206
|
...process.env,
|
|
2074
2207
|
GIT_DIR: join5(abs, ".git"),
|
|
@@ -2078,7 +2211,7 @@ function makeCleoGitEnv(cleoDir) {
|
|
|
2078
2211
|
async function cleoGitCommand(args, cleoDir) {
|
|
2079
2212
|
try {
|
|
2080
2213
|
const result = await execFileAsync("git", args, {
|
|
2081
|
-
cwd:
|
|
2214
|
+
cwd: resolve4(cleoDir),
|
|
2082
2215
|
// absolute cwd so relative paths in args resolve correctly
|
|
2083
2216
|
env: makeCleoGitEnv(cleoDir),
|
|
2084
2217
|
timeout: 1e4
|
|
@@ -2294,6 +2427,7 @@ var init_sqlite_backup = __esm({
|
|
|
2294
2427
|
// src/core/sequence/index.ts
|
|
2295
2428
|
var sequence_exports = {};
|
|
2296
2429
|
__export(sequence_exports, {
|
|
2430
|
+
allocateNextTaskId: () => allocateNextTaskId,
|
|
2297
2431
|
checkSequence: () => checkSequence,
|
|
2298
2432
|
repairSequence: () => repairSequence,
|
|
2299
2433
|
showSequence: () => showSequence
|
|
@@ -2480,7 +2614,56 @@ async function repairSequence(cwd, accessor) {
|
|
|
2480
2614
|
message: `Sequence repaired: ${oldCounter} -> ${newCounter}`
|
|
2481
2615
|
};
|
|
2482
2616
|
}
|
|
2483
|
-
|
|
2617
|
+
async function allocateNextTaskId(cwd, retryCount = 0) {
|
|
2618
|
+
await getDb(cwd);
|
|
2619
|
+
const nativeDb = getNativeDb();
|
|
2620
|
+
if (!nativeDb) {
|
|
2621
|
+
throw new CleoError(3 /* FILE_ERROR */, "Native database not available for atomic ID allocation");
|
|
2622
|
+
}
|
|
2623
|
+
nativeDb.prepare("BEGIN IMMEDIATE").run();
|
|
2624
|
+
try {
|
|
2625
|
+
nativeDb.prepare(`
|
|
2626
|
+
UPDATE schema_meta
|
|
2627
|
+
SET value = json_set(value,
|
|
2628
|
+
'$.counter', json_extract(value, '$.counter') + 1,
|
|
2629
|
+
'$.lastId', 'T' || printf('%03d', json_extract(value, '$.counter') + 1),
|
|
2630
|
+
'$.checksum', 'alloc-' || strftime('%s','now')
|
|
2631
|
+
)
|
|
2632
|
+
WHERE key = 'task_id_sequence'
|
|
2633
|
+
`).run();
|
|
2634
|
+
const row = nativeDb.prepare(`
|
|
2635
|
+
SELECT json_extract(value, '$.counter') AS counter
|
|
2636
|
+
FROM schema_meta WHERE key = 'task_id_sequence'
|
|
2637
|
+
`).get();
|
|
2638
|
+
if (!row) {
|
|
2639
|
+
throw new CleoError(3 /* FILE_ERROR */, "Sequence counter not found after increment");
|
|
2640
|
+
}
|
|
2641
|
+
const newId = `T${String(row.counter).padStart(3, "0")}`;
|
|
2642
|
+
const existing = nativeDb.prepare(
|
|
2643
|
+
"SELECT id FROM tasks WHERE id = ?"
|
|
2644
|
+
).get(newId);
|
|
2645
|
+
if (existing) {
|
|
2646
|
+
nativeDb.prepare("ROLLBACK").run();
|
|
2647
|
+
if (retryCount >= MAX_ALLOC_RETRIES) {
|
|
2648
|
+
throw new CleoError(
|
|
2649
|
+
22 /* ID_COLLISION */,
|
|
2650
|
+
`Failed to allocate unique task ID after ${MAX_ALLOC_RETRIES} retries (last attempted: ${newId})`
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
await repairSequence(cwd);
|
|
2654
|
+
return allocateNextTaskId(cwd, retryCount + 1);
|
|
2655
|
+
}
|
|
2656
|
+
nativeDb.prepare("COMMIT").run();
|
|
2657
|
+
return newId;
|
|
2658
|
+
} catch (err) {
|
|
2659
|
+
try {
|
|
2660
|
+
nativeDb.prepare("ROLLBACK").run();
|
|
2661
|
+
} catch {
|
|
2662
|
+
}
|
|
2663
|
+
throw err;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
var SEQUENCE_META_KEY, MAX_ALLOC_RETRIES;
|
|
2484
2667
|
var init_sequence = __esm({
|
|
2485
2668
|
"src/core/sequence/index.ts"() {
|
|
2486
2669
|
"use strict";
|
|
@@ -2491,6 +2674,7 @@ var init_sequence = __esm({
|
|
|
2491
2674
|
init_sqlite_data_accessor();
|
|
2492
2675
|
init_data_accessor();
|
|
2493
2676
|
SEQUENCE_META_KEY = "task_id_sequence";
|
|
2677
|
+
MAX_ALLOC_RETRIES = 3;
|
|
2494
2678
|
}
|
|
2495
2679
|
});
|
|
2496
2680
|
|
|
@@ -2803,6 +2987,11 @@ var init_safety_data_accessor = __esm({
|
|
|
2803
2987
|
this.getSafetyOptions()
|
|
2804
2988
|
);
|
|
2805
2989
|
}
|
|
2990
|
+
// ---- Relations (pass-through to inner, T5168) ----
|
|
2991
|
+
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
2992
|
+
if (!this.inner.addRelation) return;
|
|
2993
|
+
await this.inner.addRelation(taskId, relatedTo, relationType, reason);
|
|
2994
|
+
}
|
|
2806
2995
|
// ---- Metadata (pass-through to inner) ----
|
|
2807
2996
|
async getMetaValue(key) {
|
|
2808
2997
|
return this.inner.getMetaValue?.(key) ?? null;
|
|
@@ -2838,10 +3027,57 @@ var init_data_accessor = __esm({
|
|
|
2838
3027
|
}
|
|
2839
3028
|
});
|
|
2840
3029
|
|
|
2841
|
-
// src/
|
|
2842
|
-
import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync3,
|
|
2843
|
-
import { join as join8, dirname as dirname5, basename as basename2 } from "path";
|
|
3030
|
+
// src/store/file-utils.ts
|
|
3031
|
+
import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync3, existsSync as existsSync8, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
3032
|
+
import { join as join8, dirname as dirname5, basename as basename2 } from "node:path";
|
|
3033
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2844
3034
|
import * as lockfile2 from "proper-lockfile";
|
|
3035
|
+
function rotateBackup(filePath) {
|
|
3036
|
+
const dir = dirname5(filePath);
|
|
3037
|
+
const name = basename2(filePath);
|
|
3038
|
+
const backupDir = join8(dir, ".backups");
|
|
3039
|
+
if (!existsSync8(backupDir)) {
|
|
3040
|
+
mkdirSync3(backupDir, { recursive: true });
|
|
3041
|
+
}
|
|
3042
|
+
for (let i = MAX_BACKUPS; i >= 1; i--) {
|
|
3043
|
+
const current = join8(backupDir, `${name}.${i}`);
|
|
3044
|
+
if (i === MAX_BACKUPS) {
|
|
3045
|
+
try {
|
|
3046
|
+
unlinkSync2(current);
|
|
3047
|
+
} catch {
|
|
3048
|
+
}
|
|
3049
|
+
} else {
|
|
3050
|
+
const next = join8(backupDir, `${name}.${i + 1}`);
|
|
3051
|
+
try {
|
|
3052
|
+
if (existsSync8(current)) renameSync3(current, next);
|
|
3053
|
+
} catch {
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
try {
|
|
3058
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3059
|
+
writeFileSync(join8(backupDir, `${name}.1`), content, "utf-8");
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
function writeJsonFileAtomic(filePath, data, indent = 2) {
|
|
3064
|
+
const dir = dirname5(filePath);
|
|
3065
|
+
const tempPath = join8(dir, `.${basename2(filePath)}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
3066
|
+
const content = JSON.stringify(data, null, indent) + "\n";
|
|
3067
|
+
writeFileSync(tempPath, content, "utf-8");
|
|
3068
|
+
try {
|
|
3069
|
+
if (existsSync8(filePath)) {
|
|
3070
|
+
rotateBackup(filePath);
|
|
3071
|
+
}
|
|
3072
|
+
renameSync3(tempPath, filePath);
|
|
3073
|
+
} catch (error) {
|
|
3074
|
+
try {
|
|
3075
|
+
unlinkSync2(tempPath);
|
|
3076
|
+
} catch {
|
|
3077
|
+
}
|
|
3078
|
+
throw error;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
2845
3081
|
function readJsonFile(filePath) {
|
|
2846
3082
|
try {
|
|
2847
3083
|
const content = readFileSync4(filePath, "utf-8");
|
|
@@ -2853,12 +3089,95 @@ function readJsonFile(filePath) {
|
|
|
2853
3089
|
throw error;
|
|
2854
3090
|
}
|
|
2855
3091
|
}
|
|
3092
|
+
function readLogFileEntries(filePath) {
|
|
3093
|
+
let content;
|
|
3094
|
+
try {
|
|
3095
|
+
content = readFileSync4(filePath, "utf-8").trim();
|
|
3096
|
+
} catch (error) {
|
|
3097
|
+
if (error.code === "ENOENT") return [];
|
|
3098
|
+
throw error;
|
|
3099
|
+
}
|
|
3100
|
+
if (!content) return [];
|
|
3101
|
+
try {
|
|
3102
|
+
const parsed = JSON.parse(content);
|
|
3103
|
+
if (Array.isArray(parsed)) return parsed;
|
|
3104
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.entries)) {
|
|
3105
|
+
return parsed.entries;
|
|
3106
|
+
}
|
|
3107
|
+
return [parsed];
|
|
3108
|
+
} catch {
|
|
3109
|
+
}
|
|
3110
|
+
const entries = [];
|
|
3111
|
+
if (content.startsWith("{")) {
|
|
3112
|
+
let depth = 0;
|
|
3113
|
+
let inString = false;
|
|
3114
|
+
let escaped = false;
|
|
3115
|
+
let jsonEnd = -1;
|
|
3116
|
+
for (let i = 0; i < content.length; i++) {
|
|
3117
|
+
const ch = content[i];
|
|
3118
|
+
if (escaped) {
|
|
3119
|
+
escaped = false;
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
if (ch === "\\" && inString) {
|
|
3123
|
+
escaped = true;
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
if (ch === '"') {
|
|
3127
|
+
inString = !inString;
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
if (inString) continue;
|
|
3131
|
+
if (ch === "{") depth++;
|
|
3132
|
+
else if (ch === "}") {
|
|
3133
|
+
depth--;
|
|
3134
|
+
if (depth === 0) {
|
|
3135
|
+
jsonEnd = i + 1;
|
|
3136
|
+
break;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
if (jsonEnd > 0) {
|
|
3141
|
+
try {
|
|
3142
|
+
const initial = JSON.parse(content.substring(0, jsonEnd));
|
|
3143
|
+
if (initial && Array.isArray(initial.entries)) entries.push(...initial.entries);
|
|
3144
|
+
} catch {
|
|
3145
|
+
}
|
|
3146
|
+
const remainder = content.substring(jsonEnd).trim();
|
|
3147
|
+
if (remainder) {
|
|
3148
|
+
for (const line of remainder.split("\n")) {
|
|
3149
|
+
const l = line.trim();
|
|
3150
|
+
if (!l || !l.startsWith("{")) continue;
|
|
3151
|
+
try {
|
|
3152
|
+
entries.push(JSON.parse(l));
|
|
3153
|
+
} catch {
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
} else {
|
|
3159
|
+
for (const line of content.split("\n")) {
|
|
3160
|
+
const l = line.trim();
|
|
3161
|
+
if (!l || !l.startsWith("{")) continue;
|
|
3162
|
+
try {
|
|
3163
|
+
entries.push(JSON.parse(l));
|
|
3164
|
+
} catch {
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
return entries;
|
|
3169
|
+
}
|
|
2856
3170
|
function getDataPath(projectRoot, filename) {
|
|
2857
3171
|
return join8(projectRoot, ".cleo", filename);
|
|
2858
3172
|
}
|
|
2859
|
-
|
|
2860
|
-
"
|
|
3173
|
+
function resolveProjectRoot() {
|
|
3174
|
+
return process.env["CLEO_ROOT"] || process.cwd();
|
|
3175
|
+
}
|
|
3176
|
+
var MAX_BACKUPS;
|
|
3177
|
+
var init_file_utils = __esm({
|
|
3178
|
+
"src/store/file-utils.ts"() {
|
|
2861
3179
|
"use strict";
|
|
3180
|
+
MAX_BACKUPS = 10;
|
|
2862
3181
|
}
|
|
2863
3182
|
});
|
|
2864
3183
|
|
|
@@ -3102,7 +3421,7 @@ var init_plan = __esm({
|
|
|
3102
3421
|
"src/core/tasks/plan.ts"() {
|
|
3103
3422
|
"use strict";
|
|
3104
3423
|
init_data_accessor();
|
|
3105
|
-
|
|
3424
|
+
init_file_utils();
|
|
3106
3425
|
init_deps_ready();
|
|
3107
3426
|
PRIORITY_SCORE2 = {
|
|
3108
3427
|
critical: 100,
|
|
@@ -3114,7 +3433,7 @@ var init_plan = __esm({
|
|
|
3114
3433
|
});
|
|
3115
3434
|
|
|
3116
3435
|
// src/core/sessions/decisions.ts
|
|
3117
|
-
import { randomBytes as
|
|
3436
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3118
3437
|
import { readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "node:fs";
|
|
3119
3438
|
import { join as join9 } from "node:path";
|
|
3120
3439
|
async function recordDecision(projectRoot, params) {
|
|
@@ -3130,7 +3449,7 @@ async function recordDecision(projectRoot, params) {
|
|
|
3130
3449
|
}
|
|
3131
3450
|
const decisionPath = join9(auditDir, "decisions.jsonl");
|
|
3132
3451
|
const record = {
|
|
3133
|
-
id: `dec-${
|
|
3452
|
+
id: `dec-${randomBytes3(8).toString("hex")}`,
|
|
3134
3453
|
sessionId: params.sessionId,
|
|
3135
3454
|
taskId: params.taskId,
|
|
3136
3455
|
decision: params.decision,
|
|
@@ -3416,189 +3735,36 @@ async function computeChainPosition(projectRoot, sessionId) {
|
|
|
3416
3735
|
let current = sessionId;
|
|
3417
3736
|
let position = 1;
|
|
3418
3737
|
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
|
-
}
|
|
3738
|
+
while (true) {
|
|
3739
|
+
visited.add(current);
|
|
3740
|
+
const session = sessionMap.get(current);
|
|
3741
|
+
if (!session?.previousSessionId || visited.has(session.previousSessionId)) break;
|
|
3742
|
+
current = session.previousSessionId;
|
|
3743
|
+
position++;
|
|
3744
|
+
}
|
|
3745
|
+
let length = position;
|
|
3746
|
+
const startSession = sessionMap.get(sessionId);
|
|
3747
|
+
let fwd = startSession?.nextSessionId;
|
|
3748
|
+
while (fwd && !visited.has(fwd)) {
|
|
3749
|
+
visited.add(fwd);
|
|
3750
|
+
length++;
|
|
3751
|
+
const s = sessionMap.get(fwd);
|
|
3752
|
+
fwd = s?.nextSessionId ?? void 0;
|
|
3587
3753
|
}
|
|
3754
|
+
return { position, length };
|
|
3755
|
+
} catch {
|
|
3756
|
+
return { position: 1, length: 1 };
|
|
3588
3757
|
}
|
|
3589
|
-
return entries;
|
|
3590
|
-
}
|
|
3591
|
-
function getDataPath2(projectRoot, filename) {
|
|
3592
|
-
return join11(projectRoot, ".cleo", filename);
|
|
3593
3758
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
var MAX_BACKUPS;
|
|
3598
|
-
var init_file_utils = __esm({
|
|
3599
|
-
"src/store/file-utils.ts"() {
|
|
3759
|
+
var execFileAsync2;
|
|
3760
|
+
var init_handoff = __esm({
|
|
3761
|
+
"src/core/sessions/handoff.ts"() {
|
|
3600
3762
|
"use strict";
|
|
3601
|
-
|
|
3763
|
+
init_data_accessor();
|
|
3764
|
+
init_errors();
|
|
3765
|
+
init_exit_codes();
|
|
3766
|
+
init_decisions();
|
|
3767
|
+
execFileAsync2 = promisify2(execFile2);
|
|
3602
3768
|
}
|
|
3603
3769
|
});
|
|
3604
3770
|
|
|
@@ -3613,15 +3779,15 @@ __export(platform_exports, {
|
|
|
3613
3779
|
dateDaysAgo: () => dateDaysAgo,
|
|
3614
3780
|
detectPlatform: () => detectPlatform,
|
|
3615
3781
|
generateRandomHex: () => generateRandomHex,
|
|
3616
|
-
getDataPath: () =>
|
|
3782
|
+
getDataPath: () => getDataPath,
|
|
3617
3783
|
getFileMtime: () => getFileMtime,
|
|
3618
3784
|
getFileSize: () => getFileSize,
|
|
3619
3785
|
getIsoTimestamp: () => getIsoTimestamp,
|
|
3620
3786
|
getNodeUpgradeInstructions: () => getNodeUpgradeInstructions,
|
|
3621
3787
|
getNodeVersionInfo: () => getNodeVersionInfo,
|
|
3622
3788
|
isoToEpoch: () => isoToEpoch,
|
|
3623
|
-
readJsonFile: () =>
|
|
3624
|
-
readLogFileEntries: () =>
|
|
3789
|
+
readJsonFile: () => readJsonFile,
|
|
3790
|
+
readLogFileEntries: () => readLogFileEntries,
|
|
3625
3791
|
requireTool: () => requireTool,
|
|
3626
3792
|
resolveProjectRoot: () => resolveProjectRoot,
|
|
3627
3793
|
sha256: () => sha256,
|
|
@@ -3629,8 +3795,8 @@ __export(platform_exports, {
|
|
|
3629
3795
|
});
|
|
3630
3796
|
import { execFileSync } from "node:child_process";
|
|
3631
3797
|
import { tmpdir } from "node:os";
|
|
3632
|
-
import { join as
|
|
3633
|
-
import { existsSync as
|
|
3798
|
+
import { join as join11 } from "node:path";
|
|
3799
|
+
import { existsSync as existsSync11, statSync as statSync2 } from "node:fs";
|
|
3634
3800
|
import { createHash as createHash3, randomBytes as randomBytes6 } from "node:crypto";
|
|
3635
3801
|
function detectPlatform() {
|
|
3636
3802
|
switch (process.platform) {
|
|
@@ -3689,11 +3855,11 @@ function dateDaysAgo(days) {
|
|
|
3689
3855
|
return d.toISOString();
|
|
3690
3856
|
}
|
|
3691
3857
|
function getFileSize(filePath) {
|
|
3692
|
-
if (!
|
|
3858
|
+
if (!existsSync11(filePath)) return 0;
|
|
3693
3859
|
return statSync2(filePath).size;
|
|
3694
3860
|
}
|
|
3695
3861
|
function getFileMtime(filePath) {
|
|
3696
|
-
if (!
|
|
3862
|
+
if (!existsSync11(filePath)) return null;
|
|
3697
3863
|
return statSync2(filePath).mtime.toISOString();
|
|
3698
3864
|
}
|
|
3699
3865
|
function generateRandomHex(bytes = 6) {
|
|
@@ -3704,7 +3870,7 @@ function sha256(data) {
|
|
|
3704
3870
|
}
|
|
3705
3871
|
function createTempFilePath(prefix = "cleo-", suffix = ".tmp") {
|
|
3706
3872
|
const random = generateRandomHex(8);
|
|
3707
|
-
return
|
|
3873
|
+
return join11(tmpdir(), `${prefix}${random}${suffix}`);
|
|
3708
3874
|
}
|
|
3709
3875
|
function getNodeVersionInfo() {
|
|
3710
3876
|
const version = process.version.replace("v", "");
|
|
@@ -3834,20 +4000,20 @@ __export(schema_integrity_exports, {
|
|
|
3834
4000
|
checkSchemaIntegrity: () => checkSchemaIntegrity,
|
|
3835
4001
|
readSchemaVersionFromFile: () => readSchemaVersionFromFile
|
|
3836
4002
|
});
|
|
3837
|
-
import { existsSync as
|
|
3838
|
-
import { join as
|
|
4003
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14 } from "node:fs";
|
|
4004
|
+
import { join as join21, dirname as dirname6 } from "node:path";
|
|
3839
4005
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3840
4006
|
function resolveSchemaPath(schemaName) {
|
|
3841
|
-
const __dirname2 =
|
|
4007
|
+
const __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
3842
4008
|
const candidates = [
|
|
3843
4009
|
// dist/core/validation/ → schemas/
|
|
3844
|
-
|
|
4010
|
+
join21(__dirname2, "..", "..", "..", "schemas", schemaName),
|
|
3845
4011
|
// src/core/validation/ → schemas/ (ts-node / vitest)
|
|
3846
|
-
|
|
4012
|
+
join21(__dirname2, "..", "..", "schemas", schemaName),
|
|
3847
4013
|
// fallback: look relative to cwd
|
|
3848
|
-
|
|
4014
|
+
join21(process.cwd(), "schemas", schemaName)
|
|
3849
4015
|
];
|
|
3850
|
-
return candidates.find(
|
|
4016
|
+
return candidates.find(existsSync21) ?? null;
|
|
3851
4017
|
}
|
|
3852
4018
|
function readSchemaVersionFromFile(schemaName) {
|
|
3853
4019
|
const path = resolveSchemaPath(schemaName);
|
|
@@ -3856,7 +4022,7 @@ function readSchemaVersionFromFile(schemaName) {
|
|
|
3856
4022
|
}
|
|
3857
4023
|
function readSchemaVersion(schemaPath) {
|
|
3858
4024
|
try {
|
|
3859
|
-
const raw =
|
|
4025
|
+
const raw = readFileSync14(schemaPath, "utf-8");
|
|
3860
4026
|
const schema = JSON.parse(raw);
|
|
3861
4027
|
const version = schema["schemaVersion"] ?? schema["_meta"]?.["schemaVersion"];
|
|
3862
4028
|
return typeof version === "string" ? version : null;
|
|
@@ -3866,7 +4032,7 @@ function readSchemaVersion(schemaPath) {
|
|
|
3866
4032
|
}
|
|
3867
4033
|
function checkFile(target, cleoDir) {
|
|
3868
4034
|
const filePath = target.filePath(cleoDir);
|
|
3869
|
-
if (!
|
|
4035
|
+
if (!existsSync21(filePath)) {
|
|
3870
4036
|
if (target.required) {
|
|
3871
4037
|
return {
|
|
3872
4038
|
label: target.label,
|
|
@@ -3878,7 +4044,7 @@ function checkFile(target, cleoDir) {
|
|
|
3878
4044
|
}
|
|
3879
4045
|
let data;
|
|
3880
4046
|
try {
|
|
3881
|
-
data = JSON.parse(
|
|
4047
|
+
data = JSON.parse(readFileSync14(filePath, "utf-8"));
|
|
3882
4048
|
} catch (err) {
|
|
3883
4049
|
return {
|
|
3884
4050
|
label: target.label,
|
|
@@ -3896,7 +4062,7 @@ function checkFile(target, cleoDir) {
|
|
|
3896
4062
|
}
|
|
3897
4063
|
let schema;
|
|
3898
4064
|
try {
|
|
3899
|
-
schema = JSON.parse(
|
|
4065
|
+
schema = JSON.parse(readFileSync14(schemaPath, "utf-8"));
|
|
3900
4066
|
} catch (err) {
|
|
3901
4067
|
return {
|
|
3902
4068
|
label: target.label,
|
|
@@ -3960,28 +4126,28 @@ var init_schema_integrity = __esm({
|
|
|
3960
4126
|
INTEGRITY_TARGETS = [
|
|
3961
4127
|
{
|
|
3962
4128
|
label: "config.json",
|
|
3963
|
-
filePath: (d) =>
|
|
4129
|
+
filePath: (d) => join21(d, "config.json"),
|
|
3964
4130
|
schemaName: "config.schema.json",
|
|
3965
4131
|
required: true,
|
|
3966
4132
|
versionKey: "schemaVersion"
|
|
3967
4133
|
},
|
|
3968
4134
|
{
|
|
3969
4135
|
label: "project-info.json",
|
|
3970
|
-
filePath: (d) =>
|
|
4136
|
+
filePath: (d) => join21(d, "project-info.json"),
|
|
3971
4137
|
schemaName: "project-info.schema.json",
|
|
3972
4138
|
required: false,
|
|
3973
4139
|
versionKey: "schemaVersion"
|
|
3974
4140
|
},
|
|
3975
4141
|
{
|
|
3976
4142
|
label: "project-context.json",
|
|
3977
|
-
filePath: (d) =>
|
|
4143
|
+
filePath: (d) => join21(d, "project-context.json"),
|
|
3978
4144
|
schemaName: "project-context.schema.json",
|
|
3979
4145
|
required: false,
|
|
3980
4146
|
versionKey: "schemaVersion"
|
|
3981
4147
|
},
|
|
3982
4148
|
{
|
|
3983
4149
|
label: ".context-state.json",
|
|
3984
|
-
filePath: (d) =>
|
|
4150
|
+
filePath: (d) => join21(d, ".context-state.json"),
|
|
3985
4151
|
schemaName: "context-state.schema.json",
|
|
3986
4152
|
required: false,
|
|
3987
4153
|
versionKey: "version"
|
|
@@ -3996,17 +4162,17 @@ __export(mcp_exports, {
|
|
|
3996
4162
|
detectEnvMode: () => detectEnvMode,
|
|
3997
4163
|
generateMcpServerEntry: () => generateMcpServerEntry
|
|
3998
4164
|
});
|
|
3999
|
-
import { readFileSync as
|
|
4000
|
-
import { join as
|
|
4165
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
4166
|
+
import { join as join22 } from "node:path";
|
|
4001
4167
|
import { homedir as homedir3 } from "node:os";
|
|
4002
4168
|
function detectEnvMode() {
|
|
4003
|
-
const versionPath =
|
|
4004
|
-
process.env["CLEO_HOME"] ??
|
|
4169
|
+
const versionPath = join22(
|
|
4170
|
+
process.env["CLEO_HOME"] ?? join22(homedir3(), ".cleo"),
|
|
4005
4171
|
"VERSION"
|
|
4006
4172
|
);
|
|
4007
4173
|
let content;
|
|
4008
4174
|
try {
|
|
4009
|
-
content =
|
|
4175
|
+
content = readFileSync15(versionPath, "utf-8");
|
|
4010
4176
|
} catch {
|
|
4011
4177
|
return { mode: "unknown", source: null };
|
|
4012
4178
|
}
|
|
@@ -4029,13 +4195,13 @@ function generateMcpServerEntry(env) {
|
|
|
4029
4195
|
if (env.mode === "dev-ts" && env.source) {
|
|
4030
4196
|
return {
|
|
4031
4197
|
command: "node",
|
|
4032
|
-
args: [
|
|
4198
|
+
args: [join22(env.source, "dist", "mcp", "index.js")],
|
|
4033
4199
|
env: {}
|
|
4034
4200
|
};
|
|
4035
4201
|
}
|
|
4036
4202
|
return {
|
|
4037
|
-
command: "
|
|
4038
|
-
args: [],
|
|
4203
|
+
command: "npx",
|
|
4204
|
+
args: ["-y", "@cleocode/cleo@latest", "mcp"],
|
|
4039
4205
|
env: {}
|
|
4040
4206
|
};
|
|
4041
4207
|
}
|
|
@@ -4068,17 +4234,17 @@ __export(registry_exports, {
|
|
|
4068
4234
|
readRegistryRequired: () => readRegistryRequired
|
|
4069
4235
|
});
|
|
4070
4236
|
import { createHash as createHash4 } from "node:crypto";
|
|
4071
|
-
import { join as
|
|
4237
|
+
import { join as join23 } from "node:path";
|
|
4072
4238
|
import { mkdir as mkdir4, access, readFile as readFile4 } from "node:fs/promises";
|
|
4073
4239
|
import { z } from "zod";
|
|
4074
4240
|
function getNexusHome() {
|
|
4075
|
-
return process.env["NEXUS_HOME"] ??
|
|
4241
|
+
return process.env["NEXUS_HOME"] ?? join23(getCleoHome(), "nexus");
|
|
4076
4242
|
}
|
|
4077
4243
|
function getNexusCacheDir() {
|
|
4078
|
-
return process.env["NEXUS_CACHE_DIR"] ??
|
|
4244
|
+
return process.env["NEXUS_CACHE_DIR"] ?? join23(getNexusHome(), "cache");
|
|
4079
4245
|
}
|
|
4080
4246
|
function getRegistryPath() {
|
|
4081
|
-
return process.env["NEXUS_REGISTRY_FILE"] ??
|
|
4247
|
+
return process.env["NEXUS_REGISTRY_FILE"] ?? join23(getCleoHome(), "projects-registry.json");
|
|
4082
4248
|
}
|
|
4083
4249
|
function generateProjectHash(projectPath) {
|
|
4084
4250
|
const hash = createHash4("sha256").update(projectPath).digest("hex");
|
|
@@ -4120,7 +4286,7 @@ async function nexusInit() {
|
|
|
4120
4286
|
}
|
|
4121
4287
|
async function isCleoProject(projectPath) {
|
|
4122
4288
|
try {
|
|
4123
|
-
await access(
|
|
4289
|
+
await access(join23(projectPath, ".cleo", "todo.json"));
|
|
4124
4290
|
return true;
|
|
4125
4291
|
} catch {
|
|
4126
4292
|
return false;
|
|
@@ -4130,9 +4296,9 @@ async function readProjectMeta(projectPath) {
|
|
|
4130
4296
|
try {
|
|
4131
4297
|
let raw;
|
|
4132
4298
|
try {
|
|
4133
|
-
raw = await readFile4(
|
|
4299
|
+
raw = await readFile4(join23(projectPath, ".cleo", "tasks.json"), "utf-8");
|
|
4134
4300
|
} catch {
|
|
4135
|
-
raw = await readFile4(
|
|
4301
|
+
raw = await readFile4(join23(projectPath, ".cleo", "todo.json"), "utf-8");
|
|
4136
4302
|
}
|
|
4137
4303
|
const data = JSON.parse(raw);
|
|
4138
4304
|
const tasks2 = data.tasks ?? [];
|
|
@@ -4318,10 +4484,10 @@ var project_detect_exports = {};
|
|
|
4318
4484
|
__export(project_detect_exports, {
|
|
4319
4485
|
detectProjectType: () => detectProjectType
|
|
4320
4486
|
});
|
|
4321
|
-
import { existsSync as
|
|
4322
|
-
import { join as
|
|
4487
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6 } from "node:fs";
|
|
4488
|
+
import { join as join24 } from "node:path";
|
|
4323
4489
|
function detectProjectType(projectDir) {
|
|
4324
|
-
const exists = (f) =>
|
|
4490
|
+
const exists = (f) => existsSync22(join24(projectDir, f));
|
|
4325
4491
|
const info = {
|
|
4326
4492
|
type: "unknown",
|
|
4327
4493
|
testFramework: "unknown",
|
|
@@ -4392,6 +4558,7 @@ __export(validation_schemas_exports, {
|
|
|
4392
4558
|
insertLifecyclePipelineSchema: () => insertLifecyclePipelineSchema,
|
|
4393
4559
|
insertLifecycleStageSchema: () => insertLifecycleStageSchema,
|
|
4394
4560
|
insertLifecycleTransitionSchema: () => insertLifecycleTransitionSchema,
|
|
4561
|
+
insertManifestEntrySchema: () => insertManifestEntrySchema,
|
|
4395
4562
|
insertSchemaMetaSchema: () => insertSchemaMetaSchema,
|
|
4396
4563
|
insertSessionSchema: () => insertSessionSchema,
|
|
4397
4564
|
insertTaskDependencySchema: () => insertTaskDependencySchema,
|
|
@@ -4405,6 +4572,7 @@ __export(validation_schemas_exports, {
|
|
|
4405
4572
|
selectLifecyclePipelineSchema: () => selectLifecyclePipelineSchema,
|
|
4406
4573
|
selectLifecycleStageSchema: () => selectLifecycleStageSchema,
|
|
4407
4574
|
selectLifecycleTransitionSchema: () => selectLifecycleTransitionSchema,
|
|
4575
|
+
selectManifestEntrySchema: () => selectManifestEntrySchema,
|
|
4408
4576
|
selectSchemaMetaSchema: () => selectSchemaMetaSchema,
|
|
4409
4577
|
selectSessionSchema: () => selectSessionSchema,
|
|
4410
4578
|
selectTaskDependencySchema: () => selectTaskDependencySchema,
|
|
@@ -4418,7 +4586,7 @@ __export(validation_schemas_exports, {
|
|
|
4418
4586
|
});
|
|
4419
4587
|
import { createInsertSchema, createSelectSchema } from "drizzle-orm/zod";
|
|
4420
4588
|
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;
|
|
4589
|
+
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
4590
|
var init_validation_schemas = __esm({
|
|
4423
4591
|
"src/store/validation-schemas.ts"() {
|
|
4424
4592
|
"use strict";
|
|
@@ -4511,6 +4679,8 @@ var init_validation_schemas = __esm({
|
|
|
4511
4679
|
AuditLogSelectSchema = selectAuditLogSchema;
|
|
4512
4680
|
insertArchitectureDecisionSchema = createInsertSchema(architectureDecisions);
|
|
4513
4681
|
selectArchitectureDecisionSchema = createSelectSchema(architectureDecisions);
|
|
4682
|
+
insertManifestEntrySchema = createInsertSchema(manifestEntries);
|
|
4683
|
+
selectManifestEntrySchema = createSelectSchema(manifestEntries);
|
|
4514
4684
|
}
|
|
4515
4685
|
});
|
|
4516
4686
|
|
|
@@ -4684,8 +4854,8 @@ __export(session_grade_exports, {
|
|
|
4684
4854
|
gradeSession: () => gradeSession,
|
|
4685
4855
|
readGrades: () => readGrades
|
|
4686
4856
|
});
|
|
4687
|
-
import { join as
|
|
4688
|
-
import { existsSync as
|
|
4857
|
+
import { join as join41 } from "node:path";
|
|
4858
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
4689
4859
|
import { readFile as readFile6, appendFile, mkdir as mkdir6 } from "node:fs/promises";
|
|
4690
4860
|
async function gradeSession(sessionId, cwd) {
|
|
4691
4861
|
const sessionEntries = await queryAudit({ sessionId });
|
|
@@ -4865,9 +5035,9 @@ function detectDuplicateCreates(entries) {
|
|
|
4865
5035
|
async function appendGradeResult(result, cwd) {
|
|
4866
5036
|
try {
|
|
4867
5037
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
4868
|
-
const metricsDir =
|
|
5038
|
+
const metricsDir = join41(cleoDir, "metrics");
|
|
4869
5039
|
await mkdir6(metricsDir, { recursive: true });
|
|
4870
|
-
const gradesPath =
|
|
5040
|
+
const gradesPath = join41(metricsDir, "GRADES.jsonl");
|
|
4871
5041
|
const line = JSON.stringify({ ...result, evaluator: "auto" }) + "\n";
|
|
4872
5042
|
await appendFile(gradesPath, line, "utf8");
|
|
4873
5043
|
} catch {
|
|
@@ -4876,8 +5046,8 @@ async function appendGradeResult(result, cwd) {
|
|
|
4876
5046
|
async function readGrades(sessionId, cwd) {
|
|
4877
5047
|
try {
|
|
4878
5048
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
4879
|
-
const gradesPath =
|
|
4880
|
-
if (!
|
|
5049
|
+
const gradesPath = join41(cleoDir, "metrics", "GRADES.jsonl");
|
|
5050
|
+
if (!existsSync40(gradesPath)) return [];
|
|
4881
5051
|
const content = await readFile6(gradesPath, "utf8");
|
|
4882
5052
|
const results = content.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
4883
5053
|
return sessionId ? results.filter((r) => r.sessionId === sessionId) : results;
|
|
@@ -5241,7 +5411,7 @@ if (actualQueryCount < 1) {
|
|
|
5241
5411
|
function registerQueryTool() {
|
|
5242
5412
|
return {
|
|
5243
5413
|
name: "cleo_query",
|
|
5244
|
-
description:
|
|
5414
|
+
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
5415
|
inputSchema: {
|
|
5246
5416
|
type: "object",
|
|
5247
5417
|
required: ["domain", "operation"],
|
|
@@ -5253,7 +5423,7 @@ function registerQueryTool() {
|
|
|
5253
5423
|
},
|
|
5254
5424
|
operation: {
|
|
5255
5425
|
type: "string",
|
|
5256
|
-
description: "Domain-specific read operation
|
|
5426
|
+
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
5427
|
},
|
|
5258
5428
|
params: {
|
|
5259
5429
|
type: "object",
|
|
@@ -5563,7 +5733,7 @@ if (actualMutateCount < 1) {
|
|
|
5563
5733
|
function registerMutateTool() {
|
|
5564
5734
|
return {
|
|
5565
5735
|
name: "cleo_mutate",
|
|
5566
|
-
description:
|
|
5736
|
+
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
5737
|
inputSchema: {
|
|
5568
5738
|
type: "object",
|
|
5569
5739
|
required: ["domain", "operation"],
|
|
@@ -5575,7 +5745,7 @@ function registerMutateTool() {
|
|
|
5575
5745
|
},
|
|
5576
5746
|
operation: {
|
|
5577
5747
|
type: "string",
|
|
5578
|
-
description: "Domain-specific write operation
|
|
5748
|
+
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
5749
|
},
|
|
5580
5750
|
params: {
|
|
5581
5751
|
type: "object",
|
|
@@ -7813,7 +7983,6 @@ init_data_accessor();
|
|
|
7813
7983
|
|
|
7814
7984
|
// src/dispatch/engines/_error.ts
|
|
7815
7985
|
init_logger();
|
|
7816
|
-
var logger = getLogger("engine");
|
|
7817
7986
|
var STRING_TO_EXIT = {
|
|
7818
7987
|
// General Errors (1-9)
|
|
7819
7988
|
E_GENERAL: 1,
|
|
@@ -7942,6 +8111,7 @@ function logLevel(exitCode) {
|
|
|
7942
8111
|
function engineError(code, message, options) {
|
|
7943
8112
|
const exitCode = STRING_TO_EXIT[code] ?? 1;
|
|
7944
8113
|
const level = logLevel(exitCode);
|
|
8114
|
+
const logger = getLogger("engine");
|
|
7945
8115
|
logger[level]({ code, exitCode, ...options?.details && { details: options.details } }, message);
|
|
7946
8116
|
return {
|
|
7947
8117
|
success: false,
|
|
@@ -8564,6 +8734,7 @@ function validateHierarchyPlacement(parentId, tasks2, policy) {
|
|
|
8564
8734
|
}
|
|
8565
8735
|
|
|
8566
8736
|
// src/core/tasks/add.ts
|
|
8737
|
+
init_sequence();
|
|
8567
8738
|
function validateTitle(title) {
|
|
8568
8739
|
if (!title || title.trim().length === 0) {
|
|
8569
8740
|
throw new CleoError(2 /* INVALID_INPUT */, "Task title is required");
|
|
@@ -8746,20 +8917,23 @@ async function addTask(options, cwd, accessor) {
|
|
|
8746
8917
|
validateTitle(options.title);
|
|
8747
8918
|
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(taskPath);
|
|
8748
8919
|
let archivedTasks = [];
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
if (
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8920
|
+
const useSqliteAlloc = accessor?.engine === "sqlite" || !accessor && (await Promise.resolve().then(() => (init_sqlite(), sqlite_exports))).getNativeDb() !== null;
|
|
8921
|
+
if (!useSqliteAlloc) {
|
|
8922
|
+
try {
|
|
8923
|
+
if (accessor) {
|
|
8924
|
+
const archive = await accessor.loadArchive();
|
|
8925
|
+
if (archive?.archivedTasks) {
|
|
8926
|
+
archivedTasks = archive.archivedTasks;
|
|
8927
|
+
}
|
|
8928
|
+
} else {
|
|
8929
|
+
const { readJson: readJson2 } = await Promise.resolve().then(() => (init_json(), json_exports));
|
|
8930
|
+
const archive = await readJson2(archivePath);
|
|
8931
|
+
if (archive?.archivedTasks) {
|
|
8932
|
+
archivedTasks = archive.archivedTasks;
|
|
8933
|
+
}
|
|
8760
8934
|
}
|
|
8935
|
+
} catch {
|
|
8761
8936
|
}
|
|
8762
|
-
} catch {
|
|
8763
8937
|
}
|
|
8764
8938
|
const status = options.status ?? "pending";
|
|
8765
8939
|
const priority = normalizePriority(options.priority ?? "medium");
|
|
@@ -8837,9 +9011,14 @@ async function addTask(options, cwd, accessor) {
|
|
|
8837
9011
|
if (duplicate) {
|
|
8838
9012
|
return { task: duplicate, duplicate: true };
|
|
8839
9013
|
}
|
|
8840
|
-
|
|
8841
|
-
if (
|
|
8842
|
-
|
|
9014
|
+
let taskId;
|
|
9015
|
+
if (useSqliteAlloc) {
|
|
9016
|
+
taskId = await allocateNextTaskId(cwd);
|
|
9017
|
+
} else {
|
|
9018
|
+
taskId = generateTaskId(data.tasks, archivedTasks);
|
|
9019
|
+
if (data.tasks.some((t) => t.id === taskId)) {
|
|
9020
|
+
throw new CleoError(22 /* ID_COLLISION */, `Generated ID ${taskId} already exists`);
|
|
9021
|
+
}
|
|
8843
9022
|
}
|
|
8844
9023
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8845
9024
|
const position = options.position ?? getNextPosition(parentId, data.tasks);
|
|
@@ -9193,6 +9372,291 @@ async function updateTask2(options, cwd, accessor) {
|
|
|
9193
9372
|
return { task, changes };
|
|
9194
9373
|
}
|
|
9195
9374
|
|
|
9375
|
+
// src/core/tasks/complete.ts
|
|
9376
|
+
init_json();
|
|
9377
|
+
init_errors();
|
|
9378
|
+
init_exit_codes();
|
|
9379
|
+
init_paths();
|
|
9380
|
+
|
|
9381
|
+
// src/core/tasks/dependency-check.ts
|
|
9382
|
+
function detectCircularDeps(taskId, tasks2) {
|
|
9383
|
+
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
9384
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9385
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
9386
|
+
const path = [];
|
|
9387
|
+
function dfs(id) {
|
|
9388
|
+
visited.add(id);
|
|
9389
|
+
recursionStack.add(id);
|
|
9390
|
+
path.push(id);
|
|
9391
|
+
const task = taskMap.get(id);
|
|
9392
|
+
if (task?.depends) {
|
|
9393
|
+
for (const depId of task.depends) {
|
|
9394
|
+
if (!visited.has(depId)) {
|
|
9395
|
+
const cycle = dfs(depId);
|
|
9396
|
+
if (cycle.length > 0) return cycle;
|
|
9397
|
+
} else if (recursionStack.has(depId)) {
|
|
9398
|
+
const cycleStart = path.indexOf(depId);
|
|
9399
|
+
return [...path.slice(cycleStart), depId];
|
|
9400
|
+
}
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9403
|
+
path.pop();
|
|
9404
|
+
recursionStack.delete(id);
|
|
9405
|
+
return [];
|
|
9406
|
+
}
|
|
9407
|
+
return dfs(taskId);
|
|
9408
|
+
}
|
|
9409
|
+
function getBlockedTasks(tasks2) {
|
|
9410
|
+
const completedIds = new Set(
|
|
9411
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9412
|
+
);
|
|
9413
|
+
return tasks2.filter((t) => {
|
|
9414
|
+
if (!t.depends?.length) return false;
|
|
9415
|
+
if (t.status === "done" || t.status === "cancelled") return false;
|
|
9416
|
+
return t.depends.some((depId) => !completedIds.has(depId));
|
|
9417
|
+
});
|
|
9418
|
+
}
|
|
9419
|
+
function getReadyTasks(tasks2) {
|
|
9420
|
+
const completedIds = new Set(
|
|
9421
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9422
|
+
);
|
|
9423
|
+
return tasks2.filter((t) => {
|
|
9424
|
+
if (t.status === "done" || t.status === "cancelled") return false;
|
|
9425
|
+
if (!t.depends?.length) return true;
|
|
9426
|
+
return t.depends.every((depId) => completedIds.has(depId));
|
|
9427
|
+
});
|
|
9428
|
+
}
|
|
9429
|
+
function getDependents(taskId, tasks2) {
|
|
9430
|
+
return tasks2.filter((t) => t.depends?.includes(taskId));
|
|
9431
|
+
}
|
|
9432
|
+
function getUnresolvedDeps(taskId, tasks2) {
|
|
9433
|
+
const task = tasks2.find((t) => t.id === taskId);
|
|
9434
|
+
if (!task?.depends?.length) return [];
|
|
9435
|
+
const completedIds = new Set(
|
|
9436
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9437
|
+
);
|
|
9438
|
+
return task.depends.filter((depId) => !completedIds.has(depId));
|
|
9439
|
+
}
|
|
9440
|
+
function validateDependencyRefs(tasks2) {
|
|
9441
|
+
const taskIds = new Set(tasks2.map((t) => t.id));
|
|
9442
|
+
const errors = [];
|
|
9443
|
+
for (const task of tasks2) {
|
|
9444
|
+
if (!task.depends?.length) continue;
|
|
9445
|
+
for (const depId of task.depends) {
|
|
9446
|
+
if (!taskIds.has(depId)) {
|
|
9447
|
+
errors.push({
|
|
9448
|
+
code: "E_DEP_NOT_FOUND",
|
|
9449
|
+
taskId: task.id,
|
|
9450
|
+
message: `Task ${task.id} depends on ${depId}, which does not exist`,
|
|
9451
|
+
relatedIds: [depId]
|
|
9452
|
+
});
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
return errors;
|
|
9457
|
+
}
|
|
9458
|
+
function validateDependencies(tasks2) {
|
|
9459
|
+
const errors = [];
|
|
9460
|
+
const warnings = [];
|
|
9461
|
+
errors.push(...validateDependencyRefs(tasks2));
|
|
9462
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9463
|
+
for (const task of tasks2) {
|
|
9464
|
+
if (visited.has(task.id)) continue;
|
|
9465
|
+
if (!task.depends?.length) continue;
|
|
9466
|
+
const cycle = detectCircularDeps(task.id, tasks2);
|
|
9467
|
+
if (cycle.length > 0) {
|
|
9468
|
+
errors.push({
|
|
9469
|
+
code: "E_CIRCULAR_DEP",
|
|
9470
|
+
taskId: task.id,
|
|
9471
|
+
message: `Circular dependency detected: ${cycle.join(" -> ")}`,
|
|
9472
|
+
relatedIds: cycle
|
|
9473
|
+
});
|
|
9474
|
+
cycle.forEach((id) => visited.add(id));
|
|
9475
|
+
}
|
|
9476
|
+
}
|
|
9477
|
+
for (const task of tasks2) {
|
|
9478
|
+
if (task.depends?.includes(task.id)) {
|
|
9479
|
+
errors.push({
|
|
9480
|
+
code: "E_SELF_DEP",
|
|
9481
|
+
taskId: task.id,
|
|
9482
|
+
message: `Task ${task.id} depends on itself`
|
|
9483
|
+
});
|
|
9484
|
+
}
|
|
9485
|
+
}
|
|
9486
|
+
for (const task of tasks2) {
|
|
9487
|
+
if (task.status === "done" && task.depends?.length) {
|
|
9488
|
+
const unresolved = getUnresolvedDeps(task.id, tasks2);
|
|
9489
|
+
if (unresolved.length > 0) {
|
|
9490
|
+
warnings.push({
|
|
9491
|
+
code: "W_COMPLETED_WITH_UNMET_DEPS",
|
|
9492
|
+
taskId: task.id,
|
|
9493
|
+
message: `Completed task ${task.id} has unmet dependencies: ${unresolved.join(", ")}`
|
|
9494
|
+
});
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
return {
|
|
9499
|
+
valid: errors.length === 0,
|
|
9500
|
+
errors,
|
|
9501
|
+
warnings
|
|
9502
|
+
};
|
|
9503
|
+
}
|
|
9504
|
+
function getTransitiveBlockers(taskId, tasks2) {
|
|
9505
|
+
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
9506
|
+
const task = taskMap.get(taskId);
|
|
9507
|
+
if (!task?.depends?.length) return [];
|
|
9508
|
+
const blockers = /* @__PURE__ */ new Set();
|
|
9509
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9510
|
+
function walk(id) {
|
|
9511
|
+
if (visited.has(id)) return;
|
|
9512
|
+
visited.add(id);
|
|
9513
|
+
const t = taskMap.get(id);
|
|
9514
|
+
if (!t?.depends?.length) return;
|
|
9515
|
+
for (const depId of t.depends) {
|
|
9516
|
+
const dep = taskMap.get(depId);
|
|
9517
|
+
if (!dep) continue;
|
|
9518
|
+
if (dep.status === "done" || dep.status === "cancelled") continue;
|
|
9519
|
+
blockers.add(depId);
|
|
9520
|
+
walk(depId);
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
walk(taskId);
|
|
9524
|
+
return [...blockers];
|
|
9525
|
+
}
|
|
9526
|
+
function getLeafBlockers(taskId, tasks2) {
|
|
9527
|
+
const blockerIds = getTransitiveBlockers(taskId, tasks2);
|
|
9528
|
+
if (blockerIds.length === 0) return [];
|
|
9529
|
+
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
9530
|
+
const completedIds = new Set(
|
|
9531
|
+
tasks2.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
|
|
9532
|
+
);
|
|
9533
|
+
return blockerIds.filter((id) => {
|
|
9534
|
+
const t = taskMap.get(id);
|
|
9535
|
+
if (!t?.depends?.length) return true;
|
|
9536
|
+
return t.depends.every((depId) => completedIds.has(depId));
|
|
9537
|
+
});
|
|
9538
|
+
}
|
|
9539
|
+
|
|
9540
|
+
// src/core/tasks/complete.ts
|
|
9541
|
+
init_data_safety_central();
|
|
9542
|
+
async function completeTask(options, cwd, accessor) {
|
|
9543
|
+
const taskPath = getTaskPath(cwd);
|
|
9544
|
+
const logPath = getLogPath(cwd);
|
|
9545
|
+
const backupDir = getBackupDir(cwd);
|
|
9546
|
+
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(taskPath);
|
|
9547
|
+
const taskIdx = data.tasks.findIndex((t) => t.id === options.taskId);
|
|
9548
|
+
if (taskIdx === -1) {
|
|
9549
|
+
throw new CleoError(
|
|
9550
|
+
4 /* NOT_FOUND */,
|
|
9551
|
+
`Task not found: ${options.taskId}`,
|
|
9552
|
+
{ fix: `Use 'cleo find "${options.taskId}"' to search` }
|
|
9553
|
+
);
|
|
9554
|
+
}
|
|
9555
|
+
const task = data.tasks[taskIdx];
|
|
9556
|
+
if (task.status === "done") {
|
|
9557
|
+
throw new CleoError(
|
|
9558
|
+
17 /* TASK_COMPLETED */,
|
|
9559
|
+
`Task ${options.taskId} is already completed`
|
|
9560
|
+
);
|
|
9561
|
+
}
|
|
9562
|
+
if (task.depends?.length) {
|
|
9563
|
+
const incompleteDeps = task.depends.filter((depId) => {
|
|
9564
|
+
const dep = data.tasks.find((t) => t.id === depId);
|
|
9565
|
+
return dep && dep.status !== "done";
|
|
9566
|
+
});
|
|
9567
|
+
if (incompleteDeps.length > 0) {
|
|
9568
|
+
throw new CleoError(
|
|
9569
|
+
5 /* DEPENDENCY_ERROR */,
|
|
9570
|
+
`Task ${options.taskId} has incomplete dependencies: ${incompleteDeps.join(", ")}`,
|
|
9571
|
+
{ fix: `Complete dependencies first: ${incompleteDeps.map((d) => `cleo complete ${d}`).join(", ")}` }
|
|
9572
|
+
);
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9575
|
+
const children = data.tasks.filter((t) => t.parentId === options.taskId);
|
|
9576
|
+
const incompleteChildren = children.filter((c) => c.status !== "done" && c.status !== "cancelled");
|
|
9577
|
+
if (incompleteChildren.length > 0 && task.type === "epic") {
|
|
9578
|
+
if (!task.noAutoComplete) {
|
|
9579
|
+
throw new CleoError(
|
|
9580
|
+
16 /* HAS_CHILDREN */,
|
|
9581
|
+
`Epic ${options.taskId} has ${incompleteChildren.length} incomplete children: ${incompleteChildren.map((c) => c.id).join(", ")}`,
|
|
9582
|
+
{ fix: `Complete children first or use 'cleo update ${options.taskId} --no-auto-complete'` }
|
|
9583
|
+
);
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9587
|
+
const before = { ...task };
|
|
9588
|
+
task.status = "done";
|
|
9589
|
+
task.completedAt = now;
|
|
9590
|
+
task.updatedAt = now;
|
|
9591
|
+
if (options.notes) {
|
|
9592
|
+
const timestampedNote = `${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}: ${options.notes}`;
|
|
9593
|
+
if (!task.notes) task.notes = [];
|
|
9594
|
+
task.notes.push(timestampedNote);
|
|
9595
|
+
}
|
|
9596
|
+
if (options.changeset) {
|
|
9597
|
+
if (!task.notes) task.notes = [];
|
|
9598
|
+
task.notes.push(`Changeset: ${options.changeset}`);
|
|
9599
|
+
}
|
|
9600
|
+
data.tasks[taskIdx] = task;
|
|
9601
|
+
const autoCompleted = [];
|
|
9602
|
+
if (task.parentId) {
|
|
9603
|
+
const parent = data.tasks.find((t) => t.id === task.parentId);
|
|
9604
|
+
if (parent && parent.type === "epic" && !parent.noAutoComplete) {
|
|
9605
|
+
const parentChildren = data.tasks.filter((t) => t.parentId === parent.id);
|
|
9606
|
+
const allDone = parentChildren.every((c) => c.status === "done" || c.status === "cancelled");
|
|
9607
|
+
if (allDone) {
|
|
9608
|
+
parent.status = "done";
|
|
9609
|
+
parent.completedAt = now;
|
|
9610
|
+
parent.updatedAt = now;
|
|
9611
|
+
autoCompleted.push(parent.id);
|
|
9612
|
+
}
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
9615
|
+
data._meta.checksum = computeChecksum(data.tasks);
|
|
9616
|
+
data.lastUpdated = now;
|
|
9617
|
+
if (accessor) {
|
|
9618
|
+
if (accessor.upsertSingleTask) {
|
|
9619
|
+
await accessor.upsertSingleTask(task);
|
|
9620
|
+
for (const parentId of autoCompleted) {
|
|
9621
|
+
const parent = data.tasks.find((t) => t.id === parentId);
|
|
9622
|
+
if (parent) await accessor.upsertSingleTask(parent);
|
|
9623
|
+
}
|
|
9624
|
+
} else {
|
|
9625
|
+
await safeSaveTaskFile(accessor, data, cwd);
|
|
9626
|
+
}
|
|
9627
|
+
await safeAppendLog(accessor, {
|
|
9628
|
+
id: `log-${Math.floor(Date.now() / 1e3)}-${(await import("node:crypto")).randomBytes(3).toString("hex")}`,
|
|
9629
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9630
|
+
action: "task_completed",
|
|
9631
|
+
taskId: options.taskId,
|
|
9632
|
+
actor: "system",
|
|
9633
|
+
details: { title: task.title, previousStatus: before.status },
|
|
9634
|
+
before: null,
|
|
9635
|
+
after: { title: task.title, previousStatus: before.status }
|
|
9636
|
+
}, cwd);
|
|
9637
|
+
} else {
|
|
9638
|
+
await saveJson(taskPath, data, { backupDir });
|
|
9639
|
+
await logOperation(logPath, "task_completed", options.taskId, {
|
|
9640
|
+
title: task.title,
|
|
9641
|
+
previousStatus: before.status
|
|
9642
|
+
});
|
|
9643
|
+
}
|
|
9644
|
+
const dependents = getDependents(options.taskId, data.tasks);
|
|
9645
|
+
const unblockedTasks = [];
|
|
9646
|
+
for (const dep of dependents) {
|
|
9647
|
+
if (dep.status === "done" || dep.status === "cancelled") continue;
|
|
9648
|
+
const stillUnresolved = getUnresolvedDeps(dep.id, data.tasks);
|
|
9649
|
+
if (stillUnresolved.length === 0) {
|
|
9650
|
+
unblockedTasks.push({ id: dep.id, title: dep.title });
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
return {
|
|
9654
|
+
task,
|
|
9655
|
+
...autoCompleted.length > 0 && { autoCompleted },
|
|
9656
|
+
...unblockedTasks.length > 0 && { unblockedTasks }
|
|
9657
|
+
};
|
|
9658
|
+
}
|
|
9659
|
+
|
|
9196
9660
|
// src/core/tasks/delete.ts
|
|
9197
9661
|
init_json();
|
|
9198
9662
|
init_errors();
|
|
@@ -9498,6 +9962,16 @@ async function showTask(taskId, cwd, accessor) {
|
|
|
9498
9962
|
title: dep?.title ?? "Unknown task"
|
|
9499
9963
|
};
|
|
9500
9964
|
});
|
|
9965
|
+
const unresolved = detail.dependencyStatus.filter(
|
|
9966
|
+
(d) => d.status !== "done" && d.status !== "cancelled"
|
|
9967
|
+
);
|
|
9968
|
+
if (unresolved.length > 0) {
|
|
9969
|
+
detail.unresolvedDeps = unresolved;
|
|
9970
|
+
}
|
|
9971
|
+
}
|
|
9972
|
+
const dependents = data.tasks.filter((t) => t.depends?.includes(taskId)).map((t) => t.id);
|
|
9973
|
+
if (dependents.length > 0) {
|
|
9974
|
+
detail.dependents = dependents;
|
|
9501
9975
|
}
|
|
9502
9976
|
const path = [taskId];
|
|
9503
9977
|
let currentId = task.parentId;
|
|
@@ -9518,6 +9992,16 @@ async function showTask(taskId, cwd, accessor) {
|
|
|
9518
9992
|
// src/core/tasks/list.ts
|
|
9519
9993
|
init_json();
|
|
9520
9994
|
init_paths();
|
|
9995
|
+
function toCompact(task) {
|
|
9996
|
+
return {
|
|
9997
|
+
id: task.id,
|
|
9998
|
+
title: task.title,
|
|
9999
|
+
status: task.status,
|
|
10000
|
+
priority: task.priority,
|
|
10001
|
+
type: task.type,
|
|
10002
|
+
parentId: task.parentId
|
|
10003
|
+
};
|
|
10004
|
+
}
|
|
9521
10005
|
async function listTasks(options = {}, cwd, accessor) {
|
|
9522
10006
|
const taskPath = getTaskPath(cwd);
|
|
9523
10007
|
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(taskPath);
|
|
@@ -9694,34 +10178,19 @@ async function findTasks(options, cwd, accessor) {
|
|
|
9694
10178
|
|
|
9695
10179
|
// src/core/tasks/task-ops.ts
|
|
9696
10180
|
init_data_accessor();
|
|
9697
|
-
|
|
10181
|
+
init_file_utils();
|
|
9698
10182
|
init_status_registry();
|
|
9699
10183
|
init_deps_ready();
|
|
9700
10184
|
var PRIORITY_SCORE = {
|
|
9701
10185
|
critical: 100,
|
|
9702
10186
|
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;
|
|
10187
|
+
medium: 50,
|
|
10188
|
+
low: 25
|
|
10189
|
+
};
|
|
10190
|
+
async function loadAllTasks2(projectRoot) {
|
|
10191
|
+
const accessor = await getAccessor(projectRoot);
|
|
10192
|
+
const data = await accessor.loadTaskFile();
|
|
10193
|
+
return data.tasks;
|
|
9725
10194
|
}
|
|
9726
10195
|
function buildTreeNode(task, childrenMap) {
|
|
9727
10196
|
const children = (childrenMap.get(task.id) ?? []).map(
|
|
@@ -9735,6 +10204,25 @@ function buildTreeNode(task, childrenMap) {
|
|
|
9735
10204
|
children
|
|
9736
10205
|
};
|
|
9737
10206
|
}
|
|
10207
|
+
function buildUpstreamTree(taskId, taskMap, visited = /* @__PURE__ */ new Set()) {
|
|
10208
|
+
const task = taskMap.get(taskId);
|
|
10209
|
+
if (!task?.depends?.length) return [];
|
|
10210
|
+
const nodes = [];
|
|
10211
|
+
for (const depId of task.depends) {
|
|
10212
|
+
if (visited.has(depId)) continue;
|
|
10213
|
+
visited.add(depId);
|
|
10214
|
+
const dep = taskMap.get(depId);
|
|
10215
|
+
if (!dep) continue;
|
|
10216
|
+
nodes.push({
|
|
10217
|
+
id: dep.id,
|
|
10218
|
+
title: dep.title,
|
|
10219
|
+
status: dep.status,
|
|
10220
|
+
type: dep.type,
|
|
10221
|
+
children: buildUpstreamTree(depId, taskMap, visited)
|
|
10222
|
+
});
|
|
10223
|
+
}
|
|
10224
|
+
return nodes;
|
|
10225
|
+
}
|
|
9738
10226
|
function countNodes(nodes) {
|
|
9739
10227
|
let count2 = nodes.length;
|
|
9740
10228
|
for (const node of nodes) {
|
|
@@ -9827,20 +10315,21 @@ async function coreTaskBlockers(projectRoot, params) {
|
|
|
9827
10315
|
return dep && dep.status !== "done" && dep.status !== "cancelled";
|
|
9828
10316
|
})
|
|
9829
10317
|
);
|
|
10318
|
+
const tasksAsTask = allTasks;
|
|
9830
10319
|
const blockerInfos = [
|
|
9831
10320
|
...blockedTasks.map((t) => ({
|
|
9832
10321
|
id: t.id,
|
|
9833
10322
|
title: t.title,
|
|
9834
10323
|
status: t.status,
|
|
9835
10324
|
depends: t.depends,
|
|
9836
|
-
blockingChain: analyze ?
|
|
10325
|
+
blockingChain: analyze ? getTransitiveBlockers(t.id, tasksAsTask) : []
|
|
9837
10326
|
})),
|
|
9838
10327
|
...depBlockedTasks.filter((t) => !blockedTasks.some((bt) => bt.id === t.id)).map((t) => ({
|
|
9839
10328
|
id: t.id,
|
|
9840
10329
|
title: t.title,
|
|
9841
10330
|
status: t.status,
|
|
9842
10331
|
depends: t.depends,
|
|
9843
|
-
blockingChain: analyze ?
|
|
10332
|
+
blockingChain: analyze ? getTransitiveBlockers(t.id, tasksAsTask) : []
|
|
9844
10333
|
}))
|
|
9845
10334
|
];
|
|
9846
10335
|
const blockerCounts = /* @__PURE__ */ new Map();
|
|
@@ -9922,7 +10411,10 @@ async function coreTaskRelatesAdd(projectRoot, taskId, relatedId, type, reason)
|
|
|
9922
10411
|
} else {
|
|
9923
10412
|
await accessor.saveTaskFile(current);
|
|
9924
10413
|
}
|
|
9925
|
-
|
|
10414
|
+
if (accessor.addRelation) {
|
|
10415
|
+
await accessor.addRelation(taskId, relatedId, type, reason);
|
|
10416
|
+
}
|
|
10417
|
+
return { from: taskId, to: relatedId, type, reason, added: true };
|
|
9926
10418
|
}
|
|
9927
10419
|
async function coreTaskAnalyze(projectRoot, taskId) {
|
|
9928
10420
|
const allTasks = await loadAllTasks2(projectRoot);
|
|
@@ -10242,7 +10734,63 @@ async function coreTaskComplexityEstimate(projectRoot, params) {
|
|
|
10242
10734
|
else size = "large";
|
|
10243
10735
|
return { size, score, factors, dependencyDepth, subtaskCount, fileCount };
|
|
10244
10736
|
}
|
|
10245
|
-
async function
|
|
10737
|
+
async function coreTaskDepsOverview(projectRoot) {
|
|
10738
|
+
const allTasks = await loadAllTasks2(projectRoot);
|
|
10739
|
+
const tasksAsTask = allTasks;
|
|
10740
|
+
const tasksWithDeps = allTasks.filter((t) => t.depends && t.depends.length > 0);
|
|
10741
|
+
const blocked = getBlockedTasks(tasksAsTask);
|
|
10742
|
+
const ready = getReadyTasks(tasksAsTask);
|
|
10743
|
+
const validation = validateDependencies(tasksAsTask);
|
|
10744
|
+
return {
|
|
10745
|
+
totalTasks: allTasks.length,
|
|
10746
|
+
tasksWithDeps: tasksWithDeps.length,
|
|
10747
|
+
blockedTasks: blocked.map((t) => ({
|
|
10748
|
+
id: t.id,
|
|
10749
|
+
title: t.title,
|
|
10750
|
+
status: t.status,
|
|
10751
|
+
unblockedBy: (t.depends ?? []).filter((depId) => {
|
|
10752
|
+
const dep = allTasks.find((x) => x.id === depId);
|
|
10753
|
+
return dep && dep.status !== "done" && dep.status !== "cancelled";
|
|
10754
|
+
})
|
|
10755
|
+
})),
|
|
10756
|
+
readyTasks: ready.filter((t) => t.status !== "done" && t.status !== "cancelled").map((t) => ({
|
|
10757
|
+
id: t.id,
|
|
10758
|
+
title: t.title,
|
|
10759
|
+
status: t.status
|
|
10760
|
+
})),
|
|
10761
|
+
validation: {
|
|
10762
|
+
valid: validation.valid,
|
|
10763
|
+
errorCount: validation.errors.length,
|
|
10764
|
+
warningCount: validation.warnings.length
|
|
10765
|
+
}
|
|
10766
|
+
};
|
|
10767
|
+
}
|
|
10768
|
+
async function coreTaskDepsCycles(projectRoot) {
|
|
10769
|
+
const allTasks = await loadAllTasks2(projectRoot);
|
|
10770
|
+
const tasksAsTask = allTasks;
|
|
10771
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
10772
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10773
|
+
const cycles = [];
|
|
10774
|
+
for (const task of allTasks) {
|
|
10775
|
+
if (visited.has(task.id)) continue;
|
|
10776
|
+
if (!task.depends?.length) continue;
|
|
10777
|
+
const cycle = detectCircularDeps(task.id, tasksAsTask);
|
|
10778
|
+
if (cycle.length > 0) {
|
|
10779
|
+
cycles.push({
|
|
10780
|
+
path: cycle,
|
|
10781
|
+
// Deduplicate: detectCircularDeps returns [A,B,C,A] where
|
|
10782
|
+
// last element closes the cycle. Use Set for robustness.
|
|
10783
|
+
tasks: [...new Set(cycle)].map((id) => {
|
|
10784
|
+
const t = taskMap.get(id);
|
|
10785
|
+
return { id, title: t?.title ?? "unknown" };
|
|
10786
|
+
})
|
|
10787
|
+
});
|
|
10788
|
+
cycle.forEach((id) => visited.add(id));
|
|
10789
|
+
}
|
|
10790
|
+
}
|
|
10791
|
+
return { hasCycles: cycles.length > 0, cycles };
|
|
10792
|
+
}
|
|
10793
|
+
async function coreTaskDepends(projectRoot, taskId, direction = "both", options) {
|
|
10246
10794
|
const allTasks = await loadAllTasks2(projectRoot);
|
|
10247
10795
|
const task = allTasks.find((t) => t.id === taskId);
|
|
10248
10796
|
if (!task) {
|
|
@@ -10266,7 +10814,31 @@ async function coreTaskDepends(projectRoot, taskId, direction = "both") {
|
|
|
10266
10814
|
}
|
|
10267
10815
|
}
|
|
10268
10816
|
}
|
|
10269
|
-
|
|
10817
|
+
const tasksAsTask = allTasks;
|
|
10818
|
+
const transitiveIds = getTransitiveBlockers(taskId, tasksAsTask);
|
|
10819
|
+
const unresolvedChain = transitiveIds.length;
|
|
10820
|
+
const leafIds = getLeafBlockers(taskId, tasksAsTask);
|
|
10821
|
+
const leafBlockers = leafIds.map((id) => {
|
|
10822
|
+
const t = taskMap.get(id);
|
|
10823
|
+
return { id: t.id, title: t.title, status: t.status };
|
|
10824
|
+
});
|
|
10825
|
+
const allDepsReady = unresolvedChain === 0;
|
|
10826
|
+
const hint = unresolvedChain > 0 ? `Run 'ct deps show ${taskId} --tree' for full dependency graph` : void 0;
|
|
10827
|
+
let upstreamTree;
|
|
10828
|
+
if (options?.tree) {
|
|
10829
|
+
upstreamTree = buildUpstreamTree(taskId, taskMap);
|
|
10830
|
+
}
|
|
10831
|
+
return {
|
|
10832
|
+
taskId,
|
|
10833
|
+
direction,
|
|
10834
|
+
upstream,
|
|
10835
|
+
downstream,
|
|
10836
|
+
unresolvedChain,
|
|
10837
|
+
leafBlockers,
|
|
10838
|
+
allDepsReady,
|
|
10839
|
+
...hint && { hint },
|
|
10840
|
+
...upstreamTree && { upstreamTree }
|
|
10841
|
+
};
|
|
10270
10842
|
}
|
|
10271
10843
|
|
|
10272
10844
|
// src/dispatch/engines/task-engine.ts
|
|
@@ -10300,17 +10872,25 @@ async function taskList(projectRoot, params) {
|
|
|
10300
10872
|
status: params?.status,
|
|
10301
10873
|
limit: params?.limit
|
|
10302
10874
|
}, projectRoot, accessor);
|
|
10875
|
+
if (params?.compact) {
|
|
10876
|
+
return { success: true, data: { tasks: result.tasks.map((t) => toCompact(t)), total: result.total } };
|
|
10877
|
+
}
|
|
10303
10878
|
return { success: true, data: { tasks: tasksToRecords(result.tasks), total: result.total } };
|
|
10304
10879
|
} catch {
|
|
10305
10880
|
return engineError("E_NOT_INITIALIZED", "Task database not initialized");
|
|
10306
10881
|
}
|
|
10307
10882
|
}
|
|
10308
|
-
async function taskFind(projectRoot, query, limit) {
|
|
10883
|
+
async function taskFind(projectRoot, query, limit, options) {
|
|
10309
10884
|
try {
|
|
10310
10885
|
const accessor = await getAccessor(projectRoot);
|
|
10311
10886
|
const findResult = await findTasks({
|
|
10312
10887
|
query,
|
|
10313
|
-
|
|
10888
|
+
id: options?.id,
|
|
10889
|
+
exact: options?.exact,
|
|
10890
|
+
status: options?.status,
|
|
10891
|
+
includeArchive: options?.includeArchive,
|
|
10892
|
+
limit: limit ?? 20,
|
|
10893
|
+
offset: options?.offset
|
|
10314
10894
|
}, projectRoot, accessor);
|
|
10315
10895
|
const results = findResult.results.map((r) => ({
|
|
10316
10896
|
id: r.id,
|
|
@@ -10413,14 +10993,33 @@ async function taskUpdate(projectRoot, taskId, updates) {
|
|
|
10413
10993
|
}
|
|
10414
10994
|
}
|
|
10415
10995
|
async function taskComplete(projectRoot, taskId, notes) {
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10996
|
+
try {
|
|
10997
|
+
const accessor = await getAccessor(projectRoot);
|
|
10998
|
+
const result = await completeTask({ taskId, notes }, void 0, accessor);
|
|
10999
|
+
return {
|
|
11000
|
+
success: true,
|
|
11001
|
+
data: {
|
|
11002
|
+
task: result.task,
|
|
11003
|
+
...result.autoCompleted && { autoCompleted: result.autoCompleted },
|
|
11004
|
+
...result.unblockedTasks && { unblockedTasks: result.unblockedTasks }
|
|
11005
|
+
}
|
|
11006
|
+
};
|
|
11007
|
+
} catch (err) {
|
|
11008
|
+
const message = err.message;
|
|
11009
|
+
if (message.includes("already completed")) {
|
|
11010
|
+
return engineError("E_TASK_COMPLETED", message);
|
|
11011
|
+
}
|
|
11012
|
+
if (message.includes("not found")) {
|
|
11013
|
+
return engineError("E_NOT_FOUND", message);
|
|
11014
|
+
}
|
|
11015
|
+
if (message.includes("incomplete dependencies")) {
|
|
11016
|
+
return engineError("E_DEPENDENCY_ERROR", message);
|
|
11017
|
+
}
|
|
11018
|
+
if (message.includes("incomplete children")) {
|
|
11019
|
+
return engineError("E_HAS_CHILDREN", message);
|
|
11020
|
+
}
|
|
11021
|
+
return engineError("E_INTERNAL", message);
|
|
10419
11022
|
}
|
|
10420
|
-
return taskUpdate(projectRoot, taskId, {
|
|
10421
|
-
status: "done",
|
|
10422
|
-
notes: notes || void 0
|
|
10423
|
-
});
|
|
10424
11023
|
}
|
|
10425
11024
|
async function taskDelete(projectRoot, taskId, force) {
|
|
10426
11025
|
try {
|
|
@@ -10625,9 +11224,9 @@ async function taskComplexityEstimate(projectRoot, params) {
|
|
|
10625
11224
|
return engineError("E_NOT_INITIALIZED", "Task database not initialized");
|
|
10626
11225
|
}
|
|
10627
11226
|
}
|
|
10628
|
-
async function taskDepends(projectRoot, taskId, direction = "both") {
|
|
11227
|
+
async function taskDepends(projectRoot, taskId, direction = "both", tree) {
|
|
10629
11228
|
try {
|
|
10630
|
-
const result = await coreTaskDepends(projectRoot, taskId, direction);
|
|
11229
|
+
const result = await coreTaskDepends(projectRoot, taskId, direction, tree ? { tree } : void 0);
|
|
10631
11230
|
return { success: true, data: result };
|
|
10632
11231
|
} catch (err) {
|
|
10633
11232
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10637,6 +11236,24 @@ async function taskDepends(projectRoot, taskId, direction = "both") {
|
|
|
10637
11236
|
return engineError("E_NOT_INITIALIZED", "Task database not initialized");
|
|
10638
11237
|
}
|
|
10639
11238
|
}
|
|
11239
|
+
async function taskDepsOverview(projectRoot) {
|
|
11240
|
+
try {
|
|
11241
|
+
const result = await coreTaskDepsOverview(projectRoot);
|
|
11242
|
+
return { success: true, data: result };
|
|
11243
|
+
} catch (err) {
|
|
11244
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11245
|
+
return engineError("E_NOT_INITIALIZED", message);
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
async function taskDepsCycles(projectRoot) {
|
|
11249
|
+
try {
|
|
11250
|
+
const result = await coreTaskDepsCycles(projectRoot);
|
|
11251
|
+
return { success: true, data: result };
|
|
11252
|
+
} catch (err) {
|
|
11253
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11254
|
+
return engineError("E_NOT_INITIALIZED", message);
|
|
11255
|
+
}
|
|
11256
|
+
}
|
|
10640
11257
|
async function taskPlan(projectRoot) {
|
|
10641
11258
|
const { coreTaskPlan: coreTaskPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
10642
11259
|
try {
|
|
@@ -10894,7 +11511,7 @@ init_decisions();
|
|
|
10894
11511
|
init_data_accessor();
|
|
10895
11512
|
init_errors();
|
|
10896
11513
|
init_exit_codes();
|
|
10897
|
-
import { randomBytes as
|
|
11514
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
10898
11515
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "node:fs";
|
|
10899
11516
|
import { join as join10 } from "node:path";
|
|
10900
11517
|
async function recordAssumption(projectRoot, params) {
|
|
@@ -10914,7 +11531,7 @@ async function recordAssumption(projectRoot, params) {
|
|
|
10914
11531
|
const taskData = await accessor.loadTaskFile();
|
|
10915
11532
|
const current = taskData;
|
|
10916
11533
|
const sessionId = params.sessionId || current._meta?.activeSession || "default";
|
|
10917
|
-
const id = `asm-${
|
|
11534
|
+
const id = `asm-${randomBytes4(8).toString("hex")}`;
|
|
10918
11535
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10919
11536
|
const record = {
|
|
10920
11537
|
id,
|
|
@@ -10981,6 +11598,12 @@ async function computeBriefing(projectRoot, options = {}) {
|
|
|
10981
11598
|
scopeTaskIds
|
|
10982
11599
|
});
|
|
10983
11600
|
const pipelineStage = computePipelineStage(current);
|
|
11601
|
+
const warnings = [];
|
|
11602
|
+
if (currentTaskInfo?.blockedBy?.length) {
|
|
11603
|
+
warnings.push(
|
|
11604
|
+
`Focused task ${currentTaskInfo.id} is blocked by: ${currentTaskInfo.blockedBy.join(", ")}`
|
|
11605
|
+
);
|
|
11606
|
+
}
|
|
10984
11607
|
return {
|
|
10985
11608
|
lastSession,
|
|
10986
11609
|
currentTask: currentTaskInfo,
|
|
@@ -10988,7 +11611,8 @@ async function computeBriefing(projectRoot, options = {}) {
|
|
|
10988
11611
|
openBugs,
|
|
10989
11612
|
blockedTasks,
|
|
10990
11613
|
activeEpics,
|
|
10991
|
-
...pipelineStage && { pipelineStage }
|
|
11614
|
+
...pipelineStage && { pipelineStage },
|
|
11615
|
+
...warnings.length > 0 && { warnings }
|
|
10992
11616
|
};
|
|
10993
11617
|
}
|
|
10994
11618
|
function parseScope(scopeStr, current) {
|
|
@@ -11076,11 +11700,21 @@ function computeCurrentTask(current, taskMap) {
|
|
|
11076
11700
|
if (!focusTaskId) return null;
|
|
11077
11701
|
const task = taskMap.get(focusTaskId);
|
|
11078
11702
|
if (!task) return null;
|
|
11079
|
-
|
|
11703
|
+
const info = {
|
|
11080
11704
|
id: task.id,
|
|
11081
11705
|
title: task.title,
|
|
11082
11706
|
status: task.status
|
|
11083
11707
|
};
|
|
11708
|
+
if (task.depends?.length) {
|
|
11709
|
+
const unresolved = task.depends.filter((depId) => {
|
|
11710
|
+
const dep = taskMap.get(depId);
|
|
11711
|
+
return dep && dep.status !== "done" && dep.status !== "cancelled";
|
|
11712
|
+
});
|
|
11713
|
+
if (unresolved.length > 0) {
|
|
11714
|
+
info.blockedBy = unresolved;
|
|
11715
|
+
}
|
|
11716
|
+
}
|
|
11717
|
+
return info;
|
|
11084
11718
|
}
|
|
11085
11719
|
function calculateLeverage2(taskId, taskMap) {
|
|
11086
11720
|
let leverage = 0;
|
|
@@ -11227,11 +11861,11 @@ function computePipelineStage(current) {
|
|
|
11227
11861
|
}
|
|
11228
11862
|
|
|
11229
11863
|
// src/core/sessions/session-id.ts
|
|
11230
|
-
import { randomBytes as
|
|
11864
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
11231
11865
|
function generateSessionId() {
|
|
11232
11866
|
const now = /* @__PURE__ */ new Date();
|
|
11233
11867
|
const ts = now.toISOString().replace(/[-:T]/g, "").substring(0, 14);
|
|
11234
|
-
const hex =
|
|
11868
|
+
const hex = randomBytes5(3).toString("hex");
|
|
11235
11869
|
return `ses_${ts}_${hex}`;
|
|
11236
11870
|
}
|
|
11237
11871
|
|
|
@@ -11269,6 +11903,14 @@ async function startTask(taskId, cwd, accessor) {
|
|
|
11269
11903
|
{ fix: `Use 'cleo find "${taskId}"' to search` }
|
|
11270
11904
|
);
|
|
11271
11905
|
}
|
|
11906
|
+
const unresolvedDeps = getUnresolvedDeps(taskId, data.tasks);
|
|
11907
|
+
if (unresolvedDeps.length > 0) {
|
|
11908
|
+
throw new CleoError(
|
|
11909
|
+
5 /* DEPENDENCY_ERROR */,
|
|
11910
|
+
`Task ${taskId} is blocked by unresolved dependencies: ${unresolvedDeps.join(", ")}`,
|
|
11911
|
+
{ fix: `Complete blockers first: ${unresolvedDeps.map((d) => `cleo complete ${d}`).join(", ")}` }
|
|
11912
|
+
);
|
|
11913
|
+
}
|
|
11272
11914
|
const previousTask = data.focus?.currentTask ?? null;
|
|
11273
11915
|
if (!data.focus) {
|
|
11274
11916
|
data.focus = {};
|
|
@@ -11327,6 +11969,8 @@ async function stopTask(cwd, accessor) {
|
|
|
11327
11969
|
}
|
|
11328
11970
|
|
|
11329
11971
|
// src/dispatch/engines/session-engine.ts
|
|
11972
|
+
init_errors();
|
|
11973
|
+
init_exit_codes();
|
|
11330
11974
|
async function sessionStatus(projectRoot) {
|
|
11331
11975
|
try {
|
|
11332
11976
|
const accessor = await getAccessor(projectRoot);
|
|
@@ -11396,6 +12040,12 @@ async function taskStart(projectRoot, taskId) {
|
|
|
11396
12040
|
data: { taskId: result.taskId, previousTask: result.previousTask }
|
|
11397
12041
|
};
|
|
11398
12042
|
} catch (err) {
|
|
12043
|
+
if (err instanceof CleoError) {
|
|
12044
|
+
const stringCode = `E_${getExitCodeName(err.code)}`;
|
|
12045
|
+
return engineError(stringCode, err.message, {
|
|
12046
|
+
...err.fix && { fix: err.fix }
|
|
12047
|
+
});
|
|
12048
|
+
}
|
|
11399
12049
|
const message = err.message;
|
|
11400
12050
|
const code = message.includes("not found") ? "E_NOT_FOUND" : "E_NOT_INITIALIZED";
|
|
11401
12051
|
return engineError(code, message);
|
|
@@ -11860,8 +12510,8 @@ async function sessionChainShow(projectRoot, sessionId) {
|
|
|
11860
12510
|
// src/dispatch/engines/system-engine.ts
|
|
11861
12511
|
init_platform();
|
|
11862
12512
|
init_data_accessor();
|
|
11863
|
-
import { readFileSync as
|
|
11864
|
-
import { join as
|
|
12513
|
+
import { readFileSync as readFileSync13, existsSync as existsSync20, readdirSync as readdirSync5 } from "node:fs";
|
|
12514
|
+
import { join as join20, basename as basename4 } from "node:path";
|
|
11865
12515
|
|
|
11866
12516
|
// src/core/stats/index.ts
|
|
11867
12517
|
init_json();
|
|
@@ -11989,14 +12639,14 @@ init_paths();
|
|
|
11989
12639
|
// src/core/system/inject-generate.ts
|
|
11990
12640
|
init_json();
|
|
11991
12641
|
init_paths();
|
|
11992
|
-
import { readFileSync as
|
|
11993
|
-
import { join as
|
|
12642
|
+
import { readFileSync as readFileSync6, existsSync as existsSync12 } from "node:fs";
|
|
12643
|
+
import { join as join12 } from "node:path";
|
|
11994
12644
|
async function generateInjection(projectRoot, accessor) {
|
|
11995
12645
|
let version = "unknown";
|
|
11996
12646
|
try {
|
|
11997
|
-
const pkgPath =
|
|
11998
|
-
if (
|
|
11999
|
-
const pkg = JSON.parse(
|
|
12647
|
+
const pkgPath = join12(projectRoot, "package.json");
|
|
12648
|
+
if (existsSync12(pkgPath)) {
|
|
12649
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
12000
12650
|
version = pkg.version || "unknown";
|
|
12001
12651
|
}
|
|
12002
12652
|
} catch {
|
|
@@ -12011,8 +12661,8 @@ async function generateInjection(projectRoot, accessor) {
|
|
|
12011
12661
|
}
|
|
12012
12662
|
try {
|
|
12013
12663
|
const sessionsPath = getSessionsPath(projectRoot);
|
|
12014
|
-
if (
|
|
12015
|
-
const sessionsData = JSON.parse(
|
|
12664
|
+
if (existsSync12(sessionsPath)) {
|
|
12665
|
+
const sessionsData = JSON.parse(readFileSync6(sessionsPath, "utf-8"));
|
|
12016
12666
|
const active = sessionsData.sessions?.find((s) => s.status === "active");
|
|
12017
12667
|
if (active) {
|
|
12018
12668
|
activeSessionName = active.name || active.id;
|
|
@@ -12093,8 +12743,8 @@ For full protocol details, load the **ct-cleo** skill: \`cleo_query({ domain: "s
|
|
|
12093
12743
|
}
|
|
12094
12744
|
|
|
12095
12745
|
// src/core/system/health.ts
|
|
12096
|
-
import { existsSync as
|
|
12097
|
-
import { join as
|
|
12746
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
|
|
12747
|
+
import { join as join14 } from "node:path";
|
|
12098
12748
|
import { execFile as execFile3 } from "node:child_process";
|
|
12099
12749
|
import { promisify as promisify3 } from "node:util";
|
|
12100
12750
|
|
|
@@ -12106,25 +12756,25 @@ init_platform();
|
|
|
12106
12756
|
|
|
12107
12757
|
// src/core/migration/agent-outputs.ts
|
|
12108
12758
|
import {
|
|
12109
|
-
existsSync as
|
|
12110
|
-
readFileSync as
|
|
12111
|
-
writeFileSync as
|
|
12112
|
-
mkdirSync as
|
|
12759
|
+
existsSync as existsSync13,
|
|
12760
|
+
readFileSync as readFileSync7,
|
|
12761
|
+
writeFileSync as writeFileSync2,
|
|
12762
|
+
mkdirSync as mkdirSync6,
|
|
12113
12763
|
readdirSync as readdirSync3,
|
|
12114
12764
|
copyFileSync,
|
|
12115
12765
|
statSync as statSync3,
|
|
12116
12766
|
rmSync
|
|
12117
12767
|
} from "node:fs";
|
|
12118
|
-
import { join as
|
|
12768
|
+
import { join as join13 } from "node:path";
|
|
12119
12769
|
var CANONICAL_DIR = ".cleo/agent-outputs";
|
|
12120
12770
|
var MANIFEST_PATH_REWRITES = [
|
|
12121
12771
|
[/claudedocs\/research-outputs\//g, `${CANONICAL_DIR}/`],
|
|
12122
12772
|
[/claudedocs\/agent-outputs\//g, `${CANONICAL_DIR}/`]
|
|
12123
12773
|
];
|
|
12124
12774
|
function detectLegacyAgentOutputs(projectRoot, cleoDir) {
|
|
12125
|
-
const hasResearchOutputs =
|
|
12126
|
-
const hasLegacyAgentOutputs =
|
|
12127
|
-
const hasCanonical =
|
|
12775
|
+
const hasResearchOutputs = existsSync13(join13(projectRoot, "claudedocs", "research-outputs"));
|
|
12776
|
+
const hasLegacyAgentOutputs = existsSync13(join13(projectRoot, "claudedocs", "agent-outputs"));
|
|
12777
|
+
const hasCanonical = existsSync13(join13(cleoDir, "agent-outputs"));
|
|
12128
12778
|
const legacyPaths = [];
|
|
12129
12779
|
if (hasResearchOutputs) legacyPaths.push("claudedocs/research-outputs/");
|
|
12130
12780
|
if (hasLegacyAgentOutputs) legacyPaths.push("claudedocs/agent-outputs/");
|
|
@@ -12147,30 +12797,30 @@ function migrateAgentOutputs(projectRoot, cleoDir) {
|
|
|
12147
12797
|
summary: "No legacy output directories found"
|
|
12148
12798
|
};
|
|
12149
12799
|
}
|
|
12150
|
-
const newDir =
|
|
12800
|
+
const newDir = join13(cleoDir, "agent-outputs");
|
|
12151
12801
|
const hadCanonical = detection.hasCanonical;
|
|
12152
|
-
|
|
12802
|
+
mkdirSync6(newDir, { recursive: true });
|
|
12153
12803
|
let totalCopied = 0;
|
|
12154
12804
|
const mergedManifestLines = [];
|
|
12155
12805
|
const copiedFiles = /* @__PURE__ */ new Set();
|
|
12156
12806
|
const legacySources = [
|
|
12157
|
-
{ path:
|
|
12158
|
-
{ path:
|
|
12807
|
+
{ path: join13(projectRoot, "claudedocs", "research-outputs"), exists: detection.hasResearchOutputs },
|
|
12808
|
+
{ path: join13(projectRoot, "claudedocs", "agent-outputs"), exists: detection.hasLegacyAgentOutputs }
|
|
12159
12809
|
];
|
|
12160
12810
|
for (const source of legacySources) {
|
|
12161
12811
|
if (!source.exists) continue;
|
|
12162
12812
|
totalCopied += copyDirContents(source.path, newDir, mergedManifestLines, copiedFiles);
|
|
12163
12813
|
}
|
|
12164
|
-
const
|
|
12814
|
+
const manifestEntries2 = mergeManifests(newDir, hadCanonical, mergedManifestLines);
|
|
12165
12815
|
updateConfigPaths(cleoDir);
|
|
12166
12816
|
const removed = removeLegacyDirs(projectRoot, detection);
|
|
12167
12817
|
const parts = [`Migrated ${totalCopied} files \u2192 ${CANONICAL_DIR}/`];
|
|
12168
|
-
if (
|
|
12818
|
+
if (manifestEntries2 > 0) parts.push(`merged ${manifestEntries2} manifest entries`);
|
|
12169
12819
|
if (removed.length > 0) parts.push(`removed: ${removed.join(", ")}`);
|
|
12170
12820
|
return {
|
|
12171
12821
|
migrated: true,
|
|
12172
12822
|
filesCopied: totalCopied,
|
|
12173
|
-
manifestEntries,
|
|
12823
|
+
manifestEntries: manifestEntries2,
|
|
12174
12824
|
removed,
|
|
12175
12825
|
summary: parts.join("; ")
|
|
12176
12826
|
};
|
|
@@ -12180,19 +12830,19 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
12180
12830
|
const entries = readdirSync3(srcDir);
|
|
12181
12831
|
for (const entry of entries) {
|
|
12182
12832
|
if (entry === "MANIFEST.jsonl") {
|
|
12183
|
-
collectManifestLines(
|
|
12833
|
+
collectManifestLines(join13(srcDir, entry), manifestLines);
|
|
12184
12834
|
continue;
|
|
12185
12835
|
}
|
|
12186
|
-
const srcPath =
|
|
12187
|
-
const dstPath =
|
|
12836
|
+
const srcPath = join13(srcDir, entry);
|
|
12837
|
+
const dstPath = join13(dstDir, entry);
|
|
12188
12838
|
try {
|
|
12189
12839
|
const st = statSync3(srcPath);
|
|
12190
12840
|
if (st.isDirectory()) {
|
|
12191
|
-
|
|
12841
|
+
mkdirSync6(dstPath, { recursive: true });
|
|
12192
12842
|
for (const sf of readdirSync3(srcPath)) {
|
|
12193
12843
|
if (!copiedFiles.has(sf)) {
|
|
12194
12844
|
try {
|
|
12195
|
-
copyFileSync(
|
|
12845
|
+
copyFileSync(join13(srcPath, sf), join13(dstPath, sf));
|
|
12196
12846
|
copiedFiles.add(sf);
|
|
12197
12847
|
count2++;
|
|
12198
12848
|
} catch {
|
|
@@ -12211,7 +12861,7 @@ function copyDirContents(srcDir, dstDir, manifestLines, copiedFiles) {
|
|
|
12211
12861
|
}
|
|
12212
12862
|
function collectManifestLines(manifestPath, out) {
|
|
12213
12863
|
try {
|
|
12214
|
-
const content =
|
|
12864
|
+
const content = readFileSync7(manifestPath, "utf-8");
|
|
12215
12865
|
for (const line of content.split("\n")) {
|
|
12216
12866
|
if (!line.trim()) continue;
|
|
12217
12867
|
let rewritten = line;
|
|
@@ -12224,11 +12874,11 @@ function collectManifestLines(manifestPath, out) {
|
|
|
12224
12874
|
}
|
|
12225
12875
|
}
|
|
12226
12876
|
function mergeManifests(newDir, hadCanonical, legacyLines) {
|
|
12227
|
-
const manifestPath =
|
|
12877
|
+
const manifestPath = join13(newDir, "MANIFEST.jsonl");
|
|
12228
12878
|
const existingLines = [];
|
|
12229
|
-
if (hadCanonical &&
|
|
12879
|
+
if (hadCanonical && existsSync13(manifestPath)) {
|
|
12230
12880
|
try {
|
|
12231
|
-
const existing =
|
|
12881
|
+
const existing = readFileSync7(manifestPath, "utf-8");
|
|
12232
12882
|
for (const line of existing.split("\n")) {
|
|
12233
12883
|
if (line.trim()) existingLines.push(line);
|
|
12234
12884
|
}
|
|
@@ -12255,15 +12905,15 @@ function mergeManifests(newDir, hadCanonical, legacyLines) {
|
|
|
12255
12905
|
finalLines.push(line);
|
|
12256
12906
|
}
|
|
12257
12907
|
if (finalLines.length > 0) {
|
|
12258
|
-
|
|
12908
|
+
writeFileSync2(manifestPath, finalLines.join("\n") + "\n");
|
|
12259
12909
|
}
|
|
12260
12910
|
return finalLines.length;
|
|
12261
12911
|
}
|
|
12262
12912
|
function updateConfigPaths(cleoDir) {
|
|
12263
|
-
const configPath =
|
|
12264
|
-
if (!
|
|
12913
|
+
const configPath = join13(cleoDir, "config.json");
|
|
12914
|
+
if (!existsSync13(configPath)) return;
|
|
12265
12915
|
try {
|
|
12266
|
-
const config = JSON.parse(
|
|
12916
|
+
const config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
12267
12917
|
const currentDir = config.agentOutputs?.directory ?? config.agentOutputs ?? config.research?.outputDir;
|
|
12268
12918
|
if (currentDir && currentDir !== CANONICAL_DIR) {
|
|
12269
12919
|
if (typeof config.agentOutputs === "object") {
|
|
@@ -12277,7 +12927,7 @@ function updateConfigPaths(cleoDir) {
|
|
|
12277
12927
|
delete config.research;
|
|
12278
12928
|
}
|
|
12279
12929
|
}
|
|
12280
|
-
|
|
12930
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
12281
12931
|
}
|
|
12282
12932
|
} catch {
|
|
12283
12933
|
}
|
|
@@ -12286,20 +12936,20 @@ function removeLegacyDirs(projectRoot, detection) {
|
|
|
12286
12936
|
const removed = [];
|
|
12287
12937
|
if (detection.hasResearchOutputs) {
|
|
12288
12938
|
try {
|
|
12289
|
-
rmSync(
|
|
12939
|
+
rmSync(join13(projectRoot, "claudedocs", "research-outputs"), { recursive: true, force: true });
|
|
12290
12940
|
removed.push("claudedocs/research-outputs/");
|
|
12291
12941
|
} catch {
|
|
12292
12942
|
}
|
|
12293
12943
|
}
|
|
12294
12944
|
if (detection.hasLegacyAgentOutputs) {
|
|
12295
12945
|
try {
|
|
12296
|
-
rmSync(
|
|
12946
|
+
rmSync(join13(projectRoot, "claudedocs", "agent-outputs"), { recursive: true, force: true });
|
|
12297
12947
|
removed.push("claudedocs/agent-outputs/");
|
|
12298
12948
|
} catch {
|
|
12299
12949
|
}
|
|
12300
12950
|
}
|
|
12301
|
-
const claudedocsDir =
|
|
12302
|
-
if (
|
|
12951
|
+
const claudedocsDir = join13(projectRoot, "claudedocs");
|
|
12952
|
+
if (existsSync13(claudedocsDir)) {
|
|
12303
12953
|
try {
|
|
12304
12954
|
if (readdirSync3(claudedocsDir).length === 0) {
|
|
12305
12955
|
rmSync(claudedocsDir, { recursive: true, force: true });
|
|
@@ -12316,15 +12966,15 @@ init_data_accessor();
|
|
|
12316
12966
|
var execAsync = promisify3(execFile3);
|
|
12317
12967
|
var STALE_JSON_FILES = ["todo.json", "sessions.json", "todo-archive.json"];
|
|
12318
12968
|
function getSystemHealth(projectRoot, opts) {
|
|
12319
|
-
const cleoDir =
|
|
12969
|
+
const cleoDir = join14(projectRoot, ".cleo");
|
|
12320
12970
|
const checks = [];
|
|
12321
|
-
if (
|
|
12971
|
+
if (existsSync14(cleoDir)) {
|
|
12322
12972
|
checks.push({ name: "cleo_dir", status: "pass", message: ".cleo directory exists" });
|
|
12323
12973
|
} else {
|
|
12324
12974
|
checks.push({ name: "cleo_dir", status: "fail", message: ".cleo directory not found" });
|
|
12325
12975
|
}
|
|
12326
|
-
const dbPath =
|
|
12327
|
-
if (
|
|
12976
|
+
const dbPath = join14(cleoDir, "tasks.db");
|
|
12977
|
+
if (existsSync14(dbPath)) {
|
|
12328
12978
|
try {
|
|
12329
12979
|
const dbSize = statSync4(dbPath).size;
|
|
12330
12980
|
if (dbSize > 0) {
|
|
@@ -12338,10 +12988,10 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12338
12988
|
} else {
|
|
12339
12989
|
checks.push({ name: "tasks_db", status: "fail", message: "tasks.db not found" });
|
|
12340
12990
|
}
|
|
12341
|
-
const configPath =
|
|
12342
|
-
if (
|
|
12991
|
+
const configPath = join14(cleoDir, "config.json");
|
|
12992
|
+
if (existsSync14(configPath)) {
|
|
12343
12993
|
try {
|
|
12344
|
-
JSON.parse(
|
|
12994
|
+
JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
12345
12995
|
checks.push({ name: "config_json", status: "pass", message: "config.json is valid JSON" });
|
|
12346
12996
|
} catch {
|
|
12347
12997
|
checks.push({ name: "config_json", status: "warn", message: "config.json is not valid JSON" });
|
|
@@ -12349,8 +12999,8 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12349
12999
|
} else {
|
|
12350
13000
|
checks.push({ name: "config_json", status: "warn", message: "config.json not found" });
|
|
12351
13001
|
}
|
|
12352
|
-
if (
|
|
12353
|
-
const staleFiles = STALE_JSON_FILES.filter((f) =>
|
|
13002
|
+
if (existsSync14(dbPath)) {
|
|
13003
|
+
const staleFiles = STALE_JSON_FILES.filter((f) => existsSync14(join14(cleoDir, f)));
|
|
12354
13004
|
if (staleFiles.length > 0) {
|
|
12355
13005
|
checks.push({
|
|
12356
13006
|
name: "stale_json",
|
|
@@ -12360,14 +13010,14 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12360
13010
|
}
|
|
12361
13011
|
}
|
|
12362
13012
|
if (opts?.detailed) {
|
|
12363
|
-
const logPath =
|
|
12364
|
-
if (
|
|
13013
|
+
const logPath = join14(cleoDir, "todo-log.jsonl");
|
|
13014
|
+
if (existsSync14(logPath)) {
|
|
12365
13015
|
checks.push({ name: "log_file", status: "pass", message: "todo-log.jsonl exists" });
|
|
12366
13016
|
} else {
|
|
12367
13017
|
checks.push({ name: "log_file", status: "warn", message: "todo-log.jsonl not found" });
|
|
12368
13018
|
}
|
|
12369
|
-
const backupDir =
|
|
12370
|
-
if (
|
|
13019
|
+
const backupDir = join14(cleoDir, ".backups");
|
|
13020
|
+
if (existsSync14(backupDir)) {
|
|
12371
13021
|
checks.push({ name: "backups_dir", status: "pass", message: ".backups directory exists" });
|
|
12372
13022
|
} else {
|
|
12373
13023
|
checks.push({ name: "backups_dir", status: "pass", message: "No backups directory (created on first write)" });
|
|
@@ -12375,9 +13025,9 @@ function getSystemHealth(projectRoot, opts) {
|
|
|
12375
13025
|
}
|
|
12376
13026
|
let version = "unknown";
|
|
12377
13027
|
try {
|
|
12378
|
-
const pkgPath =
|
|
12379
|
-
if (
|
|
12380
|
-
const pkg = JSON.parse(
|
|
13028
|
+
const pkgPath = join14(projectRoot, "package.json");
|
|
13029
|
+
if (existsSync14(pkgPath)) {
|
|
13030
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
12381
13031
|
version = pkg.version || "unknown";
|
|
12382
13032
|
}
|
|
12383
13033
|
} catch {
|
|
@@ -12396,34 +13046,34 @@ init_paths();
|
|
|
12396
13046
|
// src/core/system/backup.ts
|
|
12397
13047
|
init_errors();
|
|
12398
13048
|
init_exit_codes();
|
|
12399
|
-
import { readFileSync as
|
|
12400
|
-
import { join as
|
|
13049
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "node:fs";
|
|
13050
|
+
import { join as join15 } from "node:path";
|
|
12401
13051
|
function createBackup2(projectRoot, opts) {
|
|
12402
|
-
const cleoDir =
|
|
13052
|
+
const cleoDir = join15(projectRoot, ".cleo");
|
|
12403
13053
|
const btype = opts?.type || "snapshot";
|
|
12404
13054
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
12405
13055
|
const backupId = `${btype}-${timestamp.replace(/[:.]/g, "-")}`;
|
|
12406
|
-
const backupDir =
|
|
12407
|
-
if (!
|
|
12408
|
-
|
|
13056
|
+
const backupDir = join15(cleoDir, "backups", btype);
|
|
13057
|
+
if (!existsSync15(backupDir)) {
|
|
13058
|
+
mkdirSync7(backupDir, { recursive: true });
|
|
12409
13059
|
}
|
|
12410
13060
|
const filesToBackup = ["todo.json", "todo-archive.json", "sessions.json", "config.json", "todo-log.jsonl"];
|
|
12411
13061
|
const backedUp = [];
|
|
12412
13062
|
for (const file of filesToBackup) {
|
|
12413
|
-
const src =
|
|
12414
|
-
if (
|
|
12415
|
-
const dest =
|
|
13063
|
+
const src = join15(cleoDir, file);
|
|
13064
|
+
if (existsSync15(src)) {
|
|
13065
|
+
const dest = join15(backupDir, `${file}.${backupId}`);
|
|
12416
13066
|
try {
|
|
12417
|
-
const content =
|
|
12418
|
-
|
|
13067
|
+
const content = readFileSync9(src, "utf-8");
|
|
13068
|
+
writeFileSync3(dest, content, "utf-8");
|
|
12419
13069
|
backedUp.push(file);
|
|
12420
13070
|
} catch {
|
|
12421
13071
|
}
|
|
12422
13072
|
}
|
|
12423
13073
|
}
|
|
12424
|
-
const metaPath =
|
|
13074
|
+
const metaPath = join15(backupDir, `${backupId}.meta.json`);
|
|
12425
13075
|
try {
|
|
12426
|
-
|
|
13076
|
+
writeFileSync3(metaPath, JSON.stringify({
|
|
12427
13077
|
backupId,
|
|
12428
13078
|
type: btype,
|
|
12429
13079
|
timestamp,
|
|
@@ -12438,15 +13088,15 @@ function restoreBackup(projectRoot, params) {
|
|
|
12438
13088
|
if (!params.backupId) {
|
|
12439
13089
|
throw new CleoError(2 /* INVALID_INPUT */, "backupId is required");
|
|
12440
13090
|
}
|
|
12441
|
-
const cleoDir =
|
|
13091
|
+
const cleoDir = join15(projectRoot, ".cleo");
|
|
12442
13092
|
const backupTypes = ["snapshot", "safety", "migration"];
|
|
12443
13093
|
let metaPath = null;
|
|
12444
13094
|
let backupDir = null;
|
|
12445
13095
|
for (const btype of backupTypes) {
|
|
12446
|
-
const candidateMeta =
|
|
12447
|
-
if (
|
|
13096
|
+
const candidateMeta = join15(cleoDir, "backups", btype, `${params.backupId}.meta.json`);
|
|
13097
|
+
if (existsSync15(candidateMeta)) {
|
|
12448
13098
|
metaPath = candidateMeta;
|
|
12449
|
-
backupDir =
|
|
13099
|
+
backupDir = join15(cleoDir, "backups", btype);
|
|
12450
13100
|
break;
|
|
12451
13101
|
}
|
|
12452
13102
|
}
|
|
@@ -12455,17 +13105,17 @@ function restoreBackup(projectRoot, params) {
|
|
|
12455
13105
|
}
|
|
12456
13106
|
let meta;
|
|
12457
13107
|
try {
|
|
12458
|
-
meta = JSON.parse(
|
|
13108
|
+
meta = JSON.parse(readFileSync9(metaPath, "utf-8"));
|
|
12459
13109
|
} catch {
|
|
12460
13110
|
throw new CleoError(3 /* FILE_ERROR */, "Failed to read backup metadata");
|
|
12461
13111
|
}
|
|
12462
13112
|
const restored = [];
|
|
12463
13113
|
for (const file of meta.files ?? []) {
|
|
12464
|
-
const backupFile =
|
|
12465
|
-
if (
|
|
13114
|
+
const backupFile = join15(backupDir, `${file}.${params.backupId}`);
|
|
13115
|
+
if (existsSync15(backupFile)) {
|
|
12466
13116
|
try {
|
|
12467
|
-
const content =
|
|
12468
|
-
|
|
13117
|
+
const content = readFileSync9(backupFile, "utf-8");
|
|
13118
|
+
writeFileSync3(join15(cleoDir, file), content, "utf-8");
|
|
12469
13119
|
restored.push(file);
|
|
12470
13120
|
} catch {
|
|
12471
13121
|
}
|
|
@@ -12482,14 +13132,14 @@ function restoreBackup(projectRoot, params) {
|
|
|
12482
13132
|
// src/core/system/migrate.ts
|
|
12483
13133
|
init_errors();
|
|
12484
13134
|
init_exit_codes();
|
|
12485
|
-
import { readFileSync as
|
|
12486
|
-
import { join as
|
|
13135
|
+
import { readFileSync as readFileSync10, existsSync as existsSync16 } from "node:fs";
|
|
13136
|
+
import { join as join16 } from "node:path";
|
|
12487
13137
|
function getMigrationStatus(projectRoot, opts) {
|
|
12488
|
-
const taskPath =
|
|
13138
|
+
const taskPath = join16(projectRoot, ".cleo", "tasks.json");
|
|
12489
13139
|
let currentVersion = "unknown";
|
|
12490
|
-
if (
|
|
13140
|
+
if (existsSync16(taskPath)) {
|
|
12491
13141
|
try {
|
|
12492
|
-
const taskFile = JSON.parse(
|
|
13142
|
+
const taskFile = JSON.parse(readFileSync10(taskPath, "utf-8"));
|
|
12493
13143
|
currentVersion = taskFile._meta?.schemaVersion ?? taskFile.version ?? "unknown";
|
|
12494
13144
|
} catch {
|
|
12495
13145
|
throw new CleoError(3 /* FILE_ERROR */, "Failed to read tasks.json");
|
|
@@ -12509,21 +13159,21 @@ function getMigrationStatus(projectRoot, opts) {
|
|
|
12509
13159
|
// src/core/system/cleanup.ts
|
|
12510
13160
|
init_errors();
|
|
12511
13161
|
init_exit_codes();
|
|
12512
|
-
import { readFileSync as
|
|
12513
|
-
import { join as
|
|
13162
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync17, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "node:fs";
|
|
13163
|
+
import { join as join17 } from "node:path";
|
|
12514
13164
|
function cleanupSystem(projectRoot, params) {
|
|
12515
13165
|
if (!params.target) {
|
|
12516
13166
|
throw new CleoError(2 /* INVALID_INPUT */, "target is required (sessions|backups|logs|archive)");
|
|
12517
13167
|
}
|
|
12518
|
-
const cleoDir =
|
|
13168
|
+
const cleoDir = join17(projectRoot, ".cleo");
|
|
12519
13169
|
const dryRun = params.dryRun ?? false;
|
|
12520
13170
|
const items = [];
|
|
12521
13171
|
switch (params.target) {
|
|
12522
13172
|
case "sessions": {
|
|
12523
|
-
const sessPath =
|
|
12524
|
-
if (
|
|
13173
|
+
const sessPath = join17(cleoDir, "sessions.json");
|
|
13174
|
+
if (existsSync17(sessPath)) {
|
|
12525
13175
|
try {
|
|
12526
|
-
const data = JSON.parse(
|
|
13176
|
+
const data = JSON.parse(readFileSync11(sessPath, "utf-8"));
|
|
12527
13177
|
const sessions2 = data.sessions ?? [];
|
|
12528
13178
|
const stale = sessions2.filter((s) => {
|
|
12529
13179
|
if (s.status !== "active") return false;
|
|
@@ -12539,7 +13189,7 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12539
13189
|
if (!dryRun && stale.length > 0) {
|
|
12540
13190
|
const staleIds = new Set(stale.map((s) => s.id));
|
|
12541
13191
|
data.sessions = sessions2.filter((s) => !staleIds.has(s.id));
|
|
12542
|
-
|
|
13192
|
+
writeFileSync4(sessPath, JSON.stringify(data, null, 2), "utf-8");
|
|
12543
13193
|
}
|
|
12544
13194
|
} catch {
|
|
12545
13195
|
}
|
|
@@ -12547,24 +13197,24 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12547
13197
|
break;
|
|
12548
13198
|
}
|
|
12549
13199
|
case "backups": {
|
|
12550
|
-
const backupBaseDir =
|
|
12551
|
-
if (
|
|
13200
|
+
const backupBaseDir = join17(cleoDir, "backups");
|
|
13201
|
+
if (existsSync17(backupBaseDir)) {
|
|
12552
13202
|
for (const typeDir of readdirSync4(backupBaseDir)) {
|
|
12553
|
-
const fullDir =
|
|
13203
|
+
const fullDir = join17(backupBaseDir, typeDir);
|
|
12554
13204
|
try {
|
|
12555
13205
|
for (const file of readdirSync4(fullDir)) {
|
|
12556
13206
|
if (file.endsWith(".meta.json")) {
|
|
12557
|
-
const metaFilePath =
|
|
13207
|
+
const metaFilePath = join17(fullDir, file);
|
|
12558
13208
|
try {
|
|
12559
|
-
const meta = JSON.parse(
|
|
13209
|
+
const meta = JSON.parse(readFileSync11(metaFilePath, "utf-8"));
|
|
12560
13210
|
if (params.olderThan && meta.timestamp < params.olderThan) {
|
|
12561
13211
|
items.push(file.replace(".meta.json", ""));
|
|
12562
13212
|
if (!dryRun) {
|
|
12563
|
-
|
|
13213
|
+
unlinkSync3(metaFilePath);
|
|
12564
13214
|
for (const bf of readdirSync4(fullDir)) {
|
|
12565
13215
|
if (bf.includes(meta.backupId)) {
|
|
12566
13216
|
try {
|
|
12567
|
-
|
|
13217
|
+
unlinkSync3(join17(fullDir, bf));
|
|
12568
13218
|
} catch {
|
|
12569
13219
|
}
|
|
12570
13220
|
}
|
|
@@ -12583,13 +13233,13 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12583
13233
|
}
|
|
12584
13234
|
case "logs": {
|
|
12585
13235
|
const auditPattern = /^audit-log-.*\.json$/;
|
|
12586
|
-
if (
|
|
13236
|
+
if (existsSync17(cleoDir)) {
|
|
12587
13237
|
for (const file of readdirSync4(cleoDir)) {
|
|
12588
13238
|
if (auditPattern.test(file)) {
|
|
12589
13239
|
items.push(file);
|
|
12590
13240
|
if (!dryRun) {
|
|
12591
13241
|
try {
|
|
12592
|
-
|
|
13242
|
+
unlinkSync3(join17(cleoDir, file));
|
|
12593
13243
|
} catch {
|
|
12594
13244
|
}
|
|
12595
13245
|
}
|
|
@@ -12607,17 +13257,17 @@ function cleanupSystem(projectRoot, params) {
|
|
|
12607
13257
|
// src/core/system/safestop.ts
|
|
12608
13258
|
init_errors();
|
|
12609
13259
|
init_exit_codes();
|
|
12610
|
-
import { readFileSync as
|
|
12611
|
-
import { join as
|
|
13260
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync5, existsSync as existsSync18 } from "node:fs";
|
|
13261
|
+
import { join as join18 } from "node:path";
|
|
12612
13262
|
function safestop(projectRoot, opts) {
|
|
12613
13263
|
const dryRun = opts?.dryRun ?? false;
|
|
12614
13264
|
const reason = opts?.reason ?? "Manual safestop";
|
|
12615
13265
|
let sessionEnded = false;
|
|
12616
13266
|
if (!dryRun && !opts?.noSessionEnd) {
|
|
12617
|
-
const sessPath =
|
|
12618
|
-
if (
|
|
13267
|
+
const sessPath = join18(projectRoot, ".cleo", "sessions.json");
|
|
13268
|
+
if (existsSync18(sessPath)) {
|
|
12619
13269
|
try {
|
|
12620
|
-
const data = JSON.parse(
|
|
13270
|
+
const data = JSON.parse(readFileSync12(sessPath, "utf-8"));
|
|
12621
13271
|
const sessions2 = data.sessions ?? [];
|
|
12622
13272
|
let changed = false;
|
|
12623
13273
|
for (const s of sessions2) {
|
|
@@ -12630,7 +13280,7 @@ function safestop(projectRoot, opts) {
|
|
|
12630
13280
|
}
|
|
12631
13281
|
}
|
|
12632
13282
|
if (changed) {
|
|
12633
|
-
|
|
13283
|
+
writeFileSync5(sessPath, JSON.stringify(data, null, 2), "utf-8");
|
|
12634
13284
|
}
|
|
12635
13285
|
} catch {
|
|
12636
13286
|
}
|
|
@@ -12643,9 +13293,9 @@ function safestop(projectRoot, opts) {
|
|
|
12643
13293
|
init_sequence();
|
|
12644
13294
|
|
|
12645
13295
|
// src/core/system/runtime.ts
|
|
12646
|
-
import { existsSync as
|
|
13296
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
12647
13297
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
12648
|
-
import { basename as
|
|
13298
|
+
import { basename as basename3, join as join19 } from "node:path";
|
|
12649
13299
|
import { homedir as homedir2 } from "node:os";
|
|
12650
13300
|
import { execFile as execFile4 } from "node:child_process";
|
|
12651
13301
|
import { promisify as promisify4 } from "node:util";
|
|
@@ -12671,16 +13321,16 @@ function detectFromDataRoot(dataRoot) {
|
|
|
12671
13321
|
function getExpectedNaming(channel) {
|
|
12672
13322
|
switch (channel) {
|
|
12673
13323
|
case "dev":
|
|
12674
|
-
return { cli: "cleo-dev", mcp: "cleo-mcp
|
|
13324
|
+
return { cli: "cleo-dev", mcp: "cleo-dev mcp", server: "cleo-dev" };
|
|
12675
13325
|
case "beta":
|
|
12676
|
-
return { cli: "cleo-beta", mcp: "cleo-mcp
|
|
13326
|
+
return { cli: "cleo-beta", mcp: "cleo-beta mcp", server: "cleo-beta" };
|
|
12677
13327
|
default:
|
|
12678
|
-
return { cli: "cleo", mcp: "cleo
|
|
13328
|
+
return { cli: "cleo", mcp: "cleo mcp", server: "cleo" };
|
|
12679
13329
|
}
|
|
12680
13330
|
}
|
|
12681
13331
|
async function parseVersionFile(dataRoot) {
|
|
12682
|
-
const versionPath =
|
|
12683
|
-
if (!
|
|
13332
|
+
const versionPath = join19(dataRoot, "VERSION");
|
|
13333
|
+
if (!existsSync19(versionPath)) return null;
|
|
12684
13334
|
let content;
|
|
12685
13335
|
try {
|
|
12686
13336
|
content = await readFile3(versionPath, "utf-8");
|
|
@@ -12709,9 +13359,9 @@ async function parseVersionFile(dataRoot) {
|
|
|
12709
13359
|
async function getPackageInfo(sourceDir) {
|
|
12710
13360
|
const candidates = [];
|
|
12711
13361
|
if (sourceDir && sourceDir !== "unknown" && sourceDir !== "npm") {
|
|
12712
|
-
candidates.push(
|
|
13362
|
+
candidates.push(join19(sourceDir, "package.json"));
|
|
12713
13363
|
}
|
|
12714
|
-
candidates.push(
|
|
13364
|
+
candidates.push(join19(process.cwd(), "package.json"));
|
|
12715
13365
|
for (const candidate of candidates) {
|
|
12716
13366
|
try {
|
|
12717
13367
|
const raw = await readFile3(candidate, "utf-8");
|
|
@@ -12735,9 +13385,9 @@ async function resolveBinaryPath(name) {
|
|
|
12735
13385
|
}
|
|
12736
13386
|
async function getRuntimeDiagnostics(options) {
|
|
12737
13387
|
const scriptPath = process.argv[1] ?? "";
|
|
12738
|
-
const invocationName =
|
|
13388
|
+
const invocationName = basename3(scriptPath || process.argv0 || "cleo");
|
|
12739
13389
|
const envChannel = normalizeChannel(process.env["CLEO_CHANNEL"]);
|
|
12740
|
-
const dataRoot = process.env["CLEO_HOME"] ??
|
|
13390
|
+
const dataRoot = process.env["CLEO_HOME"] ?? join19(homedir2(), ".cleo");
|
|
12741
13391
|
const versionInfo = await parseVersionFile(dataRoot);
|
|
12742
13392
|
const packageInfo = await getPackageInfo(versionInfo?.source);
|
|
12743
13393
|
const channel = envChannel ?? detectFromInvocation(invocationName) ?? normalizeChannel(versionInfo?.version.includes("-beta") ? "beta" : void 0) ?? detectFromDataRoot(dataRoot) ?? normalizeChannel(versionInfo?.mode.startsWith("dev") ? "dev" : void 0) ?? "stable";
|
|
@@ -12775,10 +13425,7 @@ async function getRuntimeDiagnostics(options) {
|
|
|
12775
13425
|
"cleo",
|
|
12776
13426
|
"ct",
|
|
12777
13427
|
"cleo-dev",
|
|
12778
|
-
"cleo-beta"
|
|
12779
|
-
"cleo-mcp",
|
|
12780
|
-
"cleo-mcp-dev",
|
|
12781
|
-
"cleo-mcp-beta"
|
|
13428
|
+
"cleo-beta"
|
|
12782
13429
|
];
|
|
12783
13430
|
const entries = await Promise.all(binNames.map(async (name) => [name, await resolveBinaryPath(name)]));
|
|
12784
13431
|
result.binaries = Object.fromEntries(entries.map(([name, path]) => [name, path ?? "not found"]));
|
|
@@ -12955,10 +13602,10 @@ async function systemLog(projectRoot, filters) {
|
|
|
12955
13602
|
}
|
|
12956
13603
|
async function queryAuditLogSqlite(projectRoot, filters) {
|
|
12957
13604
|
try {
|
|
12958
|
-
const { join:
|
|
12959
|
-
const { existsSync:
|
|
12960
|
-
const dbPath =
|
|
12961
|
-
if (!
|
|
13605
|
+
const { join: join45 } = await import("node:path");
|
|
13606
|
+
const { existsSync: existsSync44 } = await import("node:fs");
|
|
13607
|
+
const dbPath = join45(projectRoot, ".cleo", "tasks.db");
|
|
13608
|
+
if (!existsSync44(dbPath)) return null;
|
|
12962
13609
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
12963
13610
|
const { auditLog: auditLog2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
12964
13611
|
const { sql: sql4 } = await import("drizzle-orm");
|
|
@@ -13023,8 +13670,8 @@ async function queryAuditLogSqlite(projectRoot, filters) {
|
|
|
13023
13670
|
}
|
|
13024
13671
|
}
|
|
13025
13672
|
function queryAuditLogJsonl(projectRoot, filters) {
|
|
13026
|
-
const logPath =
|
|
13027
|
-
const raw =
|
|
13673
|
+
const logPath = getDataPath(projectRoot, "todo-log.jsonl");
|
|
13674
|
+
const raw = readLogFileEntries(logPath);
|
|
13028
13675
|
let entries = raw;
|
|
13029
13676
|
if (filters?.operation) {
|
|
13030
13677
|
entries = entries.filter((e) => e.operation === filters.operation);
|
|
@@ -13053,34 +13700,34 @@ function queryAuditLogJsonl(projectRoot, filters) {
|
|
|
13053
13700
|
}
|
|
13054
13701
|
function systemContext(projectRoot, params) {
|
|
13055
13702
|
try {
|
|
13056
|
-
const cleoDir =
|
|
13703
|
+
const cleoDir = join20(projectRoot, ".cleo");
|
|
13057
13704
|
let stateFile;
|
|
13058
13705
|
if (params?.session) {
|
|
13059
|
-
const sessionFile =
|
|
13060
|
-
stateFile =
|
|
13706
|
+
const sessionFile = join20(cleoDir, "context-states", `context-state-${params.session}.json`);
|
|
13707
|
+
stateFile = existsSync20(sessionFile) ? sessionFile : join20(cleoDir, ".context-state.json");
|
|
13061
13708
|
} else {
|
|
13062
|
-
const currentSessionPath =
|
|
13063
|
-
if (
|
|
13064
|
-
const currentSession =
|
|
13709
|
+
const currentSessionPath = join20(cleoDir, ".current-session");
|
|
13710
|
+
if (existsSync20(currentSessionPath)) {
|
|
13711
|
+
const currentSession = readFileSync13(currentSessionPath, "utf-8").trim();
|
|
13065
13712
|
if (currentSession) {
|
|
13066
|
-
const sessionFile =
|
|
13067
|
-
stateFile =
|
|
13713
|
+
const sessionFile = join20(cleoDir, "context-states", `context-state-${currentSession}.json`);
|
|
13714
|
+
stateFile = existsSync20(sessionFile) ? sessionFile : join20(cleoDir, ".context-state.json");
|
|
13068
13715
|
} else {
|
|
13069
|
-
stateFile =
|
|
13716
|
+
stateFile = join20(cleoDir, ".context-state.json");
|
|
13070
13717
|
}
|
|
13071
13718
|
} else {
|
|
13072
|
-
stateFile =
|
|
13719
|
+
stateFile = join20(cleoDir, ".context-state.json");
|
|
13073
13720
|
}
|
|
13074
13721
|
}
|
|
13075
13722
|
const sessions2 = [];
|
|
13076
|
-
const statesDir =
|
|
13077
|
-
if (
|
|
13723
|
+
const statesDir = join20(cleoDir, "context-states");
|
|
13724
|
+
if (existsSync20(statesDir)) {
|
|
13078
13725
|
for (const file of readdirSync5(statesDir)) {
|
|
13079
13726
|
if (file.startsWith("context-state-") && file.endsWith(".json")) {
|
|
13080
13727
|
try {
|
|
13081
|
-
const state = JSON.parse(
|
|
13728
|
+
const state = JSON.parse(readFileSync13(join20(statesDir, file), "utf-8"));
|
|
13082
13729
|
sessions2.push({
|
|
13083
|
-
file:
|
|
13730
|
+
file: basename4(file),
|
|
13084
13731
|
sessionId: state.sessionId ?? null,
|
|
13085
13732
|
percentage: state.contextWindow?.percentage ?? 0,
|
|
13086
13733
|
status: state.status ?? "unknown",
|
|
@@ -13091,10 +13738,10 @@ function systemContext(projectRoot, params) {
|
|
|
13091
13738
|
}
|
|
13092
13739
|
}
|
|
13093
13740
|
}
|
|
13094
|
-
const singletonFile =
|
|
13095
|
-
if (
|
|
13741
|
+
const singletonFile = join20(cleoDir, ".context-state.json");
|
|
13742
|
+
if (existsSync20(singletonFile)) {
|
|
13096
13743
|
try {
|
|
13097
|
-
const state = JSON.parse(
|
|
13744
|
+
const state = JSON.parse(readFileSync13(singletonFile, "utf-8"));
|
|
13098
13745
|
sessions2.push({
|
|
13099
13746
|
file: ".context-state.json",
|
|
13100
13747
|
sessionId: state.sessionId ?? "global",
|
|
@@ -13105,7 +13752,7 @@ function systemContext(projectRoot, params) {
|
|
|
13105
13752
|
} catch {
|
|
13106
13753
|
}
|
|
13107
13754
|
}
|
|
13108
|
-
if (!
|
|
13755
|
+
if (!existsSync20(stateFile)) {
|
|
13109
13756
|
return {
|
|
13110
13757
|
success: true,
|
|
13111
13758
|
data: {
|
|
@@ -13121,7 +13768,7 @@ function systemContext(projectRoot, params) {
|
|
|
13121
13768
|
};
|
|
13122
13769
|
}
|
|
13123
13770
|
try {
|
|
13124
|
-
const state = JSON.parse(
|
|
13771
|
+
const state = JSON.parse(readFileSync13(stateFile, "utf-8"));
|
|
13125
13772
|
const timestamp = state.timestamp;
|
|
13126
13773
|
const staleMs = state.staleAfterMs ?? 5e3;
|
|
13127
13774
|
const percentage = state.contextWindow?.percentage ?? 0;
|
|
@@ -13316,8 +13963,8 @@ init_json();
|
|
|
13316
13963
|
init_json();
|
|
13317
13964
|
init_paths();
|
|
13318
13965
|
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";
|
|
13319
|
-
import { constants as fsConstants, existsSync as
|
|
13320
|
-
import { join as
|
|
13966
|
+
import { constants as fsConstants, existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync7 } from "node:fs";
|
|
13967
|
+
import { join as join25, resolve as resolve5, basename as basename5, dirname as dirname7 } from "node:path";
|
|
13321
13968
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13322
13969
|
import { createHash as createHash5 } from "node:crypto";
|
|
13323
13970
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -13333,7 +13980,7 @@ async function fileExists(path) {
|
|
|
13333
13980
|
}
|
|
13334
13981
|
}
|
|
13335
13982
|
async function stripCLEOBlocks(filePath) {
|
|
13336
|
-
if (!
|
|
13983
|
+
if (!existsSync23(filePath)) return;
|
|
13337
13984
|
const content = await readFile5(filePath, "utf8");
|
|
13338
13985
|
const stripped = content.replace(
|
|
13339
13986
|
/\n?<!-- CLEO:START -->[\s\S]*?<!-- CLEO:END -->\n?/g,
|
|
@@ -13369,31 +14016,67 @@ function createDefaultConfig() {
|
|
|
13369
14016
|
}
|
|
13370
14017
|
function getPackageRoot() {
|
|
13371
14018
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
13372
|
-
return
|
|
14019
|
+
return resolve5(dirname7(thisFile), "..", "..");
|
|
13373
14020
|
}
|
|
13374
14021
|
function getGitignoreContent() {
|
|
13375
14022
|
try {
|
|
13376
14023
|
const packageRoot = getPackageRoot();
|
|
13377
|
-
const templatePath =
|
|
13378
|
-
if (
|
|
13379
|
-
return
|
|
14024
|
+
const templatePath = join25(packageRoot, "templates", "cleo-gitignore");
|
|
14025
|
+
if (existsSync23(templatePath)) {
|
|
14026
|
+
return readFileSync16(templatePath, "utf-8");
|
|
13380
14027
|
}
|
|
13381
14028
|
} catch {
|
|
13382
14029
|
}
|
|
13383
14030
|
return CLEO_GITIGNORE_FALLBACK;
|
|
13384
14031
|
}
|
|
13385
|
-
var CLEO_GITIGNORE_FALLBACK = `#
|
|
13386
|
-
#
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
.
|
|
14032
|
+
var CLEO_GITIGNORE_FALLBACK = `# .cleo/.gitignore \u2014 Deny-by-default for CLEO project data
|
|
14033
|
+
# Ignore everything, then explicitly allow only tracked files.
|
|
14034
|
+
|
|
14035
|
+
# Step 1: Ignore everything
|
|
14036
|
+
*
|
|
14037
|
+
|
|
14038
|
+
# Allow list
|
|
14039
|
+
!.gitignore
|
|
14040
|
+
!config.json
|
|
14041
|
+
!project-context.json
|
|
14042
|
+
!project-info.json
|
|
14043
|
+
!setup-otel.sh
|
|
14044
|
+
!DATA-SAFETY-IMPLEMENTATION-SUMMARY.md
|
|
14045
|
+
!schemas/
|
|
14046
|
+
!schemas/**
|
|
14047
|
+
!templates/
|
|
14048
|
+
!templates/**
|
|
14049
|
+
!adrs/
|
|
14050
|
+
!adrs/**
|
|
14051
|
+
!rcasd/
|
|
14052
|
+
!rcasd/**
|
|
14053
|
+
!agent-outputs/
|
|
14054
|
+
!agent-outputs/**
|
|
14055
|
+
|
|
14056
|
+
# Explicit deny safety net
|
|
14057
|
+
*.db
|
|
14058
|
+
*.db-shm
|
|
14059
|
+
*.db-wal
|
|
14060
|
+
*.db-journal
|
|
14061
|
+
log.json
|
|
14062
|
+
tasks-log.jsonl
|
|
14063
|
+
todo-log.jsonl
|
|
14064
|
+
bypass-log.json
|
|
14065
|
+
qa-log.json
|
|
14066
|
+
.deps-cache/
|
|
14067
|
+
.context-alert-state.json
|
|
14068
|
+
.context-state*.json
|
|
14069
|
+
context-states/
|
|
14070
|
+
.git-checkpoint-state
|
|
14071
|
+
.migration-state.json
|
|
14072
|
+
migrations.json
|
|
14073
|
+
sync/
|
|
14074
|
+
metrics/
|
|
14075
|
+
.backups/
|
|
14076
|
+
backups/
|
|
13394
14077
|
`;
|
|
13395
14078
|
async function removeCleoFromRootGitignore(projectRoot) {
|
|
13396
|
-
const rootGitignorePath =
|
|
14079
|
+
const rootGitignorePath = join25(projectRoot, ".gitignore");
|
|
13397
14080
|
if (!await fileExists(rootGitignorePath)) {
|
|
13398
14081
|
return false;
|
|
13399
14082
|
}
|
|
@@ -13414,8 +14097,8 @@ function generateProjectHash2(projectPath) {
|
|
|
13414
14097
|
}
|
|
13415
14098
|
function getCleoVersion() {
|
|
13416
14099
|
try {
|
|
13417
|
-
const pkgPath =
|
|
13418
|
-
const pkg = JSON.parse(
|
|
14100
|
+
const pkgPath = join25(getPackageRoot(), "package.json");
|
|
14101
|
+
const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
13419
14102
|
return pkg.version ?? "0.0.0";
|
|
13420
14103
|
} catch {
|
|
13421
14104
|
return "0.0.0";
|
|
@@ -13423,9 +14106,9 @@ function getCleoVersion() {
|
|
|
13423
14106
|
}
|
|
13424
14107
|
function getInjectionTemplateContent() {
|
|
13425
14108
|
const packageRoot = getPackageRoot();
|
|
13426
|
-
const packageTemplatePath =
|
|
13427
|
-
if (
|
|
13428
|
-
return
|
|
14109
|
+
const packageTemplatePath = join25(packageRoot, "templates", "CLEO-INJECTION.md");
|
|
14110
|
+
if (existsSync23(packageTemplatePath)) {
|
|
14111
|
+
return readFileSync16(packageTemplatePath, "utf-8");
|
|
13429
14112
|
}
|
|
13430
14113
|
return null;
|
|
13431
14114
|
}
|
|
@@ -13438,27 +14121,27 @@ async function initCoreFiles(cleoDir, _projectName, force, created, skipped) {
|
|
|
13438
14121
|
await saveJson(configPath, createDefaultConfig());
|
|
13439
14122
|
created.push("config.json");
|
|
13440
14123
|
}
|
|
13441
|
-
const legacySequencePath =
|
|
14124
|
+
const legacySequencePath = join25(cleoDir, ".sequence");
|
|
13442
14125
|
try {
|
|
13443
14126
|
await unlink3(legacySequencePath);
|
|
13444
14127
|
} catch {
|
|
13445
14128
|
}
|
|
13446
|
-
const legacySequenceJsonPath =
|
|
14129
|
+
const legacySequenceJsonPath = join25(cleoDir, ".sequence.json");
|
|
13447
14130
|
try {
|
|
13448
14131
|
await unlink3(legacySequenceJsonPath);
|
|
13449
14132
|
} catch {
|
|
13450
14133
|
}
|
|
13451
|
-
const backupDir =
|
|
13452
|
-
await mkdir5(
|
|
13453
|
-
await mkdir5(
|
|
14134
|
+
const backupDir = join25(cleoDir, "backups");
|
|
14135
|
+
await mkdir5(join25(backupDir, "operational"), { recursive: true });
|
|
14136
|
+
await mkdir5(join25(backupDir, "safety"), { recursive: true });
|
|
13454
14137
|
try {
|
|
13455
14138
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
13456
|
-
await getDb2(
|
|
14139
|
+
await getDb2(join25(cleoDir, ".."));
|
|
13457
14140
|
created.push("tasks.db");
|
|
13458
14141
|
} catch (err) {
|
|
13459
14142
|
created.push(`tasks.db (deferred: ${err instanceof Error ? err.message : String(err)})`);
|
|
13460
14143
|
}
|
|
13461
|
-
const gitignorePath =
|
|
14144
|
+
const gitignorePath = join25(cleoDir, ".gitignore");
|
|
13462
14145
|
if (await fileExists(gitignorePath) && !force) {
|
|
13463
14146
|
skipped.push(".gitignore");
|
|
13464
14147
|
} else {
|
|
@@ -13467,8 +14150,8 @@ async function initCoreFiles(cleoDir, _projectName, force, created, skipped) {
|
|
|
13467
14150
|
}
|
|
13468
14151
|
}
|
|
13469
14152
|
async function initCleoGitRepo(cleoDir, created, warnings) {
|
|
13470
|
-
const cleoGitDir =
|
|
13471
|
-
if (
|
|
14153
|
+
const cleoGitDir = join25(cleoDir, ".git");
|
|
14154
|
+
if (existsSync23(cleoGitDir)) {
|
|
13472
14155
|
return;
|
|
13473
14156
|
}
|
|
13474
14157
|
const gitEnv = {
|
|
@@ -13486,11 +14169,11 @@ async function initCleoGitRepo(cleoDir, created, warnings) {
|
|
|
13486
14169
|
}
|
|
13487
14170
|
}
|
|
13488
14171
|
async function initSchemas(cleoDir, force, created, warnings) {
|
|
13489
|
-
const schemasDir =
|
|
14172
|
+
const schemasDir = join25(cleoDir, "schemas");
|
|
13490
14173
|
await mkdir5(schemasDir, { recursive: true });
|
|
13491
14174
|
const packageRoot = getPackageRoot();
|
|
13492
|
-
const sourceSchemaDir =
|
|
13493
|
-
if (!
|
|
14175
|
+
const sourceSchemaDir = join25(packageRoot, "schemas");
|
|
14176
|
+
if (!existsSync23(sourceSchemaDir)) {
|
|
13494
14177
|
warnings.push("schemas/ directory not found in package root, skipping schema installation");
|
|
13495
14178
|
return;
|
|
13496
14179
|
}
|
|
@@ -13501,9 +14184,9 @@ async function initSchemas(cleoDir, force, created, warnings) {
|
|
|
13501
14184
|
];
|
|
13502
14185
|
let copiedCount = 0;
|
|
13503
14186
|
for (const schemaFile of coreSchemas) {
|
|
13504
|
-
const sourcePath =
|
|
13505
|
-
const destPath =
|
|
13506
|
-
if (!
|
|
14187
|
+
const sourcePath = join25(sourceSchemaDir, schemaFile);
|
|
14188
|
+
const destPath = join25(schemasDir, schemaFile);
|
|
14189
|
+
if (!existsSync23(sourcePath)) {
|
|
13507
14190
|
continue;
|
|
13508
14191
|
}
|
|
13509
14192
|
if (await fileExists(destPath) && !force) {
|
|
@@ -13521,27 +14204,27 @@ async function initSchemas(cleoDir, force, created, warnings) {
|
|
|
13521
14204
|
}
|
|
13522
14205
|
}
|
|
13523
14206
|
async function initGitHooks(projRoot, force, created, warnings) {
|
|
13524
|
-
const gitHooksDir =
|
|
13525
|
-
if (!
|
|
14207
|
+
const gitHooksDir = join25(projRoot, ".git", "hooks");
|
|
14208
|
+
if (!existsSync23(join25(projRoot, ".git"))) {
|
|
13526
14209
|
warnings.push("No .git/ directory found, skipping git hook installation");
|
|
13527
14210
|
return;
|
|
13528
14211
|
}
|
|
13529
14212
|
await mkdir5(gitHooksDir, { recursive: true });
|
|
13530
14213
|
const packageRoot = getPackageRoot();
|
|
13531
|
-
const sourceDir =
|
|
13532
|
-
if (!
|
|
14214
|
+
const sourceDir = join25(packageRoot, "templates", "git-hooks");
|
|
14215
|
+
if (!existsSync23(sourceDir)) {
|
|
13533
14216
|
warnings.push("templates/git-hooks/ not found in package root, skipping git hook installation");
|
|
13534
14217
|
return;
|
|
13535
14218
|
}
|
|
13536
14219
|
const hooks = ["commit-msg", "pre-commit"];
|
|
13537
14220
|
let installedCount = 0;
|
|
13538
14221
|
for (const hook of hooks) {
|
|
13539
|
-
const sourcePath =
|
|
13540
|
-
const destPath =
|
|
13541
|
-
if (!
|
|
14222
|
+
const sourcePath = join25(sourceDir, hook);
|
|
14223
|
+
const destPath = join25(gitHooksDir, hook);
|
|
14224
|
+
if (!existsSync23(sourcePath)) {
|
|
13542
14225
|
continue;
|
|
13543
14226
|
}
|
|
13544
|
-
if (
|
|
14227
|
+
if (existsSync23(destPath) && !force) {
|
|
13545
14228
|
continue;
|
|
13546
14229
|
}
|
|
13547
14230
|
try {
|
|
@@ -13557,7 +14240,7 @@ async function initGitHooks(projRoot, force, created, warnings) {
|
|
|
13557
14240
|
}
|
|
13558
14241
|
}
|
|
13559
14242
|
async function initProjectInfo(cleoDir, projectRoot, force, created, skipped) {
|
|
13560
|
-
const projectInfoPath =
|
|
14243
|
+
const projectInfoPath = join25(cleoDir, "project-info.json");
|
|
13561
14244
|
if (await fileExists(projectInfoPath) && !force) {
|
|
13562
14245
|
skipped.push("project-info.json");
|
|
13563
14246
|
return;
|
|
@@ -13606,40 +14289,40 @@ async function initInjection(projectRoot, created, warnings) {
|
|
|
13606
14289
|
return;
|
|
13607
14290
|
}
|
|
13608
14291
|
for (const provider of providers) {
|
|
13609
|
-
const instructFile =
|
|
14292
|
+
const instructFile = join25(projectRoot, provider.pathProject, provider.instructFile);
|
|
13610
14293
|
await stripCLEOBlocks(instructFile);
|
|
13611
14294
|
}
|
|
13612
|
-
await stripCLEOBlocks(
|
|
14295
|
+
await stripCLEOBlocks(join25(projectRoot, "AGENTS.md"));
|
|
13613
14296
|
const injectionContent = buildInjectionContent2({ references: ["@AGENTS.md"] });
|
|
13614
14297
|
const results = await injectAll2(providers, projectRoot, "project", injectionContent);
|
|
13615
14298
|
const injected = [];
|
|
13616
14299
|
for (const [filePath, action] of results) {
|
|
13617
|
-
const fileName =
|
|
14300
|
+
const fileName = basename5(filePath);
|
|
13618
14301
|
injected.push(`${fileName} (${action})`);
|
|
13619
14302
|
}
|
|
13620
14303
|
if (injected.length > 0) {
|
|
13621
14304
|
created.push(`injection: ${injected.join(", ")}`);
|
|
13622
14305
|
}
|
|
13623
|
-
const agentsMdPath =
|
|
14306
|
+
const agentsMdPath = join25(projectRoot, "AGENTS.md");
|
|
13624
14307
|
const agentsMdLines = ["@~/.cleo/templates/CLEO-INJECTION.md"];
|
|
13625
|
-
const projectContextPath =
|
|
13626
|
-
if (
|
|
14308
|
+
const projectContextPath = join25(projectRoot, ".cleo", "project-context.json");
|
|
14309
|
+
if (existsSync23(projectContextPath)) {
|
|
13627
14310
|
agentsMdLines.push("@.cleo/project-context.json");
|
|
13628
14311
|
}
|
|
13629
14312
|
const agentsAction = await inject(agentsMdPath, agentsMdLines.join("\n"));
|
|
13630
14313
|
created.push(`AGENTS.md CLEO content (${agentsAction})`);
|
|
13631
14314
|
const content = getInjectionTemplateContent();
|
|
13632
14315
|
if (content) {
|
|
13633
|
-
const globalTemplatesDir =
|
|
14316
|
+
const globalTemplatesDir = join25(getCleoHome(), "templates");
|
|
13634
14317
|
await mkdir5(globalTemplatesDir, { recursive: true });
|
|
13635
|
-
const globalPath =
|
|
13636
|
-
if (!
|
|
14318
|
+
const globalPath = join25(globalTemplatesDir, "CLEO-INJECTION.md");
|
|
14319
|
+
if (!existsSync23(globalPath)) {
|
|
13637
14320
|
await writeFile3(globalPath, content);
|
|
13638
14321
|
}
|
|
13639
14322
|
}
|
|
13640
14323
|
try {
|
|
13641
|
-
const globalAgentsDir =
|
|
13642
|
-
const globalAgentsMd =
|
|
14324
|
+
const globalAgentsDir = join25(homedir4(), ".agents");
|
|
14325
|
+
const globalAgentsMd = join25(globalAgentsDir, "AGENTS.md");
|
|
13643
14326
|
await mkdir5(globalAgentsDir, { recursive: true });
|
|
13644
14327
|
await inject(globalAgentsMd, "@~/.cleo/templates/CLEO-INJECTION.md");
|
|
13645
14328
|
} catch {
|
|
@@ -13650,13 +14333,13 @@ async function initInjection(projectRoot, created, warnings) {
|
|
|
13650
14333
|
}
|
|
13651
14334
|
async function initAgentDefinition(created, warnings) {
|
|
13652
14335
|
const packageRoot = getPackageRoot();
|
|
13653
|
-
const agentSourceDir =
|
|
13654
|
-
if (!
|
|
14336
|
+
const agentSourceDir = join25(packageRoot, "agents", "cleo-subagent");
|
|
14337
|
+
if (!existsSync23(agentSourceDir)) {
|
|
13655
14338
|
warnings.push("agents/cleo-subagent/ not found in package, skipping agent definition install");
|
|
13656
14339
|
return;
|
|
13657
14340
|
}
|
|
13658
|
-
const globalAgentsDir =
|
|
13659
|
-
await mkdir5(
|
|
14341
|
+
const globalAgentsDir = join25(homedir4(), ".agents", "agents", "cleo-subagent");
|
|
14342
|
+
await mkdir5(dirname7(globalAgentsDir), { recursive: true });
|
|
13660
14343
|
try {
|
|
13661
14344
|
try {
|
|
13662
14345
|
const stat2 = await lstat(globalAgentsDir);
|
|
@@ -13672,7 +14355,7 @@ async function initAgentDefinition(created, warnings) {
|
|
|
13672
14355
|
await mkdir5(globalAgentsDir, { recursive: true });
|
|
13673
14356
|
const files = readdirSync7(agentSourceDir);
|
|
13674
14357
|
for (const file of files) {
|
|
13675
|
-
await copyFile2(
|
|
14358
|
+
await copyFile2(join25(agentSourceDir, file), join25(globalAgentsDir, file));
|
|
13676
14359
|
}
|
|
13677
14360
|
created.push("agent: cleo-subagent (copied)");
|
|
13678
14361
|
} catch (copyErr) {
|
|
@@ -13724,12 +14407,12 @@ async function initCoreSkills(created, warnings) {
|
|
|
13724
14407
|
let ctSkillsRoot = null;
|
|
13725
14408
|
try {
|
|
13726
14409
|
const packageRoot = getPackageRoot();
|
|
13727
|
-
const bundledPath =
|
|
13728
|
-
if (
|
|
14410
|
+
const bundledPath = join25(packageRoot, "packages", "ct-skills");
|
|
14411
|
+
if (existsSync23(join25(bundledPath, "skills.json"))) {
|
|
13729
14412
|
ctSkillsRoot = bundledPath;
|
|
13730
14413
|
} else {
|
|
13731
|
-
const ctSkillsPath =
|
|
13732
|
-
if (
|
|
14414
|
+
const ctSkillsPath = join25(packageRoot, "node_modules", "@cleocode", "ct-skills");
|
|
14415
|
+
if (existsSync23(join25(ctSkillsPath, "skills.json"))) {
|
|
13733
14416
|
ctSkillsRoot = ctSkillsPath;
|
|
13734
14417
|
}
|
|
13735
14418
|
}
|
|
@@ -13744,14 +14427,14 @@ async function initCoreSkills(created, warnings) {
|
|
|
13744
14427
|
} catch {
|
|
13745
14428
|
warnings.push("Failed to register skill library with CAAMP");
|
|
13746
14429
|
}
|
|
13747
|
-
const catalogPath =
|
|
13748
|
-
const catalog2 = JSON.parse(
|
|
14430
|
+
const catalogPath = join25(ctSkillsRoot, "skills.json");
|
|
14431
|
+
const catalog2 = JSON.parse(readFileSync16(catalogPath, "utf-8"));
|
|
13749
14432
|
const skills = catalog2.skills ?? [];
|
|
13750
14433
|
const coreSkills = skills.filter((s) => s.tier <= 2);
|
|
13751
14434
|
const installed = [];
|
|
13752
14435
|
for (const skill of coreSkills) {
|
|
13753
|
-
const skillSourceDir =
|
|
13754
|
-
if (!
|
|
14436
|
+
const skillSourceDir = dirname7(join25(ctSkillsRoot, skill.path));
|
|
14437
|
+
if (!existsSync23(skillSourceDir)) {
|
|
13755
14438
|
continue;
|
|
13756
14439
|
}
|
|
13757
14440
|
try {
|
|
@@ -13786,7 +14469,7 @@ async function initProjectDetect(cleoDir, projectRoot, created, warnings) {
|
|
|
13786
14469
|
try {
|
|
13787
14470
|
const { detectProjectType: detectProjectType2 } = await Promise.resolve().then(() => (init_project_detect(), project_detect_exports));
|
|
13788
14471
|
const info = detectProjectType2(projectRoot);
|
|
13789
|
-
const contextPath =
|
|
14472
|
+
const contextPath = join25(cleoDir, "project-context.json");
|
|
13790
14473
|
const context = {
|
|
13791
14474
|
...info,
|
|
13792
14475
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -13859,8 +14542,8 @@ async function initProject(opts = {}) {
|
|
|
13859
14542
|
async function getVersion(projectRoot) {
|
|
13860
14543
|
const root = projectRoot ?? getProjectRoot();
|
|
13861
14544
|
const versionPaths = [
|
|
13862
|
-
|
|
13863
|
-
|
|
14545
|
+
join25(root, "VERSION"),
|
|
14546
|
+
join25(root, "..", "VERSION")
|
|
13864
14547
|
];
|
|
13865
14548
|
for (const versionPath of versionPaths) {
|
|
13866
14549
|
try {
|
|
@@ -13872,7 +14555,7 @@ async function getVersion(projectRoot) {
|
|
|
13872
14555
|
} catch {
|
|
13873
14556
|
}
|
|
13874
14557
|
}
|
|
13875
|
-
const pkg = await readJson(
|
|
14558
|
+
const pkg = await readJson(join25(root, "package.json"));
|
|
13876
14559
|
if (pkg?.version) {
|
|
13877
14560
|
return { version: pkg.version };
|
|
13878
14561
|
}
|
|
@@ -13915,8 +14598,8 @@ async function getVersion2(projectRoot) {
|
|
|
13915
14598
|
init_paths();
|
|
13916
14599
|
init_file_utils();
|
|
13917
14600
|
init_schema();
|
|
13918
|
-
import { existsSync as
|
|
13919
|
-
import { join as
|
|
14601
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "node:fs";
|
|
14602
|
+
import { join as join26 } from "node:path";
|
|
13920
14603
|
|
|
13921
14604
|
// src/core/lifecycle/stages.ts
|
|
13922
14605
|
var PIPELINE_STAGES = [
|
|
@@ -14062,7 +14745,7 @@ var DEFAULT_LIFECYCLE_DATA_DIR = "rcasd";
|
|
|
14062
14745
|
function resolveLifecycleDir(epicId, cwd) {
|
|
14063
14746
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
14064
14747
|
for (const dirName of LIFECYCLE_DATA_DIRS) {
|
|
14065
|
-
if (
|
|
14748
|
+
if (existsSync24(join26(cleoDir, dirName, epicId))) {
|
|
14066
14749
|
return dirName;
|
|
14067
14750
|
}
|
|
14068
14751
|
}
|
|
@@ -14071,18 +14754,18 @@ function resolveLifecycleDir(epicId, cwd) {
|
|
|
14071
14754
|
function getRcsdDir(epicId, cwd) {
|
|
14072
14755
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
14073
14756
|
const dirName = resolveLifecycleDir(epicId, cwd);
|
|
14074
|
-
return
|
|
14757
|
+
return join26(cleoDir, dirName, epicId);
|
|
14075
14758
|
}
|
|
14076
14759
|
function getRcsdManifestPath(epicId, cwd) {
|
|
14077
|
-
return
|
|
14760
|
+
return join26(getRcsdDir(epicId, cwd), "_manifest.json");
|
|
14078
14761
|
}
|
|
14079
14762
|
function readManifestSync(epicId, cwd) {
|
|
14080
|
-
return
|
|
14763
|
+
return readJsonFile(getRcsdManifestPath(epicId, cwd));
|
|
14081
14764
|
}
|
|
14082
14765
|
function writeManifestSync(epicId, manifest, cwd) {
|
|
14083
14766
|
const dir = getRcsdDir(epicId, cwd);
|
|
14084
|
-
if (!
|
|
14085
|
-
|
|
14767
|
+
if (!existsSync24(dir)) {
|
|
14768
|
+
mkdirSync8(dir, { recursive: true });
|
|
14086
14769
|
}
|
|
14087
14770
|
writeJsonFileAtomic(getRcsdManifestPath(epicId, cwd), manifest);
|
|
14088
14771
|
}
|
|
@@ -14536,16 +15219,16 @@ init_platform();
|
|
|
14536
15219
|
// src/core/validation/validate-ops.ts
|
|
14537
15220
|
init_paths();
|
|
14538
15221
|
init_data_accessor();
|
|
14539
|
-
import { readFileSync as
|
|
15222
|
+
import { readFileSync as readFileSync18, existsSync as existsSync26, appendFileSync as appendFileSync3, mkdirSync as mkdirSync9 } from "node:fs";
|
|
14540
15223
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
14541
|
-
import { join as
|
|
15224
|
+
import { join as join28, dirname as dirname8, resolve as resolve6 } from "node:path";
|
|
14542
15225
|
|
|
14543
|
-
// src/
|
|
15226
|
+
// src/core/validation/schema-validator.ts
|
|
14544
15227
|
init_validation_schemas();
|
|
14545
15228
|
import AjvModule2 from "ajv";
|
|
14546
15229
|
import addFormatsModule2 from "ajv-formats";
|
|
14547
|
-
import { readFileSync as
|
|
14548
|
-
import { join as
|
|
15230
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
15231
|
+
import { join as join27 } from "path";
|
|
14549
15232
|
var Ajv2 = AjvModule2.default || AjvModule2;
|
|
14550
15233
|
var addFormats2 = addFormatsModule2.default || addFormatsModule2;
|
|
14551
15234
|
var schemaCache2 = /* @__PURE__ */ new Map();
|
|
@@ -14569,14 +15252,14 @@ function resolveSchemaPath2(schemaType) {
|
|
|
14569
15252
|
const filename = `${schemaType}.schema.json`;
|
|
14570
15253
|
const projectRoot = process.env.CLEO_ROOT || process.cwd();
|
|
14571
15254
|
const paths = [
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
// relative from dist/
|
|
14575
|
-
|
|
14576
|
-
// relative from dist/
|
|
15255
|
+
join27(projectRoot, "schemas", filename),
|
|
15256
|
+
join27(__dirname, "..", "..", "..", "schemas", filename),
|
|
15257
|
+
// relative from dist/core/validation/
|
|
15258
|
+
join27(__dirname, "..", "..", "schemas", filename)
|
|
15259
|
+
// relative from dist/core/
|
|
14577
15260
|
];
|
|
14578
15261
|
for (const p of paths) {
|
|
14579
|
-
if (
|
|
15262
|
+
if (existsSync25(p)) {
|
|
14580
15263
|
return p;
|
|
14581
15264
|
}
|
|
14582
15265
|
}
|
|
@@ -14592,7 +15275,7 @@ function getValidator(schemaType) {
|
|
|
14592
15275
|
return null;
|
|
14593
15276
|
}
|
|
14594
15277
|
try {
|
|
14595
|
-
const schemaContent =
|
|
15278
|
+
const schemaContent = readFileSync17(schemaPath, "utf-8");
|
|
14596
15279
|
const schema = JSON.parse(schemaContent);
|
|
14597
15280
|
const ajv = getAjv2();
|
|
14598
15281
|
const validate = ajv.compile(schema);
|
|
@@ -14630,7 +15313,7 @@ function validateSchema(schemaType, data) {
|
|
|
14630
15313
|
return { valid: false, errors };
|
|
14631
15314
|
}
|
|
14632
15315
|
|
|
14633
|
-
// src/
|
|
15316
|
+
// src/core/validation/validation-rules.ts
|
|
14634
15317
|
function validateTitleDescription(title, description) {
|
|
14635
15318
|
const violations = [];
|
|
14636
15319
|
if (!title || title.trim().length === 0) {
|
|
@@ -14764,35 +15447,6 @@ function hasErrors(violations) {
|
|
|
14764
15447
|
return violations.some((v) => v.severity === "error");
|
|
14765
15448
|
}
|
|
14766
15449
|
|
|
14767
|
-
// src/core/tasks/dependency-check.ts
|
|
14768
|
-
function detectCircularDeps(taskId, tasks2) {
|
|
14769
|
-
const taskMap = new Map(tasks2.map((t) => [t.id, t]));
|
|
14770
|
-
const visited = /* @__PURE__ */ new Set();
|
|
14771
|
-
const recursionStack = /* @__PURE__ */ new Set();
|
|
14772
|
-
const path = [];
|
|
14773
|
-
function dfs(id) {
|
|
14774
|
-
visited.add(id);
|
|
14775
|
-
recursionStack.add(id);
|
|
14776
|
-
path.push(id);
|
|
14777
|
-
const task = taskMap.get(id);
|
|
14778
|
-
if (task?.depends) {
|
|
14779
|
-
for (const depId of task.depends) {
|
|
14780
|
-
if (!visited.has(depId)) {
|
|
14781
|
-
const cycle = dfs(depId);
|
|
14782
|
-
if (cycle.length > 0) return cycle;
|
|
14783
|
-
} else if (recursionStack.has(depId)) {
|
|
14784
|
-
const cycleStart = path.indexOf(depId);
|
|
14785
|
-
return [...path.slice(cycleStart), depId];
|
|
14786
|
-
}
|
|
14787
|
-
}
|
|
14788
|
-
}
|
|
14789
|
-
path.pop();
|
|
14790
|
-
recursionStack.delete(id);
|
|
14791
|
-
return [];
|
|
14792
|
-
}
|
|
14793
|
-
return dfs(taskId);
|
|
14794
|
-
}
|
|
14795
|
-
|
|
14796
15450
|
// src/core/validation/validate-ops.ts
|
|
14797
15451
|
init_json();
|
|
14798
15452
|
init_status_registry();
|
|
@@ -14801,9 +15455,9 @@ init_status_registry();
|
|
|
14801
15455
|
import { isNull as isNull2, sql as sql3 } from "drizzle-orm";
|
|
14802
15456
|
|
|
14803
15457
|
// src/core/validation/validate-ops.ts
|
|
14804
|
-
function
|
|
15458
|
+
function readJsonFile2(filePath) {
|
|
14805
15459
|
try {
|
|
14806
|
-
const raw =
|
|
15460
|
+
const raw = readFileSync18(filePath, "utf-8");
|
|
14807
15461
|
return JSON.parse(raw);
|
|
14808
15462
|
} catch {
|
|
14809
15463
|
return null;
|
|
@@ -14818,11 +15472,11 @@ async function coreValidateSchema(type, data, projectRoot) {
|
|
|
14818
15472
|
throw new Error(`Unknown schema type: ${type}. Valid types: ${validTypes.join(", ")}`);
|
|
14819
15473
|
}
|
|
14820
15474
|
if (type === "config") {
|
|
14821
|
-
const filePath =
|
|
14822
|
-
if (!
|
|
15475
|
+
const filePath = join28(projectRoot, ".cleo", "config.json");
|
|
15476
|
+
if (!existsSync26(filePath)) {
|
|
14823
15477
|
throw new Error("File not found: .cleo/config.json");
|
|
14824
15478
|
}
|
|
14825
|
-
const configData = data ??
|
|
15479
|
+
const configData = data ?? readJsonFile2(filePath);
|
|
14826
15480
|
const result = validateSchema("config", configData);
|
|
14827
15481
|
return { type, valid: result.valid, errors: result.errors, errorCount: result.errors.length };
|
|
14828
15482
|
}
|
|
@@ -14956,7 +15610,7 @@ async function coreValidateProtocol(taskId, protocolType, projectRoot) {
|
|
|
14956
15610
|
}
|
|
14957
15611
|
function coreValidateManifest(projectRoot) {
|
|
14958
15612
|
const manifestPath = getManifestPath(projectRoot);
|
|
14959
|
-
if (!
|
|
15613
|
+
if (!existsSync26(manifestPath)) {
|
|
14960
15614
|
return {
|
|
14961
15615
|
valid: true,
|
|
14962
15616
|
totalEntries: 0,
|
|
@@ -14966,7 +15620,7 @@ function coreValidateManifest(projectRoot) {
|
|
|
14966
15620
|
message: "No manifest file found"
|
|
14967
15621
|
};
|
|
14968
15622
|
}
|
|
14969
|
-
const content =
|
|
15623
|
+
const content = readFileSync18(manifestPath, "utf-8");
|
|
14970
15624
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
14971
15625
|
let validCount = 0;
|
|
14972
15626
|
let invalidCount = 0;
|
|
@@ -15006,11 +15660,11 @@ function coreValidateOutput(filePath, taskId, projectRoot) {
|
|
|
15006
15660
|
if (!filePath) {
|
|
15007
15661
|
throw new Error("filePath is required");
|
|
15008
15662
|
}
|
|
15009
|
-
const fullPath =
|
|
15010
|
-
if (!
|
|
15663
|
+
const fullPath = resolve6(projectRoot, filePath);
|
|
15664
|
+
if (!existsSync26(fullPath)) {
|
|
15011
15665
|
throw new Error(`Output file not found: ${filePath}`);
|
|
15012
15666
|
}
|
|
15013
|
-
const content =
|
|
15667
|
+
const content = readFileSync18(fullPath, "utf-8");
|
|
15014
15668
|
const issues = [];
|
|
15015
15669
|
if (!content.includes("# ")) {
|
|
15016
15670
|
issues.push({ code: "O_MISSING_TITLE", message: "Output file should have a markdown title", severity: "warning" });
|
|
@@ -15030,11 +15684,11 @@ function coreValidateOutput(filePath, taskId, projectRoot) {
|
|
|
15030
15684
|
};
|
|
15031
15685
|
}
|
|
15032
15686
|
function parseComplianceEntries(projectRoot) {
|
|
15033
|
-
const compliancePath =
|
|
15034
|
-
if (!
|
|
15687
|
+
const compliancePath = join28(projectRoot, ".cleo", "metrics", "COMPLIANCE.jsonl");
|
|
15688
|
+
if (!existsSync26(compliancePath)) {
|
|
15035
15689
|
return [];
|
|
15036
15690
|
}
|
|
15037
|
-
const content =
|
|
15691
|
+
const content = readFileSync18(compliancePath, "utf-8");
|
|
15038
15692
|
const entries = [];
|
|
15039
15693
|
for (const line of content.split("\n")) {
|
|
15040
15694
|
const trimmed = line.trim();
|
|
@@ -15094,10 +15748,10 @@ function coreComplianceRecord(taskId, result, protocol, violations, projectRoot)
|
|
|
15094
15748
|
if (!validResults.includes(result)) {
|
|
15095
15749
|
throw new Error(`Invalid result: ${result}. Valid: ${validResults.join(", ")}`);
|
|
15096
15750
|
}
|
|
15097
|
-
const compliancePath =
|
|
15098
|
-
const dir =
|
|
15099
|
-
if (!
|
|
15100
|
-
|
|
15751
|
+
const compliancePath = join28(projectRoot, ".cleo", "metrics", "COMPLIANCE.jsonl");
|
|
15752
|
+
const dir = dirname8(compliancePath);
|
|
15753
|
+
if (!existsSync26(dir)) {
|
|
15754
|
+
mkdirSync9(dir, { recursive: true });
|
|
15101
15755
|
}
|
|
15102
15756
|
const entry = {
|
|
15103
15757
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15116,10 +15770,10 @@ function coreComplianceRecord(taskId, result, protocol, violations, projectRoot)
|
|
|
15116
15770
|
};
|
|
15117
15771
|
}
|
|
15118
15772
|
function coreTestStatus(projectRoot) {
|
|
15119
|
-
const testDir =
|
|
15120
|
-
const mcpTestDir =
|
|
15121
|
-
const hasBatsTests =
|
|
15122
|
-
const hasMcpTests =
|
|
15773
|
+
const testDir = join28(projectRoot, "tests");
|
|
15774
|
+
const mcpTestDir = join28(projectRoot, "src", "mcp", "__tests__");
|
|
15775
|
+
const hasBatsTests = existsSync26(testDir);
|
|
15776
|
+
const hasMcpTests = existsSync26(mcpTestDir);
|
|
15123
15777
|
return {
|
|
15124
15778
|
batsTests: {
|
|
15125
15779
|
available: hasBatsTests,
|
|
@@ -15261,8 +15915,8 @@ async function coreCoherenceCheck(projectRoot) {
|
|
|
15261
15915
|
};
|
|
15262
15916
|
}
|
|
15263
15917
|
function coreTestRun(params, projectRoot) {
|
|
15264
|
-
const hasVitest =
|
|
15265
|
-
const hasBats =
|
|
15918
|
+
const hasVitest = existsSync26(join28(projectRoot, "node_modules", ".bin", "vitest"));
|
|
15919
|
+
const hasBats = existsSync26(join28(projectRoot, "tests"));
|
|
15266
15920
|
if (!hasVitest && !hasBats) {
|
|
15267
15921
|
return {
|
|
15268
15922
|
ran: false,
|
|
@@ -15308,14 +15962,14 @@ function coreTestRun(params, projectRoot) {
|
|
|
15308
15962
|
}
|
|
15309
15963
|
}
|
|
15310
15964
|
function coreTestCoverage(projectRoot) {
|
|
15311
|
-
const coveragePath =
|
|
15312
|
-
if (!
|
|
15965
|
+
const coveragePath = join28(projectRoot, "coverage", "coverage-summary.json");
|
|
15966
|
+
if (!existsSync26(coveragePath)) {
|
|
15313
15967
|
return {
|
|
15314
15968
|
available: false,
|
|
15315
15969
|
message: "No coverage data found. Run tests with coverage first."
|
|
15316
15970
|
};
|
|
15317
15971
|
}
|
|
15318
|
-
const coverageData =
|
|
15972
|
+
const coverageData = readJsonFile2(coveragePath);
|
|
15319
15973
|
if (!coverageData) {
|
|
15320
15974
|
throw new Error("Failed to read coverage data");
|
|
15321
15975
|
}
|
|
@@ -15447,7 +16101,7 @@ function validateTestCoverage(projectRoot) {
|
|
|
15447
16101
|
init_platform();
|
|
15448
16102
|
init_paths();
|
|
15449
16103
|
init_data_accessor();
|
|
15450
|
-
import { readFileSync as
|
|
16104
|
+
import { readFileSync as readFileSync21, existsSync as existsSync29 } from "node:fs";
|
|
15451
16105
|
|
|
15452
16106
|
// src/core/orchestration/index.ts
|
|
15453
16107
|
init_json();
|
|
@@ -15576,7 +16230,7 @@ async function analyzeEpic(epicId, cwd, accessor) {
|
|
|
15576
16230
|
completedTasks
|
|
15577
16231
|
};
|
|
15578
16232
|
}
|
|
15579
|
-
async function
|
|
16233
|
+
async function getReadyTasks2(epicId, cwd, accessor) {
|
|
15580
16234
|
const data = accessor ? await accessor.loadTaskFile() : await readJsonRequired(getTaskPath(cwd));
|
|
15581
16235
|
const childTasks = data.tasks.filter((t) => t.parentId === epicId);
|
|
15582
16236
|
const completedIds = new Set(
|
|
@@ -15596,7 +16250,7 @@ async function getReadyTasks(epicId, cwd, accessor) {
|
|
|
15596
16250
|
});
|
|
15597
16251
|
}
|
|
15598
16252
|
async function getNextTask(epicId, cwd, accessor) {
|
|
15599
|
-
const readyTasks = await
|
|
16253
|
+
const readyTasks = await getReadyTasks2(epicId, cwd, accessor);
|
|
15600
16254
|
const ready = readyTasks.filter((t) => t.ready);
|
|
15601
16255
|
if (ready.length === 0) return null;
|
|
15602
16256
|
return ready[0];
|
|
@@ -15957,27 +16611,27 @@ async function getUnblockOpportunities(cwd, accessor) {
|
|
|
15957
16611
|
// src/core/orchestration/parallel.ts
|
|
15958
16612
|
init_errors();
|
|
15959
16613
|
init_exit_codes();
|
|
15960
|
-
import { readFileSync as
|
|
15961
|
-
import { join as
|
|
16614
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync6, existsSync as existsSync27, mkdirSync as mkdirSync10 } from "node:fs";
|
|
16615
|
+
import { join as join29, dirname as dirname9 } from "node:path";
|
|
15962
16616
|
init_json();
|
|
15963
16617
|
init_paths();
|
|
15964
16618
|
function getParallelStatePath(projectRoot) {
|
|
15965
|
-
return
|
|
16619
|
+
return join29(projectRoot, ".cleo", "parallel-state.json");
|
|
15966
16620
|
}
|
|
15967
16621
|
function readParallelState(projectRoot) {
|
|
15968
16622
|
const statePath = getParallelStatePath(projectRoot);
|
|
15969
|
-
if (!
|
|
16623
|
+
if (!existsSync27(statePath)) return { active: false };
|
|
15970
16624
|
try {
|
|
15971
|
-
return JSON.parse(
|
|
16625
|
+
return JSON.parse(readFileSync19(statePath, "utf-8"));
|
|
15972
16626
|
} catch {
|
|
15973
16627
|
return { active: false };
|
|
15974
16628
|
}
|
|
15975
16629
|
}
|
|
15976
16630
|
function writeParallelState(state, projectRoot) {
|
|
15977
16631
|
const statePath = getParallelStatePath(projectRoot);
|
|
15978
|
-
const dir =
|
|
15979
|
-
if (!
|
|
15980
|
-
|
|
16632
|
+
const dir = dirname9(statePath);
|
|
16633
|
+
if (!existsSync27(dir)) mkdirSync10(dir, { recursive: true });
|
|
16634
|
+
writeFileSync6(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
15981
16635
|
}
|
|
15982
16636
|
async function startParallelExecution(epicId, wave, cwd, accessor) {
|
|
15983
16637
|
const projectRoot = cwd ?? process.cwd();
|
|
@@ -16112,8 +16766,8 @@ async function validateSpawnReadiness(taskId, cwd, accessor) {
|
|
|
16112
16766
|
// src/core/orchestration/bootstrap.ts
|
|
16113
16767
|
init_json();
|
|
16114
16768
|
init_paths();
|
|
16115
|
-
import { readFileSync as
|
|
16116
|
-
import { join as
|
|
16769
|
+
import { readFileSync as readFileSync20, existsSync as existsSync28 } from "node:fs";
|
|
16770
|
+
import { join as join30 } from "node:path";
|
|
16117
16771
|
async function buildBrainState(projectRoot, opts, accessor) {
|
|
16118
16772
|
const speed = opts?.speed || "fast";
|
|
16119
16773
|
const brain = {
|
|
@@ -16125,8 +16779,8 @@ async function buildBrainState(projectRoot, opts, accessor) {
|
|
|
16125
16779
|
};
|
|
16126
16780
|
try {
|
|
16127
16781
|
const sessionsPath = getSessionsPath(projectRoot);
|
|
16128
|
-
if (
|
|
16129
|
-
const sessionsData = JSON.parse(
|
|
16782
|
+
if (existsSync28(sessionsPath)) {
|
|
16783
|
+
const sessionsData = JSON.parse(readFileSync20(sessionsPath, "utf-8"));
|
|
16130
16784
|
const activeSession = (sessionsData.sessions ?? []).find(
|
|
16131
16785
|
(s) => s.status === "active"
|
|
16132
16786
|
);
|
|
@@ -16181,9 +16835,9 @@ async function buildBrainState(projectRoot, opts, accessor) {
|
|
|
16181
16835
|
blockedBy: b.depends || []
|
|
16182
16836
|
}));
|
|
16183
16837
|
try {
|
|
16184
|
-
const decisionLogPath =
|
|
16185
|
-
if (
|
|
16186
|
-
const content =
|
|
16838
|
+
const decisionLogPath = join30(projectRoot, ".cleo", "decision-log.jsonl");
|
|
16839
|
+
if (existsSync28(decisionLogPath)) {
|
|
16840
|
+
const content = readFileSync20(decisionLogPath, "utf-8").trim();
|
|
16187
16841
|
if (content) {
|
|
16188
16842
|
const entries = content.split("\n").filter((l) => l.trim()).map((l) => {
|
|
16189
16843
|
try {
|
|
@@ -16203,9 +16857,9 @@ async function buildBrainState(projectRoot, opts, accessor) {
|
|
|
16203
16857
|
} catch {
|
|
16204
16858
|
}
|
|
16205
16859
|
try {
|
|
16206
|
-
const contextStatePath =
|
|
16207
|
-
if (
|
|
16208
|
-
const state = JSON.parse(
|
|
16860
|
+
const contextStatePath = join30(projectRoot, ".cleo", ".context-state.json");
|
|
16861
|
+
if (existsSync28(contextStatePath)) {
|
|
16862
|
+
const state = JSON.parse(readFileSync20(contextStatePath, "utf-8"));
|
|
16209
16863
|
const percentage = state.contextWindow?.percentage ?? 0;
|
|
16210
16864
|
const factors = [];
|
|
16211
16865
|
if (percentage > 80) factors.push("high_context_usage");
|
|
@@ -16354,7 +17008,7 @@ async function orchestrateReady(epicId, projectRoot) {
|
|
|
16354
17008
|
try {
|
|
16355
17009
|
const root = projectRoot || resolveProjectRoot();
|
|
16356
17010
|
const accessor = await getAccessor(root);
|
|
16357
|
-
const readyTasks = await
|
|
17011
|
+
const readyTasks = await getReadyTasks2(epicId, root, accessor);
|
|
16358
17012
|
const ready = readyTasks.filter((t) => t.ready);
|
|
16359
17013
|
return {
|
|
16360
17014
|
success: true,
|
|
@@ -16393,7 +17047,7 @@ async function orchestrateNext(epicId, projectRoot) {
|
|
|
16393
17047
|
}
|
|
16394
17048
|
};
|
|
16395
17049
|
}
|
|
16396
|
-
const readyTasks = await
|
|
17050
|
+
const readyTasks = await getReadyTasks2(epicId, root, accessor);
|
|
16397
17051
|
const ready = readyTasks.filter((t) => t.ready);
|
|
16398
17052
|
return {
|
|
16399
17053
|
success: true,
|
|
@@ -16433,11 +17087,11 @@ async function orchestrateContext(epicId, projectRoot) {
|
|
|
16433
17087
|
}
|
|
16434
17088
|
const estimatedTokens = taskCount * 100;
|
|
16435
17089
|
const manifestPath = getManifestPath(root);
|
|
16436
|
-
let
|
|
16437
|
-
if (
|
|
17090
|
+
let manifestEntries2 = 0;
|
|
17091
|
+
if (existsSync29(manifestPath)) {
|
|
16438
17092
|
try {
|
|
16439
|
-
const content =
|
|
16440
|
-
|
|
17093
|
+
const content = readFileSync21(manifestPath, "utf-8");
|
|
17094
|
+
manifestEntries2 = content.split("\n").filter((l) => l.trim()).length;
|
|
16441
17095
|
} catch {
|
|
16442
17096
|
}
|
|
16443
17097
|
}
|
|
@@ -16446,7 +17100,7 @@ async function orchestrateContext(epicId, projectRoot) {
|
|
|
16446
17100
|
data: {
|
|
16447
17101
|
epicId: epicId || null,
|
|
16448
17102
|
taskCount,
|
|
16449
|
-
manifestEntries,
|
|
17103
|
+
manifestEntries: manifestEntries2,
|
|
16450
17104
|
estimatedTokens,
|
|
16451
17105
|
recommendation: estimatedTokens > 5e3 ? "Consider using manifest summaries instead of full task details" : "Context usage is within recommended limits",
|
|
16452
17106
|
limits: {
|
|
@@ -16473,7 +17127,7 @@ async function orchestrateValidate(taskId, projectRoot) {
|
|
|
16473
17127
|
return engineError("E_VALIDATION", err.message);
|
|
16474
17128
|
}
|
|
16475
17129
|
}
|
|
16476
|
-
async function orchestrateSpawn(taskId, protocolType, projectRoot) {
|
|
17130
|
+
async function orchestrateSpawn(taskId, protocolType, projectRoot, tier) {
|
|
16477
17131
|
if (!taskId) {
|
|
16478
17132
|
return engineError("E_INVALID_INPUT", "taskId is required");
|
|
16479
17133
|
}
|
|
@@ -16494,9 +17148,11 @@ async function orchestrateSpawn(taskId, protocolType, projectRoot) {
|
|
|
16494
17148
|
spawnContext: {
|
|
16495
17149
|
taskId: spawnContext.taskId,
|
|
16496
17150
|
protocol: spawnContext.protocol,
|
|
16497
|
-
protocolType: protocolType || spawnContext.protocol
|
|
17151
|
+
protocolType: protocolType || spawnContext.protocol,
|
|
17152
|
+
tier: tier ?? null
|
|
16498
17153
|
},
|
|
16499
17154
|
protocolType: protocolType || spawnContext.protocol,
|
|
17155
|
+
tier: tier ?? null,
|
|
16500
17156
|
tokenResolution: spawnContext.tokenResolution
|
|
16501
17157
|
}
|
|
16502
17158
|
};
|
|
@@ -16519,7 +17175,7 @@ async function orchestrateStartup(epicId, projectRoot) {
|
|
|
16519
17175
|
}
|
|
16520
17176
|
const children = tasks2.filter((t) => t.parentId === epicId);
|
|
16521
17177
|
const waves = computeWaves(children);
|
|
16522
|
-
const readyTasks = await
|
|
17178
|
+
const readyTasks = await getReadyTasks2(epicId, root, accessor);
|
|
16523
17179
|
const ready = readyTasks.filter((t) => t.ready);
|
|
16524
17180
|
return {
|
|
16525
17181
|
success: true,
|
|
@@ -16655,8 +17311,8 @@ async function orchestrateCheck(projectRoot) {
|
|
|
16655
17311
|
|
|
16656
17312
|
// src/core/memory/engine-compat.ts
|
|
16657
17313
|
init_paths();
|
|
16658
|
-
import { readFileSync as
|
|
16659
|
-
import { resolve as
|
|
17314
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync9, appendFileSync as appendFileSync6, existsSync as existsSync32, mkdirSync as mkdirSync13 } from "node:fs";
|
|
17315
|
+
import { resolve as resolve7, dirname as dirname10 } from "node:path";
|
|
16660
17316
|
|
|
16661
17317
|
// src/core/memory/index.ts
|
|
16662
17318
|
init_json();
|
|
@@ -16698,25 +17354,25 @@ function filterManifestEntries(entries, filter) {
|
|
|
16698
17354
|
|
|
16699
17355
|
// src/core/memory/patterns.ts
|
|
16700
17356
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
16701
|
-
import { readFileSync as
|
|
16702
|
-
import { join as
|
|
17357
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync7, appendFileSync as appendFileSync4, mkdirSync as mkdirSync11, existsSync as existsSync30 } from "node:fs";
|
|
17358
|
+
import { join as join31 } from "node:path";
|
|
16703
17359
|
function getMemoryDir(projectRoot) {
|
|
16704
|
-
const memDir =
|
|
16705
|
-
if (!
|
|
16706
|
-
|
|
17360
|
+
const memDir = join31(projectRoot, ".cleo", "memory");
|
|
17361
|
+
if (!existsSync30(memDir)) {
|
|
17362
|
+
mkdirSync11(memDir, { recursive: true });
|
|
16707
17363
|
}
|
|
16708
17364
|
return memDir;
|
|
16709
17365
|
}
|
|
16710
17366
|
function getPatternsPath(projectRoot) {
|
|
16711
|
-
return
|
|
17367
|
+
return join31(getMemoryDir(projectRoot), "patterns.jsonl");
|
|
16712
17368
|
}
|
|
16713
17369
|
function generatePatternId() {
|
|
16714
17370
|
return `P${randomBytes7(4).toString("hex")}`;
|
|
16715
17371
|
}
|
|
16716
17372
|
function readPatterns(projectRoot) {
|
|
16717
17373
|
const path = getPatternsPath(projectRoot);
|
|
16718
|
-
if (!
|
|
16719
|
-
const content =
|
|
17374
|
+
if (!existsSync30(path)) return [];
|
|
17375
|
+
const content = readFileSync22(path, "utf-8").trim();
|
|
16720
17376
|
if (!content) return [];
|
|
16721
17377
|
const entries = [];
|
|
16722
17378
|
for (const line of content.split("\n")) {
|
|
@@ -16753,7 +17409,7 @@ function storePattern(projectRoot, params) {
|
|
|
16753
17409
|
}
|
|
16754
17410
|
const path2 = getPatternsPath(projectRoot);
|
|
16755
17411
|
const updated = existing.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
16756
|
-
|
|
17412
|
+
writeFileSync7(path2, updated, "utf-8");
|
|
16757
17413
|
return duplicate;
|
|
16758
17414
|
}
|
|
16759
17415
|
const entry = {
|
|
@@ -16825,25 +17481,25 @@ function patternStats(projectRoot) {
|
|
|
16825
17481
|
|
|
16826
17482
|
// src/core/memory/learnings.ts
|
|
16827
17483
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
16828
|
-
import { readFileSync as
|
|
16829
|
-
import { join as
|
|
17484
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync8, appendFileSync as appendFileSync5, mkdirSync as mkdirSync12, existsSync as existsSync31 } from "node:fs";
|
|
17485
|
+
import { join as join32 } from "node:path";
|
|
16830
17486
|
function getMemoryDir2(projectRoot) {
|
|
16831
|
-
const memDir =
|
|
16832
|
-
if (!
|
|
16833
|
-
|
|
17487
|
+
const memDir = join32(projectRoot, ".cleo", "memory");
|
|
17488
|
+
if (!existsSync31(memDir)) {
|
|
17489
|
+
mkdirSync12(memDir, { recursive: true });
|
|
16834
17490
|
}
|
|
16835
17491
|
return memDir;
|
|
16836
17492
|
}
|
|
16837
17493
|
function getLearningsPath(projectRoot) {
|
|
16838
|
-
return
|
|
17494
|
+
return join32(getMemoryDir2(projectRoot), "learnings.jsonl");
|
|
16839
17495
|
}
|
|
16840
17496
|
function generateLearningId() {
|
|
16841
17497
|
return `L${randomBytes8(4).toString("hex")}`;
|
|
16842
17498
|
}
|
|
16843
17499
|
function readLearnings(projectRoot) {
|
|
16844
17500
|
const path = getLearningsPath(projectRoot);
|
|
16845
|
-
if (!
|
|
16846
|
-
const content =
|
|
17501
|
+
if (!existsSync31(path)) return [];
|
|
17502
|
+
const content = readFileSync23(path, "utf-8").trim();
|
|
16847
17503
|
if (!content) return [];
|
|
16848
17504
|
const entries = [];
|
|
16849
17505
|
for (const line of content.split("\n")) {
|
|
@@ -16882,7 +17538,7 @@ function storeLearning(projectRoot, params) {
|
|
|
16882
17538
|
}
|
|
16883
17539
|
const path2 = getLearningsPath(projectRoot);
|
|
16884
17540
|
const updated = existing.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
16885
|
-
|
|
17541
|
+
writeFileSync8(path2, updated, "utf-8");
|
|
16886
17542
|
return duplicate;
|
|
16887
17543
|
}
|
|
16888
17544
|
const entry = {
|
|
@@ -16957,7 +17613,7 @@ function resolveRoot(projectRoot) {
|
|
|
16957
17613
|
function readManifestEntries(projectRoot) {
|
|
16958
17614
|
const manifestPath = getManifestPath2(projectRoot);
|
|
16959
17615
|
try {
|
|
16960
|
-
const content =
|
|
17616
|
+
const content = readFileSync24(manifestPath, "utf-8");
|
|
16961
17617
|
const entries = [];
|
|
16962
17618
|
const lines = content.split("\n");
|
|
16963
17619
|
for (const line of lines) {
|
|
@@ -16992,9 +17648,9 @@ function memoryShow(researchId, projectRoot) {
|
|
|
16992
17648
|
const root = resolveRoot(projectRoot);
|
|
16993
17649
|
let fileContent = null;
|
|
16994
17650
|
try {
|
|
16995
|
-
const filePath =
|
|
16996
|
-
if (
|
|
16997
|
-
fileContent =
|
|
17651
|
+
const filePath = resolve7(root, entry.file);
|
|
17652
|
+
if (existsSync32(filePath)) {
|
|
17653
|
+
fileContent = readFileSync24(filePath, "utf-8");
|
|
16998
17654
|
}
|
|
16999
17655
|
} catch {
|
|
17000
17656
|
}
|
|
@@ -17122,7 +17778,7 @@ function memoryLink(taskId, researchId, notes, projectRoot) {
|
|
|
17122
17778
|
}
|
|
17123
17779
|
entry.linked_tasks.push(taskId);
|
|
17124
17780
|
const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
17125
|
-
|
|
17781
|
+
writeFileSync9(manifestPath, content, "utf-8");
|
|
17126
17782
|
return { success: true, data: { taskId, researchId, linked: true, notes: notes || null } };
|
|
17127
17783
|
}
|
|
17128
17784
|
function memoryManifestAppend(entry, projectRoot) {
|
|
@@ -17142,9 +17798,9 @@ function memoryManifestAppend(entry, projectRoot) {
|
|
|
17142
17798
|
return { success: false, error: { code: "E_VALIDATION_FAILED", message: `Invalid manifest entry: ${errors.join(", ")}` } };
|
|
17143
17799
|
}
|
|
17144
17800
|
const manifestPath = getManifestPath2(projectRoot);
|
|
17145
|
-
const dir =
|
|
17146
|
-
if (!
|
|
17147
|
-
|
|
17801
|
+
const dir = dirname10(manifestPath);
|
|
17802
|
+
if (!existsSync32(dir)) {
|
|
17803
|
+
mkdirSync13(dir, { recursive: true });
|
|
17148
17804
|
}
|
|
17149
17805
|
const serialized = JSON.stringify(entry);
|
|
17150
17806
|
appendFileSync6(manifestPath, serialized + "\n", "utf-8");
|
|
@@ -17163,14 +17819,14 @@ function memoryManifestArchive(beforeDate, projectRoot) {
|
|
|
17163
17819
|
if (toArchive.length === 0) {
|
|
17164
17820
|
return { success: true, data: { archived: 0, remaining: entries.length, message: "No entries found before the specified date" } };
|
|
17165
17821
|
}
|
|
17166
|
-
const archiveDir =
|
|
17167
|
-
if (!
|
|
17168
|
-
|
|
17822
|
+
const archiveDir = dirname10(archivePath);
|
|
17823
|
+
if (!existsSync32(archiveDir)) {
|
|
17824
|
+
mkdirSync13(archiveDir, { recursive: true });
|
|
17169
17825
|
}
|
|
17170
17826
|
const archiveContent = toArchive.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
17171
17827
|
appendFileSync6(archivePath, archiveContent, "utf-8");
|
|
17172
17828
|
const remainingContent = toKeep.length > 0 ? toKeep.map((e) => JSON.stringify(e)).join("\n") + "\n" : "";
|
|
17173
|
-
|
|
17829
|
+
writeFileSync9(manifestPath, remainingContent, "utf-8");
|
|
17174
17830
|
return { success: true, data: { archived: toArchive.length, remaining: toKeep.length, archiveFile: getManifestArchivePath() } };
|
|
17175
17831
|
}
|
|
17176
17832
|
function memoryContradictions(projectRoot, params) {
|
|
@@ -17252,16 +17908,16 @@ function memoryInject(protocolType, params, projectRoot) {
|
|
|
17252
17908
|
}
|
|
17253
17909
|
const root = resolveRoot(projectRoot);
|
|
17254
17910
|
const protocolLocations = [
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17911
|
+
resolve7(root, "protocols", `${protocolType}.md`),
|
|
17912
|
+
resolve7(root, "skills", "_shared", `${protocolType}.md`),
|
|
17913
|
+
resolve7(root, "agents", "cleo-subagent", "protocols", `${protocolType}.md`)
|
|
17258
17914
|
];
|
|
17259
17915
|
let protocolContent = null;
|
|
17260
17916
|
let protocolPath = null;
|
|
17261
17917
|
for (const loc of protocolLocations) {
|
|
17262
|
-
if (
|
|
17918
|
+
if (existsSync32(loc)) {
|
|
17263
17919
|
try {
|
|
17264
|
-
protocolContent =
|
|
17920
|
+
protocolContent = readFileSync24(loc, "utf-8");
|
|
17265
17921
|
protocolPath = loc.replace(root + "/", "");
|
|
17266
17922
|
break;
|
|
17267
17923
|
} catch {
|
|
@@ -17350,11 +18006,11 @@ init_data_accessor();
|
|
|
17350
18006
|
// src/core/release/release-manifest.ts
|
|
17351
18007
|
init_json();
|
|
17352
18008
|
init_paths();
|
|
17353
|
-
import { existsSync as
|
|
18009
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync14 } from "node:fs";
|
|
17354
18010
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
17355
|
-
import { dirname as
|
|
18011
|
+
import { dirname as dirname11, join as join33 } from "node:path";
|
|
17356
18012
|
function getReleasesPath(cwd) {
|
|
17357
|
-
return
|
|
18013
|
+
return join33(getCleoDirAbsolute(cwd), "releases.json");
|
|
17358
18014
|
}
|
|
17359
18015
|
async function readReleases(cwd) {
|
|
17360
18016
|
const data = await readJson(getReleasesPath(cwd));
|
|
@@ -17362,9 +18018,9 @@ async function readReleases(cwd) {
|
|
|
17362
18018
|
}
|
|
17363
18019
|
async function writeReleases(index2, cwd) {
|
|
17364
18020
|
const releasesPath = getReleasesPath(cwd);
|
|
17365
|
-
const dir =
|
|
17366
|
-
if (!
|
|
17367
|
-
|
|
18021
|
+
const dir = dirname11(releasesPath);
|
|
18022
|
+
if (!existsSync33(dir)) {
|
|
18023
|
+
mkdirSync14(dir, { recursive: true });
|
|
17368
18024
|
}
|
|
17369
18025
|
await saveJson(releasesPath, index2);
|
|
17370
18026
|
}
|
|
@@ -17631,7 +18287,7 @@ async function rollbackRelease(version, reason, cwd) {
|
|
|
17631
18287
|
};
|
|
17632
18288
|
}
|
|
17633
18289
|
async function readPushPolicy(cwd) {
|
|
17634
|
-
const configPath =
|
|
18290
|
+
const configPath = join33(getCleoDirAbsolute(cwd), "config.json");
|
|
17635
18291
|
const config = await readJson(configPath);
|
|
17636
18292
|
if (!config) return void 0;
|
|
17637
18293
|
const release2 = config.release;
|
|
@@ -17715,8 +18371,8 @@ async function hasManifestEntry(version, projectRoot) {
|
|
|
17715
18371
|
}
|
|
17716
18372
|
async function loadTasks2(projectRoot) {
|
|
17717
18373
|
if (projectRoot) {
|
|
17718
|
-
const taskPath =
|
|
17719
|
-
const taskData =
|
|
18374
|
+
const taskPath = getDataPath(projectRoot, "todo.json");
|
|
18375
|
+
const taskData = readJsonFile(taskPath);
|
|
17720
18376
|
return taskData?.tasks ?? [];
|
|
17721
18377
|
}
|
|
17722
18378
|
try {
|
|
@@ -17725,8 +18381,8 @@ async function loadTasks2(projectRoot) {
|
|
|
17725
18381
|
return taskFile?.tasks ?? [];
|
|
17726
18382
|
} catch {
|
|
17727
18383
|
const root = resolveProjectRoot();
|
|
17728
|
-
const taskPath =
|
|
17729
|
-
const taskData =
|
|
18384
|
+
const taskPath = getDataPath(root, "todo.json");
|
|
18385
|
+
const taskData = readJsonFile(taskPath);
|
|
17730
18386
|
return taskData?.tasks ?? [];
|
|
17731
18387
|
}
|
|
17732
18388
|
}
|
|
@@ -17846,8 +18502,8 @@ async function releasePush(version, remote, projectRoot, opts) {
|
|
|
17846
18502
|
|
|
17847
18503
|
// src/dispatch/engines/template-parser.ts
|
|
17848
18504
|
init_platform();
|
|
17849
|
-
import { readFileSync as
|
|
17850
|
-
import { join as
|
|
18505
|
+
import { readFileSync as readFileSync25, readdirSync as readdirSync9, existsSync as existsSync34 } from "fs";
|
|
18506
|
+
import { join as join34 } from "path";
|
|
17851
18507
|
import { parse as parseYaml } from "yaml";
|
|
17852
18508
|
var SUFFIX_PATTERNS = ["_report", "_request", "_question"];
|
|
17853
18509
|
function deriveSubcommand(filename) {
|
|
@@ -17862,8 +18518,8 @@ function deriveSubcommand(filename) {
|
|
|
17862
18518
|
return firstWord.toLowerCase();
|
|
17863
18519
|
}
|
|
17864
18520
|
function parseTemplateFile(templateDir, filename) {
|
|
17865
|
-
const filePath =
|
|
17866
|
-
const raw =
|
|
18521
|
+
const filePath = join34(templateDir, filename);
|
|
18522
|
+
const raw = readFileSync25(filePath, "utf-8");
|
|
17867
18523
|
const parsed = parseYaml(raw);
|
|
17868
18524
|
const name = typeof parsed.name === "string" ? parsed.name : filename;
|
|
17869
18525
|
const titlePrefix = typeof parsed.title === "string" ? parsed.title : "";
|
|
@@ -17912,8 +18568,8 @@ function parseTemplateFile(templateDir, filename) {
|
|
|
17912
18568
|
};
|
|
17913
18569
|
}
|
|
17914
18570
|
function parseIssueTemplates(projectRoot) {
|
|
17915
|
-
const templateDir =
|
|
17916
|
-
if (!
|
|
18571
|
+
const templateDir = join34(projectRoot, ".github", "ISSUE_TEMPLATE");
|
|
18572
|
+
if (!existsSync34(templateDir)) {
|
|
17917
18573
|
return engineError("E_NOT_FOUND", `Issue template directory not found: ${templateDir}`);
|
|
17918
18574
|
}
|
|
17919
18575
|
let files;
|
|
@@ -17965,7 +18621,7 @@ async function generateTemplateConfig(projectRoot) {
|
|
|
17965
18621
|
if (!result.success || !result.data) {
|
|
17966
18622
|
return result.error ? { success: false, error: result.error } : engineError("E_PARSE_ERROR", "Failed to parse issue templates");
|
|
17967
18623
|
}
|
|
17968
|
-
const outputPath =
|
|
18624
|
+
const outputPath = getDataPath(projectRoot, "issue-templates.json");
|
|
17969
18625
|
try {
|
|
17970
18626
|
writeJsonFileAtomic(outputPath, result.data);
|
|
17971
18627
|
} catch (error) {
|
|
@@ -17993,7 +18649,6 @@ function validateLabels2(labels, repoLabels) {
|
|
|
17993
18649
|
}
|
|
17994
18650
|
|
|
17995
18651
|
// src/dispatch/domains/tasks.ts
|
|
17996
|
-
var logger2 = getLogger("domain:tasks");
|
|
17997
18652
|
var TasksHandler = class {
|
|
17998
18653
|
projectRoot;
|
|
17999
18654
|
constructor() {
|
|
@@ -18015,14 +18670,26 @@ var TasksHandler = class {
|
|
|
18015
18670
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18016
18671
|
}
|
|
18017
18672
|
case "list": {
|
|
18018
|
-
const result = await taskList(this.projectRoot,
|
|
18673
|
+
const result = await taskList(this.projectRoot, {
|
|
18674
|
+
parent: params?.parent,
|
|
18675
|
+
status: params?.status,
|
|
18676
|
+
limit: params?.limit,
|
|
18677
|
+
compact: params?.compact
|
|
18678
|
+
});
|
|
18019
18679
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18020
18680
|
}
|
|
18021
18681
|
case "find": {
|
|
18022
18682
|
const result = await taskFind(
|
|
18023
18683
|
this.projectRoot,
|
|
18024
18684
|
params?.query,
|
|
18025
|
-
params?.limit
|
|
18685
|
+
params?.limit,
|
|
18686
|
+
{
|
|
18687
|
+
id: params?.id,
|
|
18688
|
+
exact: params?.exact,
|
|
18689
|
+
status: params?.status,
|
|
18690
|
+
includeArchive: params?.includeArchive,
|
|
18691
|
+
offset: params?.offset
|
|
18692
|
+
}
|
|
18026
18693
|
);
|
|
18027
18694
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18028
18695
|
}
|
|
@@ -18044,12 +18711,22 @@ var TasksHandler = class {
|
|
|
18044
18711
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18045
18712
|
}
|
|
18046
18713
|
case "depends": {
|
|
18714
|
+
const action = params?.action;
|
|
18715
|
+
if (action === "overview") {
|
|
18716
|
+
const result2 = await taskDepsOverview(this.projectRoot);
|
|
18717
|
+
return this.wrapEngineResult(result2, "query", "tasks", operation, startTime);
|
|
18718
|
+
}
|
|
18719
|
+
if (action === "cycles") {
|
|
18720
|
+
const result2 = await taskDepsCycles(this.projectRoot);
|
|
18721
|
+
return this.wrapEngineResult(result2, "query", "tasks", operation, startTime);
|
|
18722
|
+
}
|
|
18047
18723
|
const taskId = params?.taskId;
|
|
18048
18724
|
if (!taskId) {
|
|
18049
|
-
return this.errorResponse("query", "tasks", operation, "E_INVALID_INPUT", "taskId is required", startTime);
|
|
18725
|
+
return this.errorResponse("query", "tasks", operation, "E_INVALID_INPUT", "taskId is required (or use action: overview|cycles)", startTime);
|
|
18050
18726
|
}
|
|
18051
18727
|
const direction = params?.direction;
|
|
18052
|
-
const
|
|
18728
|
+
const tree = params?.tree;
|
|
18729
|
+
const result = await taskDepends(this.projectRoot, taskId, direction, tree);
|
|
18053
18730
|
return this.wrapEngineResult(result, "query", "tasks", operation, startTime);
|
|
18054
18731
|
}
|
|
18055
18732
|
case "analyze": {
|
|
@@ -18219,10 +18896,10 @@ var TasksHandler = class {
|
|
|
18219
18896
|
}
|
|
18220
18897
|
case "relates.add": {
|
|
18221
18898
|
const taskId = params?.taskId;
|
|
18222
|
-
const relatedId = params?.relatedId;
|
|
18899
|
+
const relatedId = params?.relatedId ?? params?.targetId;
|
|
18223
18900
|
const type = params?.type;
|
|
18224
18901
|
if (!taskId || !relatedId || !type) {
|
|
18225
|
-
return this.errorResponse("mutate", "tasks", operation, "E_INVALID_INPUT", "taskId, relatedId, and type are required", startTime);
|
|
18902
|
+
return this.errorResponse("mutate", "tasks", operation, "E_INVALID_INPUT", "taskId, relatedId (or targetId), and type are required", startTime);
|
|
18226
18903
|
}
|
|
18227
18904
|
const result = await taskRelatesAdd(
|
|
18228
18905
|
this.projectRoot,
|
|
@@ -18316,7 +18993,7 @@ var TasksHandler = class {
|
|
|
18316
18993
|
}
|
|
18317
18994
|
handleError(gateway, domain, operation, error, startTime) {
|
|
18318
18995
|
const message = error instanceof Error ? error.message : String(error);
|
|
18319
|
-
|
|
18996
|
+
getLogger("domain:tasks").error({ gateway, domain, operation, err: error }, message);
|
|
18320
18997
|
return {
|
|
18321
18998
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
18322
18999
|
success: false,
|
|
@@ -18354,7 +19031,6 @@ function unbindSession() {
|
|
|
18354
19031
|
}
|
|
18355
19032
|
|
|
18356
19033
|
// src/dispatch/domains/session.ts
|
|
18357
|
-
var logger3 = getLogger("domain:session");
|
|
18358
19034
|
var SessionHandler = class {
|
|
18359
19035
|
projectRoot;
|
|
18360
19036
|
constructor() {
|
|
@@ -18475,7 +19151,7 @@ var SessionHandler = class {
|
|
|
18475
19151
|
gradeMode: params?.grade ?? false
|
|
18476
19152
|
});
|
|
18477
19153
|
} catch {
|
|
18478
|
-
|
|
19154
|
+
getLogger("domain:session").warn({ sessionId: session.id }, "Session context already bound, skipping bindSession");
|
|
18479
19155
|
}
|
|
18480
19156
|
}
|
|
18481
19157
|
return this.wrapEngineResult(result, "mutate", "session", operation, startTime);
|
|
@@ -18586,7 +19262,7 @@ var SessionHandler = class {
|
|
|
18586
19262
|
}
|
|
18587
19263
|
handleError(gateway, domain, operation, error, startTime) {
|
|
18588
19264
|
const message = error instanceof Error ? error.message : String(error);
|
|
18589
|
-
|
|
19265
|
+
getLogger("domain:session").error({ gateway, domain, operation, err: error }, message);
|
|
18590
19266
|
return {
|
|
18591
19267
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
18592
19268
|
success: false,
|
|
@@ -18598,7 +19274,6 @@ var SessionHandler = class {
|
|
|
18598
19274
|
// src/dispatch/domains/check.ts
|
|
18599
19275
|
init_paths();
|
|
18600
19276
|
init_logger();
|
|
18601
|
-
var logger4 = getLogger("domain:check");
|
|
18602
19277
|
var CheckHandler = class {
|
|
18603
19278
|
projectRoot;
|
|
18604
19279
|
constructor() {
|
|
@@ -18757,7 +19432,7 @@ var CheckHandler = class {
|
|
|
18757
19432
|
}
|
|
18758
19433
|
handleError(gateway, domain, operation, error, startTime) {
|
|
18759
19434
|
const message = error instanceof Error ? error.message : String(error);
|
|
18760
|
-
|
|
19435
|
+
getLogger("domain:check").error({ gateway, domain, operation, err: error }, message);
|
|
18761
19436
|
return {
|
|
18762
19437
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
18763
19438
|
success: false,
|
|
@@ -18771,8 +19446,8 @@ init_paths();
|
|
|
18771
19446
|
init_logger();
|
|
18772
19447
|
|
|
18773
19448
|
// src/core/adrs/parse.ts
|
|
18774
|
-
import { readFileSync as
|
|
18775
|
-
import { join as
|
|
19449
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
19450
|
+
import { join as join35 } from "node:path";
|
|
18776
19451
|
function extractAdrId(filename) {
|
|
18777
19452
|
const match = filename.match(/^(ADR-\d+)/);
|
|
18778
19453
|
return match ? match[1] : filename.replace(".md", "");
|
|
@@ -18794,8 +19469,8 @@ function extractTitle(content) {
|
|
|
18794
19469
|
return match ? match[1].trim() : "Untitled";
|
|
18795
19470
|
}
|
|
18796
19471
|
function parseAdrFile(filePath, projectRoot) {
|
|
18797
|
-
const absolutePath = filePath.startsWith("/") ? filePath :
|
|
18798
|
-
const content =
|
|
19472
|
+
const absolutePath = filePath.startsWith("/") ? filePath : join35(projectRoot, filePath);
|
|
19473
|
+
const content = readFileSync26(absolutePath, "utf-8");
|
|
18799
19474
|
const filename = filePath.split("/").pop();
|
|
18800
19475
|
return {
|
|
18801
19476
|
id: extractAdrId(filename),
|
|
@@ -18806,27 +19481,27 @@ function parseAdrFile(filePath, projectRoot) {
|
|
|
18806
19481
|
}
|
|
18807
19482
|
|
|
18808
19483
|
// src/core/adrs/validate.ts
|
|
18809
|
-
import { readFileSync as
|
|
18810
|
-
import { join as
|
|
19484
|
+
import { readFileSync as readFileSync27, readdirSync as readdirSync10, existsSync as existsSync35 } from "node:fs";
|
|
19485
|
+
import { join as join36 } from "node:path";
|
|
18811
19486
|
import AjvModule3 from "ajv";
|
|
18812
19487
|
var Ajv3 = AjvModule3.default ?? AjvModule3;
|
|
18813
19488
|
async function validateAllAdrs(projectRoot) {
|
|
18814
|
-
const adrsDir =
|
|
18815
|
-
const schemaPath =
|
|
18816
|
-
if (!
|
|
19489
|
+
const adrsDir = join36(projectRoot, ".cleo", "adrs");
|
|
19490
|
+
const schemaPath = join36(projectRoot, "schemas", "adr-frontmatter.schema.json");
|
|
19491
|
+
if (!existsSync35(schemaPath)) {
|
|
18817
19492
|
return {
|
|
18818
19493
|
valid: false,
|
|
18819
19494
|
errors: [{ file: "schemas/adr-frontmatter.schema.json", field: "schema", message: "Schema file not found" }],
|
|
18820
19495
|
checked: 0
|
|
18821
19496
|
};
|
|
18822
19497
|
}
|
|
18823
|
-
if (!
|
|
19498
|
+
if (!existsSync35(adrsDir)) {
|
|
18824
19499
|
return { valid: true, errors: [], checked: 0 };
|
|
18825
19500
|
}
|
|
18826
|
-
const schema = JSON.parse(
|
|
19501
|
+
const schema = JSON.parse(readFileSync27(schemaPath, "utf-8"));
|
|
18827
19502
|
const ajv = new Ajv3({ allErrors: true });
|
|
18828
19503
|
const validate = ajv.compile(schema);
|
|
18829
|
-
const files = readdirSync10(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) =>
|
|
19504
|
+
const files = readdirSync10(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).map((f) => join36(adrsDir, f));
|
|
18830
19505
|
const errors = [];
|
|
18831
19506
|
for (const filePath of files) {
|
|
18832
19507
|
const record = parseAdrFile(filePath, projectRoot);
|
|
@@ -18847,8 +19522,8 @@ async function validateAllAdrs(projectRoot) {
|
|
|
18847
19522
|
// src/core/adrs/sync.ts
|
|
18848
19523
|
init_sqlite();
|
|
18849
19524
|
init_schema();
|
|
18850
|
-
import { readFileSync as
|
|
18851
|
-
import { join as
|
|
19525
|
+
import { readFileSync as readFileSync28, readdirSync as readdirSync11, writeFileSync as writeFileSync10, existsSync as existsSync36 } from "node:fs";
|
|
19526
|
+
import { join as join37 } from "node:path";
|
|
18852
19527
|
import { eq as eq7 } from "drizzle-orm";
|
|
18853
19528
|
function extractAdrIdFromRef(ref) {
|
|
18854
19529
|
const m = ref.match(/^(ADR-\d+)/);
|
|
@@ -18861,7 +19536,7 @@ function collectAdrFiles(dir) {
|
|
|
18861
19536
|
const results = [];
|
|
18862
19537
|
for (const entry of readdirSync11(dir, { withFileTypes: true })) {
|
|
18863
19538
|
if (entry.isDirectory()) {
|
|
18864
|
-
const sub =
|
|
19539
|
+
const sub = join37(dir, entry.name);
|
|
18865
19540
|
for (const f of readdirSync11(sub)) {
|
|
18866
19541
|
if (f.endsWith(".md") && /^ADR-\d+/.test(f)) {
|
|
18867
19542
|
results.push({ file: f, relPath: `${entry.name}/${f}` });
|
|
@@ -18874,23 +19549,23 @@ function collectAdrFiles(dir) {
|
|
|
18874
19549
|
return results.sort((a, b) => a.file.localeCompare(b.file));
|
|
18875
19550
|
}
|
|
18876
19551
|
async function syncAdrsToDb(projectRoot) {
|
|
18877
|
-
const adrsDir =
|
|
19552
|
+
const adrsDir = join37(projectRoot, ".cleo", "adrs");
|
|
18878
19553
|
const result = { inserted: 0, updated: 0, skipped: 0, errors: [] };
|
|
18879
|
-
if (!
|
|
19554
|
+
if (!existsSync36(adrsDir)) {
|
|
18880
19555
|
return result;
|
|
18881
19556
|
}
|
|
18882
19557
|
const db = await getDb(projectRoot);
|
|
18883
19558
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18884
19559
|
const allFiles = collectAdrFiles(adrsDir);
|
|
18885
19560
|
const activeFiles = allFiles.filter((f) => !f.relPath.includes("/"));
|
|
18886
|
-
const
|
|
19561
|
+
const manifestEntries2 = [];
|
|
18887
19562
|
for (const { file, relPath } of activeFiles) {
|
|
18888
19563
|
try {
|
|
18889
|
-
const filePath =
|
|
19564
|
+
const filePath = join37(adrsDir, relPath);
|
|
18890
19565
|
const record = parseAdrFile(filePath, projectRoot);
|
|
18891
19566
|
const fm = record.frontmatter;
|
|
18892
19567
|
const dbRelPath = `.cleo/adrs/${relPath}`;
|
|
18893
|
-
const content =
|
|
19568
|
+
const content = readFileSync28(filePath, "utf-8");
|
|
18894
19569
|
const supersedesId = fm.Supersedes ? extractAdrIdFromRef(fm.Supersedes) : null;
|
|
18895
19570
|
const supersededById = fm["Superseded By"] ? extractAdrIdFromRef(fm["Superseded By"]) : null;
|
|
18896
19571
|
const amendsId = fm.Amends ? extractAdrIdFromRef(fm.Amends) : null;
|
|
@@ -18932,7 +19607,7 @@ async function syncAdrsToDb(projectRoot) {
|
|
|
18932
19607
|
}
|
|
18933
19608
|
for (const { relPath } of allFiles) {
|
|
18934
19609
|
try {
|
|
18935
|
-
const filePath =
|
|
19610
|
+
const filePath = join37(adrsDir, relPath);
|
|
18936
19611
|
const record = parseAdrFile(filePath, projectRoot);
|
|
18937
19612
|
const fm = record.frontmatter;
|
|
18938
19613
|
const entry = {
|
|
@@ -18955,28 +19630,28 @@ async function syncAdrsToDb(projectRoot) {
|
|
|
18955
19630
|
if (fm.Summary) entry["summary"] = fm.Summary;
|
|
18956
19631
|
if (fm.Keywords) entry["keywords"] = fm.Keywords.split(",").map((s) => s.trim()).filter(Boolean);
|
|
18957
19632
|
if (fm.Topics) entry["topics"] = fm.Topics.split(",").map((s) => s.trim()).filter(Boolean);
|
|
18958
|
-
|
|
19633
|
+
manifestEntries2.push(entry);
|
|
18959
19634
|
} catch {
|
|
18960
19635
|
}
|
|
18961
19636
|
}
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
19637
|
+
writeFileSync10(
|
|
19638
|
+
join37(adrsDir, "MANIFEST.jsonl"),
|
|
19639
|
+
manifestEntries2.map((e) => JSON.stringify(e)).join("\n") + "\n",
|
|
18965
19640
|
"utf-8"
|
|
18966
19641
|
);
|
|
18967
19642
|
return result;
|
|
18968
19643
|
}
|
|
18969
19644
|
|
|
18970
19645
|
// src/core/adrs/list.ts
|
|
18971
|
-
import { readdirSync as readdirSync12, existsSync as
|
|
18972
|
-
import { join as
|
|
19646
|
+
import { readdirSync as readdirSync12, existsSync as existsSync37 } from "node:fs";
|
|
19647
|
+
import { join as join38 } from "node:path";
|
|
18973
19648
|
async function listAdrs(projectRoot, opts) {
|
|
18974
|
-
const adrsDir =
|
|
18975
|
-
if (!
|
|
19649
|
+
const adrsDir = join38(projectRoot, ".cleo", "adrs");
|
|
19650
|
+
if (!existsSync37(adrsDir)) {
|
|
18976
19651
|
return { adrs: [], total: 0 };
|
|
18977
19652
|
}
|
|
18978
19653
|
const files = readdirSync12(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
|
|
18979
|
-
const records = files.map((f) => parseAdrFile(
|
|
19654
|
+
const records = files.map((f) => parseAdrFile(join38(adrsDir, f), projectRoot));
|
|
18980
19655
|
const filtered = records.filter((r) => {
|
|
18981
19656
|
if (opts?.status && r.frontmatter.Status !== opts.status) return false;
|
|
18982
19657
|
if (opts?.since && r.frontmatter.Date < opts.since) return false;
|
|
@@ -18995,20 +19670,20 @@ async function listAdrs(projectRoot, opts) {
|
|
|
18995
19670
|
}
|
|
18996
19671
|
|
|
18997
19672
|
// src/core/adrs/show.ts
|
|
18998
|
-
import { existsSync as
|
|
18999
|
-
import { join as
|
|
19673
|
+
import { existsSync as existsSync38, readdirSync as readdirSync13 } from "node:fs";
|
|
19674
|
+
import { join as join39 } from "node:path";
|
|
19000
19675
|
async function showAdr(projectRoot, adrId) {
|
|
19001
|
-
const adrsDir =
|
|
19002
|
-
if (!
|
|
19676
|
+
const adrsDir = join39(projectRoot, ".cleo", "adrs");
|
|
19677
|
+
if (!existsSync38(adrsDir)) return null;
|
|
19003
19678
|
const files = readdirSync13(adrsDir).filter((f) => f.startsWith(adrId) && f.endsWith(".md"));
|
|
19004
19679
|
if (files.length === 0) return null;
|
|
19005
|
-
const filePath =
|
|
19680
|
+
const filePath = join39(adrsDir, files[0]);
|
|
19006
19681
|
return parseAdrFile(filePath, projectRoot);
|
|
19007
19682
|
}
|
|
19008
19683
|
|
|
19009
19684
|
// src/core/adrs/find.ts
|
|
19010
|
-
import { readdirSync as readdirSync14, existsSync as
|
|
19011
|
-
import { join as
|
|
19685
|
+
import { readdirSync as readdirSync14, existsSync as existsSync39 } from "node:fs";
|
|
19686
|
+
import { join as join40 } from "node:path";
|
|
19012
19687
|
function normalise(s) {
|
|
19013
19688
|
return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
19014
19689
|
}
|
|
@@ -19025,8 +19700,8 @@ function matchedTerms(target, terms) {
|
|
|
19025
19700
|
return terms.filter((term) => t.includes(term));
|
|
19026
19701
|
}
|
|
19027
19702
|
async function findAdrs(projectRoot, query, opts) {
|
|
19028
|
-
const adrsDir =
|
|
19029
|
-
if (!
|
|
19703
|
+
const adrsDir = join40(projectRoot, ".cleo", "adrs");
|
|
19704
|
+
if (!existsSync39(adrsDir)) {
|
|
19030
19705
|
return { adrs: [], query, total: 0 };
|
|
19031
19706
|
}
|
|
19032
19707
|
const files = readdirSync14(adrsDir).filter((f) => f.endsWith(".md") && f.startsWith("ADR-")).sort();
|
|
@@ -19035,7 +19710,7 @@ async function findAdrs(projectRoot, query, opts) {
|
|
|
19035
19710
|
const filterKeywords = opts?.keywords ? parseTags(opts.keywords) : null;
|
|
19036
19711
|
const results = [];
|
|
19037
19712
|
for (const file of files) {
|
|
19038
|
-
const record = parseAdrFile(
|
|
19713
|
+
const record = parseAdrFile(join40(adrsDir, file), projectRoot);
|
|
19039
19714
|
const fm = record.frontmatter;
|
|
19040
19715
|
if (opts?.status && fm.Status !== opts.status) continue;
|
|
19041
19716
|
if (filterTopics && filterTopics.length > 0) {
|
|
@@ -19098,7 +19773,6 @@ init_schema();
|
|
|
19098
19773
|
import { eq as eq8, and as and2 } from "drizzle-orm";
|
|
19099
19774
|
|
|
19100
19775
|
// src/dispatch/domains/admin.ts
|
|
19101
|
-
var logger5 = getLogger("domain:admin");
|
|
19102
19776
|
var AdminHandler = class {
|
|
19103
19777
|
projectRoot;
|
|
19104
19778
|
constructor() {
|
|
@@ -19181,6 +19855,14 @@ var AdminHandler = class {
|
|
|
19181
19855
|
case "help": {
|
|
19182
19856
|
const tier = typeof params?.tier === "number" ? params.tier : 0;
|
|
19183
19857
|
const ops = OPERATIONS.filter((op) => op.tier <= tier);
|
|
19858
|
+
const getCostHint = (domain, op) => {
|
|
19859
|
+
const key = `${domain}.${op}`;
|
|
19860
|
+
const heavyOps = ["tasks.list", "tasks.tree", "admin.log", "admin.stats", "tasks.analyze"];
|
|
19861
|
+
const moderateOps = ["tasks.show", "tasks.blockers", "tasks.depends", "admin.health", "admin.dash", "admin.help"];
|
|
19862
|
+
if (heavyOps.includes(key)) return "heavy";
|
|
19863
|
+
if (moderateOps.includes(key)) return "moderate";
|
|
19864
|
+
return "minimal";
|
|
19865
|
+
};
|
|
19184
19866
|
const tierGuidance = {
|
|
19185
19867
|
0: "Tier 0: Core task and session operations (tasks, session, admin). 80% of use cases.",
|
|
19186
19868
|
1: "Tier 1: + memory/research and check/validate operations. 15% of use cases.",
|
|
@@ -19192,14 +19874,22 @@ var AdminHandler = class {
|
|
|
19192
19874
|
data: {
|
|
19193
19875
|
tier,
|
|
19194
19876
|
operationCount: ops.length,
|
|
19877
|
+
quickStart: tier === 0 ? [
|
|
19878
|
+
"cleo_query tasks.current \u2014 check active task (~100 tokens)",
|
|
19879
|
+
"cleo_query tasks.next \u2014 get suggestion (~300 tokens)",
|
|
19880
|
+
"cleo_query tasks.find {query} \u2014 search tasks (~200 tokens)",
|
|
19881
|
+
"cleo_mutate tasks.start {taskId} \u2014 begin work (~100 tokens)",
|
|
19882
|
+
"cleo_mutate tasks.complete {taskId} \u2014 finish task (~200 tokens)"
|
|
19883
|
+
] : void 0,
|
|
19195
19884
|
operations: ops.map((op) => ({
|
|
19196
19885
|
gateway: op.gateway,
|
|
19197
19886
|
domain: op.domain,
|
|
19198
19887
|
operation: op.operation,
|
|
19199
|
-
description: op.description
|
|
19888
|
+
description: op.description,
|
|
19889
|
+
costHint: getCostHint(op.domain, op.operation)
|
|
19200
19890
|
})),
|
|
19201
19891
|
guidance: tierGuidance[tier] ?? tierGuidance[0],
|
|
19202
|
-
escalation: tier < 2 ? `For more operations:
|
|
19892
|
+
escalation: tier < 2 ? `For more operations: cleo_query({domain:"admin",operation:"help",params:{tier:${tier + 1}}})` : "Full operation set displayed."
|
|
19203
19893
|
}
|
|
19204
19894
|
};
|
|
19205
19895
|
}
|
|
@@ -19450,7 +20140,7 @@ var AdminHandler = class {
|
|
|
19450
20140
|
}
|
|
19451
20141
|
handleError(gateway, domain, operation, error, startTime) {
|
|
19452
20142
|
const message = error instanceof Error ? error.message : String(error);
|
|
19453
|
-
|
|
20143
|
+
getLogger("domain:admin").error({ gateway, domain, operation, err: error }, message);
|
|
19454
20144
|
return {
|
|
19455
20145
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
19456
20146
|
success: false,
|
|
@@ -19462,7 +20152,6 @@ var AdminHandler = class {
|
|
|
19462
20152
|
// src/dispatch/domains/memory.ts
|
|
19463
20153
|
init_paths();
|
|
19464
20154
|
init_logger();
|
|
19465
|
-
var logger6 = getLogger("domain:memory");
|
|
19466
20155
|
var MemoryHandler = class {
|
|
19467
20156
|
projectRoot;
|
|
19468
20157
|
constructor() {
|
|
@@ -19695,7 +20384,7 @@ var MemoryHandler = class {
|
|
|
19695
20384
|
}
|
|
19696
20385
|
handleError(gateway, domain, operation, error, startTime) {
|
|
19697
20386
|
const message = error instanceof Error ? error.message : String(error);
|
|
19698
|
-
|
|
20387
|
+
getLogger("domain:memory").error({ gateway, domain, operation, err: error }, message);
|
|
19699
20388
|
return {
|
|
19700
20389
|
_meta: dispatchMeta(gateway, domain, operation, startTime),
|
|
19701
20390
|
success: false,
|
|
@@ -19707,7 +20396,6 @@ var MemoryHandler = class {
|
|
|
19707
20396
|
// src/dispatch/domains/orchestrate.ts
|
|
19708
20397
|
init_paths();
|
|
19709
20398
|
init_logger();
|
|
19710
|
-
var logger7 = getLogger("domain:orchestrate");
|
|
19711
20399
|
var OrchestrateHandler = class {
|
|
19712
20400
|
projectRoot;
|
|
19713
20401
|
constructor() {
|
|
@@ -19842,7 +20530,8 @@ var OrchestrateHandler = class {
|
|
|
19842
20530
|
);
|
|
19843
20531
|
}
|
|
19844
20532
|
const protocolType = params?.protocolType;
|
|
19845
|
-
const
|
|
20533
|
+
const tier = params?.tier;
|
|
20534
|
+
const result = await orchestrateSpawn(taskId, protocolType, this.projectRoot, tier);
|
|
19846
20535
|
return this.wrapEngineResult(result, "mutate", operation, startTime);
|
|
19847
20536
|
}
|
|
19848
20537
|
case "validate": {
|
|
@@ -19976,7 +20665,7 @@ var OrchestrateHandler = class {
|
|
|
19976
20665
|
}
|
|
19977
20666
|
handleError(gateway, operation, error, startTime) {
|
|
19978
20667
|
const message = error instanceof Error ? error.message : String(error);
|
|
19979
|
-
|
|
20668
|
+
getLogger("domain:orchestrate").error({ gateway, domain: "orchestrate", operation, err: error }, message);
|
|
19980
20669
|
return this.errorResponse(
|
|
19981
20670
|
gateway,
|
|
19982
20671
|
operation,
|
|
@@ -19990,7 +20679,6 @@ var OrchestrateHandler = class {
|
|
|
19990
20679
|
// src/dispatch/domains/pipeline.ts
|
|
19991
20680
|
init_paths();
|
|
19992
20681
|
init_logger();
|
|
19993
|
-
var logger8 = getLogger("domain:pipeline");
|
|
19994
20682
|
var PipelineHandler = class {
|
|
19995
20683
|
projectRoot;
|
|
19996
20684
|
constructor() {
|
|
@@ -20391,7 +21079,7 @@ var PipelineHandler = class {
|
|
|
20391
21079
|
}
|
|
20392
21080
|
handleError(gateway, operation, error, startTime) {
|
|
20393
21081
|
const message = error instanceof Error ? error.message : String(error);
|
|
20394
|
-
|
|
21082
|
+
getLogger("domain:pipeline").error({ gateway, domain: "pipeline", operation, err: error }, message);
|
|
20395
21083
|
return this.errorResponse(
|
|
20396
21084
|
gateway,
|
|
20397
21085
|
operation,
|
|
@@ -20454,8 +21142,8 @@ import { execFileSync as execFileSync5 } from "node:child_process";
|
|
|
20454
21142
|
|
|
20455
21143
|
// src/core/issue/template-parser.ts
|
|
20456
21144
|
init_paths();
|
|
20457
|
-
import { existsSync as
|
|
20458
|
-
import { join as
|
|
21145
|
+
import { existsSync as existsSync41, readFileSync as readFileSync29, readdirSync as readdirSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
21146
|
+
import { join as join42, basename as basename6 } from "node:path";
|
|
20459
21147
|
var TEMPLATE_DIR = ".github/ISSUE_TEMPLATE";
|
|
20460
21148
|
var CACHE_FILE = "issue-templates.json";
|
|
20461
21149
|
var SUBCOMMAND_MAP = {
|
|
@@ -20512,10 +21200,10 @@ function extractYamlArray(content, field) {
|
|
|
20512
21200
|
return items;
|
|
20513
21201
|
}
|
|
20514
21202
|
function parseTemplateFile2(filePath) {
|
|
20515
|
-
if (!
|
|
21203
|
+
if (!existsSync41(filePath)) return null;
|
|
20516
21204
|
try {
|
|
20517
|
-
const content =
|
|
20518
|
-
const fileName =
|
|
21205
|
+
const content = readFileSync29(filePath, "utf-8");
|
|
21206
|
+
const fileName = basename6(filePath);
|
|
20519
21207
|
const stem = fileName.replace(/\.ya?ml$/, "");
|
|
20520
21208
|
const name = extractYamlField(content, "name");
|
|
20521
21209
|
const description = extractYamlField(content, "description");
|
|
@@ -20530,12 +21218,12 @@ function parseTemplateFile2(filePath) {
|
|
|
20530
21218
|
}
|
|
20531
21219
|
function parseIssueTemplates2(projectDir) {
|
|
20532
21220
|
const dir = projectDir ?? getProjectRoot();
|
|
20533
|
-
const templateDir =
|
|
20534
|
-
if (!
|
|
21221
|
+
const templateDir = join42(dir, TEMPLATE_DIR);
|
|
21222
|
+
if (!existsSync41(templateDir)) return [];
|
|
20535
21223
|
const templates = [];
|
|
20536
21224
|
for (const file of readdirSync15(templateDir)) {
|
|
20537
21225
|
if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
|
|
20538
|
-
const template = parseTemplateFile2(
|
|
21226
|
+
const template = parseTemplateFile2(join42(templateDir, file));
|
|
20539
21227
|
if (template) templates.push(template);
|
|
20540
21228
|
}
|
|
20541
21229
|
return templates;
|
|
@@ -20544,10 +21232,10 @@ function getTemplateConfig(cwd) {
|
|
|
20544
21232
|
const projectDir = cwd ?? getProjectRoot();
|
|
20545
21233
|
const liveTemplates = parseIssueTemplates2(projectDir);
|
|
20546
21234
|
if (liveTemplates.length > 0) return liveTemplates;
|
|
20547
|
-
const cachePath =
|
|
20548
|
-
if (
|
|
21235
|
+
const cachePath = join42(getCleoDir(cwd), CACHE_FILE);
|
|
21236
|
+
if (existsSync41(cachePath)) {
|
|
20549
21237
|
try {
|
|
20550
|
-
const cached = JSON.parse(
|
|
21238
|
+
const cached = JSON.parse(readFileSync29(cachePath, "utf-8"));
|
|
20551
21239
|
if (cached.templates?.length > 0) return cached.templates;
|
|
20552
21240
|
} catch {
|
|
20553
21241
|
}
|
|
@@ -20672,7 +21360,6 @@ import {
|
|
|
20672
21360
|
injectAll,
|
|
20673
21361
|
buildInjectionContent
|
|
20674
21362
|
} from "@cleocode/caamp";
|
|
20675
|
-
var logger9 = getLogger("domain:tools");
|
|
20676
21363
|
var ToolsHandler = class {
|
|
20677
21364
|
projectRoot;
|
|
20678
21365
|
constructor() {
|
|
@@ -21307,7 +21994,7 @@ var ToolsHandler = class {
|
|
|
21307
21994
|
}
|
|
21308
21995
|
handleError(gateway, domain, operation, error, startTime) {
|
|
21309
21996
|
const message = error instanceof Error ? error.message : String(error);
|
|
21310
|
-
|
|
21997
|
+
getLogger("domain:tools").error({ gateway, domain, operation, err: error }, message);
|
|
21311
21998
|
return this.errorResponse(
|
|
21312
21999
|
gateway,
|
|
21313
22000
|
domain,
|
|
@@ -21321,11 +22008,10 @@ var ToolsHandler = class {
|
|
|
21321
22008
|
|
|
21322
22009
|
// src/dispatch/domains/nexus.ts
|
|
21323
22010
|
init_logger();
|
|
21324
|
-
var logger10 = getLogger("domain:nexus");
|
|
21325
22011
|
var NexusHandler = class {
|
|
21326
22012
|
async query(operation, _params) {
|
|
21327
22013
|
const startTime = Date.now();
|
|
21328
|
-
|
|
22014
|
+
getLogger("domain:nexus").warn({ operation }, `Nexus domain not yet implemented: ${operation}`);
|
|
21329
22015
|
return {
|
|
21330
22016
|
_meta: dispatchMeta("query", "nexus", operation, startTime),
|
|
21331
22017
|
success: false,
|
|
@@ -21334,7 +22020,7 @@ var NexusHandler = class {
|
|
|
21334
22020
|
}
|
|
21335
22021
|
async mutate(operation, _params) {
|
|
21336
22022
|
const startTime = Date.now();
|
|
21337
|
-
|
|
22023
|
+
getLogger("domain:nexus").warn({ operation }, `Nexus domain not yet implemented: ${operation}`);
|
|
21338
22024
|
return {
|
|
21339
22025
|
_meta: dispatchMeta("mutate", "nexus", operation, startTime),
|
|
21340
22026
|
success: false,
|
|
@@ -21352,8 +22038,8 @@ init_logger();
|
|
|
21352
22038
|
|
|
21353
22039
|
// src/core/sharing/index.ts
|
|
21354
22040
|
import { readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
21355
|
-
import { existsSync as
|
|
21356
|
-
import { join as
|
|
22041
|
+
import { existsSync as existsSync42, readdirSync as readdirSync16, statSync as statSync5 } from "node:fs";
|
|
22042
|
+
import { join as join43, relative } from "node:path";
|
|
21357
22043
|
init_paths();
|
|
21358
22044
|
var GITIGNORE_START = "# CLEO:SHARING:START - Auto-managed by cleo sharing sync";
|
|
21359
22045
|
var GITIGNORE_END = "# CLEO:SHARING:END";
|
|
@@ -21381,7 +22067,7 @@ function collectCleoFiles(cleoDir) {
|
|
|
21381
22067
|
const entries = readdirSync16(dir);
|
|
21382
22068
|
for (const entry of entries) {
|
|
21383
22069
|
if (entry === ".git") continue;
|
|
21384
|
-
const fullPath =
|
|
22070
|
+
const fullPath = join43(dir, entry);
|
|
21385
22071
|
const relPath = relative(cleoDir, fullPath);
|
|
21386
22072
|
try {
|
|
21387
22073
|
const stat2 = statSync5(fullPath);
|
|
@@ -21441,7 +22127,7 @@ function generateGitignoreEntries(sharing) {
|
|
|
21441
22127
|
async function syncGitignore(cwd) {
|
|
21442
22128
|
const config = await loadConfig2(cwd);
|
|
21443
22129
|
const projectRoot = getProjectRoot(cwd);
|
|
21444
|
-
const gitignorePath =
|
|
22130
|
+
const gitignorePath = join43(projectRoot, ".gitignore");
|
|
21445
22131
|
const entries = generateGitignoreEntries(config.sharing);
|
|
21446
22132
|
const managedSection = [
|
|
21447
22133
|
"",
|
|
@@ -21451,7 +22137,7 @@ async function syncGitignore(cwd) {
|
|
|
21451
22137
|
""
|
|
21452
22138
|
].join("\n");
|
|
21453
22139
|
let content = "";
|
|
21454
|
-
if (
|
|
22140
|
+
if (existsSync42(gitignorePath)) {
|
|
21455
22141
|
content = await readFile7(gitignorePath, "utf-8");
|
|
21456
22142
|
}
|
|
21457
22143
|
const startIdx = content.indexOf(GITIGNORE_START);
|
|
@@ -21476,8 +22162,8 @@ init_data_accessor();
|
|
|
21476
22162
|
init_paths();
|
|
21477
22163
|
import { createHash as createHash6 } from "node:crypto";
|
|
21478
22164
|
import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir7 } from "node:fs/promises";
|
|
21479
|
-
import { existsSync as
|
|
21480
|
-
import { join as
|
|
22165
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
22166
|
+
import { join as join44, dirname as dirname12 } from "node:path";
|
|
21481
22167
|
var SNAPSHOT_FORMAT_VERSION = "1.0.0";
|
|
21482
22168
|
function toSnapshotTask(task) {
|
|
21483
22169
|
return {
|
|
@@ -21530,8 +22216,8 @@ async function exportSnapshot(cwd) {
|
|
|
21530
22216
|
};
|
|
21531
22217
|
}
|
|
21532
22218
|
async function writeSnapshot(snapshot, outputPath) {
|
|
21533
|
-
const dir =
|
|
21534
|
-
if (!
|
|
22219
|
+
const dir = dirname12(outputPath);
|
|
22220
|
+
if (!existsSync43(dir)) {
|
|
21535
22221
|
await mkdir7(dir, { recursive: true });
|
|
21536
22222
|
}
|
|
21537
22223
|
await writeFile5(outputPath, JSON.stringify(snapshot, null, 2) + "\n");
|
|
@@ -21547,7 +22233,7 @@ async function readSnapshot(inputPath) {
|
|
|
21547
22233
|
function getDefaultSnapshotPath(cwd) {
|
|
21548
22234
|
const cleoDir = getCleoDirAbsolute(cwd);
|
|
21549
22235
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
21550
|
-
return
|
|
22236
|
+
return join44(cleoDir, "snapshots", `snapshot-${timestamp}.json`);
|
|
21551
22237
|
}
|
|
21552
22238
|
async function importSnapshot(snapshot, cwd) {
|
|
21553
22239
|
const accessor = await getAccessor(cwd);
|
|
@@ -21614,10 +22300,10 @@ init_paths();
|
|
|
21614
22300
|
init_git_checkpoint();
|
|
21615
22301
|
import { execFile as execFile6 } from "node:child_process";
|
|
21616
22302
|
import { promisify as promisify6 } from "node:util";
|
|
21617
|
-
import { resolve as
|
|
22303
|
+
import { resolve as resolve8 } from "node:path";
|
|
21618
22304
|
var execFileAsync4 = promisify6(execFile6);
|
|
21619
22305
|
async function cleoGitExec(args, cleoDir) {
|
|
21620
|
-
const abs =
|
|
22306
|
+
const abs = resolve8(cleoDir);
|
|
21621
22307
|
const result = await execFileAsync4("git", args, {
|
|
21622
22308
|
cwd: abs,
|
|
21623
22309
|
env: makeCleoGitEnv(cleoDir),
|
|
@@ -21804,7 +22490,6 @@ async function getSyncStatus(remote = "origin", cwd) {
|
|
|
21804
22490
|
}
|
|
21805
22491
|
|
|
21806
22492
|
// src/dispatch/domains/sharing.ts
|
|
21807
|
-
var logger11 = getLogger("domain:sharing");
|
|
21808
22493
|
var SharingHandler = class {
|
|
21809
22494
|
projectRoot;
|
|
21810
22495
|
constructor() {
|
|
@@ -21989,7 +22674,7 @@ var SharingHandler = class {
|
|
|
21989
22674
|
}
|
|
21990
22675
|
handleError(gateway, operation, error, startTime) {
|
|
21991
22676
|
const message = error instanceof Error ? error.message : String(error);
|
|
21992
|
-
|
|
22677
|
+
getLogger("domain:sharing").error({ gateway, operation, err: error }, message);
|
|
21993
22678
|
return {
|
|
21994
22679
|
_meta: dispatchMeta(gateway, "sharing", operation, startTime),
|
|
21995
22680
|
success: false,
|
|
@@ -22052,7 +22737,7 @@ function createSessionResolver(cliSessionLookup) {
|
|
|
22052
22737
|
// src/dispatch/lib/security.ts
|
|
22053
22738
|
init_schema();
|
|
22054
22739
|
init_status_registry();
|
|
22055
|
-
import { resolve as
|
|
22740
|
+
import { resolve as resolve9, normalize, relative as relative2, isAbsolute } from "path";
|
|
22056
22741
|
var SecurityError = class extends Error {
|
|
22057
22742
|
constructor(message, code = "E_SECURITY_VIOLATION", field) {
|
|
22058
22743
|
super(message);
|
|
@@ -22128,12 +22813,12 @@ function sanitizePath(path, projectRoot) {
|
|
|
22128
22813
|
"path"
|
|
22129
22814
|
);
|
|
22130
22815
|
}
|
|
22131
|
-
const normalizedRoot =
|
|
22816
|
+
const normalizedRoot = resolve9(projectRoot);
|
|
22132
22817
|
let resolvedPath;
|
|
22133
22818
|
if (isAbsolute(trimmedPath)) {
|
|
22134
22819
|
resolvedPath = normalize(trimmedPath);
|
|
22135
22820
|
} else {
|
|
22136
|
-
resolvedPath =
|
|
22821
|
+
resolvedPath = resolve9(normalizedRoot, trimmedPath);
|
|
22137
22822
|
}
|
|
22138
22823
|
const relativePath = relative2(normalizedRoot, resolvedPath);
|
|
22139
22824
|
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
@@ -22182,6 +22867,27 @@ function validateEnum(value, allowed, fieldName) {
|
|
|
22182
22867
|
}
|
|
22183
22868
|
var ALL_VALID_STATUSES = [...TASK_STATUSES, ...MANIFEST_STATUSES];
|
|
22184
22869
|
var VALID_PRIORITIES2 = TASK_PRIORITIES;
|
|
22870
|
+
var ARRAY_PARAMS = /* @__PURE__ */ new Set([
|
|
22871
|
+
"labels",
|
|
22872
|
+
"addLabels",
|
|
22873
|
+
"removeLabels",
|
|
22874
|
+
"depends",
|
|
22875
|
+
"addDepends",
|
|
22876
|
+
"removeDepends",
|
|
22877
|
+
"files",
|
|
22878
|
+
"acceptance",
|
|
22879
|
+
"findings",
|
|
22880
|
+
"sources",
|
|
22881
|
+
"tasks",
|
|
22882
|
+
"removeTasks",
|
|
22883
|
+
"taskIds"
|
|
22884
|
+
]);
|
|
22885
|
+
function ensureArray(value, separator = ",") {
|
|
22886
|
+
if (value === void 0 || value === null) return void 0;
|
|
22887
|
+
if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : String(v));
|
|
22888
|
+
if (typeof value === "string") return value.split(separator).map((s) => s.trim()).filter(Boolean);
|
|
22889
|
+
return [String(value)];
|
|
22890
|
+
}
|
|
22185
22891
|
function sanitizeParams(params, projectRoot, context) {
|
|
22186
22892
|
if (!params) {
|
|
22187
22893
|
return params;
|
|
@@ -22241,6 +22947,11 @@ function sanitizeParams(params, projectRoot, context) {
|
|
|
22241
22947
|
continue;
|
|
22242
22948
|
}
|
|
22243
22949
|
}
|
|
22950
|
+
for (const key of Object.keys(sanitized)) {
|
|
22951
|
+
if (ARRAY_PARAMS.has(key) && sanitized[key] !== void 0) {
|
|
22952
|
+
sanitized[key] = ensureArray(sanitized[key]);
|
|
22953
|
+
}
|
|
22954
|
+
}
|
|
22244
22955
|
return sanitized;
|
|
22245
22956
|
}
|
|
22246
22957
|
|
|
@@ -24175,6 +24886,142 @@ function createProtocolEnforcement(strictMode = true) {
|
|
|
24175
24886
|
|
|
24176
24887
|
// src/dispatch/adapters/mcp.ts
|
|
24177
24888
|
init_audit();
|
|
24889
|
+
|
|
24890
|
+
// src/dispatch/lib/projections.ts
|
|
24891
|
+
var PROJECTIONS = {
|
|
24892
|
+
minimal: {
|
|
24893
|
+
allowedDomains: ["tasks", "session", "admin"],
|
|
24894
|
+
excludeFields: ["notes", "history", "metadata._internal", "auditLog"],
|
|
24895
|
+
maxDepth: 2
|
|
24896
|
+
},
|
|
24897
|
+
standard: {
|
|
24898
|
+
allowedDomains: ["tasks", "session", "admin", "memory", "check", "pipeline", "tools", "validate"],
|
|
24899
|
+
excludeFields: ["metadata._internal", "auditLog"],
|
|
24900
|
+
maxDepth: 4
|
|
24901
|
+
},
|
|
24902
|
+
orchestrator: {
|
|
24903
|
+
allowedDomains: [
|
|
24904
|
+
"tasks",
|
|
24905
|
+
"session",
|
|
24906
|
+
"admin",
|
|
24907
|
+
"memory",
|
|
24908
|
+
"check",
|
|
24909
|
+
"pipeline",
|
|
24910
|
+
"orchestrate",
|
|
24911
|
+
"tools",
|
|
24912
|
+
"sharing",
|
|
24913
|
+
"nexus",
|
|
24914
|
+
"validate",
|
|
24915
|
+
"lifecycle",
|
|
24916
|
+
"release",
|
|
24917
|
+
"system"
|
|
24918
|
+
],
|
|
24919
|
+
maxDepth: 8
|
|
24920
|
+
}
|
|
24921
|
+
};
|
|
24922
|
+
var VALID_TIERS = /* @__PURE__ */ new Set(["minimal", "standard", "orchestrator"]);
|
|
24923
|
+
function resolveTier(params, sessionScope) {
|
|
24924
|
+
const mvi = params?._mviTier;
|
|
24925
|
+
if (typeof mvi === "string" && VALID_TIERS.has(mvi)) return mvi;
|
|
24926
|
+
if (sessionScope?.type === "epic") return "orchestrator";
|
|
24927
|
+
return "standard";
|
|
24928
|
+
}
|
|
24929
|
+
|
|
24930
|
+
// src/dispatch/middleware/projection.ts
|
|
24931
|
+
function pruneDepth(data, maxDepth, currentDepth = 0) {
|
|
24932
|
+
if (data === null || data === void 0 || typeof data !== "object") {
|
|
24933
|
+
return data;
|
|
24934
|
+
}
|
|
24935
|
+
if (currentDepth >= maxDepth) {
|
|
24936
|
+
if (Array.isArray(data)) {
|
|
24937
|
+
return `[Array(${data.length})]`;
|
|
24938
|
+
}
|
|
24939
|
+
return "[Object]";
|
|
24940
|
+
}
|
|
24941
|
+
if (Array.isArray(data)) {
|
|
24942
|
+
return data.map((item) => pruneDepth(item, maxDepth, currentDepth + 1));
|
|
24943
|
+
}
|
|
24944
|
+
const result = {};
|
|
24945
|
+
for (const [key, value] of Object.entries(data)) {
|
|
24946
|
+
result[key] = pruneDepth(value, maxDepth, currentDepth + 1);
|
|
24947
|
+
}
|
|
24948
|
+
return result;
|
|
24949
|
+
}
|
|
24950
|
+
function applyProjection(data, config) {
|
|
24951
|
+
if (!data || typeof data !== "object") return data;
|
|
24952
|
+
let result = data;
|
|
24953
|
+
if (config.excludeFields?.length) {
|
|
24954
|
+
const obj = { ...data };
|
|
24955
|
+
for (const field of config.excludeFields) {
|
|
24956
|
+
const parts = field.split(".");
|
|
24957
|
+
if (parts.length === 1) {
|
|
24958
|
+
delete obj[parts[0]];
|
|
24959
|
+
} else {
|
|
24960
|
+
let current = obj;
|
|
24961
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
24962
|
+
const val = current?.[parts[i]];
|
|
24963
|
+
if (val && typeof val === "object") {
|
|
24964
|
+
current[parts[i]] = { ...val };
|
|
24965
|
+
current = current[parts[i]];
|
|
24966
|
+
} else {
|
|
24967
|
+
current = void 0;
|
|
24968
|
+
break;
|
|
24969
|
+
}
|
|
24970
|
+
}
|
|
24971
|
+
if (current) {
|
|
24972
|
+
delete current[parts[parts.length - 1]];
|
|
24973
|
+
}
|
|
24974
|
+
}
|
|
24975
|
+
}
|
|
24976
|
+
result = obj;
|
|
24977
|
+
}
|
|
24978
|
+
if (config.maxDepth !== void 0) {
|
|
24979
|
+
result = pruneDepth(result, config.maxDepth);
|
|
24980
|
+
}
|
|
24981
|
+
return result;
|
|
24982
|
+
}
|
|
24983
|
+
function createProjectionMiddleware() {
|
|
24984
|
+
return async (req, next) => {
|
|
24985
|
+
const session = getBoundSession();
|
|
24986
|
+
const tier = resolveTier(req.params, session?.scope ?? null);
|
|
24987
|
+
const config = PROJECTIONS[tier];
|
|
24988
|
+
if (req.params) {
|
|
24989
|
+
delete req.params["_mviTier"];
|
|
24990
|
+
}
|
|
24991
|
+
if (req.source === "mcp" && req.domain === "tasks" && req.operation === "list") {
|
|
24992
|
+
if (req.params && req.params["compact"] === void 0) {
|
|
24993
|
+
req.params["compact"] = true;
|
|
24994
|
+
}
|
|
24995
|
+
}
|
|
24996
|
+
if (!config.allowedDomains.includes(req.domain)) {
|
|
24997
|
+
return {
|
|
24998
|
+
_meta: {
|
|
24999
|
+
gateway: req.gateway,
|
|
25000
|
+
domain: req.domain,
|
|
25001
|
+
operation: req.operation,
|
|
25002
|
+
version: "1.0.0",
|
|
25003
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25004
|
+
duration_ms: 0,
|
|
25005
|
+
source: req.source,
|
|
25006
|
+
requestId: req.requestId
|
|
25007
|
+
},
|
|
25008
|
+
success: false,
|
|
25009
|
+
error: {
|
|
25010
|
+
code: "E_INVALID_OPERATION",
|
|
25011
|
+
exitCode: 2,
|
|
25012
|
+
message: `Operation not available at '${tier}' tier. Domain '${req.domain}' requires a higher tier.`
|
|
25013
|
+
}
|
|
25014
|
+
};
|
|
25015
|
+
}
|
|
25016
|
+
const response = await next();
|
|
25017
|
+
if (response.success && response.data !== void 0) {
|
|
25018
|
+
response.data = applyProjection(response.data, config);
|
|
25019
|
+
}
|
|
25020
|
+
return response;
|
|
25021
|
+
};
|
|
25022
|
+
}
|
|
25023
|
+
|
|
25024
|
+
// src/dispatch/adapters/mcp.ts
|
|
24178
25025
|
init_paths();
|
|
24179
25026
|
var _dispatcher = null;
|
|
24180
25027
|
function initMcpDispatcher(config = {}) {
|
|
@@ -24187,6 +25034,8 @@ function initMcpDispatcher(config = {}) {
|
|
|
24187
25034
|
createSessionResolver(),
|
|
24188
25035
|
// T4959: session identity first
|
|
24189
25036
|
createSanitizer(() => getProjectRoot()),
|
|
25037
|
+
createProjectionMiddleware(),
|
|
25038
|
+
// T5096: MVI tier-based domain/field projection
|
|
24190
25039
|
createFieldFilter(),
|
|
24191
25040
|
createRateLimiter(config.rateLimiting),
|
|
24192
25041
|
createVerificationGates(strictMode),
|