@cleocode/cleo 2026.5.131 → 2026.5.133

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/cli/index.js CHANGED
@@ -27495,6 +27495,70 @@ function iterationCapFor(node, runtimeDefault) {
27495
27495
  if (typeof cap === "number" && Number.isFinite(cap) && cap >= 0) return cap;
27496
27496
  return runtimeDefault;
27497
27497
  }
27498
+ function validateDecompositionTaskTree(nodeId, taskTree2) {
27499
+ if (!Array.isArray(taskTree2)) {
27500
+ return `ensures.schema[task_tree] on ${nodeId}: task_tree must be a non-empty array, got ${typeof taskTree2}`;
27501
+ }
27502
+ if (taskTree2.length === 0) {
27503
+ return `ensures.schema[task_tree] on ${nodeId}: task_tree is an empty array \u2014 decomposition produced no tasks`;
27504
+ }
27505
+ const knownIds = /* @__PURE__ */ new Set();
27506
+ for (const entry of taskTree2) {
27507
+ if (typeof entry.id === "string") {
27508
+ knownIds.add(entry.id);
27509
+ }
27510
+ }
27511
+ for (let i = 0; i < taskTree2.length; i++) {
27512
+ const entry = taskTree2[i];
27513
+ if (typeof entry !== "object" || entry === null) {
27514
+ return `ensures.schema[task_tree] on ${nodeId}: entry[${i}] must be an object, got ${entry === null ? "null" : typeof entry}`;
27515
+ }
27516
+ if (typeof entry.title !== "string" || entry.title.trim().length === 0) {
27517
+ return `ensures.schema[task_tree] on ${nodeId}: entry[${i}].title must be a non-empty string`;
27518
+ }
27519
+ if (!Array.isArray(entry.acceptance) || entry.acceptance.length === 0) {
27520
+ return `ensures.schema[task_tree] on ${nodeId}: entry[${i}] ("${entry.title}") must have a non-empty acceptance array`;
27521
+ }
27522
+ const hasValidAc = entry.acceptance.some(
27523
+ (ac) => typeof ac === "string" && ac.trim().length > 0
27524
+ );
27525
+ if (!hasValidAc) {
27526
+ return `ensures.schema[task_tree] on ${nodeId}: entry[${i}] ("${entry.title}") acceptance array contains no non-empty strings`;
27527
+ }
27528
+ if (Array.isArray(entry.depends) && knownIds.size > 0) {
27529
+ for (const depId of entry.depends) {
27530
+ if (typeof depId === "string" && /^T\d{3,}$/.test(depId) && !knownIds.has(depId)) {
27531
+ }
27532
+ }
27533
+ }
27534
+ }
27535
+ return null;
27536
+ }
27537
+ function validateIvtrEvidenceOutput(nodeId, evidence) {
27538
+ if (evidence === null || evidence === void 0) {
27539
+ return `ensures.schema[evidence] on ${nodeId}: evidence must be present (non-null, non-undefined)`;
27540
+ }
27541
+ if (typeof evidence === "string") {
27542
+ if (evidence.trim().length === 0) {
27543
+ return `ensures.schema[evidence] on ${nodeId}: evidence string must not be empty`;
27544
+ }
27545
+ return null;
27546
+ }
27547
+ if (Array.isArray(evidence)) {
27548
+ if (evidence.length === 0) {
27549
+ return `ensures.schema[evidence] on ${nodeId}: evidence array must not be empty`;
27550
+ }
27551
+ return null;
27552
+ }
27553
+ if (typeof evidence === "object") {
27554
+ const keys = Object.keys(evidence);
27555
+ if (keys.length === 0) {
27556
+ return `ensures.schema[evidence] on ${nodeId}: evidence object must have at least one key (got {})`;
27557
+ }
27558
+ return null;
27559
+ }
27560
+ return `ensures.schema[evidence] on ${nodeId}: evidence must be a string, array, or object (got ${typeof evidence})`;
27561
+ }
27498
27562
  async function runFromNode(args) {
27499
27563
  const {
27500
27564
  db,
@@ -27593,6 +27657,39 @@ async function runFromNode(args) {
27593
27657
  }
27594
27658
  }
27595
27659
  }
27660
+ if (node.ensures?.schema) {
27661
+ let schemaViolation = null;
27662
+ if (node.ensures.schema === "task_tree") {
27663
+ schemaViolation = validateDecompositionTaskTree(node.id, context["task_tree"]);
27664
+ } else if (node.ensures.schema === "evidence") {
27665
+ schemaViolation = validateIvtrEvidenceOutput(node.id, context["evidence"]);
27666
+ }
27667
+ if (schemaViolation !== null) {
27668
+ auditContractViolation(
27669
+ args.projectRoot,
27670
+ run.runId,
27671
+ node.id,
27672
+ "ensures",
27673
+ node.ensures.schema,
27674
+ playbook.name
27675
+ );
27676
+ const handled = handleContractErrorHandler(
27677
+ playbook,
27678
+ "contract_violation",
27679
+ schemaViolation
27680
+ );
27681
+ if (handled === "abort") {
27682
+ failedNodeId = node.id;
27683
+ lastError = schemaViolation;
27684
+ } else {
27685
+ context["__ensuresSchemaViolation"] = schemaViolation;
27686
+ if (handled === "hitl_escalate") {
27687
+ exceededNodeId = node.id;
27688
+ }
27689
+ }
27690
+ }
27691
+ }
27692
+ if (failedNodeId !== void 0) break;
27596
27693
  const nextId = resolveNextNodeId(node.id, edgeIndex);
