@hasna/todos 0.11.21 → 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/cli/index.js +213 -200
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.js +180 -166
- package/dist/mcp/index.js +194 -191
- package/dist/server/index.js +114 -107
- package/package.json +1 -1
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();
|
|
@@ -1942,13 +2070,6 @@ function nextTaskShortId(projectId, db) {
|
|
|
1942
2070
|
const project = getProject(projectId, d);
|
|
1943
2071
|
if (!project || !project.task_prefix)
|
|
1944
2072
|
return null;
|
|
1945
|
-
const prefix = project.task_prefix;
|
|
1946
|
-
const prefixLen = prefix.length + 2;
|
|
1947
|
-
const maxRow = d.query(`SELECT MAX(CAST(SUBSTR(short_id, ?) AS INTEGER)) as max_counter FROM tasks WHERE short_id LIKE ?`).get(prefixLen, `${prefix}-%`);
|
|
1948
|
-
const syncedMax = maxRow?.max_counter ?? 0;
|
|
1949
|
-
if (syncedMax >= (project.task_counter ?? 0)) {
|
|
1950
|
-
d.run("UPDATE projects SET task_counter = ?, updated_at = ? WHERE id = ?", [syncedMax, now(), projectId]);
|
|
1951
|
-
}
|
|
1952
2073
|
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
|
|
1953
2074
|
const updated = getProject(projectId, d);
|
|
1954
2075
|
const padded = String(updated.task_counter).padStart(5, "0");
|
|
@@ -2007,128 +2128,6 @@ function removeMachineLocalPath(projectId, machineId, db) {
|
|
|
2007
2128
|
return result.changes > 0;
|
|
2008
2129
|
}
|
|
2009
2130
|
|
|
2010
|
-
// src/lib/completion-guard.ts
|
|
2011
|
-
init_types();
|
|
2012
|
-
|
|
2013
|
-
// src/lib/config.ts
|
|
2014
|
-
import { existsSync as existsSync3 } from "fs";
|
|
2015
|
-
import { join as join3 } from "path";
|
|
2016
|
-
|
|
2017
|
-
// src/lib/sync-utils.ts
|
|
2018
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
2019
|
-
import { join as join2 } from "path";
|
|
2020
|
-
var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2021
|
-
function ensureDir2(dir) {
|
|
2022
|
-
if (!existsSync2(dir))
|
|
2023
|
-
mkdirSync2(dir, { recursive: true });
|
|
2024
|
-
}
|
|
2025
|
-
function listJsonFiles(dir) {
|
|
2026
|
-
if (!existsSync2(dir))
|
|
2027
|
-
return [];
|
|
2028
|
-
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2029
|
-
}
|
|
2030
|
-
function readJsonFile(path) {
|
|
2031
|
-
try {
|
|
2032
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
2033
|
-
} catch {
|
|
2034
|
-
return null;
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
function writeJsonFile(path, data) {
|
|
2038
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
2039
|
-
`);
|
|
2040
|
-
}
|
|
2041
|
-
function readHighWaterMark(dir) {
|
|
2042
|
-
const path = join2(dir, ".highwatermark");
|
|
2043
|
-
if (!existsSync2(path))
|
|
2044
|
-
return 1;
|
|
2045
|
-
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
2046
|
-
return isNaN(val) ? 1 : val;
|
|
2047
|
-
}
|
|
2048
|
-
function writeHighWaterMark(dir, value) {
|
|
2049
|
-
writeFileSync(join2(dir, ".highwatermark"), String(value));
|
|
2050
|
-
}
|
|
2051
|
-
function getFileMtimeMs(path) {
|
|
2052
|
-
try {
|
|
2053
|
-
return statSync(path).mtimeMs;
|
|
2054
|
-
} catch {
|
|
2055
|
-
return null;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
function parseTimestamp(value) {
|
|
2059
|
-
if (typeof value !== "string")
|
|
2060
|
-
return null;
|
|
2061
|
-
const parsed = Date.parse(value);
|
|
2062
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
2063
|
-
}
|
|
2064
|
-
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
2065
|
-
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
2066
|
-
const next = [conflict, ...current].slice(0, limit);
|
|
2067
|
-
return { ...metadata, sync_conflicts: next };
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
// src/lib/config.ts
|
|
2071
|
-
function getTodosGlobalDir() {
|
|
2072
|
-
const home = process.env["HOME"] || HOME;
|
|
2073
|
-
const newDir = join3(home, ".hasna", "todos");
|
|
2074
|
-
const legacyDir = join3(home, ".todos");
|
|
2075
|
-
if (!existsSync3(newDir) && existsSync3(legacyDir))
|
|
2076
|
-
return legacyDir;
|
|
2077
|
-
return newDir;
|
|
2078
|
-
}
|
|
2079
|
-
function getConfigPath() {
|
|
2080
|
-
return join3(getTodosGlobalDir(), "config.json");
|
|
2081
|
-
}
|
|
2082
|
-
var cached = null;
|
|
2083
|
-
function normalizeAgent(agent) {
|
|
2084
|
-
return agent.trim().toLowerCase();
|
|
2085
|
-
}
|
|
2086
|
-
function loadConfig() {
|
|
2087
|
-
if (cached)
|
|
2088
|
-
return cached;
|
|
2089
|
-
if (!existsSync3(getConfigPath())) {
|
|
2090
|
-
cached = {};
|
|
2091
|
-
return cached;
|
|
2092
|
-
}
|
|
2093
|
-
const config = readJsonFile(getConfigPath()) || {};
|
|
2094
|
-
if (typeof config.sync_agents === "string") {
|
|
2095
|
-
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
2096
|
-
}
|
|
2097
|
-
cached = config;
|
|
2098
|
-
return cached;
|
|
2099
|
-
}
|
|
2100
|
-
function getSyncAgentsFromConfig() {
|
|
2101
|
-
const config = loadConfig();
|
|
2102
|
-
const agents = config.sync_agents;
|
|
2103
|
-
if (Array.isArray(agents) && agents.length > 0)
|
|
2104
|
-
return agents.map(normalizeAgent);
|
|
2105
|
-
return null;
|
|
2106
|
-
}
|
|
2107
|
-
function getAgentTasksDir(agent) {
|
|
2108
|
-
const config = loadConfig();
|
|
2109
|
-
const key = normalizeAgent(agent);
|
|
2110
|
-
return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
|
|
2111
|
-
}
|
|
2112
|
-
function getTaskPrefixConfig() {
|
|
2113
|
-
const config = loadConfig();
|
|
2114
|
-
return config.task_prefix || null;
|
|
2115
|
-
}
|
|
2116
|
-
var GUARD_DEFAULTS = {
|
|
2117
|
-
enabled: false,
|
|
2118
|
-
min_work_seconds: 30,
|
|
2119
|
-
max_completions_per_window: 5,
|
|
2120
|
-
window_minutes: 10,
|
|
2121
|
-
cooldown_seconds: 60
|
|
2122
|
-
};
|
|
2123
|
-
function getCompletionGuardConfig(projectPath) {
|
|
2124
|
-
const config = loadConfig();
|
|
2125
|
-
const global2 = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
2126
|
-
if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
|
|
2127
|
-
return { ...global2, ...config.project_overrides[projectPath].completion_guard };
|
|
2128
|
-
}
|
|
2129
|
-
return global2;
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
2131
|
// src/lib/completion-guard.ts
|
|
2133
2132
|
function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
2134
2133
|
let config;
|
|
@@ -2931,47 +2930,56 @@ function replaceTaskTags(taskId, tags, db) {
|
|
|
2931
2930
|
}
|
|
2932
2931
|
function createTask(input, db) {
|
|
2933
2932
|
const d = db || getDatabase();
|
|
2934
|
-
const id = uuid();
|
|
2935
2933
|
const timestamp = now();
|
|
2936
2934
|
const tags = input.tags || [];
|
|
2937
|
-
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
2938
|
-
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
2939
2935
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
2940
2936
|
const assignedFromProject = input.assigned_from_project || null;
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
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
|
+
}
|
|
2975
2983
|
if (tags.length > 0) {
|
|
2976
2984
|
insertTaskTags(id, tags, d);
|
|
2977
2985
|
}
|
|
@@ -16311,6 +16319,12 @@ var PG_MIGRATIONS = [
|
|
|
16311
16319
|
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
16312
16320
|
|
|
16313
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;
|
|
16314
16328
|
`
|
|
16315
16329
|
];
|
|
16316
16330
|
|