@cleocode/core 2026.4.2 → 2026.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22134,7 +22134,7 @@ async function endSession(options = {}, cwd, accessor) {
22134
22134
  session.endedAt = (/* @__PURE__ */ new Date()).toISOString();
22135
22135
  const duration3 = Math.floor((Date.now() - new Date(session.startedAt).getTime()) / 1e3);
22136
22136
  const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
22137
- hooks2.dispatch("SessionEnd", cwd ?? process.cwd(), {
22137
+ await hooks2.dispatch("SessionEnd", cwd ?? process.cwd(), {
22138
22138
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22139
22139
  sessionId: session.id,
22140
22140
  duration: duration3,
@@ -22143,7 +22143,7 @@ async function endSession(options = {}, cwd, accessor) {
22143
22143
  }).catch(() => {
22144
22144
  });
22145
22145
  const { bridgeSessionToMemory: bridgeSessionToMemory2 } = await Promise.resolve().then(() => (init_session_memory_bridge(), session_memory_bridge_exports));
22146
- bridgeSessionToMemory2(cwd ?? process.cwd(), {
22146
+ await bridgeSessionToMemory2(cwd ?? process.cwd(), {
22147
22147
  sessionId: session.id,
22148
22148
  scope: options.sessionId ? session.scope.type : session.scope.epicId ? `epic:${session.scope.epicId}` : session.scope.type,
22149
22149
  tasksCompleted: session.tasksCompleted || [],
@@ -22156,6 +22156,11 @@ async function endSession(options = {}, cwd, accessor) {
22156
22156
  }
22157
22157
  const acc = accessor ?? await getAccessor(cwd);
22158
22158
  await acc.upsertSingleSession(session);
22159
+ try {
22160
+ const { refreshMemoryBridge: refreshMemoryBridge2 } = await Promise.resolve().then(() => (init_memory_bridge(), memory_bridge_exports));
22161
+ await refreshMemoryBridge2(cwd ?? process.cwd());
22162
+ } catch {
22163
+ }
22159
22164
  return session;
22160
22165
  }
22161
22166
  async function sessionStatus(cwd, accessor) {
@@ -26897,6 +26902,208 @@ var init_complete = __esm({
26897
26902
  }
26898
26903
  });
26899
26904
 
26905
+ // packages/core/src/validation/validation-rules.ts
26906
+ var validation_rules_exports = {};
26907
+ __export(validation_rules_exports, {
26908
+ hasErrors: () => hasErrors,
26909
+ validateHierarchy: () => validateHierarchy,
26910
+ validateIdUniqueness: () => validateIdUniqueness,
26911
+ validateNewTask: () => validateNewTask,
26912
+ validateNoDuplicateDescription: () => validateNoDuplicateDescription,
26913
+ validateStatusTransition: () => validateStatusTransition,
26914
+ validateTimestamps: () => validateTimestamps,
26915
+ validateTitleDescription: () => validateTitleDescription
26916
+ });
26917
+ function validateTitleDescription(title, description) {
26918
+ const violations = [];
26919
+ if (!title || title.trim().length === 0) {
26920
+ violations.push({
26921
+ rule: "title-required",
26922
+ field: "title",
26923
+ message: "Title is required and cannot be empty",
26924
+ severity: "error"
26925
+ });
26926
+ }
26927
+ if (!description || description.trim().length === 0) {
26928
+ violations.push({
26929
+ rule: "description-required",
26930
+ field: "description",
26931
+ message: "Description is required and cannot be empty",
26932
+ severity: "error"
26933
+ });
26934
+ }
26935
+ if (title && description && title.trim().toLowerCase() === description.trim().toLowerCase()) {
26936
+ violations.push({
26937
+ rule: "title-description-different",
26938
+ field: "description",
26939
+ message: "Title and description must be different",
26940
+ severity: "error"
26941
+ });
26942
+ }
26943
+ return violations;
26944
+ }
26945
+ function validateTimestamps(task) {
26946
+ const violations = [];
26947
+ const now = /* @__PURE__ */ new Date();
26948
+ const threshold = new Date(now.getTime() + 5 * 60 * 1e3);
26949
+ const timestampFields = [
26950
+ ["createdAt", task.createdAt],
26951
+ ["updatedAt", task.updatedAt],
26952
+ ["completedAt", task.completedAt],
26953
+ ["cancelledAt", task.cancelledAt]
26954
+ ];
26955
+ for (const [field, value] of timestampFields) {
26956
+ if (value) {
26957
+ const date6 = new Date(value);
26958
+ if (Number.isNaN(date6.getTime())) {
26959
+ violations.push({
26960
+ rule: "valid-timestamp",
26961
+ field,
26962
+ message: `Invalid timestamp format: ${value}`,
26963
+ severity: "error"
26964
+ });
26965
+ } else if (date6 > threshold) {
26966
+ violations.push({
26967
+ rule: "no-future-timestamps",
26968
+ field,
26969
+ message: `Timestamp ${value} is in the future`,
26970
+ severity: "error"
26971
+ });
26972
+ }
26973
+ }
26974
+ }
26975
+ return violations;
26976
+ }
26977
+ function validateIdUniqueness(taskId, existingIds) {
26978
+ if (existingIds.has(taskId)) {
26979
+ return [
26980
+ {
26981
+ rule: "unique-id",
26982
+ field: "id",
26983
+ message: `Task ID '${taskId}' already exists`,
26984
+ severity: "error"
26985
+ }
26986
+ ];
26987
+ }
26988
+ return [];
26989
+ }
26990
+ function validateNoDuplicateDescription(description, existingDescriptions, _excludeTaskId) {
26991
+ const normalizedNew = description.trim().toLowerCase();
26992
+ for (const existing of existingDescriptions) {
26993
+ if (existing.trim().toLowerCase() === normalizedNew) {
26994
+ return [
26995
+ {
26996
+ rule: "no-duplicate-description",
26997
+ field: "description",
26998
+ message: "A task with this exact description already exists",
26999
+ severity: "warning"
27000
+ }
27001
+ ];
27002
+ }
27003
+ }
27004
+ return [];
27005
+ }
27006
+ function validateHierarchy(parentId, tasks2, _taskType, limits) {
27007
+ const violations = [];
27008
+ if (!parentId) {
27009
+ return violations;
27010
+ }
27011
+ const maxDepth = limits?.maxDepth ?? 3;
27012
+ const maxSiblings = limits?.maxSiblings ?? 0;
27013
+ const parent = tasks2.find((t) => t.id === parentId);
27014
+ if (!parent) {
27015
+ violations.push({
27016
+ rule: "parent-exists",
27017
+ field: "parentId",
27018
+ message: `Parent task '${parentId}' not found`,
27019
+ severity: "error"
27020
+ });
27021
+ return violations;
27022
+ }
27023
+ let depth = 1;
27024
+ let current = parent;
27025
+ while (current.parentId) {
27026
+ depth++;
27027
+ const nextParent = tasks2.find((t) => t.id === current.parentId);
27028
+ if (!nextParent) break;
27029
+ current = nextParent;
27030
+ }
27031
+ if (depth > maxDepth - 1) {
27032
+ violations.push({
27033
+ rule: "max-depth",
27034
+ field: "parentId",
27035
+ message: `Maximum hierarchy depth of ${maxDepth} exceeded (epic -> task -> subtask)`,
27036
+ severity: "error"
27037
+ });
27038
+ }
27039
+ const siblingCount = tasks2.filter((t) => t.parentId === parentId).length;
27040
+ if (maxSiblings > 0 && siblingCount >= maxSiblings) {
27041
+ violations.push({
27042
+ rule: "max-siblings",
27043
+ field: "parentId",
27044
+ message: `Parent '${parentId}' already has ${siblingCount} children (max ${maxSiblings})`,
27045
+ severity: "error"
27046
+ });
27047
+ }
27048
+ return violations;
27049
+ }
27050
+ function validateStatusTransition(currentStatus, newStatus) {
27051
+ const validTransitions = {
27052
+ pending: ["active", "blocked", "done", "cancelled"],
27053
+ active: ["pending", "blocked", "done", "cancelled"],
27054
+ blocked: ["pending", "active", "done", "cancelled"],
27055
+ done: ["pending", "active"],
27056
+ // restore (alias: reopen)
27057
+ cancelled: ["pending"]
27058
+ // restore (alias: uncancel)
27059
+ };
27060
+ const allowed = validTransitions[currentStatus];
27061
+ if (!allowed) {
27062
+ return [
27063
+ {
27064
+ rule: "valid-status-transition",
27065
+ field: "status",
27066
+ message: `Unknown current status: '${currentStatus}'`,
27067
+ severity: "error"
27068
+ }
27069
+ ];
27070
+ }
27071
+ if (!allowed.includes(newStatus)) {
27072
+ return [
27073
+ {
27074
+ rule: "valid-status-transition",
27075
+ field: "status",
27076
+ message: `Cannot transition from '${currentStatus}' to '${newStatus}'. Valid: ${allowed.join(", ")}`,
27077
+ severity: "error"
27078
+ }
27079
+ ];
27080
+ }
27081
+ return [];
27082
+ }
27083
+ function validateNewTask(task, existingIds, existingDescriptions, existingTasks, limits) {
27084
+ const violations = [];
27085
+ violations.push(...validateTitleDescription(task.title, task.description));
27086
+ violations.push(...validateTimestamps(task));
27087
+ if (task.id) {
27088
+ violations.push(...validateIdUniqueness(task.id, existingIds));
27089
+ }
27090
+ if (task.description) {
27091
+ violations.push(...validateNoDuplicateDescription(task.description, existingDescriptions));
27092
+ }
27093
+ if (task.parentId) {
27094
+ violations.push(...validateHierarchy(task.parentId, existingTasks, task.type, limits));
27095
+ }
27096
+ return violations;
27097
+ }
27098
+ function hasErrors(violations) {
27099
+ return violations.some((v) => v.severity === "error");
27100
+ }
27101
+ var init_validation_rules = __esm({
27102
+ "packages/core/src/validation/validation-rules.ts"() {
27103
+ "use strict";
27104
+ }
27105
+ });
27106
+
26900
27107
  // packages/core/src/tasks/update.ts
26901
27108
  var update_exports = {};
26902
27109
  __export(update_exports, {
@@ -26946,6 +27153,11 @@ async function updateTask(options, cwd, accessor) {
26946
27153
  }
26947
27154
  if (options.status !== void 0) {
26948
27155
  validateStatus(options.status);
27156
+ const { validateStatusTransition: validateStatusTransition3 } = await Promise.resolve().then(() => (init_validation_rules(), validation_rules_exports));
27157
+ const transitionViolations = validateStatusTransition3(task.status, options.status);
27158
+ if (transitionViolations.length > 0) {
27159
+ throw new CleoError(6 /* VALIDATION_ERROR */, transitionViolations[0].message);
27160
+ }
26949
27161
  const oldStatus = task.status;
26950
27162
  task.status = options.status;
26951
27163
  changes.push("status");
@@ -65261,7 +65473,7 @@ __export(validation_exports, {
65261
65473
  validatePhaseTimestamps: () => validatePhaseTimestamps,
65262
65474
  validateSessionNote: () => validateSessionNote,
65263
65475
  validateSingleActivePhase: () => validateSingleActivePhase,
65264
- validateStatusTransition: () => validateStatusTransition,
65476
+ validateStatusTransition: () => validateStatusTransition2,
65265
65477
  validateTask: () => validateTask,
65266
65478
  validateTitle: () => validateTitle2
65267
65479
  });
@@ -66031,9 +66243,9 @@ function detectDrift(mode = "full", projectRoot = ".") {
66031
66243
  }
66032
66244
  }
66033
66245
  }
66034
- const hasErrors = issues.some((i) => i.severity === "error");
66246
+ const hasErrors2 = issues.some((i) => i.severity === "error");
66035
66247
  const hasWarnings = issues.some((i) => i.severity === "warning");
66036
- const exitCode = hasErrors ? 2 : hasWarnings ? 1 : 0;
66248
+ const exitCode = hasErrors2 ? 2 : hasWarnings ? 1 : 0;
66037
66249
  return { mode, issues, exitCode };
66038
66250
  }
66039
66251
  function shouldRunDriftDetection(enabled = true, autoCheck = false, command, criticalCommands = []) {
@@ -66529,7 +66741,7 @@ var STATUS_TRANSITIONS = {
66529
66741
  cancelled: ["pending"],
66530
66742
  archived: []
66531
66743
  };
66532
- function validateStatusTransition(oldStatus, newStatus) {
66744
+ function validateStatusTransition2(oldStatus, newStatus) {
66533
66745
  if (oldStatus === newStatus) {
66534
66746
  return { valid: true, errors: [], warnings: [] };
66535
66747
  }