@hasna/todos 0.11.51 → 0.11.53

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.
Files changed (210) hide show
  1. package/README.md +14 -1
  2. package/dashboard/dist/assets/{index-C3fBxEWP.js → index-aJefI7kh.js} +1 -1
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/cli/commands/config-serve-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/machines.d.ts.map +1 -1
  6. package/dist/cli/commands/storage-commands.d.ts +3 -0
  7. package/dist/cli/commands/storage-commands.d.ts.map +1 -0
  8. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  9. package/dist/cli/helpers.d.ts +6 -0
  10. package/dist/cli/helpers.d.ts.map +1 -1
  11. package/dist/cli/index.js +3342 -450
  12. package/dist/contracts.js +289 -8
  13. package/dist/db/artifacts.d.ts.map +1 -1
  14. package/dist/db/builtin-templates.d.ts +0 -1
  15. package/dist/db/builtin-templates.d.ts.map +1 -1
  16. package/dist/db/database.d.ts.map +1 -1
  17. package/dist/db/schema.d.ts.map +1 -1
  18. package/dist/db/tags.d.ts +26 -0
  19. package/dist/db/tags.d.ts.map +1 -0
  20. package/dist/db/task-commits.d.ts +14 -6
  21. package/dist/db/task-commits.d.ts.map +1 -1
  22. package/dist/db/tasks.d.ts +1 -1
  23. package/dist/db/tasks.d.ts.map +1 -1
  24. package/dist/index.d.ts +32 -60
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2473 -200
  27. package/dist/lib/agent-adapter-docs.d.ts +1 -1
  28. package/dist/lib/agent-adapter-docs.d.ts.map +1 -1
  29. package/dist/lib/agent-coordination.d.ts.map +1 -1
  30. package/dist/lib/agent-run-dispatcher.d.ts +5 -2
  31. package/dist/lib/agent-run-dispatcher.d.ts.map +1 -1
  32. package/dist/lib/agent-workflow-demo.d.ts +1 -1
  33. package/dist/lib/agent-workflow-demo.d.ts.map +1 -1
  34. package/dist/lib/db-backup.d.ts.map +1 -1
  35. package/dist/lib/headless-boundaries.d.ts +2 -2
  36. package/dist/lib/headless-boundaries.d.ts.map +1 -1
  37. package/dist/lib/import-export-bridge.d.ts.map +1 -1
  38. package/dist/lib/inbox-intake.d.ts.map +1 -1
  39. package/dist/lib/issue-importers.d.ts.map +1 -1
  40. package/dist/lib/machine-topology.d.ts +1 -1
  41. package/dist/lib/machine-topology.d.ts.map +1 -1
  42. package/dist/lib/native-storage-status.d.ts +65 -0
  43. package/dist/lib/native-storage-status.d.ts.map +1 -0
  44. package/dist/lib/nl-intake.d.ts.map +1 -1
  45. package/dist/lib/notification-reminders.d.ts.map +1 -1
  46. package/dist/lib/plan-execution.d.ts +1 -0
  47. package/dist/lib/plan-execution.d.ts.map +1 -1
  48. package/dist/lib/release-checks.d.ts.map +1 -1
  49. package/dist/lib/resource-snapshots.d.ts.map +1 -1
  50. package/dist/lib/saved-views.d.ts.map +1 -1
  51. package/dist/lib/secret-redaction.d.ts +1 -1
  52. package/dist/lib/secret-redaction.d.ts.map +1 -1
  53. package/dist/lib/task-scheduling.d.ts +1 -1
  54. package/dist/lib/task-scheduling.d.ts.map +1 -1
  55. package/dist/lib/template-library.d.ts +1 -1
  56. package/dist/lib/template-library.d.ts.map +1 -1
  57. package/dist/lib/user-scaffolds.d.ts.map +1 -1
  58. package/dist/lib/verification-evidence.d.ts +4 -4
  59. package/dist/lib/verification-evidence.d.ts.map +1 -1
  60. package/dist/lib/verification-providers.d.ts +3 -3
  61. package/dist/lib/verification-providers.d.ts.map +1 -1
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +562 -55
  64. package/dist/mcp/tools/task-crud.d.ts.map +1 -1
  65. package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
  66. package/dist/registry.js +289 -8
  67. package/dist/release-provenance.json +7 -0
  68. package/dist/server/index.js +588 -57
  69. package/dist/server/serve.d.ts.map +1 -1
  70. package/dist/storage/config.d.ts +86 -0
  71. package/dist/storage/config.d.ts.map +1 -0
  72. package/dist/storage/factory.d.ts +17 -0
  73. package/dist/storage/factory.d.ts.map +1 -0
  74. package/dist/storage/hybrid.d.ts +26 -0
  75. package/dist/storage/hybrid.d.ts.map +1 -0
  76. package/dist/storage/index.d.ts +15 -0
  77. package/dist/storage/index.d.ts.map +1 -1
  78. package/dist/storage/local-sqlite.d.ts.map +1 -1
  79. package/dist/storage/postgres-adapter.d.ts +11 -0
  80. package/dist/storage/postgres-adapter.d.ts.map +1 -0
  81. package/dist/storage/postgres-sync.d.ts +44 -0
  82. package/dist/storage/postgres-sync.d.ts.map +1 -0
  83. package/dist/storage/s3-artifact-sync.d.ts +75 -0
  84. package/dist/storage/s3-artifact-sync.d.ts.map +1 -0
  85. package/dist/storage/s3-artifacts.d.ts +51 -0
  86. package/dist/storage/s3-artifacts.d.ts.map +1 -0
  87. package/dist/storage/sqlite-snapshot.d.ts +5 -0
  88. package/dist/storage/sqlite-snapshot.d.ts.map +1 -0
  89. package/dist/storage.d.ts +2 -1
  90. package/dist/storage.d.ts.map +1 -1
  91. package/dist/storage.js +2053 -10
  92. package/package.json +1 -1
  93. package/dist/cli/brains.d.ts +0 -3
  94. package/dist/cli/brains.d.ts.map +0 -1
  95. package/dist/db/file-locks.d.ts +0 -43
  96. package/dist/db/file-locks.d.ts.map +0 -1
  97. package/dist/db/project-agent-roles.d.ts +0 -34
  98. package/dist/db/project-agent-roles.d.ts.map +0 -1
  99. package/dist/db/task-claim.d.ts +0 -7
  100. package/dist/db/task-claim.d.ts.map +0 -1
  101. package/dist/db/task-workflow.d.ts +0 -7
  102. package/dist/db/task-workflow.d.ts.map +0 -1
  103. package/dist/lib/north-star.d.ts +0 -33
  104. package/dist/lib/north-star.d.ts.map +0 -1
  105. package/dist/lib/public-release-gate.d.ts +0 -57
  106. package/dist/lib/public-release-gate.d.ts.map +0 -1
  107. package/dist/lib/task-runner.d.ts +0 -101
  108. package/dist/lib/task-runner.d.ts.map +0 -1
  109. package/dist/mcp/tools/access-profiles.d.ts +0 -8
  110. package/dist/mcp/tools/access-profiles.d.ts.map +0 -1
  111. package/dist/mcp/tools/activity-audit.d.ts +0 -9
  112. package/dist/mcp/tools/activity-audit.d.ts.map +0 -1
  113. package/dist/mcp/tools/agent-adapter-docs.d.ts +0 -8
  114. package/dist/mcp/tools/agent-adapter-docs.d.ts.map +0 -1
  115. package/dist/mcp/tools/agent-coordination.d.ts +0 -9
  116. package/dist/mcp/tools/agent-coordination.d.ts.map +0 -1
  117. package/dist/mcp/tools/agent-runs.d.ts +0 -9
  118. package/dist/mcp/tools/agent-runs.d.ts.map +0 -1
  119. package/dist/mcp/tools/agent-workflow-demo.d.ts +0 -8
  120. package/dist/mcp/tools/agent-workflow-demo.d.ts.map +0 -1
  121. package/dist/mcp/tools/approval-gates.d.ts +0 -9
  122. package/dist/mcp/tools/approval-gates.d.ts.map +0 -1
  123. package/dist/mcp/tools/artifacts.d.ts +0 -9
  124. package/dist/mcp/tools/artifacts.d.ts.map +0 -1
  125. package/dist/mcp/tools/branch-work-plans.d.ts +0 -8
  126. package/dist/mcp/tools/branch-work-plans.d.ts.map +0 -1
  127. package/dist/mcp/tools/cli-docs.d.ts +0 -8
  128. package/dist/mcp/tools/cli-docs.d.ts.map +0 -1
  129. package/dist/mcp/tools/command-aliases.d.ts +0 -8
  130. package/dist/mcp/tools/command-aliases.d.ts.map +0 -1
  131. package/dist/mcp/tools/context-packs.d.ts +0 -9
  132. package/dist/mcp/tools/context-packs.d.ts.map +0 -1
  133. package/dist/mcp/tools/crypto.d.ts +0 -8
  134. package/dist/mcp/tools/crypto.d.ts.map +0 -1
  135. package/dist/mcp/tools/db-backup.d.ts +0 -8
  136. package/dist/mcp/tools/db-backup.d.ts.map +0 -1
  137. package/dist/mcp/tools/decision-records.d.ts +0 -9
  138. package/dist/mcp/tools/decision-records.d.ts.map +0 -1
  139. package/dist/mcp/tools/dependency-graph.d.ts +0 -9
  140. package/dist/mcp/tools/dependency-graph.d.ts.map +0 -1
  141. package/dist/mcp/tools/failure-triage.d.ts +0 -9
  142. package/dist/mcp/tools/failure-triage.d.ts.map +0 -1
  143. package/dist/mcp/tools/feature-manifest.d.ts +0 -8
  144. package/dist/mcp/tools/feature-manifest.d.ts.map +0 -1
  145. package/dist/mcp/tools/git-traceability.d.ts +0 -9
  146. package/dist/mcp/tools/git-traceability.d.ts.map +0 -1
  147. package/dist/mcp/tools/goal.d.ts +0 -9
  148. package/dist/mcp/tools/goal.d.ts.map +0 -1
  149. package/dist/mcp/tools/handoff-packets.d.ts +0 -9
  150. package/dist/mcp/tools/handoff-packets.d.ts.map +0 -1
  151. package/dist/mcp/tools/import-export-bridge.d.ts +0 -9
  152. package/dist/mcp/tools/import-export-bridge.d.ts.map +0 -1
  153. package/dist/mcp/tools/inbox-intake.d.ts +0 -9
  154. package/dist/mcp/tools/inbox-intake.d.ts.map +0 -1
  155. package/dist/mcp/tools/issue-importers.d.ts +0 -9
  156. package/dist/mcp/tools/issue-importers.d.ts.map +0 -1
  157. package/dist/mcp/tools/json-schemas.d.ts +0 -8
  158. package/dist/mcp/tools/json-schemas.d.ts.map +0 -1
  159. package/dist/mcp/tools/machine-topology.d.ts +0 -8
  160. package/dist/mcp/tools/machine-topology.d.ts.map +0 -1
  161. package/dist/mcp/tools/mention-resolver.d.ts +0 -9
  162. package/dist/mcp/tools/mention-resolver.d.ts.map +0 -1
  163. package/dist/mcp/tools/nl-intake.d.ts +0 -9
  164. package/dist/mcp/tools/nl-intake.d.ts.map +0 -1
  165. package/dist/mcp/tools/notification-reminders.d.ts +0 -9
  166. package/dist/mcp/tools/notification-reminders.d.ts.map +0 -1
  167. package/dist/mcp/tools/parity.d.ts +0 -8
  168. package/dist/mcp/tools/parity.d.ts.map +0 -1
  169. package/dist/mcp/tools/plan-execution.d.ts +0 -9
  170. package/dist/mcp/tools/plan-execution.d.ts.map +0 -1
  171. package/dist/mcp/tools/policy-packs.d.ts +0 -9
  172. package/dist/mcp/tools/policy-packs.d.ts.map +0 -1
  173. package/dist/mcp/tools/project-bootstrap.d.ts +0 -8
  174. package/dist/mcp/tools/project-bootstrap.d.ts.map +0 -1
  175. package/dist/mcp/tools/release-checks.d.ts +0 -8
  176. package/dist/mcp/tools/release-checks.d.ts.map +0 -1
  177. package/dist/mcp/tools/release-notes.d.ts +0 -9
  178. package/dist/mcp/tools/release-notes.d.ts.map +0 -1
  179. package/dist/mcp/tools/report-exports.d.ts +0 -9
  180. package/dist/mcp/tools/report-exports.d.ts.map +0 -1
  181. package/dist/mcp/tools/resource-subscriptions.d.ts +0 -8
  182. package/dist/mcp/tools/resource-subscriptions.d.ts.map +0 -1
  183. package/dist/mcp/tools/run-records.d.ts +0 -9
  184. package/dist/mcp/tools/run-records.d.ts.map +0 -1
  185. package/dist/mcp/tools/sandbox.d.ts +0 -8
  186. package/dist/mcp/tools/sandbox.d.ts.map +0 -1
  187. package/dist/mcp/tools/saved-views.d.ts +0 -9
  188. package/dist/mcp/tools/saved-views.d.ts.map +0 -1
  189. package/dist/mcp/tools/secret-redaction.d.ts +0 -8
  190. package/dist/mcp/tools/secret-redaction.d.ts.map +0 -1
  191. package/dist/mcp/tools/task-dedupe.d.ts +0 -9
  192. package/dist/mcp/tools/task-dedupe.d.ts.map +0 -1
  193. package/dist/mcp/tools/task-scheduling.d.ts +0 -9
  194. package/dist/mcp/tools/task-scheduling.d.ts.map +0 -1
  195. package/dist/mcp/tools/template-library.d.ts +0 -8
  196. package/dist/mcp/tools/template-library.d.ts.map +0 -1
  197. package/dist/mcp/tools/terminal-notifications.d.ts +0 -9
  198. package/dist/mcp/tools/terminal-notifications.d.ts.map +0 -1
  199. package/dist/mcp/tools/todos-md.d.ts +0 -8
  200. package/dist/mcp/tools/todos-md.d.ts.map +0 -1
  201. package/dist/mcp/tools/user-scaffolds.d.ts +0 -8
  202. package/dist/mcp/tools/user-scaffolds.d.ts.map +0 -1
  203. package/dist/mcp/tools/verification.d.ts +0 -9
  204. package/dist/mcp/tools/verification.d.ts.map +0 -1
  205. package/dist/mcp/tools/webhooks.d.ts +0 -8
  206. package/dist/mcp/tools/webhooks.d.ts.map +0 -1
  207. package/dist/mcp/tools/workspace-trust.d.ts +0 -8
  208. package/dist/mcp/tools/workspace-trust.d.ts.map +0 -1
  209. package/dist/test/no-network.d.ts +0 -7
  210. package/dist/test/no-network.d.ts.map +0 -1
