@hasna/todos 0.11.20 → 0.11.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1081,6 +1081,12 @@ var init_schema = __esm(() => {
1081
1081
  ALTER TABLE tasks ADD COLUMN archived_at TEXT;
1082
1082
  CREATE INDEX IF NOT EXISTS idx_tasks_archived ON tasks(archived_at) WHERE archived_at IS NOT NULL;
1083
1083
  INSERT OR IGNORE INTO _migrations (id) VALUES (43);
1084
+ `,
1085
+ `
1086
+ DROP INDEX IF EXISTS idx_tasks_short_id;
1087
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id, machine_id) WHERE short_id IS NOT NULL AND machine_id IS NOT NULL;
1088
+ CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
1089
+ INSERT OR IGNORE INTO _migrations (id) VALUES (44);
1084
1090
  `
1085
1091
  ];
1086
1092
  });
@@ -1779,6 +1785,128 @@ init_database();
1779
1785
  init_types();
1780
1786
  init_database();
1781
1787
 
1788
+ // src/lib/completion-guard.ts
1789
+ init_types();
1790
+
1791
+ // src/lib/config.ts
1792
+ import { existsSync as existsSync3 } from "fs";
1793
+ import { join as join3 } from "path";
1794
+
1795
+ // src/lib/sync-utils.ts
1796
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
1797
+ import { join as join2 } from "path";
1798
+ var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1799
+ function ensureDir2(dir) {
1800
+ if (!existsSync2(dir))
1801
+ mkdirSync2(dir, { recursive: true });
1802
+ }
1803
+ function listJsonFiles(dir) {
1804
+ if (!existsSync2(dir))
1805
+ return [];
1806
+ return readdirSync(dir).filter((f) => f.endsWith(".json"));
1807
+ }
1808
+ function readJsonFile(path) {
1809
+ try {
1810
+ return JSON.parse(readFileSync(path, "utf-8"));
1811
+ } catch {
1812
+ return null;
1813
+ }
1814
+ }
1815
+ function writeJsonFile(path, data) {
1816
+ writeFileSync(path, JSON.stringify(data, null, 2) + `
1817
+ `);
1818
+ }
1819
+ function readHighWaterMark(dir) {
1820
+ const path = join2(dir, ".highwatermark");
1821
+ if (!existsSync2(path))
1822
+ return 1;
1823
+ const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
1824
+ return isNaN(val) ? 1 : val;
1825
+ }
1826
+ function writeHighWaterMark(dir, value) {
1827
+ writeFileSync(join2(dir, ".highwatermark"), String(value));
1828
+ }
1829
+ function getFileMtimeMs(path) {
1830
+ try {
1831
+ return statSync(path).mtimeMs;
1832
+ } catch {
1833
+ return null;
1834
+ }
1835
+ }
1836
+ function parseTimestamp(value) {
1837
+ if (typeof value !== "string")
1838
+ return null;
1839
+ const parsed = Date.parse(value);
1840
+ return Number.isNaN(parsed) ? null : parsed;
1841
+ }
1842
+ function appendSyncConflict(metadata, conflict, limit = 5) {
1843
+ const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
1844
+ const next = [conflict, ...current].slice(0, limit);
1845
+ return { ...metadata, sync_conflicts: next };
1846
+ }
1847
+
1848
+ // src/lib/config.ts
1849
+ function getTodosGlobalDir() {
1850
+ const home = process.env["HOME"] || HOME;
1851
+ const newDir = join3(home, ".hasna", "todos");
1852
+ const legacyDir = join3(home, ".todos");
1853
+ if (!existsSync3(newDir) && existsSync3(legacyDir))
1854
+ return legacyDir;
1855
+ return newDir;
1856
+ }
1857
+ function getConfigPath() {
1858
+ return join3(getTodosGlobalDir(), "config.json");
1859
+ }
1860
+ var cached = null;
1861
+ function normalizeAgent(agent) {
1862
+ return agent.trim().toLowerCase();
1863
+ }
1864
+ function loadConfig() {
1865
+ if (cached)
1866
+ return cached;
1867
+ if (!existsSync3(getConfigPath())) {
1868
+ cached = {};
1869
+ return cached;
1870
+ }
1871
+ const config = readJsonFile(getConfigPath()) || {};
1872
+ if (typeof config.sync_agents === "string") {
1873
+ config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
1874
+ }
1875
+ cached = config;
1876
+ return cached;
1877
+ }
1878
+ function getSyncAgentsFromConfig() {
1879
+ const config = loadConfig();
1880
+ const agents = config.sync_agents;
1881
+ if (Array.isArray(agents) && agents.length > 0)
1882
+ return agents.map(normalizeAgent);
1883
+ return null;
1884
+ }
1885
+ function getAgentTasksDir(agent) {
1886
+ const config = loadConfig();
1887
+ const key = normalizeAgent(agent);
1888
+ return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
1889
+ }
1890
+ function getTaskPrefixConfig() {
1891
+ const config = loadConfig();
1892
+ return config.task_prefix || null;
1893
+ }
1894
+ var GUARD_DEFAULTS = {
1895
+ enabled: false,
1896
+ min_work_seconds: 30,
1897
+ max_completions_per_window: 5,
1898
+ window_minutes: 10,
1899
+ cooldown_seconds: 60
1900
+ };
1901
+ function getCompletionGuardConfig(projectPath) {
1902
+ const config = loadConfig();
1903
+ const global2 = { ...GUARD_DEFAULTS, ...config.completion_guard };
1904
+ if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
1905
+ return { ...global2, ...config.project_overrides[projectPath].completion_guard };
1906
+ }
1907
+ return global2;
1908
+ }
1909
+
1782
1910
  // src/db/projects.ts
1783
1911
  init_types();
1784
1912
  init_database();
@@ -2000,128 +2128,6 @@ function removeMachineLocalPath(projectId, machineId, db) {
2000
2128
  return result.changes > 0;
2001
2129
  }
2002
2130
 
2003
- // src/lib/completion-guard.ts
2004
- init_types();
2005
-
2006
- // src/lib/config.ts
2007
- import { existsSync as existsSync3 } from "fs";
2008
- import { join as join3 } from "path";
2009
-
2010
- // src/lib/sync-utils.ts
2011
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
2012
- import { join as join2 } from "path";
2013
- var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2014
- function ensureDir2(dir) {
2015
- if (!existsSync2(dir))
2016
- mkdirSync2(dir, { recursive: true });
2017
- }
2018
- function listJsonFiles(dir) {
2019
- if (!existsSync2(dir))
2020
- return [];
2021
- return readdirSync(dir).filter((f) => f.endsWith(".json"));
2022
- }
2023
- function readJsonFile(path) {
2024
- try {
2025
- return JSON.parse(readFileSync(path, "utf-8"));
2026
- } catch {
2027
- return null;
2028
- }
2029
- }
2030
- function writeJsonFile(path, data) {
2031
- writeFileSync(path, JSON.stringify(data, null, 2) + `
2032
- `);
2033
- }
2034
- function readHighWaterMark(dir) {
2035
- const path = join2(dir, ".highwatermark");
2036
- if (!existsSync2(path))
2037
- return 1;
2038
- const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
2039
- return isNaN(val) ? 1 : val;
2040
- }
2041
- function writeHighWaterMark(dir, value) {
2042
- writeFileSync(join2(dir, ".highwatermark"), String(value));
2043
- }
2044
- function getFileMtimeMs(path) {
2045
- try {
2046
- return statSync(path).mtimeMs;
2047
- } catch {
2048
- return null;
2049
- }
2050
- }
2051
- function parseTimestamp(value) {
2052
- if (typeof value !== "string")
2053
- return null;
2054
- const parsed = Date.parse(value);
2055
- return Number.isNaN(parsed) ? null : parsed;
2056
- }
2057
- function appendSyncConflict(metadata, conflict, limit = 5) {
2058
- const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
2059
- const next = [conflict, ...current].slice(0, limit);
2060
- return { ...metadata, sync_conflicts: next };
2061
- }
2062
-
2063
- // src/lib/config.ts
2064
- function getTodosGlobalDir() {
2065
- const home = process.env["HOME"] || HOME;
2066
- const newDir = join3(home, ".hasna", "todos");
2067
- const legacyDir = join3(home, ".todos");
2068
- if (!existsSync3(newDir) && existsSync3(legacyDir))
2069
- return legacyDir;
2070
- return newDir;
2071
- }
2072
- function getConfigPath() {
2073
- return join3(getTodosGlobalDir(), "config.json");
2074
- }
2075
- var cached = null;
2076
- function normalizeAgent(agent) {
2077
- return agent.trim().toLowerCase();
2078
- }
2079
- function loadConfig() {
2080
- if (cached)
2081
- return cached;
2082
- if (!existsSync3(getConfigPath())) {
2083
- cached = {};
2084
- return cached;
2085
- }
2086
- const config = readJsonFile(getConfigPath()) || {};
2087
- if (typeof config.sync_agents === "string") {
2088
- config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
2089
- }
2090
- cached = config;
2091
- return cached;
2092
- }
2093
- function getSyncAgentsFromConfig() {
2094
- const config = loadConfig();
2095
- const agents = config.sync_agents;
2096
- if (Array.isArray(agents) && agents.length > 0)
2097
- return agents.map(normalizeAgent);
2098
- return null;
2099
- }
2100
- function getAgentTasksDir(agent) {
2101
- const config = loadConfig();
2102
- const key = normalizeAgent(agent);
2103
- return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
2104
- }
2105
- function getTaskPrefixConfig() {
2106
- const config = loadConfig();
2107
- return config.task_prefix || null;
2108
- }
2109
- var GUARD_DEFAULTS = {
2110
- enabled: false,
2111
- min_work_seconds: 30,
2112
- max_completions_per_window: 5,
2113
- window_minutes: 10,
2114
- cooldown_seconds: 60
2115
- };
2116
- function getCompletionGuardConfig(projectPath) {
2117
- const config = loadConfig();
2118
- const global2 = { ...GUARD_DEFAULTS, ...config.completion_guard };
2119
- if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
2120
- return { ...global2, ...config.project_overrides[projectPath].completion_guard };
2121
- }
2122
- return global2;
2123
- }
2124
-
2125
2131
  // src/lib/completion-guard.ts
2126
2132
  function checkCompletionGuard(task, agentId, db, configOverride) {
2127
2133
  let config;
@@ -2924,47 +2930,56 @@ function replaceTaskTags(taskId, tags, db) {
2924
2930
  }
2925
2931
  function createTask(input, db) {
2926
2932
  const d = db || getDatabase();
2927
- const id = uuid();
2928
2933
  const timestamp = now();
2929
2934
  const tags = input.tags || [];
2930
- const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
2931
- const title = shortId ? `${shortId}: ${input.title}` : input.title;
2932
2935
  const assignedBy = input.assigned_by || input.agent_id;
2933
2936
  const assignedFromProject = input.assigned_from_project || null;
2934
- d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
2935
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2936
- id,
2937
- shortId,
2938
- input.project_id || null,
2939
- input.parent_id || null,
2940
- input.plan_id || null,
2941
- input.task_list_id || null,
2942
- title,
2943
- input.description || null,
2944
- input.status || "pending",
2945
- input.priority || "medium",
2946
- input.agent_id || null,
2947
- input.assigned_to || null,
2948
- input.session_id || null,
2949
- input.working_dir || null,
2950
- JSON.stringify(tags),
2951
- JSON.stringify(input.metadata || {}),
2952
- timestamp,
2953
- timestamp,
2954
- input.due_at || null,
2955
- input.estimated_minutes || null,
2956
- input.requires_approval ? 1 : 0,
2957
- null,
2958
- null,
2959
- input.recurrence_rule || null,
2960
- input.recurrence_parent_id || null,
2961
- input.spawns_template_id || null,
2962
- input.reason || null,
2963
- input.spawned_from_session || null,
2964
- assignedBy || null,
2965
- assignedFromProject || null,
2966
- input.task_type || null
2967
- ]);
2937
+ let id = uuid();
2938
+ for (let attempt = 0;attempt < 3; attempt++) {
2939
+ try {
2940
+ d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
2941
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2942
+ id,
2943
+ null,
2944
+ input.project_id || null,
2945
+ input.parent_id || null,
2946
+ input.plan_id || null,
2947
+ input.task_list_id || null,
2948
+ input.title,
2949
+ input.description || null,
2950
+ input.status || "pending",
2951
+ input.priority || "medium",
2952
+ input.agent_id || null,
2953
+ input.assigned_to || null,
2954
+ input.session_id || null,
2955
+ input.working_dir || null,
2956
+ JSON.stringify(tags),
2957
+ JSON.stringify(input.metadata || {}),
2958
+ timestamp,
2959
+ timestamp,
2960
+ input.due_at || null,
2961
+ input.estimated_minutes || null,
2962
+ input.requires_approval ? 1 : 0,
2963
+ null,
2964
+ null,
2965
+ input.recurrence_rule || null,
2966
+ input.recurrence_parent_id || null,
2967
+ input.spawns_template_id || null,
2968
+ input.reason || null,
2969
+ input.spawned_from_session || null,
2970
+ assignedBy || null,
2971
+ assignedFromProject || null,
2972
+ input.task_type || null
2973
+ ]);
2974
+ break;
2975
+ } catch (e) {
2976
+ if (attempt < 2 && e?.message?.includes("UNIQUE constraint failed: tasks.id")) {
2977
+ id = uuid();
2978
+ continue;
2979
+ }
2980
+ throw e;
2981
+ }
2982
+ }
2968
2983
  if (tags.length > 0) {
2969
2984
  insertTaskTags(id, tags, d);
2970
2985
  }
@@ -16304,6 +16319,12 @@ var PG_MIGRATIONS = [
16304
16319
  CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
16305
16320
 
16306
16321
  INSERT INTO _migrations (id) VALUES (39) ON CONFLICT DO NOTHING;
16322
+ `,
16323
+ `
16324
+ DROP INDEX IF EXISTS idx_tasks_short_id;
16325
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id, machine_id) WHERE short_id IS NOT NULL AND machine_id IS NOT NULL;
16326
+ CREATE INDEX IF NOT EXISTS idx_tasks_short_id_lookup ON tasks(short_id) WHERE short_id IS NOT NULL;
16327
+ INSERT INTO _migrations (id) VALUES (40) ON CONFLICT DO NOTHING;
16307
16328
  `
16308
16329
  ];
16309
16330
 
@@ -1,7 +1,7 @@
1
1
  import type { Database } from "bun:sqlite";
2
2
  import type { Task } from "../types/index.js";
3
3
  export interface SearchOptions {
4
- query: string;
4
+ query?: string;
5
5
  project_id?: string;
6
6
  task_list_id?: string;
7
7
  status?: string | string[];
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,mBAAmB,CAAC;AAcvD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAuBD,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,GAAG,MAAM,EAC/B,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,EAAE,CAqGR"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,mBAAmB,CAAC;AAcvD,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAuBD,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,GAAG,MAAM,EAC/B,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,IAAI,EAAE,CA4GR"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AAsJA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAO9E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AAyJA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAO9E"}