27597
27694
  if (nextId !== null) {
27598
27695
  const edge = resolveEdge(node.id, nextId, playbook.edges);
@@ -27908,6 +28005,8 @@ __export(src_exports, {
27908
28005
  resumePlaybook: () => resumePlaybook,
27909
28006
  updatePlaybookApproval: () => updatePlaybookApproval,
27910
28007
  updatePlaybookRun: () => updatePlaybookRun,
28008
+ validateDecompositionTaskTree: () => validateDecompositionTaskTree,
28009
+ validateIvtrEvidenceOutput: () => validateIvtrEvidenceOutput,
27911
28010
  validatePlaybookCompliance: () => validatePlaybookCompliance
27912
28011
  });
27913
28012
  var PLAYBOOKS_PACKAGE_VERSION;
@@ -27974,8 +28073,8 @@ async function loadPlaybookByName(name) {
27974
28073
  return null;
27975
28074
  }
27976
28075
  try {
27977
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core/internal");
27978
- const projectRoot = __playbookRuntimeOverrides.projectRoot ?? getProjectRoot57();
28076
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core/internal");
28077
+ const projectRoot = __playbookRuntimeOverrides.projectRoot ?? getProjectRoot58();
27979
28078
  const resolved = resolvePlaybook(name, {
27980
28079
  projectRoot,
27981
28080
  globalPlaybooksDir: __playbookRuntimeOverrides.globalPlaybooksDir,
@@ -28019,8 +28118,8 @@ async function acquireDb() {
28019
28118
  async function buildDefaultDispatcher() {
28020
28119
  if (__playbookRuntimeOverrides.dispatcher) return __playbookRuntimeOverrides.dispatcher;
28021
28120
  const { orchestrateSpawnExecute: orchestrateSpawnExecute2 } = await import("@cleocode/runtime/gateway");
28022
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core/internal");
28023
- const projectRoot = getProjectRoot57();
28121
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core/internal");
28122
+ const projectRoot = getProjectRoot58();
28024
28123
  return {
28025
28124
  async dispatch(input2) {
28026
28125
  try {
@@ -28210,8 +28309,8 @@ var init_playbook2 = __esm({
28210
28309
  projectRoot = __playbookRuntimeOverrides.projectRoot;
28211
28310
  } else {
28212
28311
  try {
28213
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core/internal");
28214
- projectRoot = getProjectRoot57();
28312
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core/internal");
28313
+ projectRoot = getProjectRoot58();
28215
28314
  } catch {
28216
28315
  projectRoot = void 0;
28217
28316
  }
@@ -28275,14 +28374,14 @@ var init_playbook2 = __esm({
28275
28374
  const dispatcher = await buildDefaultDispatcher();
28276
28375
  let result;
28277
28376
  try {
28278
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core/internal");
28377
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core/internal");
28279
28378
  const opts = {
28280
28379
  db,
28281
28380
  playbook: parsed.definition,
28282
28381
  playbookHash: parsed.sourceHash,
28283
28382
  initialContext,
28284
28383
  dispatcher,
28285
- projectRoot: getProjectRoot57()
28384
+ projectRoot: getProjectRoot58()
28286
28385
  };
28287
28386
  if (__playbookRuntimeOverrides.approvalSecret !== void 0) {
28288
28387
  opts.approvalSecret = __playbookRuntimeOverrides.approvalSecret;
@@ -28523,7 +28622,7 @@ async function orchestrateAnalyzeOp(params) {
28523
28622
  return orchestrateAnalyze(params.epicId, getProjectRoot11(), params.mode);
28524
28623
  }
28525
28624
  async function orchestrateClassifyOp(params) {
28526
- return orchestrateClassify(params.request, params.context, getProjectRoot11());
28625
+ return orchestrateClassify(params.request, params.context, getProjectRoot11(), params.taskId);
28527
28626
  }
28528
28627
  function orchestrateFanoutStatusOp(params) {
28529
28628
  const entry = fanoutManifestStore.get(params.manifestEntryId);
@@ -28672,7 +28771,69 @@ async function orchestrateApproveOp(params) {
28672
28771
  async function orchestrateRejectOp(params) {
28673
28772
  return Promise.resolve({ success: true, data: params });
28674
28773
  }
28675
- async function orchestrateClassify(request, context, projectRoot) {
28774
+ async function orchestrateClassify(request, context, projectRoot, taskId) {
28775
+ if (taskId) {
28776
+ try {
28777
+ const { getDb: getDb3 } = await import("@cleocode/core/internal");
28778
+ const { tasks } = await import("@cleocode/core/store/tasks-schema");
28779
+ const { eq: eq2 } = await import("drizzle-orm");
28780
+ const { classifyTask: classifyTask2 } = await import("@cleocode/core");
28781
+ const db = await getDb3(projectRoot);
28782
+ const row = await db.select().from(tasks).where(eq2(tasks.id, taskId)).get();
28783
+ if (!row) {
28784
+ return {
28785
+ success: false,
28786
+ error: {
28787
+ code: "E_NOT_FOUND",
28788
+ message: `Task ${taskId} not found`
28789
+ }
28790
+ };
28791
+ }
28792
+ const task = {
28793
+ id: row.id,
28794
+ title: row.title,
28795
+ description: row.description ?? "",
28796
+ status: row.status,
28797
+ priority: row.priority ?? "medium",
28798
+ type: row.type ?? void 0,
28799
+ kind: row.kind ?? void 0,
28800
+ size: row.size ?? void 0,
28801
+ labels: (() => {
28802
+ try {
28803
+ const parsed = JSON.parse(row.labelsJson ?? "[]");
28804
+ return Array.isArray(parsed) ? parsed : [];
28805
+ } catch {
28806
+ return [];
28807
+ }
28808
+ })(),
28809
+ createdAt: row.createdAt
28810
+ };
28811
+ const result = classifyTask2(task);
28812
+ return {
28813
+ success: true,
28814
+ data: {
28815
+ team: result.agentId,
28816
+ lead: result.role === "lead" ? result.agentId : null,
28817
+ protocol: result.role,
28818
+ stage: null,
28819
+ confidence: result.confidence,
28820
+ reasoning: result.warning ? `${result.reason} | warning: ${result.warning}` : result.reason
28821
+ }
28822
+ };
28823
+ } catch (error) {
28824
+ getLogger8("domain:orchestrate").error(
28825
+ { operation: "classify", taskId, err: error },
28826
+ error instanceof Error ? error.message : String(error)
28827
+ );
28828
+ return {
28829
+ success: false,
28830
+ error: {
28831
+ code: "E_CLASSIFY_FAILED",
28832
+ message: error instanceof Error ? error.message : String(error)
28833
+ }
28834
+ };
28835
+ }
28836
+ }
28676
28837
  try {
28677
28838
  const { getCleoCantWorkflowsDir } = await import("@cleocode/core/internal");
28678
28839
  const { readFileSync: readFileSync22, readdirSync: readdirSync3, existsSync: existsSync21 } = await import("node:fs");
@@ -28734,6 +28895,9 @@ async function orchestrateClassify(request, context, projectRoot) {
28734
28895
  }
28735
28896
  matches.sort((a, b) => b.score - a.score);
28736
28897
  const best = matches[0];
28898
+ const allHintWords = best.consultWhen.toLowerCase().split(/\s+/).filter(Boolean);
28899
+ const normalised = allHintWords.length > 0 ? Math.min(best.score / allHintWords.length, 1) : 0;
28900
+ const confidence = best.score > 0 ? Math.max(normalised, 0.5) : 0.1;
28737
28901
  return {
28738
28902
  success: true,
28739
28903
  data: {
@@ -28741,7 +28905,7 @@ async function orchestrateClassify(request, context, projectRoot) {
28741
28905
  lead: null,
28742
28906
  protocol: "base-subagent",
28743
28907
  stage: best.stages[0] ?? null,
28744
- confidence: best.score > 0 ? 0.5 : 0.1,
28908
+ confidence,
28745
28909
  reasoning: best.score > 0 ? `Matched team '${best.team}' via consult-when hint: "${best.consultWhen}"` : `No strong match found; defaulting to first registered team '${best.team}'`
28746
28910
  }
28747
28911
  };
@@ -34920,11 +35084,11 @@ var init_security = __esm({
34920
35084
  });
34921
35085
 
34922
35086
  // packages/cleo/src/dispatch/middleware/sanitizer.ts
34923
- function createSanitizer(getProjectRoot57) {
35087
+ function createSanitizer(getProjectRoot58) {
34924
35088
  return async (req, next) => {
34925
35089
  if (req.params) {
34926
35090
  try {
34927
- const root = getProjectRoot57 ? getProjectRoot57() : void 0;
35091
+ const root = getProjectRoot58 ? getProjectRoot58() : void 0;
34928
35092
  req.params = sanitizeParams(req.params, root, {
34929
35093
  domain: req.domain,
34930
35094
  operation: req.operation
@@ -40725,9 +40889,9 @@ var init_backup = __esm({
40725
40889
  async run({ args }) {
40726
40890
  const scope = args.scope;
40727
40891
  const { packBundle } = await import("@cleocode/core/store/backup-pack.js");
40728
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core");
40892
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core");
40729
40893
  const includesProject = scope === "project" || scope === "all";
40730
- const projectRoot = includesProject ? getProjectRoot57() : void 0;
40894
+ const projectRoot = includesProject ? getProjectRoot58() : void 0;
40731
40895
  let passphrase;
40732
40896
  if (args.encrypt === true) {
40733
40897
  passphrase = process.env["CLEO_BACKUP_PASSPHRASE"];
@@ -40803,12 +40967,12 @@ var init_backup = __esm({
40803
40967
  },
40804
40968
  async run({ args }) {
40805
40969
  const bundlePath = args.bundle;
40806
- const { getProjectRoot: getProjectRoot57, getCleoHome: getCleoHome6, getCleoVersion } = await import("@cleocode/core");
40970
+ const { getProjectRoot: getProjectRoot58, getCleoHome: getCleoHome6, getCleoVersion } = await import("@cleocode/core");
40807
40971
  const { BundleError, cleanupStaging, unpackBundle } = await import("@cleocode/core/store/backup-unpack.js");
40808
40972
  const { regenerateConfigJson, regenerateProjectContextJson, regenerateProjectInfoJson } = await import("@cleocode/core/store/regenerators.js");
40809
40973
  const { regenerateAndCompare } = await import("@cleocode/core/store/restore-json-merge.js");
40810
40974
  const { buildConflictReport, writeConflictReport } = await import("@cleocode/core/store/restore-conflict-report.js");
40811
- const projectRoot = getProjectRoot57();
40975
+ const projectRoot = getProjectRoot58();
40812
40976
  if (args.force !== true) {
40813
40977
  const existing = checkForExistingData(projectRoot, getCleoHome6());
40814
40978
  if (existing.length > 0) {
@@ -43107,6 +43271,108 @@ var init_claim = __esm({
43107
43271
  }
43108
43272
  });
43109
43273
 
43274
+ // packages/cleo/src/cli/commands/classify.ts
43275
+ var classify_exports = {};
43276
+ __export(classify_exports, {
43277
+ classifyCommand: () => classifyCommand
43278
+ });
43279
+ import { classifyReadiness, classifyTask, getProjectRoot as getProjectRoot31 } from "@cleocode/core";
43280
+ var classifyCommand;
43281
+ var init_classify = __esm({
43282
+ "packages/cleo/src/cli/commands/classify.ts"() {
43283
+ "use strict";
43284
+ init_define_cli_command();
43285
+ init_renderers();
43286
+ classifyCommand = defineCommand({
43287
+ meta: {
43288
+ name: "classify",
43289
+ description: "Classify a task: readiness verdict (proceed|grill) + persona routing (agent, confidence)"
43290
+ },
43291
+ args: {
43292
+ taskId: {
43293
+ type: "positional",
43294
+ description: "Task ID to classify (e.g. T1234)",
43295
+ required: true
43296
+ }
43297
+ },
43298
+ async run({ args }) {
43299
+ const taskId = args.taskId;
43300
+ const projectRoot = getProjectRoot31();
43301
+ const { getDb: getDb3 } = await import("@cleocode/core/store/sqlite.js");
43302
+ const { tasks } = await import("@cleocode/core/store/tasks-schema");
43303
+ const { eq: eq2 } = await import("drizzle-orm");
43304
+ const db = await getDb3(projectRoot);
43305
+ const row = await db.select().from(tasks).where(eq2(tasks.id, taskId)).get();
43306
+ if (!row) {
43307
+ cliOutput(
43308
+ {
43309
+ success: false,
43310
+ error: { code: "E_NOT_FOUND", message: `Task ${taskId} not found` }
43311
+ },
43312
+ { command: "classify", operation: "classify.show" }
43313
+ );
43314
+ return;
43315
+ }
43316
+ const labels = (() => {
43317
+ try {
43318
+ const parsed = JSON.parse(row.labelsJson ?? "[]");
43319
+ return Array.isArray(parsed) ? parsed : [];
43320
+ } catch {
43321
+ return [];
43322
+ }
43323
+ })();
43324
+ const acceptance = (() => {
43325
+ try {
43326
+ const parsed = JSON.parse(row.acceptanceJson ?? "[]");
43327
+ return Array.isArray(parsed) ? parsed : [];
43328
+ } catch {
43329
+ return [];
43330
+ }
43331
+ })();
43332
+ const task = {
43333
+ id: row.id,
43334
+ title: row.title,
43335
+ description: row.description ?? "",
43336
+ status: row.status,
43337
+ priority: row.priority ?? "medium",
43338
+ type: row.type ?? void 0,
43339
+ kind: row.kind ?? void 0,
43340
+ size: row.size ?? void 0,
43341
+ pipelineStage: row.pipelineStage ?? void 0,
43342
+ blockedBy: row.blockedBy ?? void 0,
43343
+ phase: row.phase ?? void 0,
43344
+ scope: row.scope ?? void 0,
43345
+ labels,
43346
+ acceptance,
43347
+ createdAt: row.createdAt
43348
+ };
43349
+ const readiness = classifyReadiness(task);
43350
+ const routing = classifyTask(task);
43351
+ cliOutput(
43352
+ {
43353
+ taskId: row.id,
43354
+ title: row.title,
43355
+ readiness: {
43356
+ verdict: readiness.verdict,
43357
+ reason: readiness.reason,
43358
+ triggers: readiness.triggers
43359
+ },
43360
+ routing: {
43361
+ agentId: routing.agentId,
43362
+ role: routing.role,
43363
+ confidence: routing.confidence,
43364
+ reason: routing.reason,
43365
+ usedFallback: routing.usedFallback,
43366
+ warning: routing.warning
43367
+ }
43368
+ },
43369
+ { command: "classify", operation: "classify.show" }
43370
+ );
43371
+ }
43372
+ });
43373
+ }
43374
+ });
43375
+
43110
43376
  // packages/cleo/src/cli/commands/code.ts
43111
43377
  var code_exports = {};
43112
43378
  __export(code_exports, {
@@ -43939,7 +44205,7 @@ var init_conduit3 = __esm({
43939
44205
  });
43940
44206
 
43941
44207
  // packages/cleo/src/cli/commands/config/drift-check.ts
43942
- import { getProjectRoot as getProjectRoot31 } from "@cleocode/core";
44208
+ import { getProjectRoot as getProjectRoot32 } from "@cleocode/core";
43943
44209
  import { checkDrift } from "@cleocode/core/config/registry";
43944
44210
  function parseDriftScope(raw) {
43945
44211
  const value = raw ?? "project";
@@ -43984,7 +44250,7 @@ var init_drift_check = __esm({
43984
44250
  }
43985
44251
  let driftResult;
43986
44252
  try {
43987
- const projectRoot = getProjectRoot31();
44253
+ const projectRoot = getProjectRoot32();
43988
44254
  driftResult = await checkDrift(scope, projectRoot);
43989
44255
  } catch (err) {
43990
44256
  const message = err instanceof Error ? err.message : String(err);
@@ -44012,7 +44278,7 @@ var init_drift_check = __esm({
44012
44278
  });
44013
44279
 
44014
44280
  // packages/cleo/src/cli/commands/config/get.ts
44015
- import { getProjectRoot as getProjectRoot32 } from "@cleocode/core";
44281
+ import { getProjectRoot as getProjectRoot33 } from "@cleocode/core";
44016
44282
  import { getConfigValue } from "@cleocode/core/config/registry";
44017
44283
  function parseResolveScope(raw) {
44018
44284
  const value = raw ?? "merged";
@@ -44070,7 +44336,7 @@ var init_get = __esm({
44070
44336
  }
44071
44337
  let value;
44072
44338
  try {
44073
- const projectRoot = getProjectRoot32();
44339
+ const projectRoot = getProjectRoot33();
44074
44340
  value = await getConfigValue(key, { scope, projectRoot });
44075
44341
  } catch (err) {
44076
44342
  const message = err instanceof Error ? err.message : String(err);
@@ -44099,7 +44365,7 @@ var init_get = __esm({
44099
44365
  });
44100
44366
 
44101
44367
  // packages/cleo/src/cli/commands/config/set.ts
44102
- import { getProjectRoot as getProjectRoot33, parseConfigValue, setConfigValue } from "@cleocode/core";
44368
+ import { getProjectRoot as getProjectRoot34, parseConfigValue, setConfigValue } from "@cleocode/core";
44103
44369
  import { validateConfig as validateConfig2 } from "@cleocode/core/config/registry";
44104
44370
  function parseWriteScope(raw) {
44105
44371
  const value = raw ?? "project";
@@ -44209,7 +44475,7 @@ var init_set = __esm({
44209
44475
  let written;
44210
44476
  let validate;
44211
44477
  try {
44212
- const projectRoot = getProjectRoot33();
44478
+ const projectRoot = getProjectRoot34();
44213
44479
  written = await setConfigValue(key, coerced, projectRoot, {
44214
44480
  global: scope === "global"
44215
44481
  });
@@ -44241,7 +44507,7 @@ var init_set = __esm({
44241
44507
  });
44242
44508
 
44243
44509
  // packages/cleo/src/cli/commands/config/show.ts
44244
- import { getProjectRoot as getProjectRoot34 } from "@cleocode/core";
44510
+ import { getProjectRoot as getProjectRoot35 } from "@cleocode/core";
44245
44511
  import {
44246
44512
  resolveCleoConfig
44247
44513
  } from "@cleocode/core/config/registry";
@@ -44287,7 +44553,7 @@ var init_show = __esm({
44287
44553
  return;
44288
44554
  }
44289
44555
  try {
44290
- const projectRoot = getProjectRoot34();
44556
+ const projectRoot = getProjectRoot35();
44291
44557
  const config = await resolveCleoConfig({ scope, projectRoot });
44292
44558
  const result = { scope, config };
44293
44559
  cliOutput(result, {
@@ -44307,7 +44573,7 @@ var init_show = __esm({
44307
44573
  });
44308
44574
 
44309
44575
  // packages/cleo/src/cli/commands/config/validate.ts
44310
- import { getProjectRoot as getProjectRoot35 } from "@cleocode/core";
44576
+ import { getProjectRoot as getProjectRoot36 } from "@cleocode/core";
44311
44577
  import { validateConfig as validateConfig3 } from "@cleocode/core/config/registry";
44312
44578
  function parseValidateScope(raw) {
44313
44579
  const value = raw ?? "project";
@@ -44352,7 +44618,7 @@ var init_validate2 = __esm({
44352
44618
  }
44353
44619
  let validate;
44354
44620
  try {
44355
- const projectRoot = getProjectRoot35();
44621
+ const projectRoot = getProjectRoot36();
44356
44622
  validate = await validateConfig3(scope, projectRoot);
44357
44623
  } catch (err) {
44358
44624
  const message = err instanceof Error ? err.message : String(err);
@@ -45169,22 +45435,40 @@ var init_daemon2 = __esm({
45169
45435
  description: "Register the CLEO daemon as a user-level system service (systemd / launchd)"
45170
45436
  },
45171
45437
  args: {
45438
+ saga: {
45439
+ type: "string",
45440
+ description: "Scope the daemon to tasks within a Saga (sets CLEO_SENTIENT_SAGA in service env)",
45441
+ required: false
45442
+ },
45443
+ epic: {
45444
+ type: "string",
45445
+ description: "Scope the daemon to tasks directly under an Epic (sets CLEO_SENTIENT_EPIC in service env)",
45446
+ required: false
45447
+ },
45172
45448
  json: {
45173
45449
  type: "boolean",
45174
45450
  description: "Output result as JSON"
45175
45451
  }
45176
45452
  },
45177
- async run({ args: _args }) {
45453
+ async run({ args }) {
45178
45454
  try {
45179
45455
  const scriptPath = resolveDaemonInstallerScript();
45180
45456
  const { installDaemonService } = await import(scriptPath);
45181
- await installDaemonService();
45457
+ const scopeSagaId = typeof args.saga === "string" && args.saga.length > 0 ? args.saga : void 0;
45458
+ const scopeEpicId = typeof args.epic === "string" && args.epic.length > 0 ? args.epic : void 0;
45459
+ await installDaemonService({ scopeSagaId, scopeEpicId });
45460
+ const scopeNote = scopeEpicId !== void 0 ? ` (scoped to epic ${scopeEpicId})` : scopeSagaId !== void 0 ? ` (scoped to saga ${scopeSagaId})` : "";
45182
45461
  cliOutput(
45183
- { platform: process.platform, message: "Daemon service installation complete." },
45462
+ {
45463
+ platform: process.platform,
45464
+ scopeSagaId,
45465
+ scopeEpicId,
45466
+ message: `Daemon service installation complete${scopeNote}.`
45467
+ },
45184
45468
  {
45185
45469
  command: "daemon",
45186
45470
  operation: "daemon.install",
45187
- message: "CLEO: Daemon service installation complete."
45471
+ message: `CLEO: Daemon service installation complete${scopeNote}.`
45188
45472
  }
45189
45473
  );
45190
45474
  } catch (err) {
@@ -46875,7 +47159,7 @@ import { dirname as dirname7, join as join19, normalize, resolve as resolve4 } f
46875
47159
  import { fileURLToPath as fileURLToPath4 } from "node:url";
46876
47160
  import {
46877
47161
  createAttachmentStore as createAttachmentStore4,
46878
- getProjectRoot as getProjectRoot36,
47162
+ getProjectRoot as getProjectRoot37,
46879
47163
  searchAllProjectDocs as searchAllProjectDocs2
46880
47164
  } from "@cleocode/core/internal";
46881
47165
  function getViewerAssetsDir() {
@@ -46935,7 +47219,7 @@ async function serveStatic(res, assetsDir, relPath) {
46935
47219
  }
46936
47220
  }
46937
47221
  function buildViewerHandler(opts = {}) {
46938
- const projectRoot = opts.projectRoot ?? getProjectRoot36();
47222
+ const projectRoot = opts.projectRoot ?? getProjectRoot37();
46939
47223
  const assetsDir = getViewerAssetsDir();
46940
47224
  const store = createAttachmentStore4();
46941
47225
  return async (req, res) => {
@@ -47108,24 +47392,143 @@ var init_server = __esm({
47108
47392
  }
47109
47393
  });
47110
47394
 
47111
- // packages/cleo/src/cli/commands/docs-viewer.ts
47112
- import { spawn } from "node:child_process";
47113
- import { open as fsOpen } from "node:fs/promises";
47395
+ // packages/cleo/src/cli/docs-viewer-subsystem.ts
47396
+ import { mkdir as mkdir2 } from "node:fs/promises";
47114
47397
  import { join as join20 } from "node:path";
47115
- import { fileURLToPath as fileURLToPath5 } from "node:url";
47116
- import { getCleoHome as getCleoHome3, getProjectRoot as getProjectRoot37 } from "@cleocode/core";
47117
- function getCleoBinPath() {
47118
- const thisFile = fileURLToPath5(import.meta.url);
47119
- return join20(thisFile, "..", "..", "index.js");
47398
+ import { getCleoHome as getCleoHome3 } from "@cleocode/core";
47399
+ import { defineSubsystem } from "@cleocode/runtime/daemon";
47400
+ function getViewerPaths() {
47401
+ const cleoHome = getCleoHome3();
47402
+ return {
47403
+ pidFile: viewerPidFilePath(),
47404
+ logFile: join20(cleoHome, "viewer.log"),
47405
+ logDir: cleoHome
47406
+ };
47407
+ }
47408
+ function isViewerProcessRunning(pid) {
47409
+ return isProcessAlive(pid);
47120
47410
  }
47121
- async function waitForExit(pid, timeoutMs, intervalMs = 100) {
47122
- const deadline = Date.now() + timeoutMs;
47123
- while (Date.now() < deadline) {
47124
- if (!isProcessAlive(pid)) return true;
47125
- await new Promise((r) => setTimeout(r, intervalMs));
47411
+ async function getViewerStatus() {
47412
+ const record = await readViewerPidFile();
47413
+ if (!record) return { running: false, pid: null, port: null, host: null, url: null };
47414
+ if (!isProcessAlive(record.pid)) {
47415
+ await removeViewerPidFile();
47416
+ return { running: false, pid: null, port: null, host: null, url: null };
47126
47417
  }
47127
- return !isProcessAlive(pid);
47418
+ return {
47419
+ running: true,
47420
+ pid: record.pid,
47421
+ port: record.port,
47422
+ host: record.host,
47423
+ url: `http://${record.host}:${record.port}`
47424
+ };
47128
47425
  }
47426
+ function createDocsViewerSubsystem(opts = {}) {
47427
+ const startPort = opts.startPort ?? VIEWER_DEFAULT_PORT;
47428
+ const endPort = opts.endPort ?? VIEWER_DEFAULT_END_PORT;
47429
+ const host = opts.host ?? VIEWER_DEFAULT_HOST;
47430
+ const autoIncrement = !(opts.noAutoPort ?? false);
47431
+ let live;
47432
+ return defineSubsystem({
47433
+ name: VIEWER_SUBSYSTEM_NAME,
47434
+ async start() {
47435
+ const { pidFile, logDir } = getViewerPaths();
47436
+ const existing = await getViewerStatus();
47437
+ if (existing.running && existing.pid !== null) {
47438
+ const ctx2 = {
47439
+ pid: existing.pid,
47440
+ pidFile,
47441
+ port: existing.port ?? startPort,
47442
+ host: existing.host ?? host
47443
+ };
47444
+ live = ctx2;
47445
+ return ctx2;
47446
+ }
47447
+ await mkdir2(logDir, { recursive: true });
47448
+ const handle = await startViewer({
47449
+ startPort,
47450
+ endPort,
47451
+ host,
47452
+ autoIncrement
47453
+ });
47454
+ await writeViewerPidFile({
47455
+ pid: process.pid,
47456
+ port: handle.port,
47457
+ host: handle.host,
47458
+ projectRoot: process.cwd(),
47459
+ startedAt: Date.now()
47460
+ });
47461
+ const ctx = {
47462
+ pid: process.pid,
47463
+ pidFile,
47464
+ port: handle.port,
47465
+ host: handle.host
47466
+ };
47467
+ live = ctx;
47468
+ return ctx;
47469
+ },
47470
+ healthProbe() {
47471
+ if (live === void 0) {
47472
+ const stopped = "stopped";
47473
+ return {
47474
+ child_id: VIEWER_SUBSYSTEM_NAME,
47475
+ pid: 0,
47476
+ state: stopped,
47477
+ restart_count: 0,
47478
+ detail: "not started"
47479
+ };
47480
+ }
47481
+ const alive = isViewerProcessRunning(live.pid);
47482
+ const state = alive ? "running" : "stopped";
47483
+ return {
47484
+ child_id: VIEWER_SUBSYSTEM_NAME,
47485
+ pid: alive ? live.pid : 0,
47486
+ state,
47487
+ restart_count: 0,
47488
+ detail: alive ? `url=http://${live.host}:${live.port} pid=${live.pid}` : `pid=${live.pid} exited`
47489
+ };
47490
+ },
47491
+ async shutdown(context) {
47492
+ if (!isViewerProcessRunning(context.pid)) {
47493
+ await removeViewerPidFile();
47494
+ live = void 0;
47495
+ return;
47496
+ }
47497
+ try {
47498
+ process.kill(context.pid, "SIGTERM");
47499
+ } catch {
47500
+ }
47501
+ for (let i = 0; i < SIGTERM_GRACE_ITERATIONS; i++) {
47502
+ if (!isViewerProcessRunning(context.pid)) break;
47503
+ await new Promise((resolve11) => setTimeout(resolve11, 500));
47504
+ }
47505
+ if (isViewerProcessRunning(context.pid)) {
47506
+ try {
47507
+ process.kill(context.pid, "SIGKILL");
47508
+ } catch {
47509
+ }
47510
+ }
47511
+ await removeViewerPidFile();
47512
+ live = void 0;
47513
+ }
47514
+ });
47515
+ }
47516
+ var VIEWER_DEFAULT_PORT, VIEWER_DEFAULT_END_PORT, VIEWER_DEFAULT_HOST, VIEWER_SUBSYSTEM_NAME, SIGTERM_GRACE_ITERATIONS;
47517
+ var init_docs_viewer_subsystem = __esm({
47518
+ "packages/cleo/src/cli/docs-viewer-subsystem.ts"() {
47519
+ "use strict";
47520
+ init_pidfile();
47521
+ init_server();
47522
+ VIEWER_DEFAULT_PORT = 7777;
47523
+ VIEWER_DEFAULT_END_PORT = 7800;
47524
+ VIEWER_DEFAULT_HOST = "127.0.0.1";
47525
+ VIEWER_SUBSYSTEM_NAME = "cleo-docs-viewer";
47526
+ SIGTERM_GRACE_ITERATIONS = 20;
47527
+ }
47528
+ });
47529
+
47530
+ // packages/cleo/src/cli/commands/docs-viewer.ts
47531
+ import { spawn } from "node:child_process";
47129
47532
  function openInBrowser(url) {
47130
47533
  try {
47131
47534
  let cmd;
@@ -47144,72 +47547,40 @@ function openInBrowser(url) {
47144
47547
  } catch {
47145
47548
  }
47146
47549
  }
47147
- async function spawnDetachedServer(opts) {
47148
- const logFile = join20(getCleoHome3(), "viewer.log");
47149
- const handle = await fsOpen(logFile, "a").catch(() => null);
47150
- const stdio = handle ? ["ignore", handle.fd, handle.fd] : ["ignore", "ignore", "ignore"];
47151
- const args = [
47152
- getCleoBinPath(),
47153
- "docs",
47154
- "serve",
47155
- "--port",
47156
- String(opts.startPort),
47157
- "--end-port",
47158
- String(opts.endPort),
47159
- "--host",
47160
- opts.host
47161
- ];
47162
- if (opts.noAutoPort) args.push("--no-auto-port");
47163
- const child = spawn(process.execPath, args, {
47164
- detached: true,
47165
- stdio,
47166
- env: { ...process.env, [DETACHED_CHILD_ENV]: "1" },
47167
- cwd: getProjectRoot37()
47168
- });
47169
- child.unref();
47170
- if (handle) await handle.close();
47171
- return child;
47172
- }
47173
- var DETACHED_CHILD_ENV, serveCommand, openCommand, stopCommand4, viewerStatusCommand, runViewerStatus, viewerCommand, docsViewerSubcommands;
47550
+ var serveCommand, openCommand, stopCommand4, viewerStatusCommand, runViewerStatus, viewerCommand, docsViewerSubcommands;
47174
47551
  var init_docs_viewer = __esm({
47175
47552
  "packages/cleo/src/cli/commands/docs-viewer.ts"() {
47176
47553
  "use strict";
47177
47554
  init_src2();
47178
47555
  init_dist();
47179
47556
  init_pidfile();
47180
- init_server();
47557
+ init_docs_viewer_subsystem();
47181
47558
  init_renderers();
47182
- DETACHED_CHILD_ENV = "CLEO_VIEWER_DETACHED_CHILD";
47183
47559
  serveCommand = defineCommand({
47184
47560
  meta: {
47185
47561
  name: "serve",
47186
- description: "[legacy] Run docs viewer \u2014 prefer `cleo docs viewer start`"
47562
+ description: "Run docs viewer \u2014 prefer `cleo docs viewer start`"
47187
47563
  },
47188
47564
  args: {
47189
47565
  port: {
47190
47566
  type: "string",
47191
47567
  description: "Starting port (default 7777)",
47192
- default: "7777"
47568
+ default: String(VIEWER_DEFAULT_PORT)
47193
47569
  },
47194
47570
  "end-port": {
47195
47571
  type: "string",
47196
47572
  description: "Last port to try when auto-incrementing (default 7800)",
47197
- default: "7800"
47573
+ default: String(VIEWER_DEFAULT_END_PORT)
47198
47574
  },
47199
47575
  host: {
47200
47576
  type: "string",
47201
47577
  description: "Bind host (default 127.0.0.1)",
47202
- default: "127.0.0.1"
47578
+ default: VIEWER_DEFAULT_HOST
47203
47579
  },
47204
47580
  "no-auto-port": {
47205
47581
  type: "boolean",
47206
47582
  description: "Disable auto-increment when start port is busy",
47207
47583
  default: false
47208
- },
47209
- detach: {
47210
- type: "boolean",
47211
- description: "Run in background; write pid to viewer.pid; exit immediately",
47212
- default: false
47213
47584
  }
47214
47585
  },
47215
47586
  async run({ args }) {
@@ -47217,8 +47588,6 @@ var init_docs_viewer = __esm({
47217
47588
  const endPort = Number.parseInt(String(args["end-port"]), 10);
47218
47589
  const host = String(args.host);
47219
47590
  const noAutoPort = Boolean(args["no-auto-port"]);
47220
- const detach = Boolean(args.detach);
47221
- const isDetachedChild = process.env[DETACHED_CHILD_ENV] === "1";
47222
47591
  if (!Number.isFinite(startPort) || startPort < 1 || startPort > 65535) {
47223
47592
  cliError(`invalid --port: ${args.port}`, 6 /* VALIDATION_ERROR */, { name: "E_VALIDATION" });
47224
47593
  return;
@@ -47229,79 +47598,25 @@ var init_docs_viewer = __esm({
47229
47598
  });
47230
47599
  return;
47231
47600
  }
47232
- if (detach && !isDetachedChild) {
47233
- const existing = await readViewerPidFile();
47234
- if (existing && isProcessAlive(existing.pid)) {
47235
- cliOutput(
47236
- {
47237
- running: true,
47238
- pid: existing.pid,
47239
- port: existing.port,
47240
- host: existing.host,
47241
- url: `http://${existing.host}:${existing.port}`,
47242
- pidFile: viewerPidFilePath()
47243
- },
47244
- {
47245
- command: "docs serve",
47246
- operation: "docs.serve",
47247
- message: `viewer already running on http://${existing.host}:${existing.port}`
47248
- }
47249
- );
47250
- return;
47251
- }
47252
- const child = await spawnDetachedServer({ startPort, endPort, host, noAutoPort });
47253
- if (!child.pid) {
47254
- cliError("failed to spawn detached viewer process", 1 /* GENERAL_ERROR */, {
47255
- name: "E_SPAWN_FAILED"
47256
- });
47257
- return;
47258
- }
47259
- let record2 = null;
47260
- const deadline = Date.now() + 1e4;
47261
- while (Date.now() < deadline) {
47262
- await new Promise((r) => setTimeout(r, 150));
47263
- record2 = await readViewerPidFile();
47264
- if (record2 && record2.pid === child.pid) break;
47265
- if (!isProcessAlive(child.pid)) {
47266
- cliError(
47267
- "detached viewer exited before binding (check ~/.local/share/cleo/viewer.log)",
47268
- 1 /* GENERAL_ERROR */,
47269
- { name: "E_VIEWER_EXITED" }
47270
- );
47271
- return;
47272
- }
47273
- }
47274
- if (!record2 || record2.pid !== child.pid) {
47275
- cliError("timed out waiting for detached viewer to bind", 1 /* GENERAL_ERROR */, {
47276
- name: "E_VIEWER_TIMEOUT"
47277
- });
47278
- return;
47279
- }
47601
+ try {
47602
+ const subsystem = createDocsViewerSubsystem({ startPort, endPort, host, noAutoPort });
47603
+ const ctx = await subsystem.start();
47604
+ const { pidFile } = getViewerPaths();
47280
47605
  cliOutput(
47281
47606
  {
47282
47607
  running: true,
47283
- pid: record2.pid,
47284
- port: record2.port,
47285
- host: record2.host,
47286
- url: `http://${record2.host}:${record2.port}`,
47287
- pidFile: viewerPidFilePath()
47608
+ pid: ctx.pid,
47609
+ port: ctx.port,
47610
+ host: ctx.host,
47611
+ url: `http://${ctx.host}:${ctx.port}`,
47612
+ pidFile
47288
47613
  },
47289
47614
  {
47290
47615
  command: "docs serve",
47291
47616
  operation: "docs.serve",
47292
- message: `viewer started on http://${record2.host}:${record2.port}`
47617
+ message: `viewer started on http://${ctx.host}:${ctx.port}`
47293
47618
  }
47294
47619
  );
47295
- return;
47296
- }
47297
- let handle;
47298
- try {
47299
- handle = await startViewer({
47300
- startPort,
47301
- endPort,
47302
- host,
47303
- autoIncrement: !noAutoPort
47304
- });
47305
47620
  } catch (err) {
47306
47621
  const e = err;
47307
47622
  if (e.code === "E_NO_PORT" || e.code === "EADDRINUSE") {
@@ -47312,49 +47627,12 @@ var init_docs_viewer = __esm({
47312
47627
  }
47313
47628
  throw err;
47314
47629
  }
47315
- const record = {
47316
- pid: process.pid,
47317
- port: handle.port,
47318
- host: handle.host,
47319
- projectRoot: getProjectRoot37(),
47320
- startedAt: Date.now()
47321
- };
47322
- await writeViewerPidFile(record);
47323
- const url = `http://${handle.host}:${handle.port}`;
47324
- if (isDetachedChild) {
47325
- } else {
47326
- humanInfo(`viewer listening on ${url} (Ctrl+C to stop)`);
47327
- cliOutput(
47328
- {
47329
- running: true,
47330
- pid: record.pid,
47331
- port: record.port,
47332
- host: record.host,
47333
- url,
47334
- pidFile: viewerPidFilePath()
47335
- },
47336
- {
47337
- command: "docs serve",
47338
- operation: "docs.serve",
47339
- message: `viewer running on ${url}`
47340
- }
47341
- );
47342
- }
47343
- const shutdown = async (signal) => {
47344
- handle.server.close();
47345
- await removeViewerPidFile();
47346
- process.exit(signal === "SIGINT" ? 130 : 143);
47347
- };
47348
- process.on("SIGINT", () => void shutdown("SIGINT"));
47349
- process.on("SIGTERM", () => void shutdown("SIGTERM"));
47350
- await new Promise((res) => handle.server.on("close", res));
47351
- await removeViewerPidFile();
47352
47630
  }
47353
47631
  });
47354
47632
  openCommand = defineCommand({
47355
47633
  meta: {
47356
47634
  name: "open",
47357
- description: "[legacy] Open docs viewer in browser \u2014 prefer `cleo docs viewer open`"
47635
+ description: "Open docs viewer in browser \u2014 prefer `cleo docs viewer open`"
47358
47636
  },
47359
47637
  args: {
47360
47638
  slug: {
@@ -47365,17 +47643,17 @@ var init_docs_viewer = __esm({
47365
47643
  port: {
47366
47644
  type: "string",
47367
47645
  description: "Starting port for the viewer (default 7777)",
47368
- default: "7777"
47646
+ default: String(VIEWER_DEFAULT_PORT)
47369
47647
  },
47370
47648
  "end-port": {
47371
47649
  type: "string",
47372
47650
  description: "Last port to try (default 7800)",
47373
- default: "7800"
47651
+ default: String(VIEWER_DEFAULT_END_PORT)
47374
47652
  },
47375
47653
  host: {
47376
47654
  type: "string",
47377
47655
  description: "Bind host (default 127.0.0.1)",
47378
- default: "127.0.0.1"
47656
+ default: VIEWER_DEFAULT_HOST
47379
47657
  },
47380
47658
  "no-launch": {
47381
47659
  type: "boolean",
@@ -47385,49 +47663,30 @@ var init_docs_viewer = __esm({
47385
47663
  },
47386
47664
  async run({ args }) {
47387
47665
  const slug = args.slug ? String(args.slug) : void 0;
47388
- let record = await readViewerPidFile();
47389
- if (record && !isProcessAlive(record.pid)) {
47390
- await removeViewerPidFile();
47391
- record = null;
47392
- }
47393
- if (!record) {
47666
+ let status = await getViewerStatus();
47667
+ if (!status.running) {
47394
47668
  const startPort = Number.parseInt(String(args.port), 10);
47395
47669
  const endPort = Number.parseInt(String(args["end-port"]), 10);
47396
47670
  const host = String(args.host);
47397
- const child = await spawnDetachedServer({
47398
- startPort,
47399
- endPort,
47400
- host,
47401
- noAutoPort: false
47402
- });
47403
- if (!child.pid) {
47404
- cliError("failed to spawn detached viewer process", 1 /* GENERAL_ERROR */, {
47405
- name: "E_SPAWN_FAILED"
47406
- });
47407
- return;
47408
- }
47409
- const deadline = Date.now() + 1e4;
47410
- while (Date.now() < deadline) {
47411
- await new Promise((r) => setTimeout(r, 150));
47412
- record = await readViewerPidFile();
47413
- if (record && record.pid === child.pid) break;
47414
- if (!isProcessAlive(child.pid)) {
47415
- cliError(
47416
- "detached viewer exited before binding (check ~/.local/share/cleo/viewer.log)",
47417
- 1 /* GENERAL_ERROR */,
47418
- { name: "E_VIEWER_EXITED" }
47419
- );
47420
- return;
47421
- }
47422
- }
47423
- if (!record) {
47424
- cliError("timed out waiting for viewer to bind", 1 /* GENERAL_ERROR */, {
47425
- name: "E_VIEWER_TIMEOUT"
47671
+ try {
47672
+ const subsystem = createDocsViewerSubsystem({ startPort, endPort, host });
47673
+ await subsystem.start();
47674
+ status = await getViewerStatus();
47675
+ } catch (err) {
47676
+ const e = err;
47677
+ cliError(e.message ?? "failed to start docs viewer", 1 /* GENERAL_ERROR */, {
47678
+ name: "E_VIEWER_START_FAILED"
47426
47679
  });
47427
47680
  return;
47428
47681
  }
47429
47682
  }
47430
- const url = slug ? `http://${record.host}:${record.port}/docs/${encodeURIComponent(slug)}` : `http://${record.host}:${record.port}/`;
47683
+ if (!status.running || status.pid === null || status.port === null || status.host === null) {
47684
+ cliError("timed out waiting for viewer to bind", 1 /* GENERAL_ERROR */, {
47685
+ name: "E_VIEWER_TIMEOUT"
47686
+ });
47687
+ return;
47688
+ }
47689
+ const url = slug ? `http://${status.host}:${status.port}/docs/${encodeURIComponent(slug)}` : `http://${status.host}:${status.port}/`;
47431
47690
  if (!args["no-launch"]) {
47432
47691
  openInBrowser(url);
47433
47692
  }
@@ -47435,9 +47694,9 @@ var init_docs_viewer = __esm({
47435
47694
  {
47436
47695
  opened: true,
47437
47696
  slug: slug ?? null,
47438
- pid: record.pid,
47439
- port: record.port,
47440
- host: record.host,
47697
+ pid: status.pid,
47698
+ port: status.port,
47699
+ host: status.host,
47441
47700
  url
47442
47701
  },
47443
47702
  {
@@ -47451,7 +47710,7 @@ var init_docs_viewer = __esm({
47451
47710
  stopCommand4 = defineCommand({
47452
47711
  meta: {
47453
47712
  name: "stop",
47454
- description: "[legacy] Stop the docs viewer \u2014 prefer `cleo docs viewer stop`"
47713
+ description: "Stop the docs viewer \u2014 prefer `cleo docs viewer stop`"
47455
47714
  },
47456
47715
  args: {
47457
47716
  timeout: {
@@ -47485,38 +47744,29 @@ var init_docs_viewer = __esm({
47485
47744
  );
47486
47745
  return;
47487
47746
  }
47488
- try {
47489
- process.kill(record.pid, "SIGTERM");
47490
- } catch (err) {
47491
- const e = err;
47492
- if (e.code !== "ESRCH") {
47493
- cliError(
47494
- `failed to signal viewer pid ${record.pid}: ${e.message ?? e.code}`,
47495
- 1 /* GENERAL_ERROR */,
47496
- { name: "E_SIGNAL_FAILED" }
47497
- );
47498
- return;
47499
- }
47500
- }
47747
+ const { pidFile } = getViewerPaths();
47748
+ const subsystem = createDocsViewerSubsystem({
47749
+ startPort: record.port,
47750
+ host: record.host
47751
+ });
47752
+ await subsystem.shutdown({
47753
+ pid: record.pid,
47754
+ pidFile,
47755
+ port: record.port,
47756
+ host: record.host
47757
+ });
47501
47758
  const timeoutSec = Math.max(1, Number.parseInt(String(args.timeout), 10) || 10);
47502
- const exited = await waitForExit(record.pid, timeoutSec * 1e3);
47503
- if (!exited) {
47504
- try {
47505
- process.kill(record.pid, "SIGKILL");
47506
- } catch {
47507
- }
47508
- }
47509
- await removeViewerPidFile();
47510
47759
  cliOutput(
47511
47760
  {
47512
47761
  stopped: true,
47513
47762
  pid: record.pid,
47514
- graceful: exited
47763
+ graceful: true,
47764
+ timeoutSec
47515
47765
  },
47516
47766
  {
47517
47767
  command: "docs stop",
47518
47768
  operation: "docs.stop",
47519
- message: exited ? `viewer (pid ${record.pid}) stopped gracefully` : `viewer (pid ${record.pid}) force-killed after ${timeoutSec}s`
47769
+ message: `viewer (pid ${record.pid}) stopped`
47520
47770
  }
47521
47771
  );
47522
47772
  }
@@ -47524,15 +47774,15 @@ var init_docs_viewer = __esm({
47524
47774
  viewerStatusCommand = defineCommand({
47525
47775
  meta: {
47526
47776
  name: "viewer-status",
47527
- description: "[legacy] Report viewer state \u2014 prefer `cleo docs viewer status`"
47777
+ description: "Report viewer state \u2014 prefer `cleo docs viewer status`"
47528
47778
  },
47529
47779
  async run() {
47530
47780
  await runViewerStatus();
47531
47781
  }
47532
47782
  });
47533
47783
  runViewerStatus = async () => {
47534
- const record = await readViewerPidFile();
47535
- if (!record) {
47784
+ const status = await getViewerStatus();
47785
+ if (!status.running || status.pid === null) {
47536
47786
  cliOutput(
47537
47787
  { running: false, pidFile: viewerPidFilePath() },
47538
47788
  {
@@ -47543,40 +47793,19 @@ var init_docs_viewer = __esm({
47543
47793
  );
47544
47794
  return;
47545
47795
  }
47546
- const alive = isProcessAlive(record.pid);
47547
- if (!alive) {
47548
- await removeViewerPidFile();
47549
- cliOutput(
47550
- {
47551
- running: false,
47552
- reason: "stale pidfile",
47553
- pid: record.pid,
47554
- pidFile: viewerPidFilePath()
47555
- },
47556
- {
47557
- command: "docs viewer-status",
47558
- operation: "docs.viewer-status",
47559
- message: `stale pidfile removed (pid ${record.pid} not alive)`
47560
- }
47561
- );
47562
- return;
47563
- }
47564
47796
  cliOutput(
47565
47797
  {
47566
47798
  running: true,
47567
- pid: record.pid,
47568
- port: record.port,
47569
- host: record.host,
47570
- projectRoot: record.projectRoot,
47571
- startedAt: record.startedAt,
47572
- uptimeMs: Date.now() - record.startedAt,
47573
- url: `http://${record.host}:${record.port}`,
47799
+ pid: status.pid,
47800
+ port: status.port,
47801
+ host: status.host,
47802
+ url: status.url,
47574
47803
  pidFile: viewerPidFilePath()
47575
47804
  },
47576
47805
  {
47577
47806
  command: "docs viewer-status",
47578
47807
  operation: "docs.viewer-status",
47579
- message: `viewer running (pid ${record.pid})`
47808
+ message: `viewer running (pid ${status.pid})`
47580
47809
  }
47581
47810
  );
47582
47811
  };
@@ -47613,7 +47842,7 @@ __export(docs_exports, {
47613
47842
  _llmOutputCommand: () => _llmOutputCommand,
47614
47843
  docsCommand: () => docsCommand
47615
47844
  });
47616
- import { appendFile, mkdir as mkdir2, readdir, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
47845
+ import { appendFile, mkdir as mkdir3, readdir, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
47617
47846
  import { dirname as dirname8, isAbsolute as isAbsolute2, join as join21, resolve as resolve5 } from "node:path";
47618
47847
  import { pushWarning as pushWarning3 } from "@cleocode/core";
47619
47848
  import {
@@ -47955,7 +48184,7 @@ var init_docs3 = __esm({
47955
48184
  })}
47956
48185
  `;
47957
48186
  try {
47958
- await mkdir2(join21(projectRoot, ".cleo", "audit"), { recursive: true });
48187
+ await mkdir3(join21(projectRoot, ".cleo", "audit"), { recursive: true });
47959
48188
  await appendFile(
47960
48189
  join21(projectRoot, ".cleo", "audit", "similar-bypass.jsonl"),
47961
48190
  auditLine,
@@ -48287,7 +48516,7 @@ var init_docs3 = __esm({
48287
48516
  let writtenPath;
48288
48517
  if (typeof args.out === "string" && args.out.length > 0) {
48289
48518
  const outPath = isAbsolute2(args.out) ? args.out : resolve5(projectRoot, args.out);
48290
- await mkdir2(dirname8(outPath), { recursive: true });
48519
+ await mkdir3(dirname8(outPath), { recursive: true });
48291
48520
  await writeFile2(outPath, result.content, "utf8");
48292
48521
  writtenPath = outPath;
48293
48522
  }
@@ -48359,7 +48588,7 @@ var init_docs3 = __esm({
48359
48588
  let writtenPath;
48360
48589
  if (typeof args.out === "string" && args.out.length > 0) {
48361
48590
  const outPath = isAbsolute2(args.out) ? args.out : resolve5(projectRoot, args.out);
48362
- await mkdir2(dirname8(outPath), { recursive: true });
48591
+ await mkdir3(dirname8(outPath), { recursive: true });
48363
48592
  await writeFile2(outPath, result.content, "utf8");
48364
48593
  writtenPath = outPath;
48365
48594
  }
@@ -48436,7 +48665,7 @@ var init_docs3 = __esm({
48436
48665
  let writtenPath;
48437
48666
  if (typeof args.out === "string" && args.out.length > 0) {
48438
48667
  const outPath = isAbsolute2(args.out) ? args.out : resolve5(projectRoot, args.out);
48439
- await mkdir2(dirname8(outPath), { recursive: true });
48668
+ await mkdir3(dirname8(outPath), { recursive: true });
48440
48669
  await writeFile2(outPath, result.content, "utf8");
48441
48670
  writtenPath = outPath;
48442
48671
  }
@@ -48662,7 +48891,7 @@ var init_docs3 = __esm({
48662
48891
  });
48663
48892
  if (typeof args.out === "string" && args.out.length > 0) {
48664
48893
  const outPath = isAbsolute2(args.out) ? args.out : resolve5(projectRoot, args.out);
48665
- await mkdir2(dirname8(outPath), { recursive: true });
48894
+ await mkdir3(dirname8(outPath), { recursive: true });
48666
48895
  await writeFile2(outPath, result.merged, "utf8");
48667
48896
  humanInfo(`Wrote merged content to ${outPath}`);
48668
48897
  }
@@ -49434,7 +49663,7 @@ var init_docs3 = __esm({
49434
49663
  })}
49435
49664
  `;
49436
49665
  try {
49437
- await mkdir2(join21(projectRoot, ".cleo", "audit"), { recursive: true });
49666
+ await mkdir3(join21(projectRoot, ".cleo", "audit"), { recursive: true });
49438
49667
  await appendFile(
49439
49668
  join21(projectRoot, ".cleo", "audit", "import-force-bypass.jsonl"),
49440
49669
  auditLine,
@@ -50899,9 +51128,18 @@ var init_doctor = __esm({
50899
51128
  type: "boolean",
50900
51129
  description: "Migrate <root>/.cleo/worktree-include (legacy) \u2192 <root>/.worktreeinclude (canonical, T9983). Combine with --dry-run to preview."
50901
51130
  },
51131
+ /**
51132
+ * T11489 — DHQ-037/019: detect and auto-heal a leaked `core.worktree` key
51133
+ * in the shared `.git/config`. Reports E_WT_CONFIG_LEAK when found; heals
51134
+ * automatically unless `--dry-run` is passed.
51135
+ */
51136
+ "check-worktree-config": {
51137
+ type: "boolean",
51138
+ description: "Detect and auto-heal a leaked core.worktree key in .git/config (T11489 / DHQ-037). Combine with --dry-run to report without healing."
51139
+ },
50902
51140
  "dry-run": {
50903
51141
  type: "boolean",
50904
- description: "With --quarantine-rogue-cleo-dirs or --scan-stray-nexus-dbs: print what would be done without acting"
51142
+ description: "With --quarantine-rogue-cleo-dirs, --scan-stray-nexus-dbs, or --check-worktree-config: print what would be done without acting"
50905
51143
  },
50906
51144
  /**
50907
51145
  * Show brain.db health dashboard (T1908 / BBTT-W2-4).
@@ -50951,6 +51189,61 @@ var init_doctor = __esm({
50951
51189
  const progress = createDoctorProgress(isHuman);
50952
51190
  progress.start();
50953
51191
  try {
51192
+ if (args["check-worktree-config"]) {
51193
+ const { execFileSync: execFile2 } = await import("node:child_process");
51194
+ const { join: pathJoin } = await import("node:path");
51195
+ const { existsSync: existsSync21 } = await import("node:fs");
51196
+ const { detectAndHealCoreWorktreeLeak } = await import("@cleocode/worktree");
51197
+ const projectRoot = getProjectRoot42();
51198
+ let gitRoot = projectRoot;
51199
+ try {
51200
+ gitRoot = execFile2("git", ["rev-parse", "--show-toplevel"], {
51201
+ cwd: projectRoot,
51202
+ encoding: "utf-8",
51203
+ stdio: ["pipe", "pipe", "pipe"]
51204
+ }).trim();
51205
+ } catch {
51206
+ }
51207
+ const isDryRun = args["dry-run"] === true;
51208
+ progress.step(0, "Checking for leaked core.worktree in .git/config");
51209
+ if (isDryRun) {
51210
+ const gitConfigPath = pathJoin(gitRoot, ".git", "config");
51211
+ let leakedValue;
51212
+ if (existsSync21(gitConfigPath)) {
51213
+ try {
51214
+ leakedValue = execFile2("git", ["config", "--file", gitConfigPath, "--get", "core.worktree"], {
51215
+ encoding: "utf-8",
51216
+ stdio: ["pipe", "pipe", "pipe"]
51217
+ }).trim() || void 0;
51218
+ } catch {
51219
+ }
51220
+ }
51221
+ const report = {
51222
+ gitRoot,
51223
+ gitConfigPath,
51224
+ leakDetected: !!leakedValue,
51225
+ leakedValue,
51226
+ healed: false,
51227
+ dryRun: true,
51228
+ message: leakedValue ? `DRY-RUN: core.worktree="${leakedValue}" found \u2014 would unset` : "No core.worktree leak detected"
51229
+ };
51230
+ progress.complete(report.message);
51231
+ cliOutput(report, { command: "doctor", operation: "doctor.check-worktree-config" });
51232
+ if (leakedValue) process.exitCode = 2;
51233
+ } else {
51234
+ const result = detectAndHealCoreWorktreeLeak(gitRoot);
51235
+ const report = {
51236
+ gitRoot,
51237
+ ...result,
51238
+ dryRun: false,
51239
+ message: result.leakDetected ? result.healed ? `E_WT_CONFIG_LEAK healed: removed core.worktree="${result.leakedValue}"` : `E_WT_CONFIG_LEAK detected but heal FAILED: ${result.healError}` : "No core.worktree leak detected"
51240
+ };
51241
+ progress.complete(report.message);
51242
+ cliOutput(report, { command: "doctor", operation: "doctor.check-worktree-config" });
51243
+ if (result.leakDetected && !result.healed) process.exitCode = 1;
51244
+ }
51245
+ return;
51246
+ }
50954
51247
  if (args.brain) {
50955
51248
  const { computeBrainHealthDashboard } = await import("@cleocode/core/memory/brain-health-dashboard.js");
50956
51249
  const projectRoot = getProjectRoot42();
@@ -52522,12 +52815,54 @@ var init_gc = __esm({
52522
52815
  }
52523
52816
  });
52524
52817
 
52818
+ // packages/cleo/src/cli/commands/go.ts
52819
+ var go_exports = {};
52820
+ __export(go_exports, {
52821
+ goCommand: () => goCommand
52822
+ });
52823
+ import { getProjectRoot as getProjectRoot43, go } from "@cleocode/core";
52824
+ var goCommand;
52825
+ var init_go = __esm({
52826
+ "packages/cleo/src/cli/commands/go.ts"() {
52827
+ "use strict";
52828
+ init_define_cli_command();
52829
+ init_renderers();
52830
+ goCommand = defineCommand({
52831
+ meta: {
52832
+ name: "go",
52833
+ description: "SG-AUTOPILOT: run one turn of briefing\u2192sagaNext\u2192ready\u2192stage-branch\u2192ivtr loop"
52834
+ },
52835
+ args: {
52836
+ saga: {
52837
+ type: "string",
52838
+ description: "Optional saga task ID to scope the autopilot run (default: auto-select)",
52839
+ required: false
52840
+ },
52841
+ headless: {
52842
+ type: "boolean",
52843
+ description: "Suppress interactive annotations; suitable for daemon / unattended use",
52844
+ required: false
52845
+ }
52846
+ },
52847
+ async run({ args }) {
52848
+ const projectRoot = getProjectRoot43();
52849
+ const result = await go.cleoGo({
52850
+ sagaId: typeof args.saga === "string" && args.saga.length > 0 ? args.saga : void 0,
52851
+ headless: args.headless === true,
52852
+ projectRoot
52853
+ });
52854
+ cliOutput(result.success ? result.data : result, { command: "go", operation: "go.run" });
52855
+ }
52856
+ });
52857
+ }
52858
+ });
52859
+
52525
52860
  // packages/cleo/src/cli/commands/goal.ts
52526
52861
  var goal_exports = {};
52527
52862
  __export(goal_exports, {
52528
52863
  goalCommand: () => goalCommand
52529
52864
  });
52530
- import { getProjectRoot as getProjectRoot43, goal } from "@cleocode/core";
52865
+ import { getProjectRoot as getProjectRoot44, goal } from "@cleocode/core";
52531
52866
  function resolveGoalKind(task) {
52532
52867
  if (typeof task === "string" && task.length > 0) {
52533
52868
  if (!isValidGoalTargetTaskId(task)) {
@@ -52541,7 +52876,7 @@ function resolveTurnBudget(turns) {
52541
52876
  const n = typeof turns === "string" ? Number.parseInt(turns, 10) : Number(turns);
52542
52877
  return Number.isInteger(n) && n > 0 ? n : DEFAULT_TURN_BUDGET;
52543
52878
  }
52544
- var DEFAULT_TURN_BUDGET, setCommand, statusCommand9, subgoalCommand, appendCommand, goalCommand;
52879
+ var DEFAULT_TURN_BUDGET, setCommand, statusCommand9, subgoalCommand, advanceCommand2, appendCommand, goalCommand;
52545
52880
  var init_goal2 = __esm({
52546
52881
  "packages/cleo/src/cli/commands/goal.ts"() {
52547
52882
  "use strict";
@@ -52574,7 +52909,7 @@ var init_goal2 = __esm({
52574
52909
  }
52575
52910
  const record = await goal.createGoal(
52576
52911
  { goalKind: kindResult.kind, intent, turnBudget: resolveTurnBudget(args.turns) },
52577
- getProjectRoot43()
52912
+ getProjectRoot44()
52578
52913
  );
52579
52914
  cliOutput(record, { command: "goal set", operation: "goal.set" });
52580
52915
  }
@@ -52585,7 +52920,7 @@ var init_goal2 = __esm({
52585
52920
  description: "Show the current agent's active goal (per-agent scoped)."
52586
52921
  },
52587
52922
  async run() {
52588
- const record = await goal.getActiveGoal(getProjectRoot43());
52923
+ const record = await goal.getActiveGoal(getProjectRoot44());
52589
52924
  cliOutput(record ?? { active: null }, { command: "goal status", operation: "goal.status" });
52590
52925
  }
52591
52926
  });
@@ -52607,7 +52942,7 @@ var init_goal2 = __esm({
52607
52942
  });
52608
52943
  return;
52609
52944
  }
52610
- const cwd = getProjectRoot43();
52945
+ const cwd = getProjectRoot44();
52611
52946
  const parent = await goal.getActiveGoal(cwd);
52612
52947
  if (!parent) {
52613
52948
  cliError(
@@ -52636,6 +52971,34 @@ var init_goal2 = __esm({
52636
52971
  cliOutput(record, { command: "goal subgoal", operation: "goal.subgoal" });
52637
52972
  }
52638
52973
  });
52974
+ advanceCommand2 = defineCommand({
52975
+ meta: {
52976
+ name: "advance",
52977
+ description: "Advance a goal one turn (load \u2192 judge \u2192 persist \u2192 continuation). Entry point for the Stop-hook loop."
52978
+ },
52979
+ args: {
52980
+ goalId: {
52981
+ type: "positional",
52982
+ description: "The goal id (idempotency key) to advance"
52983
+ }
52984
+ },
52985
+ async run({ args }) {
52986
+ const goalId = String(args.goalId ?? "").trim();
52987
+ if (goalId.length === 0) {
52988
+ cliError("goal advance requires a <goalId>", 6 /* VALIDATION_ERROR */, {
52989
+ name: "E_VALIDATION"
52990
+ });
52991
+ return;
52992
+ }
52993
+ const cwd = getProjectRoot44();
52994
+ const result = await goal.advanceGoalWithPersist(goalId, { cwd });
52995
+ if (!result) {
52996
+ cliError(`Goal '${goalId}' not found.`, 4 /* NOT_FOUND */, { name: "E_NOT_FOUND" });
52997
+ return;
52998
+ }
52999
+ cliOutput(result, { command: "goal advance", operation: "goal.advance" });
53000
+ }
53001
+ });
52639
53002
  appendCommand = defineCommand({
52640
53003
  meta: {
52641
53004
  name: "append",
@@ -52652,7 +53015,7 @@ var init_goal2 = __esm({
52652
53015
  });
52653
53016
  return;
52654
53017
  }
52655
- const cwd = getProjectRoot43();
53018
+ const cwd = getProjectRoot44();
52656
53019
  const active = await goal.getActiveGoal(cwd);
52657
53020
  if (!active) {
52658
53021
  cliError(
@@ -52671,11 +53034,12 @@ var init_goal2 = __esm({
52671
53034
  goalCommand = defineCommand({
52672
53035
  meta: {
52673
53036
  name: "goal",
52674
- description: "DB-persisted, per-agent, evidence-gate-aware goal loop (set/status/subgoal/append)."
53037
+ description: "DB-persisted, per-agent, evidence-gate-aware goal loop (set/status/advance/subgoal/append)."
52675
53038
  },
52676
53039
  subCommands: {
52677
53040
  set: setCommand,
52678
53041
  status: statusCommand9,
53042
+ advance: advanceCommand2,
52679
53043
  subgoal: subgoalCommand,
52680
53044
  append: appendCommand
52681
53045
  },
@@ -53640,7 +54004,7 @@ var hygiene_exports = {};
53640
54004
  __export(hygiene_exports, {
53641
54005
  hygieneCommand: () => hygieneCommand
53642
54006
  });
53643
- import { getProjectRoot as getProjectRoot44 } from "@cleocode/core";
54007
+ import { getProjectRoot as getProjectRoot45 } from "@cleocode/core";
53644
54008
  import { runSpawnReadinessHygieneCli } from "@cleocode/core/hygiene/validate-spawn-readiness.js";
53645
54009
  var hygieneCommand;
53646
54010
  var init_hygiene = __esm({
@@ -53669,7 +54033,7 @@ var init_hygiene = __esm({
53669
54033
  }
53670
54034
  },
53671
54035
  async run({ args }) {
53672
- const projectRoot = args["project-root"] || getProjectRoot44() || process.cwd();
54036
+ const projectRoot = args["project-root"] || getProjectRoot45() || process.cwd();
53673
54037
  const worktreePath = args["worktree-path"];
53674
54038
  await runSpawnReadinessHygieneCli(projectRoot, worktreePath);
53675
54039
  }
@@ -53842,7 +54206,7 @@ __export(init_exports, {
53842
54206
  });
53843
54207
  import { existsSync as existsSync15, readFileSync as readFileSync14 } from "node:fs";
53844
54208
  import { join as join27 } from "node:path";
53845
- import { fileURLToPath as fileURLToPath6 } from "node:url";
54209
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
53846
54210
  import {
53847
54211
  CleoError as CleoError5,
53848
54212
  getWorkflowTemplatesDir as getCoreWorkflowTemplatesDir,
@@ -53853,7 +54217,7 @@ import {
53853
54217
  import { getTemplatesByKind } from "@cleocode/core/templates/registry";
53854
54218
  function getGitignoreTemplate() {
53855
54219
  try {
53856
- const thisFile = fileURLToPath6(import.meta.url);
54220
+ const thisFile = fileURLToPath5(import.meta.url);
53857
54221
  const packageRoot = join27(thisFile, "..", "..", "..", "..");
53858
54222
  const localTemplatePath = join27(packageRoot, "templates", "cleo-gitignore");
53859
54223
  const monorepoTemplatePath = join27(packageRoot, "..", "..", "templates", "cleo-gitignore");
@@ -54831,7 +55195,7 @@ var llm_cost_exports = {};
54831
55195
  __export(llm_cost_exports, {
54832
55196
  costCommand: () => costCommand
54833
55197
  });
54834
- import { getProjectRoot as getProjectRoot45 } from "@cleocode/core/internal";
55198
+ import { getProjectRoot as getProjectRoot46 } from "@cleocode/core/internal";
54835
55199
  import { computeCost } from "@cleocode/core/llm/usage-pricing";
54836
55200
  function resolveSessionId(raw) {
54837
55201
  if (raw === "current") {
@@ -54904,7 +55268,7 @@ var init_llm_cost = __esm({
54904
55268
  process.exit(6);
54905
55269
  }
54906
55270
  const sessionId = resolveSessionId(rawSessionId);
54907
- const projectRoot = getProjectRoot45(process.cwd());
55271
+ const projectRoot = getProjectRoot46(process.cwd());
54908
55272
  let breakdown;
54909
55273
  try {
54910
55274
  breakdown = await loadSessionCostBreakdown(projectRoot, sessionId);
@@ -56451,7 +56815,7 @@ var memory_exports = {};
56451
56815
  __export(memory_exports, {
56452
56816
  memoryCommand: () => memoryCommand
56453
56817
  });
56454
- import { getProjectRoot as getProjectRoot46 } from "@cleocode/core";
56818
+ import { getProjectRoot as getProjectRoot47 } from "@cleocode/core";
56455
56819
  import {
56456
56820
  getBrainDb as getBrainDb2,
56457
56821
  getDreamStatus,
@@ -57384,7 +57748,7 @@ var init_memory3 = __esm({
57384
57748
  },
57385
57749
  args: {},
57386
57750
  async run() {
57387
- const root = getProjectRoot46();
57751
+ const root = getProjectRoot47();
57388
57752
  try {
57389
57753
  const result = await runConsolidation(root);
57390
57754
  cliOutput(result, { command: "memory-consolidate", operation: "memory.consolidate" });
@@ -57408,7 +57772,7 @@ var init_memory3 = __esm({
57408
57772
  }
57409
57773
  },
57410
57774
  async run({ args }) {
57411
- const root = getProjectRoot46();
57775
+ const root = getProjectRoot47();
57412
57776
  if (args.status) {
57413
57777
  try {
57414
57778
  const status = await getDreamStatus(root);
@@ -57445,7 +57809,7 @@ var init_memory3 = __esm({
57445
57809
  }
57446
57810
  },
57447
57811
  async run({ args }) {
57448
- const root = getProjectRoot46();
57812
+ const root = getProjectRoot47();
57449
57813
  try {
57450
57814
  const { runObserver, runReflector } = await import("@cleocode/core/memory");
57451
57815
  const observerResult = await runObserver(root, args.session, {
@@ -57485,7 +57849,7 @@ var init_memory3 = __esm({
57485
57849
  }
57486
57850
  },
57487
57851
  async run({ args }) {
57488
- const root = getProjectRoot46();
57852
+ const root = getProjectRoot47();
57489
57853
  try {
57490
57854
  await getBrainDb2(root);
57491
57855
  const { totalDuplicateRows, groups } = await scanDuplicateEntries();
@@ -57531,7 +57895,7 @@ var init_memory3 = __esm({
57531
57895
  async run({ args }) {
57532
57896
  const sourceDir = args.from;
57533
57897
  const isDryRun = !!args["dry-run"];
57534
- const projectRoot = getProjectRoot46();
57898
+ const projectRoot = getProjectRoot47();
57535
57899
  try {
57536
57900
  const result = await importMemoryFiles({
57537
57901
  sourceDir,
@@ -57682,7 +58046,7 @@ var init_memory3 = __esm({
57682
58046
  },
57683
58047
  args: {},
57684
58048
  async run() {
57685
- const root = getProjectRoot46();
58049
+ const root = getProjectRoot47();
57686
58050
  try {
57687
58051
  await getBrainDb2(root);
57688
58052
  const result = await getTierStats(root);
@@ -57725,7 +58089,7 @@ var init_memory3 = __esm({
57725
58089
  }
57726
58090
  },
57727
58091
  async run({ args }) {
57728
- const root = getProjectRoot46();
58092
+ const root = getProjectRoot47();
57729
58093
  const targetTier = args.to;
57730
58094
  const reason = args.reason;
57731
58095
  const validTiers = ["medium", "long"];
@@ -57791,7 +58155,7 @@ var init_memory3 = __esm({
57791
58155
  }
57792
58156
  },
57793
58157
  async run({ args }) {
57794
- const root = getProjectRoot46();
58158
+ const root = getProjectRoot47();
57795
58159
  const targetTier = args.to;
57796
58160
  const reason = args.reason;
57797
58161
  const validTiers = ["short", "medium"];
@@ -58254,7 +58618,7 @@ var migrate_claude_mem_exports = {};
58254
58618
  __export(migrate_claude_mem_exports, {
58255
58619
  migrateClaudeMemCommand: () => migrateClaudeMemCommand
58256
58620
  });
58257
- import { getProjectRoot as getProjectRoot47, migrateClaudeMem } from "@cleocode/core/internal";
58621
+ import { getProjectRoot as getProjectRoot48, migrateClaudeMem } from "@cleocode/core/internal";
58258
58622
  import { ingestLooseAgentOutputs, ingestRcasdDirectories } from "@cleocode/core/memory";
58259
58623
  import { getDb as getDb2 } from "@cleocode/core/store/sqlite";
58260
58624
  var storageCommand, claudeMemCommand, manifestIngestCommand, migrateClaudeMemCommand;
@@ -58317,7 +58681,7 @@ var init_migrate_claude_mem = __esm({
58317
58681
  }
58318
58682
  },
58319
58683
  async run({ args }) {
58320
- const root = getProjectRoot47();
58684
+ const root = getProjectRoot48();
58321
58685
  try {
58322
58686
  const result = await migrateClaudeMem(root, {
58323
58687
  sourcePath: args.source,
@@ -58366,7 +58730,7 @@ var init_migrate_claude_mem = __esm({
58366
58730
  }
58367
58731
  },
58368
58732
  async run({ args }) {
58369
- const projectRoot = getProjectRoot47();
58733
+ const projectRoot = getProjectRoot48();
58370
58734
  try {
58371
58735
  const db = await getDb2(projectRoot);
58372
58736
  const rcasdFlag = Boolean(args.rcasd);
@@ -58481,10 +58845,10 @@ var nexus_exports = {};
58481
58845
  __export(nexus_exports, {
58482
58846
  nexusCommand: () => nexusCommand
58483
58847
  });
58484
- import { appendFile as appendFile2, mkdir as mkdir3 } from "node:fs/promises";
58848
+ import { appendFile as appendFile2, mkdir as mkdir4 } from "node:fs/promises";
58485
58849
  import { homedir as homedir5 } from "node:os";
58486
58850
  import path4 from "node:path";
58487
- import { getProjectRoot as getProjectRoot48 } from "@cleocode/core";
58851
+ import { getProjectRoot as getProjectRoot49 } from "@cleocode/core";
58488
58852
  import { getSymbolImpact } from "@cleocode/core/nexus";
58489
58853
  import { runNexusAnalysis } from "@cleocode/core/nexus/analyze-orchestrator.js";
58490
58854
  import { exportNexusGraph } from "@cleocode/core/nexus/export.js";
@@ -58493,7 +58857,7 @@ async function appendDeprecationTelemetry(op, replacement) {
58493
58857
  try {
58494
58858
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
58495
58859
  const dir = path4.join(homedir5(), ".local", "state", "cleo", "nexus-deprecation");
58496
- await mkdir3(dir, { recursive: true });
58860
+ await mkdir4(dir, { recursive: true });
58497
58861
  const record = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), op, replacement }) + "\n";
58498
58862
  await appendFile2(path4.join(dir, `${dateStr}.jsonl`), record, "utf8");
58499
58863
  } catch {
@@ -58599,7 +58963,7 @@ var init_nexus3 = __esm({
58599
58963
  async run({ args }) {
58600
58964
  applyJsonFlag2(args.json);
58601
58965
  const projectIdOverride = args["project-id"];
58602
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
58966
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
58603
58967
  const startTime = Date.now();
58604
58968
  try {
58605
58969
  const [{ getNexusDb, nexusSchema }, { getIndexStats }] = await Promise.all([
@@ -59114,7 +59478,7 @@ var init_nexus3 = __esm({
59114
59478
  applyJsonFlag2(args.json);
59115
59479
  const startTime = Date.now();
59116
59480
  const projectIdOverride = args["project-id"];
59117
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
59481
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
59118
59482
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
59119
59483
  const response = await dispatchRaw("query", "nexus", "clusters", { projectId, repoPath });
59120
59484
  const durationMs = Date.now() - startTime;
@@ -59158,7 +59522,7 @@ var init_nexus3 = __esm({
59158
59522
  applyJsonFlag2(args.json);
59159
59523
  const startTime = Date.now();
59160
59524
  const projectIdOverride = args["project-id"];
59161
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
59525
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
59162
59526
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
59163
59527
  const response = await dispatchRaw("query", "nexus", "flows", { projectId, repoPath });
59164
59528
  const durationMs = Date.now() - startTime;
@@ -59201,7 +59565,7 @@ var init_nexus3 = __esm({
59201
59565
  void appendDeprecationTelemetry("nexus.context", "cleo graph context");
59202
59566
  const startTime = Date.now();
59203
59567
  const projectIdOverride = args["project-id"];
59204
- const repoPath = getProjectRoot48();
59568
+ const repoPath = getProjectRoot49();
59205
59569
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
59206
59570
  const limit = parseInt(args.limit, 10);
59207
59571
  const symbolName = args.symbol;
@@ -59264,7 +59628,7 @@ var init_nexus3 = __esm({
59264
59628
  const startTime = Date.now();
59265
59629
  const whyFlag = !!args.why;
59266
59630
  const projectIdOverride = args["project-id"];
59267
- const repoPath = getProjectRoot48();
59631
+ const repoPath = getProjectRoot49();
59268
59632
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
59269
59633
  const maxDepth = Math.min(parseInt(args.depth, 10), 5);
59270
59634
  const symbolName = args.symbol;
@@ -59336,7 +59700,7 @@ var init_nexus3 = __esm({
59336
59700
  const projectIdOverride = args["project-id"];
59337
59701
  const isIncremental = !!args.incremental;
59338
59702
  const ctx = getFormatContext();
59339
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
59703
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
59340
59704
  humanInfo(`[nexus] Analyzing: ${repoPath}${isIncremental ? " (incremental)" : ""}`);
59341
59705
  if (!isIncremental) humanInfo("[nexus] Clearing existing index for project...");
59342
59706
  try {
@@ -59439,7 +59803,7 @@ var init_nexus3 = __esm({
59439
59803
  async run({ args }) {
59440
59804
  applyJsonFlag2(args.json);
59441
59805
  const startTime = Date.now();
59442
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
59806
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
59443
59807
  const name = args.name;
59444
59808
  const response = await dispatchRaw("mutate", "nexus", "projects.register", {
59445
59809
  path: repoPath,
@@ -59797,7 +60161,7 @@ var init_nexus3 = __esm({
59797
60161
  applyJsonFlag2(args.json);
59798
60162
  const startTime = Date.now();
59799
60163
  const projectIdOverride = args["project-id"];
59800
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
60164
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
59801
60165
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
59802
60166
  const response = await dispatchRaw("mutate", "nexus", "refresh-bridge", {
59803
60167
  repoPath,
@@ -59903,7 +60267,7 @@ var init_nexus3 = __esm({
59903
60267
  async run({ args }) {
59904
60268
  applyJsonFlag2(args.json);
59905
60269
  const startTime = Date.now();
59906
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
60270
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
59907
60271
  const projectIdOverride = args["project-id"];
59908
60272
  const beforeRef = args.before ?? "HEAD~1";
59909
60273
  const afterRef = args.after ?? "HEAD";
@@ -60021,7 +60385,7 @@ var init_nexus3 = __esm({
60021
60385
  applyJsonFlag2(args.json);
60022
60386
  const startTime = Date.now();
60023
60387
  const projectIdOverride = args["project-id"];
60024
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
60388
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
60025
60389
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
60026
60390
  const response = await dispatchRaw("query", "nexus", "route-map", { projectId });
60027
60391
  const durationMs = Date.now() - startTime;
@@ -60077,7 +60441,7 @@ var init_nexus3 = __esm({
60077
60441
  const startTime = Date.now();
60078
60442
  const routeSymbol = args.routeSymbol;
60079
60443
  const projectIdOverride = args["project-id"];
60080
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
60444
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
60081
60445
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
60082
60446
  const response = await dispatchRaw("query", "nexus", "shape-check", { routeSymbol, projectId });
60083
60447
  const durationMs = Date.now() - startTime;
@@ -60467,7 +60831,7 @@ var init_nexus3 = __esm({
60467
60831
  async run({ args }) {
60468
60832
  applyJsonFlag2(args.json);
60469
60833
  const startTime = Date.now();
60470
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
60834
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
60471
60835
  const projectIdOverride = args["project-id"];
60472
60836
  const projectId = projectIdOverride ?? Buffer.from(repoPath).toString("base64url").slice(0, 32);
60473
60837
  const response = await dispatchRaw("mutate", "nexus", "contracts-sync", {
@@ -60571,7 +60935,7 @@ var init_nexus3 = __esm({
60571
60935
  async run({ args }) {
60572
60936
  applyJsonFlag2(args.json);
60573
60937
  const startTime = Date.now();
60574
- const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot48();
60938
+ const repoPath = args.path ? path4.resolve(args.path) : getProjectRoot49();
60575
60939
  const projectId = Buffer.from(repoPath).toString("base64url").slice(0, 32);
60576
60940
  const response = await dispatchRaw("mutate", "nexus", "contracts-link-tasks", {
60577
60941
  projectId,
@@ -60652,7 +61016,7 @@ var init_nexus3 = __esm({
60652
61016
  const isIncremental = !!args.incremental;
60653
61017
  try {
60654
61018
  const result = await runNexusWiki({
60655
- projectRoot: getProjectRoot48(),
61019
+ projectRoot: getProjectRoot49(),
60656
61020
  outputDir,
60657
61021
  communityFilter,
60658
61022
  incremental: isIncremental
@@ -61002,7 +61366,7 @@ __export(orchestrate_exports, {
61002
61366
  });
61003
61367
  import { execFileSync as execFileSync3 } from "node:child_process";
61004
61368
  import { orchestration } from "@cleocode/core";
61005
- import { BUILD_CONFIG as BUILD_CONFIG2, getProjectRoot as getProjectRoot49 } from "@cleocode/core/internal";
61369
+ import { BUILD_CONFIG as BUILD_CONFIG2, getProjectRoot as getProjectRoot50 } from "@cleocode/core/internal";
61006
61370
  function formatRollupTable(rollup) {
61007
61371
  const waves = "waves" in rollup ? rollup.waves : [rollup];
61008
61372
  const lines = [];
@@ -61037,7 +61401,7 @@ function formatRollupTable(rollup) {
61037
61401
  }
61038
61402
  return lines.join("\n");
61039
61403
  }
61040
- var rollupCommand, startCommand5, statusCommand12, dashboardCommand, analyzeCommand4, readyCommand, reportCommand, nextCommand2, wavesCommand2, planCommand, spawnCommand2, validateCommand6, contextCommand5, ivtrCommand, parallelCommand, tesseraListCommand, tesseraInstantiateCommand, tesseraCommand, unblockCommand, bootstrapCommand, classifyCommand, fanoutStatusCommand, handoffCommand, spawnExecuteCommand, fanoutCommand, pruneCommand, worktreeCompleteCommand, conduitStatusCommand, conduitPeekCommand, conduitStartCommand, conduitStopCommand, approveCommand, rejectCommand, pendingCommand, conduitSendCommand, orchestrateCommand;
61404
+ var rollupCommand, startCommand5, statusCommand12, dashboardCommand, analyzeCommand4, readyCommand, reportCommand, nextCommand2, wavesCommand2, planCommand, spawnCommand2, validateCommand6, contextCommand5, ivtrCommand, parallelCommand, tesseraListCommand, tesseraInstantiateCommand, tesseraCommand, unblockCommand, bootstrapCommand, classifyCommand2, fanoutStatusCommand, handoffCommand, spawnExecuteCommand, fanoutCommand, pruneCommand, worktreeCompleteCommand, conduitStatusCommand, conduitPeekCommand, conduitStartCommand, conduitStopCommand, approveCommand, rejectCommand, pendingCommand, conduitSendCommand, orchestrateCommand;
61041
61405
  var init_orchestrate3 = __esm({
61042
61406
  "packages/cleo/src/cli/commands/orchestrate.ts"() {
61043
61407
  "use strict";
@@ -61145,7 +61509,7 @@ var init_orchestrate3 = __esm({
61145
61509
  },
61146
61510
  async run({ args }) {
61147
61511
  const rateWindowHours = args.window !== void 0 ? Number.parseFloat(String(args.window)) : void 0;
61148
- const metrics = await orchestration.collectOrchestrateDashboard(getProjectRoot49(), {
61512
+ const metrics = await orchestration.collectOrchestrateDashboard(getProjectRoot50(), {
61149
61513
  ...rateWindowHours !== void 0 && Number.isFinite(rateWindowHours) && rateWindowHours > 0 ? { rateWindowHours } : {}
61150
61514
  });
61151
61515
  cliOutput(metrics, {
@@ -61626,7 +61990,7 @@ var init_orchestrate3 = __esm({
61626
61990
  );
61627
61991
  }
61628
61992
  });
61629
- classifyCommand = defineCommand({
61993
+ classifyCommand2 = defineCommand({
61630
61994
  meta: {
61631
61995
  name: "classify",
61632
61996
  description: "Classify a request using CANT prompt-based team routing"
@@ -61965,7 +62329,7 @@ var init_orchestrate3 = __esm({
61965
62329
  tessera: tesseraCommand,
61966
62330
  unblock: unblockCommand,
61967
62331
  bootstrap: bootstrapCommand,
61968
- classify: classifyCommand,
62332
+ classify: classifyCommand2,
61969
62333
  "fanout-status": fanoutStatusCommand,
61970
62334
  handoff: handoffCommand,
61971
62335
  "spawn-execute": spawnExecuteCommand,
@@ -62168,7 +62532,7 @@ var phase_exports = {};
62168
62532
  __export(phase_exports, {
62169
62533
  phaseCommand: () => phaseCommand
62170
62534
  });
62171
- var showCommand10, listCommand15, setCommand2, startCommand6, completeCommand3, advanceCommand2, renameCommand, deleteCommand2, phaseCommand;
62535
+ var showCommand10, listCommand15, setCommand2, startCommand6, completeCommand3, advanceCommand3, renameCommand, deleteCommand2, phaseCommand;
62172
62536
  var init_phase = __esm({
62173
62537
  "packages/cleo/src/cli/commands/phase.ts"() {
62174
62538
  "use strict";
@@ -62268,7 +62632,7 @@ var init_phase = __esm({
62268
62632
  );
62269
62633
  }
62270
62634
  });
62271
- advanceCommand2 = defineCommand({
62635
+ advanceCommand3 = defineCommand({
62272
62636
  meta: { name: "advance", description: "Complete current phase and start next" },
62273
62637
  args: {
62274
62638
  force: {
@@ -62350,7 +62714,7 @@ var init_phase = __esm({
62350
62714
  set: setCommand2,
62351
62715
  start: startCommand6,
62352
62716
  complete: completeCommand3,
62353
- advance: advanceCommand2,
62717
+ advance: advanceCommand3,
62354
62718
  rename: renameCommand,
62355
62719
  delete: deleteCommand2
62356
62720
  },
@@ -63350,7 +63714,7 @@ var refresh_memory_exports = {};
63350
63714
  __export(refresh_memory_exports, {
63351
63715
  refreshMemoryCommand: () => refreshMemoryCommand
63352
63716
  });
63353
- import { getProjectRoot as getProjectRoot50 } from "@cleocode/core";
63717
+ import { getProjectRoot as getProjectRoot51 } from "@cleocode/core";
63354
63718
  var refreshMemoryCommand;
63355
63719
  var init_refresh_memory = __esm({
63356
63720
  "packages/cleo/src/cli/commands/refresh-memory.ts"() {
@@ -63363,7 +63727,7 @@ var init_refresh_memory = __esm({
63363
63727
  description: "Regenerate .cleo/memory-bridge.md from brain.db"
63364
63728
  },
63365
63729
  async run() {
63366
- const projectDir = getProjectRoot50();
63730
+ const projectDir = getProjectRoot51();
63367
63731
  const { writeMemoryBridge } = await import("@cleocode/core/internal");
63368
63732
  const result = await writeMemoryBridge(projectDir);
63369
63733
  if (result.written) {
@@ -64890,7 +65254,7 @@ import fs3 from "node:fs";
64890
65254
  import path5 from "node:path";
64891
65255
  import {
64892
65256
  CleoError as CleoError8,
64893
- getProjectRoot as getProjectRoot51,
65257
+ getProjectRoot as getProjectRoot52,
64894
65258
  getTaskAccessor as getTaskAccessor3,
64895
65259
  parseConflictReport,
64896
65260
  setAtPath
@@ -64911,7 +65275,7 @@ var init_restore = __esm({
64911
65275
  description: "Apply manually-resolved conflicts from .cleo/restore-conflicts.md"
64912
65276
  },
64913
65277
  async run() {
64914
- const projectRoot = getProjectRoot51();
65278
+ const projectRoot = getProjectRoot52();
64915
65279
  const reportPath = path5.join(projectRoot, CLEO_DIR_NAME3, RESTORE_CONFLICTS_MD);
64916
65280
  if (!fs3.existsSync(reportPath)) {
64917
65281
  humanLine("No pending restore conflicts. Nothing to finalize.");
@@ -65609,7 +65973,7 @@ __export(saga_exports, {
65609
65973
  sagaCommand: () => sagaCommand
65610
65974
  });
65611
65975
  import { parseAcceptanceCriteria } from "@cleocode/core";
65612
- var createCommand3, addCommand12, detachCommand2, listCommand22, membersCommand, repairCommand, reconcileCommand5, rollupCommand2, sagaCommand;
65976
+ var createCommand3, addCommand12, detachCommand2, listCommand22, membersCommand, repairCommand, reconcileCommand5, rollupCommand2, nextCommand3, sagaCommand;
65613
65977
  var init_saga = __esm({
65614
65978
  "packages/cleo/src/cli/commands/saga.ts"() {
65615
65979
  "use strict";
@@ -65820,6 +66184,26 @@ var init_saga = __esm({
65820
66184
  cliOutput(response.data ?? {}, { command: "saga", operation: "tasks.saga.rollup" });
65821
66185
  }
65822
66186
  });
66187
+ nextCommand3 = defineCommand({
66188
+ meta: {
66189
+ name: "next",
66190
+ description: "Return the next actionable Saga and its ready-frontier task IDs"
66191
+ },
66192
+ args: {
66193
+ sagaId: {
66194
+ type: "positional",
66195
+ description: "Optional saga task ID. Omit to auto-select from canonical order.",
66196
+ required: false
66197
+ }
66198
+ },
66199
+ async run({ args }) {
66200
+ const { sagas: sagas2, getProjectRoot: getProjectRoot58 } = await import("@cleocode/core");
66201
+ const projectRoot = getProjectRoot58();
66202
+ const sagaId = typeof args.sagaId === "string" && args.sagaId.length > 0 ? args.sagaId : void 0;
66203
+ const result = await sagas2.sagaNext(projectRoot, { sagaId });
66204
+ cliOutput(result.success ? result.data : result, { command: "saga", operation: "saga.next" });
66205
+ }
66206
+ });
65823
66207
  sagaCommand = defineCommand({
65824
66208
  meta: {
65825
66209
  name: "saga",
@@ -65833,7 +66217,8 @@ var init_saga = __esm({
65833
66217
  members: membersCommand,
65834
66218
  rollup: rollupCommand2,
65835
66219
  repair: repairCommand,
65836
- reconcile: reconcileCommand5
66220
+ reconcile: reconcileCommand5,
66221
+ next: nextCommand3
65837
66222
  },
65838
66223
  async run({ cmd, rawArgs }) {
65839
66224
  const firstArg = rawArgs?.find((a) => !a.startsWith("-"));
@@ -66075,7 +66460,7 @@ async function writeRuntimeVersionMetadata(mode, source, version) {
66075
66460
  `installed=${(/* @__PURE__ */ new Date()).toISOString()}`
66076
66461
  ];
66077
66462
  await import("node:fs/promises").then(
66078
- ({ writeFile: writeFile4, mkdir: mkdir5 }) => mkdir5(cleoHome, { recursive: true }).then(
66463
+ ({ writeFile: writeFile4, mkdir: mkdir6 }) => mkdir6(cleoHome, { recursive: true }).then(
66079
66464
  () => writeFile4(join29(cleoHome, "VERSION"), `${lines.join("\n")}
66080
66465
  `, "utf-8")
66081
66466
  )
@@ -67555,7 +67940,7 @@ var sequence_exports = {};
67555
67940
  __export(sequence_exports, {
67556
67941
  sequenceCommand: () => sequenceCommand
67557
67942
  });
67558
- import { getProjectRoot as getProjectRoot52 } from "@cleocode/core/internal";
67943
+ import { getProjectRoot as getProjectRoot53 } from "@cleocode/core/internal";
67559
67944
  var showCommand13, checkCommand7, repairCommand2, sequenceCommand;
67560
67945
  var init_sequence = __esm({
67561
67946
  "packages/cleo/src/cli/commands/sequence.ts"() {
@@ -67591,7 +67976,7 @@ var init_sequence = __esm({
67591
67976
  meta: { name: "repair", description: "Reset counter to max + 1 if behind" },
67592
67977
  async run() {
67593
67978
  const { repairSequence } = await import("@cleocode/core/internal");
67594
- const projectRoot = getProjectRoot52();
67979
+ const projectRoot = getProjectRoot53();
67595
67980
  const repair = await repairSequence(projectRoot);
67596
67981
  const result = {
67597
67982
  repaired: repair.repaired,
@@ -68046,8 +68431,8 @@ var init_session4 = __esm({
68046
68431
  "audit-scope": { type: "string", description: "Audit log scope (global|local)" }
68047
68432
  },
68048
68433
  async run({ args }) {
68049
- const { detectSessionDrift, getProjectRoot: getProjectRoot57 } = await import("@cleocode/core");
68050
- const projectRoot = await getProjectRoot57();
68434
+ const { detectSessionDrift, getProjectRoot: getProjectRoot58 } = await import("@cleocode/core");
68435
+ const projectRoot = await getProjectRoot58();
68051
68436
  const scope = args["audit-scope"] === "local" ? "local" : "global";
68052
68437
  const report = await detectSessionDrift({ projectRoot, auditScope: scope });
68053
68438
  cliOutput(report, { command: "session drift", operation: "session.drift" });
@@ -70744,13 +71129,13 @@ var init_telemetry2 = __esm({
70744
71129
  // packages/cleo/src/cli/commands/templates/lib.ts
70745
71130
  import { readFileSync as readFileSync15 } from "node:fs";
70746
71131
  import { isAbsolute as isAbsolute3, resolve as resolve8 } from "node:path";
70747
- import { getProjectRoot as getProjectRoot53 } from "@cleocode/core";
71132
+ import { getProjectRoot as getProjectRoot54 } from "@cleocode/core";
70748
71133
  import { resolveSourcePathAbsolute } from "@cleocode/core/templates/registry";
70749
71134
  function resolveProjectRoot6(raw) {
70750
71135
  if (typeof raw === "string" && raw.length > 0) {
70751
71136
  return isAbsolute3(raw) ? raw : resolve8(process.cwd(), raw);
70752
71137
  }
70753
- return getProjectRoot53();
71138
+ return getProjectRoot54();
70754
71139
  }
70755
71140
  function readTemplateSource(entry) {
70756
71141
  const sourceAbsolute = resolveSourcePathAbsolute(entry);
@@ -71534,7 +71919,7 @@ __export(token_exports, {
71534
71919
  tokenCommand: () => tokenCommand
71535
71920
  });
71536
71921
  import { readFileSync as readFileSync19 } from "node:fs";
71537
- import { getProjectRoot as getProjectRoot54, measureTokenExchange, recordTokenExchange as recordTokenExchange2 } from "@cleocode/core/internal";
71922
+ import { getProjectRoot as getProjectRoot55, measureTokenExchange, recordTokenExchange as recordTokenExchange2 } from "@cleocode/core/internal";
71538
71923
  function readPayload(args, textKey, fileKey) {
71539
71924
  const text = args[textKey];
71540
71925
  const file = args[fileKey];
@@ -71698,7 +72083,7 @@ var init_token = __esm({
71698
72083
  domain: args.domain,
71699
72084
  operation: args.operation
71700
72085
  };
71701
- const result = args.record ? await recordTokenExchange2(getProjectRoot54(), input2) : await measureTokenExchange(input2);
72086
+ const result = args.record ? await recordTokenExchange2(getProjectRoot55(), input2) : await measureTokenExchange(input2);
71702
72087
  cliOutput(result, {
71703
72088
  command: "token",
71704
72089
  operation: args.record ? "admin.token.record" : "token.estimate"
@@ -71734,7 +72119,7 @@ __export(transcript_exports, {
71734
72119
  });
71735
72120
  import { homedir as homedir6 } from "node:os";
71736
72121
  import { join as join34 } from "node:path";
71737
- import { getProjectRoot as getProjectRoot55 } from "@cleocode/core";
72122
+ import { getProjectRoot as getProjectRoot56 } from "@cleocode/core";
71738
72123
  import {
71739
72124
  parseDurationMs,
71740
72125
  pruneTranscripts,
@@ -71764,7 +72149,7 @@ var init_transcript = __esm({
71764
72149
  async run({ args }) {
71765
72150
  if (args.pending) {
71766
72151
  try {
71767
- const projectRoot = getProjectRoot55();
72152
+ const projectRoot = getProjectRoot56();
71768
72153
  const { scanPendingTranscripts } = await import("@cleocode/core/memory/transcript-scanner.js");
71769
72154
  const pending = await scanPendingTranscripts(projectRoot);
71770
72155
  cliOutput(
@@ -71861,7 +72246,7 @@ var init_transcript = __esm({
71861
72246
  async run({ args }) {
71862
72247
  const tier = args.tier ?? "warm";
71863
72248
  const dryRun = args["dry-run"] ?? false;
71864
- const projectRoot = getProjectRoot55();
72249
+ const projectRoot = getProjectRoot56();
71865
72250
  try {
71866
72251
  const { extractTranscript } = await import("@cleocode/core/memory/transcript-extractor.js");
71867
72252
  const { findSessionTranscriptPath, listAllTranscripts } = await import("@cleocode/core/memory/transcript-scanner.js");
@@ -71973,7 +72358,7 @@ var init_transcript = __esm({
71973
72358
  const dryRun = args["dry-run"] ?? false;
71974
72359
  const olderThanHours = args["older-than-hours"] ? Number.parseInt(args["older-than-hours"], 10) : 24;
71975
72360
  const limit = args.limit ? Number.parseInt(args.limit, 10) : void 0;
71976
- const projectRoot = getProjectRoot55();
72361
+ const projectRoot = getProjectRoot56();
71977
72362
  try {
71978
72363
  const { extractTranscript } = await import("@cleocode/core/memory/transcript-extractor.js");
71979
72364
  const { listAllTranscripts } = await import("@cleocode/core/memory/transcript-scanner.js");
@@ -72919,15 +73304,12 @@ var init_verify = __esm({
72919
73304
  }
72920
73305
  });
72921
73306
 
72922
- // packages/cleo/src/cli/commands/web.ts
72923
- var web_exports = {};
72924
- __export(web_exports, {
72925
- webCommand: () => webCommand
72926
- });
73307
+ // packages/cleo/src/cli/web-subsystem.ts
72927
73308
  import { execFileSync as execFileSync4, spawn as spawn3 } from "node:child_process";
72928
- import { mkdir as mkdir4, open, readFile as readFile8, rm, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
73309
+ import { mkdir as mkdir5, open, readFile as readFile8, rm, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
72929
73310
  import { join as join35 } from "node:path";
72930
- import { CleoError as CleoError12, formatError as formatError5, getCleoHome as getCleoHome5 } from "@cleocode/core";
73311
+ import { getCleoHome as getCleoHome5 } from "@cleocode/core";
73312
+ import { defineSubsystem as defineSubsystem2 } from "@cleocode/runtime/daemon";
72931
73313
  function getWebPaths() {
72932
73314
  const cleoHome = getCleoHome5();
72933
73315
  return {
@@ -72937,7 +73319,7 @@ function getWebPaths() {
72937
73319
  logFile: join35(cleoHome, "logs", "web-server.log")
72938
73320
  };
72939
73321
  }
72940
- function isProcessRunning(pid) {
73322
+ function isWebProcessRunning(pid) {
72941
73323
  try {
72942
73324
  process.kill(pid, 0);
72943
73325
  return true;
@@ -72945,20 +73327,20 @@ function isProcessRunning(pid) {
72945
73327
  return false;
72946
73328
  }
72947
73329
  }
72948
- async function getStatus() {
73330
+ async function getWebStatus() {
72949
73331
  const { pidFile, configFile } = getWebPaths();
72950
73332
  try {
72951
73333
  const pidStr = (await readFile8(pidFile, "utf-8")).trim();
72952
73334
  const pid = Number.parseInt(pidStr, 10);
72953
- if (Number.isNaN(pid) || !isProcessRunning(pid)) {
73335
+ if (Number.isNaN(pid) || !isWebProcessRunning(pid)) {
72954
73336
  return { running: false, pid: null, port: null, host: null, url: null };
72955
73337
  }
72956
- let port = DEFAULT_PORT;
72957
- let host = DEFAULT_HOST;
73338
+ let port = WEB_DEFAULT_PORT;
73339
+ let host = WEB_DEFAULT_HOST;
72958
73340
  try {
72959
73341
  const config = JSON.parse(await readFile8(configFile, "utf-8"));
72960
- port = config.port ?? DEFAULT_PORT;
72961
- host = config.host ?? DEFAULT_HOST;
73342
+ port = config.port ?? WEB_DEFAULT_PORT;
73343
+ host = config.host ?? WEB_DEFAULT_HOST;
72962
73344
  } catch {
72963
73345
  }
72964
73346
  return { running: true, pid, port, host, url: `http://${host}:${port}` };
@@ -72966,118 +73348,212 @@ async function getStatus() {
72966
73348
  return { running: false, pid: null, port: null, host: null, url: null };
72967
73349
  }
72968
73350
  }
72969
- async function startWebServer(port, host) {
72970
- const { pidFile, configFile, logFile, logDir } = getWebPaths();
72971
- const status = await getStatus();
72972
- if (status.running) {
72973
- throw new CleoError12(1 /* GENERAL_ERROR */, `Server already running (PID: ${status.pid})`);
72974
- }
72975
- const projectRoot = process.env["CLEO_ROOT"] ?? process.cwd();
72976
- const studioDir = process.env["CLEO_STUDIO_DIR"] ?? join35(projectRoot, "packages", "studio", "build");
72977
- await mkdir4(logDir, { recursive: true });
72978
- await writeFile3(
72979
- configFile,
72980
- JSON.stringify({
72981
- port,
72982
- host,
72983
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
72984
- })
72985
- );
72986
- const webIndexPath = join35(studioDir, "index.js");
72987
- try {
72988
- await stat2(webIndexPath);
72989
- } catch {
72990
- try {
72991
- execFileSync4("pnpm", ["--filter", "@cleocode/studio", "run", "build"], {
72992
- cwd: projectRoot,
72993
- stdio: "ignore"
72994
- });
72995
- } catch {
72996
- throw new CleoError12(
72997
- 1 /* GENERAL_ERROR */,
72998
- `Studio build failed. Run: pnpm --filter @cleocode/studio run build
72999
- Logs: ${logFile}`
73351
+ function createWebSubsystem(opts = {}) {
73352
+ const port = opts.port ?? WEB_DEFAULT_PORT;
73353
+ const host = opts.host ?? WEB_DEFAULT_HOST;
73354
+ let live;
73355
+ return defineSubsystem2({
73356
+ name: WEB_SUBSYSTEM_NAME,
73357
+ async start() {
73358
+ const { pidFile, configFile, logFile, logDir } = getWebPaths();
73359
+ const existing = await getWebStatus();
73360
+ if (existing.running && existing.pid !== null) {
73361
+ const ctx2 = {
73362
+ pid: existing.pid,
73363
+ pidFile,
73364
+ logFile,
73365
+ port: existing.port ?? port,
73366
+ host: existing.host ?? host
73367
+ };
73368
+ live = ctx2;
73369
+ return ctx2;
73370
+ }
73371
+ const projectRoot = process.env["CLEO_ROOT"] ?? process.cwd();
73372
+ const studioDir = process.env["CLEO_STUDIO_DIR"] ?? join35(projectRoot, "packages", "studio", "build");
73373
+ const webIndexPath = join35(studioDir, "index.js");
73374
+ await mkdir5(logDir, { recursive: true });
73375
+ await writeFile3(
73376
+ configFile,
73377
+ JSON.stringify({ port, host, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
73000
73378
  );
73001
- }
73002
- }
73003
- const logFileHandle = await open(logFile, "a");
73004
- const serverProcess = spawn3("node", [webIndexPath], {
73005
- cwd: studioDir,
73006
- env: {
73007
- ...process.env,
73008
- HOST: host,
73009
- PORT: String(port),
73010
- // Pass CLEO paths through to the studio server
73011
- CLEO_ROOT: projectRoot
73379
+ try {
73380
+ await stat2(webIndexPath);
73381
+ } catch {
73382
+ try {
73383
+ execFileSync4("pnpm", ["--filter", "@cleocode/studio", "run", "build"], {
73384
+ cwd: projectRoot,
73385
+ stdio: "ignore"
73386
+ });
73387
+ } catch {
73388
+ throw new Error(
73389
+ `Studio build failed. Run: pnpm --filter @cleocode/studio run build
73390
+ Logs: ${logFile}`
73391
+ );
73392
+ }
73393
+ }
73394
+ const logFileHandle = await open(logFile, "a");
73395
+ const serverProcess = spawn3("node", [webIndexPath], {
73396
+ cwd: studioDir,
73397
+ env: {
73398
+ ...process.env,
73399
+ HOST: host,
73400
+ PORT: String(port),
73401
+ CLEO_ROOT: projectRoot
73402
+ },
73403
+ detached: true,
73404
+ stdio: ["ignore", logFileHandle.fd, logFileHandle.fd]
73405
+ });
73406
+ serverProcess.unref();
73407
+ const pidFileTmp = `${pidFile}.tmp`;
73408
+ await writeFile3(pidFileTmp, String(serverProcess.pid));
73409
+ await rm(pidFile, { force: true });
73410
+ await writeFile3(pidFile, String(serverProcess.pid));
73411
+ await rm(pidFileTmp, { force: true });
73412
+ await logFileHandle.close();
73413
+ let started = false;
73414
+ for (let i = 0; i < STARTUP_POLL_ITERATIONS; i++) {
73415
+ try {
73416
+ const response = await fetch(`http://${host}:${port}/api/health`);
73417
+ if (response.ok) {
73418
+ started = true;
73419
+ break;
73420
+ }
73421
+ } catch {
73422
+ }
73423
+ await new Promise((resolve11) => setTimeout(resolve11, 500));
73424
+ }
73425
+ if (!started) {
73426
+ try {
73427
+ process.kill(serverProcess.pid, "SIGTERM");
73428
+ } catch {
73429
+ }
73430
+ await rm(pidFile, { force: true });
73431
+ throw new Error("Studio web server failed to start within 15 seconds");
73432
+ }
73433
+ const ctx = {
73434
+ pid: serverProcess.pid,
73435
+ pidFile,
73436
+ logFile,
73437
+ port,
73438
+ host
73439
+ };
73440
+ live = ctx;
73441
+ return ctx;
73012
73442
  },
73013
- detached: true,
73014
- stdio: ["ignore", logFileHandle.fd, logFileHandle.fd]
73015
- });
73016
- serverProcess.unref();
73017
- const pidFileTmp = `${pidFile}.tmp`;
73018
- await writeFile3(pidFileTmp, String(serverProcess.pid));
73019
- await rm(pidFile, { force: true });
73020
- await writeFile3(pidFile, String(serverProcess.pid));
73021
- await rm(pidFileTmp, { force: true });
73022
- await logFileHandle.close();
73023
- const maxAttempts = 30;
73024
- let started = false;
73025
- for (let i = 0; i < maxAttempts; i++) {
73026
- try {
73027
- const response = await fetch(`http://${host}:${port}/api/health`);
73028
- if (response.ok) {
73029
- started = true;
73030
- break;
73443
+ healthProbe() {
73444
+ if (live === void 0) {
73445
+ const stopped = "stopped";
73446
+ return {
73447
+ child_id: WEB_SUBSYSTEM_NAME,
73448
+ pid: 0,
73449
+ state: stopped,
73450
+ restart_count: 0,
73451
+ detail: "not started"
73452
+ };
73031
73453
  }
73032
- } catch {
73033
- }
73034
- await new Promise((resolve11) => setTimeout(resolve11, 500));
73035
- }
73036
- if (!started) {
73037
- try {
73038
- process.kill(serverProcess.pid);
73039
- } catch {
73040
- }
73041
- await rm(pidFile, { force: true });
73042
- throw new CleoError12(1 /* GENERAL_ERROR */, "Server failed to start within 15 seconds");
73043
- }
73044
- cliOutput(
73045
- {
73046
- pid: serverProcess.pid,
73047
- port,
73048
- host,
73049
- url: `http://${host}:${port}`,
73050
- logFile
73454
+ const alive = isWebProcessRunning(live.pid);
73455
+ const state = alive ? "running" : "stopped";
73456
+ return {
73457
+ child_id: WEB_SUBSYSTEM_NAME,
73458
+ pid: alive ? live.pid : 0,
73459
+ state,
73460
+ restart_count: 0,
73461
+ detail: alive ? `url=http://${live.host}:${live.port} pid=${live.pid}` : `pid=${live.pid} exited`
73462
+ };
73051
73463
  },
73052
- { command: "web", message: `CLEO Web UI running on port ${port}` }
73053
- );
73464
+ async shutdown(context) {
73465
+ const { pidFile } = context;
73466
+ if (!isWebProcessRunning(context.pid)) {
73467
+ await rm(pidFile, { force: true });
73468
+ live = void 0;
73469
+ return;
73470
+ }
73471
+ try {
73472
+ if (process.platform === "win32") {
73473
+ spawn3("taskkill", ["/PID", String(context.pid), "/T"], { stdio: "ignore" });
73474
+ } else {
73475
+ process.kill(context.pid, "SIGTERM");
73476
+ }
73477
+ } catch {
73478
+ }
73479
+ for (let i = 0; i < SIGTERM_GRACE_ITERATIONS2; i++) {
73480
+ if (!isWebProcessRunning(context.pid)) break;
73481
+ await new Promise((resolve11) => setTimeout(resolve11, 500));
73482
+ }
73483
+ if (isWebProcessRunning(context.pid)) {
73484
+ try {
73485
+ if (process.platform === "win32") {
73486
+ spawn3("taskkill", ["/PID", String(context.pid), "/F", "/T"], { stdio: "ignore" });
73487
+ } else {
73488
+ process.kill(context.pid, "SIGKILL");
73489
+ }
73490
+ } catch {
73491
+ }
73492
+ }
73493
+ await rm(pidFile, { force: true });
73494
+ live = void 0;
73495
+ }
73496
+ });
73054
73497
  }
73055
- var DEFAULT_PORT, DEFAULT_HOST, startCommand9, stopCommand6, restartCommand, statusCommand18, openCommand3, webCommand;
73498
+ var WEB_DEFAULT_PORT, WEB_DEFAULT_HOST, WEB_SUBSYSTEM_NAME, SIGTERM_GRACE_ITERATIONS2, STARTUP_POLL_ITERATIONS;
73499
+ var init_web_subsystem = __esm({
73500
+ "packages/cleo/src/cli/web-subsystem.ts"() {
73501
+ "use strict";
73502
+ WEB_DEFAULT_PORT = 3456;
73503
+ WEB_DEFAULT_HOST = "127.0.0.1";
73504
+ WEB_SUBSYSTEM_NAME = "cleo-web";
73505
+ SIGTERM_GRACE_ITERATIONS2 = 60;
73506
+ STARTUP_POLL_ITERATIONS = 30;
73507
+ }
73508
+ });
73509
+
73510
+ // packages/cleo/src/cli/commands/web.ts
73511
+ var web_exports = {};
73512
+ __export(web_exports, {
73513
+ webCommand: () => webCommand
73514
+ });
73515
+ import { spawn as spawn4 } from "node:child_process";
73516
+ import { rm as rm2 } from "node:fs/promises";
73517
+ import { CleoError as CleoError12, formatError as formatError5 } from "@cleocode/core";
73518
+ var startCommand9, stopCommand6, restartCommand, statusCommand18, openCommand3, webCommand;
73056
73519
  var init_web = __esm({
73057
73520
  "packages/cleo/src/cli/commands/web.ts"() {
73058
73521
  "use strict";
73059
73522
  init_src2();
73060
73523
  init_dist();
73061
73524
  init_renderers();
73062
- DEFAULT_PORT = 3456;
73063
- DEFAULT_HOST = "127.0.0.1";
73525
+ init_web_subsystem();
73064
73526
  startCommand9 = defineCommand({
73065
73527
  meta: { name: "start", description: "Start the web server" },
73066
73528
  args: {
73067
73529
  port: {
73068
73530
  type: "string",
73069
73531
  description: "Server port",
73070
- default: String(DEFAULT_PORT)
73532
+ default: String(WEB_DEFAULT_PORT)
73071
73533
  },
73072
73534
  host: {
73073
73535
  type: "string",
73074
73536
  description: "Server host",
73075
- default: DEFAULT_HOST
73537
+ default: WEB_DEFAULT_HOST
73076
73538
  }
73077
73539
  },
73078
73540
  async run({ args }) {
73079
73541
  try {
73080
- await startWebServer(Number.parseInt(args.port, 10), args.host);
73542
+ const port = Number.parseInt(args.port, 10);
73543
+ const host = args.host;
73544
+ const subsystem = createWebSubsystem({ port, host });
73545
+ const ctx = await subsystem.start();
73546
+ const { logFile } = getWebPaths();
73547
+ cliOutput(
73548
+ {
73549
+ pid: ctx.pid,
73550
+ port: ctx.port,
73551
+ host: ctx.host,
73552
+ url: `http://${ctx.host}:${ctx.port}`,
73553
+ logFile
73554
+ },
73555
+ { command: "web", message: `CLEO Web UI running on port ${ctx.port}` }
73556
+ );
73081
73557
  } catch (err) {
73082
73558
  if (err instanceof CleoError12) {
73083
73559
  console.error(formatError5(err));
@@ -73091,36 +73567,24 @@ var init_web = __esm({
73091
73567
  meta: { name: "stop", description: "Stop the web server" },
73092
73568
  async run() {
73093
73569
  try {
73094
- const { pidFile } = getWebPaths();
73095
- const status = await getStatus();
73096
- if (!status.running || !status.pid) {
73097
- await rm(pidFile, { force: true });
73570
+ const { pidFile, logFile } = getWebPaths();
73571
+ const status = await getWebStatus();
73572
+ if (!status.running || status.pid === null) {
73573
+ await rm2(pidFile, { force: true });
73098
73574
  cliOutput({ running: false }, { command: "web", message: "Server is not running" });
73099
73575
  return;
73100
73576
  }
73101
- try {
73102
- if (process.platform === "win32") {
73103
- spawn3("taskkill", ["/PID", String(status.pid), "/T"], { stdio: "ignore" });
73104
- } else {
73105
- process.kill(status.pid, "SIGTERM");
73106
- }
73107
- } catch {
73108
- }
73109
- for (let i = 0; i < 60; i++) {
73110
- if (!isProcessRunning(status.pid)) break;
73111
- await new Promise((resolve11) => setTimeout(resolve11, 500));
73112
- }
73113
- if (isProcessRunning(status.pid)) {
73114
- try {
73115
- if (process.platform === "win32") {
73116
- spawn3("taskkill", ["/PID", String(status.pid), "/F", "/T"], { stdio: "ignore" });
73117
- } else {
73118
- process.kill(status.pid, "SIGKILL");
73119
- }
73120
- } catch {
73121
- }
73122
- }
73123
- await rm(pidFile, { force: true });
73577
+ const subsystem = createWebSubsystem({
73578
+ port: status.port ?? WEB_DEFAULT_PORT,
73579
+ host: status.host ?? WEB_DEFAULT_HOST
73580
+ });
73581
+ await subsystem.shutdown({
73582
+ pid: status.pid,
73583
+ pidFile,
73584
+ logFile,
73585
+ port: status.port ?? WEB_DEFAULT_PORT,
73586
+ host: status.host ?? WEB_DEFAULT_HOST
73587
+ });
73124
73588
  cliOutput({ stopped: true }, { command: "web", message: "CLEO Web UI stopped" });
73125
73589
  } catch (err) {
73126
73590
  if (err instanceof CleoError12) {
@@ -73137,44 +73601,45 @@ var init_web = __esm({
73137
73601
  port: {
73138
73602
  type: "string",
73139
73603
  description: "Server port",
73140
- default: String(DEFAULT_PORT)
73604
+ default: String(WEB_DEFAULT_PORT)
73141
73605
  },
73142
73606
  host: {
73143
73607
  type: "string",
73144
73608
  description: "Server host",
73145
- default: DEFAULT_HOST
73609
+ default: WEB_DEFAULT_HOST
73146
73610
  }
73147
73611
  },
73148
73612
  async run({ args }) {
73149
73613
  try {
73150
- const { pidFile } = getWebPaths();
73151
- const status = await getStatus();
73152
- if (status.running && status.pid) {
73153
- try {
73154
- if (process.platform === "win32") {
73155
- spawn3("taskkill", ["/PID", String(status.pid), "/T"], { stdio: "ignore" });
73156
- } else {
73157
- process.kill(status.pid, "SIGTERM");
73158
- }
73159
- } catch {
73160
- }
73161
- for (let i = 0; i < 60; i++) {
73162
- if (!isProcessRunning(status.pid)) break;
73163
- await new Promise((resolve11) => setTimeout(resolve11, 500));
73164
- }
73165
- if (isProcessRunning(status.pid)) {
73166
- try {
73167
- if (process.platform === "win32") {
73168
- spawn3("taskkill", ["/PID", String(status.pid), "/F", "/T"], { stdio: "ignore" });
73169
- } else {
73170
- process.kill(status.pid, "SIGKILL");
73171
- }
73172
- } catch {
73173
- }
73174
- }
73175
- await rm(pidFile, { force: true });
73614
+ const port = Number.parseInt(args.port, 10);
73615
+ const host = args.host;
73616
+ const { pidFile, logFile } = getWebPaths();
73617
+ const status = await getWebStatus();
73618
+ if (status.running && status.pid !== null) {
73619
+ const stopSubsystem = createWebSubsystem({
73620
+ port: status.port ?? port,
73621
+ host: status.host ?? host
73622
+ });
73623
+ await stopSubsystem.shutdown({
73624
+ pid: status.pid,
73625
+ pidFile,
73626
+ logFile,
73627
+ port: status.port ?? port,
73628
+ host: status.host ?? host
73629
+ });
73176
73630
  }
73177
- await startWebServer(Number.parseInt(args.port, 10), args.host);
73631
+ const startSubsystem = createWebSubsystem({ port, host });
73632
+ const ctx = await startSubsystem.start();
73633
+ cliOutput(
73634
+ {
73635
+ pid: ctx.pid,
73636
+ port: ctx.port,
73637
+ host: ctx.host,
73638
+ url: `http://${ctx.host}:${ctx.port}`,
73639
+ logFile
73640
+ },
73641
+ { command: "web", message: `CLEO Web UI running on port ${ctx.port}` }
73642
+ );
73178
73643
  } catch (err) {
73179
73644
  if (err instanceof CleoError12) {
73180
73645
  console.error(formatError5(err));
@@ -73188,7 +73653,7 @@ var init_web = __esm({
73188
73653
  meta: { name: "status", description: "Check server status" },
73189
73654
  async run() {
73190
73655
  try {
73191
- const status = await getStatus();
73656
+ const status = await getWebStatus();
73192
73657
  cliOutput(status, { command: "web" });
73193
73658
  } catch (err) {
73194
73659
  if (err instanceof CleoError12) {
@@ -73203,7 +73668,7 @@ var init_web = __esm({
73203
73668
  meta: { name: "open", description: "Open browser to the UI" },
73204
73669
  async run() {
73205
73670
  try {
73206
- const status = await getStatus();
73671
+ const status = await getWebStatus();
73207
73672
  if (!status.running || !status.url) {
73208
73673
  throw new CleoError12(
73209
73674
  1 /* GENERAL_ERROR */,
@@ -73214,11 +73679,11 @@ var init_web = __esm({
73214
73679
  const platform = process.platform;
73215
73680
  try {
73216
73681
  if (platform === "linux") {
73217
- spawn3("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
73682
+ spawn4("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
73218
73683
  } else if (platform === "darwin") {
73219
- spawn3("open", [url], { detached: true, stdio: "ignore" }).unref();
73684
+ spawn4("open", [url], { detached: true, stdio: "ignore" }).unref();
73220
73685
  } else if (platform === "win32") {
73221
- spawn3("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
73686
+ spawn4("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
73222
73687
  }
73223
73688
  } catch {
73224
73689
  }
@@ -73257,7 +73722,7 @@ __export(workgraph_exports, {
73257
73722
  });
73258
73723
  import { readFileSync as readFileSync20 } from "node:fs";
73259
73724
  import { resolve as resolve10 } from "node:path";
73260
- var validateCommand10, applyCommand, planCommand4, structureCommand, workgraphCommand;
73725
+ var validateCommand10, applyCommand, planCommand4, structureCommand, statusCommand19, workgraphCommand;
73261
73726
  var init_workgraph2 = __esm({
73262
73727
  "packages/cleo/src/cli/commands/workgraph.ts"() {
73263
73728
  "use strict";
@@ -73367,9 +73832,9 @@ var init_workgraph2 = __esm({
73367
73832
  },
73368
73833
  async run({ args }) {
73369
73834
  const { generatePlanningDoc } = await import("@cleocode/core/workgraph");
73370
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core");
73835
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core");
73371
73836
  const { cliOutput: cliOutput2 } = await Promise.resolve().then(() => (init_renderers(), renderers_exports));
73372
- const projectRoot = getProjectRoot57();
73837
+ const projectRoot = getProjectRoot58();
73373
73838
  const audience = String(args.audience) === "agent" ? "agent" : "maintainer";
73374
73839
  const result = await generatePlanningDoc(projectRoot, {
73375
73840
  sagaId: String(args.sagaId),
@@ -73386,9 +73851,9 @@ var init_workgraph2 = __esm({
73386
73851
  async run() {
73387
73852
  const { validateWorkGraphStructure } = await import("@cleocode/core/workgraph");
73388
73853
  const { taskList: taskList2 } = await import("@cleocode/core/internal");
73389
- const { getProjectRoot: getProjectRoot57 } = await import("@cleocode/core");
73854
+ const { getProjectRoot: getProjectRoot58 } = await import("@cleocode/core");
73390
73855
  const { cliOutput: cliOutput2 } = await Promise.resolve().then(() => (init_renderers(), renderers_exports));
73391
- const projectRoot = getProjectRoot57();
73856
+ const projectRoot = getProjectRoot58();
73392
73857
  const listResult = await taskList2(projectRoot, { limit: 5e3 });
73393
73858
  const tasks = listResult.success ? listResult.data?.tasks ?? [] : [];
73394
73859
  const nodes = tasks.map((t) => ({
@@ -73401,16 +73866,64 @@ var init_workgraph2 = __esm({
73401
73866
  cliOutput2(result, { command: "workgraph", operation: "structure" });
73402
73867
  }
73403
73868
  });
73869
+ statusCommand19 = defineCommand({
73870
+ meta: {
73871
+ name: "status",
73872
+ description: "One-shot saga-to-saga workgraph dashboard (tracking checklist)"
73873
+ },
73874
+ async run() {
73875
+ const { sagas: sagas2, getProjectRoot: getProjectRoot58 } = await import("@cleocode/core");
73876
+ const { cliOutput: cliOutput2 } = await Promise.resolve().then(() => (init_renderers(), renderers_exports));
73877
+ const projectRoot = getProjectRoot58();
73878
+ const listResult = await sagas2.sagaList(projectRoot);
73879
+ if (!listResult.success) {
73880
+ cliOutput2(listResult, { command: "workgraph", operation: "workgraph.status" });
73881
+ return;
73882
+ }
73883
+ const allSagas = listResult.data?.sagas ?? [];
73884
+ const sagaMap = new Map(
73885
+ allSagas.map((s) => [s.id, s])
73886
+ );
73887
+ const TERMINAL = /* @__PURE__ */ new Set(["done", "cancelled", "deleted", "archived", "completed"]);
73888
+ const rows = [];
73889
+ for (let i = 0; i < sagas2.CANONICAL_SAGA_ORDER.length; i++) {
73890
+ const [id, label] = sagas2.CANONICAL_SAGA_ORDER[i];
73891
+ const saga = sagaMap.get(id);
73892
+ if (!saga) continue;
73893
+ const status = saga.status ?? "pending";
73894
+ const rollupResult = await sagas2.sagaRollup(projectRoot, { sagaId: id });
73895
+ const rollup = rollupResult.success ? rollupResult.data : { total: 0, done: 0, completionPct: 0 };
73896
+ rows.push({
73897
+ rank: i + 1,
73898
+ sagaId: id,
73899
+ status,
73900
+ completionPct: rollup.completionPct ?? 0,
73901
+ done: rollup.done ?? 0,
73902
+ total: rollup.total ?? 0,
73903
+ label: TERMINAL.has(status) ? `${label} \u2713` : label
73904
+ });
73905
+ }
73906
+ cliOutput2(
73907
+ {
73908
+ rows,
73909
+ total: rows.length,
73910
+ activeSagaCount: rows.filter((r) => !TERMINAL.has(r.status)).length
73911
+ },
73912
+ { command: "workgraph", operation: "workgraph.status" }
73913
+ );
73914
+ }
73915
+ });
73404
73916
  workgraphCommand = defineCommand({
73405
73917
  meta: {
73406
73918
  name: "workgraph",
73407
- description: "PM-Core V2 WorkGraph operations \u2014 validate, apply, plan, structure"
73919
+ description: "PM-Core V2 WorkGraph operations \u2014 validate, apply, plan, structure, status"
73408
73920
  },
73409
73921
  subCommands: {
73410
73922
  validate: validateCommand10,
73411
73923
  apply: applyCommand,
73412
73924
  plan: planCommand4,
73413
- structure: structureCommand
73925
+ structure: structureCommand,
73926
+ status: statusCommand19
73414
73927
  },
73415
73928
  async run({ cmd, rawArgs }) {
73416
73929
  const firstArg = rawArgs?.find((a) => !a.startsWith("-"));
@@ -73427,7 +73940,7 @@ __export(worktree_exports, {
73427
73940
  worktreeCommand: () => worktreeCommand
73428
73941
  });
73429
73942
  import readline4 from "node:readline";
73430
- import { getProjectRoot as getProjectRoot56, listWorktrees as listWorktrees2 } from "@cleocode/core/internal";
73943
+ import { getProjectRoot as getProjectRoot57, listWorktrees as listWorktrees2 } from "@cleocode/core/internal";
73431
73944
  async function promptYesNo2(question) {
73432
73945
  return new Promise((resolve11) => {
73433
73946
  const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
@@ -73532,7 +74045,7 @@ var init_worktree3 = __esm({
73532
74045
  const staleDays = staleDaysRaw !== void 0 ? Number.parseInt(staleDaysRaw, 10) : void 0;
73533
74046
  const idleDaysRaw = typeof args["idle-days"] === "string" ? args["idle-days"] : void 0;
73534
74047
  const idleDays = idleDaysRaw !== void 0 ? Number.parseInt(idleDaysRaw, 10) : void 0;
73535
- const projectRoot = getProjectRoot56();
74048
+ const projectRoot = getProjectRoot57();
73536
74049
  const listResult = await listWorktrees2({
73537
74050
  projectRoot,
73538
74051
  ...staleDays !== void 0 && !Number.isNaN(staleDays) ? { staleDays } : {}
@@ -73775,7 +74288,7 @@ init_field_context();
73775
74288
  init_format_context();
73776
74289
  import { readFileSync as readFileSync21 } from "node:fs";
73777
74290
  import { dirname as dirname11, join as join37 } from "node:path";
73778
- import { fileURLToPath as fileURLToPath7 } from "node:url";
74291
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
73779
74292
  import { enforceNodeVersion } from "@cleocode/paths";
73780
74293
 
73781
74294
  // packages/cleo/src/cli/generated/command-manifest.ts
@@ -73948,6 +74461,12 @@ var COMMAND_MANIFEST = [
73948
74461
  description: "Claim a task by assigning it to an agent",
73949
74462
  load: async () => (await Promise.resolve().then(() => (init_claim(), claim_exports))).claimCommand
73950
74463
  },
74464
+ {
74465
+ exportName: "classifyCommand",
74466
+ name: "classify",
74467
+ description: "Classify a task: readiness verdict (proceed|grill) + persona routing (agent, confidence)",
74468
+ load: async () => (await Promise.resolve().then(() => (init_classify(), classify_exports))).classifyCommand
74469
+ },
73951
74470
  {
73952
74471
  exportName: "unclaimCommand",
73953
74472
  name: "unclaim",
@@ -74176,10 +74695,16 @@ var COMMAND_MANIFEST = [
74176
74695
  description: "Transcript garbage collection: manual trigger and status",
74177
74696
  load: async () => (await Promise.resolve().then(() => (init_gc(), gc_exports))).gcCommand
74178
74697
  },
74698
+ {
74699
+ exportName: "goCommand",
74700
+ name: "go",
74701
+ description: "SG-AUTOPILOT: run one turn of briefing\u2192sagaNext\u2192ready\u2192stage-branch\u2192ivtr loop",
74702
+ load: async () => (await Promise.resolve().then(() => (init_go(), go_exports))).goCommand
74703
+ },
74179
74704
  {
74180
74705
  exportName: "goalCommand",
74181
74706
  name: "goal",
74182
- description: "DB-persisted, per-agent, evidence-gate-aware goal loop (set/status/subgoal/append).",
74707
+ description: "DB-persisted, per-agent, evidence-gate-aware goal loop (set/status/advance/subgoal/append).",
74183
74708
  load: async () => (await Promise.resolve().then(() => (init_goal2(), goal_exports))).goalCommand
74184
74709
  },
74185
74710
  {
@@ -74659,7 +75184,7 @@ var COMMAND_MANIFEST = [
74659
75184
  {
74660
75185
  exportName: "workgraphCommand",
74661
75186
  name: "workgraph",
74662
- description: "PM-Core V2 WorkGraph operations \u2014 validate, apply, plan, structure",
75187
+ description: "PM-Core V2 WorkGraph operations \u2014 validate, apply, plan, structure, status",
74663
75188
  load: async () => (await Promise.resolve().then(() => (init_workgraph2(), workgraph_exports))).workgraphCommand
74664
75189
  },
74665
75190
  {
@@ -74940,7 +75465,7 @@ async function resolveSubCommandForHelp(cmd, rawArgs) {
74940
75465
  init_summary_context();
74941
75466
  enforceNodeVersion();
74942
75467
  function getPackageVersion() {
74943
- const pkgPath = join37(dirname11(fileURLToPath7(import.meta.url)), "../../package.json");
75468
+ const pkgPath = join37(dirname11(fileURLToPath6(import.meta.url)), "../../package.json");
74944
75469
  const pkg = JSON.parse(readFileSync21(pkgPath, "utf-8"));
74945
75470
  return pkg.version;
74946
75471
  }
@@ -75124,7 +75649,7 @@ async function runStartupMaintenance() {
75124
75649
  detectAndRemoveStrayProjectNexus,
75125
75650
  getGlobalSalt,
75126
75651
  getLogger: getLogger20,
75127
- getProjectRoot: getProjectRoot57,
75652
+ getProjectRoot: getProjectRoot58,
75128
75653
  isCleanupMarkerSet,
75129
75654
  migrateSignaldockToConduit,
75130
75655
  needsSignaldockToConduitMigration,
@@ -75133,7 +75658,7 @@ async function runStartupMaintenance() {
75133
75658
  } = await import("@cleocode/core/internal");
75134
75659
  let projectRootForCleanup = "";
75135
75660
  try {
75136
- projectRootForCleanup = getProjectRoot57();
75661
+ projectRootForCleanup = getProjectRoot58();
75137
75662
  } catch {
75138
75663
  }
75139
75664
  if (!isCleanupMarkerSet(CLI_VERSION, projectRootForCleanup)) {
@@ -75153,7 +75678,7 @@ async function runStartupMaintenance() {
75153
75678
  const isInitInvocation = process.argv.slice(2).some((a) => a === "init");
75154
75679
  if (!isInitInvocation) {
75155
75680
  try {
75156
- const _projectRootForMigration = getProjectRoot57();
75681
+ const _projectRootForMigration = getProjectRoot58();
75157
75682
  if (needsSignaldockToConduitMigration(_projectRootForMigration)) {
75158
75683
  const migrationResult = migrateSignaldockToConduit(_projectRootForMigration);
75159
75684
  if (migrationResult.status === "failed") {