package/dist/mcp/index.js CHANGED
@@ -1224,6 +1224,15 @@ function ensureSchema(db) {
1224
1224
  task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1225
1225
  tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
1226
1226
  )`);
1227
+ ensureTable("tags", `
1228
+ CREATE TABLE tags (
1229
+ id TEXT PRIMARY KEY,
1230
+ name TEXT NOT NULL UNIQUE,
1231
+ color TEXT,
1232
+ description TEXT,
1233
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1234
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1235
+ )`);
1227
1236
  ensureTable("task_dependencies", `
1228
1237
  CREATE TABLE task_dependencies (
1229
1238
  task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
@@ -1437,6 +1446,36 @@ function ensureSchema(db) {
1437
1446
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_task ON task_git_refs(task_id)");
1438
1447
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_lookup ON task_git_refs(ref_type, name)");
1439
1448
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_url ON task_git_refs(url)");
1449
+ ensureTable("task_commits", `
1450
+ CREATE TABLE task_commits (
1451
+ id TEXT PRIMARY KEY,
1452
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1453
+ sha TEXT NOT NULL,
1454
+ message TEXT,
1455
+ author TEXT,
1456
+ files_changed TEXT,
1457
+ committed_at TEXT,
1458
+ branch TEXT,
1459
+ pr_url TEXT,
1460
+ pr_number INTEGER,
1461
+ pr_state TEXT,
1462
+ ci_snapshot TEXT,
1463
+ release_tag TEXT,
1464
+ repo_path TEXT,
1465
+ traceability TEXT DEFAULT '{}',
1466
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1467
+ UNIQUE(task_id, sha)
1468
+ )`);
1469
+ ensureColumn("task_commits", "branch", "TEXT");
1470
+ ensureColumn("task_commits", "pr_url", "TEXT");
1471
+ ensureColumn("task_commits", "pr_number", "INTEGER");
1472
+ ensureColumn("task_commits", "pr_state", "TEXT");
1473
+ ensureColumn("task_commits", "ci_snapshot", "TEXT");
1474
+ ensureColumn("task_commits", "release_tag", "TEXT");
1475
+ ensureColumn("task_commits", "repo_path", "TEXT");
1476
+ ensureColumn("task_commits", "traceability", "TEXT DEFAULT '{}'");
1477
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_commits_task ON task_commits(task_id)");
1478
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_commits_sha ON task_commits(sha)");
1440
1479
  ensureTable("task_verifications", `
1441
1480
  CREATE TABLE task_verifications (
1442
1481
  id TEXT PRIMARY KEY,
@@ -1621,6 +1660,9 @@ function ensureSchema(db) {
1621
1660
  ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
1622
1661
  ensureColumn("tasks", "retry_after", "TEXT");
1623
1662
  ensureColumn("tasks", "sla_minutes", "INTEGER");
1663
+ ensureColumn("tasks", "scheduled_start_at", "TEXT");
1664
+ ensureColumn("tasks", "priority_score", "INTEGER");
1665
+ ensureColumn("tasks", "priority_reason", "TEXT");
1624
1666
  ensureColumn("tasks", "archived_at", "TEXT");
1625
1667
  ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
1626
1668
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
@@ -1743,6 +1785,7 @@ function ensureSchema(db) {
1743
1785
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
1744
1786
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
1745
1787
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
1788
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)");
1746
1789
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
1747
1790
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
1748
1791
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
@@ -1880,6 +1923,217 @@ function ensureSchema(db) {
1880
1923
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date)");
1881
1924
  ensureColumn("tasks", "cycle_id", "TEXT REFERENCES cycles(id) ON DELETE SET NULL");
1882
1925
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL");
1926
+ ensureTable("labels", `
1927
+ CREATE TABLE labels (
1928
+ id TEXT PRIMARY KEY,
1929
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1930
+ name TEXT NOT NULL,
1931
+ color TEXT,
1932
+ description TEXT,
1933
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1934
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
1935
+ UNIQUE(project_id, name)
1936
+ )`);
1937
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_labels_project ON labels(project_id)");
1938
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_labels_name ON labels(name)");
1939
+ ensureTable("task_labels", `
1940
+ CREATE TABLE task_labels (
1941
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1942
+ label_id TEXT NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
1943
+ PRIMARY KEY (task_id, label_id)
1944
+ )`);
1945
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_labels_task ON task_labels(task_id)");
1946
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_labels_label ON task_labels(label_id)");
1947
+ ensureTable("custom_field_definitions", `
1948
+ CREATE TABLE custom_field_definitions (
1949
+ id TEXT PRIMARY KEY,
1950
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1951
+ name TEXT NOT NULL,
1952
+ slug TEXT NOT NULL,
1953
+ field_type TEXT NOT NULL CHECK(field_type IN ('text', 'number', 'boolean', 'date', 'enum')),
1954
+ options TEXT DEFAULT '[]',
1955
+ required INTEGER NOT NULL DEFAULT 0,
1956
+ default_value TEXT,
1957
+ sort_order INTEGER NOT NULL DEFAULT 0,
1958
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1959
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
1960
+ UNIQUE(project_id, slug)
1961
+ )`);
1962
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_custom_fields_project ON custom_field_definitions(project_id)");
1963
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_custom_fields_slug ON custom_field_definitions(slug)");
1964
+ ensureTable("task_custom_field_values", `
1965
+ CREATE TABLE task_custom_field_values (
1966
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1967
+ field_id TEXT NOT NULL REFERENCES custom_field_definitions(id) ON DELETE CASCADE,
1968
+ value TEXT,
1969
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
1970
+ PRIMARY KEY (task_id, field_id)
1971
+ )`);
1972
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_custom_values_task ON task_custom_field_values(task_id)");
1973
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_custom_values_field ON task_custom_field_values(field_id)");
1974
+ ensureTable("saved_views", `
1975
+ CREATE TABLE saved_views (
1976
+ id TEXT PRIMARY KEY,
1977
+ name TEXT NOT NULL UNIQUE,
1978
+ slug TEXT NOT NULL UNIQUE,
1979
+ entity_type TEXT NOT NULL DEFAULT 'task',
1980
+ filters TEXT NOT NULL DEFAULT '{}',
1981
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1982
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1983
+ )`);
1984
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_views_slug ON saved_views(slug)");
1985
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_views_entity ON saved_views(entity_type)");
1986
+ ensureTable("decision_records", `
1987
+ CREATE TABLE decision_records (
1988
+ id TEXT PRIMARY KEY,
1989
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
1990
+ task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
1991
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
1992
+ agent_id TEXT,
1993
+ sequence_num INTEGER NOT NULL,
1994
+ short_ref TEXT NOT NULL UNIQUE,
1995
+ title TEXT NOT NULL,
1996
+ status TEXT NOT NULL DEFAULT 'proposed' CHECK(status IN ('proposed', 'accepted', 'deprecated', 'superseded', 'rejected')),
1997
+ context TEXT,
1998
+ decision TEXT NOT NULL,
1999
+ consequences TEXT,
2000
+ alternatives TEXT DEFAULT '[]',
2001
+ tags TEXT DEFAULT '[]',
2002
+ supersedes_id TEXT REFERENCES decision_records(id) ON DELETE SET NULL,
2003
+ superseded_by_id TEXT REFERENCES decision_records(id) ON DELETE SET NULL,
2004
+ metadata TEXT DEFAULT '{}',
2005
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2006
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2007
+ )`);
2008
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_project ON decision_records(project_id)");
2009
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_task ON decision_records(task_id)");
2010
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_plan ON decision_records(plan_id)");
2011
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_status ON decision_records(status)");
2012
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_short_ref ON decision_records(short_ref)");
2013
+ ensureTable("knowledge_snapshots", `
2014
+ CREATE TABLE knowledge_snapshots (
2015
+ id TEXT PRIMARY KEY,
2016
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2017
+ title TEXT NOT NULL,
2018
+ summary TEXT,
2019
+ content_hash TEXT NOT NULL,
2020
+ snapshot TEXT NOT NULL,
2021
+ decision_ids TEXT DEFAULT '[]',
2022
+ topics TEXT DEFAULT '[]',
2023
+ source TEXT NOT NULL DEFAULT 'auto' CHECK(source IN ('manual', 'auto', 'import')),
2024
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2025
+ )`);
2026
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_knowledge_snapshots_project ON knowledge_snapshots(project_id)");
2027
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_knowledge_snapshots_hash ON knowledge_snapshots(content_hash)");
2028
+ ensureTable("verification_records", `
2029
+ CREATE TABLE verification_records (
2030
+ id TEXT PRIMARY KEY,
2031
+ task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE,
2032
+ provider_name TEXT NOT NULL,
2033
+ provider_type TEXT NOT NULL,
2034
+ status TEXT NOT NULL DEFAULT 'unknown' CHECK(status IN ('passed', 'failed', 'unknown')),
2035
+ summary TEXT,
2036
+ evidence TEXT DEFAULT '{}',
2037
+ artifact_id TEXT,
2038
+ started_at TEXT,
2039
+ completed_at TEXT,
2040
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2041
+ )`);
2042
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_verification_records_task ON verification_records(task_id)");
2043
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_verification_records_status ON verification_records(status)");
2044
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_verification_records_created ON verification_records(created_at)");
2045
+ ensureTable("task_leases", `
2046
+ CREATE TABLE task_leases (
2047
+ task_id TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
2048
+ agent_id TEXT NOT NULL,
2049
+ acquired_at TEXT NOT NULL,
2050
+ expires_at TEXT NOT NULL,
2051
+ heartbeat_at TEXT,
2052
+ steal_count INTEGER NOT NULL DEFAULT 0,
2053
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2054
+ )`);
2055
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_leases_agent ON task_leases(agent_id)");
2056
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_leases_expires ON task_leases(expires_at)");
2057
+ ensureTable("reminder_preferences", `
2058
+ CREATE TABLE reminder_preferences (
2059
+ id TEXT PRIMARY KEY,
2060
+ due_soon_hours INTEGER NOT NULL DEFAULT 24,
2061
+ sla_warning_minutes INTEGER NOT NULL DEFAULT 30,
2062
+ enabled INTEGER NOT NULL DEFAULT 1,
2063
+ desktop_notify INTEGER NOT NULL DEFAULT 0,
2064
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2065
+ )`);
2066
+ ensureTable("notification_reminders", `
2067
+ CREATE TABLE notification_reminders (
2068
+ id TEXT PRIMARY KEY,
2069
+ task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE,
2070
+ reminder_type TEXT NOT NULL CHECK(reminder_type IN ('due_soon', 'due_overdue', 'sla_warning', 'sla_breach', 'custom')),
2071
+ title TEXT NOT NULL,
2072
+ message TEXT,
2073
+ trigger_at TEXT NOT NULL,
2074
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'fired', 'dismissed', 'snoozed')),
2075
+ snoozed_until TEXT,
2076
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2077
+ agent_id TEXT,
2078
+ priority TEXT NOT NULL DEFAULT 'medium',
2079
+ metadata TEXT DEFAULT '{}',
2080
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2081
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2082
+ fired_at TEXT,
2083
+ dismissed_at TEXT
2084
+ )`);
2085
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_task ON notification_reminders(task_id)");
2086
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_status ON notification_reminders(status)");
2087
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_trigger ON notification_reminders(trigger_at)");
2088
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_project ON notification_reminders(project_id)");
2089
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_agent ON notification_reminders(agent_id)");
2090
+ ensureTable("run_records", `
2091
+ CREATE TABLE run_records (
2092
+ id TEXT PRIMARY KEY,
2093
+ agent_run_id TEXT,
2094
+ agent_id TEXT,
2095
+ objective TEXT,
2096
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
2097
+ claimed_task_ids TEXT DEFAULT '[]',
2098
+ commands TEXT DEFAULT '[]',
2099
+ stdout_summary TEXT,
2100
+ stderr_summary TEXT,
2101
+ files_touched TEXT DEFAULT '[]',
2102
+ verification_results TEXT DEFAULT '[]',
2103
+ artifact_ids TEXT DEFAULT '[]',
2104
+ status_transitions TEXT DEFAULT '[]',
2105
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'failed', 'archived')),
2106
+ replay_bundle TEXT,
2107
+ metadata TEXT DEFAULT '{}',
2108
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
2109
+ completed_at TEXT,
2110
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2111
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2112
+ )`);
2113
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_agent_run ON run_records(agent_run_id)");
2114
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_agent ON run_records(agent_id)");
2115
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_plan ON run_records(plan_id)");
2116
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_status ON run_records(status)");
2117
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_started ON run_records(started_at)");
2118
+ ensureTable("activity_log", `
2119
+ CREATE TABLE activity_log (
2120
+ id TEXT PRIMARY KEY,
2121
+ entity_type TEXT NOT NULL,
2122
+ entity_id TEXT NOT NULL,
2123
+ action TEXT NOT NULL,
2124
+ field TEXT,
2125
+ old_value TEXT,
2126
+ new_value TEXT,
2127
+ actor_id TEXT,
2128
+ session_id TEXT,
2129
+ machine_id TEXT,
2130
+ metadata TEXT DEFAULT '{}',
2131
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2132
+ )`);
2133
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_entity ON activity_log(entity_type, entity_id)");
2134
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_actor ON activity_log(actor_id)");
2135
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_action ON activity_log(action)");
2136
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_created ON activity_log(created_at)");
1883
2137
  ensureTable("api_keys", `
