@hasna/todos 0.11.52 → 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 +3306 -445
  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 +526 -50
  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 +552 -52
  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")
@@ -14446,6 +14724,25 @@ var init_project_bootstrap = __esm(() => {
14446
14724
  function rowToLabel(row) {
14447
14725
  return { ...row };
14448
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
+ }
14449
14746
  function listLabels(projectId, db) {
14450
14747
  const d = db || getDatabase();
14451
14748
  if (projectId) {
@@ -14453,10 +14750,164 @@ function listLabels(projectId, db) {
14453
14750
  }
14454
14751
  return d.query("SELECT * FROM labels ORDER BY name").all().map(rowToLabel);
14455
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
+ }
14456
14784
  var init_labels = __esm(() => {
14457
14785
  init_database();
14458
14786
  });
14459
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
+
14460
14911
  // src/lib/retention-cleanup.ts
14461
14912
  import { existsSync as existsSync7, unlinkSync } from "fs";
14462
14913
  function normalizeScopes(scopes) {
@@ -16691,18 +17142,18 @@ var init_local_fields = __esm(() => {
16691
17142
  });
16692
17143
 
16693
17144
  // src/lib/workflow-states.ts
16694
- function normalizeName(value) {
17145
+ function normalizeName3(value) {
16695
17146
  return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
16696
17147
  }
16697
17148
  function isTaskStatus(value) {
16698
17149
  return TASK_STATUSES.includes(value);
16699
17150
  }
16700
17151
  function normalizeState(input) {
16701
- const name = normalizeName(input.name || "");
17152
+ const name = normalizeName3(input.name || "");
16702
17153
  if (!name || !isTaskStatus(input.canonical_status))
16703
17154
  return null;
16704
- const aliases = [...new Set((input.aliases || []).map(normalizeName).filter((alias) => alias && alias !== name))].sort();
16705
- 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;
16706
17157
  return {
16707
17158
  name,
16708
17159
  canonical_status: input.canonical_status,
@@ -16731,7 +17182,7 @@ function listWorkflowStates(projectPath) {
16731
17182
  return mergeWorkflowConfig(workflowConfig);
16732
17183
  }
16733
17184
  function resolveWorkflowState(input, projectPath) {
16734
- const normalized = normalizeName(input);
17185
+ const normalized = normalizeName3(input);
16735
17186
  const states = listWorkflowStates(projectPath);
16736
17187
  const byName = states.find((state) => state.name === normalized);
16737
17188
  if (byName)
@@ -17721,10 +18172,10 @@ function summarizeTasks(taskIds, planIds, runIds, db) {
17721
18172
  const completed = tasks.filter((task) => task.status === "completed");
17722
18173
  const inProgress = tasks.filter((task) => task.status === "in_progress");
17723
18174
  const pending = tasks.filter((task) => task.status === "pending");
17724
- const taskCount = tasks.length;
17725
- 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);
17726
18177
  return {
17727
- task_count: taskCount,
18178
+ task_count: taskCount2,
17728
18179
  completed_count: completed.length,
17729
18180
  in_progress_count: inProgress.length,
17730
18181
  pending_count: pending.length,
@@ -17732,7 +18183,7 @@ function summarizeTasks(taskIds, planIds, runIds, db) {
17732
18183
  plan_count: planIds.length,
17733
18184
  run_count: runIds.length,
17734
18185
  percent_complete: percent,
17735
- 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"
17736
18187
  };
17737
18188
  }
17738
18189
  function createRoadmap(input) {
@@ -18911,7 +19362,7 @@ function normalizeScope(scope) {
18911
19362
  }
18912
19363
  return "tasks";
18913
19364
  }
18914
- function normalizeName2(name) {
19365
+ function normalizeName4(name) {
18915
19366
  const normalized = name.trim();
18916
19367
  if (!normalized)
18917
19368
  throw new Error("Saved view name is required");
@@ -19161,7 +19612,7 @@ function runSavedSearch(filters = {}, scope = "tasks", db) {
19161
19612
  }
19162
19613
  function saveSearchView(input, db) {
19163
19614
  const d = db || getDatabase();
19164
- const name = normalizeName2(input.name);
19615
+ const name = normalizeName4(input.name);
19165
19616
  const timestamp3 = now();
19166
19617
  const existing = getSearchView(name, d);
19167
19618
  if (existing) {
@@ -20818,10 +21269,10 @@ Tasks:` : null,
20818
21269
  if (shouldRegisterTool("list_tags")) {
20819
21270
  server.tool("list_tags", "List all distinct task tags in use, with task counts.", async () => {
20820
21271
  try {
20821
- const rows = getDatabase().query("SELECT tag, COUNT(*) AS count FROM task_tags GROUP BY tag ORDER BY tag").all();
21272
+ const rows = listTags();
20822
21273
  if (rows.length === 0)
20823
21274
  return { content: [{ type: "text", text: "No tags found." }] };
20824
- const lines = rows.map((r) => `${r.tag} (${r.count})`);
21275
+ const lines = rows.map((r) => `${r.color ? "[" + r.color + "] " : ""}${r.name} (${r.task_count})`);
20825
21276
  return { content: [{ type: "text", text: lines.join(`
20826
21277
  `) }] };
20827
21278
  } catch (e) {
@@ -21390,8 +21841,8 @@ var init_task_project_tools = __esm(() => {
21390
21841
  init_comments();
21391
21842
  init_task_runs();
21392
21843
  init_project_bootstrap();
21393
- init_database();
21394
21844
  init_labels();
21845
+ init_tags();
21395
21846
  init_redaction();
21396
21847
  init_retention_cleanup();
21397
21848
  init_mention_resolver();
@@ -24537,7 +24988,7 @@ var init_agent_run_dispatcher = __esm(() => {
24537
24988
 
24538
24989
  // src/lib/verification-providers.ts
24539
24990
  import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
24540
- function normalizeName3(name) {
24991
+ function normalizeName5(name) {
24541
24992
  const normalized = name.trim().toLowerCase();
24542
24993
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
24543
24994
  throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
@@ -24556,10 +25007,10 @@ function timeoutMs(value) {
24556
25007
  return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
24557
25008
  }
24558
25009
  function getProvider(name) {
24559
- return loadConfig().verification_providers?.[normalizeName3(name)] || null;
25010
+ return loadConfig().verification_providers?.[normalizeName5(name)] || null;
24560
25011
  }
24561
25012
  function upsertVerificationProvider(input) {
24562
- const name = normalizeName3(input.name);
25013
+ const name = normalizeName5(input.name);
24563
25014
  const config = loadConfig();
24564
25015
  const existing = config.verification_providers?.[name];
24565
25016
  const timestamp3 = new Date().toISOString();
@@ -24589,7 +25040,7 @@ function listVerificationProviders() {
24589
25040
  return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
24590
25041
  }
24591
25042
  function removeVerificationProvider(name) {
24592
- const normalized = normalizeName3(name);
25043
+ const normalized = normalizeName5(name);
24593
25044
  const config = loadConfig();
24594
25045
  if (!config.verification_providers?.[normalized])
24595
25046
  return false;
@@ -28949,7 +29400,7 @@ import { basename as basename5, join as join9, resolve as resolve12 } from "path
28949
29400
  function isObject2(value) {
28950
29401
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
28951
29402
  }
28952
- function normalizeName4(name) {
29403
+ function normalizeName6(name) {
28953
29404
  const normalized = name.trim().toLowerCase();
28954
29405
  if (!/^[a-z0-9][a-z0-9_.-]{0,63}$/.test(normalized)) {
28955
29406
  throw new Error("extension name must use lowercase letters, numbers, dots, dashes, or underscores");
@@ -28967,7 +29418,7 @@ function permissionList(value) {
28967
29418
  function normalizeManifest(input) {
28968
29419
  if (!isObject2(input))
28969
29420
  throw new Error("extension manifest must be a JSON object");
28970
- const name = normalizeName4(String(input["name"] || ""));
29421
+ const name = normalizeName6(String(input["name"] || ""));
28971
29422
  const version = String(input["version"] || "").trim();
28972
29423
  if (!version)
28973
29424
  throw new Error("extension manifest requires version");
@@ -28975,7 +29426,7 @@ function normalizeManifest(input) {
28975
29426
  todos: typeof input["compatibility"]["todos"] === "string" ? input["compatibility"]["todos"] : undefined
28976
29427
  } : undefined;
28977
29428
  const commands = Array.isArray(input["commands"]) ? input["commands"].filter(isObject2).map((command) => ({
28978
- name: normalizeName4(String(command["name"] || "")),
29429
+ name: normalizeName6(String(command["name"] || "")),
28979
29430
  command: typeof command["command"] === "string" ? command["command"] : undefined,
28980
29431
  description: typeof command["description"] === "string" ? command["description"] : undefined,
28981
29432
  permissions: permissionList(command["permissions"]),
@@ -28984,12 +29435,12 @@ function normalizeManifest(input) {
28984
29435
  network: typeof command["network"] === "boolean" ? command["network"] : undefined
28985
29436
  })) : [];
28986
29437
  const mcpTools = Array.isArray(input["mcp_tools"]) ? input["mcp_tools"].filter(isObject2).map((tool) => ({
28987
- name: normalizeName4(String(tool["name"] || "")),
29438
+ name: normalizeName6(String(tool["name"] || "")),
28988
29439
  description: typeof tool["description"] === "string" ? tool["description"] : undefined,
28989
29440
  permissions: permissionList(tool["permissions"])
28990
29441
  })) : [];
28991
29442
  const templates = Array.isArray(input["templates"]) ? input["templates"].filter(isObject2).map((template) => ({
28992
- name: normalizeName4(String(template["name"] || "")),
29443
+ name: normalizeName6(String(template["name"] || "")),
28993
29444
  kind: typeof template["kind"] === "string" ? template["kind"] : undefined,
28994
29445
  description: typeof template["description"] === "string" ? template["description"] : undefined,
28995
29446
  path: typeof template["path"] === "string" ? template["path"] : undefined,
@@ -28998,7 +29449,7 @@ function normalizeManifest(input) {
28998
29449
  permissions: permissionList(template["permissions"])
28999
29450
  })) : [];
29000
29451
  const renderers = Array.isArray(input["renderers"]) ? input["renderers"].filter(isObject2).map((renderer) => ({
29001
- name: normalizeName4(String(renderer["name"] || "")),
29452
+ name: normalizeName6(String(renderer["name"] || "")),
29002
29453
  target: typeof renderer["target"] === "string" ? renderer["target"] : "",
29003
29454
  description: typeof renderer["description"] === "string" ? renderer["description"] : undefined,
29004
29455
  command: typeof renderer["command"] === "string" ? renderer["command"] : undefined,
@@ -29397,10 +29848,10 @@ function listLocalExtensions() {
29397
29848
  return Object.values(loadConfig().extension_registry || {}).sort((a, b) => a.name.localeCompare(b.name));
29398
29849
  }
29399
29850
  function getLocalExtension(name) {
29400
- return loadConfig().extension_registry?.[normalizeName4(name)] || null;
29851
+ return loadConfig().extension_registry?.[normalizeName6(name)] || null;
29401
29852
  }
29402
29853
  function removeLocalExtension(name) {
29403
- const normalized = normalizeName4(name);
29854
+ const normalized = normalizeName6(name);
29404
29855
  const config = loadConfig();
29405
29856
  if (!config.extension_registry?.[normalized])
29406
29857
  return false;
@@ -34911,8 +35362,6 @@ var init_builtin_templates = __esm(() => {
34911
35362
  },
34912
35363
  {
34913
35364
  name: "open-source-project",
34914
- version: 1,
34915
- category: "project",
34916
35365
  description: "Full open-source project bootstrap \u2014 scaffold to publish",
34917
35366
  category: "open-source",
34918
35367
  version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
@@ -34938,7 +35387,7 @@ var init_builtin_templates = __esm(() => {
34938
35387
  },
34939
35388
  {
34940
35389
  name: "release",
34941
- version: 1,
35390
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34942
35391
  category: "ops",
34943
35392
  description: "Version release workflow \u2014 test, changelog, publish, verify",
34944
35393
  variables: [
@@ -34955,7 +35404,7 @@ var init_builtin_templates = __esm(() => {
34955
35404
  },
34956
35405
  {
34957
35406
  name: "docs-refresh",
34958
- version: 1,
35407
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34959
35408
  category: "workflow",
34960
35409
  description: "Documentation refresh \u2014 audit, update, verify links",
34961
35410
  variables: [{ name: "scope", required: true, description: "Docs scope (README, API, AGENTS.md)" }],
@@ -34968,7 +35417,7 @@ var init_builtin_templates = __esm(() => {
34968
35417
  },
34969
35418
  {
34970
35419
  name: "migration",
34971
- version: 1,
35420
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34972
35421
  category: "ops",
34973
35422
  description: "Schema/data migration workflow",
34974
35423
  variables: [
@@ -34985,7 +35434,7 @@ var init_builtin_templates = __esm(() => {
34985
35434
  },
34986
35435
  {
34987
35436
  name: "incident-response",
34988
- version: 1,
35437
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
34989
35438
  category: "ops",
34990
35439
  description: "Incident triage, mitigation, postmortem",
34991
35440
  variables: [{ name: "incident", required: true, description: "Incident summary" }],
@@ -35032,8 +35481,8 @@ function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatEr
35032
35481
  const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
35033
35482
  const t = createTemplate2(params);
35034
35483
  const withTasks = getTemplateWithTasks2(t.id);
35035
- const taskCount = withTasks?.tasks.length ?? 0;
35036
- const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
35484
+ const taskCount2 = withTasks?.tasks.length ?? 0;
35485
+ const taskInfo = taskCount2 > 0 ? ` | ${taskCount2} task(s)` : "";
35037
35486
  return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
35038
35487
  } catch (e) {
35039
35488
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -35952,22 +36401,27 @@ function getHeadlessBoundaryManifest() {
35952
36401
  notes: [
35953
36402
  "Use todos CLI or todos-mcp for agent workflows.",
35954
36403
  "todos serve exposes a local-only REST API on 127.0.0.1 \u2014 not a hosted SaaS.",
35955
- "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."
35956
36405
  ]
35957
36406
  };
35958
36407
  }
35959
- 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;
35960
36409
  var init_headless_boundaries = __esm(() => {
35961
36410
  FORBIDDEN_HOSTED_HOSTS = [
35962
36411
  "todos.md",
35963
36412
  "www.todos.md",
35964
36413
  "preview.todos.md",
35965
36414
  "pay.hasna.tools",
35966
- "platform-todos"
36415
+ ["platform", "todos"].join("-")
35967
36416
  ];
36417
+ PRIVATE_PLATFORM_ORG = ["hasna", "studio"].join("");
36418
+ HOSTED_TODOS_PACKAGE = ["platform", "todos"].join("-");
35968
36419
  FORBIDDEN_WEB_PATTERNS = [
35969
36420
  { name: "hosted todos.md API", pattern: /https?:\/\/(?:www\.)?todos\.md/i },
35970
- { 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
+ },
35971
36425
  { name: "browser sign-in flow", pattern: /\/sign-?in\b|\/login\b.*(?:oauth|session|auth)/i },
35972
36426
  { name: "Stripe billing UI", pattern: /\bstripe\.(?:com|js)\b|\bcheckout\.sessions?\b/i },
35973
36427
  { name: "hosted OAuth redirect", pattern: /oauth.*redirect.*todos\.md/i }
@@ -36912,8 +37366,6 @@ Dashboard not found at: ${dashboardDir}`);
36912
37366
  return handleMcpHttpRequest2(req, buildServer2);
36913
37367
  }
36914
37368
  if (method === "OPTIONS") {
36915
- const reqOrigin2 = req.headers.get("origin") || undefined;
36916
- const allowed = reqOrigin2 && (reqOrigin2 === `http://localhost:${port}` || reqOrigin2 === "http://localhost:0");
36917
37369
  return new Response(null, {
36918
37370
  headers: corsHeaders || {
36919
37371
  Vary: "Origin"
@@ -37194,6 +37646,26 @@ function getMcpVersion() {
37194
37646
  function hasVersionFlag() {
37195
37647
  return process.argv.includes("--version") || process.argv.includes("-V");
37196
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
+ }
37197
37669
  function shouldRegisterTool(name) {
37198
37670
  return shouldRegisterToolForProfile(name);
37199
37671
  }
@@ -37407,6 +37879,10 @@ var init_mcp2 = __esm(() => {
37407
37879
  console.log(getMcpVersion());
37408
37880
  process.exit(0);
37409
37881
  }
37882
+ if (hasHelpFlag()) {
37883
+ printHelp();
37884
+ process.exit(0);
37885
+ }
37410
37886
  agentFocusMap = new Map;
37411
37887
  isDirectRun = import.meta.main || process.argv[1]?.endsWith("/mcp/index.ts") || process.argv[1]?.endsWith("/mcp/index.js");
37412
37888
  if (isDirectRun) {