@hasna/todos 0.13.0 → 0.13.2

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
@@ -2123,7 +2123,7 @@ var package_default;
2123
2123
  var init_package = __esm(() => {
2124
2124
  package_default = {
2125
2125
  name: "@hasna/todos",
2126
- version: "0.13.0",
2126
+ version: "0.13.2",
2127
2127
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
2128
2128
  type: "module",
2129
2129
  main: "dist/index.js",
@@ -2175,7 +2175,7 @@ var init_package = __esm(() => {
2175
2175
  "backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
2176
2176
  "generate:sdk": "bun run scripts/generate-sdk.ts",
2177
2177
  "build:dashboard": "cd dashboard && bun install --frozen-lockfile && bun run build",
2178
- typecheck: "tsc --noEmit",
2178
+ typecheck: "tsc --noEmit -p tsconfig.typecheck.json",
2179
2179
  test: "bun test",
2180
2180
  "test:no-cloud": "bun test src/no-cloud-boundary.test.ts src/local-first.test.ts src/lib/public-release-gate.test.ts",
2181
2181
  "dev:cli": "bun run src/cli/index.tsx",
@@ -2250,6 +2250,161 @@ var init_package_version = __esm(() => {
2250
2250
  init_package();
2251
2251
  });
2252
2252
 
2253
+ // src/types/index.ts
2254
+ function isBlockingDependencyStatus(status) {
2255
+ return status !== "completed" && status !== "cancelled";
2256
+ }
2257
+ var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
2258
+ var init_types = __esm(() => {
2259
+ TASK_STATUSES = [
2260
+ "pending",
2261
+ "in_progress",
2262
+ "completed",
2263
+ "failed",
2264
+ "cancelled"
2265
+ ];
2266
+ TASK_PRIORITIES = [
2267
+ "low",
2268
+ "medium",
2269
+ "high",
2270
+ "critical"
2271
+ ];
2272
+ VersionConflictError = class VersionConflictError extends Error {
2273
+ taskId;
2274
+ expectedVersion;
2275
+ actualVersion;
2276
+ static code = "VERSION_CONFLICT";
2277
+ static suggestion = "Fetch the task with get_task to get the current version before updating.";
2278
+ constructor(taskId, expectedVersion, actualVersion) {
2279
+ super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
2280
+ this.taskId = taskId;
2281
+ this.expectedVersion = expectedVersion;
2282
+ this.actualVersion = actualVersion;
2283
+ this.name = "VersionConflictError";
2284
+ }
2285
+ };
2286
+ TaskNotFoundError = class TaskNotFoundError extends Error {
2287
+ taskId;
2288
+ static code = "TASK_NOT_FOUND";
2289
+ static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
2290
+ constructor(taskId) {
2291
+ super(`Task not found: ${taskId}`);
2292
+ this.taskId = taskId;
2293
+ this.name = "TaskNotFoundError";
2294
+ }
2295
+ };
2296
+ ProjectNotFoundError = class ProjectNotFoundError extends Error {
2297
+ projectId;
2298
+ static code = "PROJECT_NOT_FOUND";
2299
+ static suggestion = "Use list_projects to see available projects.";
2300
+ constructor(projectId) {
2301
+ super(`Project not found: ${projectId}`);
2302
+ this.projectId = projectId;
2303
+ this.name = "ProjectNotFoundError";
2304
+ }
2305
+ };
2306
+ ResourceConflictError = class ResourceConflictError extends Error {
2307
+ code;
2308
+ constructor(code, message) {
2309
+ super(message);
2310
+ this.code = code;
2311
+ this.name = "ResourceConflictError";
2312
+ }
2313
+ };
2314
+ PlanNotFoundError = class PlanNotFoundError extends Error {
2315
+ planId;
2316
+ static code = "PLAN_NOT_FOUND";
2317
+ static suggestion = "Use list_plans to see available plans.";
2318
+ constructor(planId) {
2319
+ super(`Plan not found: ${planId}`);
2320
+ this.planId = planId;
2321
+ this.name = "PlanNotFoundError";
2322
+ }
2323
+ };
2324
+ LockError = class LockError extends Error {
2325
+ taskId;
2326
+ lockedBy;
2327
+ static code = "LOCK_ERROR";
2328
+ static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
2329
+ constructor(taskId, lockedBy) {
2330
+ super(`Task ${taskId} is locked by ${lockedBy}`);
2331
+ this.taskId = taskId;
2332
+ this.lockedBy = lockedBy;
2333
+ this.name = "LockError";
2334
+ }
2335
+ };
2336
+ AgentNotFoundError = class AgentNotFoundError extends Error {
2337
+ agentId;
2338
+ static code = "AGENT_NOT_FOUND";
2339
+ static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
2340
+ constructor(agentId) {
2341
+ super(`Agent not found: ${agentId}`);
2342
+ this.agentId = agentId;
2343
+ this.name = "AgentNotFoundError";
2344
+ }
2345
+ };
2346
+ IdentityAliasAmbiguousError = class IdentityAliasAmbiguousError extends Error {
2347
+ code = "IDENTITY_ALIAS_AMBIGUOUS";
2348
+ candidates;
2349
+ constructor(subject, candidates = []) {
2350
+ super(`Identity alias or source is ambiguous: ${subject}`);
2351
+ this.name = "IdentityAliasAmbiguousError";
2352
+ this.candidates = [...new Set(candidates)].sort();
2353
+ }
2354
+ };
2355
+ IdentityIdImmutableError = class IdentityIdImmutableError extends Error {
2356
+ code = "IDENTITY_ID_IMMUTABLE";
2357
+ constructor(agentId) {
2358
+ super(`IDENTITY_ID_IMMUTABLE: canonical identity for agent ${agentId} cannot be replaced`);
2359
+ this.name = "IdentityIdImmutableError";
2360
+ }
2361
+ };
2362
+ TaskListNotFoundError = class TaskListNotFoundError extends Error {
2363
+ taskListId;
2364
+ static code = "TASK_LIST_NOT_FOUND";
2365
+ static suggestion = "Use list_task_lists to see available lists.";
2366
+ constructor(taskListId) {
2367
+ super(`Task list not found: ${taskListId}`);
2368
+ this.taskListId = taskListId;
2369
+ this.name = "TaskListNotFoundError";
2370
+ }
2371
+ };
2372
+ DependencyCycleError = class DependencyCycleError extends Error {
2373
+ taskId;
2374
+ dependsOn;
2375
+ static code = "DEPENDENCY_CYCLE";
2376
+ static suggestion = "Check the dependency chain with get_task to avoid circular references.";
2377
+ constructor(taskId, dependsOn) {
2378
+ super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
2379
+ this.taskId = taskId;
2380
+ this.dependsOn = dependsOn;
2381
+ this.name = "DependencyCycleError";
2382
+ }
2383
+ };
2384
+ CompletionGuardError = class CompletionGuardError extends Error {
2385
+ reason;
2386
+ retryAfterSeconds;
2387
+ static code = "COMPLETION_BLOCKED";
2388
+ static suggestion = "Wait for the cooldown period, then retry.";
2389
+ constructor(reason, retryAfterSeconds) {
2390
+ super(reason);
2391
+ this.reason = reason;
2392
+ this.retryAfterSeconds = retryAfterSeconds;
2393
+ this.name = "CompletionGuardError";
2394
+ }
2395
+ };
2396
+ DispatchNotFoundError = class DispatchNotFoundError extends Error {
2397
+ dispatchId;
2398
+ static code = "DISPATCH_NOT_FOUND";
2399
+ static suggestion = "Check the dispatch ID with list_dispatches.";
2400
+ constructor(dispatchId) {
2401
+ super(`Dispatch not found: ${dispatchId}`);
2402
+ this.dispatchId = dispatchId;
2403
+ this.name = "DispatchNotFoundError";
2404
+ }
2405
+ };
2406
+ });
2407
+
2253
2408
  // src/lib/sync-utils.ts
2254
2409
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
2255
2410
  import { join } from "path";
@@ -2472,11 +2627,15 @@ function secretPatterns() {
2472
2627
  return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
2473
2628
  }
2474
2629
  function isRedactionPlaceholderMatch(match) {
2475
- const placeholder = String.raw`\[REDACTED(?:_[A-Z_]+)?\]`;
2476
2630
  const trimmed = match.trim();
2477
- return new RegExp(`^${placeholder}$`).test(trimmed) || new RegExp(`=\\s*['"]?${placeholder}['"]?$`).test(trimmed);
2631
+ return new RegExp(`^${REDACTION_PLACEHOLDER}$`).test(trimmed) || new RegExp(`=\\s*['"]?${REDACTION_PLACEHOLDER}['"]?$`).test(trimmed);
2632
+ }
2633
+ function isRedactionPlaceholderKey(key) {
2634
+ return new RegExp(`^${REDACTION_PLACEHOLDER}$`).test(key.trim());
2478
2635
  }
2479
2636
  function isSecretKey(key) {
2637
+ if (isRedactionPlaceholderKey(key))
2638
+ return false;
2480
2639
  if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
2481
2640
  return false;
2482
2641
  if (DEFAULT_SECRET_KEY_PATTERN.test(key))
@@ -2537,7 +2696,7 @@ function upsertSecretSafetyConfig(input) {
2537
2696
  saveConfig({ ...config, secret_safety: next });
2538
2697
  return next;
2539
2698
  }
2540
- var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS;
2699
+ var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS, REDACTION_PLACEHOLDER;
2541
2700
  var init_redaction = __esm(() => {
2542
2701
  init_config();
2543
2702
  DEFAULT_SECRET_PATTERNS = [
@@ -2561,11 +2720,12 @@ var init_redaction = __esm(() => {
2561
2720
  "completion_tokens",
2562
2721
  "cost_tokens"
2563
2722
  ]);
2723
+ REDACTION_PLACEHOLDER = String.raw`\[REDACTED(?:_[A-Z_]+)?\]`;
2564
2724
  });
2565
2725
 
2566
2726
  // src/pr-groups/types.ts
2567
2727
  var PR_GROUP_LEDGER_SCHEMA_VERSION = 1, PR_GROUP_REPAIR_CYCLE_LIMIT = 2, PrGroupLedgerError;
2568
- var init_types = __esm(() => {
2728
+ var init_types2 = __esm(() => {
2569
2729
  PrGroupLedgerError = class PrGroupLedgerError extends Error {
2570
2730
  code;
2571
2731
  details;
@@ -3920,7 +4080,7 @@ class PrGroupLedger {
3920
4080
  }
3921
4081
  var SHA_PATTERN, SAFE_REFERENCE_PATTERN, SAFE_PROFILE_PATTERN, FORBIDDEN_METADATA_KEY, EMAIL_PATTERN, CREDENTIAL_PATTERN, AUTH_PATH_PATTERN, STATE_VIEW_ATTEMPT_LIMIT = 100, STATE_VIEW_EVENT_LIMIT = 500, PR_GROUP_WRITER_STALE_AFTER_MS = 30000, APPENDABLE_EVENT_TYPES, EVENT_OUTCOMES, RECEIPT_EVENT_TYPES;
3922
4082
  var init_ledger = __esm(() => {
3923
- init_types();
4083
+ init_types2();
3924
4084
  SHA_PATTERN = /^[0-9a-f]{40}$/;
3925
4085
  SAFE_REFERENCE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/;
3926
4086
  SAFE_PROFILE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
@@ -4722,7 +4882,7 @@ class PrGroupHttpClient {
4722
4882
  var LEDGER_CODES, GROUP_STATES, ATTEMPT_STATES, EVENT_TYPES, EVENT_OUTCOMES2;
4723
4883
  var init_http_client = __esm(() => {
4724
4884
  init_ledger();
4725
- init_types();
4885
+ init_types2();
4726
4886
  LEDGER_CODES = new Set([
4727
4887
  "PR_GROUP_INVALID_INPUT",
4728
4888
  "PR_GROUP_NOT_FOUND",
@@ -5539,7 +5699,7 @@ async function cloudUnlockTask(client, id, agentId, force = false) {
5539
5699
  async function cloudGetDependencies(client, id) {
5540
5700
  const raw = await client.transport.get(`/tasks/${encodeURIComponent(id)}/dependencies`);
5541
5701
  const env = raw ?? {};
5542
- return { dependencies: env.dependencies ?? [], blocked_by: env.blocked_by ?? [] };
5702
+ return { dependencies: env.dependencies ?? [], blocks: env.blocks ?? env.blocked_by ?? [] };
5543
5703
  }
5544
5704
  function unresolvedRelatedTask(id) {
5545
5705
  const now = new Date(0).toISOString();
@@ -5600,7 +5760,7 @@ function unresolvedRelatedTask(id) {
5600
5760
  async function cloudGetTaskRelations(client, id) {
5601
5761
  const edges = await cloudGetDependencies(client, id);
5602
5762
  const upstream = dedupe(edges.dependencies.map((edge) => edge.depends_on));
5603
- const downstream = dedupe(edges.blocked_by.map((edge) => edge.task_id));
5763
+ const downstream = dedupe(edges.blocks.map((edge) => edge.task_id));
5604
5764
  const wanted = dedupe([...upstream, ...downstream]).filter((ref) => ref !== id);
5605
5765
  const rows = new Map;
5606
5766
  let cursor = 0;
@@ -5616,7 +5776,9 @@ async function cloudGetTaskRelations(client, id) {
5616
5776
  });
5617
5777
  await Promise.all(workers);
5618
5778
  const materialize = (ref) => rows.get(ref) ?? unresolvedRelatedTask(ref);
5619
- return { dependencies: upstream.map(materialize), blocked_by: downstream.map(materialize) };
5779
+ const dependencies = upstream.map(materialize);
5780
+ const blocked_by = dependencies.filter((dep) => isBlockingDependencyStatus(dep.status));
5781
+ return { dependencies, blocked_by, blocks: downstream.map(materialize) };
5620
5782
  }
5621
5783
  function dedupe(ids) {
5622
5784
  const seen = new Set;
@@ -5938,6 +6100,7 @@ async function cloudTimeline(client, options = {}) {
5938
6100
  }
5939
6101
  var UUID_RE, CLOUD_MODES, VALID_STORAGE_MODES, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, RELATION_HYDRATION_CONCURRENCY = 6, PRIORITY_RANK;
5940
6102
  var init_cloud_router = __esm(() => {
6103
+ init_types();
5941
6104
  init_redaction();
5942
6105
  init_http_client();
5943
6106
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -9769,158 +9932,6 @@ var init_machines = __esm(() => {
9769
9932
  ];
9770
9933
  });
9771
9934
 
9772
- // src/types/index.ts
9773
- var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
9774
- var init_types2 = __esm(() => {
9775
- TASK_STATUSES = [
9776
- "pending",
9777
- "in_progress",
9778
- "completed",
9779
- "failed",
9780
- "cancelled"
9781
- ];
9782
- TASK_PRIORITIES = [
9783
- "low",
9784
- "medium",
9785
- "high",
9786
- "critical"
9787
- ];
9788
- VersionConflictError = class VersionConflictError extends Error {
9789
- taskId;
9790
- expectedVersion;
9791
- actualVersion;
9792
- static code = "VERSION_CONFLICT";
9793
- static suggestion = "Fetch the task with get_task to get the current version before updating.";
9794
- constructor(taskId, expectedVersion, actualVersion) {
9795
- super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
9796
- this.taskId = taskId;
9797
- this.expectedVersion = expectedVersion;
9798
- this.actualVersion = actualVersion;
9799
- this.name = "VersionConflictError";
9800
- }
9801
- };
9802
- TaskNotFoundError = class TaskNotFoundError extends Error {
9803
- taskId;
9804
- static code = "TASK_NOT_FOUND";
9805
- static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
9806
- constructor(taskId) {
9807
- super(`Task not found: ${taskId}`);
9808
- this.taskId = taskId;
9809
- this.name = "TaskNotFoundError";
9810
- }
9811
- };
9812
- ProjectNotFoundError = class ProjectNotFoundError extends Error {
9813
- projectId;
9814
- static code = "PROJECT_NOT_FOUND";
9815
- static suggestion = "Use list_projects to see available projects.";
9816
- constructor(projectId) {
9817
- super(`Project not found: ${projectId}`);
9818
- this.projectId = projectId;
9819
- this.name = "ProjectNotFoundError";
9820
- }
9821
- };
9822
- ResourceConflictError = class ResourceConflictError extends Error {
9823
- code;
9824
- constructor(code, message) {
9825
- super(message);
9826
- this.code = code;
9827
- this.name = "ResourceConflictError";
9828
- }
9829
- };
9830
- PlanNotFoundError = class PlanNotFoundError extends Error {
9831
- planId;
9832
- static code = "PLAN_NOT_FOUND";
9833
- static suggestion = "Use list_plans to see available plans.";
9834
- constructor(planId) {
9835
- super(`Plan not found: ${planId}`);
9836
- this.planId = planId;
9837
- this.name = "PlanNotFoundError";
9838
- }
9839
- };
9840
- LockError = class LockError extends Error {
9841
- taskId;
9842
- lockedBy;
9843
- static code = "LOCK_ERROR";
9844
- static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
9845
- constructor(taskId, lockedBy) {
9846
- super(`Task ${taskId} is locked by ${lockedBy}`);
9847
- this.taskId = taskId;
9848
- this.lockedBy = lockedBy;
9849
- this.name = "LockError";
9850
- }
9851
- };
9852
- AgentNotFoundError = class AgentNotFoundError extends Error {
9853
- agentId;
9854
- static code = "AGENT_NOT_FOUND";
9855
- static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
9856
- constructor(agentId) {
9857
- super(`Agent not found: ${agentId}`);
9858
- this.agentId = agentId;
9859
- this.name = "AgentNotFoundError";
9860
- }
9861
- };
9862
- IdentityAliasAmbiguousError = class IdentityAliasAmbiguousError extends Error {
9863
- code = "IDENTITY_ALIAS_AMBIGUOUS";
9864
- candidates;
9865
- constructor(subject, candidates = []) {
9866
- super(`Identity alias or source is ambiguous: ${subject}`);
9867
- this.name = "IdentityAliasAmbiguousError";
9868
- this.candidates = [...new Set(candidates)].sort();
9869
- }
9870
- };
9871
- IdentityIdImmutableError = class IdentityIdImmutableError extends Error {
9872
- code = "IDENTITY_ID_IMMUTABLE";
9873
- constructor(agentId) {
9874
- super(`IDENTITY_ID_IMMUTABLE: canonical identity for agent ${agentId} cannot be replaced`);
9875
- this.name = "IdentityIdImmutableError";
9876
- }
9877
- };
9878
- TaskListNotFoundError = class TaskListNotFoundError extends Error {
9879
- taskListId;
9880
- static code = "TASK_LIST_NOT_FOUND";
9881
- static suggestion = "Use list_task_lists to see available lists.";
9882
- constructor(taskListId) {
9883
- super(`Task list not found: ${taskListId}`);
9884
- this.taskListId = taskListId;
9885
- this.name = "TaskListNotFoundError";
9886
- }
9887
- };
9888
- DependencyCycleError = class DependencyCycleError extends Error {
9889
- taskId;
9890
- dependsOn;
9891
- static code = "DEPENDENCY_CYCLE";
9892
- static suggestion = "Check the dependency chain with get_task to avoid circular references.";
9893
- constructor(taskId, dependsOn) {
9894
- super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
9895
- this.taskId = taskId;
9896
- this.dependsOn = dependsOn;
9897
- this.name = "DependencyCycleError";
9898
- }
9899
- };
9900
- CompletionGuardError = class CompletionGuardError extends Error {
9901
- reason;
9902
- retryAfterSeconds;
9903
- static code = "COMPLETION_BLOCKED";
9904
- static suggestion = "Wait for the cooldown period, then retry.";
9905
- constructor(reason, retryAfterSeconds) {
9906
- super(reason);
9907
- this.reason = reason;
9908
- this.retryAfterSeconds = retryAfterSeconds;
9909
- this.name = "CompletionGuardError";
9910
- }
9911
- };
9912
- DispatchNotFoundError = class DispatchNotFoundError extends Error {
9913
- dispatchId;
9914
- static code = "DISPATCH_NOT_FOUND";
9915
- static suggestion = "Check the dispatch ID with list_dispatches.";
9916
- constructor(dispatchId) {
9917
- super(`Dispatch not found: ${dispatchId}`);
9918
- this.dispatchId = dispatchId;
9919
- this.name = "DispatchNotFoundError";
9920
- }
9921
- };
9922
- });
9923
-
9924
9935
  // src/db/identity-mapping.ts
9925
9936
  function timestamp() {
9926
9937
  return new Date().toISOString();
@@ -10075,8 +10086,8 @@ function recordAgentAlias(agentId, label, db) {
10075
10086
  }
10076
10087
  var IDENTITY_PROJECTION_CONTRACT, IDENTITY_MIGRATION_ID = 65;
10077
10088
  var init_identity_mapping = __esm(() => {
10078
- init_types2();
10079
- init_types2();
10089
+ init_types();
10090
+ init_types();
10080
10091
  IDENTITY_PROJECTION_CONTRACT = Object.freeze({
10081
10092
  name: "hasna.todos.agent-identity-projection/v1",
10082
10093
  version: 1,
@@ -10467,8 +10478,8 @@ function ensureDir2(filePath) {
10467
10478
  function openDatabase(path) {
10468
10479
  ensureDir2(path);
10469
10480
  const db = new Database(path);
10470
- db.run("PRAGMA journal_mode = WAL");
10471
10481
  db.run("PRAGMA busy_timeout = 5000");
10482
+ db.run("PRAGMA journal_mode = WAL");
10472
10483
  db.run("PRAGMA foreign_keys = ON");
10473
10484
  runMigrations(db);
10474
10485
  ensureAgentIdentitySchema(db);
@@ -10601,7 +10612,7 @@ var init_database = __esm(() => {
10601
10612
  init_schema();
10602
10613
  init_machines();
10603
10614
  init_identity_mapping();
10604
- init_types2();
10615
+ init_types();
10605
10616
  ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
10606
10617
  });
10607
10618
 
@@ -11042,7 +11053,7 @@ function removeMachineLocalPath(projectId, machineId, db) {
11042
11053
  return result.changes > 0;
11043
11054
  }
11044
11055
  var init_projects = __esm(() => {
11045
- init_types2();
11056
+ init_types();
11046
11057
  init_database();
11047
11058
  init_machines();
11048
11059
  init_storage_tombstones();
@@ -11387,7 +11398,7 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
11387
11398
  }
11388
11399
  }
11389
11400
  var init_completion_guard = __esm(() => {
11390
- init_types2();
11401
+ init_types();
11391
11402
  init_config();
11392
11403
  init_projects();
11393
11404
  });
@@ -12811,7 +12822,7 @@ function ensureTaskList(name, slug, projectId, db) {
12811
12822
  return createTaskList({ name, slug, project_id: projectId }, d);
12812
12823
  }
12813
12824
  var init_task_lists = __esm(() => {
12814
- init_types2();
12825
+ init_types();
12815
12826
  init_database();
12816
12827
  init_projects();
12817
12828
  init_storage_tombstones();
@@ -13101,7 +13112,7 @@ function scanTextForSecrets(text, options = {}) {
13101
13112
  };
13102
13113
  }
13103
13114
  function redactText(text, options = {}) {
13104
- const placeholder = options.placeholder ?? REDACTION_PLACEHOLDER;
13115
+ const placeholder = options.placeholder ?? REDACTION_PLACEHOLDER2;
13105
13116
  let out = text;
13106
13117
  for (const { pattern, allowlist_ok } of DEFAULT_PATTERNS) {
13107
13118
  out = out.replace(new RegExp(pattern.source, pattern.flags), (match) => {
@@ -13128,7 +13139,7 @@ function redactExportRecord(record) {
13128
13139
  }
13129
13140
  return base;
13130
13141
  }
13131
- var SECRET_REDACTION_SCHEMA, REDACTION_PLACEHOLDER = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
13142
+ var SECRET_REDACTION_SCHEMA, REDACTION_PLACEHOLDER2 = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
13132
13143
  var init_secret_redaction = __esm(() => {
13133
13144
  init_redaction();
13134
13145
  SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
@@ -14435,7 +14446,7 @@ function wouldCreateCycle(taskId, dependsOn, db) {
14435
14446
  return false;
14436
14447
  }
14437
14448
  var init_task_graph = __esm(() => {
14438
- init_types2();
14449
+ init_types();
14439
14450
  init_database();
14440
14451
  init_task_crud();
14441
14452
  });
@@ -14994,7 +15005,7 @@ function spawnNextRecurrence(completedTask, db, completedAt) {
14994
15005
  }
14995
15006
  var MAX_SPAWN_DEPTH = 10;
14996
15007
  var init_task_lifecycle = __esm(() => {
14997
- init_types2();
15008
+ init_types();
14998
15009
  init_database();
14999
15010
  init_completion_guard();
15000
15011
  init_event_emission_safety();
@@ -15153,10 +15164,11 @@ function getTaskWithRelations(id, db) {
15153
15164
  JOIN task_dependencies td ON td.depends_on = t.id
15154
15165
  WHERE td.task_id = ?`).all(id);
15155
15166
  const dependencies = depRows.map(rowToTask);
15156
- const blockedByRows = d.query(`SELECT t.* FROM tasks t
15167
+ const blocked_by = dependencies.filter((dep) => isBlockingDependencyStatus(dep.status));
15168
+ const blocksRows = d.query(`SELECT t.* FROM tasks t
15157
15169
  JOIN task_dependencies td ON td.task_id = t.id
15158
15170
  WHERE td.depends_on = ?`).all(id);
15159
- const blocked_by = blockedByRows.map(rowToTask);
15171
+ const blocks = blocksRows.map(rowToTask);
15160
15172
  const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
15161
15173
  const parent = task.parent_id ? getTask(task.parent_id, d) : null;
15162
15174
  const checklist = getChecklist(id, d);
@@ -15165,6 +15177,7 @@ function getTaskWithRelations(id, db) {
15165
15177
  subtasks,
15166
15178
  dependencies,
15167
15179
  blocked_by,
15180
+ blocks,
15168
15181
  comments,
15169
15182
  parent,
15170
15183
  checklist
@@ -15622,7 +15635,7 @@ function deleteTask(id, db) {
15622
15635
  return result.changes > 0;
15623
15636
  }
15624
15637
  var init_task_crud = __esm(() => {
15625
- init_types2();
15638
+ init_types();
15626
15639
  init_database();
15627
15640
  init_completion_guard();
15628
15641
  init_event_emission_safety();
@@ -15808,7 +15821,7 @@ function getTaskStats(filters, db) {
15808
15821
  return { total: totalRow.count, by_status, by_priority, completion_rate, by_agent };
15809
15822
  }
15810
15823
  var init_task_status = __esm(() => {
15811
- init_types2();
15824
+ init_types();
15812
15825
  init_database();
15813
15826
  init_task_crud();
15814
15827
  init_task_graph();
@@ -16489,7 +16502,7 @@ function deletePlan(id, db) {
16489
16502
  return result.changes > 0;
16490
16503
  }
16491
16504
  var init_plans = __esm(() => {
16492
- init_types2();
16505
+ init_types();
16493
16506
  init_event_emission_safety();
16494
16507
  init_event_hooks();
16495
16508
  init_database();
@@ -17176,7 +17189,7 @@ function deleteComment(id, db) {
17176
17189
  return result.changes > 0;
17177
17190
  }
17178
17191
  var init_comments = __esm(() => {
17179
- init_types2();
17192
+ init_types();
17180
17193
  init_database();
17181
17194
  init_tasks();
17182
17195
  init_prewrite_secrets();
@@ -18055,7 +18068,7 @@ var init_task_runs = __esm(() => {
18055
18068
  init_event_emission_safety();
18056
18069
  init_event_hooks();
18057
18070
  init_prewrite_secrets();
18058
- init_types2();
18071
+ init_types();
18059
18072
  init_comments();
18060
18073
  init_database();
18061
18074
  init_task_files();
@@ -18845,7 +18858,7 @@ async function cloudDetailRelations(cloud, id) {
18845
18858
  if (status !== 404 && status !== 501) {
18846
18859
  console.error(chalk2.dim(`Warning: could not load task dependencies: ${e instanceof Error ? e.message : String(e)}`));
18847
18860
  }
18848
- return { dependencies: [], blocked_by: [] };
18861
+ return { dependencies: [], blocked_by: [], blocks: [] };
18849
18862
  }
18850
18863
  }
18851
18864
  function resolveProjectIdOrSlug(input) {
@@ -19422,6 +19435,7 @@ function registerTaskCommands(program2) {
19422
19435
  tags: remote.tags ?? [],
19423
19436
  dependencies: relations.dependencies,
19424
19437
  blocked_by: relations.blocked_by,
19438
+ blocks: relations.blocks,
19425
19439
  comments: commentPage.comments,
19426
19440
  comments_page: {
19427
19441
  count: commentPage.count,
@@ -19505,10 +19519,10 @@ function registerTaskCommands(program2) {
19505
19519
  console.log(` ${formatTaskLine(dep)}`);
19506
19520
  }
19507
19521
  }
19508
- if (task2.blocked_by.length > 0) {
19522
+ if (task2.blocks.length > 0) {
19509
19523
  console.log(chalk2.bold(`
19510
- Blocks (${task2.blocked_by.length}):`));
19511
- for (const b of task2.blocked_by) {
19524
+ Blocks (${task2.blocks.length}):`));
19525
+ for (const b of task2.blocks) {
19512
19526
  console.log(` ${formatTaskLine(b)}`);
19513
19527
  }
19514
19528
  }
@@ -19551,6 +19565,7 @@ function registerTaskCommands(program2) {
19551
19565
  tags: remote.tags ?? [],
19552
19566
  dependencies: relations.dependencies,
19553
19567
  blocked_by: relations.blocked_by,
19568
+ blocks: relations.blocks,
19554
19569
  comments: commentPage.comments,
19555
19570
  comments_page: {
19556
19571
  count: commentPage.count,
@@ -19632,10 +19647,10 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
19632
19647
  console.log(chalk2.red(`
19633
19648
  BLOCKED by ${unfinishedDeps.length} unfinished dep(s)`));
19634
19649
  }
19635
- if (task2.blocked_by.length > 0) {
19650
+ if (task2.blocks.length > 0) {
19636
19651
  console.log(chalk2.bold(`
19637
- Blocks (${task2.blocked_by.length}):`));
19638
- for (const b of task2.blocked_by)
19652
+ Blocks (${task2.blocks.length}):`));
19653
+ for (const b of task2.blocks)
19639
19654
  console.log(` ${formatTaskLine(b)}`);
19640
19655
  }
19641
19656
  if (task2.subtasks.length > 0) {
@@ -20203,7 +20218,7 @@ var init_task_commands = __esm(() => {
20203
20218
  init_cloud_router();
20204
20219
  init_helpers();
20205
20220
  init_output_redaction();
20206
- init_types2();
20221
+ init_types();
20207
20222
  });
20208
20223
 
20209
20224
  // src/lib/plan-artifacts.ts
@@ -21666,6 +21681,111 @@ var init_plan_template_commands = __esm(() => {
21666
21681
  init_cloud_router();
21667
21682
  });
21668
21683
 
21684
+ // src/lib/dependency-graph.ts
21685
+ function toNode(task) {
21686
+ return {
21687
+ id: task.id,
21688
+ short_id: task.short_id,
21689
+ title: task.title,
21690
+ status: task.status,
21691
+ priority: task.priority,
21692
+ plan_id: task.plan_id,
21693
+ project_id: task.project_id
21694
+ };
21695
+ }
21696
+ function filterTasks(tasks, filter) {
21697
+ let out = tasks;
21698
+ if (filter.project_id)
21699
+ out = out.filter((t) => t.project_id === filter.project_id);
21700
+ if (filter.plan_id)
21701
+ out = out.filter((t) => t.plan_id === filter.plan_id);
21702
+ if (filter.status?.length)
21703
+ out = out.filter((t) => filter.status.includes(t.status));
21704
+ return out;
21705
+ }
21706
+ function detectCyclesFromEdges(edges) {
21707
+ const adj = new Map;
21708
+ const nodes = new Set;
21709
+ for (const e of edges) {
21710
+ nodes.add(e.task_id);
21711
+ nodes.add(e.depends_on);
21712
+ if (!adj.has(e.task_id))
21713
+ adj.set(e.task_id, []);
21714
+ adj.get(e.task_id).push(e.depends_on);
21715
+ }
21716
+ const cycles = [];
21717
+ const visited = new Set;
21718
+ const stack = new Set;
21719
+ const path = [];
21720
+ function dfs(node) {
21721
+ visited.add(node);
21722
+ stack.add(node);
21723
+ path.push(node);
21724
+ for (const next of adj.get(node) ?? []) {
21725
+ if (!visited.has(next))
21726
+ dfs(next);
21727
+ else if (stack.has(next)) {
21728
+ const idx = path.indexOf(next);
21729
+ if (idx >= 0)
21730
+ cycles.push([...path.slice(idx), next]);
21731
+ }
21732
+ }
21733
+ path.pop();
21734
+ stack.delete(node);
21735
+ }
21736
+ for (const node of nodes) {
21737
+ if (!visited.has(node))
21738
+ dfs(node);
21739
+ }
21740
+ return cycles;
21741
+ }
21742
+ function buildTaskDependencyEdges(task, dependencies, blocks) {
21743
+ return {
21744
+ schema_version: TASK_DEPENDENCY_EDGES_SCHEMA,
21745
+ task_id: task.id,
21746
+ short_id: task.short_id,
21747
+ dependencies: dependencies.map(toNode),
21748
+ blocked_by: dependencies.filter((dep) => isBlockingDependencyStatus(dep.status)).map(toNode),
21749
+ blocks: blocks.map(toNode)
21750
+ };
21751
+ }
21752
+ function getTaskDependencyEdges(taskId, db) {
21753
+ const d = db || getDatabase();
21754
+ const task = getTask(taskId, d);
21755
+ if (!task)
21756
+ return null;
21757
+ const dependencies = getTaskDependencies(taskId, d).map((edge) => getTask(edge.depends_on, d)).filter((t) => Boolean(t));
21758
+ const blocks = getTaskDependents(taskId, d).map((edge) => getTask(edge.task_id, d)).filter((t) => Boolean(t));
21759
+ return buildTaskDependencyEdges(task, dependencies, blocks);
21760
+ }
21761
+ function buildProjectDependencyGraph(projectId, tasks, edges, generatedAt = new Date().toISOString()) {
21762
+ const nodeIds = new Set(tasks.map((t) => t.id));
21763
+ const scoped = edges.filter((edge) => nodeIds.has(edge.task_id)).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on }));
21764
+ return {
21765
+ schema_version: PROJECT_DEPENDENCY_GRAPH_SCHEMA,
21766
+ generated_at: generatedAt,
21767
+ project_id: projectId,
21768
+ nodes: tasks.map(toNode),
21769
+ edges: scoped,
21770
+ cycles: detectCyclesFromEdges(scoped)
21771
+ };
21772
+ }
21773
+ function getProjectDependencyGraph(filter = {}, db) {
21774
+ const d = db || getDatabase();
21775
+ const tasks = filterTasks(listTasks({}, d), filter);
21776
+ const edges = d.query("SELECT task_id, depends_on FROM task_dependencies").all();
21777
+ return buildProjectDependencyGraph(filter.project_id ?? null, tasks, edges);
21778
+ }
21779
+ var TASK_DEPENDENCY_EDGES_SCHEMA = "todos.task_dependency_edges.v1", PROJECT_DEPENDENCY_GRAPH_SCHEMA = "todos.project_dependency_graph.v1";
21780
+ var init_dependency_graph = __esm(() => {
21781
+ init_database();
21782
+ init_tasks();
21783
+ init_task_graph();
21784
+ init_task_lifecycle();
21785
+ init_types();
21786
+ init_types();
21787
+ });
21788
+
21669
21789
  // src/lib/local-fields.ts
21670
21790
  function normalizeList(values) {
21671
21791
  return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
@@ -21758,7 +21878,7 @@ var LOCAL_FIELDS_KEY = "local_fields";
21758
21878
  var init_local_fields = __esm(() => {
21759
21879
  init_database();
21760
21880
  init_tasks();
21761
- init_types2();
21881
+ init_types();
21762
21882
  init_redaction();
21763
21883
  });
21764
21884
 
@@ -24709,6 +24829,46 @@ import { basename as basename5, resolve as resolve13, sep as sep3 } from "path";
24709
24829
  function collectOption(value, previous = []) {
24710
24830
  return [...previous, value];
24711
24831
  }
24832
+ function isResolvedTask(task) {
24833
+ const metadata = task.metadata;
24834
+ return !(metadata && metadata["unresolved"] === true);
24835
+ }
24836
+ async function buildCloudProjectDependencyGraph(cloud, projectId) {
24837
+ const tasks = await cloudListTasks(cloud, projectId ? { project_id: projectId } : {});
24838
+ const edges = [];
24839
+ const seen = new Set;
24840
+ let cursor = 0;
24841
+ const workers = Array.from({ length: Math.min(CLOUD_GRAPH_EDGE_CONCURRENCY, tasks.length) }, async () => {
24842
+ for (let index = cursor++;index < tasks.length; index = cursor++) {
24843
+ const task = tasks[index];
24844
+ try {
24845
+ const deps = await cloudGetDependencies(cloud, task.id);
24846
+ for (const edge of deps.dependencies) {
24847
+ const key = `${edge.task_id}|${edge.depends_on}`;
24848
+ if (seen.has(key))
24849
+ continue;
24850
+ seen.add(key);
24851
+ edges.push({ task_id: edge.task_id, depends_on: edge.depends_on });
24852
+ }
24853
+ } catch {}
24854
+ }
24855
+ });
24856
+ await Promise.all(workers);
24857
+ return buildProjectDependencyGraph(projectId, tasks, edges);
24858
+ }
24859
+ function printProjectDependencyGraph(graph) {
24860
+ if (graph.nodes.length === 0) {
24861
+ console.log(chalk4.dim("No tasks in scope."));
24862
+ return;
24863
+ }
24864
+ console.log(chalk4.bold(`Dependency graph: ${graph.nodes.length} task(s), ${graph.edges.length} edge(s)`));
24865
+ if (graph.cycles.length > 0) {
24866
+ console.log(chalk4.red(` ${graph.cycles.length} cycle(s) detected`));
24867
+ }
24868
+ for (const edge of graph.edges) {
24869
+ console.log(` ${chalk4.cyan(edge.task_id.slice(0, 8))} depends on ${chalk4.cyan(edge.depends_on.slice(0, 8))}`);
24870
+ }
24871
+ }
24712
24872
  function splitList(value) {
24713
24873
  if (!value)
24714
24874
  return;
@@ -25042,9 +25202,39 @@ function registerProjectCommands(program2) {
25042
25202
  handleError(e);
25043
25203
  }
25044
25204
  });
25045
- program2.command("deps <id>").description("Manage task dependencies").option("--needs <dep-id>", "Add dependency (this task needs dep-id)").option("--remove <dep-id>", "Remove dependency").option("--graph", "Show the dependency graph instead of direct edges").option("--direction <direction>", "Graph direction: up, down, or both", "both").action(async (id, opts) => {
25205
+ program2.command("deps [id]").description("Read or manage task dependencies. With no id, --project reads the whole-project dependency graph").option("--needs <dep-id>", "Add dependency (this task needs dep-id)").option("--remove <dep-id>", "Remove dependency").option("--graph", "Show the dependency graph instead of direct edges").option("--direction <direction>", "Graph direction: up, down, or both", "both").option("--project <ref>", "With no id: read the whole-project graph (project id, slug, name, or path)").action(async (id, opts) => {
25046
25206
  const globalOpts = program2.opts();
25047
25207
  const cloud = getTodosCloudClient();
25208
+ if (!id) {
25209
+ if (opts.needs || opts.remove || opts.graph || opts.direction !== "both") {
25210
+ handleError(new Error("A task id is required with --needs, --remove, --graph, or --direction."));
25211
+ }
25212
+ const projectRef = opts.project ?? globalOpts.project;
25213
+ if (cloud) {
25214
+ if (!projectRef) {
25215
+ handleError(new Error("deps needs a task id, or --project <ref> to read a whole-project graph."));
25216
+ }
25217
+ const projectId = await cloudResolveProjectRef(cloud, projectRef);
25218
+ const graph2 = await buildCloudProjectDependencyGraph(cloud, projectId);
25219
+ if (globalOpts.json) {
25220
+ output(graph2, true);
25221
+ return;
25222
+ }
25223
+ printProjectDependencyGraph(graph2);
25224
+ return;
25225
+ }
25226
+ const resolvedProjectId = projectRef ? resolveExplicitProject(projectRef).id : autoProject(globalOpts);
25227
+ if (!resolvedProjectId) {
25228
+ handleError(new Error("deps needs a task id, or --project <ref> to read a whole-project graph."));
25229
+ }
25230
+ const graph = getProjectDependencyGraph({ project_id: resolvedProjectId });
25231
+ if (globalOpts.json) {
25232
+ output(graph, true);
25233
+ return;
25234
+ }
25235
+ printProjectDependencyGraph(graph);
25236
+ return;
25237
+ }
25048
25238
  if (cloud) {
25049
25239
  const cloudId = await resolveTaskIdForCommand(id, cloud);
25050
25240
  if (opts.needs) {
@@ -25067,22 +25257,23 @@ function registerProjectCommands(program2) {
25067
25257
  console.log(removed ? chalk4.green("Dependency removed.") : chalk4.red("Dependency not found."));
25068
25258
  return;
25069
25259
  }
25070
- const edges = await cloudGetDependencies(cloud, cloudId);
25071
25260
  if (globalOpts.json) {
25072
- output(edges, true);
25261
+ const relations = await cloudGetTaskRelations(cloud, cloudId);
25262
+ output(buildTaskDependencyEdges({ id: cloudId, short_id: null }, relations.dependencies.filter(isResolvedTask), relations.blocks.filter(isResolvedTask)), true);
25073
25263
  return;
25074
25264
  }
25265
+ const edges = await cloudGetDependencies(cloud, cloudId);
25075
25266
  if (edges.dependencies.length > 0) {
25076
25267
  console.log(chalk4.bold("Depends on:"));
25077
25268
  for (const dep of edges.dependencies)
25078
25269
  console.log(` ${chalk4.cyan(dep.depends_on)}`);
25079
25270
  }
25080
- if (edges.blocked_by.length > 0) {
25271
+ if (edges.blocks.length > 0) {
25081
25272
  console.log(chalk4.bold("Blocks:"));
25082
- for (const b of edges.blocked_by)
25273
+ for (const b of edges.blocks)
25083
25274
  console.log(` ${chalk4.cyan(b.task_id)}`);
25084
25275
  }
25085
- if (edges.dependencies.length === 0 && edges.blocked_by.length === 0) {
25276
+ if (edges.dependencies.length === 0 && edges.blocks.length === 0) {
25086
25277
  console.log(chalk4.dim("No dependencies."));
25087
25278
  }
25088
25279
  return;
@@ -25127,28 +25318,30 @@ function registerProjectCommands(program2) {
25127
25318
  printNode(dependent, depth + 1, "blocks");
25128
25319
  };
25129
25320
  printNode(graph, 0, "root");
25321
+ } else if (globalOpts.json) {
25322
+ const edges = getTaskDependencyEdges(resolvedId);
25323
+ if (!edges)
25324
+ handleError(new Error("Task not found."));
25325
+ output(edges, true);
25326
+ return;
25130
25327
  } else {
25131
25328
  const task = getTaskWithRelations2(resolvedId);
25132
25329
  if (!task) {
25133
25330
  handleError(new Error("Task not found."));
25134
25331
  }
25135
- if (globalOpts.json) {
25136
- output({ dependencies: task.dependencies, blocked_by: task.blocked_by }, true);
25137
- return;
25138
- }
25139
25332
  if (task.dependencies.length > 0) {
25140
25333
  console.log(chalk4.bold("Depends on:"));
25141
25334
  for (const dep of task.dependencies) {
25142
25335
  console.log(` ${formatTaskLine(dep)}`);
25143
25336
  }
25144
25337
  }
25145
- if (task.blocked_by.length > 0) {
25338
+ if (task.blocks.length > 0) {
25146
25339
  console.log(chalk4.bold("Blocks:"));
25147
- for (const b of task.blocked_by) {
25340
+ for (const b of task.blocks) {
25148
25341
  console.log(` ${formatTaskLine(b)}`);
25149
25342
  }
25150
25343
  }
25151
- if (task.dependencies.length === 0 && task.blocked_by.length === 0) {
25344
+ if (task.dependencies.length === 0 && task.blocks.length === 0) {
25152
25345
  console.log(chalk4.dim("No dependencies."));
25153
25346
  }
25154
25347
  }
@@ -25641,11 +25834,13 @@ function resolveTaskListId(partialId) {
25641
25834
  }
25642
25835
  return id;
25643
25836
  }
25837
+ var CLOUD_GRAPH_EDGE_CONCURRENCY = 6;
25644
25838
  var init_project_commands = __esm(() => {
25645
25839
  init_database();
25646
25840
  init_projects();
25647
25841
  init_comments();
25648
25842
  init_cloud_router();
25843
+ init_dependency_graph();
25649
25844
  init_saved_search_views();
25650
25845
  init_sync();
25651
25846
  init_config();
@@ -26323,7 +26518,7 @@ function getCapableAgents(capabilities, opts, db) {
26323
26518
  return opts?.limit ? scored.slice(0, opts.limit) : scored;
26324
26519
  }
26325
26520
  var init_agents = __esm(() => {
26326
- init_types2();
26521
+ init_types();
26327
26522
  init_database();
26328
26523
  init_identity_mapping();
26329
26524
  init_storage_tombstones();
@@ -26771,6 +26966,44 @@ var init_agent_commands = __esm(() => {
26771
26966
  init_cloud_router();
26772
26967
  });
26773
26968
 
26969
+ // src/server/port.ts
26970
+ var exports_port = {};
26971
+ __export(exports_port, {
26972
+ refuseInvalidPort: () => refuseInvalidPort,
26973
+ findFreePort: () => findFreePort,
26974
+ coercePort: () => coercePort,
26975
+ DEFAULT_PORT: () => DEFAULT_PORT
26976
+ });
26977
+ function coercePort(raw) {
26978
+ if (raw === undefined)
26979
+ return;
26980
+ const trimmed = raw.trim();
26981
+ if (!/^\d+$/.test(trimmed))
26982
+ return;
26983
+ if (trimmed.length > 1 && trimmed.startsWith("0"))
26984
+ return;
26985
+ const parsed = Number.parseInt(trimmed, 10);
26986
+ if (!Number.isInteger(parsed) || parsed > 65535)
26987
+ return;
26988
+ return parsed;
26989
+ }
26990
+ function refuseInvalidPort(source, raw) {
26991
+ console.error(`Invalid ${source}: ${JSON.stringify(raw)} is not a port.
26992
+ ` + `Use an integer from 0 to 65535, where 0 asks the kernel for a free port.`);
26993
+ process.exit(1);
26994
+ }
26995
+ async function findFreePort(start) {
26996
+ for (let port = start;port < start + 100; port++) {
26997
+ try {
26998
+ const server = Bun.serve({ port, fetch: () => new Response("") });
26999
+ server.stop(true);
27000
+ return port;
27001
+ } catch {}
27002
+ }
27003
+ return start;
27004
+ }
27005
+ var DEFAULT_PORT = 19427;
27006
+
26774
27007
  // src/lib/db-backup.ts
26775
27008
  import { existsSync as existsSync14, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync8, renameSync, statSync as statSync6, writeFileSync as writeFileSync7, unlinkSync } from "fs";
26776
27009
  import { dirname as dirname7, join as join12, resolve as resolve14 } from "path";
@@ -29342,7 +29575,7 @@ var init_approval_gates = __esm(() => {
29342
29575
  init_database();
29343
29576
  init_task_runs();
29344
29577
  init_tasks();
29345
- init_types2();
29578
+ init_types();
29346
29579
  init_event_emission_safety();
29347
29580
  init_event_hooks();
29348
29581
  });
@@ -31050,9 +31283,11 @@ async function removeDependency2(taskId, dependsOn, store) {
31050
31283
  }
31051
31284
  async function listDependencies(taskId, store) {
31052
31285
  const edges = await store.list("dependencies");
31286
+ const incoming = edges.filter((edge) => edge.depends_on === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on }));
31053
31287
  return {
31054
31288
  dependencies: edges.filter((edge) => edge.task_id === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on })),
31055
- blocked_by: edges.filter((edge) => edge.depends_on === taskId).map((edge) => ({ task_id: edge.task_id, depends_on: edge.depends_on }))
31289
+ blocks: incoming,
31290
+ blocked_by: incoming
31056
31291
  };
31057
31292
  }
31058
31293
  async function addVerification(input, store, context) {
@@ -31596,7 +31831,7 @@ function postgresConstraintName(error) {
31596
31831
  }
31597
31832
  var CLOUD_LOCK_EXPIRY_MINUTES = 30, TASK_ORDER_TIEBREAK = "CASE payload->>'priority' WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END ASC, payload->>'created_at' ASC, payload->>'id' ASC", TASK_ORDER_BY;
31598
31833
  var init_postgres_adapter = __esm(() => {
31599
- init_types2();
31834
+ init_types();
31600
31835
  init_postgres_sync();
31601
31836
  init_redaction();
31602
31837
  TASK_ORDER_BY = `ORDER BY ${TASK_ORDER_TIEBREAK}`;
@@ -32079,7 +32314,7 @@ class PostgresPrGroupLedgerPersistence {
32079
32314
  }
32080
32315
  }
32081
32316
  var init_postgres = __esm(() => {
32082
- init_types();
32317
+ init_types2();
32083
32318
  });
32084
32319
 
32085
32320
  // src/storage/comment-redaction-backfill.ts
@@ -33797,7 +34032,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
33797
34032
  var init_routes = __esm(() => {
33798
34033
  init_tasks();
33799
34034
  init_database();
33800
- init_types2();
34035
+ init_types();
33801
34036
  init_projects();
33802
34037
  init_agents();
33803
34038
  init_plans();
@@ -36435,7 +36670,7 @@ async function handlePrGroupHttpRequest(req, url, ledger, basePath, principal) {
36435
36670
  }
36436
36671
  var JSON_HEADERS;
36437
36672
  var init_pr_groups = __esm(() => {
36438
- init_types();
36673
+ init_types2();
36439
36674
  JSON_HEADERS = { "Content-Type": "application/json" };
36440
36675
  });
36441
36676
 
@@ -37554,7 +37789,7 @@ async function handleV1Request(req, url, dependencies = {}) {
37554
37789
  }
37555
37790
  var JSON_HEADERS2, DEFAULT_COMMENT_PAGE_SIZE = 100, MAX_COMMENT_PAGE_SIZE = 500, LEGACY_COMMENT_RESPONSE_LIMIT = 500;
37556
37791
  var init_v1 = __esm(() => {
37557
- init_types2();
37792
+ init_types();
37558
37793
  init_cloud();
37559
37794
  init_pr_groups();
37560
37795
  init_redaction();
@@ -41639,7 +41874,7 @@ function getDueDispatches(db) {
41639
41874
  }
41640
41875
  var init_dispatches = __esm(() => {
41641
41876
  init_database();
41642
- init_types2();
41877
+ init_types();
41643
41878
  init_prewrite_secrets();
41644
41879
  });
41645
41880
 
@@ -42418,7 +42653,7 @@ ${task.description}` : null
42418
42653
  var init_task_crud2 = __esm(() => {
42419
42654
  init_zod();
42420
42655
  init_tasks();
42421
- init_types2();
42656
+ init_types();
42422
42657
  init_token_utils();
42423
42658
  init_cloud_router();
42424
42659
  });
@@ -43816,7 +44051,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
43816
44051
  var LOCAL_FIELDS_KEY2 = "local_fields", WORKFLOW_STATE_KEY = "workflow_state", DEFAULT_WORKFLOW_STATES;
43817
44052
  var init_workflow_states = __esm(() => {
43818
44053
  init_tasks();
43819
- init_types2();
44054
+ init_types();
43820
44055
  init_database();
43821
44056
  init_config();
43822
44057
  init_local_fields();
@@ -48060,7 +48295,7 @@ var init_task_project_tools = __esm(() => {
48060
48295
  init_capacity_forecasts();
48061
48296
  init_audit_ledger();
48062
48297
  init_release_compatibility();
48063
- init_types2();
48298
+ init_types();
48064
48299
  });
48065
48300
 
48066
48301
  // src/db/handoffs.ts
@@ -48737,7 +48972,7 @@ function registerTaskWorkflowTools(server, ctx) {
48737
48972
  }
48738
48973
  var init_task_workflow_tools = __esm(() => {
48739
48974
  init_zod();
48740
- init_types2();
48975
+ init_types();
48741
48976
  init_token_utils();
48742
48977
  init_cloud_router();
48743
48978
  });
@@ -49702,7 +49937,7 @@ var init_context_packs = __esm(() => {
49702
49937
  init_task_graph();
49703
49938
  init_task_runs();
49704
49939
  init_tasks();
49705
- init_types2();
49940
+ init_types();
49706
49941
  init_redaction();
49707
49942
  DEFAULT_LIMITS = {
49708
49943
  comment_limit: 8,
@@ -51097,7 +51332,7 @@ function resolveMissingTaskFindings(input, db) {
51097
51332
  var TASK_FINDING_SCHEMA_VERSION = "todos.task_finding.v1", TASK_FINDING_UPSERT_SCHEMA_VERSION = "todos.task_finding_upsert.v1", TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION = "todos.task_finding_resolve_missing.v1", SEVERITIES, STATUSES;
51098
51333
  var init_findings = __esm(() => {
51099
51334
  init_redaction();
51100
- init_types2();
51335
+ init_types();
51101
51336
  init_database();
51102
51337
  init_tasks();
51103
51338
  init_task_runs();
@@ -56195,7 +56430,7 @@ function forceUnlockFile(path, db) {
56195
56430
  var FILE_LOCK_DEFAULT_TTL_SECONDS;
56196
56431
  var init_file_locks = __esm(() => {
56197
56432
  init_database();
56198
- init_types2();
56433
+ init_types();
56199
56434
  FILE_LOCK_DEFAULT_TTL_SECONDS = 30 * 60;
56200
56435
  });
56201
56436
 
@@ -61343,7 +61578,7 @@ var agentFocusMap, isDirectRun;
61343
61578
  var init_mcp2 = __esm(() => {
61344
61579
  init_agents();
61345
61580
  init_database();
61346
- init_types2();
61581
+ init_types();
61347
61582
  init_dispatch2();
61348
61583
  init_task_crud2();
61349
61584
  init_task_project_tools();
@@ -61697,7 +61932,7 @@ var init_pr_groups2 = __esm(() => {
61697
61932
  init_database();
61698
61933
  init_ledger();
61699
61934
  init_sqlite();
61700
- init_types();
61935
+ init_types2();
61701
61936
  init_ledger();
61702
61937
  init_sqlite();
61703
61938
  init_http_client();
@@ -61935,7 +62170,7 @@ Dashboard not found at: ${dashboardDir}`);
61935
62170
  const path = url.pathname;
61936
62171
  const method = req.method;
61937
62172
  const reqOrigin = req.headers.get("origin") || undefined;
61938
- const corsHeaders = reqOrigin && (reqOrigin === `http://localhost:${port}` || reqOrigin === "http://localhost:0") ? {
62173
+ const corsHeaders = reqOrigin && reqOrigin === `http://localhost:${ctx.port}` ? {
61939
62174
  "Access-Control-Allow-Origin": reqOrigin,
61940
62175
  "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
61941
62176
  "Access-Control-Allow-Headers": "Content-Type, X-API-Key, Authorization",
@@ -62223,7 +62458,9 @@ Dashboard not found at: ${dashboardDir}`);
62223
62458
  };
62224
62459
  process.on("SIGINT", shutdown);
62225
62460
  process.on("SIGTERM", shutdown);
62226
- const serverUrl = `http://localhost:${port}`;
62461
+ const boundPort = server.port ?? port;
62462
+ ctx.port = boundPort;
62463
+ const serverUrl = `http://localhost:${boundPort}`;
62227
62464
  console.log(`Todos Dashboard running at ${serverUrl}`);
62228
62465
  if (shouldOpen) {
62229
62466
  try {
@@ -62705,7 +62942,7 @@ function TaskDetail({ task: task2 }) {
62705
62942
  }, dep.id, true, undefined, this))
62706
62943
  ]
62707
62944
  }, undefined, true, undefined, this),
62708
- task2.blocked_by.length > 0 && /* @__PURE__ */ jsxDEV3(Box3, {
62945
+ task2.blocks.length > 0 && /* @__PURE__ */ jsxDEV3(Box3, {
62709
62946
  flexDirection: "column",
62710
62947
  marginBottom: 1,
62711
62948
  children: [
@@ -62713,11 +62950,11 @@ function TaskDetail({ task: task2 }) {
62713
62950
  bold: true,
62714
62951
  children: [
62715
62952
  "Blocks (",
62716
- task2.blocked_by.length,
62953
+ task2.blocks.length,
62717
62954
  "):"
62718
62955
  ]
62719
62956
  }, undefined, true, undefined, this),
62720
- task2.blocked_by.map((b) => /* @__PURE__ */ jsxDEV3(Box3, {
62957
+ task2.blocks.map((b) => /* @__PURE__ */ jsxDEV3(Box3, {
62721
62958
  marginLeft: 2,
62722
62959
  children: [
62723
62960
  /* @__PURE__ */ jsxDEV3(Text3, {
@@ -64701,18 +64938,11 @@ function registerConfigServeCommands(program2) {
64701
64938
  console.log(renderTerminalNotification2(notification, rule?.format || "line"));
64702
64939
  }
64703
64940
  });
64704
- program2.command("serve").description("Start the web dashboard").option("--port <port>", "Port number", "19427").option("--host <host>", "Host to bind (default: 127.0.0.1 localhost only, use 0.0.0.0 for all interfaces)").option("--api-key <key>", "Require this API key for /api/* requests").option("--allow-anonymous", "Local dev only: serve /api/* and /mcp without a credential (refused unless the bind host is loopback)").option("--no-open", "Don't open browser automatically").action(async (opts) => {
64941
+ program2.command("serve").description("Start the web dashboard").option("--port <port>", "Port number", String(DEFAULT_PORT)).option("--host <host>", "Host to bind (default: 127.0.0.1 localhost only, use 0.0.0.0 for all interfaces)").option("--api-key <key>", "Require this API key for /api/* requests").option("--allow-anonymous", "Local dev only: serve /api/* and /mcp without a credential (refused unless the bind host is loopback)").option("--no-open", "Don't open browser automatically").action(async (opts) => {
64705
64942
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
64706
- const requestedPort = parseInt(opts.port, 10);
64707
- let port = requestedPort;
64708
- for (let p = requestedPort;p < requestedPort + 100; p++) {
64709
- try {
64710
- const s = Bun.serve({ port: p, fetch: () => new Response("") });
64711
- s.stop(true);
64712
- port = p;
64713
- break;
64714
- } catch {}
64715
- }
64943
+ const { coercePort: coercePort2, findFreePort: findFreePort2, refuseInvalidPort: refuseInvalidPort2 } = await Promise.resolve().then(() => exports_port);
64944
+ const requestedPort = coercePort2(opts.port) ?? refuseInvalidPort2("--port", String(opts.port));
64945
+ const port = requestedPort === 0 ? requestedPort : await findFreePort2(requestedPort);
64716
64946
  if (port !== requestedPort) {
64717
64947
  console.log(`Port ${requestedPort} in use, using ${port}`);
64718
64948
  }
@@ -69127,7 +69357,7 @@ var init_query_commands = __esm(() => {
69127
69357
  init_local_reports();
69128
69358
  init_helpers();
69129
69359
  init_cloud_router();
69130
- init_types2();
69360
+ init_types();
69131
69361
  });
69132
69362
 
69133
69363
  // src/cli/commands/mcp-hooks-commands.ts