@hasna/todos 0.11.73 → 0.11.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -12874,6 +12874,7 @@ __export(exports_task_routing, {
12874
12874
  setTaskWorkflowPointers: () => setTaskWorkflowPointers,
12875
12875
  getTaskRouteState: () => getTaskRouteState
12876
12876
  });
12877
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
12877
12878
  function machineLocalPath(project, db) {
12878
12879
  const machineId = process.env["TODOS_MACHINE_ID"];
12879
12880
  if (!machineId)
@@ -12911,7 +12912,33 @@ function routeConcurrencyKey(task, project, taskList, projectPath) {
12911
12912
  return `path:${projectPath}`;
12912
12913
  return `task:${task.id}`;
12913
12914
  }
12914
- function getTaskRouteState(taskOrId, db) {
12915
+ function directoryExists(path) {
12916
+ try {
12917
+ return existsSync8(path) && statSync3(path).isDirectory();
12918
+ } catch {
12919
+ return false;
12920
+ }
12921
+ }
12922
+ function classifyRoute(input) {
12923
+ if (input.terminal)
12924
+ return "terminal";
12925
+ if (input.notPending)
12926
+ return "in_progress";
12927
+ if (input.blocked)
12928
+ return "blocked";
12929
+ if (input.locked)
12930
+ return "locked";
12931
+ if (input.missingProjectRoot)
12932
+ return "missing_metadata";
12933
+ if (!input.eligible)
12934
+ return "unroutable";
12935
+ if (input.workflowPointerActive)
12936
+ return "deduped_active";
12937
+ if (input.workflowPointerTerminal)
12938
+ return "terminal_requeue_needed";
12939
+ return "eligible";
12940
+ }
12941
+ function getTaskRouteState(taskOrId, db, options = {}) {
12915
12942
  const d = db || getDatabase();
12916
12943
  const task = typeof taskOrId === "string" ? getTask(taskOrId, d) : taskOrId;
12917
12944
  if (!task)
@@ -12919,8 +12946,10 @@ function getTaskRouteState(taskOrId, db) {
12919
12946
  const { project, projectPath } = resolveProject(task, d);
12920
12947
  const taskList = resolveTaskList(task, project, d);
12921
12948
  const automation = routingAutomationMetadata(task, taskList) ?? {};
12922
- const routeEnabled = routeEnabledForTask(task, taskList) === true;
12949
+ const explicitRouteEnabled = routeEnabledForTask(task, taskList);
12923
12950
  const tagOptIn = task.tags.includes("auto:route") || task.tags.includes("route:enabled");
12951
+ const routeEnabled = explicitRouteEnabled === undefined ? tagOptIn : explicitRouteEnabled;
12952
+ const tagNoAuto = task.tags.includes("no-auto") || task.tags.includes("noauto") || task.tags.includes("no:auto");
12924
12953
  const projectKind = projectKindFromMetadata(task.metadata, taskList?.metadata);
12925
12954
  const locked = Boolean(task.locked_by && !isLockExpired(task.locked_at));
12926
12955
  const blockers = getBlockingDeps(task.id, d);
@@ -12929,10 +12958,21 @@ function getTaskRouteState(taskOrId, db) {
12929
12958
  const requiresApproval = automation.requires_approval === true || task.requires_approval === true;
12930
12959
  const approvalRequired = automation.approval_required === true;
12931
12960
  const approved = Boolean(task.approved_by);
12961
+ const pointers = workflowPointersFromMetadata(task.metadata);
12962
+ const workflowState = (pointers.workflow_state ?? "").trim().toLowerCase();
12963
+ const hasWorkflowPointer = Boolean(pointers.current_workflow_invocation_id || pointers.current_run_id || workflowState);
12964
+ const workflowPointerTerminal = hasWorkflowPointer && TERMINAL_WORKFLOW_STATES.has(workflowState);
12965
+ const workflowPointerActive = hasWorkflowPointer && !workflowPointerTerminal;
12966
+ const verifyProjectRoot = options.verifyProjectRoot === true;
12967
+ let projectRootExists = null;
12968
+ if (verifyProjectRoot) {
12969
+ projectRootExists = projectPath ? directoryExists(projectPath) : false;
12970
+ }
12971
+ const missingProjectRoot = verifyProjectRoot && projectRootExists !== true;
12932
12972
  const gates = {
12933
12973
  route_enabled: routeEnabled,
12934
12974
  tag_opt_in: tagOptIn,
12935
- no_auto: automation.no_auto === true,
12975
+ no_auto: automation.no_auto === true || tagNoAuto,
12936
12976
  manual: automation.manual === true,
12937
12977
  manual_required: automation.manual_required === true,
12938
12978
  requires_approval: requiresApproval,
@@ -12940,7 +12980,10 @@ function getTaskRouteState(taskOrId, db) {
12940
12980
  approved,
12941
12981
  locked,
12942
12982
  blocked,
12943
- terminal
12983
+ terminal,
12984
+ missing_project_root: missingProjectRoot,
12985
+ workflow_pointer_active: workflowPointerActive,
12986
+ workflow_pointer_terminal: workflowPointerTerminal
12944
12987
  };
12945
12988
  const reasons = [];
12946
12989
  if (task.status !== "pending")
@@ -12965,12 +13008,44 @@ function getTaskRouteState(taskOrId, db) {
12965
13008
  reasons.push("approval_required");
12966
13009
  if (automation.allowed === false)
12967
13010
  reasons.push("automation_disallowed");
13011
+ if (missingProjectRoot)
13012
+ reasons.push("missing_project_root");
13013
+ const eligible = reasons.length === 0;
13014
+ const staleAfterMs = options.staleInProgressAfterMs ?? DEFAULT_STALE_IN_PROGRESS_MS;
13015
+ const updatedAtMs = Date.parse(task.updated_at);
13016
+ const ageMs = Number.isNaN(updatedAtMs) ? 0 : Math.max(0, Date.now() - updatedAtMs);
13017
+ const evidence = {
13018
+ owner: task.assigned_to ?? task.locked_by ?? null,
13019
+ assigned_to: task.assigned_to ?? null,
13020
+ locked_by: task.locked_by ?? null,
13021
+ locked_at: task.locked_at ?? null,
13022
+ updated_at: task.updated_at,
13023
+ age_ms: ageMs,
13024
+ stale: task.status === "in_progress" && ageMs > staleAfterMs,
13025
+ stale_after_ms: staleAfterMs,
13026
+ current_run_id: pointers.current_run_id ?? null,
13027
+ current_workflow_invocation_id: pointers.current_workflow_invocation_id ?? null,
13028
+ workflow_state: pointers.workflow_state ?? null,
13029
+ project_root_verified: verifyProjectRoot,
13030
+ project_root_exists: projectRootExists
13031
+ };
13032
+ const route_class = classifyRoute({
13033
+ terminal,
13034
+ notPending: task.status !== "pending",
13035
+ blocked,
13036
+ locked,
13037
+ missingProjectRoot,
13038
+ eligible,
13039
+ workflowPointerActive,
13040
+ workflowPointerTerminal
13041
+ });
12968
13042
  return {
12969
13043
  schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
12970
13044
  task_id: task.id,
12971
13045
  task_short_id: task.short_id,
12972
13046
  status: task.status,
12973
- eligible: reasons.length === 0,
13047
+ eligible,
13048
+ route_class,
12974
13049
  reasons,
12975
13050
  blockers: blockers.map((blocker) => ({
12976
13051
  id: blocker.id,
@@ -12990,7 +13065,8 @@ function getTaskRouteState(taskOrId, db) {
12990
13065
  task_list_name: taskList?.name ?? null,
12991
13066
  concurrency_key: routeConcurrencyKey(task, project, taskList, projectPath)
12992
13067
  },
12993
- pointers: workflowPointersFromMetadata(task.metadata)
13068
+ pointers,
13069
+ evidence
12994
13070
  };
12995
13071
  }
12996
13072
  function setTaskWorkflowPointers(taskId, input, db) {
@@ -13036,12 +13112,28 @@ function pointerPatch(previous, input, key) {
13036
13112
  return previous;
13037
13113
  return typeof value === "string" && value.trim() ? value : undefined;
13038
13114
  }
13115
+ var DEFAULT_STALE_IN_PROGRESS_MS, TERMINAL_WORKFLOW_STATES;
13039
13116
  var init_task_routing = __esm(() => {
13040
13117
  init_database();
13041
13118
  init_projects();
13042
13119
  init_task_crud();
13043
13120
  init_task_lifecycle();
13044
13121
  init_task_lists();
13122
+ DEFAULT_STALE_IN_PROGRESS_MS = 3 * 24 * 60 * 60 * 1000;
13123
+ TERMINAL_WORKFLOW_STATES = new Set([
13124
+ "failed",
13125
+ "cancelled",
13126
+ "canceled",
13127
+ "completed",
13128
+ "complete",
13129
+ "done",
13130
+ "error",
13131
+ "errored",
13132
+ "timeout",
13133
+ "timed_out",
13134
+ "aborted",
13135
+ "superseded"
13136
+ ]);
13045
13137
  });
13046
13138
 
13047
13139
  // src/cli/commands/task-commands.ts
@@ -13277,13 +13369,13 @@ function registerTaskCommands(program2) {
13277
13369
  console.log(formatTaskLine(result.task));
13278
13370
  }
13279
13371
  });
13280
- task.command("route-state <id>").description("Show deterministic routing eligibility and workflow pointers for a task").action(async (id) => {
13372
+ task.command("route-state <id>").description("Show deterministic routing eligibility and workflow pointers for a task").option("--verify-project-root", "Filesystem-check the resolved project root and surface missing_project_root before admission").action(async (id, opts) => {
13281
13373
  const globalOpts = program2.opts();
13282
13374
  const resolvedId = resolveTaskId(id);
13283
13375
  const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
13284
13376
  let state;
13285
13377
  try {
13286
- state = getTaskRouteState2(resolvedId);
13378
+ state = getTaskRouteState2(resolvedId, undefined, { verifyProjectRoot: Boolean(opts.verifyProjectRoot) });
13287
13379
  } catch (e) {
13288
13380
  handleError(e);
13289
13381
  }
@@ -13294,8 +13386,12 @@ function registerTaskCommands(program2) {
13294
13386
  console.log(chalk2.bold("Task route state"));
13295
13387
  console.log(` ${chalk2.dim("Task:")} ${state.task_short_id || state.task_id.slice(0, 8)}`);
13296
13388
  console.log(` ${chalk2.dim("Eligible:")} ${state.eligible ? chalk2.green("yes") : chalk2.yellow("no")}`);
13389
+ console.log(` ${chalk2.dim("Class:")} ${state.route_class}`);
13297
13390
  console.log(` ${chalk2.dim("Reasons:")} ${state.reasons.length > 0 ? state.reasons.join(", ") : "none"}`);
13298
13391
  console.log(` ${chalk2.dim("Route:")} ${state.route.concurrency_key}`);
13392
+ if (state.evidence.owner) {
13393
+ console.log(` ${chalk2.dim("Owner:")} ${state.evidence.owner}${state.evidence.stale ? chalk2.yellow(" (stale)") : ""}`);
13394
+ }
13299
13395
  if (state.pointers.current_workflow_invocation_id) {
13300
13396
  console.log(` ${chalk2.dim("Invocation:")} ${state.pointers.current_workflow_invocation_id}`);
13301
13397
  }
@@ -13978,7 +14074,7 @@ var init_task_commands = __esm(() => {
13978
14074
  });
13979
14075
 
13980
14076
  // src/lib/plan-artifacts.ts
13981
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
14077
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
13982
14078
  import { join as join7, resolve as resolve10 } from "path";
13983
14079
  function assertSafePathSegment(value, label) {
13984
14080
  const trimmed = value.trim();
@@ -14214,7 +14310,7 @@ function readPlanArtifact(plan, db) {
14214
14310
  return null;
14215
14311
  const d = db || getDatabase();
14216
14312
  const paths = resolvePlanArtifactCandidatePaths(plan, d);
14217
- const path = existsSync8(paths.primary.file_path) ? paths.primary.file_path : existsSync8(paths.legacy.file_path) ? paths.legacy.file_path : null;
14313
+ const path = existsSync9(paths.primary.file_path) ? paths.primary.file_path : existsSync9(paths.legacy.file_path) ? paths.legacy.file_path : null;
14218
14314
  if (!path)
14219
14315
  return null;
14220
14316
  const markdown = readFileSync4(path, "utf8");
@@ -14229,7 +14325,7 @@ function inspectPlanArtifact(plan, db) {
14229
14325
  return null;
14230
14326
  const d = db || getDatabase();
14231
14327
  const paths = resolvePlanArtifactCandidatePaths(plan, d);
14232
- const path = existsSync8(paths.primary.file_path) ? paths.primary.file_path : existsSync8(paths.legacy.file_path) ? paths.legacy.file_path : null;
14328
+ const path = existsSync9(paths.primary.file_path) ? paths.primary.file_path : existsSync9(paths.legacy.file_path) ? paths.legacy.file_path : null;
14233
14329
  if (!path) {
14234
14330
  return {
14235
14331
  path: paths.primary.file_path,
@@ -15705,7 +15801,7 @@ var init_saved_search_views = __esm(() => {
15705
15801
  });
15706
15802
 
15707
15803
  // src/lib/claude-tasks.ts
15708
- import { existsSync as existsSync9, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
15804
+ import { existsSync as existsSync10, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
15709
15805
  import { join as join9 } from "path";
15710
15806
  function getTaskListDir(taskListId) {
15711
15807
  return join9(HOME, ".claude", "tasks", taskListId);
@@ -15727,7 +15823,7 @@ function toSqliteStatus(status) {
15727
15823
  }
15728
15824
  function readPrefixCounter(dir) {
15729
15825
  const path = join9(dir, ".prefix-counter");
15730
- if (!existsSync9(path))
15826
+ if (!existsSync10(path))
15731
15827
  return 0;
15732
15828
  const val = parseInt(readFileSync5(path, "utf-8").trim(), 10);
15733
15829
  return isNaN(val) ? 0 : val;
@@ -15760,7 +15856,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
15760
15856
  }
15761
15857
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
15762
15858
  const dir = getTaskListDir(taskListId);
15763
- if (!existsSync9(dir))
15859
+ if (!existsSync10(dir))
15764
15860
  ensureDir2(dir);
15765
15861
  const filter = {};
15766
15862
  if (projectId)
@@ -15856,7 +15952,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
15856
15952
  }
15857
15953
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
15858
15954
  const dir = getTaskListDir(taskListId);
15859
- if (!existsSync9(dir)) {
15955
+ if (!existsSync10(dir)) {
15860
15956
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
15861
15957
  }
15862
15958
  const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
@@ -15949,7 +16045,7 @@ var init_claude_tasks = __esm(() => {
15949
16045
  });
15950
16046
 
15951
16047
  // src/lib/agent-tasks.ts
15952
- import { existsSync as existsSync10 } from "fs";
16048
+ import { existsSync as existsSync11 } from "fs";
15953
16049
  import { join as join10 } from "path";
15954
16050
  function agentBaseDir(agent) {
15955
16051
  const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
@@ -15987,7 +16083,7 @@ function metadataKey(agent) {
15987
16083
  }
15988
16084
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
15989
16085
  const dir = getTaskListDir2(agent, taskListId);
15990
- if (!existsSync10(dir))
16086
+ if (!existsSync11(dir))
15991
16087
  ensureDir2(dir);
15992
16088
  const filter = {};
15993
16089
  if (projectId)
@@ -16070,7 +16166,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
16070
16166
  }
16071
16167
  function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
16072
16168
  const dir = getTaskListDir2(agent, taskListId);
16073
- if (!existsSync10(dir)) {
16169
+ if (!existsSync11(dir)) {
16074
16170
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
16075
16171
  }
16076
16172
  const files = listJsonFiles(dir);
@@ -16242,11 +16338,11 @@ __export(exports_project_bootstrap, {
16242
16338
  discoverProjectWorkspace: () => discoverProjectWorkspace,
16243
16339
  bootstrapProject: () => bootstrapProject
16244
16340
  });
16245
- import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
16341
+ import { existsSync as existsSync12, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
16246
16342
  import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
16247
16343
  function safeStat(path) {
16248
16344
  try {
16249
- return statSync3(path);
16345
+ return statSync4(path);
16250
16346
  } catch {
16251
16347
  return null;
16252
16348
  }
@@ -16261,7 +16357,7 @@ function canonicalPath(input) {
16261
16357
  function findUp(start, marker) {
16262
16358
  let current = canonicalPath(start);
16263
16359
  while (true) {
16264
- if (existsSync11(resolve11(current, marker)))
16360
+ if (existsSync12(resolve11(current, marker)))
16265
16361
  return current;
16266
16362
  const parent = dirname6(current);
16267
16363
  if (parent === current)
@@ -16273,7 +16369,7 @@ function readPackageJson(path) {
16273
16369
  if (!path)
16274
16370
  return null;
16275
16371
  const file = resolve11(path, "package.json");
16276
- if (!existsSync11(file))
16372
+ if (!existsSync12(file))
16277
16373
  return null;
16278
16374
  try {
16279
16375
  const parsed = JSON.parse(readFileSync6(file, "utf-8"));
@@ -16295,7 +16391,7 @@ function workspaceMarker(root, rootPackage) {
16295
16391
  if (rootPackage?.workspaces)
16296
16392
  markers.push("package.json#workspaces");
16297
16393
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
16298
- if (existsSync11(resolve11(root, marker)))
16394
+ if (existsSync12(resolve11(root, marker)))
16299
16395
  markers.push(marker);
16300
16396
  }
16301
16397
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -21818,7 +21914,7 @@ __export(exports_extract, {
21818
21914
  buildCodebaseIndex: () => buildCodebaseIndex,
21819
21915
  EXTRACT_TAGS: () => EXTRACT_TAGS
21820
21916
  });
21821
- import { existsSync as existsSync12, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
21917
+ import { existsSync as existsSync13, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
21822
21918
  import { createHash as createHash3 } from "crypto";
21823
21919
  import { relative as relative3, resolve as resolve12, join as join11 } from "path";
21824
21920
  function stableHash(value) {
@@ -21828,9 +21924,9 @@ function normalizePathForMatch(value) {
21828
21924
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
21829
21925
  }
21830
21926
  function readGitignorePatterns(basePath) {
21831
- const root = statSync4(basePath).isFile() ? resolve12(basePath, "..") : basePath;
21927
+ const root = statSync5(basePath).isFile() ? resolve12(basePath, "..") : basePath;
21832
21928
  const gitignorePath = join11(root, ".gitignore");
21833
- if (!existsSync12(gitignorePath))
21929
+ if (!existsSync13(gitignorePath))
21834
21930
  return [];
21835
21931
  try {
21836
21932
  return readFileSync7(gitignorePath, "utf-8").split(`
@@ -21939,7 +22035,7 @@ function extractFromSource(source, filePath, tags = [...EXTRACT_TAGS]) {
21939
22035
  return results;
21940
22036
  }
21941
22037
  function collectFiles(basePath, extensions, excludes, respectGitignore) {
21942
- const stat = statSync4(basePath);
22038
+ const stat = statSync5(basePath);
21943
22039
  if (stat.isFile()) {
21944
22040
  return [basePath];
21945
22041
  }
@@ -21972,10 +22068,10 @@ function buildCodebaseIndex(options) {
21972
22068
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
21973
22069
  const indexed = [];
21974
22070
  for (const file of files) {
21975
- const fullPath = statSync4(basePath).isFile() ? basePath : join11(basePath, file);
22071
+ const fullPath = statSync5(basePath).isFile() ? basePath : join11(basePath, file);
21976
22072
  try {
21977
22073
  const source = readFileSync7(fullPath, "utf-8");
21978
- const relPath = statSync4(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
22074
+ const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
21979
22075
  indexed.push({
21980
22076
  file: relPath,
21981
22077
  checksum: stableHash(source).slice(0, 24),
@@ -22003,10 +22099,10 @@ function extractTodos(options, db) {
22003
22099
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
22004
22100
  const allComments = [];
22005
22101
  for (const file of files) {
22006
- const fullPath = statSync4(basePath).isFile() ? basePath : join11(basePath, file);
22102
+ const fullPath = statSync5(basePath).isFile() ? basePath : join11(basePath, file);
22007
22103
  try {
22008
22104
  const source = readFileSync7(fullPath, "utf-8");
22009
- const relPath = statSync4(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
22105
+ const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
22010
22106
  const comments = extractFromSource(source, relPath, tags);
22011
22107
  allComments.push(...comments);
22012
22108
  } catch {}
@@ -25133,7 +25229,7 @@ __export(exports_retention_cleanup, {
25133
25229
  applyRetentionCleanup: () => applyRetentionCleanup,
25134
25230
  RETENTION_CLEANUP_CONFIRMATION: () => RETENTION_CLEANUP_CONFIRMATION
25135
25231
  });
25136
- import { existsSync as existsSync13, unlinkSync } from "fs";
25232
+ import { existsSync as existsSync14, unlinkSync } from "fs";
25137
25233
  function normalizeScopes(scopes) {
25138
25234
  if (!scopes || scopes.length === 0)
25139
25235
  return [...ALL_SCOPES];
@@ -25336,7 +25432,7 @@ function applyRetentionCleanup(input, db) {
25336
25432
  for (const artifact of report.candidates.artifact_files) {
25337
25433
  try {
25338
25434
  const path = artifactStorePath(artifact.relative_path);
25339
- if (!existsSync13(path)) {
25435
+ if (!existsSync14(path)) {
25340
25436
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
25341
25437
  continue;
25342
25438
  }
@@ -26012,7 +26108,7 @@ __export(exports_local_extensions, {
26012
26108
  discoverLocalExtensions: () => discoverLocalExtensions
26013
26109
  });
26014
26110
  import { createHash as createHash5, createVerify } from "crypto";
26015
- import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
26111
+ import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
26016
26112
  import { basename as basename6, join as join12, resolve as resolve14 } from "path";
26017
26113
  function isObject(value) {
26018
26114
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -26272,10 +26368,10 @@ function verifyExtensionSignature(input) {
26272
26368
  }
26273
26369
  function inspectExtensionSource(source2) {
26274
26370
  const resolved = resolve14(source2);
26275
- if (!existsSync14(resolved))
26371
+ if (!existsSync15(resolved))
26276
26372
  throw new Error(`extension source not found: ${source2}`);
26277
- const stat = statSync5(resolved);
26278
- const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync14) : resolved;
26373
+ const stat = statSync6(resolved);
26374
+ const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync15) : resolved;
26279
26375
  if (!manifestPath)
26280
26376
  throw new Error(`extension directory ${source2} is missing todos.extension.json`);
26281
26377
  const raw = readFileSync8(manifestPath);
@@ -26375,16 +26471,16 @@ function projectExtensionSources(projectPath) {
26375
26471
  join12(root, ".todos", "todos.extension.json")
26376
26472
  ];
26377
26473
  const extensionDir = join12(root, ".todos", "extensions");
26378
- if (existsSync14(extensionDir)) {
26474
+ if (existsSync15(extensionDir)) {
26379
26475
  for (const entry of readdirSync3(extensionDir)) {
26380
26476
  if (entry.startsWith("."))
26381
26477
  continue;
26382
26478
  const full = join12(extensionDir, entry);
26383
- if (statSync5(full).isDirectory() || entry.endsWith(".json"))
26479
+ if (statSync6(full).isDirectory() || entry.endsWith(".json"))
26384
26480
  candidates.push(full);
26385
26481
  }
26386
26482
  }
26387
- return candidates.filter(existsSync14);
26483
+ return candidates.filter(existsSync15);
26388
26484
  }
26389
26485
  function discoverLocalExtensions(options = {}) {
26390
26486
  const config = loadConfig();
@@ -27684,7 +27780,7 @@ var exports_doctor = {};
27684
27780
  __export(exports_doctor, {
27685
27781
  runTodosDoctor: () => runTodosDoctor
27686
27782
  });
27687
- import { chmodSync, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7, statSync as statSync6 } from "fs";
27783
+ import { chmodSync, copyFileSync, existsSync as existsSync16, mkdirSync as mkdirSync7, statSync as statSync7 } from "fs";
27688
27784
  import { basename as basename7, dirname as dirname7, join as join13 } from "path";
27689
27785
  function tableExists(db, table) {
27690
27786
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
@@ -27779,7 +27875,7 @@ function findMissingProjectRoots(db) {
27779
27875
  continue;
27780
27876
  if (!row.path.startsWith("/"))
27781
27877
  continue;
27782
- if (!existsSync15(row.path))
27878
+ if (!existsSync16(row.path))
27783
27879
  missing++;
27784
27880
  }
27785
27881
  return missing;
@@ -27831,7 +27927,7 @@ function databasePermissionsAreUnsafe(dbPath) {
27831
27927
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
27832
27928
  return false;
27833
27929
  try {
27834
- return (statSync6(dbPath).mode & 63) !== 0;
27930
+ return (statSync7(dbPath).mode & 63) !== 0;
27835
27931
  } catch {
27836
27932
  return false;
27837
27933
  }
@@ -27839,14 +27935,14 @@ function databasePermissionsAreUnsafe(dbPath) {
27839
27935
  function createBackup(dbPath) {
27840
27936
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
27841
27937
  return;
27842
- if (!existsSync15(dbPath))
27938
+ if (!existsSync16(dbPath))
27843
27939
  return;
27844
27940
  const stamp = now().replace(/[:.]/g, "-");
27845
27941
  const backupDir = join13(dirname7(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
27846
27942
  const files = [];
27847
27943
  mkdirSync7(backupDir, { recursive: true });
27848
27944
  for (const source2 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
27849
- if (!existsSync15(source2))
27945
+ if (!existsSync16(source2))
27850
27946
  continue;
27851
27947
  const target = join13(backupDir, basename7(source2));
27852
27948
  copyFileSync(source2, target);
@@ -33872,7 +33968,7 @@ var exports_mention_resolver = {};
33872
33968
  __export(exports_mention_resolver, {
33873
33969
  resolveMentions: () => resolveMentions
33874
33970
  });
33875
- import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
33971
+ import { existsSync as existsSync17, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync8 } from "fs";
33876
33972
  import { basename as basename8, isAbsolute, join as join15, relative as relative5, resolve as resolve17, sep as sep3 } from "path";
33877
33973
  function blankResolution(parsed) {
33878
33974
  return {
@@ -33971,11 +34067,11 @@ function resolveFile(parsed, workspace) {
33971
34067
  return resolution;
33972
34068
  }
33973
34069
  resolution.path = relPath;
33974
- if (!existsSync16(absolutePath)) {
34070
+ if (!existsSync17(absolutePath)) {
33975
34071
  resolution.warnings.push("file does not exist in the local workspace");
33976
34072
  return resolution;
33977
34073
  }
33978
- const stats = statSync7(absolutePath);
34074
+ const stats = statSync8(absolutePath);
33979
34075
  if (!stats.isFile()) {
33980
34076
  resolution.warnings.push("path exists but is not a file");
33981
34077
  return resolution;
@@ -34013,7 +34109,7 @@ function walkSourceFiles(root, current = root, files = []) {
34013
34109
  if (!entry.isFile())
34014
34110
  continue;
34015
34111
  const extension = `.${basename8(entry.name).split(".").pop() || ""}`;
34016
- if (SOURCE_EXTENSIONS.has(extension) && statSync7(absolutePath).size <= 512 * 1024) {
34112
+ if (SOURCE_EXTENSIONS.has(extension) && statSync8(absolutePath).size <= 512 * 1024) {
34017
34113
  files.push(absolutePath);
34018
34114
  }
34019
34115
  }
@@ -42461,7 +42557,7 @@ __export(exports_verification_providers, {
42461
42557
  getVerificationRecord: () => getVerificationRecord,
42462
42558
  discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
42463
42559
  });
42464
- import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
42560
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
42465
42561
  function normalizeName6(name) {
42466
42562
  const normalized = name.trim().toLowerCase();
42467
42563
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -42613,7 +42709,7 @@ Timed out after ${provider.timeout_ms}ms`);
42613
42709
  };
42614
42710
  }
42615
42711
  function runCiLogProvider(input) {
42616
- const text = input.log_text ?? (input.log_path && existsSync17(input.log_path) ? readFileSync11(input.log_path, "utf-8") : "");
42712
+ const text = input.log_text ?? (input.log_path && existsSync18(input.log_path) ? readFileSync11(input.log_path, "utf-8") : "");
42617
42713
  return {
42618
42714
  status: classifyLog(text),
42619
42715
  attempts: 1,
@@ -42625,7 +42721,7 @@ function runBrowserProvider(input) {
42625
42721
  if (!input.artifact_path) {
42626
42722
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
42627
42723
  }
42628
- if (!existsSync17(input.artifact_path)) {
42724
+ if (!existsSync18(input.artifact_path)) {
42629
42725
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
42630
42726
  }
42631
42727
  return {
@@ -51716,7 +51812,7 @@ __export(exports_environment_snapshots, {
51716
51812
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
51717
51813
  });
51718
51814
  import { createHash as createHash12 } from "crypto";
51719
- import { existsSync as existsSync18, readFileSync as readFileSync14, statSync as statSync8 } from "fs";
51815
+ import { existsSync as existsSync19, readFileSync as readFileSync14, statSync as statSync9 } from "fs";
51720
51816
  import { hostname as hostname2, platform, arch } from "os";
51721
51817
  import { dirname as dirname9, join as join18, resolve as resolve20 } from "path";
51722
51818
  import { tmpdir as tmpdir3 } from "os";
@@ -51725,9 +51821,9 @@ function sha2566(value) {
51725
51821
  }
51726
51822
  function fileRecord(root, relativePath) {
51727
51823
  const path = join18(root, relativePath);
51728
- if (!existsSync18(path))
51824
+ if (!existsSync19(path))
51729
51825
  return null;
51730
- const stat = statSync8(path);
51826
+ const stat = statSync9(path);
51731
51827
  if (!stat.isFile())
51732
51828
  return null;
51733
51829
  const content = readFileSync14(path);
@@ -52428,7 +52524,7 @@ __export(exports_serve, {
52428
52524
  SECURITY_HEADERS: () => SECURITY_HEADERS,
52429
52525
  MIME_TYPES: () => MIME_TYPES
52430
52526
  });
52431
- import { existsSync as existsSync19 } from "fs";
52527
+ import { existsSync as existsSync20 } from "fs";
52432
52528
  import { join as join19, dirname as dirname10, extname } from "path";
52433
52529
  import { fileURLToPath as fileURLToPath2 } from "url";
52434
52530
  function resolveDashboardDir() {
@@ -52445,7 +52541,7 @@ function resolveDashboardDir() {
52445
52541
  }
52446
52542
  candidates.push(join19(process.cwd(), "dashboard", "dist"));
52447
52543
  for (const candidate of candidates) {
52448
- if (existsSync19(candidate))
52544
+ if (existsSync20(candidate))
52449
52545
  return candidate;
52450
52546
  }
52451
52547
  return join19(process.cwd(), "dashboard", "dist");
@@ -52507,7 +52603,7 @@ function json(data, status = 200, headers) {
52507
52603
  });
52508
52604
  }
52509
52605
  function serveStaticFile(filePath) {
52510
- if (!existsSync19(filePath))
52606
+ if (!existsSync20(filePath))
52511
52607
  return null;
52512
52608
  const ext = extname(filePath);
52513
52609
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -52588,7 +52684,7 @@ data: ${data}
52588
52684
  filteredSseClients.delete(client);
52589
52685
  }
52590
52686
  const dashboardDir = resolveDashboardDir();
52591
- const dashboardExists = existsSync19(dashboardDir);
52687
+ const dashboardExists = existsSync20(dashboardDir);
52592
52688
  if (!dashboardExists) {
52593
52689
  console.error(`
52594
52690
  Dashboard not found at: ${dashboardDir}`);
@@ -54421,7 +54517,7 @@ __export(exports_config_serve_commands, {
54421
54517
  registerConfigServeCommands: () => registerConfigServeCommands
54422
54518
  });
54423
54519
  import chalk6 from "chalk";
54424
- import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
54520
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
54425
54521
  import { dirname as dirname11, join as join20 } from "path";
54426
54522
  function registerConfigServeCommands(program2) {
54427
54523
  program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
@@ -54463,7 +54559,7 @@ function registerConfigServeCommands(program2) {
54463
54559
  }
54464
54560
  obj[keys[keys.length - 1]] = parsedValue;
54465
54561
  const dir = dirname11(configPath);
54466
- if (!existsSync20(dir))
54562
+ if (!existsSync21(dir))
54467
54563
  mkdirSync10(dir, { recursive: true });
54468
54564
  writeFileSync8(configPath, JSON.stringify(config2, null, 2));
54469
54565
  if (globalOpts.json) {
@@ -55503,7 +55599,7 @@ __export(exports_task_route_sources, {
55503
55599
  });
55504
55600
  import { Database as Database3 } from "bun:sqlite";
55505
55601
  import { createHash as createHash13 } from "crypto";
55506
- import { existsSync as existsSync21, readdirSync as readdirSync5, statSync as statSync9 } from "fs";
55602
+ import { existsSync as existsSync22, readdirSync as readdirSync5, statSync as statSync10 } from "fs";
55507
55603
  import { basename as basename9, dirname as dirname12, join as join21, resolve as resolve21 } from "path";
55508
55604
  function normalizePath5(input) {
55509
55605
  return resolve21(input);
@@ -55568,7 +55664,7 @@ function discoverStoresUnderRoot(sourceRoot) {
55568
55664
  const rootPath = normalizePath5(sourceRoot);
55569
55665
  const errors2 = [];
55570
55666
  const stores = [];
55571
- if (!existsSync21(rootPath)) {
55667
+ if (!existsSync22(rootPath)) {
55572
55668
  const ref = createStoreRef(join21(rootPath, TODO_STORE_RELATIVE_PATH));
55573
55669
  errors2.push({
55574
55670
  ...ref,
@@ -55579,7 +55675,7 @@ function discoverStoresUnderRoot(sourceRoot) {
55579
55675
  }
55580
55676
  let rootStat;
55581
55677
  try {
55582
- rootStat = statSync9(rootPath);
55678
+ rootStat = statSync10(rootPath);
55583
55679
  } catch (error) {
55584
55680
  const ref = createStoreRef(join21(rootPath, TODO_STORE_RELATIVE_PATH));
55585
55681
  errors2.push({
@@ -55595,7 +55691,7 @@ function discoverStoresUnderRoot(sourceRoot) {
55595
55691
  }
55596
55692
  function scanDirectory(dir, depth) {
55597
55693
  const candidate = join21(dir, TODO_STORE_RELATIVE_PATH);
55598
- if (existsSync21(candidate)) {
55694
+ if (existsSync22(candidate)) {
55599
55695
  stores.push(createStoreRef(candidate));
55600
55696
  }
55601
55697
  if (depth >= ROOT_SCAN_MAX_DEPTH)
@@ -55641,7 +55737,7 @@ function collectStoreRefs(input) {
55641
55737
  };
55642
55738
  }
55643
55739
  function openReadonlyStore(ref) {
55644
- if (!existsSync21(ref.source_db_path)) {
55740
+ if (!existsSync22(ref.source_db_path)) {
55645
55741
  throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
55646
55742
  }
55647
55743
  return new Database3(ref.source_db_path, { readonly: true, create: false });
@@ -57001,13 +57097,13 @@ Repairs`));
57001
57097
  try {
57002
57098
  const db = getDatabase();
57003
57099
  const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
57004
- const { statSync: statSync10 } = await import("fs");
57100
+ const { statSync: statSync11 } = await import("fs");
57005
57101
  const { join: join22 } = await import("path");
57006
57102
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
57007
57103
  const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join22(home, ".hasna", "todos", "todos.db");
57008
57104
  let size = "unknown";
57009
57105
  try {
57010
- size = `${(statSync10(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
57106
+ size = `${(statSync11(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
57011
57107
  } catch {}
57012
57108
  checks.push({ name: "Database", ok: true, message: `${row.count} tasks \xB7 ${size} \xB7 ${chalk7.dim(dbPath)}` });
57013
57109
  } catch (e) {
@@ -58854,7 +58950,7 @@ __export(exports_mcp_hooks_commands, {
58854
58950
  });
58855
58951
  import chalk8 from "chalk";
58856
58952
  import { execSync as execSync3 } from "child_process";
58857
- import { existsSync as existsSync22, readFileSync as readFileSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11, chmodSync as chmodSync2 } from "fs";
58953
+ import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11, chmodSync as chmodSync2 } from "fs";
58858
58954
  import { dirname as dirname13, join as join22 } from "path";
58859
58955
  function getMcpBinaryPath() {
58860
58956
  try {
@@ -58863,12 +58959,12 @@ function getMcpBinaryPath() {
58863
58959
  return p;
58864
58960
  } catch {}
58865
58961
  const bunBin = join22(HOME2, ".bun", "bin", "todos-mcp");
58866
- if (existsSync22(bunBin))
58962
+ if (existsSync23(bunBin))
58867
58963
  return bunBin;
58868
58964
  return "todos-mcp";
58869
58965
  }
58870
58966
  function readJsonFile2(path) {
58871
- if (!existsSync22(path))
58967
+ if (!existsSync23(path))
58872
58968
  return {};
58873
58969
  try {
58874
58970
  return JSON.parse(readFileSync17(path, "utf-8"));
@@ -58878,19 +58974,19 @@ function readJsonFile2(path) {
58878
58974
  }
58879
58975
  function writeJsonFile2(path, data) {
58880
58976
  const dir = dirname13(path);
58881
- if (!existsSync22(dir))
58977
+ if (!existsSync23(dir))
58882
58978
  mkdirSync11(dir, { recursive: true });
58883
58979
  writeFileSync10(path, JSON.stringify(data, null, 2) + `
58884
58980
  `);
58885
58981
  }
58886
58982
  function readTomlFile(path) {
58887
- if (!existsSync22(path))
58983
+ if (!existsSync23(path))
58888
58984
  return "";
58889
58985
  return readFileSync17(path, "utf-8");
58890
58986
  }
58891
58987
  function writeTomlFile(path, content) {
58892
58988
  const dir = dirname13(path);
58893
- if (!existsSync22(dir))
58989
+ if (!existsSync23(dir))
58894
58990
  mkdirSync11(dir, { recursive: true });
58895
58991
  writeFileSync10(path, content);
58896
58992
  }
@@ -59054,7 +59150,7 @@ function registerMcpHooksCommands(program2) {
59054
59150
  todosBin = p;
59055
59151
  } catch {}
59056
59152
  const hooksDir = join22(process.cwd(), ".claude", "hooks");
59057
- if (!existsSync22(hooksDir))
59153
+ if (!existsSync23(hooksDir))
59058
59154
  mkdirSync11(hooksDir, { recursive: true });
59059
59155
  const hookScript = `#!/usr/bin/env bash
59060
59156
  # Auto-generated by: todos hooks install
@@ -59956,7 +60052,7 @@ Artifacts:`));
59956
60052
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
59957
60053
  const hookPath = `${gitDir}/hooks/post-commit`;
59958
60054
  const marker = "# todos-auto-link";
59959
- if (existsSync22(hookPath)) {
60055
+ if (existsSync23(hookPath)) {
59960
60056
  const existing = readFileSync17(hookPath, "utf-8");
59961
60057
  if (existing.includes(marker)) {
59962
60058
  console.log(chalk8.yellow("Hook already installed."));
@@ -59984,7 +60080,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
59984
60080
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
59985
60081
  const hookPath = `${gitDir}/hooks/post-commit`;
59986
60082
  const marker = "# todos-auto-link";
59987
- if (!existsSync22(hookPath)) {
60083
+ if (!existsSync23(hookPath)) {
59988
60084
  console.log(chalk8.dim("No post-commit hook found."));
59989
60085
  return;
59990
60086
  }