@hasna/todos 0.11.71 → 0.11.72

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
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1011
1011
  this._exitCallback = (err) => {
1012
1012
  if (err.code !== "commander.executeSubCommandAsync") {
1013
1013
  throw err;
1014
- }
1014
+ } else {}
1015
1015
  };
1016
1016
  }
1017
1017
  return this;
@@ -7215,8 +7215,6 @@ function routeEnabledForTask(task, taskList) {
7215
7215
  const explicit = booleanField(task.metadata.route_enabled);
7216
7216
  if (explicit !== undefined)
7217
7217
  return explicit;
7218
- if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
7219
- return true;
7220
7218
  const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
7221
7219
  if (taskListDefault !== undefined)
7222
7220
  return taskListDefault;
@@ -7238,8 +7236,26 @@ function workflowPointersFromMetadata(metadata) {
7238
7236
  function compactWorkflowPointers(pointers) {
7239
7237
  return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
7240
7238
  }
7241
- function classifyProjectKind(path) {
7242
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
7239
+ function metadataStringField(record, keys) {
7240
+ if (!record)
7241
+ return;
7242
+ for (const key of keys) {
7243
+ const value = record[key];
7244
+ if (typeof value === "string" && value.trim())
7245
+ return value.trim();
7246
+ }
7247
+ return;
7248
+ }
7249
+ function projectKindFromMetadata(...records) {
7250
+ for (const record of records) {
7251
+ const value = metadataStringField(record ?? undefined, ["project_kind", "projectKind", "source_kind", "sourceKind"]);
7252
+ if (value)
7253
+ return value;
7254
+ }
7255
+ return null;
7256
+ }
7257
+ function classifyProjectKind(_path, metadata) {
7258
+ return projectKindFromMetadata(metadata);
7243
7259
  }
7244
7260
  function isWorktreePath(path) {
7245
7261
  return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
@@ -7312,7 +7328,6 @@ function taskEventMetadata(task) {
7312
7328
  metadata.project_canonical_path = projectPath;
7313
7329
  }
7314
7330
  if (projectPath) {
7315
- metadata.project_kind = classifyProjectKind(projectPath);
7316
7331
  metadata.project_is_worktree = isWorktreePath(projectPath);
7317
7332
  metadata.working_dir = task.working_dir ?? projectPath;
7318
7333
  }
@@ -7324,6 +7339,10 @@ function taskEventMetadata(task) {
7324
7339
  metadata.task_list_project_id = taskList.project_id;
7325
7340
  metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
7326
7341
  }
7342
+ const projectKind = projectKindFromMetadata(task.metadata, taskList?.metadata);
7343
+ if (projectKind) {
7344
+ metadata.project_kind = classifyProjectKind(projectPath ?? "", { project_kind: projectKind });
7345
+ }
7327
7346
  const routeEnabled = routeEnabledForTask(task, taskList);
7328
7347
  if (routeEnabled !== undefined) {
7329
7348
  metadata.route_enabled = routeEnabled;
@@ -12742,11 +12761,6 @@ __export(exports_task_routing, {
12742
12761
  setTaskWorkflowPointers: () => setTaskWorkflowPointers,
12743
12762
  getTaskRouteState: () => getTaskRouteState
12744
12763
  });
12745
- function classifyProjectKind2(path) {
12746
- if (!path)
12747
- return null;
12748
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
12749
- }
12750
12764
  function machineLocalPath(project, db) {
12751
12765
  const machineId = process.env["TODOS_MACHINE_ID"];
12752
12766
  if (!machineId)
@@ -12794,6 +12808,7 @@ function getTaskRouteState(taskOrId, db) {
12794
12808
  const automation = routingAutomationMetadata(task, taskList) ?? {};
12795
12809
  const routeEnabled = routeEnabledForTask(task, taskList) === true;
12796
12810
  const tagOptIn = task.tags.includes("auto:route") || task.tags.includes("route:enabled");
12811
+ const projectKind = projectKindFromMetadata(task.metadata, taskList?.metadata);
12797
12812
  const locked = Boolean(task.locked_by && !isLockExpired(task.locked_at));
12798
12813
  const blockers = getBlockingDeps(task.id, d);
12799
12814
  const blocked = blockers.length > 0;
@@ -12856,7 +12871,7 @@ function getTaskRouteState(taskOrId, db) {
12856
12871
  project_id: project?.id ?? task.project_id,
12857
12872
  project_path: projectPath,
12858
12873
  working_dir: task.working_dir ?? projectPath,
12859
- project_kind: classifyProjectKind2(projectPath),
12874
+ project_kind: projectKind,
12860
12875
  task_list_id: taskList?.id ?? task.task_list_id,
12861
12876
  task_list_slug: taskList?.slug ?? null,
12862
12877
  task_list_name: taskList?.name ?? null,
@@ -55219,6 +55234,339 @@ var init_config_serve_commands = __esm(() => {
55219
55234
  init_helpers();
55220
55235
  });
55221
55236
 
55237
+ // src/lib/task-route-sources.ts
55238
+ var exports_task_route_sources = {};
55239
+ __export(exports_task_route_sources, {
55240
+ discoverTaskRouteSources: () => discoverTaskRouteSources,
55241
+ TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION: () => TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION
55242
+ });
55243
+ import { Database as Database3 } from "bun:sqlite";
55244
+ import { createHash as createHash13 } from "crypto";
55245
+ import { existsSync as existsSync21, readdirSync as readdirSync5, statSync as statSync9 } from "fs";
55246
+ import { basename as basename9, dirname as dirname12, join as join21, resolve as resolve21 } from "path";
55247
+ function normalizePath5(input) {
55248
+ return resolve21(input);
55249
+ }
55250
+ function sourceStoreId(sourceDbPath) {
55251
+ const digest = createHash13("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
55252
+ return `sqlite:${digest}`;
55253
+ }
55254
+ function inferSourceRepoPath(sourceDbPath) {
55255
+ const normalized = normalizePath5(sourceDbPath);
55256
+ if (normalized.endsWith(TODO_STORE_RELATIVE_PATH)) {
55257
+ return dirname12(dirname12(dirname12(normalized)));
55258
+ }
55259
+ return dirname12(normalized);
55260
+ }
55261
+ function createStoreRef(sourceDbPath) {
55262
+ const normalized = normalizePath5(sourceDbPath);
55263
+ return {
55264
+ source_store_id: sourceStoreId(normalized),
55265
+ source_repo_path: inferSourceRepoPath(normalized),
55266
+ source_db_path: normalized
55267
+ };
55268
+ }
55269
+ function normalizePatterns(patterns) {
55270
+ return (patterns ?? []).map((pattern) => pattern.trim()).filter(Boolean);
55271
+ }
55272
+ function escapeRegExp(value) {
55273
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
55274
+ }
55275
+ function globPatternToRegExp(pattern) {
55276
+ let source3 = "";
55277
+ for (const char of pattern) {
55278
+ if (char === "*")
55279
+ source3 += ".*";
55280
+ else if (char === "?")
55281
+ source3 += ".";
55282
+ else
55283
+ source3 += escapeRegExp(char);
55284
+ }
55285
+ return new RegExp(`^${source3}$`);
55286
+ }
55287
+ function matchesPattern4(value, pattern) {
55288
+ const normalizedValue = value.replace(/\\/g, "/");
55289
+ const normalizedPattern = pattern.replace(/\\/g, "/");
55290
+ if (normalizedPattern.includes("*") || normalizedPattern.includes("?")) {
55291
+ return globPatternToRegExp(normalizedPattern).test(normalizedValue);
55292
+ }
55293
+ return normalizedValue.includes(normalizedPattern);
55294
+ }
55295
+ function storeMatchesAny(ref, patterns) {
55296
+ if (patterns.length === 0)
55297
+ return false;
55298
+ const paths = [ref.source_db_path, ref.source_repo_path].filter((value) => Boolean(value));
55299
+ const values = paths.flatMap((value) => [value, basename9(value)]);
55300
+ return patterns.some((pattern) => values.some((value) => matchesPattern4(value, pattern)));
55301
+ }
55302
+ function shouldIncludeStore(ref, include, exclude) {
55303
+ const included = include.length === 0 || storeMatchesAny(ref, include);
55304
+ return included && !storeMatchesAny(ref, exclude);
55305
+ }
55306
+ function discoverStoresUnderRoot(sourceRoot) {
55307
+ const rootPath = normalizePath5(sourceRoot);
55308
+ const errors2 = [];
55309
+ const stores = [];
55310
+ if (!existsSync21(rootPath)) {
55311
+ const ref = createStoreRef(join21(rootPath, TODO_STORE_RELATIVE_PATH));
55312
+ errors2.push({
55313
+ ...ref,
55314
+ code: "SOURCE_ROOT_MISSING",
55315
+ message: `Source root does not exist: ${rootPath}`
55316
+ });
55317
+ return { stores, errors: errors2 };
55318
+ }
55319
+ let rootStat;
55320
+ try {
55321
+ rootStat = statSync9(rootPath);
55322
+ } catch (error) {
55323
+ const ref = createStoreRef(join21(rootPath, TODO_STORE_RELATIVE_PATH));
55324
+ errors2.push({
55325
+ ...ref,
55326
+ code: "SOURCE_ROOT_UNREADABLE",
55327
+ message: error instanceof Error ? error.message : `Unable to read source root: ${rootPath}`
55328
+ });
55329
+ return { stores, errors: errors2 };
55330
+ }
55331
+ if (rootStat.isFile()) {
55332
+ stores.push(createStoreRef(rootPath));
55333
+ return { stores, errors: errors2 };
55334
+ }
55335
+ function scanDirectory(dir, depth) {
55336
+ const candidate = join21(dir, TODO_STORE_RELATIVE_PATH);
55337
+ if (existsSync21(candidate)) {
55338
+ stores.push(createStoreRef(candidate));
55339
+ }
55340
+ if (depth >= ROOT_SCAN_MAX_DEPTH)
55341
+ return;
55342
+ let entries;
55343
+ try {
55344
+ entries = readdirSync5(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
55345
+ } catch (error) {
55346
+ const ref = createStoreRef(candidate);
55347
+ errors2.push({
55348
+ ...ref,
55349
+ code: "SOURCE_ROOT_UNREADABLE",
55350
+ message: error instanceof Error ? error.message : `Unable to read source root: ${dir}`
55351
+ });
55352
+ return;
55353
+ }
55354
+ for (const entry of entries) {
55355
+ if (!entry.isDirectory() || SKIPPED_SCAN_DIRS.has(entry.name))
55356
+ continue;
55357
+ scanDirectory(join21(dir, entry.name), depth + 1);
55358
+ }
55359
+ }
55360
+ scanDirectory(rootPath, 0);
55361
+ return { stores, errors: errors2 };
55362
+ }
55363
+ function collectStoreRefs(input) {
55364
+ const byPath = new Map;
55365
+ const errors2 = [];
55366
+ for (const storePath of input.sourceStores ?? []) {
55367
+ const ref = createStoreRef(storePath);
55368
+ byPath.set(ref.source_db_path, ref);
55369
+ }
55370
+ for (const sourceRoot of input.sourceRoots ?? []) {
55371
+ const discovered = discoverStoresUnderRoot(sourceRoot);
55372
+ for (const ref of discovered.stores) {
55373
+ byPath.set(ref.source_db_path, ref);
55374
+ }
55375
+ errors2.push(...discovered.errors);
55376
+ }
55377
+ return {
55378
+ stores: [...byPath.values()].sort((a, b) => a.source_db_path.localeCompare(b.source_db_path)),
55379
+ errors: errors2.sort((a, b) => a.source_db_path.localeCompare(b.source_db_path))
55380
+ };
55381
+ }
55382
+ function openReadonlyStore(ref) {
55383
+ if (!existsSync21(ref.source_db_path)) {
55384
+ throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
55385
+ }
55386
+ return new Database3(ref.source_db_path, { readonly: true, create: false });
55387
+ }
55388
+ function hasTable2(db, tableName) {
55389
+ const row = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName);
55390
+ return Boolean(row);
55391
+ }
55392
+ function tableColumns2(db, tableName) {
55393
+ const rows = db.query(`PRAGMA table_info(${tableName})`).all();
55394
+ return new Set(rows.map((row) => row.name));
55395
+ }
55396
+ function listPendingTasksReadonly(db) {
55397
+ if (!hasTable2(db, "tasks")) {
55398
+ throw Object.assign(new Error("Store does not contain a tasks table"), { code: "STORE_INVALID" });
55399
+ }
55400
+ const columns = tableColumns2(db, "tasks");
55401
+ const conditions = ["status = 'pending'"];
55402
+ if (columns.has("archived_at"))
55403
+ conditions.push("archived_at IS NULL");
55404
+ const rows = db.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")}
55405
+ ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END, created_at DESC`).all();
55406
+ return rows.map(rowToTask);
55407
+ }
55408
+ function isReadyTask(task2, db) {
55409
+ if (task2.locked_by && !isLockExpired(task2.locked_at))
55410
+ return false;
55411
+ return getBlockingDeps(task2.id, db).length === 0;
55412
+ }
55413
+ function metadataFingerprint(metadata) {
55414
+ const value = metadata.fingerprint;
55415
+ if (typeof value === "string" && value.trim())
55416
+ return value;
55417
+ if (typeof value === "number" && Number.isFinite(value))
55418
+ return String(value);
55419
+ return null;
55420
+ }
55421
+ function boundedMetadataValue(value, depth = 0) {
55422
+ if (depth > 6)
55423
+ return "[TRUNCATED]";
55424
+ if (typeof value === "string") {
55425
+ return value.length > 2000 ? `${value.slice(0, 2000)}[TRUNCATED]` : value;
55426
+ }
55427
+ if (Array.isArray(value)) {
55428
+ return value.slice(0, 50).map((item) => boundedMetadataValue(item, depth + 1));
55429
+ }
55430
+ if (value && typeof value === "object") {
55431
+ const result = {};
55432
+ for (const [key, child] of Object.entries(value).slice(0, 80)) {
55433
+ const normalized = key.toLowerCase();
55434
+ if (normalized === "comment" || normalized === "comments" || normalized === "task_comments") {
55435
+ result[key] = "[REDACTED_COMMENT]";
55436
+ continue;
55437
+ }
55438
+ result[key] = boundedMetadataValue(child, depth + 1);
55439
+ }
55440
+ return result;
55441
+ }
55442
+ return value;
55443
+ }
55444
+ function discoveryMetadata(metadata) {
55445
+ return redactValue(boundedMetadataValue(metadata));
55446
+ }
55447
+ function sourceCandidate(ref, task2, db) {
55448
+ const routeState = getTaskRouteState(task2, db);
55449
+ const autoRoute = task2.tags.includes("auto:route") || task2.tags.includes("route:enabled");
55450
+ return {
55451
+ source_store_id: ref.source_store_id,
55452
+ source_repo_path: ref.source_repo_path,
55453
+ source_db_path: ref.source_db_path,
55454
+ source_task_key: `${ref.source_store_id}:${task2.id}`,
55455
+ source_selected_by_input: true,
55456
+ task_id: task2.id,
55457
+ task_short_id: task2.short_id,
55458
+ title: task2.title,
55459
+ status: task2.status,
55460
+ priority: task2.priority,
55461
+ project_path: routeState.route.project_path ?? task2.working_dir ?? ref.source_repo_path,
55462
+ task_version: task2.version,
55463
+ task_updated_at: task2.updated_at,
55464
+ task_fingerprint: metadataFingerprint(task2.metadata),
55465
+ tags: task2.tags,
55466
+ task_intent: {
55467
+ auto_route: autoRoute
55468
+ },
55469
+ metadata: discoveryMetadata(task2.metadata),
55470
+ route_state: routeState
55471
+ };
55472
+ }
55473
+ function discoveryError(ref, code, error) {
55474
+ return {
55475
+ ...ref,
55476
+ code,
55477
+ message: error instanceof Error ? error.message : String(error)
55478
+ };
55479
+ }
55480
+ function errorCode(error) {
55481
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "STORE_MISSING") {
55482
+ return "STORE_MISSING";
55483
+ }
55484
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "STORE_INVALID") {
55485
+ return "STORE_INVALID";
55486
+ }
55487
+ return "STORE_UNREADABLE";
55488
+ }
55489
+ function discoverTaskRouteSources(input) {
55490
+ const include = normalizePatterns(input.include);
55491
+ const exclude = normalizePatterns(input.exclude);
55492
+ const sourceRoots = (input.sourceRoots ?? []).map(normalizePath5).sort();
55493
+ const sourceStores = (input.sourceStores ?? []).map(normalizePath5).sort();
55494
+ const limit = Number.isFinite(input.limit ?? NaN) && (input.limit ?? 0) >= 0 ? Math.floor(input.limit ?? 0) : null;
55495
+ const collected = collectStoreRefs(input);
55496
+ const stores = [];
55497
+ const errors2 = [...collected.errors];
55498
+ const candidates = [];
55499
+ let totalCandidateCount = 0;
55500
+ for (const ref of collected.stores) {
55501
+ if (!shouldIncludeStore(ref, include, exclude))
55502
+ continue;
55503
+ const storeErrors = [];
55504
+ let db = null;
55505
+ try {
55506
+ db = openReadonlyStore(ref);
55507
+ const readyTasks = listPendingTasksReadonly(db).filter((task2) => isReadyTask(task2, db));
55508
+ totalCandidateCount += readyTasks.length;
55509
+ const remaining = limit === null ? readyTasks.length : Math.max(0, limit - candidates.length);
55510
+ const selectedTasks = limit === null ? readyTasks : readyTasks.slice(0, remaining);
55511
+ candidates.push(...selectedTasks.map((task2) => sourceCandidate(ref, task2, db)));
55512
+ stores.push({
55513
+ ...ref,
55514
+ status: "ok",
55515
+ candidate_count: readyTasks.length,
55516
+ returned_candidate_count: selectedTasks.length,
55517
+ errors: []
55518
+ });
55519
+ } catch (error) {
55520
+ const storeError = discoveryError(ref, errorCode(error), error);
55521
+ storeErrors.push(storeError);
55522
+ errors2.push(storeError);
55523
+ stores.push({
55524
+ ...ref,
55525
+ status: storeError.code === "STORE_MISSING" ? "missing" : "error",
55526
+ candidate_count: 0,
55527
+ returned_candidate_count: 0,
55528
+ errors: storeErrors
55529
+ });
55530
+ } finally {
55531
+ db?.close();
55532
+ }
55533
+ }
55534
+ return {
55535
+ schema_version: TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION,
55536
+ sourceRoots,
55537
+ sourceStores,
55538
+ include,
55539
+ exclude,
55540
+ limit,
55541
+ total_candidate_count: totalCandidateCount,
55542
+ returned_candidate_count: candidates.length,
55543
+ truncated: limit !== null && totalCandidateCount > candidates.length,
55544
+ stores,
55545
+ candidates,
55546
+ errors: errors2
55547
+ };
55548
+ }
55549
+ var TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION = "todos.task_route_sources.v1", TODO_STORE_RELATIVE_PATH, ROOT_SCAN_MAX_DEPTH = 5, SKIPPED_SCAN_DIRS;
55550
+ var init_task_route_sources = __esm(() => {
55551
+ init_database();
55552
+ init_task_lifecycle();
55553
+ init_task_crud();
55554
+ init_redaction();
55555
+ init_task_routing();
55556
+ TODO_STORE_RELATIVE_PATH = join21(".hasna", "todos", "todos.db");
55557
+ SKIPPED_SCAN_DIRS = new Set([
55558
+ ".git",
55559
+ ".hg",
55560
+ ".svn",
55561
+ "node_modules",
55562
+ "dist",
55563
+ "build",
55564
+ ".next",
55565
+ ".turbo",
55566
+ ".cache"
55567
+ ]);
55568
+ });
55569
+
55222
55570
  // src/lib/tester-issue-reports.ts
55223
55571
  var exports_tester_issue_reports = {};
55224
55572
  __export(exports_tester_issue_reports, {
@@ -55231,7 +55579,7 @@ __export(exports_tester_issue_reports, {
55231
55579
  TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION: () => TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
55232
55580
  TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION: () => TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION
55233
55581
  });
55234
- import { createHash as createHash13 } from "crypto";
55582
+ import { createHash as createHash14 } from "crypto";
55235
55583
  function asObject3(value) {
55236
55584
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
55237
55585
  }
@@ -55403,7 +55751,7 @@ function fingerprintTesterIssueReport(report) {
55403
55751
  normalizeText4(report.failure?.message || report.summary || report.title).slice(0, 240),
55404
55752
  normalizeText4(stackTop).slice(0, 160)
55405
55753
  ].join("::");
55406
- return `testers:${createHash13("sha256").update(raw).digest("hex").slice(0, 16)}`;
55754
+ return `testers:${createHash14("sha256").update(raw).digest("hex").slice(0, 16)}`;
55407
55755
  }
55408
55756
  function priorityForSeverity(severity, fallback) {
55409
55757
  return PRIORITIES5.includes(severity) ? severity : fallback;
@@ -55722,6 +56070,15 @@ function parseCsvOption(value) {
55722
56070
  const values = value.split(",").map((item) => item.trim()).filter(Boolean);
55723
56071
  return values.length > 0 ? values : undefined;
55724
56072
  }
56073
+ function collectOption2(value, previous = []) {
56074
+ return [...previous, value];
56075
+ }
56076
+ function expandRepeatedCsvOption(value) {
56077
+ if (!value || value.length === 0)
56078
+ return;
56079
+ const values = value.flatMap((item) => item.split(",").map((part) => part.trim()).filter(Boolean));
56080
+ return values.length > 0 ? values : undefined;
56081
+ }
55725
56082
  function resolveOptionalId(table, value) {
55726
56083
  if (!value)
55727
56084
  return;
@@ -56360,13 +56717,13 @@ Repairs`));
56360
56717
  try {
56361
56718
  const db = getDatabase();
56362
56719
  const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
56363
- const { statSync: statSync9 } = await import("fs");
56364
- const { join: join21 } = await import("path");
56720
+ const { statSync: statSync10 } = await import("fs");
56721
+ const { join: join22 } = await import("path");
56365
56722
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
56366
- const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join21(home, ".hasna", "todos", "todos.db");
56723
+ const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join22(home, ".hasna", "todos", "todos.db");
56367
56724
  let size = "unknown";
56368
56725
  try {
56369
- size = `${(statSync9(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
56726
+ size = `${(statSync10(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
56370
56727
  } catch {}
56371
56728
  checks.push({ name: "Database", ok: true, message: `${row.count} tasks \xB7 ${size} \xB7 ${chalk7.dim(dbPath)}` });
56372
56729
  } catch (e) {
@@ -56856,8 +57213,42 @@ Repairs`));
56856
57213
  console.log(` ${chalk7.dim(time2)} ${chalk7.cyan(entry.source)} ${chalk7.dim(ref)} ${entry.event_type}${message}${agent}`);
56857
57214
  }
56858
57215
  });
56859
- program2.command("ready").description("Show all tasks ready to be claimed (pending, unblocked, unlocked)").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").option("--limit <n>", "Max tasks to show", "20").action(async (opts) => {
57216
+ program2.command("ready").description("Show all tasks ready to be claimed (pending, unblocked, unlocked)").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").option("--limit <n>", "Max tasks to show", "20").option("--source-root <path>", "Read-only source root to scan for .hasna/todos/todos.db (repeatable)", collectOption2, []).option("--source-store <path>", "Read-only todos SQLite store path to scan (repeatable)", collectOption2, []).option("--include <pattern>", "Include source repo/store paths matching substring or glob (repeatable or comma-separated)", collectOption2, []).option("--exclude <pattern>", "Exclude source repo/store paths matching substring or glob (repeatable or comma-separated)", collectOption2, []).action(async (opts) => {
56860
57217
  const globalOpts = program2.opts();
57218
+ const sourceRoots = expandRepeatedCsvOption(opts.sourceRoot);
57219
+ const sourceStores = expandRepeatedCsvOption(opts.sourceStore);
57220
+ const include = expandRepeatedCsvOption(opts.include);
57221
+ const exclude = expandRepeatedCsvOption(opts.exclude);
57222
+ if (sourceRoots || sourceStores || include || exclude) {
57223
+ const { discoverTaskRouteSources: discoverTaskRouteSources2 } = await Promise.resolve().then(() => (init_task_route_sources(), exports_task_route_sources));
57224
+ const result = discoverTaskRouteSources2({
57225
+ sourceRoots,
57226
+ sourceStores,
57227
+ include,
57228
+ exclude,
57229
+ limit: parseInt(opts.limit, 10)
57230
+ });
57231
+ if (opts.json || globalOpts.json) {
57232
+ console.log(JSON.stringify(result));
57233
+ return;
57234
+ }
57235
+ if (result.candidates.length === 0) {
57236
+ console.log(chalk7.dim(" No source tasks ready to claim."));
57237
+ } else {
57238
+ console.log(chalk7.bold(`Ready source tasks (${result.candidates.length}):
57239
+ `));
57240
+ for (const candidate of result.candidates) {
57241
+ const source3 = candidate.source_repo_path ?? candidate.source_db_path;
57242
+ const pri = candidate.priority === "critical" ? chalk7.bgRed.white(" CRIT ") : candidate.priority === "high" ? chalk7.red("[high]") : candidate.priority === "medium" ? chalk7.yellow("[med]") : "";
57243
+ console.log(` ${chalk7.cyan(candidate.task_short_id || candidate.task_id.slice(0, 8))} ${candidate.title} ${pri}${chalk7.dim(` ${source3}`)}`);
57244
+ }
57245
+ }
57246
+ if (result.errors.length > 0) {
57247
+ console.log(chalk7.yellow(`
57248
+ ${result.errors.length} source error${result.errors.length === 1 ? "" : "s"} isolated; rerun with --json for details.`));
57249
+ }
57250
+ return;
57251
+ }
56861
57252
  const db = getDatabase();
56862
57253
  const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
56863
57254
  const { isLockExpired: isLockExpired2 } = await Promise.resolve().then(() => (init_database(), exports_database));
@@ -58179,21 +58570,21 @@ __export(exports_mcp_hooks_commands, {
58179
58570
  });
58180
58571
  import chalk8 from "chalk";
58181
58572
  import { execSync as execSync3 } from "child_process";
58182
- import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11, chmodSync as chmodSync2 } from "fs";
58183
- import { dirname as dirname12, join as join21 } from "path";
58573
+ import { existsSync as existsSync22, readFileSync as readFileSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11, chmodSync as chmodSync2 } from "fs";
58574
+ import { dirname as dirname13, join as join22 } from "path";
58184
58575
  function getMcpBinaryPath() {
58185
58576
  try {
58186
58577
  const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
58187
58578
  if (p)
58188
58579
  return p;
58189
58580
  } catch {}
58190
- const bunBin = join21(HOME2, ".bun", "bin", "todos-mcp");
58191
- if (existsSync21(bunBin))
58581
+ const bunBin = join22(HOME2, ".bun", "bin", "todos-mcp");
58582
+ if (existsSync22(bunBin))
58192
58583
  return bunBin;
58193
58584
  return "todos-mcp";
58194
58585
  }
58195
58586
  function readJsonFile2(path) {
58196
- if (!existsSync21(path))
58587
+ if (!existsSync22(path))
58197
58588
  return {};
58198
58589
  try {
58199
58590
  return JSON.parse(readFileSync17(path, "utf-8"));
@@ -58202,20 +58593,20 @@ function readJsonFile2(path) {
58202
58593
  }
58203
58594
  }
58204
58595
  function writeJsonFile2(path, data) {
58205
- const dir = dirname12(path);
58206
- if (!existsSync21(dir))
58596
+ const dir = dirname13(path);
58597
+ if (!existsSync22(dir))
58207
58598
  mkdirSync11(dir, { recursive: true });
58208
58599
  writeFileSync10(path, JSON.stringify(data, null, 2) + `
58209
58600
  `);
58210
58601
  }
58211
58602
  function readTomlFile(path) {
58212
- if (!existsSync21(path))
58603
+ if (!existsSync22(path))
58213
58604
  return "";
58214
58605
  return readFileSync17(path, "utf-8");
58215
58606
  }
58216
58607
  function writeTomlFile(path, content) {
58217
- const dir = dirname12(path);
58218
- if (!existsSync21(dir))
58608
+ const dir = dirname13(path);
58609
+ if (!existsSync22(dir))
58219
58610
  mkdirSync11(dir, { recursive: true });
58220
58611
  writeFileSync10(path, content);
58221
58612
  }
@@ -58281,7 +58672,7 @@ function unregisterClaude(_global) {
58281
58672
  }
58282
58673
  }
58283
58674
  function registerCodex(binPath) {
58284
- const configPath = join21(HOME2, ".codex", "config.toml");
58675
+ const configPath = join22(HOME2, ".codex", "config.toml");
58285
58676
  let content = readTomlFile(configPath);
58286
58677
  content = removeTomlBlock(content, "mcp_servers.todos");
58287
58678
  const block = `
@@ -58295,7 +58686,7 @@ args = []
58295
58686
  console.log(chalk8.green(`Codex CLI: registered in ${configPath}`));
58296
58687
  }
58297
58688
  function unregisterCodex() {
58298
- const configPath = join21(HOME2, ".codex", "config.toml");
58689
+ const configPath = join22(HOME2, ".codex", "config.toml");
58299
58690
  let content = readTomlFile(configPath);
58300
58691
  if (!content.includes("[mcp_servers.todos]")) {
58301
58692
  console.log(chalk8.dim(`Codex CLI: todos not found in ${configPath}`));
@@ -58307,7 +58698,7 @@ function unregisterCodex() {
58307
58698
  console.log(chalk8.green(`Codex CLI: unregistered from ${configPath}`));
58308
58699
  }
58309
58700
  function registerGemini(binPath) {
58310
- const configPath = join21(HOME2, ".gemini", "settings.json");
58701
+ const configPath = join22(HOME2, ".gemini", "settings.json");
58311
58702
  const config = readJsonFile2(configPath);
58312
58703
  if (!config["mcpServers"]) {
58313
58704
  config["mcpServers"] = {};
@@ -58321,7 +58712,7 @@ function registerGemini(binPath) {
58321
58712
  console.log(chalk8.green(`Gemini CLI: registered in ${configPath}`));
58322
58713
  }
58323
58714
  function unregisterGemini() {
58324
- const configPath = join21(HOME2, ".gemini", "settings.json");
58715
+ const configPath = join22(HOME2, ".gemini", "settings.json");
58325
58716
  const config = readJsonFile2(configPath);
58326
58717
  const servers = config["mcpServers"];
58327
58718
  if (!servers || !("todos" in servers)) {
@@ -58378,8 +58769,8 @@ function registerMcpHooksCommands(program2) {
58378
58769
  if (p)
58379
58770
  todosBin = p;
58380
58771
  } catch {}
58381
- const hooksDir = join21(process.cwd(), ".claude", "hooks");
58382
- if (!existsSync21(hooksDir))
58772
+ const hooksDir = join22(process.cwd(), ".claude", "hooks");
58773
+ if (!existsSync22(hooksDir))
58383
58774
  mkdirSync11(hooksDir, { recursive: true });
58384
58775
  const hookScript = `#!/usr/bin/env bash
58385
58776
  # Auto-generated by: todos hooks install
@@ -58404,11 +58795,11 @@ esac
58404
58795
 
58405
58796
  exit 0
58406
58797
  `;
58407
- const hookPath = join21(hooksDir, "todos-sync.sh");
58798
+ const hookPath = join22(hooksDir, "todos-sync.sh");
58408
58799
  writeFileSync10(hookPath, hookScript);
58409
58800
  execSync3(`chmod +x "${hookPath}"`);
58410
58801
  console.log(chalk8.green(`Hook script created: ${hookPath}`));
58411
- const settingsPath = join21(process.cwd(), ".claude", "settings.json");
58802
+ const settingsPath = join22(process.cwd(), ".claude", "settings.json");
58412
58803
  const settings = readJsonFile2(settingsPath);
58413
58804
  if (!settings["hooks"]) {
58414
58805
  settings["hooks"] = {};
@@ -59277,7 +59668,7 @@ Artifacts:`));
59277
59668
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
59278
59669
  const hookPath = `${gitDir}/hooks/post-commit`;
59279
59670
  const marker = "# todos-auto-link";
59280
- if (existsSync21(hookPath)) {
59671
+ if (existsSync22(hookPath)) {
59281
59672
  const existing = readFileSync17(hookPath, "utf-8");
59282
59673
  if (existing.includes(marker)) {
59283
59674
  console.log(chalk8.yellow("Hook already installed."));
@@ -59305,7 +59696,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
59305
59696
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
59306
59697
  const hookPath = `${gitDir}/hooks/post-commit`;
59307
59698
  const marker = "# todos-auto-link";
59308
- if (!existsSync21(hookPath)) {
59699
+ if (!existsSync22(hookPath)) {
59309
59700
  console.log(chalk8.dim("No post-commit hook found."));
59310
59701
  return;
59311
59702
  }
@@ -59491,7 +59882,7 @@ import chalk10 from "chalk";
59491
59882
  import { execSync as execSync4 } from "child_process";
59492
59883
  import { readFileSync as readFileSync18, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
59493
59884
  import { tmpdir as tmpdir4 } from "os";
59494
- import { join as join22 } from "path";
59885
+ import { join as join23 } from "path";
59495
59886
  function getOrCreateLocalMachineName() {
59496
59887
  return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
59497
59888
  }
@@ -59529,7 +59920,7 @@ function remoteTempPath(sshAddress) {
59529
59920
  }
59530
59921
  function readRemoteBridgeBundle(sshAddress) {
59531
59922
  const remotePath = remoteTempPath(sshAddress);
59532
- const localPath = join22(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
59923
+ const localPath = join23(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
59533
59924
  try {
59534
59925
  runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
59535
59926
  scpFromRemote(sshAddress, remotePath, localPath);
@@ -59544,7 +59935,7 @@ function readRemoteBridgeBundle(sshAddress) {
59544
59935
  }
59545
59936
  }
59546
59937
  function writeLocalBridgeBundle() {
59547
- const localPath = join22(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
59938
+ const localPath = join23(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
59548
59939
  writeFileSync11(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
59549
59940
  return localPath;
59550
59941
  }
@@ -60608,7 +60999,7 @@ __export(exports_onboarding_commands, {
60608
60999
  registerOnboardingCommands: () => registerOnboardingCommands
60609
61000
  });
60610
61001
  import chalk17 from "chalk";
60611
- import { resolve as resolve21 } from "path";
61002
+ import { resolve as resolve22 } from "path";
60612
61003
  function registerOnboardingCommands(program2) {
60613
61004
  program2.command("onboarding").alias("demo-fixtures").description("List, show, write, or import bundled local onboarding fixtures").option("--show <name>", "Show one fixture bridge bundle as JSON").option("--write <dir>", "Write all bundled fixture bridge bundles to a directory").option("--import <name>", "Dry-run or apply an onboarding fixture import").option("--apply", "Apply an onboarding fixture import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge existing local tasks while preserving divergent fields").action(async (opts) => {
60614
61005
  const globalOpts = program2.opts();
@@ -60624,7 +61015,7 @@ function registerOnboardingCommands(program2) {
60624
61015
  return;
60625
61016
  }
60626
61017
  if (opts.write) {
60627
- const result = writeOnboardingFixtureFiles2(resolve21(opts.write));
61018
+ const result = writeOnboardingFixtureFiles2(resolve22(opts.write));
60628
61019
  if (globalOpts.json) {
60629
61020
  output(result, true);
60630
61021
  return;
@@ -64073,7 +64464,7 @@ __export(exports_sdk_integration_fixtures, {
64073
64464
  TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
64074
64465
  });
64075
64466
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
64076
- import { join as join23 } from "path";
64467
+ import { join as join24 } from "path";
64077
64468
  function source5(version) {
64078
64469
  return {
64079
64470
  packageName: "@hasna/todos",
@@ -64180,7 +64571,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
64180
64571
  ];
64181
64572
  const written = [];
64182
64573
  for (const [name, payload] of files) {
64183
- const file = join23(directory, name);
64574
+ const file = join24(directory, name);
64184
64575
  writeFileSync12(file, `${JSON.stringify(payload, null, 2)}
64185
64576
  `, "utf-8");
64186
64577
  written.push(file);
@@ -64204,7 +64595,7 @@ __export(exports_sdk_fixture_commands, {
64204
64595
  registerSdkFixtureCommands: () => registerSdkFixtureCommands
64205
64596
  });
64206
64597
  import chalk19 from "chalk";
64207
- import { resolve as resolve22 } from "path";
64598
+ import { resolve as resolve23 } from "path";
64208
64599
  function registerSdkFixtureCommands(program2) {
64209
64600
  program2.command("sdk-fixtures").description("List, show, or write local SDK integration fixtures").option("--show", "Print the full fixture pack JSON").option("--write <dir>", "Write fixture pack, bridge fixture, contract snapshots, and example index to a directory").action(async (opts) => {
64210
64601
  const globalOpts = program2.opts();
@@ -64215,7 +64606,7 @@ function registerSdkFixtureCommands(program2) {
64215
64606
  writeSdkIntegrationFixtures: writeSdkIntegrationFixtures2
64216
64607
  } = await Promise.resolve().then(() => (init_sdk_integration_fixtures(), exports_sdk_integration_fixtures));
64217
64608
  if (opts.write) {
64218
- const result = writeSdkIntegrationFixtures2(resolve22(opts.write));
64609
+ const result = writeSdkIntegrationFixtures2(resolve23(opts.write));
64219
64610
  if (globalOpts.json) {
64220
64611
  console.log(JSON.stringify(result));
64221
64612
  return;
@@ -65037,7 +65428,7 @@ __export(exports_local_backup_commands, {
65037
65428
  registerLocalBackupCommands: () => registerLocalBackupCommands
65038
65429
  });
65039
65430
  import chalk26 from "chalk";
65040
- import { resolve as resolve23 } from "path";
65431
+ import { resolve as resolve24 } from "path";
65041
65432
  function globalOptions6(program2) {
65042
65433
  const command = program2;
65043
65434
  return command.optsWithGlobals?.() ?? program2.opts();
@@ -65059,10 +65450,10 @@ function registerLocalBackupCommands(program2) {
65059
65450
  const projectId = opts.projectId ?? autoProject(globalOpts);
65060
65451
  const backupBundle = createLocalBackup2({
65061
65452
  project_id: projectId,
65062
- output_path: opts.output ? resolve23(opts.output) : undefined
65453
+ output_path: opts.output ? resolve24(opts.output) : undefined
65063
65454
  });
65064
65455
  const result = {
65065
- output_path: opts.output ? resolve23(opts.output) : null,
65456
+ output_path: opts.output ? resolve24(opts.output) : null,
65066
65457
  backup: backupBundle
65067
65458
  };
65068
65459
  if (opts.json || globalOpts.json) {
@@ -66866,7 +67257,7 @@ var init_factory = __esm(() => {
66866
67257
  });
66867
67258
 
66868
67259
  // src/storage/s3-artifacts.ts
66869
- import { createHash as createHash14, createHmac as createHmac2 } from "crypto";
67260
+ import { createHash as createHash15, createHmac as createHmac2 } from "crypto";
66870
67261
  function createTodosS3ArtifactStore(options) {
66871
67262
  const requestFetch = options.fetch ?? fetch;
66872
67263
  const now4 = options.now ?? (() => new Date);
@@ -67038,7 +67429,7 @@ function toAmzDate(date) {
67038
67429
  return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
67039
67430
  }
67040
67431
  function sha256Hex(value) {
67041
- return createHash14("sha256").update(value).digest("hex");
67432
+ return createHash15("sha256").update(value).digest("hex");
67042
67433
  }
67043
67434
  function hmac(key, value) {
67044
67435
  return createHmac2("sha256", key).update(value).digest();