1884
2138
  CREATE TABLE api_keys (
1885
2139
  id TEXT PRIMARY KEY,
@@ -2331,11 +2585,17 @@ function ensureDir(filePath) {
2331
2585
  }
2332
2586
  }
2333
2587
  function getDatabase(dbPath) {
2334
- if (_db)
2335
- return _db;
2336
2588
  const path = dbPath || getDbPath();
2589
+ if (_db && _dbPath === path)
2590
+ return _db;
2591
+ if (_db && _dbPath !== path) {
2592
+ _db.close();
2593
+ _db = null;
2594
+ _dbPath = null;
2595
+ }
2337
2596
  ensureDir(path);
2338
2597
  _db = new Database(path);
2598
+ _dbPath = path;
2339
2599
  _db.run("PRAGMA journal_mode = WAL");
2340
2600
  _db.run("PRAGMA busy_timeout = 5000");
2341
2601
  _db.run("PRAGMA foreign_keys = ON");
@@ -2348,10 +2608,12 @@ function closeDatabase() {
2348
2608
  if (_db) {
2349
2609
  _db.close();
2350
2610
  _db = null;
2611
+ _dbPath = null;
2351
2612
  }
2352
2613
  }
2353
2614
  function resetDatabase() {
2354
2615
  _db = null;
2616
+ _dbPath = null;
2355
2617
  }
2356
2618
  function now() {
2357
2619
  return new Date().toISOString();
@@ -2412,7 +2674,7 @@ function resolvePartialId(db, table, partialId) {
2412
2674
  }
2413
2675
  return null;
2414
2676
  }
2415
- var LOCK_EXPIRY_MINUTES = 30, _db = null, ALLOWED_TABLES;
2677
+ var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
2416
2678
  var init_database = __esm(() => {
2417
2679
  init_schema();
2418
2680
  init_machines();
@@ -8423,9 +8685,10 @@ function redactExportRecord(record) {
8423
8685
  }
8424
8686
  return base;
8425
8687
  }
8426
- var REDACTION_PLACEHOLDER = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
8688
+ var SECRET_REDACTION_SCHEMA, REDACTION_PLACEHOLDER = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
8427
8689
  var init_secret_redaction = __esm(() => {
8428
8690
  init_redaction();
8691
+ SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
8429
8692
  DEFAULT_PATTERNS = [
8430
8693
  { name: "openai_sk", pattern: /\bsk-[a-zA-Z0-9]{10,}\b/g },
8431
8694
  { name: "github_pat", pattern: /\bghp_[a-zA-Z0-9]{20,}\b/g },
@@ -12293,11 +12556,19 @@ function getTaskVerifications(taskId, db) {
12293
12556
  }
12294
12557
  function getTaskTraceability(taskId, db) {
12295
12558
  const d = db || getDatabase();
12559
+ const commits = getTaskCommits(taskId, d);
12560
+ const gitRefs = getTaskGitRefs(taskId, d);
12296
12561
  return {
12297
12562
  task_id: taskId,
12298
- commits: getTaskCommits(taskId, d),
12299
- git_refs: getTaskGitRefs(taskId, d),
12300
- verifications: getTaskVerifications(taskId, d)
12563
+ commits,
12564
+ git_refs: gitRefs,
12565
+ verifications: getTaskVerifications(taskId, d),
12566
+ branches: Array.from(new Set([
12567
+ ...commits.map((commit) => commit.branch).filter((branch) => Boolean(branch)),
12568
+ ...gitRefs.filter((ref) => ref.ref_type === "branch").map((ref) => ref.name)
12569
+ ])).sort(),
12570
+ release_tags: Array.from(new Set(commits.map((commit) => commit.release_tag).filter((tag) => Boolean(tag)))).sort(),
12571
+ pull_requests: commits.filter((commit) => commit.pr_url).map((commit) => ({ url: commit.pr_url, state: commit.pr_state, number: commit.pr_number }))
12301
12572
  };
12302
12573
  }
12303
12574
  var init_task_commits = __esm(() => {
@@ -14069,6 +14340,13 @@ function registerTaskCrudTools(server, ctx) {
14069
14340
  }
14070
14341
  return current.version;
14071
14342
  }
14343
+ function resolveAssignee(value) {
14344
+ try {
14345
+ return resolveId(value, "agents");
14346
+ } catch {
14347
+ return value;
14348
+ }
14349
+ }
14072
14350
  if (shouldRegisterTool("create_task")) {
14073
14351
  server.tool("create_task", "Create a new task in a project. Pass short_id=null to auto-generate.", {
14074
14352
  title: exports_external.string().describe("Task title"),
@@ -14091,7 +14369,7 @@ function registerTaskCrudTools(server, ctx) {
14091
14369
  const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, ...rest } = params;
14092
14370
  const resolved = { ...rest };
14093
14371
  if (assigned_to)
14094
- resolved.assigned_to = resolveId(assigned_to, "agents");
14372
+ resolved.assigned_to = resolveAssignee(assigned_to);
14095
14373
  if (project_id)
14096
14374
  resolved.project_id = resolveId(project_id, "projects");
14097
14375
  if (task_list_id)
@@ -14135,7 +14413,7 @@ function registerTaskCrudTools(server, ctx) {
14135
14413
  if (params.task_list_id)
14136
14414
  resolved.task_list_id = resolveId(params.task_list_id, "task_lists");
14137
14415
  if (params.assigned_to)
14138
- resolved.assigned_to = resolveId(params.assigned_to, "agents");
14416
+ resolved.assigned_to = resolveAssignee(params.assigned_to);
14139
14417
  const tasks = listTasks(resolved, undefined);
14140
14418
  if (tasks.length === 0)
14141
14419
  return { content: [{ type: "text", text: "No tasks found." }] };
@@ -14232,7 +14510,7 @@ ${task.description}` : null
14232
14510
  if (resolved.assigned_to === "")
14233
14511
  resolved.assigned_to = null;
14234
14512
  if (resolved.assigned_to && typeof resolved.assigned_to === "string")
14235
- resolved.assigned_to = resolveId(resolved.assigned_to, "agents");
14513
+ resolved.assigned_to = resolveAssignee(resolved.assigned_to);
14236
14514
  if (resolved.project_id && typeof resolved.project_id === "string")
14237
14515
  resolved.project_id = resolveId(resolved.project_id, "projects");
14238
14516
  if (resolved.task_list_id && typeof resolved.task_list_id === "string")
@@ -14442,6 +14720,194 @@ var init_project_bootstrap = __esm(() => {
14442
14720
  init_task_lists();
14443
14721
  });
14444
14722
 
14723
+ // src/db/labels.ts
14724
+ function rowToLabel(row) {
14725
+ return { ...row };
14726
+ }
14727
+ function normalizeName(name) {
14728
+ return name.trim().toLowerCase();
14729
+ }
14730
+ function createLabel(input, db) {
14731
+ const d = db || getDatabase();
14732
+ const id = uuid();
14733
+ const ts = now();
14734
+ d.run(`INSERT INTO labels (id, project_id, name, color, description, created_at, updated_at)
14735
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id ?? null, input.name.trim(), input.color ?? null, input.description ?? null, ts, ts]);
14736
+ return rowToLabel(d.query("SELECT * FROM labels WHERE id = ?").get(id));
14737
+ }
14738
+ function getLabel(idOrName, db) {
14739
+ const d = db || getDatabase();
14740
+ let row = d.query("SELECT * FROM labels WHERE id = ?").get(idOrName);
14741
+ if (!row) {
14742
+ row = d.query("SELECT * FROM labels WHERE lower(name) = ?").get(normalizeName(idOrName));
14743
+ }
14744
+ return row ? rowToLabel(row) : null;
14745
+ }
14746
+ function listLabels(projectId, db) {
14747
+ const d = db || getDatabase();
14748
+ if (projectId) {
14749
+ return d.query("SELECT * FROM labels WHERE project_id IS NULL OR project_id = ? ORDER BY name").all(projectId).map(rowToLabel);
14750
+ }
14751
+ return d.query("SELECT * FROM labels ORDER BY name").all().map(rowToLabel);
14752
+ }
14753
+ function updateLabel(idOrName, input, db) {
14754
+ const d = db || getDatabase();
14755
+ const existing = getLabel(idOrName, d);
14756
+ if (!existing)
14757
+ throw new Error(`Label not found: ${idOrName}`);
14758
+ const ts = now();
14759
+ d.run(`UPDATE labels SET
14760
+ name = COALESCE(?, name),
14761
+ color = COALESCE(?, color),
14762
+ description = COALESCE(?, description),
14763
+ updated_at = ?
14764
+ WHERE id = ?`, [input.name?.trim() ?? null, input.color ?? null, input.description ?? null, ts, existing.id]);
14765
+ return getLabel(existing.id, d);
14766
+ }
14767
+ function deleteLabel(idOrName, db) {
14768
+ const d = db || getDatabase();
14769
+ const existing = getLabel(idOrName, d);
14770
+ if (!existing)
14771
+ return false;
14772
+ d.run("DELETE FROM task_labels WHERE label_id = ?", [existing.id]);
14773
+ return d.run("DELETE FROM labels WHERE id = ?", [existing.id]).changes > 0;
14774
+ }
14775
+ function assignLabelToTask(taskId, labelIdOrName, db) {
14776
+ const d = db || getDatabase();
14777
+ const label = getLabel(labelIdOrName, d);
14778
+ if (!label)
14779
+ throw new Error(`Label not found: ${labelIdOrName}`);
14780
+ d.run("INSERT OR IGNORE INTO task_labels (task_id, label_id) VALUES (?, ?)", [taskId, label.id]);
14781
+ d.run("INSERT OR IGNORE INTO task_tags (task_id, tag) VALUES (?, ?)", [taskId, label.name]);
14782
+ return label;
14783
+ }
14784
+ var init_labels = __esm(() => {
14785
+ init_database();
14786
+ });
14787
+
14788
+ // src/db/tags.ts
14789
+ function normalizeName2(name) {
14790
+ return name.trim();
14791
+ }
14792
+ function taskCount(name, db) {
14793
+ const row = db.query("SELECT COUNT(*) AS count FROM task_tags WHERE tag = ?").get(name);
14794
+ return row?.count ?? 0;
14795
+ }
14796
+ function rowToTag(row, db) {
14797
+ return {
14798
+ ...row,
14799
+ task_count: taskCount(row.name, db)
14800
+ };
14801
+ }
14802
+ function virtualTag(name, db) {
14803
+ return {
14804
+ id: name,
14805
+ name,
14806
+ color: null,
14807
+ description: null,
14808
+ task_count: taskCount(name, db),
14809
+ created_at: null,
14810
+ updated_at: null
14811
+ };
14812
+ }
14813
+ function getTagRow(idOrName, db) {
14814
+ return db.query("SELECT * FROM tags WHERE id = ?").get(idOrName) ?? db.query("SELECT * FROM tags WHERE lower(name) = lower(?)").get(idOrName);
14815
+ }
14816
+ function updateTaskTagJson(oldName, newName, db) {
14817
+ const rows = db.query("SELECT id, tags FROM tasks WHERE tags IS NOT NULL AND tags != '[]'").all();
14818
+ const update = db.prepare("UPDATE tasks SET tags = ?, updated_at = ? WHERE id = ?");
14819
+ const ts = now();
14820
+ for (const row of rows) {
14821
+ if (!row.tags)
14822
+ continue;
14823
+ let tags;
14824
+ try {
14825
+ tags = JSON.parse(row.tags);
14826
+ } catch {
14827
+ continue;
14828
+ }
14829
+ if (!tags.includes(oldName))
14830
+ continue;
14831
+ const next = newName ? Array.from(new Set(tags.map((tag) => tag === oldName ? newName : tag))) : tags.filter((tag) => tag !== oldName);
14832
+ update.run(JSON.stringify(next), ts, row.id);
14833
+ }
14834
+ }
14835
+ function renameTaskTagRows(oldName, newName, db) {
14836
+ const rows = db.query("SELECT task_id FROM task_tags WHERE tag = ?").all(oldName);
14837
+ const insert = db.prepare("INSERT OR IGNORE INTO task_tags (task_id, tag) VALUES (?, ?)");
14838
+ for (const row of rows)
14839
+ insert.run(row.task_id, newName);
14840
+ db.run("DELETE FROM task_tags WHERE tag = ?", [oldName]);
14841
+ }
14842
+ function createTag(input, db) {
14843
+ const d = db || getDatabase();
14844
+ const name = normalizeName2(input.name);
14845
+ if (!name)
14846
+ throw new Error("Tag name is required");
14847
+ const id = uuid();
14848
+ const ts = now();
14849
+ d.run(`INSERT INTO tags (id, name, color, description, created_at, updated_at)
14850
+ VALUES (?, ?, ?, ?, ?, ?)`, [id, name, input.color ?? null, input.description ?? null, ts, ts]);
14851
+ return rowToTag(d.query("SELECT * FROM tags WHERE id = ?").get(id), d);
14852
+ }
14853
+ function listTags(db) {
14854
+ const d = db || getDatabase();
14855
+ const rows = d.query("SELECT * FROM tags ORDER BY name").all();
14856
+ const tagsByName = new Map(rows.map((row) => [row.name.toLowerCase(), rowToTag(row, d)]));
14857
+ const taskTags = d.query("SELECT tag, COUNT(*) AS count FROM task_tags GROUP BY tag ORDER BY tag").all();
14858
+ for (const row of taskTags) {
14859
+ const key = row.tag.toLowerCase();
14860
+ if (!tagsByName.has(key))
14861
+ tagsByName.set(key, { ...virtualTag(row.tag, d), task_count: row.count });
14862
+ }
14863
+ return [...tagsByName.values()].sort((a, b) => a.name.localeCompare(b.name));
14864
+ }
14865
+ function getTag(idOrName, db) {
14866
+ const d = db || getDatabase();
14867
+ const row = getTagRow(idOrName, d);
14868
+ if (row)
14869
+ return rowToTag(row, d);
14870
+ const existing = d.query("SELECT tag FROM task_tags WHERE lower(tag) = lower(?) LIMIT 1").get(idOrName);
14871
+ return existing ? virtualTag(existing.tag, d) : null;
14872
+ }
14873
+ function updateTag(idOrName, input, db) {
14874
+ const d = db || getDatabase();
14875
+ const existing = getTag(idOrName, d);
14876
+ if (!existing)
14877
+ throw new Error(`Tag not found: ${idOrName}`);
14878
+ const row = getTagRow(existing.id, d);
14879
+ if (!row)
14880
+ createTag({ name: existing.name, color: existing.color ?? undefined, description: existing.description ?? undefined }, d);
14881
+ const stored = getTagRow(existing.id, d) ?? getTagRow(existing.name, d);
14882
+ if (!stored)
14883
+ throw new Error(`Tag not found: ${idOrName}`);
14884
+ const nextName = input.name !== undefined ? normalizeName2(input.name) : stored.name;
14885
+ if (!nextName)
14886
+ throw new Error("Tag name is required");
14887
+ const duplicate = getTagRow(nextName, d);
14888
+ if (duplicate && duplicate.id !== stored.id)
14889
+ throw new Error(`Tag already exists: ${nextName}`);
14890
+ if (nextName !== stored.name) {
14891
+ renameTaskTagRows(stored.name, nextName, d);
14892
+ updateTaskTagJson(stored.name, nextName, d);
14893
+ }
14894
+ d.run(`UPDATE tags SET name = ?, color = COALESCE(?, color), description = COALESCE(?, description), updated_at = ? WHERE id = ?`, [nextName, input.color ?? null, input.description ?? null, now(), stored.id]);
14895
+ return getTag(stored.id, d);
14896
+ }
14897
+ function deleteTag(idOrName, db) {
14898
+ const d = db || getDatabase();
14899
+ const existing = getTag(idOrName, d);
14900
+ if (!existing)
14901
+ return false;
14902
+ d.run("DELETE FROM tags WHERE id = ? OR lower(name) = lower(?)", [existing.id, existing.name]);
14903
+ d.run("DELETE FROM task_tags WHERE tag = ?", [existing.name]);
14904
+ updateTaskTagJson(existing.name, null, d);
14905
+ return true;
14906
+ }
14907
+ var init_tags = __esm(() => {
14908
+ init_database();
14909
+ });
14910
+
14445
14911
  // src/lib/retention-cleanup.ts
14446
14912
  import { existsSync as existsSync7, unlinkSync } from "fs";
14447
14913
  function normalizeScopes(scopes) {
@@ -16676,18 +17142,18 @@ var init_local_fields = __esm(() => {
16676
17142
  });
16677
17143
 
16678
17144
  // src/lib/workflow-states.ts
16679
- function normalizeName(value) {
17145
+ function normalizeName3(value) {
16680
17146
  return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
16681
17147
  }
16682
17148
  function isTaskStatus(value) {
16683
17149
  return TASK_STATUSES.includes(value);
16684
17150
  }
16685
17151
  function normalizeState(input) {
16686
- const name = normalizeName(input.name || "");
17152
+ const name = normalizeName3(input.name || "");
16687
17153
  if (!name || !isTaskStatus(input.canonical_status))
16688
17154
  return null;
16689
- const aliases = [...new Set((input.aliases || []).map(normalizeName).filter((alias) => alias && alias !== name))].sort();
16690
- const transitions = input.transitions ? [...new Set(input.transitions.map(normalizeName).filter(Boolean))].sort() : null;
17155
+ const aliases = [...new Set((input.aliases || []).map(normalizeName3).filter((alias) => alias && alias !== name))].sort();
17156
+ const transitions = input.transitions ? [...new Set(input.transitions.map(normalizeName3).filter(Boolean))].sort() : null;
16691
17157
  return {
16692
17158
  name,
16693
17159
  canonical_status: input.canonical_status,
@@ -16716,7 +17182,7 @@ function listWorkflowStates(projectPath) {
16716
17182
  return mergeWorkflowConfig(workflowConfig);
16717
17183
  }
16718
17184
  function resolveWorkflowState(input, projectPath) {
16719
- const normalized = normalizeName(input);
17185
+ const normalized = normalizeName3(input);
16720
17186
  const states = listWorkflowStates(projectPath);
16721
17187
  const byName = states.find((state) => state.name === normalized);
16722
17188
  if (byName)
@@ -17706,10 +18172,10 @@ function summarizeTasks(taskIds, planIds, runIds, db) {
17706
18172
  const completed = tasks.filter((task) => task.status === "completed");
17707
18173
  const inProgress = tasks.filter((task) => task.status === "in_progress");
17708
18174
  const pending = tasks.filter((task) => task.status === "pending");
17709
- const taskCount = tasks.length;
17710
- const percent = taskCount === 0 ? 0 : Math.round(completed.length / taskCount * 100);
18175
+ const taskCount2 = tasks.length;
18176
+ const percent = taskCount2 === 0 ? 0 : Math.round(completed.length / taskCount2 * 100);
17711
18177
  return {
17712
- task_count: taskCount,
18178
+ task_count: taskCount2,
17713
18179
  completed_count: completed.length,
17714
18180
  in_progress_count: inProgress.length,
17715
18181
  pending_count: pending.length,
@@ -17717,7 +18183,7 @@ function summarizeTasks(taskIds, planIds, runIds, db) {
17717
18183
  plan_count: planIds.length,
17718
18184
  run_count: runIds.length,
17719
18185
  percent_complete: percent,
17720
- readiness: taskCount === 0 ? "empty" : blocked.length > 0 ? "blocked" : completed.length === taskCount ? "complete" : inProgress.length > 0 ? "in_progress" : "ready"
18186
+ readiness: taskCount2 === 0 ? "empty" : blocked.length > 0 ? "blocked" : completed.length === taskCount2 ? "complete" : inProgress.length > 0 ? "in_progress" : "ready"
17721
18187
  };
17722
18188
  }
17723
18189
  function createRoadmap(input) {
@@ -18896,7 +19362,7 @@ function normalizeScope(scope) {
18896
19362
  }
18897
19363
  return "tasks";
18898
19364
  }
18899
- function normalizeName2(name) {
19365
+ function normalizeName4(name) {
18900
19366
  const normalized = name.trim();
18901
19367
  if (!normalized)
18902
19368
  throw new Error("Saved view name is required");
@@ -19146,7 +19612,7 @@ function runSavedSearch(filters = {}, scope = "tasks", db) {
19146
19612
  }
19147
19613
  function saveSearchView(input, db) {
19148
19614
  const d = db || getDatabase();
19149
- const name = normalizeName2(input.name);
19615
+ const name = normalizeName4(input.name);
19150
19616
  const timestamp3 = now();
19151
19617
  const existing = getSearchView(name, d);
19152
19618
  if (existing) {
@@ -20147,6 +20613,7 @@ function registerTaskProjectTools(server, ctx) {
20147
20613
  if (shouldRegisterTool("create_project")) {
20148
20614
  server.tool("create_project", "Create a new project.", {
20149
20615
  name: exports_external.string().describe("Project name"),
20616
+ path: exports_external.string().describe("Unique filesystem path for the project"),
20150
20617
  description: exports_external.string().optional(),
20151
20618
  status: exports_external.enum(["active", "completed", "on_hold", "archived"]).optional(),
20152
20619
  short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if omitted)"),
@@ -20166,7 +20633,11 @@ function registerTaskProjectTools(server, ctx) {
20166
20633
  limit: exports_external.number().optional()
20167
20634
  }, async ({ status, limit }) => {
20168
20635
  try {
20169
- const projects = listProjects({ status, limit });
20636
+ let projects = listProjects();
20637
+ if (status)
20638
+ projects = projects.filter((p) => p.status === status);
20639
+ if (limit)
20640
+ projects = projects.slice(0, limit);
20170
20641
  if (projects.length === 0)
20171
20642
  return { content: [{ type: "text", text: "No projects found." }] };
20172
20643
  const lines = projects.map((p) => `[${p.status}] ${p.short_id || p.id.slice(0, 8)} ${p.name}`);
@@ -20796,12 +21267,12 @@ Tasks:` : null,
20796
21267
  });
20797
21268
  }
20798
21269
  if (shouldRegisterTool("list_tags")) {
20799
- server.tool("list_tags", "List all tags.", async () => {
21270
+ server.tool("list_tags", "List all distinct task tags in use, with task counts.", async () => {
20800
21271
  try {
20801
- const tags = listTags();
20802
- if (tags.length === 0)
21272
+ const rows = listTags();
21273
+ if (rows.length === 0)
20803
21274
  return { content: [{ type: "text", text: "No tags found." }] };
20804
- const lines = tags.map((t) => `${t.color ? "[" + t.color + "] " : ""}${t.name}`);
21275
+ const lines = rows.map((r) => `${r.color ? "[" + r.color + "] " : ""}${r.name} (${r.task_count})`);
20805
21276
  return { content: [{ type: "text", text: lines.join(`
20806
21277
  `) }] };
20807
21278
  } catch (e) {
@@ -21370,6 +21841,8 @@ var init_task_project_tools = __esm(() => {
21370
21841
  init_comments();
21371
21842
  init_task_runs();
21372
21843
  init_project_bootstrap();
21844
+ init_labels();
21845
+ init_tags();
21373
21846
  init_redaction();
21374
21847
  init_retention_cleanup();
21375
21848
  init_mention_resolver();
@@ -24515,7 +24988,7 @@ var init_agent_run_dispatcher = __esm(() => {
24515
24988
 
24516
24989
  // src/lib/verification-providers.ts
24517
24990
  import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
24518
- function normalizeName3(name) {
24991
+ function normalizeName5(name) {
24519
24992
  const normalized = name.trim().toLowerCase();
24520
24993
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
24521
24994
  throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
@@ -24534,10 +25007,10 @@ function timeoutMs(value) {
24534
25007
  return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
24535
25008
  }
24536
25009
  function getProvider(name) {
24537
- return loadConfig().verification_providers?.[normalizeName3(name)] || null;
25010
+ return loadConfig().verification_providers?.[normalizeName5(name)] || null;
24538
25011
  }
24539
25012
  function upsertVerificationProvider(input) {
24540
- const name = normalizeName3(input.name);
25013
+ const name = normalizeName5(input.name);
24541
25014
  const config = loadConfig();
24542
25015
  const existing = config.verification_providers?.[name];
24543
25016
  const timestamp3 = new Date().toISOString();
@@ -24567,7 +25040,7 @@ function listVerificationProviders() {
24567
25040
  return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
24568
25041
  }
24569
25042
  function removeVerificationProvider(name) {
24570
- const normalized = normalizeName3(name);
25043
+ const normalized = normalizeName5(name);
24571
25044
  const config = loadConfig();
24572
25045
  if (!config.verification_providers?.[normalized])
24573
25046
  return false;
@@ -28927,7 +29400,7 @@ import { basename as basename5, join as join9, resolve as resolve12 } from "path
28927
29400
  function isObject2(value) {
28928
29401
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
28929
29402
  }
28930
- function normalizeName4(name) {
29403
+ function normalizeName6(name) {
28931
29404
  const normalized = name.trim().toLowerCase();
28932
29405
  if (!/^[a-z0-9][a-z0-9_.-]{0,63}$/.test(normalized)) {
28933
29406
  throw new Error("extension name must use lowercase letters, numbers, dots, dashes, or underscores");
@@ -28945,7 +29418,7 @@ function permissionList(value) {
28945
29418
  function normalizeManifest(input) {
28946
29419
  if (!isObject2(input))
28947
29420
  throw new Error("extension manifest must be a JSON object");
28948
- const name = normalizeName4(String(input["name"] || ""));
29421
+ const name = normalizeName6(String(input["name"] || ""));
28949
29422
  const version = String(input["version"] || "").trim();
28950
29423
  if (!version)
28951
29424
  throw new Error("extension manifest requires version");
@@ -28953,7 +29426,7 @@ function normalizeManifest(input) {
28953
29426
  todos: typeof input["compatibility"]["todos"] === "string" ? input["compatibility"]["todos"] : undefined
28954
29427
  } : undefined;
28955
29428
  const commands = Array.isArray(input["commands"]) ? input["commands"].filter(isObject2).map((command) => ({
28956
- name: normalizeName4(String(command["name"] || "")),
29429
+ name: normalizeName6(String(command["name"] || "")),
28957
29430
  command: typeof command["command"] === "string" ? command["command"] : undefined,
28958
29431
  description: typeof command["description"] === "string" ? command["description"] : undefined,
28959
29432
  permissions: permissionList(command["permissions"]),
@@ -28962,12 +29435,12 @@ function normalizeManifest(input) {
28962
29435
  network: typeof command["network"] === "boolean" ? command["network"] : undefined
28963
29436
  })) : [];
28964
29437
  const mcpTools = Array.isArray(input["mcp_tools"]) ? input["mcp_tools"].filter(isObject2).map((tool) => ({
28965
- name: normalizeName4(String(tool["name"] || "")),
29438
+ name: normalizeName6(String(tool["name"] || "")),
28966
29439
  description: typeof tool["description"] === "string" ? tool["description"] : undefined,
28967
29440
  permissions: permissionList(tool["permissions"])
28968
29441
  })) : [];
28969
29442
  const templates = Array.isArray(input["templates"]) ? input["templates"].filter(isObject2).map((template) => ({
28970
- name: normalizeName4(String(template["name"] || "")),
29443
+ name: normalizeName6(String(template["name"] || "")),
28971
29444
  kind: typeof template["kind"] === "string" ? template["kind"] : undefined,
28972
29445
  description: typeof template["description"] === "string" ? template["description"] : undefined,
28973
29446
  path: typeof template["path"] === "string" ? template["path"] : undefined,
@@ -28976,7 +29449,7 @@ function normalizeManifest(input) {
28976
29449
  permissions: permissionList(template["permissions"])
28977
29450
  })) : [];
28978
29451
  const renderers = Array.isArray(input["renderers"]) ? input["renderers"].filter(isObject2).map((renderer) => ({
28979
- name: normalizeName4(String(renderer["name"] || "")),
29452
+ name: normalizeName6(String(renderer["name"] || "")),
28980
29453
  target: typeof renderer["target"] === "string" ? renderer["target"] : "",
28981
29454
  description: typeof renderer["description"] === "string" ? renderer["description"] : undefined,
28982
29455
  command: typeof renderer["command"] === "string" ? renderer["command"] : undefined,
@@ -29375,10 +29848,10 @@ function listLocalExtensions() {
29375
29848
  return Object.values(loadConfig().extension_registry || {}).sort((a, b) => a.name.localeCompare(b.name));
29376
29849
  }
29377
29850
  function getLocalExtension(name) {
29378
- return loadConfig().extension_registry?.[normalizeName4(name)] || null;
29851
+ return loadConfig().extension_registry?.[normalizeName6(name)] || null;
29379
29852
  }
29380
29853
  function removeLocalExtension(name) {
29381
- const normalized = normalizeName4(name);
29854
+ const normalized = normalizeName6(name);
29382
29855
  const config = loadConfig();
29383
29856
  if (!config.extension_registry?.[normalized])
29384
29857
  return false;
@@ -34889,8 +35362,6 @@ var init_builtin_templates = __esm(() => {
34889
35362
  },
34890
35363
  {
34891
35364
  name: "open-source-project",
34892
- version: 1,
34893
- category: "project",
34894
35365
  description: "Full open-source project bootstrap \u2014 scaffold to publish",
34895
35366
  category: "open-source",
34896
35367
  version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
@@ -34916,7 +35387,7 @@ var init_builtin_templates = __esm(() => {
34916
35387
  },
34917
35388
  {
34918
35389
  name: "release",
34919
- version: 1,
35390
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34920
35391
  category: "ops",
34921
35392
  description: "Version release workflow \u2014 test, changelog, publish, verify",
34922
35393
  variables: [
@@ -34933,7 +35404,7 @@ var init_builtin_templates = __esm(() => {
34933
35404
  },
34934
35405
  {
34935
35406
  name: "docs-refresh",
34936
- version: 1,
35407
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34937
35408
  category: "workflow",
34938
35409
  description: "Documentation refresh \u2014 audit, update, verify links",
34939
35410
  variables: [{ name: "scope", required: true, description: "Docs scope (README, API, AGENTS.md)" }],
@@ -34946,7 +35417,7 @@ var init_builtin_templates = __esm(() => {
34946
35417
  },
34947
35418
  {
34948
35419
  name: "migration",
34949
- version: 1,
35420
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34950
35421
  category: "ops",
34951
35422
  description: "Schema/data migration workflow",
34952
35423
  variables: [
@@ -34963,7 +35434,7 @@ var init_builtin_templates = __esm(() => {
34963
35434
  },
34964
35435
  {
34965
35436
  name: "incident-response",
34966
- version: 1,
35437
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34967
35438
  category: "ops",
34968
35439
  description: "Incident triage, mitigation, postmortem",
34969
35440
  variables: [{ name: "incident", required: true, description: "Incident summary" }],
@@ -35010,8 +35481,8 @@ function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatEr
35010
35481
  const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
35011
35482
  const t = createTemplate2(params);
35012
35483
  const withTasks = getTemplateWithTasks2(t.id);
35013
- const taskCount = withTasks?.tasks.length ?? 0;
35014
- const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
35484
+ const taskCount2 = withTasks?.tasks.length ?? 0;
35485
+ const taskInfo = taskCount2 > 0 ? ` | ${taskCount2} task(s)` : "";
35015
35486
  return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
35016
35487
  } catch (e) {
35017
35488
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -35930,22 +36401,27 @@ function getHeadlessBoundaryManifest() {
35930
36401
  notes: [
35931
36402
  "Use todos CLI or todos-mcp for agent workflows.",
35932
36403
  "todos serve exposes a local-only REST API on 127.0.0.1 \u2014 not a hosted SaaS.",
35933
- "Cloud sync via @hasna/cloud is explicit opt-in from CLI/MCP, never from the dashboard."
36404
+ "Remote sync is explicit opt-in from CLI/MCP, never from the dashboard."
35934
36405
  ]
35935
36406
  };
35936
36407
  }
35937
- var HEADLESS_BOUNDARY_VERSION = "todos.headless-boundary.v1", FORBIDDEN_HOSTED_HOSTS, FORBIDDEN_WEB_PATTERNS, LOCAL_HOSTS;
36408
+ var HEADLESS_BOUNDARY_VERSION = "todos.headless-boundary.v1", FORBIDDEN_HOSTED_HOSTS, PRIVATE_PLATFORM_ORG, HOSTED_TODOS_PACKAGE, FORBIDDEN_WEB_PATTERNS, LOCAL_HOSTS;
35938
36409
  var init_headless_boundaries = __esm(() => {
35939
36410
  FORBIDDEN_HOSTED_HOSTS = [
35940
36411
  "todos.md",
35941
36412
  "www.todos.md",
35942
36413
  "preview.todos.md",
35943
36414
  "pay.hasna.tools",
35944
- "platform-todos"
36415
+ ["platform", "todos"].join("-")
35945
36416
  ];
36417
+ PRIVATE_PLATFORM_ORG = ["hasna", "studio"].join("");
36418
+ HOSTED_TODOS_PACKAGE = ["platform", "todos"].join("-");
35946
36419
  FORBIDDEN_WEB_PATTERNS = [
35947
36420
  { name: "hosted todos.md API", pattern: /https?:\/\/(?:www\.)?todos\.md/i },
35948
- { name: "platform-todos package", pattern: /@hasnastudio\/platform-todos|hasnastudio\/platform-todos/i },
36421
+ {
36422
+ name: "hosted platform package",
36423
+ pattern: new RegExp(`@${PRIVATE_PLATFORM_ORG}/${HOSTED_TODOS_PACKAGE}|${PRIVATE_PLATFORM_ORG}/${HOSTED_TODOS_PACKAGE}`, "i")
36424
+ },
35949
36425
  { name: "browser sign-in flow", pattern: /\/sign-?in\b|\/login\b.*(?:oauth|session|auth)/i },
35950
36426
  { name: "Stripe billing UI", pattern: /\bstripe\.(?:com|js)\b|\bcheckout\.sessions?\b/i },
35951
36427
  { name: "hosted OAuth redirect", pattern: /oauth.*redirect.*todos\.md/i }
@@ -36890,8 +37366,6 @@ Dashboard not found at: ${dashboardDir}`);
36890
37366
  return handleMcpHttpRequest2(req, buildServer2);
36891
37367
  }
36892
37368
  if (method === "OPTIONS") {
36893
- const reqOrigin2 = req.headers.get("origin") || undefined;
36894
- const allowed = reqOrigin2 && (reqOrigin2 === `http://localhost:${port}` || reqOrigin2 === "http://localhost:0");
36895
37369
  return new Response(null, {
36896
37370
  headers: corsHeaders || {
36897
37371
  Vary: "Origin"
@@ -37172,6 +37646,26 @@ function getMcpVersion() {
37172
37646
  function hasVersionFlag() {
37173
37647
  return process.argv.includes("--version") || process.argv.includes("-V");
37174
37648
  }
37649
+ function hasHelpFlag() {
37650
+ return process.argv.includes("--help") || process.argv.includes("-h");
37651
+ }
37652
+ function printHelp() {
37653
+ console.log(`Usage: todos-mcp [options]
37654
+
37655
+ Start the @hasna/todos MCP server.
37656
+
37657
+ Options:
37658
+ --stdio Use stdio transport
37659
+ --port <port> Use Streamable HTTP on the given port
37660
+ -V, --version output the version number
37661
+ -h, --help display help for command
37662
+
37663
+ Environment:
37664
+ TODOS_MCP_STDIO=true Force stdio transport
37665
+ TODOS_MCP_PORT=<port> HTTP port when not using stdio
37666
+ TODOS_PROFILE=<profile> Tool profile filter
37667
+ TODOS_TOOL_GROUPS=<list> Comma-separated tool group filter`);
37668
+ }
37175
37669
  function shouldRegisterTool(name) {
37176
37670
  return shouldRegisterToolForProfile(name);
37177
37671
  }
@@ -37312,8 +37806,17 @@ function buildServer() {
37312
37806
  version: getMcpVersion()
37313
37807
  });
37314
37808
  installMcpTokenDiagnostics(server);
37809
+ const registeredToolNames = new Set;
37810
+ const shouldRegisterToolOnce = (name) => {
37811
+ if (!shouldRegisterTool(name))
37812
+ return false;
37813
+ if (registeredToolNames.has(name))
37814
+ return false;
37815
+ registeredToolNames.add(name);
37816
+ return true;
37817
+ };
37315
37818
  const toolContext = {
37316
- shouldRegisterTool,
37819
+ shouldRegisterTool: shouldRegisterToolOnce,
37317
37820
  resolveId,
37318
37821
  formatError,
37319
37822
  formatTask,
@@ -37333,8 +37836,8 @@ function buildServer() {
37333
37836
  registerTemplateTools(server, toolContext);
37334
37837
  registerEnvironmentSnapshotTools(server, toolContext);
37335
37838
  registerWorkflowPrompts(server);
37336
- registerMachineTools(server, { shouldRegisterTool, formatError });
37337
- registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
37839
+ registerMachineTools(server, { shouldRegisterTool: shouldRegisterToolOnce, formatError });
37840
+ registerDispatchTools(server, { shouldRegisterTool: shouldRegisterToolOnce, resolveId, formatError });
37338
37841
  return server;
37339
37842
  }
37340
37843
  async function main() {
@@ -37376,6 +37879,10 @@ var init_mcp2 = __esm(() => {
37376
37879
  console.log(getMcpVersion());
37377
37880
  process.exit(0);
37378
37881
  }
37882
+ if (hasHelpFlag()) {
37883
+ printHelp();
37884
+ process.exit(0);
37885
+ }
37379
37886
  agentFocusMap = new Map;
37380
37887
  isDirectRun = import.meta.main || process.argv[1]?.endsWith("/mcp/index.ts") || process.argv[1]?.endsWith("/mcp/index.js");
37381
37888
  if (isDirectRun) {