@cleocode/cleo 2026.3.73 → 2026.3.74

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/mcp/index.js CHANGED
@@ -632,6 +632,13 @@ var init_discovery = __esm({
632
632
  });
633
633
 
634
634
  // packages/core/src/logger.ts
635
+ var logger_exports = {};
636
+ __export(logger_exports, {
637
+ closeLogger: () => closeLogger,
638
+ getLogDir: () => getLogDir,
639
+ getLogger: () => getLogger,
640
+ initLogger: () => initLogger
641
+ });
635
642
  import { existsSync as existsSync2 } from "node:fs";
636
643
  import { dirname, join as join3 } from "node:path";
637
644
  import pino from "pino";
@@ -45251,8 +45258,8 @@ var init_checksum = __esm({
45251
45258
  });
45252
45259
 
45253
45260
  // packages/core/src/migration/logger.ts
45254
- var logger_exports = {};
45255
- __export(logger_exports, {
45261
+ var logger_exports2 = {};
45262
+ __export(logger_exports2, {
45256
45263
  MigrationLogger: () => MigrationLogger,
45257
45264
  createMigrationLogger: () => createMigrationLogger,
45258
45265
  getLatestMigrationLog: () => getLatestMigrationLog,
@@ -48205,1009 +48212,1911 @@ var init_transfer = __esm({
48205
48212
  }
48206
48213
  });
48207
48214
 
48208
- // packages/core/src/nexus/index.ts
48209
- var nexus_exports = {};
48210
- __export(nexus_exports, {
48211
- blockingAnalysis: () => blockingAnalysis,
48212
- buildGlobalGraph: () => buildGlobalGraph,
48213
- canExecute: () => canExecute,
48214
- canRead: () => canRead,
48215
- canWrite: () => canWrite,
48216
- checkPermission: () => checkPermission,
48217
- checkPermissionDetail: () => checkPermissionDetail,
48218
- criticalPath: () => criticalPath,
48219
- discoverRelated: () => discoverRelated,
48220
- executeTransfer: () => executeTransfer,
48221
- extractKeywords: () => extractKeywords2,
48222
- generateProjectHash: () => generateProjectHash,
48223
- getCurrentProject: () => getCurrentProject,
48224
- getNexusCacheDir: () => getNexusCacheDir,
48225
- getNexusHome: () => getNexusHome,
48226
- getPermission: () => getPermission,
48227
- getProjectFromQuery: () => getProjectFromQuery,
48228
- getSharingStatus: () => getSharingStatus,
48229
- invalidateGraphCache: () => invalidateGraphCache,
48230
- nexusDeps: () => nexusDeps,
48231
- nexusGetProject: () => nexusGetProject,
48232
- nexusInit: () => nexusInit,
48233
- nexusList: () => nexusList,
48234
- nexusProjectExists: () => nexusProjectExists,
48235
- nexusReconcile: () => nexusReconcile,
48236
- nexusRegister: () => nexusRegister,
48237
- nexusSetPermission: () => nexusSetPermission,
48238
- nexusSync: () => nexusSync,
48239
- nexusSyncAll: () => nexusSyncAll,
48240
- nexusUnregister: () => nexusUnregister,
48241
- orphanDetection: () => orphanDetection,
48242
- parseQuery: () => parseQuery,
48243
- permissionLevel: () => permissionLevel,
48244
- previewTransfer: () => previewTransfer,
48245
- readRegistry: () => readRegistry,
48246
- readRegistryRequired: () => readRegistryRequired,
48247
- requirePermission: () => requirePermission,
48248
- resetNexusDbState: () => resetNexusDbState,
48249
- resolveCrossDeps: () => resolveCrossDeps,
48250
- resolveProjectPath: () => resolveProjectPath2,
48251
- resolveTask: () => resolveTask,
48252
- searchAcrossProjects: () => searchAcrossProjects,
48253
- setPermission: () => setPermission,
48254
- syncGitignore: () => syncGitignore,
48255
- validateSyntax: () => validateSyntax
48256
- });
48257
- var init_nexus = __esm({
48258
- "packages/core/src/nexus/index.ts"() {
48259
- "use strict";
48260
- init_deps();
48261
- init_discover();
48262
- init_hash();
48263
- init_permissions();
48264
- init_query3();
48265
- init_registry3();
48266
- init_sharing();
48267
- init_transfer();
48268
- }
48269
- });
48270
-
48271
- // packages/core/src/observability/types.ts
48272
- var PINO_LEVEL_VALUES;
48273
- var init_types = __esm({
48274
- "packages/core/src/observability/types.ts"() {
48275
- "use strict";
48276
- PINO_LEVEL_VALUES = {
48277
- TRACE: 10,
48278
- DEBUG: 20,
48279
- INFO: 30,
48280
- WARN: 40,
48281
- ERROR: 50,
48282
- FATAL: 60
48283
- };
48284
- }
48215
+ // packages/core/src/task-work/index.ts
48216
+ var task_work_exports = {};
48217
+ __export(task_work_exports, {
48218
+ currentTask: () => currentTask,
48219
+ getTaskHistory: () => getTaskHistory,
48220
+ getWorkHistory: () => getWorkHistory,
48221
+ startTask: () => startTask,
48222
+ stopTask: () => stopTask
48285
48223
  });
48286
-
48287
- // packages/core/src/observability/log-filter.ts
48288
- function compareLevels(a, b) {
48289
- return PINO_LEVEL_VALUES[a] - PINO_LEVEL_VALUES[b];
48224
+ async function currentTask(cwd, accessor) {
48225
+ const acc = accessor ?? await getAccessor(cwd);
48226
+ const focus = await acc.getMetaValue("focus_state");
48227
+ return {
48228
+ currentTask: focus?.currentTask ?? null,
48229
+ currentPhase: focus?.currentPhase ?? null,
48230
+ sessionNote: focus?.sessionNote ?? null,
48231
+ nextAction: focus?.nextAction ?? null
48232
+ };
48290
48233
  }
48291
- function matchesFilter(entry, filter) {
48292
- if (filter.level !== void 0 && entry.level !== filter.level) return false;
48293
- if (filter.minLevel !== void 0 && compareLevels(entry.level, filter.minLevel) < 0)
48294
- return false;
48295
- if (filter.since !== void 0 && entry.time < filter.since) return false;
48296
- if (filter.until !== void 0 && entry.time > filter.until) return false;
48297
- if (filter.subsystem !== void 0 && entry.subsystem !== filter.subsystem) return false;
48298
- if (filter.code !== void 0 && entry.code !== filter.code) return false;
48299
- if (filter.exitCode !== void 0 && entry.exitCode !== filter.exitCode) return false;
48300
- if (filter.pid !== void 0 && entry.pid !== filter.pid) return false;
48301
- if (filter.msgContains !== void 0) {
48302
- if (!entry.msg.toLowerCase().includes(filter.msgContains.toLowerCase())) return false;
48234
+ async function startTask(taskId, cwd, accessor) {
48235
+ if (!taskId) {
48236
+ throw new CleoError(2 /* INVALID_INPUT */, "Task ID is required");
48303
48237
  }
48304
- return true;
48305
- }
48306
- function filterEntries(entries, filter) {
48307
- return entries.filter((entry) => matchesFilter(entry, filter));
48308
- }
48309
- function paginate2(entries, limit, offset) {
48310
- const start = offset ?? 0;
48311
- if (limit !== void 0) {
48312
- return entries.slice(start, start + limit);
48238
+ const acc = accessor ?? await getAccessor(cwd);
48239
+ const task = await acc.loadSingleTask(taskId);
48240
+ if (!task) {
48241
+ throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${taskId}`, {
48242
+ fix: `Use 'cleo find "${taskId}"' to search`
48243
+ });
48313
48244
  }
48314
- return start > 0 ? entries.slice(start) : entries;
48315
- }
48316
- var init_log_filter = __esm({
48317
- "packages/core/src/observability/log-filter.ts"() {
48318
- "use strict";
48319
- init_types();
48245
+ const { tasks: allTasks } = await acc.queryTasks({});
48246
+ const unresolvedDeps = getUnresolvedDeps(taskId, allTasks);
48247
+ if (unresolvedDeps.length > 0) {
48248
+ throw new CleoError(
48249
+ 5 /* DEPENDENCY_ERROR */,
48250
+ `Task ${taskId} is blocked by unresolved dependencies: ${unresolvedDeps.join(", ")}`,
48251
+ {
48252
+ fix: `Complete blockers first: ${unresolvedDeps.map((d) => `cleo complete ${d}`).join(", ")}`
48253
+ }
48254
+ );
48320
48255
  }
48321
- });
48322
-
48323
- // packages/core/src/observability/log-parser.ts
48324
- function isValidLevel(level) {
48325
- return VALID_LEVELS.has(level);
48256
+ const focus = await acc.getMetaValue("focus_state") ?? {};
48257
+ const previousTask = focus.currentTask ?? null;
48258
+ focus.currentTask = taskId;
48259
+ focus.currentPhase = task.phase ?? null;
48260
+ const noteEntry = {
48261
+ note: `Started work on ${taskId}: ${task.title}`,
48262
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
48263
+ };
48264
+ if (!focus.sessionNotes) {
48265
+ focus.sessionNotes = [];
48266
+ }
48267
+ focus.sessionNotes.push(noteEntry);
48268
+ await acc.setMetaValue("focus_state", focus);
48269
+ await logOperation(
48270
+ "task_start",
48271
+ taskId,
48272
+ {
48273
+ previousTask,
48274
+ title: task.title
48275
+ },
48276
+ accessor
48277
+ );
48278
+ const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
48279
+ hooks2.dispatch("PreToolUse", cwd ?? process.cwd(), {
48280
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
48281
+ taskId,
48282
+ taskTitle: task.title
48283
+ }).catch(() => {
48284
+ });
48285
+ return {
48286
+ taskId,
48287
+ taskTitle: task.title,
48288
+ previousTask
48289
+ };
48326
48290
  }
48327
- function parseLogLine(line2) {
48328
- const trimmed = line2.trim();
48329
- if (!trimmed) return null;
48330
- let raw;
48331
- try {
48332
- raw = JSON.parse(trimmed);
48333
- } catch {
48334
- return null;
48291
+ async function stopTask(cwd, accessor) {
48292
+ const acc = accessor ?? await getAccessor(cwd);
48293
+ const focus = await acc.getMetaValue("focus_state");
48294
+ const previousTask = focus?.currentTask ?? null;
48295
+ if (!focus) {
48296
+ return { previousTask: null };
48335
48297
  }
48336
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
48337
- const level = raw.level;
48338
- if (typeof level !== "string" || !isValidLevel(level)) return null;
48339
- const time4 = raw.time;
48340
- if (typeof time4 !== "string") return null;
48341
- const pid = raw.pid;
48342
- if (typeof pid !== "number") return null;
48343
- const hostname4 = raw.hostname;
48344
- if (typeof hostname4 !== "string") return null;
48345
- const msg = raw.msg;
48346
- if (typeof msg !== "string") return null;
48347
- const extra = {};
48348
- for (const key of Object.keys(raw)) {
48349
- if (!KNOWN_FIELDS.has(key)) {
48350
- extra[key] = raw[key];
48351
- }
48298
+ const taskId = focus.currentTask;
48299
+ const task = taskId ? await acc.loadSingleTask(taskId) : void 0;
48300
+ focus.currentTask = null;
48301
+ focus.nextAction = null;
48302
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
48303
+ if (taskId && task) {
48304
+ const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
48305
+ hooks2.dispatch("PostToolUse", cwd ?? process.cwd(), {
48306
+ timestamp: now2,
48307
+ taskId,
48308
+ taskTitle: task.title,
48309
+ status: "done"
48310
+ }).catch(() => {
48311
+ });
48352
48312
  }
48353
- const entry = {
48354
- level,
48355
- time: time4,
48356
- pid,
48357
- hostname: hostname4,
48358
- msg,
48359
- extra
48360
- };
48361
- if (typeof raw.subsystem === "string") entry.subsystem = raw.subsystem;
48362
- if (typeof raw.code === "string") entry.code = raw.code;
48363
- if (typeof raw.exitCode === "number") entry.exitCode = raw.exitCode;
48364
- return entry;
48313
+ await acc.setMetaValue("focus_state", focus);
48314
+ await logOperation(
48315
+ "task_stop",
48316
+ previousTask ?? "none",
48317
+ {
48318
+ previousTask
48319
+ },
48320
+ accessor
48321
+ );
48322
+ return { previousTask };
48365
48323
  }
48366
- function parseLogLines(lines) {
48367
- const entries = [];
48368
- for (const line2 of lines) {
48369
- const entry = parseLogLine(line2);
48370
- if (entry) entries.push(entry);
48324
+ async function getWorkHistory(cwd, accessor) {
48325
+ const acc = accessor ?? await getAccessor(cwd);
48326
+ const focus = await acc.getMetaValue("focus_state");
48327
+ const notes = focus?.sessionNotes ?? [];
48328
+ const history = [];
48329
+ for (const note of notes) {
48330
+ const match = note.note.match(/^(?:Focus set to|Started work on) (T\d+)/);
48331
+ if (match) {
48332
+ history.push({
48333
+ taskId: match[1],
48334
+ timestamp: note.timestamp
48335
+ });
48336
+ }
48371
48337
  }
48372
- return entries;
48338
+ return history.reverse();
48373
48339
  }
48374
- var VALID_LEVELS, KNOWN_FIELDS;
48375
- var init_log_parser = __esm({
48376
- "packages/core/src/observability/log-parser.ts"() {
48340
+ var getTaskHistory;
48341
+ var init_task_work = __esm({
48342
+ "packages/core/src/task-work/index.ts"() {
48377
48343
  "use strict";
48378
- init_types();
48379
- VALID_LEVELS = new Set(Object.keys(PINO_LEVEL_VALUES));
48380
- KNOWN_FIELDS = /* @__PURE__ */ new Set([
48381
- "level",
48382
- "time",
48383
- "pid",
48384
- "hostname",
48385
- "msg",
48386
- "subsystem",
48387
- "code",
48388
- "exitCode"
48389
- ]);
48344
+ init_src();
48345
+ init_errors3();
48346
+ init_data_accessor();
48347
+ init_add();
48348
+ init_dependency_check();
48349
+ init_handlers();
48350
+ getTaskHistory = getWorkHistory;
48390
48351
  }
48391
48352
  });
48392
48353
 
48393
- // packages/core/src/observability/log-reader.ts
48394
- import { createReadStream, existsSync as existsSync56, readdirSync as readdirSync23, readFileSync as readFileSync38, statSync as statSync16 } from "node:fs";
48395
- import { join as join62 } from "node:path";
48396
- import { createInterface } from "node:readline";
48397
- function getProjectLogDir(cwd) {
48398
- const runtimeDir = getLogDir();
48399
- if (runtimeDir) return runtimeDir;
48400
- const cleoDir = getCleoDirAbsolute(cwd);
48401
- const logsDir = join62(cleoDir, "logs");
48402
- return existsSync56(logsDir) ? logsDir : null;
48354
+ // packages/core/src/tasks/complete.ts
48355
+ var complete_exports = {};
48356
+ __export(complete_exports, {
48357
+ completeTask: () => completeTask
48358
+ });
48359
+ function isVerificationGate(value) {
48360
+ return VERIFICATION_GATES.has(value);
48403
48361
  }
48404
- function getGlobalLogDir() {
48405
- return join62(getCleoHome(), "logs");
48362
+ async function loadCompletionEnforcement(cwd) {
48363
+ const isTest = !!process.env.VITEST;
48364
+ const config2 = await loadConfig(cwd);
48365
+ const acceptance = config2.enforcement?.acceptance;
48366
+ const verificationCfg = config2.verification;
48367
+ const acceptanceMode = acceptance?.mode ?? (isTest ? "off" : "block");
48368
+ const acceptanceRequiredForPriorities = acceptance?.requiredForPriorities ?? (isTest ? [] : ["critical", "high", "medium", "low"]);
48369
+ const rawVerificationEnabled = await getRawConfigValue("verification.enabled", cwd);
48370
+ const verificationEnabled = rawVerificationEnabled !== void 0 ? rawVerificationEnabled : !isTest;
48371
+ const verificationRequiredGates = (verificationCfg?.requiredGates ?? []).filter(isVerificationGate).length > 0 ? (verificationCfg?.requiredGates ?? []).filter(isVerificationGate) : DEFAULT_VERIFICATION_REQUIRED_GATES;
48372
+ const verificationMaxRounds = verificationCfg?.maxRounds ?? 5;
48373
+ const lifecycleMode = config2.lifecycle?.mode ?? (isTest ? "off" : "strict");
48374
+ return {
48375
+ acceptanceMode,
48376
+ acceptanceRequiredForPriorities,
48377
+ verificationEnabled,
48378
+ verificationRequiredGates,
48379
+ verificationMaxRounds,
48380
+ lifecycleMode
48381
+ };
48406
48382
  }
48407
- function scanLogDir(dir, includeMigration) {
48408
- if (!existsSync56(dir)) return [];
48409
- let fileNames;
48410
- try {
48411
- fileNames = readdirSync23(dir);
48412
- } catch {
48413
- return [];
48414
- }
48415
- const files = [];
48416
- for (const name2 of fileNames) {
48417
- const cleoMatch = name2.match(CLEO_LOG_PATTERN);
48418
- const isMigration = MIGRATION_LOG_PATTERN.test(name2);
48419
- if (!cleoMatch && (!isMigration || !includeMigration)) continue;
48420
- const filePath = join62(dir, name2);
48421
- let stat2;
48422
- try {
48423
- stat2 = statSync16(filePath);
48424
- } catch {
48425
- continue;
48426
- }
48427
- files.push({
48428
- path: filePath,
48429
- name: name2,
48430
- size: stat2.size,
48431
- mtime: stat2.mtime.toISOString(),
48432
- date: cleoMatch ? cleoMatch[1] : null,
48433
- isActive: false
48434
- // set below
48383
+ async function completeTask(options, cwd, accessor) {
48384
+ const acc = accessor ?? await getAccessor(cwd);
48385
+ const task = await acc.loadSingleTask(options.taskId);
48386
+ if (!task) {
48387
+ throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${options.taskId}`, {
48388
+ fix: `Use 'cleo find "${options.taskId}"' to search`
48435
48389
  });
48436
48390
  }
48437
- return files;
48438
- }
48439
- function discoverLogFiles(options, cwd) {
48440
- const scope = options?.scope ?? "project";
48441
- const includeMigration = options?.includeMigration ?? false;
48442
- const sinceDate = options?.since;
48443
- let files = [];
48444
- if (scope === "project" || scope === "both") {
48445
- const dir = getProjectLogDir(cwd);
48446
- if (dir) files.push(...scanLogDir(dir, includeMigration));
48391
+ await requireActiveSession("tasks.complete", cwd);
48392
+ const enforcement = await loadCompletionEnforcement(cwd);
48393
+ if (task.status === "done") {
48394
+ throw new CleoError(17 /* TASK_COMPLETED */, `Task ${options.taskId} is already completed`);
48447
48395
  }
48448
- if (scope === "global" || scope === "both") {
48449
- const dir = getGlobalLogDir();
48450
- files.push(...scanLogDir(dir, includeMigration));
48396
+ if (task.depends?.length) {
48397
+ const deps = await acc.loadTasks(task.depends);
48398
+ const incompleteDeps = deps.filter((d) => d.status !== "done" && d.status !== "cancelled").map((d) => d.id);
48399
+ if (incompleteDeps.length > 0) {
48400
+ throw new CleoError(
48401
+ 5 /* DEPENDENCY_ERROR */,
48402
+ `Task ${options.taskId} has incomplete dependencies: ${incompleteDeps.join(", ")}`,
48403
+ {
48404
+ fix: `Complete dependencies first: ${incompleteDeps.map((d) => `cleo complete ${d}`).join(", ")}`
48405
+ }
48406
+ );
48407
+ }
48451
48408
  }
48452
- if (sinceDate) {
48453
- files = files.filter((f) => f.mtime >= sinceDate);
48409
+ const acceptanceEnforcement = await createAcceptanceEnforcement(cwd);
48410
+ const completionValidation = acceptanceEnforcement.validateCompletion(task);
48411
+ if (!completionValidation.valid) {
48412
+ throw new CleoError(
48413
+ completionValidation.exitCode ?? 6 /* VALIDATION_ERROR */,
48414
+ completionValidation.error,
48415
+ { fix: completionValidation.fix }
48416
+ );
48454
48417
  }
48455
- files.sort((a, b) => b.mtime.localeCompare(a.mtime));
48456
- if (files.length > 0) {
48457
- files[0].isActive = true;
48418
+ if (enforcement.verificationEnabled && task.type !== "epic") {
48419
+ if (!task.verification) {
48420
+ throw new CleoError(
48421
+ 40 /* VERIFICATION_INIT_FAILED */,
48422
+ `Task ${options.taskId} is missing verification metadata`,
48423
+ {
48424
+ fix: `Initialize verification for ${options.taskId} before completion`
48425
+ }
48426
+ );
48427
+ }
48428
+ if (task.verification.round > enforcement.verificationMaxRounds) {
48429
+ throw new CleoError(
48430
+ 44 /* MAX_ROUNDS_EXCEEDED */,
48431
+ `Task ${options.taskId} exceeded verification max rounds (${enforcement.verificationMaxRounds})`,
48432
+ {
48433
+ fix: `Review failure log and resolve blockers before retrying completion`
48434
+ }
48435
+ );
48436
+ }
48437
+ const missingRequiredGates = enforcement.verificationRequiredGates.filter(
48438
+ (gate) => task.verification?.gates?.[gate] !== true
48439
+ );
48440
+ if (missingRequiredGates.length > 0 || task.verification.passed !== true) {
48441
+ const exitCode = enforcement.lifecycleMode === "strict" ? 80 /* LIFECYCLE_GATE_FAILED */ : 45 /* GATE_DEPENDENCY */;
48442
+ throw new CleoError(
48443
+ exitCode,
48444
+ `Task ${options.taskId} failed verification gates: ${missingRequiredGates.join(", ") || "verification.passed=false"}`,
48445
+ {
48446
+ fix: `Set required verification gates before completion: ${enforcement.verificationRequiredGates.join(", ")}`
48447
+ }
48448
+ );
48449
+ }
48458
48450
  }
48459
- return files;
48460
- }
48461
- function readLogFileLines(filePath) {
48462
- let content;
48463
- try {
48464
- content = readFileSync38(filePath, "utf-8");
48465
- } catch {
48466
- return [];
48451
+ const children = await acc.getChildren(options.taskId);
48452
+ const incompleteChildren = children.filter(
48453
+ (c) => c.status !== "done" && c.status !== "cancelled"
48454
+ );
48455
+ if (incompleteChildren.length > 0 && task.type === "epic") {
48456
+ if (!task.noAutoComplete) {
48457
+ throw new CleoError(
48458
+ 16 /* HAS_CHILDREN */,
48459
+ `Epic ${options.taskId} has ${incompleteChildren.length} incomplete children: ${incompleteChildren.map((c) => c.id).join(", ")}`,
48460
+ {
48461
+ fix: `Complete children first or use 'cleo update ${options.taskId} --no-auto-complete'`
48462
+ }
48463
+ );
48464
+ }
48467
48465
  }
48468
- if (!content.trim()) return [];
48469
- return content.split("\n").filter((line2) => line2.trim() !== "");
48470
- }
48471
- async function* streamLogFileLines(filePath) {
48472
- if (!existsSync56(filePath)) return;
48473
- const rl = createInterface({
48474
- input: createReadStream(filePath, { encoding: "utf-8" }),
48475
- crlfDelay: Infinity
48476
- });
48477
- for await (const line2 of rl) {
48478
- if (line2.trim()) yield line2;
48466
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
48467
+ const before = { ...task };
48468
+ task.status = "done";
48469
+ task.completedAt = now2;
48470
+ task.updatedAt = now2;
48471
+ if (options.notes) {
48472
+ const timestampedNote = `${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}: ${options.notes}`;
48473
+ if (!task.notes) task.notes = [];
48474
+ task.notes.push(timestampedNote);
48479
48475
  }
48480
- }
48481
- var CLEO_LOG_PATTERN, MIGRATION_LOG_PATTERN;
48482
- var init_log_reader = __esm({
48483
- "packages/core/src/observability/log-reader.ts"() {
48484
- "use strict";
48485
- init_logger();
48486
- init_paths();
48487
- CLEO_LOG_PATTERN = /^cleo\.(\d{4}-\d{2}-\d{2})\.(\d+)\.log$/;
48488
- MIGRATION_LOG_PATTERN = /^migration-.*\.jsonl$/;
48476
+ if (options.changeset) {
48477
+ if (!task.notes) task.notes = [];
48478
+ task.notes.push(`Changeset: ${options.changeset}`);
48489
48479
  }
48490
- });
48491
-
48492
- // packages/core/src/observability/index.ts
48493
- var observability_exports = {};
48494
- __export(observability_exports, {
48495
- PINO_LEVEL_VALUES: () => PINO_LEVEL_VALUES,
48496
- compareLevels: () => compareLevels,
48497
- discoverLogFiles: () => discoverLogFiles,
48498
- filterEntries: () => filterEntries,
48499
- getGlobalLogDir: () => getGlobalLogDir,
48500
- getLogSummary: () => getLogSummary,
48501
- getProjectLogDir: () => getProjectLogDir,
48502
- isValidLevel: () => isValidLevel,
48503
- matchesFilter: () => matchesFilter,
48504
- paginate: () => paginate2,
48505
- parseLogLine: () => parseLogLine,
48506
- parseLogLines: () => parseLogLines,
48507
- queryLogs: () => queryLogs,
48508
- readLogFileLines: () => readLogFileLines,
48509
- streamLogFileLines: () => streamLogFileLines,
48510
- streamLogs: () => streamLogs
48511
- });
48512
- function queryLogs(filter, options, cwd) {
48513
- const files = discoverLogFiles(options, cwd);
48514
- const filesPaths = files.map((f) => f.path);
48515
- let totalScanned = 0;
48516
- const allEntries = [];
48517
- for (const file2 of files) {
48518
- const lines = readLogFileLines(file2.path);
48519
- totalScanned += lines.length;
48520
- const parsed = parseLogLines(lines);
48521
- allEntries.push(...parsed);
48522
- }
48523
- const matched = filter ? filterEntries(allEntries, filter) : allEntries;
48524
- const totalMatched = matched.length;
48525
- const entries = paginate2(matched, filter?.limit, filter?.offset);
48526
- return {
48527
- entries,
48528
- totalScanned,
48529
- totalMatched,
48530
- files: filesPaths
48531
- };
48532
- }
48533
- async function* streamLogs(filter, options, cwd) {
48534
- const files = discoverLogFiles(options, cwd);
48535
- let yielded = 0;
48536
- const limit = filter?.limit;
48537
- for (const file2 of files) {
48538
- if (limit !== void 0 && yielded >= limit) break;
48539
- for await (const line2 of streamLogFileLines(file2.path)) {
48540
- const entry = parseLogLine(line2);
48541
- if (!entry) continue;
48542
- if (filter && !matchesFilter(entry, filter)) continue;
48543
- yield entry;
48544
- yielded++;
48545
- if (limit !== void 0 && yielded >= limit) return;
48480
+ const autoCompleted = [];
48481
+ const autoCompletedTasks = [];
48482
+ if (task.parentId) {
48483
+ const parent = await acc.loadSingleTask(task.parentId);
48484
+ if (parent && parent.type === "epic" && !parent.noAutoComplete) {
48485
+ const siblings = await acc.getChildren(parent.id);
48486
+ const allDone = siblings.every(
48487
+ (c) => c.id === task.id || c.status === "done" || c.status === "cancelled"
48488
+ );
48489
+ if (allDone) {
48490
+ parent.status = "done";
48491
+ parent.completedAt = now2;
48492
+ parent.updatedAt = now2;
48493
+ autoCompleted.push(parent.id);
48494
+ autoCompletedTasks.push(parent);
48495
+ }
48546
48496
  }
48547
48497
  }
48548
- }
48549
- function getLogSummary(options, cwd) {
48550
- const files = discoverLogFiles(options, cwd);
48551
- const byLevel = {};
48552
- const bySubsystem = {};
48553
- let totalEntries = 0;
48554
- let earliest = null;
48555
- let latest = null;
48556
- for (const file2 of files) {
48557
- const lines = readLogFileLines(file2.path);
48558
- const entries = parseLogLines(lines);
48559
- totalEntries += entries.length;
48560
- for (const entry of entries) {
48561
- byLevel[entry.level] = (byLevel[entry.level] ?? 0) + 1;
48562
- if (entry.subsystem) {
48563
- bySubsystem[entry.subsystem] = (bySubsystem[entry.subsystem] ?? 0) + 1;
48498
+ await acc.transaction(async (tx) => {
48499
+ await tx.upsertSingleTask(task);
48500
+ for (const parentTask of autoCompletedTasks) {
48501
+ await tx.upsertSingleTask(parentTask);
48502
+ }
48503
+ await tx.appendLog({
48504
+ id: `log-${Math.floor(Date.now() / 1e3)}-${(await import("node:crypto")).randomBytes(3).toString("hex")}`,
48505
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
48506
+ action: "task_completed",
48507
+ taskId: options.taskId,
48508
+ actor: "system",
48509
+ details: { title: task.title, previousStatus: before.status },
48510
+ before: null,
48511
+ after: { title: task.title, previousStatus: before.status }
48512
+ });
48513
+ });
48514
+ const dependents = await acc.getDependents(options.taskId);
48515
+ const unblockedTasks = [];
48516
+ for (const dep of dependents) {
48517
+ if (dep.status === "done" || dep.status === "cancelled") continue;
48518
+ if (dep.depends?.length) {
48519
+ const depDeps = await acc.loadTasks(dep.depends);
48520
+ const stillUnresolved = depDeps.filter(
48521
+ (d) => d.id !== options.taskId && d.status !== "done" && d.status !== "cancelled"
48522
+ );
48523
+ if (stillUnresolved.length === 0) {
48524
+ unblockedTasks.push({ id: dep.id, title: dep.title });
48564
48525
  }
48565
- if (earliest === null || entry.time < earliest) earliest = entry.time;
48566
- if (latest === null || entry.time > latest) latest = entry.time;
48526
+ } else {
48527
+ unblockedTasks.push({ id: dep.id, title: dep.title });
48567
48528
  }
48568
48529
  }
48530
+ Promise.resolve().then(() => (init_auto_extract(), auto_extract_exports)).then(
48531
+ ({ extractTaskCompletionMemory: extractTaskCompletionMemory2 }) => extractTaskCompletionMemory2(cwd ?? process.cwd(), task)
48532
+ ).catch(() => {
48533
+ });
48569
48534
  return {
48570
- totalEntries,
48571
- byLevel,
48572
- bySubsystem,
48573
- dateRange: earliest && latest ? { earliest, latest } : null,
48574
- files
48535
+ task,
48536
+ ...autoCompleted.length > 0 && { autoCompleted },
48537
+ ...unblockedTasks.length > 0 && { unblockedTasks }
48575
48538
  };
48576
48539
  }
48577
- var init_observability = __esm({
48578
- "packages/core/src/observability/index.ts"() {
48540
+ var DEFAULT_VERIFICATION_REQUIRED_GATES, VERIFICATION_GATES;
48541
+ var init_complete = __esm({
48542
+ "packages/core/src/tasks/complete.ts"() {
48579
48543
  "use strict";
48580
- init_log_filter();
48581
- init_log_parser();
48582
- init_log_reader();
48583
- init_types();
48584
- init_log_filter();
48585
- init_log_parser();
48586
- init_log_reader();
48544
+ init_src();
48545
+ init_config();
48546
+ init_errors3();
48547
+ init_session_enforcement();
48548
+ init_data_accessor();
48549
+ init_enforcement();
48550
+ DEFAULT_VERIFICATION_REQUIRED_GATES = [
48551
+ "implemented",
48552
+ "testsPassed",
48553
+ "qaPassed",
48554
+ "securityPassed",
48555
+ "documented"
48556
+ ];
48557
+ VERIFICATION_GATES = /* @__PURE__ */ new Set([
48558
+ "implemented",
48559
+ "testsPassed",
48560
+ "qaPassed",
48561
+ "cleanupDone",
48562
+ "securityPassed",
48563
+ "documented"
48564
+ ]);
48587
48565
  }
48588
48566
  });
48589
48567
 
48590
- // packages/core/src/phases/deps.ts
48591
- async function loadAllTasks3(cwd, accessor) {
48592
- const acc = accessor ?? await getAccessor(cwd);
48593
- const { tasks: tasks2 } = await acc.queryTasks({});
48594
- return tasks2;
48568
+ // packages/core/src/tasks/update.ts
48569
+ var update_exports = {};
48570
+ __export(update_exports, {
48571
+ updateTask: () => updateTask
48572
+ });
48573
+ function hasNonStatusDoneFields(options) {
48574
+ return NON_STATUS_DONE_FIELDS.some((field) => options[field] !== void 0);
48595
48575
  }
48596
- function buildGraph(tasks2) {
48597
- const graph = /* @__PURE__ */ new Map();
48598
- const taskMap = new Map(tasks2.map((t) => [t.id, t]));
48599
- for (const task of tasks2) {
48600
- graph.set(task.id, {
48601
- id: task.id,
48602
- title: task.title,
48603
- status: task.status,
48604
- depends: (task.depends ?? []).filter((d) => taskMap.has(d)),
48605
- dependents: []
48576
+ async function updateTask(options, cwd, accessor) {
48577
+ const acc = accessor ?? await getAccessor(cwd);
48578
+ const task = await acc.loadSingleTask(options.taskId);
48579
+ if (!task) {
48580
+ throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${options.taskId}`, {
48581
+ fix: `Use 'cleo find "${options.taskId}"' to search`
48606
48582
  });
48607
48583
  }
48608
- for (const [id, node] of graph) {
48609
- for (const depId of node.depends) {
48610
- const depNode = graph.get(depId);
48611
- if (depNode) {
48612
- depNode.dependents.push(id);
48584
+ await requireActiveSession("tasks.update", cwd);
48585
+ const changes = [];
48586
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
48587
+ const isStatusOnlyDoneTransition = options.status === "done" && task.status !== "done" && !hasNonStatusDoneFields(options);
48588
+ if (isStatusOnlyDoneTransition) {
48589
+ const result = await completeTask({ taskId: options.taskId }, cwd, accessor);
48590
+ return { task: result.task, changes: ["status"] };
48591
+ }
48592
+ if (options.status === "done" && task.status !== "done") {
48593
+ throw new CleoError(
48594
+ 6 /* VALIDATION_ERROR */,
48595
+ "status=done must use complete flow; do not combine with other update fields",
48596
+ {
48597
+ fix: `Run 'cleo complete ${options.taskId}' first, then apply additional updates with 'cleo update ${options.taskId} ...'`
48613
48598
  }
48614
- }
48599
+ );
48615
48600
  }
48616
- return graph;
48617
- }
48618
- async function getExecutionWaves(epicId, cwd, accessor) {
48619
- const allTasks = await loadAllTasks3(cwd, accessor);
48620
- let tasks2 = allTasks.filter((t) => t.status !== "done" && t.status !== "cancelled");
48621
- if (epicId) {
48622
- const epicTask = allTasks.find((t) => t.id === epicId);
48623
- if (!epicTask) {
48624
- throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
48625
- }
48626
- const childIds = new Set(allTasks.filter((t) => t.parentId === epicId).map((t) => t.id));
48627
- tasks2 = tasks2.filter((t) => childIds.has(t.id) || t.id === epicId);
48601
+ const enforcement = await createAcceptanceEnforcement(cwd);
48602
+ const updateValidation = enforcement.validateUpdate(task, { acceptance: options.acceptance });
48603
+ if (!updateValidation.valid) {
48604
+ throw new CleoError(
48605
+ updateValidation.exitCode ?? 6 /* VALIDATION_ERROR */,
48606
+ updateValidation.error,
48607
+ { fix: updateValidation.fix }
48608
+ );
48628
48609
  }
48629
- const graph = buildGraph(tasks2);
48630
- const waves = [];
48631
- const completed = /* @__PURE__ */ new Set();
48632
- const remaining = new Set(tasks2.map((t) => t.id));
48633
- let waveNum = 1;
48634
- while (remaining.size > 0) {
48635
- const wave = [];
48636
- for (const id of remaining) {
48637
- const node = graph.get(id);
48638
- if (!node) continue;
48639
- const allDepsComplete = node.depends.every(
48640
- (dep) => completed.has(dep) || !remaining.has(dep)
48641
- );
48642
- if (allDepsComplete) {
48643
- wave.push(id);
48644
- }
48645
- }
48646
- if (wave.length === 0) {
48647
- wave.push(...remaining);
48610
+ if (options.title !== void 0) {
48611
+ validateTitle(options.title);
48612
+ task.title = options.title;
48613
+ changes.push("title");
48614
+ }
48615
+ if (options.status !== void 0) {
48616
+ validateStatus(options.status);
48617
+ const oldStatus = task.status;
48618
+ task.status = options.status;
48619
+ changes.push("status");
48620
+ if (options.status === "done" && oldStatus !== "done") {
48621
+ task.completedAt = now2;
48648
48622
  }
48649
- waves.push({
48650
- wave: waveNum,
48651
- tasks: wave.map((id) => {
48652
- const node = graph.get(id);
48653
- return {
48654
- id,
48655
- title: node.title,
48656
- status: node.status,
48657
- depends: node.depends
48658
- };
48659
- })
48660
- });
48661
- for (const id of wave) {
48662
- completed.add(id);
48663
- remaining.delete(id);
48623
+ if (options.status === "cancelled" && oldStatus !== "cancelled") {
48624
+ task.cancelledAt = now2;
48664
48625
  }
48665
- waveNum++;
48666
48626
  }
48667
- return waves;
48668
- }
48669
- async function getCriticalPath2(taskId, cwd, accessor) {
48670
- const allTasks = await loadAllTasks3(cwd, accessor);
48671
- const task = allTasks.find((t) => t.id === taskId);
48672
- if (!task) {
48673
- throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${taskId}`);
48627
+ if (options.priority !== void 0) {
48628
+ const normalizedPriority = normalizePriority(options.priority);
48629
+ task.priority = normalizedPriority;
48630
+ changes.push("priority");
48674
48631
  }
48675
- const graph = buildGraph(allTasks);
48676
- const taskMap = new Map(allTasks.map((t) => [t.id, t]));
48677
- function findLongestPath(id, visited) {
48678
- if (visited.has(id)) return [];
48679
- visited.add(id);
48680
- const node = graph.get(id);
48681
- if (!node || node.dependents.length === 0) {
48682
- return [id];
48683
- }
48684
- let longest = [];
48685
- for (const depId of node.dependents) {
48686
- const path3 = findLongestPath(depId, new Set(visited));
48687
- if (path3.length > longest.length) {
48688
- longest = path3;
48689
- }
48690
- }
48691
- return [id, ...longest];
48632
+ if (options.type !== void 0) {
48633
+ validateTaskType(options.type);
48634
+ task.type = options.type;
48635
+ changes.push("type");
48692
48636
  }
48693
- const path2 = findLongestPath(taskId, /* @__PURE__ */ new Set());
48694
- return {
48695
- path: path2.map((id) => {
48696
- const t = taskMap.get(id);
48697
- return t ? { id: t.id, title: t.title, status: t.status } : { id, title: "Unknown", status: "unknown" };
48698
- }),
48699
- length: path2.length
48700
- };
48701
- }
48702
- var init_deps2 = __esm({
48703
- "packages/core/src/phases/deps.ts"() {
48704
- "use strict";
48705
- init_src();
48706
- init_errors3();
48707
- init_data_accessor();
48637
+ if (options.size !== void 0) {
48638
+ validateSize(options.size);
48639
+ task.size = options.size;
48640
+ changes.push("size");
48708
48641
  }
48709
- });
48710
-
48711
- // packages/core/src/orchestration/analyze.ts
48712
- function buildDependencyGraph(tasks2) {
48713
- const graph = /* @__PURE__ */ new Map();
48714
- for (const task of tasks2) {
48715
- if (!graph.has(task.id)) {
48716
- graph.set(task.id, /* @__PURE__ */ new Set());
48717
- }
48718
- if (task.depends) {
48719
- for (const dep of task.depends) {
48720
- graph.get(task.id).add(dep);
48721
- }
48722
- }
48642
+ if (options.phase !== void 0) {
48643
+ task.phase = options.phase;
48644
+ changes.push("phase");
48723
48645
  }
48724
- return graph;
48725
- }
48726
- function detectCircularDependencies(tasks2, graph) {
48727
- const depGraph = graph ?? buildDependencyGraph(tasks2);
48728
- const circularDeps = [];
48729
- const visited = /* @__PURE__ */ new Set();
48730
- const recursionStack = /* @__PURE__ */ new Set();
48731
- function dfs(taskId, path2) {
48732
- visited.add(taskId);
48733
- recursionStack.add(taskId);
48734
- const deps = depGraph.get(taskId) || /* @__PURE__ */ new Set();
48735
- for (const dep of deps) {
48736
- if (!visited.has(dep)) {
48737
- dfs(dep, [...path2, taskId]);
48738
- } else if (recursionStack.has(dep)) {
48739
- circularDeps.push([...path2, taskId, dep]);
48740
- }
48741
- }
48742
- recursionStack.delete(taskId);
48646
+ if (options.description !== void 0) {
48647
+ task.description = options.description;
48648
+ changes.push("description");
48743
48649
  }
48744
- for (const task of tasks2) {
48745
- if (!visited.has(task.id)) {
48746
- dfs(task.id, []);
48747
- }
48650
+ if (options.labels !== void 0) {
48651
+ if (options.labels.length) validateLabels(options.labels);
48652
+ task.labels = options.labels;
48653
+ changes.push("labels");
48748
48654
  }
48749
- return circularDeps;
48750
- }
48751
- function findMissingDependencies(children, allTasks) {
48752
- const childIds = new Set(children.map((t) => t.id));
48753
- const missingDeps = [];
48754
- for (const task of children) {
48755
- if (task.depends) {
48756
- for (const dep of task.depends) {
48757
- if (!childIds.has(dep) && !allTasks.find((t) => t.id === dep && t.status === "done")) {
48758
- missingDeps.push({ taskId: task.id, missingDep: dep });
48759
- }
48760
- }
48761
- }
48655
+ if (options.addLabels?.length) {
48656
+ validateLabels(options.addLabels);
48657
+ const existing = new Set(task.labels ?? []);
48658
+ for (const l of options.addLabels) existing.add(l.trim());
48659
+ task.labels = [...existing];
48660
+ changes.push("labels");
48762
48661
  }
48763
- return missingDeps;
48764
- }
48765
- function analyzeDependencies(children, allTasks) {
48766
- const graph = buildDependencyGraph(children);
48767
- const circularDependencies = detectCircularDependencies(children, graph);
48768
- const missingDependencies = findMissingDependencies(children, allTasks);
48769
- const dependencyGraph = {};
48770
- for (const [key, value] of graph.entries()) {
48771
- dependencyGraph[key] = Array.from(value);
48662
+ if (options.removeLabels?.length) {
48663
+ const toRemove = new Set(options.removeLabels.map((l) => l.trim()));
48664
+ task.labels = (task.labels ?? []).filter((l) => !toRemove.has(l));
48665
+ changes.push("labels");
48772
48666
  }
48773
- return {
48774
- dependencyGraph,
48775
- circularDependencies,
48776
- missingDependencies
48777
- };
48778
- }
48779
- var init_analyze = __esm({
48780
- "packages/core/src/orchestration/analyze.ts"() {
48781
- "use strict";
48667
+ if (options.depends !== void 0) {
48668
+ task.depends = options.depends;
48669
+ changes.push("depends");
48782
48670
  }
48783
- });
48784
-
48785
- // packages/core/src/orchestration/context.ts
48786
- import { existsSync as existsSync57, readFileSync as readFileSync39 } from "node:fs";
48787
- function countManifestEntries(projectRoot) {
48788
- const manifestPath = getManifestPath(projectRoot);
48789
- if (!existsSync57(manifestPath)) {
48790
- return 0;
48671
+ if (options.addDepends?.length) {
48672
+ const existing = new Set(task.depends ?? []);
48673
+ for (const d of options.addDepends) existing.add(d.trim());
48674
+ task.depends = [...existing];
48675
+ changes.push("depends");
48791
48676
  }
48792
- try {
48793
- const content = readFileSync39(manifestPath, "utf-8");
48794
- return content.split("\n").filter((l) => l.trim()).length;
48795
- } catch {
48796
- return 0;
48677
+ if (options.removeDepends?.length) {
48678
+ const toRemove = new Set(options.removeDepends.map((d) => d.trim()));
48679
+ task.depends = (task.depends ?? []).filter((d) => !toRemove.has(d));
48680
+ changes.push("depends");
48797
48681
  }
48798
- }
48799
- function estimateContext(taskCount, projectRoot, epicId) {
48800
- const estimatedTokens = taskCount * 100;
48801
- const manifestEntries2 = countManifestEntries(projectRoot);
48802
- return {
48803
- epicId: epicId || null,
48804
- taskCount,
48805
- manifestEntries: manifestEntries2,
48806
- estimatedTokens,
48807
- recommendation: estimatedTokens > 5e3 ? "Consider using manifest summaries instead of full task details" : "Context usage is within recommended limits",
48808
- limits: {
48809
- orchestratorBudget: 1e4,
48810
- maxFilesPerAgent: 3,
48811
- currentUsage: estimatedTokens
48812
- }
48813
- };
48814
- }
48815
- var init_context2 = __esm({
48816
- "packages/core/src/orchestration/context.ts"() {
48817
- "use strict";
48818
- init_paths();
48682
+ if (options.notes !== void 0) {
48683
+ const timestampedNote = `${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}: ${options.notes}`;
48684
+ if (!task.notes) task.notes = [];
48685
+ task.notes.push(timestampedNote);
48686
+ changes.push("notes");
48819
48687
  }
48820
- });
48821
-
48822
- // packages/core/src/orchestration/waves.ts
48823
- function buildDependencyGraph2(tasks2) {
48824
- const graph = /* @__PURE__ */ new Map();
48825
- for (const task of tasks2) {
48826
- if (!graph.has(task.id)) {
48827
- graph.set(task.id, /* @__PURE__ */ new Set());
48688
+ if (options.acceptance !== void 0) {
48689
+ task.acceptance = options.acceptance;
48690
+ changes.push("acceptance");
48691
+ }
48692
+ if (options.files !== void 0) {
48693
+ task.files = options.files;
48694
+ changes.push("files");
48695
+ }
48696
+ if (options.blockedBy !== void 0) {
48697
+ task.blockedBy = options.blockedBy;
48698
+ changes.push("blockedBy");
48699
+ }
48700
+ if (options.noAutoComplete !== void 0) {
48701
+ task.noAutoComplete = options.noAutoComplete;
48702
+ changes.push("noAutoComplete");
48703
+ }
48704
+ if (options.pipelineStage !== void 0) {
48705
+ validatePipelineTransition(task.pipelineStage, options.pipelineStage);
48706
+ if (task.type === "epic" && task.pipelineStage) {
48707
+ await validateEpicStageAdvancement(
48708
+ {
48709
+ epicId: task.id,
48710
+ currentStage: task.pipelineStage,
48711
+ newStage: options.pipelineStage
48712
+ },
48713
+ acc,
48714
+ cwd
48715
+ );
48828
48716
  }
48829
- if (task.depends) {
48830
- for (const dep of task.depends) {
48831
- graph.get(task.id).add(dep);
48717
+ if (task.type !== "epic") {
48718
+ const epicAncestor = task.parentId ? await findEpicAncestor(task.parentId, acc) : null;
48719
+ const directParent = task.parentId ? await acc.loadSingleTask(task.parentId) : null;
48720
+ const epicToCheck = directParent?.type === "epic" ? directParent : epicAncestor;
48721
+ if (epicToCheck) {
48722
+ await validateChildStageCeiling(
48723
+ { childStage: options.pipelineStage, epicId: epicToCheck.id },
48724
+ acc,
48725
+ cwd
48726
+ );
48832
48727
  }
48833
48728
  }
48729
+ task.pipelineStage = options.pipelineStage;
48730
+ changes.push("pipelineStage");
48834
48731
  }
48835
- return graph;
48836
- }
48837
- function computeWaves(tasks2) {
48838
- const graph = buildDependencyGraph2(tasks2);
48839
- const waves = [];
48840
- const completed = /* @__PURE__ */ new Set();
48841
- for (const task of tasks2) {
48842
- if (task.status === "done" || task.status === "cancelled") {
48843
- completed.add(task.id);
48732
+ if (options.parentId !== void 0) {
48733
+ const newParentId = options.parentId || null;
48734
+ const currentParentId = task.parentId ?? null;
48735
+ if (newParentId !== currentParentId) {
48736
+ const originalType = task.type;
48737
+ if (!newParentId) {
48738
+ task.parentId = null;
48739
+ if (task.type === "subtask") task.type = "task";
48740
+ changes.push("parentId");
48741
+ if (task.type !== originalType) changes.push("type");
48742
+ } else {
48743
+ const newParent = await acc.loadSingleTask(newParentId);
48744
+ if (!newParent) {
48745
+ throw new CleoError(10 /* PARENT_NOT_FOUND */, `Parent task ${newParentId} not found`);
48746
+ }
48747
+ if (newParent.type === "subtask") {
48748
+ throw new CleoError(
48749
+ 13 /* INVALID_PARENT_TYPE */,
48750
+ `Cannot parent under subtask '${newParentId}'`
48751
+ );
48752
+ }
48753
+ const subtree = await acc.getSubtree(options.taskId);
48754
+ if (subtree.some((t) => t.id === newParentId)) {
48755
+ throw new CleoError(
48756
+ 14 /* CIRCULAR_REFERENCE */,
48757
+ `Moving '${options.taskId}' under '${newParentId}' would create a circular reference`
48758
+ );
48759
+ }
48760
+ const ancestors = await acc.getAncestorChain(newParentId);
48761
+ const parentDepth = ancestors.length;
48762
+ const config2 = await loadConfig(cwd);
48763
+ const policy = resolveHierarchyPolicy(config2);
48764
+ if (parentDepth + 1 >= policy.maxDepth) {
48765
+ throw new CleoError(
48766
+ 11 /* DEPTH_EXCEEDED */,
48767
+ `Maximum nesting depth ${policy.maxDepth} would be exceeded`
48768
+ );
48769
+ }
48770
+ task.parentId = newParentId;
48771
+ const newDepth = parentDepth + 1;
48772
+ if (newDepth === 1) task.type = "task";
48773
+ else if (newDepth >= 2) task.type = "subtask";
48774
+ changes.push("parentId");
48775
+ if (task.type !== originalType) changes.push("type");
48776
+ }
48844
48777
  }
48845
48778
  }
48846
- let remaining = tasks2.filter((t) => t.status !== "done" && t.status !== "cancelled");
48847
- let waveNumber = 1;
48848
- const maxWaves = 50;
48849
- while (remaining.length > 0 && waveNumber <= maxWaves) {
48850
- const waveTasks = remaining.filter((t) => {
48851
- const deps = graph.get(t.id) || /* @__PURE__ */ new Set();
48852
- return Array.from(deps).every((d) => completed.has(d));
48853
- });
48854
- if (waveTasks.length === 0) break;
48855
- waves.push({
48856
- waveNumber,
48857
- tasks: waveTasks.map((t) => t.id),
48858
- status: waveTasks.every((t) => completed.has(t.id)) ? "completed" : waveTasks.some((t) => t.status === "active") ? "in_progress" : "pending"
48859
- });
48860
- for (const t of waveTasks) {
48861
- completed.add(t.id);
48862
- }
48863
- remaining = remaining.filter((t) => !waveTasks.some((wt) => wt.id === t.id));
48864
- waveNumber++;
48779
+ if (changes.length === 0) {
48780
+ throw new CleoError(102 /* NO_CHANGE */, "No changes specified");
48865
48781
  }
48866
- if (remaining.length > 0) {
48867
- waves.push({
48868
- waveNumber,
48869
- tasks: remaining.map((t) => t.id),
48870
- status: "pending"
48782
+ task.updatedAt = now2;
48783
+ await acc.transaction(async (tx) => {
48784
+ await tx.upsertSingleTask(task);
48785
+ await tx.appendLog({
48786
+ id: `log-${Math.floor(Date.now() / 1e3)}-${(await import("node:crypto")).randomBytes(3).toString("hex")}`,
48787
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
48788
+ action: "task_updated",
48789
+ taskId: options.taskId,
48790
+ actor: "system",
48791
+ details: { changes, title: task.title },
48792
+ before: null,
48793
+ after: { changes, title: task.title }
48871
48794
  });
48872
- }
48873
- return waves;
48874
- }
48875
- async function getEnrichedWaves(epicId, cwd, accessor) {
48876
- const acc = accessor ?? await getAccessor(cwd);
48877
- const children = await acc.getChildren(epicId);
48878
- const waves = computeWaves(children);
48879
- const taskMap = new Map(children.map((t) => [t.id, t]));
48880
- const enrichedWaves = waves.map((w) => ({
48881
- ...w,
48882
- tasks: w.tasks.map((id) => ({
48883
- id,
48884
- title: taskMap.get(id)?.title || id,
48885
- status: taskMap.get(id)?.status || "unknown"
48886
- }))
48887
- }));
48888
- return {
48889
- epicId,
48890
- waves: enrichedWaves,
48891
- totalWaves: waves.length,
48892
- totalTasks: children.length
48893
- };
48795
+ });
48796
+ return { task, changes };
48894
48797
  }
48895
- var init_waves = __esm({
48896
- "packages/core/src/orchestration/waves.ts"() {
48798
+ var NON_STATUS_DONE_FIELDS;
48799
+ var init_update2 = __esm({
48800
+ "packages/core/src/tasks/update.ts"() {
48897
48801
  "use strict";
48802
+ init_src();
48803
+ init_config();
48804
+ init_errors3();
48805
+ init_session_enforcement();
48898
48806
  init_data_accessor();
48807
+ init_add();
48808
+ init_complete();
48809
+ init_enforcement();
48810
+ init_epic_enforcement();
48811
+ init_hierarchy_policy();
48812
+ init_pipeline_stage();
48813
+ NON_STATUS_DONE_FIELDS = [
48814
+ "title",
48815
+ "priority",
48816
+ "type",
48817
+ "size",
48818
+ "phase",
48819
+ "description",
48820
+ "labels",
48821
+ "addLabels",
48822
+ "removeLabels",
48823
+ "depends",
48824
+ "addDepends",
48825
+ "removeDepends",
48826
+ "notes",
48827
+ "acceptance",
48828
+ "files",
48829
+ "blockedBy",
48830
+ "parentId",
48831
+ "noAutoComplete",
48832
+ "pipelineStage"
48833
+ ];
48899
48834
  }
48900
48835
  });
48901
48836
 
48902
- // packages/core/src/orchestration/status.ts
48903
- function countByStatus(tasks2) {
48904
- return {
48905
- pending: tasks2.filter((t) => t.status === "pending").length,
48906
- active: tasks2.filter((t) => t.status === "active").length,
48907
- blocked: tasks2.filter((t) => t.status === "blocked").length,
48908
- done: tasks2.filter((t) => t.status === "done").length,
48909
- cancelled: tasks2.filter((t) => t.status === "cancelled").length
48910
- };
48911
- }
48912
- function computeEpicStatus(epicId, epicTitle, children) {
48913
- const waves = computeWaves(children);
48914
- const byStatus = countByStatus(children);
48915
- return {
48916
- epicId,
48917
- epicTitle,
48918
- totalTasks: children.length,
48919
- byStatus,
48920
- waves: waves.length,
48921
- currentWave: waves.find((w) => w.status !== "completed")?.waveNumber || null
48922
- };
48923
- }
48924
- function computeOverallStatus(tasks2) {
48925
- const epics = tasks2.filter(
48926
- (t) => !t.parentId && (t.type === "epic" || tasks2.some((c) => c.parentId === t.id))
48927
- );
48928
- return {
48929
- totalEpics: epics.length,
48930
- totalTasks: tasks2.length,
48931
- byStatus: {
48932
- pending: tasks2.filter((t) => t.status === "pending").length,
48933
- active: tasks2.filter((t) => t.status === "active").length,
48934
- blocked: tasks2.filter((t) => t.status === "blocked").length,
48935
- done: tasks2.filter((t) => t.status === "done").length
48936
- }
48937
- };
48938
- }
48939
- function computeProgress(tasks2) {
48940
- const total = tasks2.length;
48941
- const done = tasks2.filter((t) => t.status === "done").length;
48942
- const pending = tasks2.filter((t) => t.status === "pending").length;
48943
- const blocked = tasks2.filter((t) => t.status === "blocked").length;
48944
- const active = tasks2.filter((t) => t.status === "active").length;
48945
- return {
48946
- total,
48947
- done,
48948
- pending,
48949
- blocked,
48950
- active,
48951
- percentComplete: total > 0 ? Math.round(done / total * 100) : 0
48952
- };
48953
- }
48954
- function computeStartupSummary(epicId, epicTitle, children, readyCount) {
48955
- const waves = computeWaves(children);
48956
- const byStatus = countByStatus(children);
48957
- return {
48958
- epicId,
48959
- epicTitle,
48960
- initialized: true,
48961
- summary: {
48962
- totalTasks: children.length,
48963
- totalWaves: waves.length,
48964
- readyTasks: readyCount,
48965
- byStatus
48966
- },
48967
- firstWave: waves[0] || null
48968
- };
48969
- }
48970
- var init_status = __esm({
48971
- "packages/core/src/orchestration/status.ts"() {
48972
- "use strict";
48973
- init_waves();
48974
- }
48975
- });
48976
-
48977
- // packages/core/src/orchestration/index.ts
48978
- var orchestration_exports = {};
48979
- __export(orchestration_exports, {
48980
- analyzeDependencies: () => analyzeDependencies,
48981
- analyzeEpic: () => analyzeEpic,
48982
- autoDispatch: () => autoDispatch,
48983
- buildDependencyGraph: () => buildDependencyGraph,
48984
- computeEpicStatus: () => computeEpicStatus,
48985
- computeOverallStatus: () => computeOverallStatus,
48986
- computeProgress: () => computeProgress,
48987
- computeStartupSummary: () => computeStartupSummary,
48988
- countByStatus: () => countByStatus,
48989
- countManifestEntries: () => countManifestEntries,
48990
- detectCircularDependencies: () => detectCircularDependencies,
48991
- estimateContext: () => estimateContext,
48992
- findMissingDependencies: () => findMissingDependencies,
48993
- getNextTask: () => getNextTask,
48994
- getOrchestratorContext: () => getOrchestratorContext,
48995
- getReadyTasks: () => getReadyTasks2,
48996
- prepareSpawn: () => prepareSpawn,
48997
- resolveTokens: () => resolveTokens,
48998
- startOrchestration: () => startOrchestration,
48999
- validateSpawnOutput: () => validateSpawnOutput
49000
- });
49001
- async function startOrchestration(epicId, _cwd, accessor) {
49002
- const epic = await accessor.loadSingleTask(epicId);
49003
- if (!epic) {
49004
- throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
48837
+ // packages/core/src/nexus/workspace.ts
48838
+ function checkRateLimit(agentId) {
48839
+ const now2 = Date.now();
48840
+ const entry = rateLimitCounters.get(agentId);
48841
+ if (!entry || now2 - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
48842
+ rateLimitCounters.set(agentId, { count: 1, windowStart: now2 });
48843
+ return;
49005
48844
  }
49006
- if (epic.type !== "epic") {
48845
+ entry.count++;
48846
+ if (entry.count > RATE_LIMIT_MAX_OPS) {
49007
48847
  throw new CleoError(
49008
- 2 /* INVALID_INPUT */,
49009
- `Task ${epicId} is not an epic (type: ${epic.type})`
48848
+ 1 /* GENERAL_ERROR */,
48849
+ `Agent '${agentId}' exceeded rate limit: ${RATE_LIMIT_MAX_OPS} routing ops per ${RATE_LIMIT_WINDOW_MS / 1e3}s`
49010
48850
  );
49011
48851
  }
49012
- const session = {
49013
- epicId,
49014
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
49015
- status: "active",
49016
- currentWave: 1,
49017
- completedTasks: [],
49018
- spawnedAgents: []
49019
- };
49020
- return session;
49021
48852
  }
49022
- async function analyzeEpic(epicId, cwd, accessor) {
49023
- const epic = await accessor.loadSingleTask(epicId);
49024
- if (!epic) {
49025
- throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
48853
+ async function loadProjectACL(projectPath) {
48854
+ try {
48855
+ const { loadConfig: loadConfig4 } = await Promise.resolve().then(() => (init_config(), config_exports));
48856
+ const config2 = await loadConfig4(projectPath);
48857
+ const agents = config2?.authorizedAgents;
48858
+ if (Array.isArray(agents) && agents.length > 0) {
48859
+ return { authorizedAgents: agents };
48860
+ }
48861
+ } catch {
49026
48862
  }
49027
- const childTasks = await accessor.getChildren(epicId);
49028
- const waves = await getExecutionWaves(epicId, cwd, accessor);
49029
- const completedTasks = childTasks.filter((t) => t.status === "done").map((t) => t.id);
49030
- const blockedTasks = childTasks.filter((t) => t.status === "blocked").map((t) => t.id);
49031
- const completedSet = new Set(completedTasks);
49032
- const readyTasks = childTasks.filter((t) => {
49033
- if (t.status === "done" || t.status === "cancelled") return false;
49034
- const deps = t.depends ?? [];
49035
- return deps.every((d) => completedSet.has(d));
49036
- }).map((t) => t.id);
49037
- return {
49038
- epicId,
49039
- totalTasks: childTasks.length,
49040
- waves: waves.map((w) => ({
49041
- wave: w.wave,
49042
- tasks: w.tasks.map((t) => ({ id: t.id, title: t.title, status: t.status }))
49043
- })),
49044
- readyTasks,
49045
- blockedTasks,
49046
- completedTasks
49047
- };
49048
- }
49049
- async function getReadyTasks2(epicId, _cwd, accessor) {
49050
- const childTasks = await accessor.getChildren(epicId);
49051
- const { tasks: allTasks } = await accessor.queryTasks({});
49052
- const completedIds = new Set(allTasks.filter((t) => t.status === "done").map((t) => t.id));
49053
- return childTasks.filter((t) => t.status !== "done" && t.status !== "cancelled").map((task) => {
49054
- const deps = task.depends ?? [];
49055
- const unmetDeps = deps.filter((d) => !completedIds.has(d));
49056
- const protocol = autoDispatch(task);
49057
- return {
49058
- taskId: task.id,
49059
- title: task.title,
49060
- ready: unmetDeps.length === 0,
49061
- blockers: unmetDeps,
49062
- protocol
49063
- };
49064
- });
48863
+ return DEFAULT_ACL;
49065
48864
  }
49066
- async function getNextTask(epicId, cwd, accessor) {
49067
- const readyTasks = await getReadyTasks2(epicId, cwd, accessor);
49068
- const ready = readyTasks.filter((t) => t.ready);
49069
- if (ready.length === 0) return null;
49070
- return ready[0];
48865
+ function isAuthorized(acl, agentId) {
48866
+ if (acl.authorizedAgents.includes("*")) return true;
48867
+ return acl.authorizedAgents.includes(agentId);
49071
48868
  }
49072
- async function prepareSpawn(taskId, _cwd, accessor) {
49073
- const task = await accessor.loadSingleTask(taskId);
49074
- if (!task) {
49075
- throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${taskId}`);
48869
+ function parseDirective(message) {
48870
+ const content = message.content;
48871
+ const verbMatch = content.match(/^\/(\w+)/);
48872
+ if (!verbMatch) return null;
48873
+ const verb = verbMatch[1];
48874
+ const taskRefs = [];
48875
+ const pattern = new RegExp(TASK_REF_PATTERN.source, "g");
48876
+ for (const m of content.matchAll(pattern)) {
48877
+ taskRefs.push(`T${m[1]}`);
49076
48878
  }
49077
- const protocol = autoDispatch(task);
49078
- const prompt = buildSpawnPrompt(task, protocol);
49079
- const unresolvedTokens = findUnresolvedTokens(prompt);
49080
- return {
49081
- taskId,
49082
- protocol,
49083
- prompt,
49084
- tokenResolution: {
49085
- fullyResolved: unresolvedTokens.length === 0,
49086
- unresolvedTokens
48879
+ const metaRefs = message.metadata?.taskRefs;
48880
+ if (Array.isArray(metaRefs)) {
48881
+ for (const ref of metaRefs) {
48882
+ if (typeof ref === "string" && !taskRefs.includes(ref)) {
48883
+ taskRefs.push(ref);
48884
+ }
49087
48885
  }
48886
+ }
48887
+ if (taskRefs.length === 0) return null;
48888
+ return {
48889
+ verb,
48890
+ taskRefs,
48891
+ agentId: message.from,
48892
+ messageId: message.id,
48893
+ timestamp: message.timestamp
49088
48894
  };
49089
48895
  }
49090
- async function validateSpawnOutput(_taskId, output) {
49091
- const errors = [];
49092
- if (!output.file) {
49093
- errors.push("No output file specified");
49094
- }
49095
- if (!output.manifestEntry) {
49096
- errors.push("No manifest entry appended");
48896
+ async function routeDirective(directive) {
48897
+ checkRateLimit(directive.agentId);
48898
+ const results = [];
48899
+ const operation = VERB_TO_OPERATION[directive.verb];
48900
+ if (!operation) {
48901
+ return results;
49097
48902
  }
49098
- return { valid: errors.length === 0, errors };
49099
- }
49100
- async function getOrchestratorContext(epicId, _cwd, accessor) {
49101
- const epic = await accessor.loadSingleTask(epicId);
49102
- if (!epic) {
49103
- throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
48903
+ const projects = await nexusList();
48904
+ for (const taskRef of directive.taskRefs) {
48905
+ const result = await routeSingleTask(taskRef, directive, operation, projects);
48906
+ results.push(result);
49104
48907
  }
49105
- const children = await accessor.getChildren(epicId);
49106
- const completed = children.filter((t) => t.status === "done").length;
49107
- const inProgress = children.filter((t) => t.status === "active").length;
49108
- const blocked = children.filter((t) => t.status === "blocked").length;
49109
- const pending = children.filter((t) => t.status === "pending").length;
49110
- return {
49111
- epicId,
49112
- epicTitle: epic.title,
49113
- totalTasks: children.length,
49114
- completed,
49115
- inProgress,
49116
- blocked,
49117
- pending,
49118
- completionPercent: children.length > 0 ? Math.floor(completed * 100 / children.length) : 0
49119
- };
48908
+ return results;
49120
48909
  }
49121
- function autoDispatch(task) {
49122
- if (task.labels?.length) {
49123
- for (const [protocol, config2] of Object.entries(DISPATCH_MAP)) {
49124
- if (task.labels.some((l) => config2.labels.includes(l))) {
49125
- return protocol;
48910
+ async function routeSingleTask(taskId, directive, operation, projects) {
48911
+ let targetProject = null;
48912
+ let targetAccessor = null;
48913
+ for (const project of projects) {
48914
+ try {
48915
+ const acc = await getAccessor(project.path);
48916
+ const { tasks: tasks2 } = await acc.queryTasks({});
48917
+ const task = tasks2.find((t) => t.id === taskId);
48918
+ if (task) {
48919
+ targetProject = project;
48920
+ targetAccessor = acc;
48921
+ break;
49126
48922
  }
48923
+ } catch {
49127
48924
  }
49128
48925
  }
49129
- if (task.type === "epic") return "decomposition";
49130
- const text3 = `${task.title} ${task.description ?? ""}`.toLowerCase();
49131
- for (const [protocol, config2] of Object.entries(DISPATCH_MAP)) {
49132
- if (config2.keywords.some((kw) => text3.includes(kw))) {
49133
- return protocol;
49134
- }
48926
+ if (!targetProject || !targetAccessor) {
48927
+ return {
48928
+ success: false,
48929
+ project: "unknown",
48930
+ projectPath: "",
48931
+ taskId,
48932
+ operation,
48933
+ error: `Task ${taskId} not found in any registered project`
48934
+ };
48935
+ }
48936
+ const acl = await loadProjectACL(targetProject.path);
48937
+ if (!isAuthorized(acl, directive.agentId)) {
48938
+ return {
48939
+ success: false,
48940
+ project: targetProject.name,
48941
+ projectPath: targetProject.path,
48942
+ taskId,
48943
+ operation,
48944
+ error: `Agent '${directive.agentId}' not authorized to mutate project '${targetProject.name}'`
48945
+ };
48946
+ }
48947
+ try {
48948
+ await executeOperation(operation, taskId, targetProject.path, targetAccessor, directive);
48949
+ await logRouteAudit(directive, targetProject.name, taskId, operation, true);
48950
+ return {
48951
+ success: true,
48952
+ project: targetProject.name,
48953
+ projectPath: targetProject.path,
48954
+ taskId,
48955
+ operation
48956
+ };
48957
+ } catch (err) {
48958
+ const errorMsg = err instanceof Error ? err.message : String(err);
48959
+ await logRouteAudit(directive, targetProject.name, taskId, operation, false, errorMsg);
48960
+ return {
48961
+ success: false,
48962
+ project: targetProject.name,
48963
+ projectPath: targetProject.path,
48964
+ taskId,
48965
+ operation,
48966
+ error: errorMsg
48967
+ };
49135
48968
  }
49136
- return "implementation";
49137
48969
  }
49138
- function buildSpawnPrompt(task, protocol) {
49139
- const epicId = task.parentId ?? "none";
49140
- const date6 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
49141
- return [
49142
- `## Task: ${task.id}`,
49143
- `**Title**: ${task.title}`,
49144
- task.description ? `**Description**: ${task.description}` : "",
49145
- `**Protocol**: ${protocol}`,
49146
- `**Epic**: ${epicId}`,
49147
- `**Date**: ${date6}`,
49148
- "",
49149
- `### Instructions`,
49150
- `1. Start task: \`cleo start ${task.id}\``,
49151
- `2. Execute the ${protocol} protocol`,
49152
- `3. Write output file`,
49153
- `4. Append manifest entry to MANIFEST.jsonl`,
49154
- `5. Complete: \`cleo complete ${task.id}\``,
49155
- "",
49156
- task.acceptance?.length ? `### Acceptance Criteria
49157
- ${task.acceptance.map((a) => `- ${a}`).join("\n")}` : "",
49158
- task.depends?.length ? `### Dependencies
49159
- ${task.depends.map((d) => `- ${d}`).join("\n")}` : ""
49160
- ].filter(Boolean).join("\n");
48970
+ async function executeOperation(operation, taskId, projectPath, accessor, directive) {
48971
+ switch (operation) {
48972
+ case "tasks.start": {
48973
+ const { startTask: startTask3 } = await Promise.resolve().then(() => (init_task_work(), task_work_exports));
48974
+ await startTask3(taskId, projectPath, accessor);
48975
+ break;
48976
+ }
48977
+ case "tasks.complete": {
48978
+ const { completeTask: completeTask2 } = await Promise.resolve().then(() => (init_complete(), complete_exports));
48979
+ await completeTask2(
48980
+ { taskId, notes: `Completed via Conduit directive from ${directive.agentId}` },
48981
+ projectPath,
48982
+ accessor
48983
+ );
48984
+ break;
48985
+ }
48986
+ case "tasks.stop": {
48987
+ const { stopTask: stopTask3 } = await Promise.resolve().then(() => (init_task_work(), task_work_exports));
48988
+ await stopTask3(projectPath, accessor);
48989
+ break;
48990
+ }
48991
+ case "tasks.update": {
48992
+ const { updateTask: updateTask3 } = await Promise.resolve().then(() => (init_update2(), update_exports));
48993
+ await updateTask3(
48994
+ { taskId, notes: `Marked blocked via Conduit directive from ${directive.agentId}` },
48995
+ projectPath,
48996
+ accessor
48997
+ );
48998
+ break;
48999
+ }
49000
+ }
49161
49001
  }
49162
- function findUnresolvedTokens(prompt) {
49163
- const tokens = [];
49164
- const tokenRegex = /\{\{([A-Z_]+)\}\}/g;
49165
- for (const match of prompt.matchAll(tokenRegex)) {
49166
- tokens.push(match[1]);
49002
+ async function logRouteAudit(directive, projectName, taskId, operation, success2, error40) {
49003
+ try {
49004
+ const { getLogger: getLogger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
49005
+ const log11 = getLogger2("nexus.route");
49006
+ const level = success2 ? "info" : "warn";
49007
+ log11[level](
49008
+ {
49009
+ directive: directive.verb,
49010
+ agentId: directive.agentId,
49011
+ messageId: directive.messageId,
49012
+ project: projectName,
49013
+ taskId,
49014
+ operation,
49015
+ success: success2,
49016
+ error: error40
49017
+ },
49018
+ `Conduit directive routed: ${directive.verb} ${taskId} \u2192 ${projectName} (${success2 ? "OK" : "FAILED"})`
49019
+ );
49020
+ } catch {
49167
49021
  }
49168
- const refRegex = /@([a-zA-Z0-9_./-]+\.md)/g;
49169
- for (const match of prompt.matchAll(refRegex)) {
49170
- tokens.push(`@${match[1]}`);
49022
+ }
49023
+ async function workspaceStatus() {
49024
+ const projects = await nexusList();
49025
+ const summaries = [];
49026
+ const totals = { pending: 0, active: 0, done: 0, total: 0 };
49027
+ for (const project of projects) {
49028
+ try {
49029
+ const acc = await getAccessor(project.path);
49030
+ const { tasks: tasks2 } = await acc.queryTasks({});
49031
+ const counts2 = {
49032
+ pending: tasks2.filter((t) => t.status === "pending").length,
49033
+ active: tasks2.filter((t) => t.status === "active").length,
49034
+ done: tasks2.filter((t) => t.status === "done").length,
49035
+ total: tasks2.length
49036
+ };
49037
+ summaries.push({
49038
+ name: project.name,
49039
+ path: project.path,
49040
+ counts: counts2,
49041
+ health: project.healthStatus,
49042
+ lastSync: project.lastSync
49043
+ });
49044
+ totals.pending += counts2.pending;
49045
+ totals.active += counts2.active;
49046
+ totals.done += counts2.done;
49047
+ totals.total += counts2.total;
49048
+ } catch {
49049
+ summaries.push({
49050
+ name: project.name,
49051
+ path: project.path,
49052
+ counts: { pending: 0, active: 0, done: 0, total: 0 },
49053
+ health: "unreachable",
49054
+ lastSync: project.lastSync
49055
+ });
49056
+ }
49171
49057
  }
49172
- return tokens;
49058
+ return {
49059
+ projectCount: projects.length,
49060
+ projects: summaries,
49061
+ totals,
49062
+ computedAt: (/* @__PURE__ */ new Date()).toISOString()
49063
+ };
49173
49064
  }
49174
- function resolveTokens(prompt, context) {
49175
- let resolved = prompt;
49176
- const unresolved = [];
49177
- resolved = resolved.replace(/\{\{([A-Z_]+)\}\}/g, (fullMatch, token) => {
49178
- if (context[token] !== void 0) {
49179
- return context[token];
49065
+ async function workspaceAgents() {
49066
+ const projects = await nexusList();
49067
+ const agents = [];
49068
+ for (const project of projects) {
49069
+ try {
49070
+ const { listAgentInstances: listAgentInstances2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports2));
49071
+ const instances = await listAgentInstances2(void 0, project.path);
49072
+ for (const inst of instances) {
49073
+ agents.push({
49074
+ agentId: inst.id,
49075
+ agentType: inst.agentType,
49076
+ status: inst.status,
49077
+ project: project.name,
49078
+ taskId: inst.taskId ?? null,
49079
+ lastHeartbeat: inst.lastHeartbeat
49080
+ });
49081
+ }
49082
+ } catch {
49180
49083
  }
49181
- unresolved.push(token);
49182
- return fullMatch;
49183
- });
49184
- return { resolved, unresolved };
49084
+ }
49085
+ return agents;
49185
49086
  }
49186
- var DISPATCH_MAP;
49187
- var init_orchestration = __esm({
49188
- "packages/core/src/orchestration/index.ts"() {
49087
+ var RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_OPS, rateLimitCounters, DEFAULT_ACL, TASK_REF_PATTERN, VERB_TO_OPERATION;
49088
+ var init_workspace = __esm({
49089
+ "packages/core/src/nexus/workspace.ts"() {
49189
49090
  "use strict";
49190
49091
  init_src();
49191
49092
  init_errors3();
49192
- init_deps2();
49193
- init_analyze();
49194
- init_context2();
49195
- init_status();
49196
- DISPATCH_MAP = {
49197
- research: {
49198
- keywords: ["research", "investigate", "explore", "analyze", "study"],
49199
- labels: ["research", "investigation", "analysis"]
49200
- },
49201
- consensus: {
49202
- keywords: ["vote", "validate", "decide", "consensus"],
49203
- labels: ["consensus", "voting", "decision"]
49204
- },
49205
- specification: {
49206
- keywords: ["spec", "rfc", "design", "specification"],
49207
- labels: ["specification", "design", "rfc"]
49208
- },
49209
- decomposition: {
49210
- keywords: ["epic", "plan", "decompose", "breakdown"],
49093
+ init_data_accessor();
49094
+ init_registry3();
49095
+ RATE_LIMIT_WINDOW_MS = 6e4;
49096
+ RATE_LIMIT_MAX_OPS = 100;
49097
+ rateLimitCounters = /* @__PURE__ */ new Map();
49098
+ DEFAULT_ACL = { authorizedAgents: ["*"] };
49099
+ TASK_REF_PATTERN = /\bT(\d+)\b/g;
49100
+ VERB_TO_OPERATION = {
49101
+ claim: "tasks.start",
49102
+ done: "tasks.complete",
49103
+ complete: "tasks.complete",
49104
+ blocked: "tasks.update",
49105
+ // Update status to blocked
49106
+ start: "tasks.start",
49107
+ stop: "tasks.stop"
49108
+ };
49109
+ }
49110
+ });
49111
+
49112
+ // packages/core/src/nexus/index.ts
49113
+ var nexus_exports = {};
49114
+ __export(nexus_exports, {
49115
+ blockingAnalysis: () => blockingAnalysis,
49116
+ buildGlobalGraph: () => buildGlobalGraph,
49117
+ canExecute: () => canExecute,
49118
+ canRead: () => canRead,
49119
+ canWrite: () => canWrite,
49120
+ checkPermission: () => checkPermission,
49121
+ checkPermissionDetail: () => checkPermissionDetail,
49122
+ criticalPath: () => criticalPath,
49123
+ discoverRelated: () => discoverRelated,
49124
+ executeTransfer: () => executeTransfer,
49125
+ extractKeywords: () => extractKeywords2,
49126
+ generateProjectHash: () => generateProjectHash,
49127
+ getCurrentProject: () => getCurrentProject,
49128
+ getNexusCacheDir: () => getNexusCacheDir,
49129
+ getNexusHome: () => getNexusHome,
49130
+ getPermission: () => getPermission,
49131
+ getProjectFromQuery: () => getProjectFromQuery,
49132
+ getSharingStatus: () => getSharingStatus,
49133
+ invalidateGraphCache: () => invalidateGraphCache,
49134
+ nexusDeps: () => nexusDeps,
49135
+ nexusGetProject: () => nexusGetProject,
49136
+ nexusInit: () => nexusInit,
49137
+ nexusList: () => nexusList,
49138
+ nexusProjectExists: () => nexusProjectExists,
49139
+ nexusReconcile: () => nexusReconcile,
49140
+ nexusRegister: () => nexusRegister,
49141
+ nexusSetPermission: () => nexusSetPermission,
49142
+ nexusSync: () => nexusSync,
49143
+ nexusSyncAll: () => nexusSyncAll,
49144
+ nexusUnregister: () => nexusUnregister,
49145
+ orphanDetection: () => orphanDetection,
49146
+ parseDirective: () => parseDirective,
49147
+ parseQuery: () => parseQuery,
49148
+ permissionLevel: () => permissionLevel,
49149
+ previewTransfer: () => previewTransfer,
49150
+ readRegistry: () => readRegistry,
49151
+ readRegistryRequired: () => readRegistryRequired,
49152
+ requirePermission: () => requirePermission,
49153
+ resetNexusDbState: () => resetNexusDbState,
49154
+ resolveCrossDeps: () => resolveCrossDeps,
49155
+ resolveProjectPath: () => resolveProjectPath2,
49156
+ resolveTask: () => resolveTask,
49157
+ routeDirective: () => routeDirective,
49158
+ searchAcrossProjects: () => searchAcrossProjects,
49159
+ setPermission: () => setPermission,
49160
+ syncGitignore: () => syncGitignore,
49161
+ validateSyntax: () => validateSyntax,
49162
+ workspaceAgents: () => workspaceAgents,
49163
+ workspaceStatus: () => workspaceStatus
49164
+ });
49165
+ var init_nexus = __esm({
49166
+ "packages/core/src/nexus/index.ts"() {
49167
+ "use strict";
49168
+ init_deps();
49169
+ init_discover();
49170
+ init_hash();
49171
+ init_permissions();
49172
+ init_query3();
49173
+ init_registry3();
49174
+ init_sharing();
49175
+ init_transfer();
49176
+ init_workspace();
49177
+ }
49178
+ });
49179
+
49180
+ // packages/core/src/observability/types.ts
49181
+ var PINO_LEVEL_VALUES;
49182
+ var init_types = __esm({
49183
+ "packages/core/src/observability/types.ts"() {
49184
+ "use strict";
49185
+ PINO_LEVEL_VALUES = {
49186
+ TRACE: 10,
49187
+ DEBUG: 20,
49188
+ INFO: 30,
49189
+ WARN: 40,
49190
+ ERROR: 50,
49191
+ FATAL: 60
49192
+ };
49193
+ }
49194
+ });
49195
+
49196
+ // packages/core/src/observability/log-filter.ts
49197
+ function compareLevels(a, b) {
49198
+ return PINO_LEVEL_VALUES[a] - PINO_LEVEL_VALUES[b];
49199
+ }
49200
+ function matchesFilter(entry, filter) {
49201
+ if (filter.level !== void 0 && entry.level !== filter.level) return false;
49202
+ if (filter.minLevel !== void 0 && compareLevels(entry.level, filter.minLevel) < 0)
49203
+ return false;
49204
+ if (filter.since !== void 0 && entry.time < filter.since) return false;
49205
+ if (filter.until !== void 0 && entry.time > filter.until) return false;
49206
+ if (filter.subsystem !== void 0 && entry.subsystem !== filter.subsystem) return false;
49207
+ if (filter.code !== void 0 && entry.code !== filter.code) return false;
49208
+ if (filter.exitCode !== void 0 && entry.exitCode !== filter.exitCode) return false;
49209
+ if (filter.pid !== void 0 && entry.pid !== filter.pid) return false;
49210
+ if (filter.msgContains !== void 0) {
49211
+ if (!entry.msg.toLowerCase().includes(filter.msgContains.toLowerCase())) return false;
49212
+ }
49213
+ return true;
49214
+ }
49215
+ function filterEntries(entries, filter) {
49216
+ return entries.filter((entry) => matchesFilter(entry, filter));
49217
+ }
49218
+ function paginate2(entries, limit, offset) {
49219
+ const start = offset ?? 0;
49220
+ if (limit !== void 0) {
49221
+ return entries.slice(start, start + limit);
49222
+ }
49223
+ return start > 0 ? entries.slice(start) : entries;
49224
+ }
49225
+ var init_log_filter = __esm({
49226
+ "packages/core/src/observability/log-filter.ts"() {
49227
+ "use strict";
49228
+ init_types();
49229
+ }
49230
+ });
49231
+
49232
+ // packages/core/src/observability/log-parser.ts
49233
+ function isValidLevel(level) {
49234
+ return VALID_LEVELS.has(level);
49235
+ }
49236
+ function parseLogLine(line2) {
49237
+ const trimmed = line2.trim();
49238
+ if (!trimmed) return null;
49239
+ let raw;
49240
+ try {
49241
+ raw = JSON.parse(trimmed);
49242
+ } catch {
49243
+ return null;
49244
+ }
49245
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
49246
+ const level = raw.level;
49247
+ if (typeof level !== "string" || !isValidLevel(level)) return null;
49248
+ const time4 = raw.time;
49249
+ if (typeof time4 !== "string") return null;
49250
+ const pid = raw.pid;
49251
+ if (typeof pid !== "number") return null;
49252
+ const hostname4 = raw.hostname;
49253
+ if (typeof hostname4 !== "string") return null;
49254
+ const msg = raw.msg;
49255
+ if (typeof msg !== "string") return null;
49256
+ const extra = {};
49257
+ for (const key of Object.keys(raw)) {
49258
+ if (!KNOWN_FIELDS.has(key)) {
49259
+ extra[key] = raw[key];
49260
+ }
49261
+ }
49262
+ const entry = {
49263
+ level,
49264
+ time: time4,
49265
+ pid,
49266
+ hostname: hostname4,
49267
+ msg,
49268
+ extra
49269
+ };
49270
+ if (typeof raw.subsystem === "string") entry.subsystem = raw.subsystem;
49271
+ if (typeof raw.code === "string") entry.code = raw.code;
49272
+ if (typeof raw.exitCode === "number") entry.exitCode = raw.exitCode;
49273
+ return entry;
49274
+ }
49275
+ function parseLogLines(lines) {
49276
+ const entries = [];
49277
+ for (const line2 of lines) {
49278
+ const entry = parseLogLine(line2);
49279
+ if (entry) entries.push(entry);
49280
+ }
49281
+ return entries;
49282
+ }
49283
+ var VALID_LEVELS, KNOWN_FIELDS;
49284
+ var init_log_parser = __esm({
49285
+ "packages/core/src/observability/log-parser.ts"() {
49286
+ "use strict";
49287
+ init_types();
49288
+ VALID_LEVELS = new Set(Object.keys(PINO_LEVEL_VALUES));
49289
+ KNOWN_FIELDS = /* @__PURE__ */ new Set([
49290
+ "level",
49291
+ "time",
49292
+ "pid",
49293
+ "hostname",
49294
+ "msg",
49295
+ "subsystem",
49296
+ "code",
49297
+ "exitCode"
49298
+ ]);
49299
+ }
49300
+ });
49301
+
49302
+ // packages/core/src/observability/log-reader.ts
49303
+ import { createReadStream, existsSync as existsSync56, readdirSync as readdirSync23, readFileSync as readFileSync38, statSync as statSync16 } from "node:fs";
49304
+ import { join as join62 } from "node:path";
49305
+ import { createInterface } from "node:readline";
49306
+ function getProjectLogDir(cwd) {
49307
+ const runtimeDir = getLogDir();
49308
+ if (runtimeDir) return runtimeDir;
49309
+ const cleoDir = getCleoDirAbsolute(cwd);
49310
+ const logsDir = join62(cleoDir, "logs");
49311
+ return existsSync56(logsDir) ? logsDir : null;
49312
+ }
49313
+ function getGlobalLogDir() {
49314
+ return join62(getCleoHome(), "logs");
49315
+ }
49316
+ function scanLogDir(dir, includeMigration) {
49317
+ if (!existsSync56(dir)) return [];
49318
+ let fileNames;
49319
+ try {
49320
+ fileNames = readdirSync23(dir);
49321
+ } catch {
49322
+ return [];
49323
+ }
49324
+ const files = [];
49325
+ for (const name2 of fileNames) {
49326
+ const cleoMatch = name2.match(CLEO_LOG_PATTERN);
49327
+ const isMigration = MIGRATION_LOG_PATTERN.test(name2);
49328
+ if (!cleoMatch && (!isMigration || !includeMigration)) continue;
49329
+ const filePath = join62(dir, name2);
49330
+ let stat2;
49331
+ try {
49332
+ stat2 = statSync16(filePath);
49333
+ } catch {
49334
+ continue;
49335
+ }
49336
+ files.push({
49337
+ path: filePath,
49338
+ name: name2,
49339
+ size: stat2.size,
49340
+ mtime: stat2.mtime.toISOString(),
49341
+ date: cleoMatch ? cleoMatch[1] : null,
49342
+ isActive: false
49343
+ // set below
49344
+ });
49345
+ }
49346
+ return files;
49347
+ }
49348
+ function discoverLogFiles(options, cwd) {
49349
+ const scope = options?.scope ?? "project";
49350
+ const includeMigration = options?.includeMigration ?? false;
49351
+ const sinceDate = options?.since;
49352
+ let files = [];
49353
+ if (scope === "project" || scope === "both") {
49354
+ const dir = getProjectLogDir(cwd);
49355
+ if (dir) files.push(...scanLogDir(dir, includeMigration));
49356
+ }
49357
+ if (scope === "global" || scope === "both") {
49358
+ const dir = getGlobalLogDir();
49359
+ files.push(...scanLogDir(dir, includeMigration));
49360
+ }
49361
+ if (sinceDate) {
49362
+ files = files.filter((f) => f.mtime >= sinceDate);
49363
+ }
49364
+ files.sort((a, b) => b.mtime.localeCompare(a.mtime));
49365
+ if (files.length > 0) {
49366
+ files[0].isActive = true;
49367
+ }
49368
+ return files;
49369
+ }
49370
+ function readLogFileLines(filePath) {
49371
+ let content;
49372
+ try {
49373
+ content = readFileSync38(filePath, "utf-8");
49374
+ } catch {
49375
+ return [];
49376
+ }
49377
+ if (!content.trim()) return [];
49378
+ return content.split("\n").filter((line2) => line2.trim() !== "");
49379
+ }
49380
+ async function* streamLogFileLines(filePath) {
49381
+ if (!existsSync56(filePath)) return;
49382
+ const rl = createInterface({
49383
+ input: createReadStream(filePath, { encoding: "utf-8" }),
49384
+ crlfDelay: Infinity
49385
+ });
49386
+ for await (const line2 of rl) {
49387
+ if (line2.trim()) yield line2;
49388
+ }
49389
+ }
49390
+ var CLEO_LOG_PATTERN, MIGRATION_LOG_PATTERN;
49391
+ var init_log_reader = __esm({
49392
+ "packages/core/src/observability/log-reader.ts"() {
49393
+ "use strict";
49394
+ init_logger();
49395
+ init_paths();
49396
+ CLEO_LOG_PATTERN = /^cleo\.(\d{4}-\d{2}-\d{2})\.(\d+)\.log$/;
49397
+ MIGRATION_LOG_PATTERN = /^migration-.*\.jsonl$/;
49398
+ }
49399
+ });
49400
+
49401
+ // packages/core/src/observability/index.ts
49402
+ var observability_exports = {};
49403
+ __export(observability_exports, {
49404
+ PINO_LEVEL_VALUES: () => PINO_LEVEL_VALUES,
49405
+ compareLevels: () => compareLevels,
49406
+ discoverLogFiles: () => discoverLogFiles,
49407
+ filterEntries: () => filterEntries,
49408
+ getGlobalLogDir: () => getGlobalLogDir,
49409
+ getLogSummary: () => getLogSummary,
49410
+ getProjectLogDir: () => getProjectLogDir,
49411
+ isValidLevel: () => isValidLevel,
49412
+ matchesFilter: () => matchesFilter,
49413
+ paginate: () => paginate2,
49414
+ parseLogLine: () => parseLogLine,
49415
+ parseLogLines: () => parseLogLines,
49416
+ queryLogs: () => queryLogs,
49417
+ readLogFileLines: () => readLogFileLines,
49418
+ streamLogFileLines: () => streamLogFileLines,
49419
+ streamLogs: () => streamLogs
49420
+ });
49421
+ function queryLogs(filter, options, cwd) {
49422
+ const files = discoverLogFiles(options, cwd);
49423
+ const filesPaths = files.map((f) => f.path);
49424
+ let totalScanned = 0;
49425
+ const allEntries = [];
49426
+ for (const file2 of files) {
49427
+ const lines = readLogFileLines(file2.path);
49428
+ totalScanned += lines.length;
49429
+ const parsed = parseLogLines(lines);
49430
+ allEntries.push(...parsed);
49431
+ }
49432
+ const matched = filter ? filterEntries(allEntries, filter) : allEntries;
49433
+ const totalMatched = matched.length;
49434
+ const entries = paginate2(matched, filter?.limit, filter?.offset);
49435
+ return {
49436
+ entries,
49437
+ totalScanned,
49438
+ totalMatched,
49439
+ files: filesPaths
49440
+ };
49441
+ }
49442
+ async function* streamLogs(filter, options, cwd) {
49443
+ const files = discoverLogFiles(options, cwd);
49444
+ let yielded = 0;
49445
+ const limit = filter?.limit;
49446
+ for (const file2 of files) {
49447
+ if (limit !== void 0 && yielded >= limit) break;
49448
+ for await (const line2 of streamLogFileLines(file2.path)) {
49449
+ const entry = parseLogLine(line2);
49450
+ if (!entry) continue;
49451
+ if (filter && !matchesFilter(entry, filter)) continue;
49452
+ yield entry;
49453
+ yielded++;
49454
+ if (limit !== void 0 && yielded >= limit) return;
49455
+ }
49456
+ }
49457
+ }
49458
+ function getLogSummary(options, cwd) {
49459
+ const files = discoverLogFiles(options, cwd);
49460
+ const byLevel = {};
49461
+ const bySubsystem = {};
49462
+ let totalEntries = 0;
49463
+ let earliest = null;
49464
+ let latest = null;
49465
+ for (const file2 of files) {
49466
+ const lines = readLogFileLines(file2.path);
49467
+ const entries = parseLogLines(lines);
49468
+ totalEntries += entries.length;
49469
+ for (const entry of entries) {
49470
+ byLevel[entry.level] = (byLevel[entry.level] ?? 0) + 1;
49471
+ if (entry.subsystem) {
49472
+ bySubsystem[entry.subsystem] = (bySubsystem[entry.subsystem] ?? 0) + 1;
49473
+ }
49474
+ if (earliest === null || entry.time < earliest) earliest = entry.time;
49475
+ if (latest === null || entry.time > latest) latest = entry.time;
49476
+ }
49477
+ }
49478
+ return {
49479
+ totalEntries,
49480
+ byLevel,
49481
+ bySubsystem,
49482
+ dateRange: earliest && latest ? { earliest, latest } : null,
49483
+ files
49484
+ };
49485
+ }
49486
+ var init_observability = __esm({
49487
+ "packages/core/src/observability/index.ts"() {
49488
+ "use strict";
49489
+ init_log_filter();
49490
+ init_log_parser();
49491
+ init_log_reader();
49492
+ init_types();
49493
+ init_log_filter();
49494
+ init_log_parser();
49495
+ init_log_reader();
49496
+ }
49497
+ });
49498
+
49499
+ // packages/core/src/phases/deps.ts
49500
+ async function loadAllTasks3(cwd, accessor) {
49501
+ const acc = accessor ?? await getAccessor(cwd);
49502
+ const { tasks: tasks2 } = await acc.queryTasks({});
49503
+ return tasks2;
49504
+ }
49505
+ function buildGraph(tasks2) {
49506
+ const graph = /* @__PURE__ */ new Map();
49507
+ const taskMap = new Map(tasks2.map((t) => [t.id, t]));
49508
+ for (const task of tasks2) {
49509
+ graph.set(task.id, {
49510
+ id: task.id,
49511
+ title: task.title,
49512
+ status: task.status,
49513
+ depends: (task.depends ?? []).filter((d) => taskMap.has(d)),
49514
+ dependents: []
49515
+ });
49516
+ }
49517
+ for (const [id, node] of graph) {
49518
+ for (const depId of node.depends) {
49519
+ const depNode = graph.get(depId);
49520
+ if (depNode) {
49521
+ depNode.dependents.push(id);
49522
+ }
49523
+ }
49524
+ }
49525
+ return graph;
49526
+ }
49527
+ async function getExecutionWaves(epicId, cwd, accessor) {
49528
+ const allTasks = await loadAllTasks3(cwd, accessor);
49529
+ let tasks2 = allTasks.filter((t) => t.status !== "done" && t.status !== "cancelled");
49530
+ if (epicId) {
49531
+ const epicTask = allTasks.find((t) => t.id === epicId);
49532
+ if (!epicTask) {
49533
+ throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
49534
+ }
49535
+ const childIds = new Set(allTasks.filter((t) => t.parentId === epicId).map((t) => t.id));
49536
+ tasks2 = tasks2.filter((t) => childIds.has(t.id) || t.id === epicId);
49537
+ }
49538
+ const graph = buildGraph(tasks2);
49539
+ const waves = [];
49540
+ const completed = /* @__PURE__ */ new Set();
49541
+ const remaining = new Set(tasks2.map((t) => t.id));
49542
+ let waveNum = 1;
49543
+ while (remaining.size > 0) {
49544
+ const wave = [];
49545
+ for (const id of remaining) {
49546
+ const node = graph.get(id);
49547
+ if (!node) continue;
49548
+ const allDepsComplete = node.depends.every(
49549
+ (dep) => completed.has(dep) || !remaining.has(dep)
49550
+ );
49551
+ if (allDepsComplete) {
49552
+ wave.push(id);
49553
+ }
49554
+ }
49555
+ if (wave.length === 0) {
49556
+ wave.push(...remaining);
49557
+ }
49558
+ waves.push({
49559
+ wave: waveNum,
49560
+ tasks: wave.map((id) => {
49561
+ const node = graph.get(id);
49562
+ return {
49563
+ id,
49564
+ title: node.title,
49565
+ status: node.status,
49566
+ depends: node.depends
49567
+ };
49568
+ })
49569
+ });
49570
+ for (const id of wave) {
49571
+ completed.add(id);
49572
+ remaining.delete(id);
49573
+ }
49574
+ waveNum++;
49575
+ }
49576
+ return waves;
49577
+ }
49578
+ async function getCriticalPath2(taskId, cwd, accessor) {
49579
+ const allTasks = await loadAllTasks3(cwd, accessor);
49580
+ const task = allTasks.find((t) => t.id === taskId);
49581
+ if (!task) {
49582
+ throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${taskId}`);
49583
+ }
49584
+ const graph = buildGraph(allTasks);
49585
+ const taskMap = new Map(allTasks.map((t) => [t.id, t]));
49586
+ function findLongestPath(id, visited) {
49587
+ if (visited.has(id)) return [];
49588
+ visited.add(id);
49589
+ const node = graph.get(id);
49590
+ if (!node || node.dependents.length === 0) {
49591
+ return [id];
49592
+ }
49593
+ let longest = [];
49594
+ for (const depId of node.dependents) {
49595
+ const path3 = findLongestPath(depId, new Set(visited));
49596
+ if (path3.length > longest.length) {
49597
+ longest = path3;
49598
+ }
49599
+ }
49600
+ return [id, ...longest];
49601
+ }
49602
+ const path2 = findLongestPath(taskId, /* @__PURE__ */ new Set());
49603
+ return {
49604
+ path: path2.map((id) => {
49605
+ const t = taskMap.get(id);
49606
+ return t ? { id: t.id, title: t.title, status: t.status } : { id, title: "Unknown", status: "unknown" };
49607
+ }),
49608
+ length: path2.length
49609
+ };
49610
+ }
49611
+ var init_deps2 = __esm({
49612
+ "packages/core/src/phases/deps.ts"() {
49613
+ "use strict";
49614
+ init_src();
49615
+ init_errors3();
49616
+ init_data_accessor();
49617
+ }
49618
+ });
49619
+
49620
+ // packages/core/src/orchestration/analyze.ts
49621
+ function buildDependencyGraph(tasks2) {
49622
+ const graph = /* @__PURE__ */ new Map();
49623
+ for (const task of tasks2) {
49624
+ if (!graph.has(task.id)) {
49625
+ graph.set(task.id, /* @__PURE__ */ new Set());
49626
+ }
49627
+ if (task.depends) {
49628
+ for (const dep of task.depends) {
49629
+ graph.get(task.id).add(dep);
49630
+ }
49631
+ }
49632
+ }
49633
+ return graph;
49634
+ }
49635
+ function detectCircularDependencies(tasks2, graph) {
49636
+ const depGraph = graph ?? buildDependencyGraph(tasks2);
49637
+ const circularDeps = [];
49638
+ const visited = /* @__PURE__ */ new Set();
49639
+ const recursionStack = /* @__PURE__ */ new Set();
49640
+ function dfs(taskId, path2) {
49641
+ visited.add(taskId);
49642
+ recursionStack.add(taskId);
49643
+ const deps = depGraph.get(taskId) || /* @__PURE__ */ new Set();
49644
+ for (const dep of deps) {
49645
+ if (!visited.has(dep)) {
49646
+ dfs(dep, [...path2, taskId]);
49647
+ } else if (recursionStack.has(dep)) {
49648
+ circularDeps.push([...path2, taskId, dep]);
49649
+ }
49650
+ }
49651
+ recursionStack.delete(taskId);
49652
+ }
49653
+ for (const task of tasks2) {
49654
+ if (!visited.has(task.id)) {
49655
+ dfs(task.id, []);
49656
+ }
49657
+ }
49658
+ return circularDeps;
49659
+ }
49660
+ function findMissingDependencies(children, allTasks) {
49661
+ const childIds = new Set(children.map((t) => t.id));
49662
+ const missingDeps = [];
49663
+ for (const task of children) {
49664
+ if (task.depends) {
49665
+ for (const dep of task.depends) {
49666
+ if (!childIds.has(dep) && !allTasks.find((t) => t.id === dep && t.status === "done")) {
49667
+ missingDeps.push({ taskId: task.id, missingDep: dep });
49668
+ }
49669
+ }
49670
+ }
49671
+ }
49672
+ return missingDeps;
49673
+ }
49674
+ function analyzeDependencies(children, allTasks) {
49675
+ const graph = buildDependencyGraph(children);
49676
+ const circularDependencies = detectCircularDependencies(children, graph);
49677
+ const missingDependencies = findMissingDependencies(children, allTasks);
49678
+ const dependencyGraph = {};
49679
+ for (const [key, value] of graph.entries()) {
49680
+ dependencyGraph[key] = Array.from(value);
49681
+ }
49682
+ return {
49683
+ dependencyGraph,
49684
+ circularDependencies,
49685
+ missingDependencies
49686
+ };
49687
+ }
49688
+ var init_analyze = __esm({
49689
+ "packages/core/src/orchestration/analyze.ts"() {
49690
+ "use strict";
49691
+ }
49692
+ });
49693
+
49694
+ // packages/core/src/orchestration/context.ts
49695
+ import { existsSync as existsSync57, readFileSync as readFileSync39 } from "node:fs";
49696
+ function countManifestEntries(projectRoot) {
49697
+ const manifestPath = getManifestPath(projectRoot);
49698
+ if (!existsSync57(manifestPath)) {
49699
+ return 0;
49700
+ }
49701
+ try {
49702
+ const content = readFileSync39(manifestPath, "utf-8");
49703
+ return content.split("\n").filter((l) => l.trim()).length;
49704
+ } catch {
49705
+ return 0;
49706
+ }
49707
+ }
49708
+ function estimateContext(taskCount, projectRoot, epicId) {
49709
+ const estimatedTokens = taskCount * 100;
49710
+ const manifestEntries2 = countManifestEntries(projectRoot);
49711
+ return {
49712
+ epicId: epicId || null,
49713
+ taskCount,
49714
+ manifestEntries: manifestEntries2,
49715
+ estimatedTokens,
49716
+ recommendation: estimatedTokens > 5e3 ? "Consider using manifest summaries instead of full task details" : "Context usage is within recommended limits",
49717
+ limits: {
49718
+ orchestratorBudget: 1e4,
49719
+ maxFilesPerAgent: 3,
49720
+ currentUsage: estimatedTokens
49721
+ }
49722
+ };
49723
+ }
49724
+ var init_context2 = __esm({
49725
+ "packages/core/src/orchestration/context.ts"() {
49726
+ "use strict";
49727
+ init_paths();
49728
+ }
49729
+ });
49730
+
49731
+ // packages/core/src/orchestration/waves.ts
49732
+ function buildDependencyGraph2(tasks2) {
49733
+ const graph = /* @__PURE__ */ new Map();
49734
+ for (const task of tasks2) {
49735
+ if (!graph.has(task.id)) {
49736
+ graph.set(task.id, /* @__PURE__ */ new Set());
49737
+ }
49738
+ if (task.depends) {
49739
+ for (const dep of task.depends) {
49740
+ graph.get(task.id).add(dep);
49741
+ }
49742
+ }
49743
+ }
49744
+ return graph;
49745
+ }
49746
+ function computeWaves(tasks2) {
49747
+ const graph = buildDependencyGraph2(tasks2);
49748
+ const waves = [];
49749
+ const completed = /* @__PURE__ */ new Set();
49750
+ for (const task of tasks2) {
49751
+ if (task.status === "done" || task.status === "cancelled") {
49752
+ completed.add(task.id);
49753
+ }
49754
+ }
49755
+ let remaining = tasks2.filter((t) => t.status !== "done" && t.status !== "cancelled");
49756
+ let waveNumber = 1;
49757
+ const maxWaves = 50;
49758
+ while (remaining.length > 0 && waveNumber <= maxWaves) {
49759
+ const waveTasks = remaining.filter((t) => {
49760
+ const deps = graph.get(t.id) || /* @__PURE__ */ new Set();
49761
+ return Array.from(deps).every((d) => completed.has(d));
49762
+ });
49763
+ if (waveTasks.length === 0) break;
49764
+ waves.push({
49765
+ waveNumber,
49766
+ tasks: waveTasks.map((t) => t.id),
49767
+ status: waveTasks.every((t) => completed.has(t.id)) ? "completed" : waveTasks.some((t) => t.status === "active") ? "in_progress" : "pending"
49768
+ });
49769
+ for (const t of waveTasks) {
49770
+ completed.add(t.id);
49771
+ }
49772
+ remaining = remaining.filter((t) => !waveTasks.some((wt) => wt.id === t.id));
49773
+ waveNumber++;
49774
+ }
49775
+ if (remaining.length > 0) {
49776
+ waves.push({
49777
+ waveNumber,
49778
+ tasks: remaining.map((t) => t.id),
49779
+ status: "pending"
49780
+ });
49781
+ }
49782
+ return waves;
49783
+ }
49784
+ async function getEnrichedWaves(epicId, cwd, accessor) {
49785
+ const acc = accessor ?? await getAccessor(cwd);
49786
+ const children = await acc.getChildren(epicId);
49787
+ const waves = computeWaves(children);
49788
+ const taskMap = new Map(children.map((t) => [t.id, t]));
49789
+ const enrichedWaves = waves.map((w) => ({
49790
+ ...w,
49791
+ tasks: w.tasks.map((id) => ({
49792
+ id,
49793
+ title: taskMap.get(id)?.title || id,
49794
+ status: taskMap.get(id)?.status || "unknown"
49795
+ }))
49796
+ }));
49797
+ return {
49798
+ epicId,
49799
+ waves: enrichedWaves,
49800
+ totalWaves: waves.length,
49801
+ totalTasks: children.length
49802
+ };
49803
+ }
49804
+ var init_waves = __esm({
49805
+ "packages/core/src/orchestration/waves.ts"() {
49806
+ "use strict";
49807
+ init_data_accessor();
49808
+ }
49809
+ });
49810
+
49811
+ // packages/core/src/orchestration/status.ts
49812
+ function countByStatus(tasks2) {
49813
+ return {
49814
+ pending: tasks2.filter((t) => t.status === "pending").length,
49815
+ active: tasks2.filter((t) => t.status === "active").length,
49816
+ blocked: tasks2.filter((t) => t.status === "blocked").length,
49817
+ done: tasks2.filter((t) => t.status === "done").length,
49818
+ cancelled: tasks2.filter((t) => t.status === "cancelled").length
49819
+ };
49820
+ }
49821
+ function computeEpicStatus(epicId, epicTitle, children) {
49822
+ const waves = computeWaves(children);
49823
+ const byStatus = countByStatus(children);
49824
+ return {
49825
+ epicId,
49826
+ epicTitle,
49827
+ totalTasks: children.length,
49828
+ byStatus,
49829
+ waves: waves.length,
49830
+ currentWave: waves.find((w) => w.status !== "completed")?.waveNumber || null
49831
+ };
49832
+ }
49833
+ function computeOverallStatus(tasks2) {
49834
+ const epics = tasks2.filter(
49835
+ (t) => !t.parentId && (t.type === "epic" || tasks2.some((c) => c.parentId === t.id))
49836
+ );
49837
+ return {
49838
+ totalEpics: epics.length,
49839
+ totalTasks: tasks2.length,
49840
+ byStatus: {
49841
+ pending: tasks2.filter((t) => t.status === "pending").length,
49842
+ active: tasks2.filter((t) => t.status === "active").length,
49843
+ blocked: tasks2.filter((t) => t.status === "blocked").length,
49844
+ done: tasks2.filter((t) => t.status === "done").length
49845
+ }
49846
+ };
49847
+ }
49848
+ function computeProgress(tasks2) {
49849
+ const total = tasks2.length;
49850
+ const done = tasks2.filter((t) => t.status === "done").length;
49851
+ const pending = tasks2.filter((t) => t.status === "pending").length;
49852
+ const blocked = tasks2.filter((t) => t.status === "blocked").length;
49853
+ const active = tasks2.filter((t) => t.status === "active").length;
49854
+ return {
49855
+ total,
49856
+ done,
49857
+ pending,
49858
+ blocked,
49859
+ active,
49860
+ percentComplete: total > 0 ? Math.round(done / total * 100) : 0
49861
+ };
49862
+ }
49863
+ function computeStartupSummary(epicId, epicTitle, children, readyCount) {
49864
+ const waves = computeWaves(children);
49865
+ const byStatus = countByStatus(children);
49866
+ return {
49867
+ epicId,
49868
+ epicTitle,
49869
+ initialized: true,
49870
+ summary: {
49871
+ totalTasks: children.length,
49872
+ totalWaves: waves.length,
49873
+ readyTasks: readyCount,
49874
+ byStatus
49875
+ },
49876
+ firstWave: waves[0] || null
49877
+ };
49878
+ }
49879
+ var init_status = __esm({
49880
+ "packages/core/src/orchestration/status.ts"() {
49881
+ "use strict";
49882
+ init_waves();
49883
+ }
49884
+ });
49885
+
49886
+ // packages/core/src/orchestration/index.ts
49887
+ var orchestration_exports = {};
49888
+ __export(orchestration_exports, {
49889
+ analyzeDependencies: () => analyzeDependencies,
49890
+ analyzeEpic: () => analyzeEpic,
49891
+ autoDispatch: () => autoDispatch,
49892
+ buildDependencyGraph: () => buildDependencyGraph,
49893
+ computeEpicStatus: () => computeEpicStatus,
49894
+ computeOverallStatus: () => computeOverallStatus,
49895
+ computeProgress: () => computeProgress,
49896
+ computeStartupSummary: () => computeStartupSummary,
49897
+ countByStatus: () => countByStatus,
49898
+ countManifestEntries: () => countManifestEntries,
49899
+ detectCircularDependencies: () => detectCircularDependencies,
49900
+ estimateContext: () => estimateContext,
49901
+ findMissingDependencies: () => findMissingDependencies,
49902
+ getNextTask: () => getNextTask,
49903
+ getOrchestratorContext: () => getOrchestratorContext,
49904
+ getReadyTasks: () => getReadyTasks2,
49905
+ prepareSpawn: () => prepareSpawn,
49906
+ resolveTokens: () => resolveTokens,
49907
+ startOrchestration: () => startOrchestration,
49908
+ validateSpawnOutput: () => validateSpawnOutput
49909
+ });
49910
+ async function startOrchestration(epicId, _cwd, accessor) {
49911
+ const epic = await accessor.loadSingleTask(epicId);
49912
+ if (!epic) {
49913
+ throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
49914
+ }
49915
+ if (epic.type !== "epic") {
49916
+ throw new CleoError(
49917
+ 2 /* INVALID_INPUT */,
49918
+ `Task ${epicId} is not an epic (type: ${epic.type})`
49919
+ );
49920
+ }
49921
+ const session = {
49922
+ epicId,
49923
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
49924
+ status: "active",
49925
+ currentWave: 1,
49926
+ completedTasks: [],
49927
+ spawnedAgents: []
49928
+ };
49929
+ return session;
49930
+ }
49931
+ async function analyzeEpic(epicId, cwd, accessor) {
49932
+ const epic = await accessor.loadSingleTask(epicId);
49933
+ if (!epic) {
49934
+ throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
49935
+ }
49936
+ const childTasks = await accessor.getChildren(epicId);
49937
+ const waves = await getExecutionWaves(epicId, cwd, accessor);
49938
+ const completedTasks = childTasks.filter((t) => t.status === "done").map((t) => t.id);
49939
+ const blockedTasks = childTasks.filter((t) => t.status === "blocked").map((t) => t.id);
49940
+ const completedSet = new Set(completedTasks);
49941
+ const readyTasks = childTasks.filter((t) => {
49942
+ if (t.status === "done" || t.status === "cancelled") return false;
49943
+ const deps = t.depends ?? [];
49944
+ return deps.every((d) => completedSet.has(d));
49945
+ }).map((t) => t.id);
49946
+ return {
49947
+ epicId,
49948
+ totalTasks: childTasks.length,
49949
+ waves: waves.map((w) => ({
49950
+ wave: w.wave,
49951
+ tasks: w.tasks.map((t) => ({ id: t.id, title: t.title, status: t.status }))
49952
+ })),
49953
+ readyTasks,
49954
+ blockedTasks,
49955
+ completedTasks
49956
+ };
49957
+ }
49958
+ async function getReadyTasks2(epicId, _cwd, accessor) {
49959
+ const childTasks = await accessor.getChildren(epicId);
49960
+ const { tasks: allTasks } = await accessor.queryTasks({});
49961
+ const completedIds = new Set(allTasks.filter((t) => t.status === "done").map((t) => t.id));
49962
+ return childTasks.filter((t) => t.status !== "done" && t.status !== "cancelled").map((task) => {
49963
+ const deps = task.depends ?? [];
49964
+ const unmetDeps = deps.filter((d) => !completedIds.has(d));
49965
+ const protocol = autoDispatch(task);
49966
+ return {
49967
+ taskId: task.id,
49968
+ title: task.title,
49969
+ ready: unmetDeps.length === 0,
49970
+ blockers: unmetDeps,
49971
+ protocol
49972
+ };
49973
+ });
49974
+ }
49975
+ async function getNextTask(epicId, cwd, accessor) {
49976
+ const readyTasks = await getReadyTasks2(epicId, cwd, accessor);
49977
+ const ready = readyTasks.filter((t) => t.ready);
49978
+ if (ready.length === 0) return null;
49979
+ return ready[0];
49980
+ }
49981
+ async function prepareSpawn(taskId, _cwd, accessor) {
49982
+ const task = await accessor.loadSingleTask(taskId);
49983
+ if (!task) {
49984
+ throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${taskId}`);
49985
+ }
49986
+ const protocol = autoDispatch(task);
49987
+ const prompt = buildSpawnPrompt(task, protocol);
49988
+ const unresolvedTokens = findUnresolvedTokens(prompt);
49989
+ return {
49990
+ taskId,
49991
+ protocol,
49992
+ prompt,
49993
+ tokenResolution: {
49994
+ fullyResolved: unresolvedTokens.length === 0,
49995
+ unresolvedTokens
49996
+ }
49997
+ };
49998
+ }
49999
+ async function validateSpawnOutput(_taskId, output) {
50000
+ const errors = [];
50001
+ if (!output.file) {
50002
+ errors.push("No output file specified");
50003
+ }
50004
+ if (!output.manifestEntry) {
50005
+ errors.push("No manifest entry appended");
50006
+ }
50007
+ return { valid: errors.length === 0, errors };
50008
+ }
50009
+ async function getOrchestratorContext(epicId, _cwd, accessor) {
50010
+ const epic = await accessor.loadSingleTask(epicId);
50011
+ if (!epic) {
50012
+ throw new CleoError(4 /* NOT_FOUND */, `Epic not found: ${epicId}`);
50013
+ }
50014
+ const children = await accessor.getChildren(epicId);
50015
+ const completed = children.filter((t) => t.status === "done").length;
50016
+ const inProgress = children.filter((t) => t.status === "active").length;
50017
+ const blocked = children.filter((t) => t.status === "blocked").length;
50018
+ const pending = children.filter((t) => t.status === "pending").length;
50019
+ return {
50020
+ epicId,
50021
+ epicTitle: epic.title,
50022
+ totalTasks: children.length,
50023
+ completed,
50024
+ inProgress,
50025
+ blocked,
50026
+ pending,
50027
+ completionPercent: children.length > 0 ? Math.floor(completed * 100 / children.length) : 0
50028
+ };
50029
+ }
50030
+ function autoDispatch(task) {
50031
+ if (task.labels?.length) {
50032
+ for (const [protocol, config2] of Object.entries(DISPATCH_MAP)) {
50033
+ if (task.labels.some((l) => config2.labels.includes(l))) {
50034
+ return protocol;
50035
+ }
50036
+ }
50037
+ }
50038
+ if (task.type === "epic") return "decomposition";
50039
+ const text3 = `${task.title} ${task.description ?? ""}`.toLowerCase();
50040
+ for (const [protocol, config2] of Object.entries(DISPATCH_MAP)) {
50041
+ if (config2.keywords.some((kw) => text3.includes(kw))) {
50042
+ return protocol;
50043
+ }
50044
+ }
50045
+ return "implementation";
50046
+ }
50047
+ function buildSpawnPrompt(task, protocol) {
50048
+ const epicId = task.parentId ?? "none";
50049
+ const date6 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
50050
+ return [
50051
+ `## Task: ${task.id}`,
50052
+ `**Title**: ${task.title}`,
50053
+ task.description ? `**Description**: ${task.description}` : "",
50054
+ `**Protocol**: ${protocol}`,
50055
+ `**Epic**: ${epicId}`,
50056
+ `**Date**: ${date6}`,
50057
+ "",
50058
+ `### Instructions`,
50059
+ `1. Start task: \`cleo start ${task.id}\``,
50060
+ `2. Execute the ${protocol} protocol`,
50061
+ `3. Write output file`,
50062
+ `4. Append manifest entry to MANIFEST.jsonl`,
50063
+ `5. Complete: \`cleo complete ${task.id}\``,
50064
+ "",
50065
+ task.acceptance?.length ? `### Acceptance Criteria
50066
+ ${task.acceptance.map((a) => `- ${a}`).join("\n")}` : "",
50067
+ task.depends?.length ? `### Dependencies
50068
+ ${task.depends.map((d) => `- ${d}`).join("\n")}` : ""
50069
+ ].filter(Boolean).join("\n");
50070
+ }
50071
+ function findUnresolvedTokens(prompt) {
50072
+ const tokens = [];
50073
+ const tokenRegex = /\{\{([A-Z_]+)\}\}/g;
50074
+ for (const match of prompt.matchAll(tokenRegex)) {
50075
+ tokens.push(match[1]);
50076
+ }
50077
+ const refRegex = /@([a-zA-Z0-9_./-]+\.md)/g;
50078
+ for (const match of prompt.matchAll(refRegex)) {
50079
+ tokens.push(`@${match[1]}`);
50080
+ }
50081
+ return tokens;
50082
+ }
50083
+ function resolveTokens(prompt, context) {
50084
+ let resolved = prompt;
50085
+ const unresolved = [];
50086
+ resolved = resolved.replace(/\{\{([A-Z_]+)\}\}/g, (fullMatch, token) => {
50087
+ if (context[token] !== void 0) {
50088
+ return context[token];
50089
+ }
50090
+ unresolved.push(token);
50091
+ return fullMatch;
50092
+ });
50093
+ return { resolved, unresolved };
50094
+ }
50095
+ var DISPATCH_MAP;
50096
+ var init_orchestration = __esm({
50097
+ "packages/core/src/orchestration/index.ts"() {
50098
+ "use strict";
50099
+ init_src();
50100
+ init_errors3();
50101
+ init_deps2();
50102
+ init_analyze();
50103
+ init_context2();
50104
+ init_status();
50105
+ DISPATCH_MAP = {
50106
+ research: {
50107
+ keywords: ["research", "investigate", "explore", "analyze", "study"],
50108
+ labels: ["research", "investigation", "analysis"]
50109
+ },
50110
+ consensus: {
50111
+ keywords: ["vote", "validate", "decide", "consensus"],
50112
+ labels: ["consensus", "voting", "decision"]
50113
+ },
50114
+ specification: {
50115
+ keywords: ["spec", "rfc", "design", "specification"],
50116
+ labels: ["specification", "design", "rfc"]
50117
+ },
50118
+ decomposition: {
50119
+ keywords: ["epic", "plan", "decompose", "breakdown"],
49211
50120
  labels: ["decomposition", "planning", "epic"]
49212
50121
  },
49213
50122
  implementation: {
@@ -49313,969 +50222,490 @@ async function getOtelSessions(opts) {
49313
50222
  });
49314
50223
  }
49315
50224
  if (opts.task) {
49316
- sessions2 = sessions2.filter((e) => e.task_id === opts.task);
49317
- }
49318
- return { sessions: sessions2, count: sessions2.length };
49319
- }
49320
- async function getOtelSpawns(opts) {
49321
- const entries = readJsonlFile3(getTokenFilePath2());
49322
- let spawns = entries.filter((e) => e.event_type !== "session_start");
49323
- if (opts.task) {
49324
- spawns = spawns.filter((e) => e.task_id === opts.task);
49325
- }
49326
- return { spawns, count: spawns.length };
49327
- }
49328
- async function getRealTokenUsage(opts) {
49329
- const otelEnabled = process.env.CLAUDE_CODE_ENABLE_TELEMETRY === "1";
49330
- const entries = readJsonlFile3(getTokenFilePath2());
49331
- if (entries.length === 0) {
49332
- return {
49333
- message: otelEnabled ? "OTel enabled but no token data recorded yet" : "Real token usage requires OpenTelemetry configuration",
49334
- otelEnabled,
49335
- totalEvents: 0
49336
- };
49337
- }
49338
- let filtered = entries;
49339
- if (opts.session) {
49340
- filtered = filtered.filter((e) => {
49341
- const ctx = e.context ?? {};
49342
- return ctx.session_id === opts.session || e.session_id === opts.session;
49343
- });
49344
- }
49345
- if (opts.since) {
49346
- const sinceDate = new Date(opts.since).getTime();
49347
- filtered = filtered.filter((e) => {
49348
- const ts = e.timestamp ?? e.recorded_at;
49349
- return ts ? new Date(ts).getTime() >= sinceDate : true;
49350
- });
49351
- }
49352
- const totalTokens = filtered.reduce((sum, e) => sum + (e.estimated_tokens ?? 0), 0);
49353
- const inputTokens = filtered.reduce((sum, e) => sum + (e.input_tokens ?? 0), 0);
49354
- const outputTokens = filtered.reduce((sum, e) => sum + (e.output_tokens ?? 0), 0);
49355
- return {
49356
- otelEnabled,
49357
- totalEvents: filtered.length,
49358
- totalTokens,
49359
- inputTokens,
49360
- outputTokens,
49361
- filters: {
49362
- session: opts.session ?? null,
49363
- since: opts.since ?? null
49364
- }
49365
- };
49366
- }
49367
- async function clearOtelData() {
49368
- const tokenFile = getTokenFilePath2();
49369
- if (existsSync58(tokenFile)) {
49370
- const backup = `${tokenFile}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
49371
- copyFileSync6(tokenFile, backup);
49372
- writeFileSync5(tokenFile, "");
49373
- return { message: "Token tracking cleared", backup };
49374
- }
49375
- return { message: "No token file to clear" };
49376
- }
49377
- var init_otel = __esm({
49378
- "packages/core/src/otel/index.ts"() {
49379
- "use strict";
49380
- }
49381
- });
49382
-
49383
- // packages/core/src/phases/index.ts
49384
- var phases_exports = {};
49385
- __export(phases_exports, {
49386
- advancePhase: () => advancePhase,
49387
- completePhase: () => completePhase,
49388
- deletePhase: () => deletePhase,
49389
- listPhases: () => listPhases,
49390
- renamePhase: () => renamePhase,
49391
- setPhase: () => setPhase,
49392
- showPhase: () => showPhase,
49393
- startPhase: () => startPhase
49394
- });
49395
- async function listPhases(_cwd, accessor) {
49396
- const meta = await accessor.getMetaValue("project_meta");
49397
- const phases = meta?.phases ?? {};
49398
- const currentPhase = meta?.currentPhase ?? null;
49399
- const entries = Object.entries(phases).map(([slug, phase]) => ({
49400
- slug,
49401
- name: phase.name,
49402
- order: phase.order,
49403
- status: phase.status,
49404
- startedAt: phase.startedAt ?? null,
49405
- completedAt: phase.completedAt ?? null,
49406
- isCurrent: slug === currentPhase
49407
- })).sort((a, b) => a.order - b.order);
49408
- return {
49409
- currentPhase,
49410
- phases: entries,
49411
- summary: {
49412
- total: entries.length,
49413
- pending: entries.filter((p) => p.status === "pending").length,
49414
- active: entries.filter((p) => p.status === "active").length,
49415
- completed: entries.filter((p) => p.status === "completed").length
49416
- }
49417
- };
49418
- }
49419
- async function showPhase(slug, _cwd, accessor) {
49420
- const meta = await accessor.getMetaValue("project_meta");
49421
- const targetSlug = slug ?? meta?.currentPhase ?? null;
49422
- if (!targetSlug) {
49423
- throw new CleoError(4 /* NOT_FOUND */, "No current phase set");
49424
- }
49425
- const phase = meta?.phases?.[targetSlug];
49426
- if (!phase) {
49427
- throw new CleoError(4 /* NOT_FOUND */, `Phase '${targetSlug}' not found`);
49428
- }
49429
- const { tasks: phaseTasks } = await accessor.queryTasks({ phase: targetSlug });
49430
- const completedTasks = phaseTasks.filter((t) => t.status === "done");
49431
- return {
49432
- slug: targetSlug,
49433
- name: phase.name,
49434
- status: phase.status,
49435
- order: phase.order,
49436
- startedAt: phase.startedAt ?? null,
49437
- completedAt: phase.completedAt ?? null,
49438
- taskCount: phaseTasks.length,
49439
- completedTaskCount: completedTasks.length
49440
- };
49441
- }
49442
- async function setPhase(options, _cwd, accessor) {
49443
- const meta = await accessor.getMetaValue("project_meta");
49444
- const phases = meta?.phases ?? {};
49445
- if (!phases[options.slug]) {
49446
- throw new CleoError(4 /* NOT_FOUND */, `Phase '${options.slug}' does not exist`);
49447
- }
49448
- const oldPhase = meta?.currentPhase ?? null;
49449
- let isRollback = false;
49450
- let isSkip = false;
49451
- let skippedPhases = 0;
49452
- if (oldPhase && phases[oldPhase]) {
49453
- const oldOrder = phases[oldPhase].order;
49454
- const newOrder = phases[options.slug].order;
49455
- if (newOrder < oldOrder) {
49456
- isRollback = true;
49457
- if (!options.rollback) {
49458
- throw new CleoError(
49459
- 6 /* VALIDATION_ERROR */,
49460
- `Rolling back from '${oldPhase}' (order ${oldOrder}) to '${options.slug}' (order ${newOrder}) requires --rollback flag`
49461
- );
49462
- }
49463
- if (!options.force) {
49464
- throw new CleoError(
49465
- 6 /* VALIDATION_ERROR */,
49466
- "Rollback requires --force flag in non-interactive mode"
49467
- );
49468
- }
49469
- } else if (newOrder > oldOrder + 1) {
49470
- isSkip = true;
49471
- skippedPhases = newOrder - oldOrder - 1;
49472
- }
49473
- }
49474
- if (options.dryRun) {
49475
- return {
49476
- previousPhase: oldPhase,
49477
- currentPhase: options.slug,
49478
- isRollback,
49479
- isSkip,
49480
- ...isSkip && { skippedPhases },
49481
- dryRun: true
49482
- };
49483
- }
49484
- const updatedMeta = meta ?? { name: "", phases: {} };
49485
- updatedMeta.currentPhase = options.slug;
49486
- if (isRollback && oldPhase) {
49487
- const taskCount = await accessor.countTasks({ status: void 0 });
49488
- addPhaseHistoryEntryToMeta(
49489
- updatedMeta,
49490
- options.slug,
49491
- "rollback",
49492
- oldPhase,
49493
- `Rollback from ${oldPhase}`,
49494
- taskCount
49495
- );
49496
- }
49497
- await accessor.setMetaValue("project_meta", updatedMeta);
49498
- await logOperation(
49499
- "phase_set",
49500
- options.slug,
49501
- {
49502
- previousPhase: oldPhase,
49503
- isRollback
49504
- },
49505
- accessor
49506
- );
49507
- return {
49508
- previousPhase: oldPhase,
49509
- currentPhase: options.slug,
49510
- isRollback,
49511
- isSkip,
49512
- ...isSkip && {
49513
- skippedPhases,
49514
- warning: `Skipped ${skippedPhases} intermediate phase(s)`
49515
- }
49516
- };
49517
- }
49518
- async function startPhase(slug, _cwd, accessor) {
49519
- const meta = await accessor.getMetaValue("project_meta");
49520
- const phase = meta?.phases?.[slug];
49521
- if (!phase) {
49522
- throw new CleoError(4 /* NOT_FOUND */, `Phase '${slug}' does not exist`);
49523
- }
49524
- if (phase.status !== "pending") {
49525
- throw new CleoError(
49526
- 2 /* INVALID_INPUT */,
49527
- `Can only start pending phases. Phase '${slug}' has status '${phase.status}'`
49528
- );
49529
- }
49530
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
49531
- phase.status = "active";
49532
- phase.startedAt = now2;
49533
- const updatedMeta = meta;
49534
- const { tasks: phaseTasks } = await accessor.queryTasks({ phase: slug });
49535
- addPhaseHistoryEntryToMeta(
49536
- updatedMeta,
49537
- slug,
49538
- "started",
49539
- null,
49540
- "Phase started",
49541
- phaseTasks.length
49542
- );
49543
- await accessor.setMetaValue("project_meta", updatedMeta);
49544
- await logOperation("phase_started", slug, {}, accessor);
49545
- return { phase: slug, startedAt: now2 };
49546
- }
49547
- async function completePhase(slug, _cwd, accessor) {
49548
- const meta = await accessor.getMetaValue("project_meta");
49549
- const phase = meta?.phases?.[slug];
49550
- if (!phase) {
49551
- throw new CleoError(4 /* NOT_FOUND */, `Phase '${slug}' does not exist`);
49552
- }
49553
- if (phase.status !== "active") {
49554
- throw new CleoError(
49555
- 2 /* INVALID_INPUT */,
49556
- `Can only complete active phases. Phase '${slug}' has status '${phase.status}'`
49557
- );
49558
- }
49559
- const { tasks: phaseTasks } = await accessor.queryTasks({ phase: slug });
49560
- const incompleteTasks = phaseTasks.filter((t) => t.status !== "done");
49561
- if (incompleteTasks.length > 0) {
49562
- throw new CleoError(
49563
- 6 /* VALIDATION_ERROR */,
49564
- `Cannot complete phase '${slug}' - ${incompleteTasks.length} incomplete task(s) pending`
49565
- );
49566
- }
49567
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
49568
- phase.status = "completed";
49569
- phase.completedAt = now2;
49570
- const updatedMeta = meta;
49571
- addPhaseHistoryEntryToMeta(
49572
- updatedMeta,
49573
- slug,
49574
- "completed",
49575
- null,
49576
- "Phase completed",
49577
- phaseTasks.length
49578
- );
49579
- await accessor.setMetaValue("project_meta", updatedMeta);
49580
- await logOperation("phase_completed", slug, {}, accessor);
49581
- return { phase: slug, completedAt: now2 };
49582
- }
49583
- async function advancePhase(force = false, _cwd, accessor) {
49584
- const meta = await accessor.getMetaValue("project_meta");
49585
- const currentSlug = meta?.currentPhase ?? null;
49586
- if (!currentSlug) {
49587
- throw new CleoError(4 /* NOT_FOUND */, "No current phase set");
49588
- }
49589
- const phases = meta?.phases ?? {};
49590
- const currentPhase = phases[currentSlug];
49591
- if (!currentPhase) {
49592
- throw new CleoError(4 /* NOT_FOUND */, `Current phase '${currentSlug}' not found`);
49593
- }
49594
- const sortedEntries = Object.entries(phases).sort(([, a], [, b]) => a.order - b.order);
49595
- const currentIndex = sortedEntries.findIndex(([slug]) => slug === currentSlug);
49596
- if (currentIndex === -1 || currentIndex >= sortedEntries.length - 1) {
49597
- throw new CleoError(100 /* NO_DATA */, `No more phases after '${currentSlug}'`);
49598
- }
49599
- const [nextSlug] = sortedEntries[currentIndex + 1];
49600
- const { tasks: phaseTasks } = await accessor.queryTasks({ phase: currentSlug });
49601
- const incompleteTasks = phaseTasks.filter((t) => t.status !== "done");
49602
- if (incompleteTasks.length > 0) {
49603
- const criticalTasks = incompleteTasks.filter((t) => t.priority === "critical");
49604
- if (criticalTasks.length > 0) {
49605
- throw new CleoError(
49606
- 6 /* VALIDATION_ERROR */,
49607
- `Cannot advance - ${criticalTasks.length} critical task(s) remain in phase '${currentSlug}'`
49608
- );
49609
- }
49610
- const totalTasks = phaseTasks.length;
49611
- const completionPercent = totalTasks > 0 ? Math.floor((totalTasks - incompleteTasks.length) * 100 / totalTasks) : 0;
49612
- const threshold = 90;
49613
- if (completionPercent < threshold && !force) {
49614
- throw new CleoError(
49615
- 6 /* VALIDATION_ERROR */,
49616
- `Cannot advance - ${incompleteTasks.length} incomplete task(s) in phase '${currentSlug}' (${completionPercent}% complete, threshold: ${threshold}%)`,
49617
- { fix: "Use --force to override" }
49618
- );
49619
- }
49620
- }
49621
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
49622
- currentPhase.status = "completed";
49623
- currentPhase.completedAt = now2;
49624
- const nextPhase = phases[nextSlug];
49625
- nextPhase.status = "active";
49626
- nextPhase.startedAt = now2;
49627
- const updatedMeta = meta;
49628
- updatedMeta.currentPhase = nextSlug;
49629
- const currentPhaseTaskCount = phaseTasks.length;
49630
- const { tasks: nextPhaseTasks } = await accessor.queryTasks({ phase: nextSlug });
49631
- const nextPhaseTaskCount = nextPhaseTasks.length;
49632
- addPhaseHistoryEntryToMeta(
49633
- updatedMeta,
49634
- currentSlug,
49635
- "completed",
49636
- null,
49637
- "Phase completed via advance",
49638
- currentPhaseTaskCount
49639
- );
49640
- addPhaseHistoryEntryToMeta(
49641
- updatedMeta,
49642
- nextSlug,
49643
- "started",
49644
- currentSlug,
49645
- `Phase started via advance from ${currentSlug}`,
49646
- nextPhaseTaskCount
49647
- );
49648
- await accessor.setMetaValue("project_meta", updatedMeta);
49649
- return {
49650
- previousPhase: currentSlug,
49651
- currentPhase: nextSlug,
49652
- forced: force
49653
- };
49654
- }
49655
- async function renamePhase(oldName, newName, _cwd, accessor) {
49656
- const meta = await accessor.getMetaValue("project_meta");
49657
- const phases = meta?.phases ?? {};
49658
- if (!phases[oldName]) {
49659
- throw new CleoError(4 /* NOT_FOUND */, `Phase '${oldName}' does not exist`);
49660
- }
49661
- if (phases[newName]) {
49662
- throw new CleoError(101 /* ALREADY_EXISTS */, `Phase '${newName}' already exists`);
49663
- }
49664
- if (!/^[a-z][a-z0-9-]*$/.test(newName)) {
49665
- throw new CleoError(2 /* INVALID_INPUT */, `Invalid phase name '${newName}'`);
49666
- }
49667
- phases[newName] = phases[oldName];
49668
- delete phases[oldName];
49669
- const { tasks: oldPhaseTasks } = await accessor.queryTasks({ phase: oldName });
49670
- let tasksUpdated = 0;
49671
- for (const task of oldPhaseTasks) {
49672
- task.phase = newName;
49673
- await accessor.upsertSingleTask(task);
49674
- tasksUpdated++;
49675
- }
49676
- const updatedMeta = meta;
49677
- let currentPhaseUpdated = false;
49678
- if (updatedMeta.currentPhase === oldName) {
49679
- updatedMeta.currentPhase = newName;
49680
- currentPhaseUpdated = true;
49681
- }
49682
- await accessor.setMetaValue("project_meta", updatedMeta);
49683
- const focus = await accessor.getMetaValue("focus_state");
49684
- if (focus?.currentPhase === oldName) {
49685
- focus.currentPhase = newName;
49686
- await accessor.setMetaValue("focus_state", focus);
49687
- }
49688
- return { oldName, newName, tasksUpdated, currentPhaseUpdated };
49689
- }
49690
- async function deletePhase(slug, options = {}, _cwd, accessor) {
49691
- const meta = await accessor.getMetaValue("project_meta");
49692
- const phases = meta?.phases ?? {};
49693
- if (!phases[slug]) {
49694
- throw new CleoError(4 /* NOT_FOUND */, `Phase '${slug}' does not exist`);
49695
- }
49696
- if (meta?.currentPhase === slug) {
49697
- throw new CleoError(
49698
- 6 /* VALIDATION_ERROR */,
49699
- `Cannot delete current project phase '${slug}'. Use 'phase set' to change phase first`
49700
- );
49701
- }
49702
- const { tasks: phaseTasks } = await accessor.queryTasks({ phase: slug });
49703
- if (phaseTasks.length > 0 && !options.reassignTo) {
49704
- throw new CleoError(
49705
- 6 /* VALIDATION_ERROR */,
49706
- `Cannot delete '${slug}': ${phaseTasks.length} tasks would be orphaned. Use --reassign-to <phase>`
49707
- );
49708
- }
49709
- if (!options.force) {
49710
- throw new CleoError(2 /* INVALID_INPUT */, "Phase deletion requires --force flag for safety");
49711
- }
49712
- if (options.reassignTo) {
49713
- if (!phases[options.reassignTo]) {
49714
- throw new CleoError(
49715
- 4 /* NOT_FOUND */,
49716
- `Reassignment target phase '${options.reassignTo}' does not exist`
49717
- );
49718
- }
49719
- }
49720
- let tasksReassigned = 0;
49721
- if (options.reassignTo) {
49722
- for (const task of phaseTasks) {
49723
- task.phase = options.reassignTo;
49724
- await accessor.upsertSingleTask(task);
49725
- tasksReassigned++;
49726
- }
49727
- }
49728
- delete phases[slug];
49729
- await accessor.setMetaValue("project_meta", meta);
49730
- return {
49731
- deletedPhase: slug,
49732
- tasksReassigned,
49733
- reassignedTo: options.reassignTo ?? null
49734
- };
49735
- }
49736
- function addPhaseHistoryEntryToMeta(meta, phase, transitionType, fromPhase, reason, taskCount) {
49737
- if (!meta.phaseHistory) {
49738
- meta.phaseHistory = [];
49739
- }
49740
- meta.phaseHistory.push({
49741
- phase,
49742
- transitionType,
49743
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
49744
- taskCount,
49745
- fromPhase,
49746
- reason
49747
- });
49748
- }
49749
- var init_phases = __esm({
49750
- "packages/core/src/phases/index.ts"() {
49751
- "use strict";
49752
- init_src();
49753
- init_errors3();
49754
- init_add();
50225
+ sessions2 = sessions2.filter((e) => e.task_id === opts.task);
49755
50226
  }
49756
- });
49757
-
49758
- // packages/core/src/pipeline/phase.ts
49759
- async function listPhases2(projectRoot, accessor) {
49760
- try {
49761
- const result = await listPhases(projectRoot, accessor);
49762
- return { success: true, data: result };
49763
- } catch (err) {
49764
- const message = err instanceof Error ? err.message : String(err);
49765
- return {
49766
- success: false,
49767
- error: { code: "E_PHASE_LIST_FAILED", message }
49768
- };
50227
+ return { sessions: sessions2, count: sessions2.length };
50228
+ }
50229
+ async function getOtelSpawns(opts) {
50230
+ const entries = readJsonlFile3(getTokenFilePath2());
50231
+ let spawns = entries.filter((e) => e.event_type !== "session_start");
50232
+ if (opts.task) {
50233
+ spawns = spawns.filter((e) => e.task_id === opts.task);
49769
50234
  }
50235
+ return { spawns, count: spawns.length };
49770
50236
  }
49771
- async function showPhase2(projectRoot, phaseId, accessor) {
49772
- try {
49773
- const result = await showPhase(phaseId, projectRoot, accessor);
49774
- return { success: true, data: result };
49775
- } catch (err) {
49776
- const message = err instanceof Error ? err.message : String(err);
50237
+ async function getRealTokenUsage(opts) {
50238
+ const otelEnabled = process.env.CLAUDE_CODE_ENABLE_TELEMETRY === "1";
50239
+ const entries = readJsonlFile3(getTokenFilePath2());
50240
+ if (entries.length === 0) {
49777
50241
  return {
49778
- success: false,
49779
- error: { code: "E_PHASE_SHOW_FAILED", message }
50242
+ message: otelEnabled ? "OTel enabled but no token data recorded yet" : "Real token usage requires OpenTelemetry configuration",
50243
+ otelEnabled,
50244
+ totalEvents: 0
49780
50245
  };
49781
50246
  }
50247
+ let filtered = entries;
50248
+ if (opts.session) {
50249
+ filtered = filtered.filter((e) => {
50250
+ const ctx = e.context ?? {};
50251
+ return ctx.session_id === opts.session || e.session_id === opts.session;
50252
+ });
50253
+ }
50254
+ if (opts.since) {
50255
+ const sinceDate = new Date(opts.since).getTime();
50256
+ filtered = filtered.filter((e) => {
50257
+ const ts = e.timestamp ?? e.recorded_at;
50258
+ return ts ? new Date(ts).getTime() >= sinceDate : true;
50259
+ });
50260
+ }
50261
+ const totalTokens = filtered.reduce((sum, e) => sum + (e.estimated_tokens ?? 0), 0);
50262
+ const inputTokens = filtered.reduce((sum, e) => sum + (e.input_tokens ?? 0), 0);
50263
+ const outputTokens = filtered.reduce((sum, e) => sum + (e.output_tokens ?? 0), 0);
50264
+ return {
50265
+ otelEnabled,
50266
+ totalEvents: filtered.length,
50267
+ totalTokens,
50268
+ inputTokens,
50269
+ outputTokens,
50270
+ filters: {
50271
+ session: opts.session ?? null,
50272
+ since: opts.since ?? null
50273
+ }
50274
+ };
50275
+ }
50276
+ async function clearOtelData() {
50277
+ const tokenFile = getTokenFilePath2();
50278
+ if (existsSync58(tokenFile)) {
50279
+ const backup = `${tokenFile}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
50280
+ copyFileSync6(tokenFile, backup);
50281
+ writeFileSync5(tokenFile, "");
50282
+ return { message: "Token tracking cleared", backup };
50283
+ }
50284
+ return { message: "No token file to clear" };
49782
50285
  }
49783
- var init_phase = __esm({
49784
- "packages/core/src/pipeline/phase.ts"() {
50286
+ var init_otel = __esm({
50287
+ "packages/core/src/otel/index.ts"() {
49785
50288
  "use strict";
49786
- init_phases();
49787
50289
  }
49788
50290
  });
49789
50291
 
49790
- // packages/core/src/pipeline/index.ts
49791
- var pipeline_exports2 = {};
49792
- __export(pipeline_exports2, {
49793
- listPhases: () => listPhases2,
49794
- showPhase: () => showPhase2
49795
- });
49796
- var init_pipeline2 = __esm({
49797
- "packages/core/src/pipeline/index.ts"() {
49798
- "use strict";
49799
- init_phase();
49800
- }
50292
+ // packages/core/src/phases/index.ts
50293
+ var phases_exports = {};
50294
+ __export(phases_exports, {
50295
+ advancePhase: () => advancePhase,
50296
+ completePhase: () => completePhase,
50297
+ deletePhase: () => deletePhase,
50298
+ listPhases: () => listPhases,
50299
+ renamePhase: () => renamePhase,
50300
+ setPhase: () => setPhase,
50301
+ showPhase: () => showPhase,
50302
+ startPhase: () => startPhase
49801
50303
  });
49802
-
49803
- // packages/core/src/tasks/complete.ts
49804
- function isVerificationGate(value) {
49805
- return VERIFICATION_GATES.has(value);
49806
- }
49807
- async function loadCompletionEnforcement(cwd) {
49808
- const isTest = !!process.env.VITEST;
49809
- const config2 = await loadConfig(cwd);
49810
- const acceptance = config2.enforcement?.acceptance;
49811
- const verificationCfg = config2.verification;
49812
- const acceptanceMode = acceptance?.mode ?? (isTest ? "off" : "block");
49813
- const acceptanceRequiredForPriorities = acceptance?.requiredForPriorities ?? (isTest ? [] : ["critical", "high", "medium", "low"]);
49814
- const rawVerificationEnabled = await getRawConfigValue("verification.enabled", cwd);
49815
- const verificationEnabled = rawVerificationEnabled !== void 0 ? rawVerificationEnabled : !isTest;
49816
- const verificationRequiredGates = (verificationCfg?.requiredGates ?? []).filter(isVerificationGate).length > 0 ? (verificationCfg?.requiredGates ?? []).filter(isVerificationGate) : DEFAULT_VERIFICATION_REQUIRED_GATES;
49817
- const verificationMaxRounds = verificationCfg?.maxRounds ?? 5;
49818
- const lifecycleMode = config2.lifecycle?.mode ?? (isTest ? "off" : "strict");
50304
+ async function listPhases(_cwd, accessor) {
50305
+ const meta = await accessor.getMetaValue("project_meta");
50306
+ const phases = meta?.phases ?? {};
50307
+ const currentPhase = meta?.currentPhase ?? null;
50308
+ const entries = Object.entries(phases).map(([slug, phase]) => ({
50309
+ slug,
50310
+ name: phase.name,
50311
+ order: phase.order,
50312
+ status: phase.status,
50313
+ startedAt: phase.startedAt ?? null,
50314
+ completedAt: phase.completedAt ?? null,
50315
+ isCurrent: slug === currentPhase
50316
+ })).sort((a, b) => a.order - b.order);
49819
50317
  return {
49820
- acceptanceMode,
49821
- acceptanceRequiredForPriorities,
49822
- verificationEnabled,
49823
- verificationRequiredGates,
49824
- verificationMaxRounds,
49825
- lifecycleMode
50318
+ currentPhase,
50319
+ phases: entries,
50320
+ summary: {
50321
+ total: entries.length,
50322
+ pending: entries.filter((p) => p.status === "pending").length,
50323
+ active: entries.filter((p) => p.status === "active").length,
50324
+ completed: entries.filter((p) => p.status === "completed").length
50325
+ }
49826
50326
  };
49827
50327
  }
49828
- async function completeTask(options, cwd, accessor) {
49829
- const acc = accessor ?? await getAccessor(cwd);
49830
- const task = await acc.loadSingleTask(options.taskId);
49831
- if (!task) {
49832
- throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${options.taskId}`, {
49833
- fix: `Use 'cleo find "${options.taskId}"' to search`
49834
- });
50328
+ async function showPhase(slug, _cwd, accessor) {
50329
+ const meta = await accessor.getMetaValue("project_meta");
50330
+ const targetSlug = slug ?? meta?.currentPhase ?? null;
50331
+ if (!targetSlug) {
50332
+ throw new CleoError(4 /* NOT_FOUND */, "No current phase set");
49835
50333
  }
49836
- await requireActiveSession("tasks.complete", cwd);
49837
- const enforcement = await loadCompletionEnforcement(cwd);
49838
- if (task.status === "done") {
49839
- throw new CleoError(17 /* TASK_COMPLETED */, `Task ${options.taskId} is already completed`);
50334
+ const phase = meta?.phases?.[targetSlug];
50335
+ if (!phase) {
50336
+ throw new CleoError(4 /* NOT_FOUND */, `Phase '${targetSlug}' not found`);
49840
50337
  }
49841
- if (task.depends?.length) {
49842
- const deps = await acc.loadTasks(task.depends);
49843
- const incompleteDeps = deps.filter((d) => d.status !== "done" && d.status !== "cancelled").map((d) => d.id);
49844
- if (incompleteDeps.length > 0) {
49845
- throw new CleoError(
49846
- 5 /* DEPENDENCY_ERROR */,
49847
- `Task ${options.taskId} has incomplete dependencies: ${incompleteDeps.join(", ")}`,
49848
- {
49849
- fix: `Complete dependencies first: ${incompleteDeps.map((d) => `cleo complete ${d}`).join(", ")}`
49850
- }
49851
- );
50338
+ const { tasks: phaseTasks } = await accessor.queryTasks({ phase: targetSlug });
50339
+ const completedTasks = phaseTasks.filter((t) => t.status === "done");
50340
+ return {
50341
+ slug: targetSlug,
50342
+ name: phase.name,
50343
+ status: phase.status,
50344
+ order: phase.order,
50345
+ startedAt: phase.startedAt ?? null,
50346
+ completedAt: phase.completedAt ?? null,
50347
+ taskCount: phaseTasks.length,
50348
+ completedTaskCount: completedTasks.length
50349
+ };
50350
+ }
50351
+ async function setPhase(options, _cwd, accessor) {
50352
+ const meta = await accessor.getMetaValue("project_meta");
50353
+ const phases = meta?.phases ?? {};
50354
+ if (!phases[options.slug]) {
50355
+ throw new CleoError(4 /* NOT_FOUND */, `Phase '${options.slug}' does not exist`);
50356
+ }
50357
+ const oldPhase = meta?.currentPhase ?? null;
50358
+ let isRollback = false;
50359
+ let isSkip = false;
50360
+ let skippedPhases = 0;
50361
+ if (oldPhase && phases[oldPhase]) {
50362
+ const oldOrder = phases[oldPhase].order;
50363
+ const newOrder = phases[options.slug].order;
50364
+ if (newOrder < oldOrder) {
50365
+ isRollback = true;
50366
+ if (!options.rollback) {
50367
+ throw new CleoError(
50368
+ 6 /* VALIDATION_ERROR */,
50369
+ `Rolling back from '${oldPhase}' (order ${oldOrder}) to '${options.slug}' (order ${newOrder}) requires --rollback flag`
50370
+ );
50371
+ }
50372
+ if (!options.force) {
50373
+ throw new CleoError(
50374
+ 6 /* VALIDATION_ERROR */,
50375
+ "Rollback requires --force flag in non-interactive mode"
50376
+ );
50377
+ }
50378
+ } else if (newOrder > oldOrder + 1) {
50379
+ isSkip = true;
50380
+ skippedPhases = newOrder - oldOrder - 1;
49852
50381
  }
49853
50382
  }
49854
- const acceptanceEnforcement = await createAcceptanceEnforcement(cwd);
49855
- const completionValidation = acceptanceEnforcement.validateCompletion(task);
49856
- if (!completionValidation.valid) {
49857
- throw new CleoError(
49858
- completionValidation.exitCode ?? 6 /* VALIDATION_ERROR */,
49859
- completionValidation.error,
49860
- { fix: completionValidation.fix }
49861
- );
50383
+ if (options.dryRun) {
50384
+ return {
50385
+ previousPhase: oldPhase,
50386
+ currentPhase: options.slug,
50387
+ isRollback,
50388
+ isSkip,
50389
+ ...isSkip && { skippedPhases },
50390
+ dryRun: true
50391
+ };
49862
50392
  }
49863
- if (enforcement.verificationEnabled && task.type !== "epic") {
49864
- if (!task.verification) {
49865
- throw new CleoError(
49866
- 40 /* VERIFICATION_INIT_FAILED */,
49867
- `Task ${options.taskId} is missing verification metadata`,
49868
- {
49869
- fix: `Initialize verification for ${options.taskId} before completion`
49870
- }
49871
- );
49872
- }
49873
- if (task.verification.round > enforcement.verificationMaxRounds) {
49874
- throw new CleoError(
49875
- 44 /* MAX_ROUNDS_EXCEEDED */,
49876
- `Task ${options.taskId} exceeded verification max rounds (${enforcement.verificationMaxRounds})`,
49877
- {
49878
- fix: `Review failure log and resolve blockers before retrying completion`
49879
- }
49880
- );
49881
- }
49882
- const missingRequiredGates = enforcement.verificationRequiredGates.filter(
49883
- (gate) => task.verification?.gates?.[gate] !== true
50393
+ const updatedMeta = meta ?? { name: "", phases: {} };
50394
+ updatedMeta.currentPhase = options.slug;
50395
+ if (isRollback && oldPhase) {
50396
+ const taskCount = await accessor.countTasks({ status: void 0 });
50397
+ addPhaseHistoryEntryToMeta(
50398
+ updatedMeta,
50399
+ options.slug,
50400
+ "rollback",
50401
+ oldPhase,
50402
+ `Rollback from ${oldPhase}`,
50403
+ taskCount
49884
50404
  );
49885
- if (missingRequiredGates.length > 0 || task.verification.passed !== true) {
49886
- const exitCode = enforcement.lifecycleMode === "strict" ? 80 /* LIFECYCLE_GATE_FAILED */ : 45 /* GATE_DEPENDENCY */;
49887
- throw new CleoError(
49888
- exitCode,
49889
- `Task ${options.taskId} failed verification gates: ${missingRequiredGates.join(", ") || "verification.passed=false"}`,
49890
- {
49891
- fix: `Set required verification gates before completion: ${enforcement.verificationRequiredGates.join(", ")}`
49892
- }
49893
- );
49894
- }
49895
50405
  }
49896
- const children = await acc.getChildren(options.taskId);
49897
- const incompleteChildren = children.filter(
49898
- (c) => c.status !== "done" && c.status !== "cancelled"
50406
+ await accessor.setMetaValue("project_meta", updatedMeta);
50407
+ await logOperation(
50408
+ "phase_set",
50409
+ options.slug,
50410
+ {
50411
+ previousPhase: oldPhase,
50412
+ isRollback
50413
+ },
50414
+ accessor
49899
50415
  );
49900
- if (incompleteChildren.length > 0 && task.type === "epic") {
49901
- if (!task.noAutoComplete) {
49902
- throw new CleoError(
49903
- 16 /* HAS_CHILDREN */,
49904
- `Epic ${options.taskId} has ${incompleteChildren.length} incomplete children: ${incompleteChildren.map((c) => c.id).join(", ")}`,
49905
- {
49906
- fix: `Complete children first or use 'cleo update ${options.taskId} --no-auto-complete'`
49907
- }
49908
- );
49909
- }
49910
- }
49911
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
49912
- const before = { ...task };
49913
- task.status = "done";
49914
- task.completedAt = now2;
49915
- task.updatedAt = now2;
49916
- if (options.notes) {
49917
- const timestampedNote = `${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}: ${options.notes}`;
49918
- if (!task.notes) task.notes = [];
49919
- task.notes.push(timestampedNote);
49920
- }
49921
- if (options.changeset) {
49922
- if (!task.notes) task.notes = [];
49923
- task.notes.push(`Changeset: ${options.changeset}`);
49924
- }
49925
- const autoCompleted = [];
49926
- const autoCompletedTasks = [];
49927
- if (task.parentId) {
49928
- const parent = await acc.loadSingleTask(task.parentId);
49929
- if (parent && parent.type === "epic" && !parent.noAutoComplete) {
49930
- const siblings = await acc.getChildren(parent.id);
49931
- const allDone = siblings.every(
49932
- (c) => c.id === task.id || c.status === "done" || c.status === "cancelled"
49933
- );
49934
- if (allDone) {
49935
- parent.status = "done";
49936
- parent.completedAt = now2;
49937
- parent.updatedAt = now2;
49938
- autoCompleted.push(parent.id);
49939
- autoCompletedTasks.push(parent);
49940
- }
49941
- }
49942
- }
49943
- await acc.transaction(async (tx) => {
49944
- await tx.upsertSingleTask(task);
49945
- for (const parentTask of autoCompletedTasks) {
49946
- await tx.upsertSingleTask(parentTask);
49947
- }
49948
- await tx.appendLog({
49949
- id: `log-${Math.floor(Date.now() / 1e3)}-${(await import("node:crypto")).randomBytes(3).toString("hex")}`,
49950
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
49951
- action: "task_completed",
49952
- taskId: options.taskId,
49953
- actor: "system",
49954
- details: { title: task.title, previousStatus: before.status },
49955
- before: null,
49956
- after: { title: task.title, previousStatus: before.status }
49957
- });
49958
- });
49959
- const dependents = await acc.getDependents(options.taskId);
49960
- const unblockedTasks = [];
49961
- for (const dep of dependents) {
49962
- if (dep.status === "done" || dep.status === "cancelled") continue;
49963
- if (dep.depends?.length) {
49964
- const depDeps = await acc.loadTasks(dep.depends);
49965
- const stillUnresolved = depDeps.filter(
49966
- (d) => d.id !== options.taskId && d.status !== "done" && d.status !== "cancelled"
49967
- );
49968
- if (stillUnresolved.length === 0) {
49969
- unblockedTasks.push({ id: dep.id, title: dep.title });
49970
- }
49971
- } else {
49972
- unblockedTasks.push({ id: dep.id, title: dep.title });
49973
- }
49974
- }
49975
- Promise.resolve().then(() => (init_auto_extract(), auto_extract_exports)).then(
49976
- ({ extractTaskCompletionMemory: extractTaskCompletionMemory2 }) => extractTaskCompletionMemory2(cwd ?? process.cwd(), task)
49977
- ).catch(() => {
49978
- });
49979
50416
  return {
49980
- task,
49981
- ...autoCompleted.length > 0 && { autoCompleted },
49982
- ...unblockedTasks.length > 0 && { unblockedTasks }
50417
+ previousPhase: oldPhase,
50418
+ currentPhase: options.slug,
50419
+ isRollback,
50420
+ isSkip,
50421
+ ...isSkip && {
50422
+ skippedPhases,
50423
+ warning: `Skipped ${skippedPhases} intermediate phase(s)`
50424
+ }
49983
50425
  };
49984
50426
  }
49985
- var DEFAULT_VERIFICATION_REQUIRED_GATES, VERIFICATION_GATES;
49986
- var init_complete = __esm({
49987
- "packages/core/src/tasks/complete.ts"() {
49988
- "use strict";
49989
- init_src();
49990
- init_config();
49991
- init_errors3();
49992
- init_session_enforcement();
49993
- init_data_accessor();
49994
- init_enforcement();
49995
- DEFAULT_VERIFICATION_REQUIRED_GATES = [
49996
- "implemented",
49997
- "testsPassed",
49998
- "qaPassed",
49999
- "securityPassed",
50000
- "documented"
50001
- ];
50002
- VERIFICATION_GATES = /* @__PURE__ */ new Set([
50003
- "implemented",
50004
- "testsPassed",
50005
- "qaPassed",
50006
- "cleanupDone",
50007
- "securityPassed",
50008
- "documented"
50009
- ]);
50427
+ async function startPhase(slug, _cwd, accessor) {
50428
+ const meta = await accessor.getMetaValue("project_meta");
50429
+ const phase = meta?.phases?.[slug];
50430
+ if (!phase) {
50431
+ throw new CleoError(4 /* NOT_FOUND */, `Phase '${slug}' does not exist`);
50010
50432
  }
50011
- });
50012
-
50013
- // packages/core/src/tasks/update.ts
50014
- var update_exports = {};
50015
- __export(update_exports, {
50016
- updateTask: () => updateTask
50017
- });
50018
- function hasNonStatusDoneFields(options) {
50019
- return NON_STATUS_DONE_FIELDS.some((field) => options[field] !== void 0);
50020
- }
50021
- async function updateTask(options, cwd, accessor) {
50022
- const acc = accessor ?? await getAccessor(cwd);
50023
- const task = await acc.loadSingleTask(options.taskId);
50024
- if (!task) {
50025
- throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${options.taskId}`, {
50026
- fix: `Use 'cleo find "${options.taskId}"' to search`
50027
- });
50433
+ if (phase.status !== "pending") {
50434
+ throw new CleoError(
50435
+ 2 /* INVALID_INPUT */,
50436
+ `Can only start pending phases. Phase '${slug}' has status '${phase.status}'`
50437
+ );
50028
50438
  }
50029
- await requireActiveSession("tasks.update", cwd);
50030
- const changes = [];
50031
50439
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
50032
- const isStatusOnlyDoneTransition = options.status === "done" && task.status !== "done" && !hasNonStatusDoneFields(options);
50033
- if (isStatusOnlyDoneTransition) {
50034
- const result = await completeTask({ taskId: options.taskId }, cwd, accessor);
50035
- return { task: result.task, changes: ["status"] };
50440
+ phase.status = "active";
50441
+ phase.startedAt = now2;
50442
+ const updatedMeta = meta;
50443
+ const { tasks: phaseTasks } = await accessor.queryTasks({ phase: slug });
50444
+ addPhaseHistoryEntryToMeta(
50445
+ updatedMeta,
50446
+ slug,
50447
+ "started",
50448
+ null,
50449
+ "Phase started",
50450
+ phaseTasks.length
50451
+ );
50452
+ await accessor.setMetaValue("project_meta", updatedMeta);
50453
+ await logOperation("phase_started", slug, {}, accessor);
50454
+ return { phase: slug, startedAt: now2 };
50455
+ }
50456
+ async function completePhase(slug, _cwd, accessor) {
50457
+ const meta = await accessor.getMetaValue("project_meta");
50458
+ const phase = meta?.phases?.[slug];
50459
+ if (!phase) {
50460
+ throw new CleoError(4 /* NOT_FOUND */, `Phase '${slug}' does not exist`);
50036
50461
  }
50037
- if (options.status === "done" && task.status !== "done") {
50462
+ if (phase.status !== "active") {
50038
50463
  throw new CleoError(
50039
- 6 /* VALIDATION_ERROR */,
50040
- "status=done must use complete flow; do not combine with other update fields",
50041
- {
50042
- fix: `Run 'cleo complete ${options.taskId}' first, then apply additional updates with 'cleo update ${options.taskId} ...'`
50043
- }
50464
+ 2 /* INVALID_INPUT */,
50465
+ `Can only complete active phases. Phase '${slug}' has status '${phase.status}'`
50044
50466
  );
50045
50467
  }
50046
- const enforcement = await createAcceptanceEnforcement(cwd);
50047
- const updateValidation = enforcement.validateUpdate(task, { acceptance: options.acceptance });
50048
- if (!updateValidation.valid) {
50468
+ const { tasks: phaseTasks } = await accessor.queryTasks({ phase: slug });
50469
+ const incompleteTasks = phaseTasks.filter((t) => t.status !== "done");
50470
+ if (incompleteTasks.length > 0) {
50049
50471
  throw new CleoError(
50050
- updateValidation.exitCode ?? 6 /* VALIDATION_ERROR */,
50051
- updateValidation.error,
50052
- { fix: updateValidation.fix }
50472
+ 6 /* VALIDATION_ERROR */,
50473
+ `Cannot complete phase '${slug}' - ${incompleteTasks.length} incomplete task(s) pending`
50053
50474
  );
50054
50475
  }
50055
- if (options.title !== void 0) {
50056
- validateTitle(options.title);
50057
- task.title = options.title;
50058
- changes.push("title");
50059
- }
50060
- if (options.status !== void 0) {
50061
- validateStatus(options.status);
50062
- const oldStatus = task.status;
50063
- task.status = options.status;
50064
- changes.push("status");
50065
- if (options.status === "done" && oldStatus !== "done") {
50066
- task.completedAt = now2;
50067
- }
50068
- if (options.status === "cancelled" && oldStatus !== "cancelled") {
50069
- task.cancelledAt = now2;
50070
- }
50071
- }
50072
- if (options.priority !== void 0) {
50073
- const normalizedPriority = normalizePriority(options.priority);
50074
- task.priority = normalizedPriority;
50075
- changes.push("priority");
50076
- }
50077
- if (options.type !== void 0) {
50078
- validateTaskType(options.type);
50079
- task.type = options.type;
50080
- changes.push("type");
50081
- }
50082
- if (options.size !== void 0) {
50083
- validateSize(options.size);
50084
- task.size = options.size;
50085
- changes.push("size");
50476
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
50477
+ phase.status = "completed";
50478
+ phase.completedAt = now2;
50479
+ const updatedMeta = meta;
50480
+ addPhaseHistoryEntryToMeta(
50481
+ updatedMeta,
50482
+ slug,
50483
+ "completed",
50484
+ null,
50485
+ "Phase completed",
50486
+ phaseTasks.length
50487
+ );
50488
+ await accessor.setMetaValue("project_meta", updatedMeta);
50489
+ await logOperation("phase_completed", slug, {}, accessor);
50490
+ return { phase: slug, completedAt: now2 };
50491
+ }
50492
+ async function advancePhase(force = false, _cwd, accessor) {
50493
+ const meta = await accessor.getMetaValue("project_meta");
50494
+ const currentSlug = meta?.currentPhase ?? null;
50495
+ if (!currentSlug) {
50496
+ throw new CleoError(4 /* NOT_FOUND */, "No current phase set");
50086
50497
  }
50087
- if (options.phase !== void 0) {
50088
- task.phase = options.phase;
50089
- changes.push("phase");
50498
+ const phases = meta?.phases ?? {};
50499
+ const currentPhase = phases[currentSlug];
50500
+ if (!currentPhase) {
50501
+ throw new CleoError(4 /* NOT_FOUND */, `Current phase '${currentSlug}' not found`);
50090
50502
  }
50091
- if (options.description !== void 0) {
50092
- task.description = options.description;
50093
- changes.push("description");
50503
+ const sortedEntries = Object.entries(phases).sort(([, a], [, b]) => a.order - b.order);
50504
+ const currentIndex = sortedEntries.findIndex(([slug]) => slug === currentSlug);
50505
+ if (currentIndex === -1 || currentIndex >= sortedEntries.length - 1) {
50506
+ throw new CleoError(100 /* NO_DATA */, `No more phases after '${currentSlug}'`);
50094
50507
  }
50095
- if (options.labels !== void 0) {
50096
- if (options.labels.length) validateLabels(options.labels);
50097
- task.labels = options.labels;
50098
- changes.push("labels");
50508
+ const [nextSlug] = sortedEntries[currentIndex + 1];
50509
+ const { tasks: phaseTasks } = await accessor.queryTasks({ phase: currentSlug });
50510
+ const incompleteTasks = phaseTasks.filter((t) => t.status !== "done");
50511
+ if (incompleteTasks.length > 0) {
50512
+ const criticalTasks = incompleteTasks.filter((t) => t.priority === "critical");
50513
+ if (criticalTasks.length > 0) {
50514
+ throw new CleoError(
50515
+ 6 /* VALIDATION_ERROR */,
50516
+ `Cannot advance - ${criticalTasks.length} critical task(s) remain in phase '${currentSlug}'`
50517
+ );
50518
+ }
50519
+ const totalTasks = phaseTasks.length;
50520
+ const completionPercent = totalTasks > 0 ? Math.floor((totalTasks - incompleteTasks.length) * 100 / totalTasks) : 0;
50521
+ const threshold = 90;
50522
+ if (completionPercent < threshold && !force) {
50523
+ throw new CleoError(
50524
+ 6 /* VALIDATION_ERROR */,
50525
+ `Cannot advance - ${incompleteTasks.length} incomplete task(s) in phase '${currentSlug}' (${completionPercent}% complete, threshold: ${threshold}%)`,
50526
+ { fix: "Use --force to override" }
50527
+ );
50528
+ }
50099
50529
  }
50100
- if (options.addLabels?.length) {
50101
- validateLabels(options.addLabels);
50102
- const existing = new Set(task.labels ?? []);
50103
- for (const l of options.addLabels) existing.add(l.trim());
50104
- task.labels = [...existing];
50105
- changes.push("labels");
50530
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
50531
+ currentPhase.status = "completed";
50532
+ currentPhase.completedAt = now2;
50533
+ const nextPhase = phases[nextSlug];
50534
+ nextPhase.status = "active";
50535
+ nextPhase.startedAt = now2;
50536
+ const updatedMeta = meta;
50537
+ updatedMeta.currentPhase = nextSlug;
50538
+ const currentPhaseTaskCount = phaseTasks.length;
50539
+ const { tasks: nextPhaseTasks } = await accessor.queryTasks({ phase: nextSlug });
50540
+ const nextPhaseTaskCount = nextPhaseTasks.length;
50541
+ addPhaseHistoryEntryToMeta(
50542
+ updatedMeta,
50543
+ currentSlug,
50544
+ "completed",
50545
+ null,
50546
+ "Phase completed via advance",
50547
+ currentPhaseTaskCount
50548
+ );
50549
+ addPhaseHistoryEntryToMeta(
50550
+ updatedMeta,
50551
+ nextSlug,
50552
+ "started",
50553
+ currentSlug,
50554
+ `Phase started via advance from ${currentSlug}`,
50555
+ nextPhaseTaskCount
50556
+ );
50557
+ await accessor.setMetaValue("project_meta", updatedMeta);
50558
+ return {
50559
+ previousPhase: currentSlug,
50560
+ currentPhase: nextSlug,
50561
+ forced: force
50562
+ };
50563
+ }
50564
+ async function renamePhase(oldName, newName, _cwd, accessor) {
50565
+ const meta = await accessor.getMetaValue("project_meta");
50566
+ const phases = meta?.phases ?? {};
50567
+ if (!phases[oldName]) {
50568
+ throw new CleoError(4 /* NOT_FOUND */, `Phase '${oldName}' does not exist`);
50106
50569
  }
50107
- if (options.removeLabels?.length) {
50108
- const toRemove = new Set(options.removeLabels.map((l) => l.trim()));
50109
- task.labels = (task.labels ?? []).filter((l) => !toRemove.has(l));
50110
- changes.push("labels");
50570
+ if (phases[newName]) {
50571
+ throw new CleoError(101 /* ALREADY_EXISTS */, `Phase '${newName}' already exists`);
50111
50572
  }
50112
- if (options.depends !== void 0) {
50113
- task.depends = options.depends;
50114
- changes.push("depends");
50573
+ if (!/^[a-z][a-z0-9-]*$/.test(newName)) {
50574
+ throw new CleoError(2 /* INVALID_INPUT */, `Invalid phase name '${newName}'`);
50115
50575
  }
50116
- if (options.addDepends?.length) {
50117
- const existing = new Set(task.depends ?? []);
50118
- for (const d of options.addDepends) existing.add(d.trim());
50119
- task.depends = [...existing];
50120
- changes.push("depends");
50576
+ phases[newName] = phases[oldName];
50577
+ delete phases[oldName];
50578
+ const { tasks: oldPhaseTasks } = await accessor.queryTasks({ phase: oldName });
50579
+ let tasksUpdated = 0;
50580
+ for (const task of oldPhaseTasks) {
50581
+ task.phase = newName;
50582
+ await accessor.upsertSingleTask(task);
50583
+ tasksUpdated++;
50121
50584
  }
50122
- if (options.removeDepends?.length) {
50123
- const toRemove = new Set(options.removeDepends.map((d) => d.trim()));
50124
- task.depends = (task.depends ?? []).filter((d) => !toRemove.has(d));
50125
- changes.push("depends");
50585
+ const updatedMeta = meta;
50586
+ let currentPhaseUpdated = false;
50587
+ if (updatedMeta.currentPhase === oldName) {
50588
+ updatedMeta.currentPhase = newName;
50589
+ currentPhaseUpdated = true;
50126
50590
  }
50127
- if (options.notes !== void 0) {
50128
- const timestampedNote = `${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}: ${options.notes}`;
50129
- if (!task.notes) task.notes = [];
50130
- task.notes.push(timestampedNote);
50131
- changes.push("notes");
50591
+ await accessor.setMetaValue("project_meta", updatedMeta);
50592
+ const focus = await accessor.getMetaValue("focus_state");
50593
+ if (focus?.currentPhase === oldName) {
50594
+ focus.currentPhase = newName;
50595
+ await accessor.setMetaValue("focus_state", focus);
50132
50596
  }
50133
- if (options.acceptance !== void 0) {
50134
- task.acceptance = options.acceptance;
50135
- changes.push("acceptance");
50597
+ return { oldName, newName, tasksUpdated, currentPhaseUpdated };
50598
+ }
50599
+ async function deletePhase(slug, options = {}, _cwd, accessor) {
50600
+ const meta = await accessor.getMetaValue("project_meta");
50601
+ const phases = meta?.phases ?? {};
50602
+ if (!phases[slug]) {
50603
+ throw new CleoError(4 /* NOT_FOUND */, `Phase '${slug}' does not exist`);
50136
50604
  }
50137
- if (options.files !== void 0) {
50138
- task.files = options.files;
50139
- changes.push("files");
50605
+ if (meta?.currentPhase === slug) {
50606
+ throw new CleoError(
50607
+ 6 /* VALIDATION_ERROR */,
50608
+ `Cannot delete current project phase '${slug}'. Use 'phase set' to change phase first`
50609
+ );
50140
50610
  }
50141
- if (options.blockedBy !== void 0) {
50142
- task.blockedBy = options.blockedBy;
50143
- changes.push("blockedBy");
50611
+ const { tasks: phaseTasks } = await accessor.queryTasks({ phase: slug });
50612
+ if (phaseTasks.length > 0 && !options.reassignTo) {
50613
+ throw new CleoError(
50614
+ 6 /* VALIDATION_ERROR */,
50615
+ `Cannot delete '${slug}': ${phaseTasks.length} tasks would be orphaned. Use --reassign-to <phase>`
50616
+ );
50144
50617
  }
50145
- if (options.noAutoComplete !== void 0) {
50146
- task.noAutoComplete = options.noAutoComplete;
50147
- changes.push("noAutoComplete");
50618
+ if (!options.force) {
50619
+ throw new CleoError(2 /* INVALID_INPUT */, "Phase deletion requires --force flag for safety");
50148
50620
  }
50149
- if (options.pipelineStage !== void 0) {
50150
- validatePipelineTransition(task.pipelineStage, options.pipelineStage);
50151
- if (task.type === "epic" && task.pipelineStage) {
50152
- await validateEpicStageAdvancement(
50153
- {
50154
- epicId: task.id,
50155
- currentStage: task.pipelineStage,
50156
- newStage: options.pipelineStage
50157
- },
50158
- acc,
50159
- cwd
50621
+ if (options.reassignTo) {
50622
+ if (!phases[options.reassignTo]) {
50623
+ throw new CleoError(
50624
+ 4 /* NOT_FOUND */,
50625
+ `Reassignment target phase '${options.reassignTo}' does not exist`
50160
50626
  );
50161
50627
  }
50162
- if (task.type !== "epic") {
50163
- const epicAncestor = task.parentId ? await findEpicAncestor(task.parentId, acc) : null;
50164
- const directParent = task.parentId ? await acc.loadSingleTask(task.parentId) : null;
50165
- const epicToCheck = directParent?.type === "epic" ? directParent : epicAncestor;
50166
- if (epicToCheck) {
50167
- await validateChildStageCeiling(
50168
- { childStage: options.pipelineStage, epicId: epicToCheck.id },
50169
- acc,
50170
- cwd
50171
- );
50172
- }
50173
- }
50174
- task.pipelineStage = options.pipelineStage;
50175
- changes.push("pipelineStage");
50176
50628
  }
50177
- if (options.parentId !== void 0) {
50178
- const newParentId = options.parentId || null;
50179
- const currentParentId = task.parentId ?? null;
50180
- if (newParentId !== currentParentId) {
50181
- const originalType = task.type;
50182
- if (!newParentId) {
50183
- task.parentId = null;
50184
- if (task.type === "subtask") task.type = "task";
50185
- changes.push("parentId");
50186
- if (task.type !== originalType) changes.push("type");
50187
- } else {
50188
- const newParent = await acc.loadSingleTask(newParentId);
50189
- if (!newParent) {
50190
- throw new CleoError(10 /* PARENT_NOT_FOUND */, `Parent task ${newParentId} not found`);
50191
- }
50192
- if (newParent.type === "subtask") {
50193
- throw new CleoError(
50194
- 13 /* INVALID_PARENT_TYPE */,
50195
- `Cannot parent under subtask '${newParentId}'`
50196
- );
50197
- }
50198
- const subtree = await acc.getSubtree(options.taskId);
50199
- if (subtree.some((t) => t.id === newParentId)) {
50200
- throw new CleoError(
50201
- 14 /* CIRCULAR_REFERENCE */,
50202
- `Moving '${options.taskId}' under '${newParentId}' would create a circular reference`
50203
- );
50204
- }
50205
- const ancestors = await acc.getAncestorChain(newParentId);
50206
- const parentDepth = ancestors.length;
50207
- const config2 = await loadConfig(cwd);
50208
- const policy = resolveHierarchyPolicy(config2);
50209
- if (parentDepth + 1 >= policy.maxDepth) {
50210
- throw new CleoError(
50211
- 11 /* DEPTH_EXCEEDED */,
50212
- `Maximum nesting depth ${policy.maxDepth} would be exceeded`
50213
- );
50214
- }
50215
- task.parentId = newParentId;
50216
- const newDepth = parentDepth + 1;
50217
- if (newDepth === 1) task.type = "task";
50218
- else if (newDepth >= 2) task.type = "subtask";
50219
- changes.push("parentId");
50220
- if (task.type !== originalType) changes.push("type");
50221
- }
50629
+ let tasksReassigned = 0;
50630
+ if (options.reassignTo) {
50631
+ for (const task of phaseTasks) {
50632
+ task.phase = options.reassignTo;
50633
+ await accessor.upsertSingleTask(task);
50634
+ tasksReassigned++;
50222
50635
  }
50223
50636
  }
50224
- if (changes.length === 0) {
50225
- throw new CleoError(102 /* NO_CHANGE */, "No changes specified");
50637
+ delete phases[slug];
50638
+ await accessor.setMetaValue("project_meta", meta);
50639
+ return {
50640
+ deletedPhase: slug,
50641
+ tasksReassigned,
50642
+ reassignedTo: options.reassignTo ?? null
50643
+ };
50644
+ }
50645
+ function addPhaseHistoryEntryToMeta(meta, phase, transitionType, fromPhase, reason, taskCount) {
50646
+ if (!meta.phaseHistory) {
50647
+ meta.phaseHistory = [];
50226
50648
  }
50227
- task.updatedAt = now2;
50228
- await acc.transaction(async (tx) => {
50229
- await tx.upsertSingleTask(task);
50230
- await tx.appendLog({
50231
- id: `log-${Math.floor(Date.now() / 1e3)}-${(await import("node:crypto")).randomBytes(3).toString("hex")}`,
50232
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
50233
- action: "task_updated",
50234
- taskId: options.taskId,
50235
- actor: "system",
50236
- details: { changes, title: task.title },
50237
- before: null,
50238
- after: { changes, title: task.title }
50239
- });
50649
+ meta.phaseHistory.push({
50650
+ phase,
50651
+ transitionType,
50652
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
50653
+ taskCount,
50654
+ fromPhase,
50655
+ reason
50240
50656
  });
50241
- return { task, changes };
50242
50657
  }
50243
- var NON_STATUS_DONE_FIELDS;
50244
- var init_update2 = __esm({
50245
- "packages/core/src/tasks/update.ts"() {
50658
+ var init_phases = __esm({
50659
+ "packages/core/src/phases/index.ts"() {
50246
50660
  "use strict";
50247
50661
  init_src();
50248
- init_config();
50249
50662
  init_errors3();
50250
- init_session_enforcement();
50251
- init_data_accessor();
50252
50663
  init_add();
50253
- init_complete();
50254
- init_enforcement();
50255
- init_epic_enforcement();
50256
- init_hierarchy_policy();
50257
- init_pipeline_stage();
50258
- NON_STATUS_DONE_FIELDS = [
50259
- "title",
50260
- "priority",
50261
- "type",
50262
- "size",
50263
- "phase",
50264
- "description",
50265
- "labels",
50266
- "addLabels",
50267
- "removeLabels",
50268
- "depends",
50269
- "addDepends",
50270
- "removeDepends",
50271
- "notes",
50272
- "acceptance",
50273
- "files",
50274
- "blockedBy",
50275
- "parentId",
50276
- "noAutoComplete",
50277
- "pipelineStage"
50278
- ];
50664
+ }
50665
+ });
50666
+
50667
+ // packages/core/src/pipeline/phase.ts
50668
+ async function listPhases2(projectRoot, accessor) {
50669
+ try {
50670
+ const result = await listPhases(projectRoot, accessor);
50671
+ return { success: true, data: result };
50672
+ } catch (err) {
50673
+ const message = err instanceof Error ? err.message : String(err);
50674
+ return {
50675
+ success: false,
50676
+ error: { code: "E_PHASE_LIST_FAILED", message }
50677
+ };
50678
+ }
50679
+ }
50680
+ async function showPhase2(projectRoot, phaseId, accessor) {
50681
+ try {
50682
+ const result = await showPhase(phaseId, projectRoot, accessor);
50683
+ return { success: true, data: result };
50684
+ } catch (err) {
50685
+ const message = err instanceof Error ? err.message : String(err);
50686
+ return {
50687
+ success: false,
50688
+ error: { code: "E_PHASE_SHOW_FAILED", message }
50689
+ };
50690
+ }
50691
+ }
50692
+ var init_phase = __esm({
50693
+ "packages/core/src/pipeline/phase.ts"() {
50694
+ "use strict";
50695
+ init_phases();
50696
+ }
50697
+ });
50698
+
50699
+ // packages/core/src/pipeline/index.ts
50700
+ var pipeline_exports2 = {};
50701
+ __export(pipeline_exports2, {
50702
+ listPhases: () => listPhases2,
50703
+ showPhase: () => showPhase2
50704
+ });
50705
+ var init_pipeline2 = __esm({
50706
+ "packages/core/src/pipeline/index.ts"() {
50707
+ "use strict";
50708
+ init_phase();
50279
50709
  }
50280
50710
  });
50281
50711
 
@@ -62663,145 +63093,6 @@ var init_system2 = __esm({
62663
63093
  }
62664
63094
  });
62665
63095
 
62666
- // packages/core/src/task-work/index.ts
62667
- var task_work_exports = {};
62668
- __export(task_work_exports, {
62669
- currentTask: () => currentTask,
62670
- getTaskHistory: () => getTaskHistory,
62671
- getWorkHistory: () => getWorkHistory,
62672
- startTask: () => startTask,
62673
- stopTask: () => stopTask
62674
- });
62675
- async function currentTask(cwd, accessor) {
62676
- const acc = accessor ?? await getAccessor(cwd);
62677
- const focus = await acc.getMetaValue("focus_state");
62678
- return {
62679
- currentTask: focus?.currentTask ?? null,
62680
- currentPhase: focus?.currentPhase ?? null,
62681
- sessionNote: focus?.sessionNote ?? null,
62682
- nextAction: focus?.nextAction ?? null
62683
- };
62684
- }
62685
- async function startTask(taskId, cwd, accessor) {
62686
- if (!taskId) {
62687
- throw new CleoError(2 /* INVALID_INPUT */, "Task ID is required");
62688
- }
62689
- const acc = accessor ?? await getAccessor(cwd);
62690
- const task = await acc.loadSingleTask(taskId);
62691
- if (!task) {
62692
- throw new CleoError(4 /* NOT_FOUND */, `Task not found: ${taskId}`, {
62693
- fix: `Use 'cleo find "${taskId}"' to search`
62694
- });
62695
- }
62696
- const { tasks: allTasks } = await acc.queryTasks({});
62697
- const unresolvedDeps = getUnresolvedDeps(taskId, allTasks);
62698
- if (unresolvedDeps.length > 0) {
62699
- throw new CleoError(
62700
- 5 /* DEPENDENCY_ERROR */,
62701
- `Task ${taskId} is blocked by unresolved dependencies: ${unresolvedDeps.join(", ")}`,
62702
- {
62703
- fix: `Complete blockers first: ${unresolvedDeps.map((d) => `cleo complete ${d}`).join(", ")}`
62704
- }
62705
- );
62706
- }
62707
- const focus = await acc.getMetaValue("focus_state") ?? {};
62708
- const previousTask = focus.currentTask ?? null;
62709
- focus.currentTask = taskId;
62710
- focus.currentPhase = task.phase ?? null;
62711
- const noteEntry = {
62712
- note: `Started work on ${taskId}: ${task.title}`,
62713
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
62714
- };
62715
- if (!focus.sessionNotes) {
62716
- focus.sessionNotes = [];
62717
- }
62718
- focus.sessionNotes.push(noteEntry);
62719
- await acc.setMetaValue("focus_state", focus);
62720
- await logOperation(
62721
- "task_start",
62722
- taskId,
62723
- {
62724
- previousTask,
62725
- title: task.title
62726
- },
62727
- accessor
62728
- );
62729
- const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
62730
- hooks2.dispatch("PreToolUse", cwd ?? process.cwd(), {
62731
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
62732
- taskId,
62733
- taskTitle: task.title
62734
- }).catch(() => {
62735
- });
62736
- return {
62737
- taskId,
62738
- taskTitle: task.title,
62739
- previousTask
62740
- };
62741
- }
62742
- async function stopTask(cwd, accessor) {
62743
- const acc = accessor ?? await getAccessor(cwd);
62744
- const focus = await acc.getMetaValue("focus_state");
62745
- const previousTask = focus?.currentTask ?? null;
62746
- if (!focus) {
62747
- return { previousTask: null };
62748
- }
62749
- const taskId = focus.currentTask;
62750
- const task = taskId ? await acc.loadSingleTask(taskId) : void 0;
62751
- focus.currentTask = null;
62752
- focus.nextAction = null;
62753
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
62754
- if (taskId && task) {
62755
- const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
62756
- hooks2.dispatch("PostToolUse", cwd ?? process.cwd(), {
62757
- timestamp: now2,
62758
- taskId,
62759
- taskTitle: task.title,
62760
- status: "done"
62761
- }).catch(() => {
62762
- });
62763
- }
62764
- await acc.setMetaValue("focus_state", focus);
62765
- await logOperation(
62766
- "task_stop",
62767
- previousTask ?? "none",
62768
- {
62769
- previousTask
62770
- },
62771
- accessor
62772
- );
62773
- return { previousTask };
62774
- }
62775
- async function getWorkHistory(cwd, accessor) {
62776
- const acc = accessor ?? await getAccessor(cwd);
62777
- const focus = await acc.getMetaValue("focus_state");
62778
- const notes = focus?.sessionNotes ?? [];
62779
- const history = [];
62780
- for (const note of notes) {
62781
- const match = note.note.match(/^(?:Focus set to|Started work on) (T\d+)/);
62782
- if (match) {
62783
- history.push({
62784
- taskId: match[1],
62785
- timestamp: note.timestamp
62786
- });
62787
- }
62788
- }
62789
- return history.reverse();
62790
- }
62791
- var getTaskHistory;
62792
- var init_task_work = __esm({
62793
- "packages/core/src/task-work/index.ts"() {
62794
- "use strict";
62795
- init_src();
62796
- init_errors3();
62797
- init_data_accessor();
62798
- init_add();
62799
- init_dependency_check();
62800
- init_handlers();
62801
- getTaskHistory = getWorkHistory;
62802
- }
62803
- });
62804
-
62805
63096
  // packages/core/src/tasks/archive.ts
62806
63097
  async function archiveTasks(options = {}, cwd, accessor) {
62807
63098
  const acc = accessor ?? await getAccessor(cwd);
@@ -68304,6 +68595,7 @@ var init_cleo = __esm({
68304
68595
  init_permissions();
68305
68596
  init_registry3();
68306
68597
  init_sharing();
68598
+ init_workspace();
68307
68599
  init_orchestration();
68308
68600
  init_reconciliation();
68309
68601
  init_link_store();
@@ -68542,7 +68834,14 @@ var init_cleo = __esm({
68542
68834
  discover: (p) => discoverRelated(p.query, p.method, p.limit),
68543
68835
  search: (p) => searchAcrossProjects(p.pattern, p.project, p.limit),
68544
68836
  setPermission: (p) => setPermission(p.name, p.level),
68545
- sharingStatus: () => getSharingStatus()
68837
+ sharingStatus: () => getSharingStatus(),
68838
+ route: (message) => {
68839
+ const directive = parseDirective(message);
68840
+ if (!directive) return Promise.resolve([]);
68841
+ return routeDirective(directive);
68842
+ },
68843
+ workspaceStatus: () => workspaceStatus(),
68844
+ workspaceAgents: () => workspaceAgents()
68546
68845
  };
68547
68846
  }
68548
68847
  // === Agents ===
@@ -75669,7 +75968,7 @@ async function runUpgrade(options = {}) {
75669
75968
  return { success: false, upToDate: false, dryRun: isDryRun, actions, applied: 0, errors };
75670
75969
  }
75671
75970
  await forceCheckpointBeforeOperation("storage-migration", options.cwd);
75672
- const { MigrationLogger: MigrationLogger2 } = await Promise.resolve().then(() => (init_logger3(), logger_exports));
75971
+ const { MigrationLogger: MigrationLogger2 } = await Promise.resolve().then(() => (init_logger3(), logger_exports2));
75673
75972
  const {
75674
75973
  createMigrationState: createMigrationState2,
75675
75974
  updateMigrationPhase: updateMigrationPhase2,