@hasna/todos 0.14.0 → 0.15.1
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/README.md +2 -0
- package/dist/cli/commands/config-serve-commands.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +258 -43
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +113 -112
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +120 -119
- package/dist/lib/bulk-tags.d.ts +57 -0
- package/dist/lib/bulk-tags.d.ts.map +1 -0
- package/dist/lib/enum-vocabulary.d.ts.map +1 -1
- package/dist/lib/run-records.d.ts.map +1 -1
- package/dist/lib/sandbox-profiles.d.ts.map +1 -1
- package/dist/lib/sync-utils.d.ts +1 -1
- package/dist/lib/sync-utils.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +75 -58
- package/dist/mcp/tools/task-crud.d.ts +1 -0
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +113 -112
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.js +3 -2
- package/dist/server/index.js +31 -14
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +112 -111
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -3804,6 +3804,41 @@ var init_identity_mapping = __esm(() => {
|
|
|
3804
3804
|
});
|
|
3805
3805
|
});
|
|
3806
3806
|
|
|
3807
|
+
// src/lib/sync-utils.ts
|
|
3808
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
3809
|
+
import { homedir } from "os";
|
|
3810
|
+
import { join } from "path";
|
|
3811
|
+
function getHomeDir() {
|
|
3812
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
3813
|
+
}
|
|
3814
|
+
function getTodosGlobalDir() {
|
|
3815
|
+
return join(getHomeDir(), ".hasna", "todos");
|
|
3816
|
+
}
|
|
3817
|
+
function ensureDir(dir) {
|
|
3818
|
+
if (!existsSync2(dir))
|
|
3819
|
+
mkdirSync(dir, { recursive: true });
|
|
3820
|
+
}
|
|
3821
|
+
function readJsonFile(path) {
|
|
3822
|
+
try {
|
|
3823
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
3824
|
+
} catch {
|
|
3825
|
+
return null;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
function writeJsonFile(path, data) {
|
|
3829
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
3830
|
+
`);
|
|
3831
|
+
}
|
|
3832
|
+
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
3833
|
+
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
3834
|
+
const next = [conflict, ...current].slice(0, limit);
|
|
3835
|
+
return { ...metadata, sync_conflicts: next };
|
|
3836
|
+
}
|
|
3837
|
+
var HOME;
|
|
3838
|
+
var init_sync_utils = __esm(() => {
|
|
3839
|
+
HOME = getHomeDir();
|
|
3840
|
+
});
|
|
3841
|
+
|
|
3807
3842
|
// src/storage/config.ts
|
|
3808
3843
|
var exports_config = {};
|
|
3809
3844
|
__export(exports_config, {
|
|
@@ -4134,8 +4169,8 @@ __export(exports_database, {
|
|
|
4134
4169
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
4135
4170
|
});
|
|
4136
4171
|
import { Database } from "bun:sqlite";
|
|
4137
|
-
import { existsSync as
|
|
4138
|
-
import { dirname, join, resolve as resolve2 } from "path";
|
|
4172
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
4173
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
4139
4174
|
function isInMemoryDb(path) {
|
|
4140
4175
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
4141
4176
|
}
|
|
@@ -4144,8 +4179,8 @@ function findNearestProjectDb(startDir) {
|
|
|
4144
4179
|
const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
|
|
4145
4180
|
let dir = resolve2(startDir);
|
|
4146
4181
|
while (true) {
|
|
4147
|
-
const candidate =
|
|
4148
|
-
if (
|
|
4182
|
+
const candidate = join2(dir, ".hasna", "todos", "todos.db");
|
|
4183
|
+
if (existsSync3(candidate))
|
|
4149
4184
|
return candidate;
|
|
4150
4185
|
if (dir === stopAt)
|
|
4151
4186
|
break;
|
|
@@ -4159,7 +4194,7 @@ function findNearestProjectDb(startDir) {
|
|
|
4159
4194
|
function findGitRoot(startDir) {
|
|
4160
4195
|
let dir = resolve2(startDir);
|
|
4161
4196
|
while (true) {
|
|
4162
|
-
if (
|
|
4197
|
+
if (existsSync3(join2(dir, ".git")))
|
|
4163
4198
|
return dir;
|
|
4164
4199
|
const parent = dirname(dir);
|
|
4165
4200
|
if (parent === dir)
|
|
@@ -4169,8 +4204,7 @@ function findGitRoot(startDir) {
|
|
|
4169
4204
|
return null;
|
|
4170
4205
|
}
|
|
4171
4206
|
function getGlobalDbPath() {
|
|
4172
|
-
|
|
4173
|
-
return join(home, ".hasna", "todos", "todos.db");
|
|
4207
|
+
return join2(getHomeDir(), ".hasna", "todos", "todos.db");
|
|
4174
4208
|
}
|
|
4175
4209
|
function hasExplicitProjectArg(args = process.argv.slice(2)) {
|
|
4176
4210
|
return args.some((arg) => arg === "--project" || arg.startsWith("--project="));
|
|
@@ -4208,7 +4242,7 @@ function getDbPath() {
|
|
|
4208
4242
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
4209
4243
|
const gitRoot = findGitRoot(cwd);
|
|
4210
4244
|
if (gitRoot && canCreateScopedProjectDb()) {
|
|
4211
|
-
return
|
|
4245
|
+
return join2(gitRoot, ".hasna", "todos", "todos.db");
|
|
4212
4246
|
}
|
|
4213
4247
|
}
|
|
4214
4248
|
return getGlobalDbPath();
|
|
@@ -4216,16 +4250,16 @@ function getDbPath() {
|
|
|
4216
4250
|
function getDatabasePath() {
|
|
4217
4251
|
return getDbPath();
|
|
4218
4252
|
}
|
|
4219
|
-
function
|
|
4253
|
+
function ensureDir2(filePath) {
|
|
4220
4254
|
if (isInMemoryDb(filePath))
|
|
4221
4255
|
return;
|
|
4222
4256
|
const dir = dirname(resolve2(filePath));
|
|
4223
|
-
if (!
|
|
4224
|
-
|
|
4257
|
+
if (!existsSync3(dir)) {
|
|
4258
|
+
mkdirSync2(dir, { recursive: true });
|
|
4225
4259
|
}
|
|
4226
4260
|
}
|
|
4227
4261
|
function openDatabase(path) {
|
|
4228
|
-
|
|
4262
|
+
ensureDir2(path);
|
|
4229
4263
|
const db = new Database(path);
|
|
4230
4264
|
db.run("PRAGMA busy_timeout = 5000");
|
|
4231
4265
|
db.run("PRAGMA journal_mode = WAL");
|
|
@@ -4392,6 +4426,7 @@ var init_database = __esm(() => {
|
|
|
4392
4426
|
init_machines();
|
|
4393
4427
|
init_identity_mapping();
|
|
4394
4428
|
init_types();
|
|
4429
|
+
init_sync_utils();
|
|
4395
4430
|
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
|
|
4396
4431
|
});
|
|
4397
4432
|
|
|
@@ -9135,40 +9170,6 @@ var init_zod = __esm(() => {
|
|
|
9135
9170
|
init_external();
|
|
9136
9171
|
});
|
|
9137
9172
|
|
|
9138
|
-
// src/lib/sync-utils.ts
|
|
9139
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
9140
|
-
import { join as join2 } from "path";
|
|
9141
|
-
function getHomeDir() {
|
|
9142
|
-
return process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
9143
|
-
}
|
|
9144
|
-
function getTodosGlobalDir() {
|
|
9145
|
-
return join2(getHomeDir(), ".hasna", "todos");
|
|
9146
|
-
}
|
|
9147
|
-
function ensureDir2(dir) {
|
|
9148
|
-
if (!existsSync3(dir))
|
|
9149
|
-
mkdirSync2(dir, { recursive: true });
|
|
9150
|
-
}
|
|
9151
|
-
function readJsonFile(path) {
|
|
9152
|
-
try {
|
|
9153
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
9154
|
-
} catch {
|
|
9155
|
-
return null;
|
|
9156
|
-
}
|
|
9157
|
-
}
|
|
9158
|
-
function writeJsonFile(path, data) {
|
|
9159
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
9160
|
-
`);
|
|
9161
|
-
}
|
|
9162
|
-
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
9163
|
-
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
9164
|
-
const next = [conflict, ...current].slice(0, limit);
|
|
9165
|
-
return { ...metadata, sync_conflicts: next };
|
|
9166
|
-
}
|
|
9167
|
-
var HOME;
|
|
9168
|
-
var init_sync_utils = __esm(() => {
|
|
9169
|
-
HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
9170
|
-
});
|
|
9171
|
-
|
|
9172
9173
|
// src/lib/config.ts
|
|
9173
9174
|
import { existsSync as existsSync4 } from "fs";
|
|
9174
9175
|
import { dirname as dirname2, join as join3 } from "path";
|
|
@@ -9191,7 +9192,7 @@ function loadConfig() {
|
|
|
9191
9192
|
}
|
|
9192
9193
|
function saveConfig(config) {
|
|
9193
9194
|
const configPath = getConfigPath();
|
|
9194
|
-
|
|
9195
|
+
ensureDir(dirname2(configPath));
|
|
9195
9196
|
writeJsonFile(configPath, config);
|
|
9196
9197
|
cached = config;
|
|
9197
9198
|
return config;
|
|
@@ -10746,7 +10747,7 @@ var init_event_hooks = __esm(() => {
|
|
|
10746
10747
|
// node_modules/.bun/@hasna+events@0.1.11/node_modules/@hasna/events/dist/index.js
|
|
10747
10748
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
10748
10749
|
import { existsSync as existsSync5 } from "fs";
|
|
10749
|
-
import { homedir } from "os";
|
|
10750
|
+
import { homedir as homedir2 } from "os";
|
|
10750
10751
|
import { join as join4 } from "path";
|
|
10751
10752
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
10752
10753
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -10848,7 +10849,7 @@ function channelMatchesEvent(channel, event) {
|
|
|
10848
10849
|
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
10849
10850
|
}
|
|
10850
10851
|
function getEventsDataDir(override) {
|
|
10851
|
-
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(
|
|
10852
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
|
|
10852
10853
|
}
|
|
10853
10854
|
|
|
10854
10855
|
class JsonEventsStore {
|
|
@@ -18002,10 +18003,10 @@ var init_token_utils = __esm(() => {
|
|
|
18002
18003
|
|
|
18003
18004
|
// src/lib/assignee-validation.ts
|
|
18004
18005
|
import { readFileSync as readFileSync3 } from "fs";
|
|
18005
|
-
import { homedir as
|
|
18006
|
+
import { homedir as homedir3 } from "os";
|
|
18006
18007
|
import { join as join7 } from "path";
|
|
18007
18008
|
function defaultSeatRosterPath() {
|
|
18008
|
-
return process.env["TODOS_SEAT_ROSTER_PATH"] || join7(
|
|
18009
|
+
return process.env["TODOS_SEAT_ROSTER_PATH"] || join7(homedir3(), ".hasna", "identities", "hasna-seats.roster.json");
|
|
18009
18010
|
}
|
|
18010
18011
|
function loadSeatSlugs(path = defaultSeatRosterPath()) {
|
|
18011
18012
|
try {
|
|
@@ -20833,7 +20834,7 @@ async function validateMcpAssignee(value, allowSeat) {
|
|
|
20833
20834
|
return verdict.assignee;
|
|
20834
20835
|
}
|
|
20835
20836
|
function registerTaskCrudTools(server, ctx) {
|
|
20836
|
-
const { shouldRegisterTool, resolveId, formatError, formatTask } = ctx;
|
|
20837
|
+
const { shouldRegisterTool, resolveId, formatError, formatTask, applyFocus } = ctx;
|
|
20837
20838
|
function mutationTaskResponse(task) {
|
|
20838
20839
|
const compact = compactTask(task, 240);
|
|
20839
20840
|
compact["version"] = task.version;
|
|
@@ -20909,6 +20910,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
20909
20910
|
payload.max_retries = retry_count;
|
|
20910
20911
|
if (deadline)
|
|
20911
20912
|
payload.due_at = deadline;
|
|
20913
|
+
applyFocus(payload, router.agent_id || undefined);
|
|
20912
20914
|
const created = await cloudCreateTask(cloud, payload);
|
|
20913
20915
|
return { content: [{ type: "text", text: mutationTaskResponse(created) }] };
|
|
20914
20916
|
}
|
|
@@ -20935,6 +20937,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
20935
20937
|
resolved.max_retries = retry_count;
|
|
20936
20938
|
if (deadline)
|
|
20937
20939
|
resolved.due_at = deadline;
|
|
20940
|
+
applyFocus(resolved, router.agent_id || undefined);
|
|
20938
20941
|
const task = createTask(resolved);
|
|
20939
20942
|
return { content: [{ type: "text", text: mutationTaskResponse(task) }] };
|
|
20940
20943
|
} catch (e) {
|
|
@@ -34912,7 +34915,7 @@ var package_default;
|
|
|
34912
34915
|
var init_package = __esm(() => {
|
|
34913
34916
|
package_default = {
|
|
34914
34917
|
name: "@hasna/todos",
|
|
34915
|
-
version: "0.
|
|
34918
|
+
version: "0.15.1",
|
|
34916
34919
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
34917
34920
|
type: "module",
|
|
34918
34921
|
main: "dist/index.js",
|
|
@@ -43853,7 +43856,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
43853
43856
|
}
|
|
43854
43857
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
43855
43858
|
const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
43856
|
-
|
|
43859
|
+
ensureDir(dirname8(path));
|
|
43857
43860
|
writeJsonFile(path, snapshot);
|
|
43858
43861
|
return path;
|
|
43859
43862
|
}
|
|
@@ -48112,14 +48115,21 @@ function suggestVocabularyMatches(value, vocabulary, limit = 3) {
|
|
|
48112
48115
|
}
|
|
48113
48116
|
function resolveEnumVocabulary(raw, spec) {
|
|
48114
48117
|
const allowList = spec.allowList !== false;
|
|
48115
|
-
const rawElements = (allowList ? raw.split(",") : [raw]).map((element) => element.trim())
|
|
48116
|
-
if (rawElements.length === 0) {
|
|
48118
|
+
const rawElements = (allowList ? raw.split(",") : [raw]).map((element) => element.trim());
|
|
48119
|
+
if (rawElements.every((element) => element.length === 0)) {
|
|
48117
48120
|
return {
|
|
48118
48121
|
ok: false,
|
|
48119
48122
|
message: `${spec.name} requires a value. Allowed values: ${spec.vocabulary.join(", ")}.`,
|
|
48120
48123
|
invalid: []
|
|
48121
48124
|
};
|
|
48122
48125
|
}
|
|
48126
|
+
if (rawElements.some((element) => element.length === 0)) {
|
|
48127
|
+
return {
|
|
48128
|
+
ok: false,
|
|
48129
|
+
message: `${spec.name} has an empty value in "${raw}" \u2014 remove the stray comma. ` + `Allowed values: ${spec.vocabulary.join(", ")}.`,
|
|
48130
|
+
invalid: []
|
|
48131
|
+
};
|
|
48132
|
+
}
|
|
48123
48133
|
const normalized = rawElements.map((element) => spec.normalize ? spec.normalize(element) : element);
|
|
48124
48134
|
const allowed = new Set(spec.vocabulary);
|
|
48125
48135
|
const invalid2 = [];
|
|
@@ -51976,7 +51986,13 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
51976
51986
|
return error(405, `method ${method} not allowed on /v1/refs/:ref`);
|
|
51977
51987
|
if (!store.gitRefs)
|
|
51978
51988
|
return error(501, "git ref links are not supported by this storage backend");
|
|
51979
|
-
|
|
51989
|
+
let decodedRef;
|
|
51990
|
+
try {
|
|
51991
|
+
decodedRef = decodeURIComponent(id);
|
|
51992
|
+
} catch {
|
|
51993
|
+
return error(400, "ref path segment has invalid percent encoding");
|
|
51994
|
+
}
|
|
51995
|
+
const refs = await store.gitRefs.find(decodedRef);
|
|
51980
51996
|
return json3({ refs, count: refs.length });
|
|
51981
51997
|
}
|
|
51982
51998
|
if (resource === "next" && !id) {
|
|
@@ -53082,7 +53098,8 @@ function buildServer() {
|
|
|
53082
53098
|
formatError,
|
|
53083
53099
|
formatTask,
|
|
53084
53100
|
formatTaskDetail,
|
|
53085
|
-
getAgentFocus
|
|
53101
|
+
getAgentFocus,
|
|
53102
|
+
applyFocus
|
|
53086
53103
|
};
|
|
53087
53104
|
registerTaskCrudTools(server, toolContext);
|
|
53088
53105
|
registerTaskProjectTools(server, toolContext);
|
|
@@ -14,6 +14,7 @@ interface TaskCrudContext {
|
|
|
14
14
|
agent_id: string;
|
|
15
15
|
project_id?: string;
|
|
16
16
|
} | undefined;
|
|
17
|
+
applyFocus: (params: Record<string, any>, agentId?: string) => void;
|
|
17
18
|
}
|
|
18
19
|
export declare function registerTaskCrudTools(server: McpServer, ctx: TaskCrudContext): void;
|
|
19
20
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-crud.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/task-crud.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAoBjD,UAAU,eAAe;IACvB,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACzD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;IACnC,gBAAgB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACvE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"task-crud.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/task-crud.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAoBjD,UAAU,eAAe;IACvB,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACzD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;IACnC,gBAAgB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACvE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC1F,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACrE;AAuBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,eAAe,QAoY5E"}
|
package/dist/mcp.js
CHANGED
|
@@ -41,7 +41,7 @@ var __require = import.meta.require;
|
|
|
41
41
|
// package.json
|
|
42
42
|
var package_default = {
|
|
43
43
|
name: "@hasna/todos",
|
|
44
|
-
version: "0.
|
|
44
|
+
version: "0.15.1",
|
|
45
45
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
46
46
|
type: "module",
|
|
47
47
|
main: "dist/index.js",
|
package/dist/registry.js
CHANGED
|
@@ -3790,6 +3790,103 @@ var init_identity_mapping = __esm(() => {
|
|
|
3790
3790
|
});
|
|
3791
3791
|
});
|
|
3792
3792
|
|
|
3793
|
+
// src/lib/sync-utils.ts
|
|
3794
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
3795
|
+
import { createHash } from "crypto";
|
|
3796
|
+
import { homedir } from "os";
|
|
3797
|
+
import { join } from "path";
|
|
3798
|
+
function getHomeDir() {
|
|
3799
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
3800
|
+
}
|
|
3801
|
+
function getTodosGlobalDir() {
|
|
3802
|
+
return join(getHomeDir(), ".hasna", "todos");
|
|
3803
|
+
}
|
|
3804
|
+
function ensureDir(dir) {
|
|
3805
|
+
if (!existsSync2(dir))
|
|
3806
|
+
mkdirSync(dir, { recursive: true });
|
|
3807
|
+
}
|
|
3808
|
+
function listJsonFiles(dir) {
|
|
3809
|
+
if (!existsSync2(dir))
|
|
3810
|
+
return [];
|
|
3811
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
3812
|
+
}
|
|
3813
|
+
function readJsonFile(path) {
|
|
3814
|
+
try {
|
|
3815
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
3816
|
+
} catch {
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function writeJsonFile(path, data) {
|
|
3821
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
3822
|
+
`);
|
|
3823
|
+
}
|
|
3824
|
+
function readHighWaterMark(dir) {
|
|
3825
|
+
const path = join(dir, ".highwatermark");
|
|
3826
|
+
if (!existsSync2(path))
|
|
3827
|
+
return 1;
|
|
3828
|
+
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
3829
|
+
return isNaN(val) ? 1 : val;
|
|
3830
|
+
}
|
|
3831
|
+
function writeHighWaterMark(dir, value) {
|
|
3832
|
+
writeFileSync(join(dir, ".highwatermark"), String(value));
|
|
3833
|
+
}
|
|
3834
|
+
function getFileMtimeMs(path) {
|
|
3835
|
+
try {
|
|
3836
|
+
return statSync(path).mtimeMs;
|
|
3837
|
+
} catch {
|
|
3838
|
+
return null;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
function parseTimestamp(value) {
|
|
3842
|
+
if (typeof value !== "string")
|
|
3843
|
+
return null;
|
|
3844
|
+
const parsed = Date.parse(value);
|
|
3845
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
3846
|
+
}
|
|
3847
|
+
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
3848
|
+
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
3849
|
+
const next = [conflict, ...current].slice(0, limit);
|
|
3850
|
+
return { ...metadata, sync_conflicts: next };
|
|
3851
|
+
}
|
|
3852
|
+
function canonicalize(value) {
|
|
3853
|
+
if (Array.isArray(value))
|
|
3854
|
+
return value.map(canonicalize);
|
|
3855
|
+
if (!value || typeof value !== "object")
|
|
3856
|
+
return value;
|
|
3857
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).sort(([a], [b]) => a.localeCompare(b));
|
|
3858
|
+
return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalize(entryValue)]));
|
|
3859
|
+
}
|
|
3860
|
+
function withoutSyncFingerprintMetadata(metadata) {
|
|
3861
|
+
const { [TODO_SYNC_FINGERPRINT_KEY]: _fingerprint, ...rest } = metadata;
|
|
3862
|
+
return rest;
|
|
3863
|
+
}
|
|
3864
|
+
function syncFingerprint(record) {
|
|
3865
|
+
const metadata = withoutSyncFingerprintMetadata(record.metadata || {});
|
|
3866
|
+
const canonical = canonicalize({ ...record, metadata });
|
|
3867
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
|
|
3868
|
+
}
|
|
3869
|
+
function withSyncFingerprint(record) {
|
|
3870
|
+
const metadata = withoutSyncFingerprintMetadata(record.metadata);
|
|
3871
|
+
return {
|
|
3872
|
+
...record,
|
|
3873
|
+
metadata: {
|
|
3874
|
+
...metadata,
|
|
3875
|
+
[TODO_SYNC_FINGERPRINT_KEY]: syncFingerprint({ ...record, metadata })
|
|
3876
|
+
}
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
function hasSyncFingerprintChanged(record) {
|
|
3880
|
+
const stored = record.metadata?.[TODO_SYNC_FINGERPRINT_KEY];
|
|
3881
|
+
if (typeof stored !== "string" || stored.length === 0)
|
|
3882
|
+
return null;
|
|
3883
|
+
return stored !== syncFingerprint(record);
|
|
3884
|
+
}
|
|
3885
|
+
var TODO_SYNC_FINGERPRINT_KEY = "todos_sync_fingerprint", HOME;
|
|
3886
|
+
var init_sync_utils = __esm(() => {
|
|
3887
|
+
HOME = getHomeDir();
|
|
3888
|
+
});
|
|
3889
|
+
|
|
3793
3890
|
// src/storage/config.ts
|
|
3794
3891
|
var exports_config = {};
|
|
3795
3892
|
__export(exports_config, {
|
|
@@ -4120,8 +4217,8 @@ __export(exports_database, {
|
|
|
4120
4217
|
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
4121
4218
|
});
|
|
4122
4219
|
import { Database } from "bun:sqlite";
|
|
4123
|
-
import { existsSync as
|
|
4124
|
-
import { dirname, join, resolve as resolve2 } from "path";
|
|
4220
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
4221
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
4125
4222
|
function isInMemoryDb(path) {
|
|
4126
4223
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
4127
4224
|
}
|
|
@@ -4130,8 +4227,8 @@ function findNearestProjectDb(startDir) {
|
|
|
4130
4227
|
const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
|
|
4131
4228
|
let dir = resolve2(startDir);
|
|
4132
4229
|
while (true) {
|
|
4133
|
-
const candidate =
|
|
4134
|
-
if (
|
|
4230
|
+
const candidate = join2(dir, ".hasna", "todos", "todos.db");
|
|
4231
|
+
if (existsSync3(candidate))
|
|
4135
4232
|
return candidate;
|
|
4136
4233
|
if (dir === stopAt)
|
|
4137
4234
|
break;
|
|
@@ -4145,7 +4242,7 @@ function findNearestProjectDb(startDir) {
|
|
|
4145
4242
|
function findGitRoot(startDir) {
|
|
4146
4243
|
let dir = resolve2(startDir);
|
|
4147
4244
|
while (true) {
|
|
4148
|
-
if (
|
|
4245
|
+
if (existsSync3(join2(dir, ".git")))
|
|
4149
4246
|
return dir;
|
|
4150
4247
|
const parent = dirname(dir);
|
|
4151
4248
|
if (parent === dir)
|
|
@@ -4155,8 +4252,7 @@ function findGitRoot(startDir) {
|
|
|
4155
4252
|
return null;
|
|
4156
4253
|
}
|
|
4157
4254
|
function getGlobalDbPath() {
|
|
4158
|
-
|
|
4159
|
-
return join(home, ".hasna", "todos", "todos.db");
|
|
4255
|
+
return join2(getHomeDir(), ".hasna", "todos", "todos.db");
|
|
4160
4256
|
}
|
|
4161
4257
|
function hasExplicitProjectArg(args = process.argv.slice(2)) {
|
|
4162
4258
|
return args.some((arg) => arg === "--project" || arg.startsWith("--project="));
|
|
@@ -4194,7 +4290,7 @@ function getDbPath() {
|
|
|
4194
4290
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
4195
4291
|
const gitRoot = findGitRoot(cwd);
|
|
4196
4292
|
if (gitRoot && canCreateScopedProjectDb()) {
|
|
4197
|
-
return
|
|
4293
|
+
return join2(gitRoot, ".hasna", "todos", "todos.db");
|
|
4198
4294
|
}
|
|
4199
4295
|
}
|
|
4200
4296
|
return getGlobalDbPath();
|
|
@@ -4202,16 +4298,16 @@ function getDbPath() {
|
|
|
4202
4298
|
function getDatabasePath() {
|
|
4203
4299
|
return getDbPath();
|
|
4204
4300
|
}
|
|
4205
|
-
function
|
|
4301
|
+
function ensureDir2(filePath) {
|
|
4206
4302
|
if (isInMemoryDb(filePath))
|
|
4207
4303
|
return;
|
|
4208
4304
|
const dir = dirname(resolve2(filePath));
|
|
4209
|
-
if (!
|
|
4210
|
-
|
|
4305
|
+
if (!existsSync3(dir)) {
|
|
4306
|
+
mkdirSync2(dir, { recursive: true });
|
|
4211
4307
|
}
|
|
4212
4308
|
}
|
|
4213
4309
|
function openDatabase(path) {
|
|
4214
|
-
|
|
4310
|
+
ensureDir2(path);
|
|
4215
4311
|
const db = new Database(path);
|
|
4216
4312
|
db.run("PRAGMA busy_timeout = 5000");
|
|
4217
4313
|
db.run("PRAGMA journal_mode = WAL");
|
|
@@ -4378,105 +4474,10 @@ var init_database = __esm(() => {
|
|
|
4378
4474
|
init_machines();
|
|
4379
4475
|
init_identity_mapping();
|
|
4380
4476
|
init_types();
|
|
4477
|
+
init_sync_utils();
|
|
4381
4478
|
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
|
|
4382
4479
|
});
|
|
4383
4480
|
|
|
4384
|
-
// src/lib/sync-utils.ts
|
|
4385
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
4386
|
-
import { createHash } from "crypto";
|
|
4387
|
-
import { join as join2 } from "path";
|
|
4388
|
-
function getHomeDir() {
|
|
4389
|
-
return process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4390
|
-
}
|
|
4391
|
-
function getTodosGlobalDir() {
|
|
4392
|
-
return join2(getHomeDir(), ".hasna", "todos");
|
|
4393
|
-
}
|
|
4394
|
-
function ensureDir2(dir) {
|
|
4395
|
-
if (!existsSync3(dir))
|
|
4396
|
-
mkdirSync2(dir, { recursive: true });
|
|
4397
|
-
}
|
|
4398
|
-
function listJsonFiles(dir) {
|
|
4399
|
-
if (!existsSync3(dir))
|
|
4400
|
-
return [];
|
|
4401
|
-
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
4402
|
-
}
|
|
4403
|
-
function readJsonFile(path) {
|
|
4404
|
-
try {
|
|
4405
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
4406
|
-
} catch {
|
|
4407
|
-
return null;
|
|
4408
|
-
}
|
|
4409
|
-
}
|
|
4410
|
-
function writeJsonFile(path, data) {
|
|
4411
|
-
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
4412
|
-
`);
|
|
4413
|
-
}
|
|
4414
|
-
function readHighWaterMark(dir) {
|
|
4415
|
-
const path = join2(dir, ".highwatermark");
|
|
4416
|
-
if (!existsSync3(path))
|
|
4417
|
-
return 1;
|
|
4418
|
-
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
4419
|
-
return isNaN(val) ? 1 : val;
|
|
4420
|
-
}
|
|
4421
|
-
function writeHighWaterMark(dir, value) {
|
|
4422
|
-
writeFileSync(join2(dir, ".highwatermark"), String(value));
|
|
4423
|
-
}
|
|
4424
|
-
function getFileMtimeMs(path) {
|
|
4425
|
-
try {
|
|
4426
|
-
return statSync(path).mtimeMs;
|
|
4427
|
-
} catch {
|
|
4428
|
-
return null;
|
|
4429
|
-
}
|
|
4430
|
-
}
|
|
4431
|
-
function parseTimestamp(value) {
|
|
4432
|
-
if (typeof value !== "string")
|
|
4433
|
-
return null;
|
|
4434
|
-
const parsed = Date.parse(value);
|
|
4435
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
4436
|
-
}
|
|
4437
|
-
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
4438
|
-
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
4439
|
-
const next = [conflict, ...current].slice(0, limit);
|
|
4440
|
-
return { ...metadata, sync_conflicts: next };
|
|
4441
|
-
}
|
|
4442
|
-
function canonicalize(value) {
|
|
4443
|
-
if (Array.isArray(value))
|
|
4444
|
-
return value.map(canonicalize);
|
|
4445
|
-
if (!value || typeof value !== "object")
|
|
4446
|
-
return value;
|
|
4447
|
-
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).sort(([a], [b]) => a.localeCompare(b));
|
|
4448
|
-
return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalize(entryValue)]));
|
|
4449
|
-
}
|
|
4450
|
-
function withoutSyncFingerprintMetadata(metadata) {
|
|
4451
|
-
const { [TODO_SYNC_FINGERPRINT_KEY]: _fingerprint, ...rest } = metadata;
|
|
4452
|
-
return rest;
|
|
4453
|
-
}
|
|
4454
|
-
function syncFingerprint(record) {
|
|
4455
|
-
const metadata = withoutSyncFingerprintMetadata(record.metadata || {});
|
|
4456
|
-
const canonical = canonicalize({ ...record, metadata });
|
|
4457
|
-
return `sha256:${createHash("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
|
|
4458
|
-
}
|
|
4459
|
-
function withSyncFingerprint(record) {
|
|
4460
|
-
const metadata = withoutSyncFingerprintMetadata(record.metadata);
|
|
4461
|
-
return {
|
|
4462
|
-
...record,
|
|
4463
|
-
metadata: {
|
|
4464
|
-
...metadata,
|
|
4465
|
-
[TODO_SYNC_FINGERPRINT_KEY]: syncFingerprint({ ...record, metadata })
|
|
4466
|
-
}
|
|
4467
|
-
};
|
|
4468
|
-
}
|
|
4469
|
-
function hasSyncFingerprintChanged(record) {
|
|
4470
|
-
const stored = record.metadata?.[TODO_SYNC_FINGERPRINT_KEY];
|
|
4471
|
-
if (typeof stored !== "string" || stored.length === 0)
|
|
4472
|
-
return null;
|
|
4473
|
-
return stored !== syncFingerprint(record);
|
|
4474
|
-
}
|
|
4475
|
-
var TODO_SYNC_FINGERPRINT_KEY = "todos_sync_fingerprint", HOME;
|
|
4476
|
-
var init_sync_utils = __esm(() => {
|
|
4477
|
-
HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4478
|
-
});
|
|
4479
|
-
|
|
4480
4481
|
// src/lib/config.ts
|
|
4481
4482
|
import { existsSync as existsSync4 } from "fs";
|
|
4482
4483
|
import { dirname as dirname2, join as join3 } from "path";
|
|
@@ -4502,7 +4503,7 @@ function loadConfig() {
|
|
|
4502
4503
|
}
|
|
4503
4504
|
function saveConfig(config) {
|
|
4504
4505
|
const configPath = getConfigPath();
|
|
4505
|
-
|
|
4506
|
+
ensureDir(dirname2(configPath));
|
|
4506
4507
|
writeJsonFile(configPath, config);
|
|
4507
4508
|
cached = config;
|
|
4508
4509
|
return config;
|
|
@@ -5847,7 +5848,7 @@ var init_event_hooks = __esm(() => {
|
|
|
5847
5848
|
// node_modules/.bun/@hasna+events@0.1.11/node_modules/@hasna/events/dist/index.js
|
|
5848
5849
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
5849
5850
|
import { existsSync as existsSync5 } from "fs";
|
|
5850
|
-
import { homedir } from "os";
|
|
5851
|
+
import { homedir as homedir2 } from "os";
|
|
5851
5852
|
import { join as join4 } from "path";
|
|
5852
5853
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
5853
5854
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -5949,7 +5950,7 @@ function channelMatchesEvent(channel, event) {
|
|
|
5949
5950
|
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
5950
5951
|
}
|
|
5951
5952
|
function getEventsDataDir(override) {
|
|
5952
|
-
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(
|
|
5953
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
|
|
5953
5954
|
}
|
|
5954
5955
|
|
|
5955
5956
|
class JsonEventsStore {
|
|
@@ -12031,7 +12032,7 @@ var init_tasks = __esm(() => {
|
|
|
12031
12032
|
// package.json
|
|
12032
12033
|
var package_default = {
|
|
12033
12034
|
name: "@hasna/todos",
|
|
12034
|
-
version: "0.
|
|
12035
|
+
version: "0.15.1",
|
|
12035
12036
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12036
12037
|
type: "module",
|
|
12037
12038
|
main: "dist/index.js",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageName": "@hasna/todos",
|
|
3
|
-
"packageVersion": "0.
|
|
3
|
+
"packageVersion": "0.15.1",
|
|
4
4
|
"repository": "https://github.com/hasna/todos.git",
|
|
5
|
-
"gitCommit": "
|
|
6
|
-
"gitTree": "
|
|
7
|
-
"sourceTreeSha256": "
|
|
8
|
-
"generatedAt": "2026-08-
|
|
5
|
+
"gitCommit": "f62a9681a546c432583e5393670e4596b913e4f2",
|
|
6
|
+
"gitTree": "0337b5c4f304074196541df984c30443e0459ae6",
|
|
7
|
+
"sourceTreeSha256": "d293f3d60bdc62d1b2d616b674146a99cae188828ac07a1ba303fc58e3d8341c",
|
|
8
|
+
"generatedAt": "2026-08-03T16:59:17.000Z"
|
|
9
9
|
}
|
package/dist/sdk/index.js
CHANGED
|
@@ -58,11 +58,12 @@ import { dirname, join as join2 } from "path";
|
|
|
58
58
|
|
|
59
59
|
// src/lib/sync-utils.ts
|
|
60
60
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
61
|
+
import { homedir } from "os";
|
|
61
62
|
import { join } from "path";
|
|
62
|
-
var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
63
63
|
function getHomeDir() {
|
|
64
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
64
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
65
65
|
}
|
|
66
|
+
var HOME = getHomeDir();
|
|
66
67
|
function getTodosGlobalDir() {
|
|
67
68
|
return join(getHomeDir(), ".hasna", "todos");
|
|
68
69
|
}
|