@hasna/todos 0.15.49 → 0.15.51

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/mcp/index.js CHANGED
@@ -4089,18 +4089,112 @@ var init_identity_mapping = __esm(() => {
4089
4089
  });
4090
4090
  });
4091
4091
 
4092
- // src/lib/sync-utils.ts
4093
- import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
4092
+ // node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
4094
4093
  import { homedir } from "os";
4095
4094
  import { join } from "path";
4095
+ function assertApp(app) {
4096
+ if (typeof app !== "string" || app.length === 0) {
4097
+ throw new TypeError("paths: app must be a non-empty string");
4098
+ }
4099
+ if (!APP_SLUG_RE.test(app)) {
4100
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
4101
+ }
4102
+ }
4103
+ function envOf(options) {
4104
+ return options.env ?? process.env;
4105
+ }
4106
+ function envValue(options, kind) {
4107
+ const value = envOf(options)[KIND_ENV[kind]];
4108
+ return typeof value === "string" && value.length > 0 ? value : undefined;
4109
+ }
4110
+ function isMacOS(platform) {
4111
+ return platform === "darwin";
4112
+ }
4113
+ function baseDir(kind, options) {
4114
+ const override = envValue(options, kind);
4115
+ if (override)
4116
+ return override;
4117
+ const home = options.home ?? homedir();
4118
+ const platform = options.platform ?? process.platform;
4119
+ if (isMacOS(platform)) {
4120
+ switch (kind) {
4121
+ case "config":
4122
+ case "data":
4123
+ return join(home, "Library", "Application Support", "Hasna");
4124
+ case "cache":
4125
+ return join(home, "Library", "Caches", "Hasna");
4126
+ case "state":
4127
+ return join(home, "Library", "Logs", "Hasna");
4128
+ }
4129
+ }
4130
+ switch (kind) {
4131
+ case "config":
4132
+ return join(home, ".config", "hasna");
4133
+ case "data":
4134
+ return join(home, ".local", "share", "hasna");
4135
+ case "state":
4136
+ return join(home, ".local", "state", "hasna");
4137
+ case "cache":
4138
+ return join(home, ".cache", "hasna");
4139
+ }
4140
+ }
4141
+ function resolvePath(kind, options) {
4142
+ assertApp(options.app);
4143
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
4144
+ return join(baseDir(kind, options), appSegment);
4145
+ }
4146
+ function dataDir(options) {
4147
+ return resolvePath("data", options);
4148
+ }
4149
+ var KIND_ENV, APP_SLUG_RE;
4150
+ var init_dist = __esm(() => {
4151
+ KIND_ENV = {
4152
+ config: "HASNA_CONFIG_HOME",
4153
+ data: "HASNA_DATA_HOME",
4154
+ state: "HASNA_STATE_HOME",
4155
+ cache: "HASNA_CACHE_HOME"
4156
+ };
4157
+ APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4158
+ });
4159
+
4160
+ // src/lib/paths.ts
4161
+ import { existsSync as existsSync2 } from "fs";
4162
+ import { homedir as homedir2 } from "os";
4163
+ import { join as join2, resolve as resolve2 } from "path";
4164
+ function effectiveHome(env = process.env) {
4165
+ return env.HOME || env.USERPROFILE || homedir2();
4166
+ }
4167
+ function legacyHomeDir(env = process.env) {
4168
+ return join2(effectiveHome(env), ".hasna", "todos");
4169
+ }
4170
+ function resolverHome(env = process.env) {
4171
+ return dataDir({ app: "todos", home: effectiveHome(env), env });
4172
+ }
4173
+ function adoptResolverHome(resolved, env = process.env) {
4174
+ const dataOverride = env.HASNA_DATA_HOME;
4175
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
4176
+ return true;
4177
+ return existsSync2(join2(resolved, "todos.db")) || existsSync2(join2(resolved, "config.json"));
4178
+ }
4179
+ function getTodosDir(env = process.env) {
4180
+ const resolved = resolverHome(env);
4181
+ return resolve2(adoptResolverHome(resolved, env) ? resolved : legacyHomeDir(env));
4182
+ }
4183
+ var init_paths = __esm(() => {
4184
+ init_dist();
4185
+ });
4186
+
4187
+ // src/lib/sync-utils.ts
4188
+ import { existsSync as existsSync3, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
4189
+ import { homedir as homedir3 } from "os";
4096
4190
  function getHomeDir() {
4097
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
4191
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir3();
4098
4192
  }
4099
4193
  function getTodosGlobalDir() {
4100
- return join(getHomeDir(), ".hasna", "todos");
4194
+ return getTodosDir();
4101
4195
  }
4102
4196
  function ensureDir(dir) {
4103
- if (!existsSync2(dir))
4197
+ if (!existsSync3(dir))
4104
4198
  mkdirSync(dir, { recursive: true });
4105
4199
  }
4106
4200
  function readJsonFile(path) {
@@ -4121,6 +4215,7 @@ function appendSyncConflict(metadata, conflict, limit = 5) {
4121
4215
  }
4122
4216
  var HOME;
4123
4217
  var init_sync_utils = __esm(() => {
4218
+ init_paths();
4124
4219
  HOME = getHomeDir();
4125
4220
  });
4126
4221
 
@@ -4431,18 +4526,18 @@ __export(exports_database, {
4431
4526
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
4432
4527
  });
4433
4528
  import { Database } from "bun:sqlite";
4434
- import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
4435
- import { dirname, join as join2, resolve as resolve2 } from "path";
4529
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
4530
+ import { dirname, join as join3, resolve as resolve3 } from "path";
4436
4531
  function isInMemoryDb(path) {
4437
4532
  return path === ":memory:" || path.startsWith("file::memory:");
4438
4533
  }
4439
4534
  function findNearestProjectDb(startDir) {
4440
4535
  const gitRoot = findGitRoot(startDir);
4441
- const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
4442
- let dir = resolve2(startDir);
4536
+ const stopAt = gitRoot ? resolve3(gitRoot) : resolve3(startDir);
4537
+ let dir = resolve3(startDir);
4443
4538
  while (true) {
4444
- const candidate = join2(dir, ".hasna", "todos", "todos.db");
4445
- if (existsSync3(candidate))
4539
+ const candidate = join3(dir, ".hasna", "todos", "todos.db");
4540
+ if (existsSync4(candidate))
4446
4541
  return candidate;
4447
4542
  if (dir === stopAt)
4448
4543
  break;
@@ -4454,9 +4549,9 @@ function findNearestProjectDb(startDir) {
4454
4549
  return null;
4455
4550
  }
4456
4551
  function findGitRoot(startDir) {
4457
- let dir = resolve2(startDir);
4552
+ let dir = resolve3(startDir);
4458
4553
  while (true) {
4459
- if (existsSync3(join2(dir, ".git")))
4554
+ if (existsSync4(join3(dir, ".git")))
4460
4555
  return dir;
4461
4556
  const parent = dirname(dir);
4462
4557
  if (parent === dir)
@@ -4466,7 +4561,7 @@ function findGitRoot(startDir) {
4466
4561
  return null;
4467
4562
  }
4468
4563
  function getGlobalDbPath() {
4469
- return join2(getHomeDir(), ".hasna", "todos", "todos.db");
4564
+ return join3(getTodosGlobalDir(), "todos.db");
4470
4565
  }
4471
4566
  function hasExplicitProjectArg(args = process.argv.slice(2)) {
4472
4567
  return args.some((arg) => arg === "--project" || arg.startsWith("--project="));
@@ -4504,7 +4599,7 @@ function getDbPath() {
4504
4599
  if (process.env["TODOS_DB_SCOPE"] === "project") {
4505
4600
  const gitRoot = findGitRoot(cwd);
4506
4601
  if (gitRoot && canCreateScopedProjectDb()) {
4507
- return join2(gitRoot, ".hasna", "todos", "todos.db");
4602
+ return join3(gitRoot, ".hasna", "todos", "todos.db");
4508
4603
  }
4509
4604
  }
4510
4605
  return getGlobalDbPath();
@@ -4515,8 +4610,8 @@ function getDatabasePath() {
4515
4610
  function ensureDir2(filePath) {
4516
4611
  if (isInMemoryDb(filePath))
4517
4612
  return;
4518
- const dir = dirname(resolve2(filePath));
4519
- if (!existsSync3(dir)) {
4613
+ const dir = dirname(resolve3(filePath));
4614
+ if (!existsSync4(dir)) {
4520
4615
  mkdirSync2(dir, { recursive: true });
4521
4616
  }
4522
4617
  }
@@ -9433,15 +9528,15 @@ var init_zod = __esm(() => {
9433
9528
  });
9434
9529
 
9435
9530
  // src/lib/config.ts
9436
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
9437
- import { dirname as dirname2, join as join3 } from "path";
9531
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
9532
+ import { dirname as dirname2, join as join4 } from "path";
9438
9533
  function getConfigPath() {
9439
- return join3(getTodosGlobalDir(), "config.json");
9534
+ return join4(getTodosGlobalDir(), "config.json");
9440
9535
  }
9441
9536
  function loadConfig() {
9442
9537
  if (cached)
9443
9538
  return cached;
9444
- if (!existsSync4(getConfigPath())) {
9539
+ if (!existsSync5(getConfigPath())) {
9445
9540
  cached = {};
9446
9541
  return cached;
9447
9542
  }
@@ -10404,14 +10499,14 @@ var init_completion_guard = __esm(() => {
10404
10499
 
10405
10500
  // src/lib/event-emission-safety.ts
10406
10501
  import { tmpdir } from "os";
10407
- import { resolve as resolve3, sep } from "path";
10502
+ import { resolve as resolve4, sep } from "path";
10408
10503
  function envFlag(name) {
10409
10504
  const value = process.env[name]?.trim().toLowerCase();
10410
10505
  return value === "1" || value === "true" || value === "yes" || value === "on";
10411
10506
  }
10412
10507
  function isUnder(parent, child) {
10413
- const normalizedParent = resolve3(parent);
10414
- const normalizedChild = resolve3(child);
10508
+ const normalizedParent = resolve4(parent);
10509
+ const normalizedChild = resolve4(child);
10415
10510
  return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
10416
10511
  }
10417
10512
  function databasePathFromDatabase(db) {
@@ -10454,9 +10549,9 @@ var init_event_emission_safety = __esm(() => {
10454
10549
  });
10455
10550
 
10456
10551
  // src/lib/workspace-trust.ts
10457
- import { relative, resolve as resolve4 } from "path";
10552
+ import { relative, resolve as resolve5 } from "path";
10458
10553
  function normalizePath(path) {
10459
- return resolve4(path);
10554
+ return resolve5(path);
10460
10555
  }
10461
10556
  function unique2(values) {
10462
10557
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -10638,9 +10733,9 @@ var init_workspace_trust = __esm(() => {
10638
10733
  });
10639
10734
 
10640
10735
  // src/lib/runner-sandbox.ts
10641
- import { relative as relative2, resolve as resolve5 } from "path";
10736
+ import { relative as relative2, resolve as resolve6 } from "path";
10642
10737
  function normalizePath2(path) {
10643
- return resolve5(path);
10738
+ return resolve6(path);
10644
10739
  }
10645
10740
  function unique3(values) {
10646
10741
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -10830,7 +10925,7 @@ var init_runner_sandbox = __esm(() => {
10830
10925
  // src/lib/event-hooks.ts
10831
10926
  import { createHash, randomUUID } from "crypto";
10832
10927
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
10833
- import { dirname as dirname3, resolve as resolve6 } from "path";
10928
+ import { dirname as dirname3, resolve as resolve7 } from "path";
10834
10929
  import { createConnection } from "net";
10835
10930
  function safeName(name) {
10836
10931
  const trimmed = name.trim();
@@ -10968,7 +11063,7 @@ async function deliverHook(hook, envelope) {
10968
11063
  if (hook.target === "stdout") {
10969
11064
  output = line.trim();
10970
11065
  } else if (hook.target === "file") {
10971
- const filePath = resolve6(hook.file_path);
11066
+ const filePath = resolve7(hook.file_path);
10972
11067
  mkdirSync3(dirname3(filePath), { recursive: true });
10973
11068
  appendFileSync(filePath, line);
10974
11069
  } else if (hook.target === "socket") {
@@ -11089,9 +11184,9 @@ var init_event_hooks = __esm(() => {
11089
11184
  // node_modules/.bun/@hasna+events@0.1.16/node_modules/@hasna/events/dist/index.js
11090
11185
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
11091
11186
  import { Buffer as Buffer2 } from "buffer";
11092
- import { existsSync as existsSync5 } from "fs";
11093
- import { homedir as homedir2 } from "os";
11094
- import { join as join4 } from "path";
11187
+ import { existsSync as existsSync6 } from "fs";
11188
+ import { homedir as homedir4 } from "os";
11189
+ import { join as join5 } from "path";
11095
11190
  import { createHmac, timingSafeEqual } from "crypto";
11096
11191
  import { randomUUID as randomUUID2 } from "crypto";
11097
11192
  import { spawn } from "child_process";
@@ -11192,7 +11287,7 @@ function channelMatchesEvent(channel, event) {
11192
11287
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
11193
11288
  }
11194
11289
  function getEventsDataDir(override) {
11195
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
11290
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir4(), ".hasna", "events");
11196
11291
  }
11197
11292
 
11198
11293
  class JsonEventsStore {
@@ -11201,12 +11296,12 @@ class JsonEventsStore {
11201
11296
  channelsPath;
11202
11297
  eventsPath;
11203
11298
  deliveriesPath;
11204
- constructor(dataDir = getEventsDataDir()) {
11205
- this.dataDir = dataDir;
11206
- this.runtime = localJsonRuntime(dataDir);
11207
- this.channelsPath = join4(dataDir, "channels.json");
11208
- this.eventsPath = join4(dataDir, "events.json");
11209
- this.deliveriesPath = join4(dataDir, "deliveries.json");
11299
+ constructor(dataDir2 = getEventsDataDir()) {
11300
+ this.dataDir = dataDir2;
11301
+ this.runtime = localJsonRuntime(dataDir2);
11302
+ this.channelsPath = join5(dataDir2, "channels.json");
11303
+ this.eventsPath = join5(dataDir2, "events.json");
11304
+ this.deliveriesPath = join5(dataDir2, "deliveries.json");
11210
11305
  }
11211
11306
  async init() {
11212
11307
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -11323,7 +11418,7 @@ class JsonEventsStore {
11323
11418
  };
11324
11419
  }
11325
11420
  async ensureArrayFile(path) {
11326
- if (!existsSync5(path)) {
11421
+ if (!existsSync6(path)) {
11327
11422
  await writeFile(path, `[]
11328
11423
  `, { encoding: "utf-8", mode: 384 });
11329
11424
  }
@@ -11353,7 +11448,7 @@ class JsonEventsStore {
11353
11448
  });
11354
11449
  }
11355
11450
  }
11356
- function localJsonRuntime(dataDir = getEventsDataDir()) {
11451
+ function localJsonRuntime(dataDir2 = getEventsDataDir()) {
11357
11452
  return {
11358
11453
  mode: "local-files",
11359
11454
  name: "json-events-store",
@@ -11366,7 +11461,7 @@ function localJsonRuntime(dataDir = getEventsDataDir()) {
11366
11461
  durable: true,
11367
11462
  idempotency: "best-effort-local",
11368
11463
  replayCursors: true,
11369
- description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
11464
+ description: `Local JSON files in ${dataDir2}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
11370
11465
  };
11371
11466
  }
11372
11467
  function encodeLocalJsonEventCursor(offset, options = {}) {
@@ -11545,7 +11640,7 @@ async function dispatchCommand(event, channel) {
11545
11640
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
11546
11641
  HASNA_EVENT_JSON: eventJson
11547
11642
  };
11548
- return new Promise((resolve7) => {
11643
+ return new Promise((resolve8) => {
11549
11644
  const child = spawn(channel.command.command, channel.command.args ?? [], {
11550
11645
  cwd: channel.command.cwd,
11551
11646
  env,
@@ -11563,7 +11658,7 @@ async function dispatchCommand(event, channel) {
11563
11658
  });
11564
11659
  child.on("error", (error) => {
11565
11660
  clearTimeout(timeout);
11566
- resolve7({
11661
+ resolve8({
11567
11662
  attempt: 1,
11568
11663
  status: "failed",
11569
11664
  startedAt,
@@ -11576,7 +11671,7 @@ async function dispatchCommand(event, channel) {
11576
11671
  child.on("close", (code, signal) => {
11577
11672
  clearTimeout(timeout);
11578
11673
  const success = code === 0;
11579
- resolve7({
11674
+ resolve8({
11580
11675
  attempt: 1,
11581
11676
  status: success ? "success" : "failed",
11582
11677
  startedAt,
@@ -11918,7 +12013,7 @@ function normalizeRetryPolicy(policy) {
11918
12013
  };
11919
12014
  }
11920
12015
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:", DEFAULT_EVENT_PAGE_LIMIT = 100, MAX_EVENT_PAGE_LIMIT = 1000, DEFAULT_SIGNATURE_TOLERANCE_MS, EventValidationError, defaultEventTypeCatalog, APP_EVENT_V1_MAX_DATA_BYTES;
11921
- var init_dist = __esm(() => {
12016
+ var init_dist2 = __esm(() => {
11922
12017
  DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
11923
12018
  EventValidationError = class EventValidationError extends Error {
11924
12019
  eventType;
@@ -12338,7 +12433,7 @@ function emitSharedTaskEventQuiet(input) {
12338
12433
  }
12339
12434
  var SOURCE = "todos";
12340
12435
  var init_shared_events = __esm(() => {
12341
- init_dist();
12436
+ init_dist2();
12342
12437
  init_database();
12343
12438
  init_projects();
12344
12439
  init_task_lists();
@@ -12882,14 +12977,14 @@ var init_task_parent_integrity = __esm(() => {
12882
12977
  });
12883
12978
 
12884
12979
  // src/lib/creator-identity.ts
12885
- import { existsSync as existsSync6, rmSync } from "fs";
12886
- import { join as join5 } from "path";
12980
+ import { existsSync as existsSync7, rmSync } from "fs";
12981
+ import { join as join6 } from "path";
12887
12982
  function identityFilePath() {
12888
- return join5(getTodosGlobalDir(), "identity.json");
12983
+ return join6(getTodosGlobalDir(), "identity.json");
12889
12984
  }
12890
12985
  function readPersistedIdentity() {
12891
12986
  const path = identityFilePath();
12892
- if (!existsSync6(path))
12987
+ if (!existsSync7(path))
12893
12988
  return null;
12894
12989
  const parsed = readJsonFile(path);
12895
12990
  if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
@@ -14420,17 +14515,17 @@ function sanitizeCreateTaskInput(input) {
14420
14515
  return {
14421
14516
  ...input,
14422
14517
  title: sanitizePreWriteText(input.title, "task.title"),
14423
- description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
14518
+ description: input.description == null ? input.description : sanitizePreWriteText(input.description, "task.description"),
14424
14519
  tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
14425
14520
  metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined,
14426
- reason: input.reason !== undefined ? sanitizePreWriteText(input.reason, "task.reason") : undefined
14521
+ reason: input.reason == null ? input.reason : sanitizePreWriteText(input.reason, "task.reason")
14427
14522
  };
14428
14523
  }
14429
14524
  function sanitizeUpdateTaskInput(input) {
14430
14525
  return {
14431
14526
  ...input,
14432
14527
  title: input.title !== undefined ? sanitizePreWriteText(input.title, "task.title") : undefined,
14433
- description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
14528
+ description: input.description == null ? input.description : sanitizePreWriteText(input.description, "task.description"),
14434
14529
  tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
14435
14530
  metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined
14436
14531
  };
@@ -16427,28 +16522,28 @@ var init_boards = __esm(() => {
16427
16522
 
16428
16523
  // src/lib/artifact-store.ts
16429
16524
  import { createHash as createHash2 } from "crypto";
16430
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
16431
- import { basename, dirname as dirname4, join as join6, resolve as resolve7 } from "path";
16525
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
16526
+ import { basename, dirname as dirname4, join as join7, resolve as resolve8 } from "path";
16432
16527
  import { tmpdir as tmpdir2 } from "os";
16433
16528
  function isInMemoryDb2(path) {
16434
16529
  return path === ":memory:" || path.startsWith("file::memory:");
16435
16530
  }
16436
16531
  function artifactStoreRoot() {
16437
16532
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
16438
- return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
16533
+ return resolve8(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
16439
16534
  if (process.env["TODOS_ARTIFACTS_DIR"])
16440
- return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
16535
+ return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
16441
16536
  const dbPath = getDatabasePath();
16442
16537
  if (isInMemoryDb2(dbPath))
16443
- return join6(tmpdir2(), "hasna-todos-artifacts");
16444
- return join6(dirname4(resolve7(dbPath)), "artifacts");
16538
+ return join7(tmpdir2(), "hasna-todos-artifacts");
16539
+ return join7(dirname4(resolve8(dbPath)), "artifacts");
16445
16540
  }
16446
16541
  function artifactStorePath(relativePath) {
16447
16542
  const normalized = relativePath.replace(/\\/g, "/");
16448
16543
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
16449
16544
  throw new Error("Invalid artifact store path");
16450
16545
  }
16451
- return join6(artifactStoreRoot(), normalized);
16546
+ return join7(artifactStoreRoot(), normalized);
16452
16547
  }
16453
16548
  function sha256(buffer) {
16454
16549
  return createHash2("sha256").update(buffer).digest("hex");
@@ -16488,8 +16583,8 @@ function mediaTypeFor(path, textLike) {
16488
16583
  return "application/octet-stream";
16489
16584
  }
16490
16585
  function storeArtifactContent(input) {
16491
- const sourcePath = resolve7(input.path);
16492
- if (!existsSync7(sourcePath))
16586
+ const sourcePath = resolve8(input.path);
16587
+ if (!existsSync8(sourcePath))
16493
16588
  return null;
16494
16589
  const sourceStat = statSync2(sourcePath);
16495
16590
  if (!sourceStat.isFile())
@@ -16506,9 +16601,9 @@ function storeArtifactContent(input) {
16506
16601
  redactionStatus = "redacted";
16507
16602
  }
16508
16603
  const storedSha = sha256(storedBuffer);
16509
- const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
16604
+ const relativePath = join7("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
16510
16605
  const destination = artifactStorePath(relativePath);
16511
- if (!existsSync7(destination)) {
16606
+ if (!existsSync8(destination)) {
16512
16607
  mkdirSync4(dirname4(destination), { recursive: true });
16513
16608
  writeFileSync2(destination, storedBuffer);
16514
16609
  }
@@ -16568,7 +16663,7 @@ function verifyStoredArtifact(input) {
16568
16663
  };
16569
16664
  }
16570
16665
  const storedPath = artifactStorePath(store.relative_path);
16571
- if (!existsSync7(storedPath)) {
16666
+ if (!existsSync8(storedPath)) {
16572
16667
  return {
16573
16668
  id: input.id,
16574
16669
  path: input.path,
@@ -19046,10 +19141,10 @@ var init_token_utils = __esm(() => {
19046
19141
 
19047
19142
  // src/lib/assignee-validation.ts
19048
19143
  import { readFileSync as readFileSync4 } from "fs";
19049
- import { homedir as homedir3 } from "os";
19050
- import { join as join7 } from "path";
19144
+ import { homedir as homedir5 } from "os";
19145
+ import { join as join8 } from "path";
19051
19146
  function defaultSeatRosterPath() {
19052
- return process.env["TODOS_SEAT_ROSTER_PATH"] || join7(homedir3(), ".hasna", "identities", "hasna-seats.roster.json");
19147
+ return process.env["TODOS_SEAT_ROSTER_PATH"] || join8(homedir5(), ".hasna", "identities", "hasna-seats.roster.json");
19053
19148
  }
19054
19149
  function loadSeatSlugs(path = defaultSeatRosterPath()) {
19055
19150
  try {
@@ -21518,7 +21613,7 @@ var init_page_validation = __esm(() => {
21518
21613
  // src/cli/cloud-router.ts
21519
21614
  import { resolveStorageClient } from "@hasna/contracts/client/storage";
21520
21615
  import { randomUUID as randomUUID3 } from "crypto";
21521
- import { resolve as resolvePath } from "path";
21616
+ import { resolve as resolvePath2 } from "path";
21522
21617
  function emitTodosLocalFallbackNotice(env) {
21523
21618
  if (todosLocalFallbackNoticeEmitted)
21524
21619
  return;
@@ -22084,11 +22179,11 @@ function resolveCloudProjectRef(projects, ref) {
22084
22179
  const input = ref.trim();
22085
22180
  const normalizedRef = input.toLowerCase();
22086
22181
  const pathLike = input.startsWith(".") || input.includes("/") || input.includes("\\");
22087
- const normalizedPath = pathLike ? resolvePath(input) : undefined;
22182
+ const normalizedPath = pathLike ? resolvePath2(input) : undefined;
22088
22183
  const slug = cloudProjectSlug(pathLike ? cloudProjectPathBasename(input) : input);
22089
22184
  const matchGroups = [
22090
22185
  uniqueProjectMatches(projects, (project) => project.id.toLowerCase() === normalizedRef),
22091
- uniqueProjectMatches(projects, (project) => project.path === input || normalizedPath !== undefined && resolvePath(project.path) === normalizedPath),
22186
+ uniqueProjectMatches(projects, (project) => project.path === input || normalizedPath !== undefined && resolvePath2(project.path) === normalizedPath),
22092
22187
  uniqueProjectMatches(projects, (project) => project.name.toLowerCase() === normalizedRef),
22093
22188
  uniqueProjectMatches(projects, (project) => project.task_list_id === input || cloudProjectSlug(project.name) === slug || cloudProjectSlug(cloudProjectPathBasename(project.path)) === slug),
22094
22189
  uniqueProjectMatches(projects, (project) => project.id.toLowerCase().startsWith(normalizedRef))
@@ -22671,8 +22766,8 @@ var init_task_crud2 = __esm(() => {
22671
22766
  });
22672
22767
 
22673
22768
  // src/lib/project-bootstrap.ts
22674
- import { existsSync as existsSync8, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
22675
- import { basename as basename2, dirname as dirname5, resolve as resolve8 } from "path";
22769
+ import { existsSync as existsSync9, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
22770
+ import { basename as basename2, dirname as dirname5, resolve as resolve9 } from "path";
22676
22771
  function safeStat(path) {
22677
22772
  try {
22678
22773
  return statSync3(path);
@@ -22681,7 +22776,7 @@ function safeStat(path) {
22681
22776
  }
22682
22777
  }
22683
22778
  function canonicalPath(input) {
22684
- const resolved = resolve8(input);
22779
+ const resolved = resolve9(input);
22685
22780
  const stats = safeStat(resolved);
22686
22781
  if (stats?.isFile())
22687
22782
  return dirname5(resolved);
@@ -22690,7 +22785,7 @@ function canonicalPath(input) {
22690
22785
  function findUp(start, marker) {
22691
22786
  let current = canonicalPath(start);
22692
22787
  while (true) {
22693
- if (existsSync8(resolve8(current, marker)))
22788
+ if (existsSync9(resolve9(current, marker)))
22694
22789
  return current;
22695
22790
  const parent = dirname5(current);
22696
22791
  if (parent === current)
@@ -22701,8 +22796,8 @@ function findUp(start, marker) {
22701
22796
  function readPackageJson(path) {
22702
22797
  if (!path)
22703
22798
  return null;
22704
- const file = resolve8(path, "package.json");
22705
- if (!existsSync8(file))
22799
+ const file = resolve9(path, "package.json");
22800
+ if (!existsSync9(file))
22706
22801
  return null;
22707
22802
  try {
22708
22803
  const parsed = JSON.parse(readFileSync5(file, "utf-8"));
@@ -22724,7 +22819,7 @@ function workspaceMarker(root, rootPackage) {
22724
22819
  if (rootPackage?.workspaces)
22725
22820
  markers.push("package.json#workspaces");
22726
22821
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
22727
- if (existsSync8(resolve8(root, marker)))
22822
+ if (existsSync9(resolve9(root, marker)))
22728
22823
  markers.push(marker);
22729
22824
  }
22730
22825
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -23037,7 +23132,7 @@ var init_tags = __esm(() => {
23037
23132
  });
23038
23133
 
23039
23134
  // src/lib/retention-cleanup.ts
23040
- import { existsSync as existsSync9, unlinkSync } from "fs";
23135
+ import { existsSync as existsSync10, unlinkSync } from "fs";
23041
23136
  function normalizeScopes(scopes) {
23042
23137
  if (!scopes || scopes.length === 0)
23043
23138
  return [...ALL_SCOPES];
@@ -23240,7 +23335,7 @@ function applyRetentionCleanup(input, db) {
23240
23335
  for (const artifact of report.candidates.artifact_files) {
23241
23336
  try {
23242
23337
  const path = artifactStorePath(artifact.relative_path);
23243
- if (!existsSync9(path)) {
23338
+ if (!existsSync10(path)) {
23244
23339
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
23245
23340
  continue;
23246
23341
  }
@@ -23267,8 +23362,8 @@ var init_retention_cleanup = __esm(() => {
23267
23362
  });
23268
23363
 
23269
23364
  // src/lib/mention-resolver.ts
23270
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
23271
- import { basename as basename3, isAbsolute, join as join8, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
23365
+ import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
23366
+ import { basename as basename3, isAbsolute, join as join9, relative as relative3, resolve as resolve10, sep as sep2 } from "path";
23272
23367
  function blankResolution(parsed) {
23273
23368
  return {
23274
23369
  input: parsed.input,
@@ -23291,7 +23386,7 @@ function backlink(kind, key, label, target = key) {
23291
23386
  return { kind, key, label, target };
23292
23387
  }
23293
23388
  function normalizeWorkspace(workspace) {
23294
- return resolve9(workspace || process.cwd());
23389
+ return resolve10(workspace || process.cwd());
23295
23390
  }
23296
23391
  function isInside(root, absolutePath) {
23297
23392
  const rel = relative3(root, absolutePath);
@@ -23359,14 +23454,14 @@ function resolveFile(parsed, workspace) {
23359
23454
  resolution.warnings.push("path is empty or escapes the workspace");
23360
23455
  return resolution;
23361
23456
  }
23362
- const absolutePath = resolve9(workspace, relPath);
23457
+ const absolutePath = resolve10(workspace, relPath);
23363
23458
  if (!isInside(workspace, absolutePath)) {
23364
23459
  resolution.path = relPath;
23365
23460
  resolution.warnings.push("path escapes the workspace");
23366
23461
  return resolution;
23367
23462
  }
23368
23463
  resolution.path = relPath;
23369
- if (!existsSync10(absolutePath)) {
23464
+ if (!existsSync11(absolutePath)) {
23370
23465
  resolution.warnings.push("file does not exist in the local workspace");
23371
23466
  return resolution;
23372
23467
  }
@@ -23399,7 +23494,7 @@ function walkSourceFiles(root, current = root, files = []) {
23399
23494
  if (SKIP_DIRS.has(entry.name))
23400
23495
  continue;
23401
23496
  }
23402
- const absolutePath = join8(current, entry.name);
23497
+ const absolutePath = join9(current, entry.name);
23403
23498
  if (entry.isDirectory()) {
23404
23499
  if (!SKIP_DIRS.has(entry.name))
23405
23500
  walkSourceFiles(root, absolutePath, files);
@@ -23704,9 +23799,9 @@ var init_mention_resolver = __esm(() => {
23704
23799
  });
23705
23800
 
23706
23801
  // src/lib/policy-packs.ts
23707
- import { relative as relative4, resolve as resolve10 } from "path";
23802
+ import { relative as relative4, resolve as resolve11 } from "path";
23708
23803
  function normalizePath3(path) {
23709
- return resolve10(path);
23804
+ return resolve11(path);
23710
23805
  }
23711
23806
  function unique4(values) {
23712
23807
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -23761,7 +23856,7 @@ function commandMatches(commands, pattern) {
23761
23856
  }
23762
23857
  function pathMatches(paths, pattern, root) {
23763
23858
  return paths.filter((path) => {
23764
- const candidate = path.startsWith("/") ? path : resolve10(root, path);
23859
+ const candidate = path.startsWith("/") ? path : resolve11(root, path);
23765
23860
  if (!isPathInside3(root, candidate))
23766
23861
  return matchesPattern3(path, pattern);
23767
23862
  return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
@@ -27140,7 +27235,7 @@ var init_audit_ledger = __esm(() => {
27140
27235
 
27141
27236
  // src/lib/release-compatibility.ts
27142
27237
  import { readFileSync as readFileSync7 } from "fs";
27143
- import { join as join9, resolve as resolve11 } from "path";
27238
+ import { join as join10, resolve as resolve12 } from "path";
27144
27239
  import { Database as Database2 } from "bun:sqlite";
27145
27240
  function pass(id, message, details) {
27146
27241
  return { id, status: "passed", message, details };
@@ -27152,7 +27247,7 @@ function warn(id, message, details) {
27152
27247
  return { id, status: "warning", message, details };
27153
27248
  }
27154
27249
  function readPackageJson2(root) {
27155
- return JSON.parse(readFileSync7(join9(root, "package.json"), "utf8"));
27250
+ return JSON.parse(readFileSync7(join10(root, "package.json"), "utf8"));
27156
27251
  }
27157
27252
  function sortedKeys(value) {
27158
27253
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -27248,7 +27343,7 @@ function checkChangelog() {
27248
27343
  ];
27249
27344
  }
27250
27345
  function createReleaseCompatibilityReport(options = {}) {
27251
- const root = resolve11(options.root ?? process.cwd());
27346
+ const root = resolve12(options.root ?? process.cwd());
27252
27347
  const packageJson = readPackageJson2(root);
27253
27348
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
27254
27349
  const checks = [
@@ -31170,8 +31265,8 @@ var exports_doctor = {};
31170
31265
  __export(exports_doctor, {
31171
31266
  runTodosDoctor: () => runTodosDoctor
31172
31267
  });
31173
- import { chmodSync, copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
31174
- import { basename as basename4, dirname as dirname6, join as join10 } from "path";
31268
+ import { chmodSync, copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
31269
+ import { basename as basename4, dirname as dirname6, join as join11 } from "path";
31175
31270
  function tableExists2(db, table) {
31176
31271
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
31177
31272
  }
@@ -31265,7 +31360,7 @@ function findMissingProjectRoots(db) {
31265
31360
  continue;
31266
31361
  if (!row.path.startsWith("/"))
31267
31362
  continue;
31268
- if (!existsSync11(row.path))
31363
+ if (!existsSync12(row.path))
31269
31364
  missing++;
31270
31365
  }
31271
31366
  return missing;
@@ -31325,16 +31420,16 @@ function databasePermissionsAreUnsafe(dbPath) {
31325
31420
  function createBackup(dbPath) {
31326
31421
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
31327
31422
  return;
31328
- if (!existsSync11(dbPath))
31423
+ if (!existsSync12(dbPath))
31329
31424
  return;
31330
31425
  const stamp = now().replace(/[:.]/g, "-");
31331
- const backupDir = join10(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
31426
+ const backupDir = join11(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
31332
31427
  const files = [];
31333
31428
  mkdirSync5(backupDir, { recursive: true });
31334
31429
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
31335
- if (!existsSync11(source))
31430
+ if (!existsSync12(source))
31336
31431
  continue;
31337
- const target = join10(backupDir, basename4(source));
31432
+ const target = join11(backupDir, basename4(source));
31338
31433
  copyFileSync(source, target);
31339
31434
  files.push(target);
31340
31435
  }
@@ -34095,7 +34190,7 @@ var init_agent_run_dispatcher = __esm(() => {
34095
34190
  });
34096
34191
 
34097
34192
  // src/lib/verification-providers.ts
34098
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
34193
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
34099
34194
  function normalizeName5(name) {
34100
34195
  const normalized = name.trim().toLowerCase();
34101
34196
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -34192,7 +34287,7 @@ function classifyLog(text) {
34192
34287
  async function sleep2(ms) {
34193
34288
  if (ms <= 0)
34194
34289
  return;
34195
- await new Promise((resolve12) => setTimeout(resolve12, ms));
34290
+ await new Promise((resolve13) => setTimeout(resolve13, ms));
34196
34291
  }
34197
34292
  async function runCommandProvider(provider, input) {
34198
34293
  const commandTemplate = input.command || provider.command;
@@ -34247,7 +34342,7 @@ Timed out after ${provider.timeout_ms}ms`);
34247
34342
  };
34248
34343
  }
34249
34344
  function runCiLogProvider(input) {
34250
- const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync8(input.log_path, "utf-8") : "");
34345
+ const text = input.log_text ?? (input.log_path && existsSync13(input.log_path) ? readFileSync8(input.log_path, "utf-8") : "");
34251
34346
  return {
34252
34347
  status: classifyLog(text),
34253
34348
  attempts: 1,
@@ -34259,7 +34354,7 @@ function runBrowserProvider(input) {
34259
34354
  if (!input.artifact_path) {
34260
34355
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
34261
34356
  }
34262
- if (!existsSync12(input.artifact_path)) {
34357
+ if (!existsSync13(input.artifact_path)) {
34263
34358
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
34264
34359
  }
34265
34360
  return {
@@ -36417,7 +36512,7 @@ var package_default;
36417
36512
  var init_package = __esm(() => {
36418
36513
  package_default = {
36419
36514
  name: "@hasna/todos",
36420
- version: "0.15.49",
36515
+ version: "0.15.51",
36421
36516
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
36422
36517
  type: "module",
36423
36518
  main: "dist/index.js",
@@ -36476,6 +36571,7 @@ var init_package = __esm(() => {
36476
36571
  files: [
36477
36572
  "dist",
36478
36573
  "dashboard/dist",
36574
+ "postinstall.js",
36479
36575
  "LICENSE",
36480
36576
  "README.md"
36481
36577
  ],
@@ -36502,7 +36598,7 @@ var init_package = __esm(() => {
36502
36598
  "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
36503
36599
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
36504
36600
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
36505
- postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
36601
+ postinstall: "node postinstall.js"
36506
36602
  },
36507
36603
  keywords: [
36508
36604
  "todos",
@@ -36536,13 +36632,15 @@ var init_package = __esm(() => {
36536
36632
  author: "Andrei Hasna <andrei@hasna.com>",
36537
36633
  license: "Apache-2.0",
36538
36634
  dependencies: {
36539
- "@hasna/contracts": "0.14.0",
36635
+ "@hasna/contracts": "0.14.2",
36540
36636
  "@hasna/events": "^0.1.11",
36637
+ "@hasna/paths": "0.1.0",
36541
36638
  "@modelcontextprotocol/sdk": "^1.12.1",
36542
36639
  chalk: "^5.4.1",
36543
36640
  commander: "^13.1.0",
36544
36641
  ink: "^5.2.0",
36545
36642
  react: "^18.3.1",
36643
+ "signal-exit": "3.0.7",
36546
36644
  zod: "3.25.76"
36547
36645
  },
36548
36646
  overrides: {
@@ -37149,7 +37247,7 @@ var init_local_bridge = __esm(() => {
37149
37247
  // src/lib/local-backups.ts
37150
37248
  import { createHash as createHash7 } from "crypto";
37151
37249
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
37152
- import { dirname as dirname7, resolve as resolve12 } from "path";
37250
+ import { dirname as dirname7, resolve as resolve13 } from "path";
37153
37251
  import { mkdirSync as mkdirSync6 } from "fs";
37154
37252
  function stableJson2(value) {
37155
37253
  if (value === null || typeof value !== "object")
@@ -37251,14 +37349,14 @@ function createLocalBackup(options = {}, db) {
37251
37349
  return backup;
37252
37350
  }
37253
37351
  function writeLocalBackupFile(backup, outputPath) {
37254
- const path = resolve12(outputPath);
37352
+ const path = resolve13(outputPath);
37255
37353
  mkdirSync6(dirname7(path), { recursive: true });
37256
37354
  writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
37257
37355
  `);
37258
37356
  return path;
37259
37357
  }
37260
37358
  function readLocalBackupFile(path) {
37261
- return JSON.parse(readFileSync9(resolve12(path), "utf-8"));
37359
+ return JSON.parse(readFileSync9(resolve13(path), "utf-8"));
37262
37360
  }
37263
37361
  function verifyLocalBackup(value, options = {}, db) {
37264
37362
  const verifiedAt = options.verified_at ?? now();
@@ -38710,8 +38808,8 @@ __export(exports_local_extensions, {
38710
38808
  discoverLocalExtensions: () => discoverLocalExtensions
38711
38809
  });
38712
38810
  import { createHash as createHash10, createVerify } from "crypto";
38713
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
38714
- import { basename as basename5, join as join11, resolve as resolve13 } from "path";
38811
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
38812
+ import { basename as basename5, join as join12, resolve as resolve14 } from "path";
38715
38813
  function isObject2(value) {
38716
38814
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
38717
38815
  }
@@ -38969,11 +39067,11 @@ function verifyExtensionSignature(input) {
38969
39067
  return verifier.verify(input.public_key, decodeSignature(input.signature));
38970
39068
  }
38971
39069
  function inspectExtensionSource(source3) {
38972
- const resolved = resolve13(source3);
38973
- if (!existsSync13(resolved))
39070
+ const resolved = resolve14(source3);
39071
+ if (!existsSync14(resolved))
38974
39072
  throw new Error(`extension source not found: ${source3}`);
38975
39073
  const stat = statSync6(resolved);
38976
- const manifestPath = stat.isDirectory() ? [join11(resolved, "todos.extension.json"), join11(resolved, "extension.json")].find(existsSync13) : resolved;
39074
+ const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync14) : resolved;
38977
39075
  if (!manifestPath)
38978
39076
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
38979
39077
  const raw = readFileSync10(manifestPath);
@@ -39067,26 +39165,26 @@ function testExtensionCompatibility(sourceOrManifest) {
39067
39165
  function projectExtensionSources(projectPath) {
39068
39166
  if (!projectPath)
39069
39167
  return [];
39070
- const root = resolve13(projectPath);
39168
+ const root = resolve14(projectPath);
39071
39169
  const candidates = [
39072
- join11(root, "todos.extension.json"),
39073
- join11(root, ".todos", "todos.extension.json")
39170
+ join12(root, "todos.extension.json"),
39171
+ join12(root, ".todos", "todos.extension.json")
39074
39172
  ];
39075
- const extensionDir = join11(root, ".todos", "extensions");
39076
- if (existsSync13(extensionDir)) {
39173
+ const extensionDir = join12(root, ".todos", "extensions");
39174
+ if (existsSync14(extensionDir)) {
39077
39175
  for (const entry of readdirSync3(extensionDir)) {
39078
39176
  if (entry.startsWith("."))
39079
39177
  continue;
39080
- const full = join11(extensionDir, entry);
39178
+ const full = join12(extensionDir, entry);
39081
39179
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
39082
39180
  candidates.push(full);
39083
39181
  }
39084
39182
  }
39085
- return candidates.filter(existsSync13);
39183
+ return candidates.filter(existsSync14);
39086
39184
  }
39087
39185
  function discoverLocalExtensions(options = {}) {
39088
39186
  const config = loadConfig();
39089
- const projectPath = options.project_path ? resolve13(options.project_path) : null;
39187
+ const projectPath = options.project_path ? resolve14(options.project_path) : null;
39090
39188
  const configuredSources = [
39091
39189
  ...config.extension_sources || [],
39092
39190
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -39094,7 +39192,7 @@ function discoverLocalExtensions(options = {}) {
39094
39192
  const sources = Array.from(new Set([
39095
39193
  ...configuredSources,
39096
39194
  ...projectExtensionSources(projectPath || undefined)
39097
- ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
39195
+ ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve14(projectPath, source3) : resolve14(source3));
39098
39196
  const warnings = [];
39099
39197
  const discovered = [];
39100
39198
  for (const source3 of sources) {
@@ -43489,9 +43587,9 @@ __export(exports_extract, {
43489
43587
  buildCodebaseIndex: () => buildCodebaseIndex,
43490
43588
  EXTRACT_TAGS: () => EXTRACT_TAGS
43491
43589
  });
43492
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
43590
+ import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
43493
43591
  import { createHash as createHash12 } from "crypto";
43494
- import { relative as relative5, resolve as resolve14, join as join12 } from "path";
43592
+ import { relative as relative5, resolve as resolve15, join as join13 } from "path";
43495
43593
  function stableHash(value) {
43496
43594
  return createHash12("sha256").update(value).digest("hex");
43497
43595
  }
@@ -43499,9 +43597,9 @@ function normalizePathForMatch(value) {
43499
43597
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
43500
43598
  }
43501
43599
  function readGitignorePatterns(basePath) {
43502
- const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
43503
- const gitignorePath = join12(root, ".gitignore");
43504
- if (!existsSync14(gitignorePath))
43600
+ const root = statSync7(basePath).isFile() ? resolve15(basePath, "..") : basePath;
43601
+ const gitignorePath = join13(root, ".gitignore");
43602
+ if (!existsSync15(gitignorePath))
43505
43603
  return [];
43506
43604
  try {
43507
43605
  return readFileSync11(gitignorePath, "utf-8").split(`
@@ -43635,7 +43733,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
43635
43733
  return files.sort();
43636
43734
  }
43637
43735
  function buildCodebaseIndex(options) {
43638
- const basePath = resolve14(options.path);
43736
+ const basePath = resolve15(options.path);
43639
43737
  const tags = options.patterns || [...EXTRACT_TAGS];
43640
43738
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
43641
43739
  const excludes = options.exclude || [];
@@ -43643,10 +43741,10 @@ function buildCodebaseIndex(options) {
43643
43741
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
43644
43742
  const indexed = [];
43645
43743
  for (const file of files) {
43646
- const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
43744
+ const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
43647
43745
  try {
43648
43746
  const source3 = readFileSync11(fullPath, "utf-8");
43649
- const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
43747
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
43650
43748
  indexed.push({
43651
43749
  file: relPath,
43652
43750
  checksum: stableHash(source3).slice(0, 24),
@@ -43666,7 +43764,7 @@ function buildCodebaseIndex(options) {
43666
43764
  };
43667
43765
  }
43668
43766
  function extractTodos(options, db) {
43669
- const basePath = resolve14(options.path);
43767
+ const basePath = resolve15(options.path);
43670
43768
  const tags = options.patterns || [...EXTRACT_TAGS];
43671
43769
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
43672
43770
  const excludes = options.exclude || [];
@@ -43674,10 +43772,10 @@ function extractTodos(options, db) {
43674
43772
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
43675
43773
  const allComments = [];
43676
43774
  for (const file of files) {
43677
- const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
43775
+ const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
43678
43776
  try {
43679
43777
  const source3 = readFileSync11(fullPath, "utf-8");
43680
- const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
43778
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
43681
43779
  const comments = extractFromSource(source3, relPath, tags);
43682
43780
  allComments.push(...comments);
43683
43781
  } catch {}
@@ -43771,7 +43869,7 @@ async function watchSourceTodos(options, onRun) {
43771
43869
  const interval = Math.max(100, options.interval_ms || 2000);
43772
43870
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
43773
43871
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
43774
- const root = resolve14(options.path);
43872
+ const root = resolve15(options.path);
43775
43873
  const runs = [];
43776
43874
  let previous = new Map;
43777
43875
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -44330,7 +44428,7 @@ Last seen: ${agent.last_seen_at}`
44330
44428
  `Suggested names: ${suggestions.slice(0, 8).join(", ")}`,
44331
44429
  allActive.length > 0 ? `Active agents (avoid these names): ${allActive.map((a) => `${a.name} (seen ${Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000)}m ago)`).join(", ")}` : "No active agents.",
44332
44430
  `
44333
- To restrict names, configure agent_pool or project_pools in ~/.hasna/todos/config.json`
44431
+ To restrict names, configure agent_pool or project_pools in the todos data home config file (default <data home>/config.json)`
44334
44432
  ];
44335
44433
  return { content: [{ type: "text", text: lines2.join(`
44336
44434
  `) }] };
@@ -44633,7 +44731,7 @@ __export(exports_builtin_templates, {
44633
44731
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
44634
44732
  });
44635
44733
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
44636
- import { join as join13 } from "path";
44734
+ import { join as join14 } from "path";
44637
44735
  function templateMetadata(template) {
44638
44736
  return {
44639
44737
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -44692,7 +44790,7 @@ function writeBuiltinTemplateFiles(directory) {
44692
44790
  mkdirSync7(directory, { recursive: true });
44693
44791
  const files = [];
44694
44792
  for (const entry of exportBuiltinTemplateFiles()) {
44695
- const path = join13(directory, entry.filename);
44793
+ const path = join14(directory, entry.filename);
44696
44794
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
44697
44795
  `, "utf-8");
44698
44796
  files.push(path);
@@ -45218,16 +45316,16 @@ __export(exports_environment_snapshots, {
45218
45316
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
45219
45317
  });
45220
45318
  import { createHash as createHash13 } from "crypto";
45221
- import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
45319
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
45222
45320
  import { hostname as hostname2, platform, arch } from "os";
45223
- import { dirname as dirname8, join as join14, resolve as resolve15 } from "path";
45321
+ import { dirname as dirname8, join as join15, resolve as resolve16 } from "path";
45224
45322
  import { tmpdir as tmpdir3 } from "os";
45225
45323
  function sha2567(value) {
45226
45324
  return createHash13("sha256").update(value).digest("hex");
45227
45325
  }
45228
45326
  function fileRecord(root, relativePath) {
45229
- const path = join14(root, relativePath);
45230
- if (!existsSync15(path))
45327
+ const path = join15(root, relativePath);
45328
+ if (!existsSync16(path))
45231
45329
  return null;
45232
45330
  const stat = statSync8(path);
45233
45331
  if (!stat.isFile())
@@ -45239,7 +45337,7 @@ function manifestRecord(root, relativePath) {
45239
45337
  const base = fileRecord(root, relativePath);
45240
45338
  if (!base)
45241
45339
  return null;
45242
- const parsed = readJsonFile(join14(root, relativePath));
45340
+ const parsed = readJsonFile(join15(root, relativePath));
45243
45341
  if (!parsed)
45244
45342
  return { ...base, redacted: {} };
45245
45343
  const redacted = redactValue({
@@ -45334,15 +45432,15 @@ function commandEnv(env, includeValues) {
45334
45432
  function defaultSnapshotDir() {
45335
45433
  const dbPath = getDatabasePath();
45336
45434
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
45337
- return join14(tmpdir3(), "hasna-todos", "environment-snapshots");
45338
- return join14(dirname8(resolve15(dbPath)), "environment-snapshots");
45435
+ return join15(tmpdir3(), "hasna-todos", "environment-snapshots");
45436
+ return join15(dirname8(resolve16(dbPath)), "environment-snapshots");
45339
45437
  }
45340
45438
  function snapshotWithId(snapshot) {
45341
45439
  const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
45342
45440
  return { id: `env_${digest}`, ...snapshot };
45343
45441
  }
45344
45442
  function captureEnvironmentSnapshot(input = {}) {
45345
- const root = resolve15(input.root || process.cwd());
45443
+ const root = resolve16(input.root || process.cwd());
45346
45444
  const env = input.env || process.env;
45347
45445
  const warnings = [];
45348
45446
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -45382,13 +45480,13 @@ function captureEnvironmentSnapshot(input = {}) {
45382
45480
  });
45383
45481
  }
45384
45482
  function writeEnvironmentSnapshot(snapshot, outputPath) {
45385
- const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
45483
+ const path = outputPath ? resolve16(outputPath) : join15(defaultSnapshotDir(), `${snapshot.id}.json`);
45386
45484
  ensureDir(dirname8(path));
45387
45485
  writeJsonFile(path, snapshot);
45388
45486
  return path;
45389
45487
  }
45390
45488
  function readEnvironmentSnapshot(path) {
45391
- const snapshot = readJsonFile(resolve15(path));
45489
+ const snapshot = readJsonFile(resolve16(path));
45392
45490
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
45393
45491
  throw new Error(`Invalid environment snapshot: ${path}`);
45394
45492
  }
@@ -47360,7 +47458,7 @@ class TodosShadowOutbox {
47360
47458
  const remaining = deadline - Date.now();
47361
47459
  if (remaining <= 0)
47362
47460
  break;
47363
- await new Promise((resolve16) => setTimeout(resolve16, Math.min(200, remaining)));
47461
+ await new Promise((resolve17) => setTimeout(resolve17, Math.min(200, remaining)));
47364
47462
  }
47365
47463
  }
47366
47464
  return this.getStats();
@@ -50213,7 +50311,7 @@ async function retryOnTransientPostgresError(fn, attempts = 2, delayMs = 150) {
50213
50311
  lastError = error;
50214
50312
  if (!isTransientPostgresError(error) || attempt === attempts)
50215
50313
  throw error;
50216
- await new Promise((resolve16) => setTimeout(resolve16, delayMs * attempt));
50314
+ await new Promise((resolve17) => setTimeout(resolve17, delayMs * attempt));
50217
50315
  }
50218
50316
  }
50219
50317
  throw lastError;
@@ -56648,7 +56746,7 @@ var init_headless_boundaries = __esm(() => {
56648
56746
  });
56649
56747
 
56650
56748
  // src/server/routes.ts
56651
- import { join as join15, resolve as resolve16, sep as sep3 } from "path";
56749
+ import { join as join16, resolve as resolve17, sep as sep3 } from "path";
56652
56750
  function parseFieldsParam(url) {
56653
56751
  const fieldsParam = url.searchParams.get("fields");
56654
56752
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -57485,9 +57583,9 @@ function handleStaticFiles(path, method, ctx, json5, serveStaticFile2) {
57485
57583
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
57486
57584
  return null;
57487
57585
  if (path !== "/") {
57488
- const filePath = join15(ctx.dashboardDir, path);
57489
- const resolvedFile = resolve16(filePath);
57490
- const resolvedBase = resolve16(ctx.dashboardDir);
57586
+ const filePath = join16(ctx.dashboardDir, path);
57587
+ const resolvedFile = resolve17(filePath);
57588
+ const resolvedBase = resolve17(ctx.dashboardDir);
57491
57589
  if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
57492
57590
  return json5({ error: "Forbidden" }, 403);
57493
57591
  }
@@ -57495,7 +57593,7 @@ function handleStaticFiles(path, method, ctx, json5, serveStaticFile2) {
57495
57593
  if (res2)
57496
57594
  return res2;
57497
57595
  }
57498
- const indexPath = join15(ctx.dashboardDir, "index.html");
57596
+ const indexPath = join16(ctx.dashboardDir, "index.html");
57499
57597
  const res = serveStaticFile2(indexPath);
57500
57598
  if (res)
57501
57599
  return res;
@@ -63476,8 +63574,8 @@ class SqlitePrGroupLedgerPersistence {
63476
63574
  async transaction(fn) {
63477
63575
  const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
63478
63576
  let release;
63479
- const current = new Promise((resolve17) => {
63480
- release = resolve17;
63577
+ const current = new Promise((resolve18) => {
63578
+ release = resolve18;
63481
63579
  });
63482
63580
  sqliteTransactionTails2.set(this.db, current);
63483
63581
  await previous;
@@ -63575,27 +63673,27 @@ __export(exports_serve, {
63575
63673
  SECURITY_HEADERS: () => SECURITY_HEADERS,
63576
63674
  MIME_TYPES: () => MIME_TYPES
63577
63675
  });
63578
- import { existsSync as existsSync16 } from "fs";
63579
- import { join as join16, dirname as dirname9, extname } from "path";
63676
+ import { existsSync as existsSync17 } from "fs";
63677
+ import { join as join17, dirname as dirname9, extname } from "path";
63580
63678
  import { fileURLToPath } from "url";
63581
63679
  function resolveDashboardDir() {
63582
63680
  const candidates = [];
63583
63681
  try {
63584
63682
  const scriptDir = dirname9(fileURLToPath(import.meta.url));
63585
- candidates.push(join16(scriptDir, "..", "dashboard", "dist"));
63586
- candidates.push(join16(scriptDir, "..", "..", "dashboard", "dist"));
63683
+ candidates.push(join17(scriptDir, "..", "dashboard", "dist"));
63684
+ candidates.push(join17(scriptDir, "..", "..", "dashboard", "dist"));
63587
63685
  } catch {}
63588
63686
  if (process.argv[1]) {
63589
63687
  const mainDir = dirname9(process.argv[1]);
63590
- candidates.push(join16(mainDir, "..", "dashboard", "dist"));
63591
- candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
63688
+ candidates.push(join17(mainDir, "..", "dashboard", "dist"));
63689
+ candidates.push(join17(mainDir, "..", "..", "dashboard", "dist"));
63592
63690
  }
63593
- candidates.push(join16(process.cwd(), "dashboard", "dist"));
63691
+ candidates.push(join17(process.cwd(), "dashboard", "dist"));
63594
63692
  for (const candidate of candidates) {
63595
- if (existsSync16(candidate))
63693
+ if (existsSync17(candidate))
63596
63694
  return candidate;
63597
63695
  }
63598
- return join16(process.cwd(), "dashboard", "dist");
63696
+ return join17(process.cwd(), "dashboard", "dist");
63599
63697
  }
63600
63698
  function getProvidedApiKey(req) {
63601
63699
  const headerKey = req.headers.get("x-api-key");
@@ -63667,7 +63765,7 @@ function json4(data, status3 = 200, headers) {
63667
63765
  });
63668
63766
  }
63669
63767
  function serveStaticFile(filePath) {
63670
- if (!existsSync16(filePath))
63768
+ if (!existsSync17(filePath))
63671
63769
  return null;
63672
63770
  const ext = extname(filePath);
63673
63771
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -63768,7 +63866,7 @@ data: ${data}
63768
63866
  filteredSseClients.delete(client);
63769
63867
  }
63770
63868
  const dashboardDir = resolveDashboardDir();
63771
- const dashboardExists = existsSync16(dashboardDir);
63869
+ const dashboardExists = existsSync17(dashboardDir);
63772
63870
  if (!dashboardExists) {
63773
63871
  console.error(`
63774
63872
  Dashboard not found at: ${dashboardDir}`);