@hasna/todos 0.15.50 → 0.15.52

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;
@@ -21578,15 +21673,23 @@ function normalizeRemoteAuthorityUrl(value) {
21578
21673
  if (url.search || url.hash) {
21579
21674
  throw new Error("REMOTE_API_URL_INVALID: HASNA_TODOS_API_URL must not contain a query or fragment; local SQLite fallback is disabled");
21580
21675
  }
21581
- if (url.pathname !== "/" && url.pathname !== "/v1" && url.pathname !== "/v1/") {
21582
- throw new Error("REMOTE_API_URL_INVALID: HASNA_TODOS_API_URL must be an authority root or end in /v1, not /api/v1 or another path; " + "local SQLite fallback is disabled");
21676
+ const path = url.pathname.replace(/\/+$/, "");
21677
+ const segments = path.split("/").filter(Boolean);
21678
+ const reservedGatewaySegments = new Set(["api", "v1"]);
21679
+ const isRoot = path === "";
21680
+ const isV1Root = segments.length === 1 && segments[0] === "v1";
21681
+ const isAppRoot = segments.length === 1 && !reservedGatewaySegments.has(segments[0].toLowerCase());
21682
+ const isAppV1Root = segments.length === 2 && segments[1] === "v1" && !reservedGatewaySegments.has(segments[0].toLowerCase());
21683
+ if (!isRoot && !isV1Root && !isAppRoot && !isAppV1Root) {
21684
+ throw new Error("REMOTE_API_URL_INVALID: HASNA_TODOS_API_URL must be an authority root, /v1, or <app>[/v1], not /api/v1 or another path; " + "local SQLite fallback is disabled");
21583
21685
  }
21584
21686
  const hostname2 = url.hostname.toLowerCase();
21585
21687
  const loopback = hostname2 === "localhost" || hostname2 === "::1" || hostname2 === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(hostname2);
21586
21688
  if (url.protocol === "http:" && !loopback) {
21587
21689
  throw new Error("REMOTE_API_URL_INVALID: plaintext HTTP is allowed only for loopback Todos authorities; local SQLite fallback is disabled");
21588
21690
  }
21589
- return url.origin;
21691
+ const rootPath = isV1Root || isAppV1Root ? path.slice(0, -"/v1".length) : path;
21692
+ return rootPath ? `${url.origin}${rootPath}` : url.origin;
21590
21693
  }
21591
21694
  function getTodosRemoteAuthorityConfigStatus(env = process.env) {
21592
21695
  let resolution;
@@ -22084,11 +22187,11 @@ function resolveCloudProjectRef(projects, ref) {
22084
22187
  const input = ref.trim();
22085
22188
  const normalizedRef = input.toLowerCase();
22086
22189
  const pathLike = input.startsWith(".") || input.includes("/") || input.includes("\\");
22087
- const normalizedPath = pathLike ? resolvePath(input) : undefined;
22190
+ const normalizedPath = pathLike ? resolvePath2(input) : undefined;
22088
22191
  const slug = cloudProjectSlug(pathLike ? cloudProjectPathBasename(input) : input);
22089
22192
  const matchGroups = [
22090
22193
  uniqueProjectMatches(projects, (project) => project.id.toLowerCase() === normalizedRef),
22091
- uniqueProjectMatches(projects, (project) => project.path === input || normalizedPath !== undefined && resolvePath(project.path) === normalizedPath),
22194
+ uniqueProjectMatches(projects, (project) => project.path === input || normalizedPath !== undefined && resolvePath2(project.path) === normalizedPath),
22092
22195
  uniqueProjectMatches(projects, (project) => project.name.toLowerCase() === normalizedRef),
22093
22196
  uniqueProjectMatches(projects, (project) => project.task_list_id === input || cloudProjectSlug(project.name) === slug || cloudProjectSlug(cloudProjectPathBasename(project.path)) === slug),
22094
22197
  uniqueProjectMatches(projects, (project) => project.id.toLowerCase().startsWith(normalizedRef))
@@ -22671,8 +22774,8 @@ var init_task_crud2 = __esm(() => {
22671
22774
  });
22672
22775
 
22673
22776
  // 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";
22777
+ import { existsSync as existsSync9, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
22778
+ import { basename as basename2, dirname as dirname5, resolve as resolve9 } from "path";
22676
22779
  function safeStat(path) {
22677
22780
  try {
22678
22781
  return statSync3(path);
@@ -22681,7 +22784,7 @@ function safeStat(path) {
22681
22784
  }
22682
22785
  }
22683
22786
  function canonicalPath(input) {
22684
- const resolved = resolve8(input);
22787
+ const resolved = resolve9(input);
22685
22788
  const stats = safeStat(resolved);
22686
22789
  if (stats?.isFile())
22687
22790
  return dirname5(resolved);
@@ -22690,7 +22793,7 @@ function canonicalPath(input) {
22690
22793
  function findUp(start, marker) {
22691
22794
  let current = canonicalPath(start);
22692
22795
  while (true) {
22693
- if (existsSync8(resolve8(current, marker)))
22796
+ if (existsSync9(resolve9(current, marker)))
22694
22797
  return current;
22695
22798
  const parent = dirname5(current);
22696
22799
  if (parent === current)
@@ -22701,8 +22804,8 @@ function findUp(start, marker) {
22701
22804
  function readPackageJson(path) {
22702
22805
  if (!path)
22703
22806
  return null;
22704
- const file = resolve8(path, "package.json");
22705
- if (!existsSync8(file))
22807
+ const file = resolve9(path, "package.json");
22808
+ if (!existsSync9(file))
22706
22809
  return null;
22707
22810
  try {
22708
22811
  const parsed = JSON.parse(readFileSync5(file, "utf-8"));
@@ -22724,7 +22827,7 @@ function workspaceMarker(root, rootPackage) {
22724
22827
  if (rootPackage?.workspaces)
22725
22828
  markers.push("package.json#workspaces");
22726
22829
  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)))
22830
+ if (existsSync9(resolve9(root, marker)))
22728
22831
  markers.push(marker);
22729
22832
  }
22730
22833
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -23037,7 +23140,7 @@ var init_tags = __esm(() => {
23037
23140
  });
23038
23141
 
23039
23142
  // src/lib/retention-cleanup.ts
23040
- import { existsSync as existsSync9, unlinkSync } from "fs";
23143
+ import { existsSync as existsSync10, unlinkSync } from "fs";
23041
23144
  function normalizeScopes(scopes) {
23042
23145
  if (!scopes || scopes.length === 0)
23043
23146
  return [...ALL_SCOPES];
@@ -23240,7 +23343,7 @@ function applyRetentionCleanup(input, db) {
23240
23343
  for (const artifact of report.candidates.artifact_files) {
23241
23344
  try {
23242
23345
  const path = artifactStorePath(artifact.relative_path);
23243
- if (!existsSync9(path)) {
23346
+ if (!existsSync10(path)) {
23244
23347
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
23245
23348
  continue;
23246
23349
  }
@@ -23267,8 +23370,8 @@ var init_retention_cleanup = __esm(() => {
23267
23370
  });
23268
23371
 
23269
23372
  // 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";
23373
+ import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
23374
+ import { basename as basename3, isAbsolute, join as join9, relative as relative3, resolve as resolve10, sep as sep2 } from "path";
23272
23375
  function blankResolution(parsed) {
23273
23376
  return {
23274
23377
  input: parsed.input,
@@ -23291,7 +23394,7 @@ function backlink(kind, key, label, target = key) {
23291
23394
  return { kind, key, label, target };
23292
23395
  }
23293
23396
  function normalizeWorkspace(workspace) {
23294
- return resolve9(workspace || process.cwd());
23397
+ return resolve10(workspace || process.cwd());
23295
23398
  }
23296
23399
  function isInside(root, absolutePath) {
23297
23400
  const rel = relative3(root, absolutePath);
@@ -23359,14 +23462,14 @@ function resolveFile(parsed, workspace) {
23359
23462
  resolution.warnings.push("path is empty or escapes the workspace");
23360
23463
  return resolution;
23361
23464
  }
23362
- const absolutePath = resolve9(workspace, relPath);
23465
+ const absolutePath = resolve10(workspace, relPath);
23363
23466
  if (!isInside(workspace, absolutePath)) {
23364
23467
  resolution.path = relPath;
23365
23468
  resolution.warnings.push("path escapes the workspace");
23366
23469
  return resolution;
23367
23470
  }
23368
23471
  resolution.path = relPath;
23369
- if (!existsSync10(absolutePath)) {
23472
+ if (!existsSync11(absolutePath)) {
23370
23473
  resolution.warnings.push("file does not exist in the local workspace");
23371
23474
  return resolution;
23372
23475
  }
@@ -23399,7 +23502,7 @@ function walkSourceFiles(root, current = root, files = []) {
23399
23502
  if (SKIP_DIRS.has(entry.name))
23400
23503
  continue;
23401
23504
  }
23402
- const absolutePath = join8(current, entry.name);
23505
+ const absolutePath = join9(current, entry.name);
23403
23506
  if (entry.isDirectory()) {
23404
23507
  if (!SKIP_DIRS.has(entry.name))
23405
23508
  walkSourceFiles(root, absolutePath, files);
@@ -23704,9 +23807,9 @@ var init_mention_resolver = __esm(() => {
23704
23807
  });
23705
23808
 
23706
23809
  // src/lib/policy-packs.ts
23707
- import { relative as relative4, resolve as resolve10 } from "path";
23810
+ import { relative as relative4, resolve as resolve11 } from "path";
23708
23811
  function normalizePath3(path) {
23709
- return resolve10(path);
23812
+ return resolve11(path);
23710
23813
  }
23711
23814
  function unique4(values) {
23712
23815
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -23761,7 +23864,7 @@ function commandMatches(commands, pattern) {
23761
23864
  }
23762
23865
  function pathMatches(paths, pattern, root) {
23763
23866
  return paths.filter((path) => {
23764
- const candidate = path.startsWith("/") ? path : resolve10(root, path);
23867
+ const candidate = path.startsWith("/") ? path : resolve11(root, path);
23765
23868
  if (!isPathInside3(root, candidate))
23766
23869
  return matchesPattern3(path, pattern);
23767
23870
  return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
@@ -27140,7 +27243,7 @@ var init_audit_ledger = __esm(() => {
27140
27243
 
27141
27244
  // src/lib/release-compatibility.ts
27142
27245
  import { readFileSync as readFileSync7 } from "fs";
27143
- import { join as join9, resolve as resolve11 } from "path";
27246
+ import { join as join10, resolve as resolve12 } from "path";
27144
27247
  import { Database as Database2 } from "bun:sqlite";
27145
27248
  function pass(id, message, details) {
27146
27249
  return { id, status: "passed", message, details };
@@ -27152,7 +27255,7 @@ function warn(id, message, details) {
27152
27255
  return { id, status: "warning", message, details };
27153
27256
  }
27154
27257
  function readPackageJson2(root) {
27155
- return JSON.parse(readFileSync7(join9(root, "package.json"), "utf8"));
27258
+ return JSON.parse(readFileSync7(join10(root, "package.json"), "utf8"));
27156
27259
  }
27157
27260
  function sortedKeys(value) {
27158
27261
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -27248,7 +27351,7 @@ function checkChangelog() {
27248
27351
  ];
27249
27352
  }
27250
27353
  function createReleaseCompatibilityReport(options = {}) {
27251
- const root = resolve11(options.root ?? process.cwd());
27354
+ const root = resolve12(options.root ?? process.cwd());
27252
27355
  const packageJson = readPackageJson2(root);
27253
27356
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
27254
27357
  const checks = [
@@ -31170,8 +31273,8 @@ var exports_doctor = {};
31170
31273
  __export(exports_doctor, {
31171
31274
  runTodosDoctor: () => runTodosDoctor
31172
31275
  });
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";
31276
+ import { chmodSync, copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
31277
+ import { basename as basename4, dirname as dirname6, join as join11 } from "path";
31175
31278
  function tableExists2(db, table) {
31176
31279
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
31177
31280
  }
@@ -31265,7 +31368,7 @@ function findMissingProjectRoots(db) {
31265
31368
  continue;
31266
31369
  if (!row.path.startsWith("/"))
31267
31370
  continue;
31268
- if (!existsSync11(row.path))
31371
+ if (!existsSync12(row.path))
31269
31372
  missing++;
31270
31373
  }
31271
31374
  return missing;
@@ -31325,16 +31428,16 @@ function databasePermissionsAreUnsafe(dbPath) {
31325
31428
  function createBackup(dbPath) {
31326
31429
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
31327
31430
  return;
31328
- if (!existsSync11(dbPath))
31431
+ if (!existsSync12(dbPath))
31329
31432
  return;
31330
31433
  const stamp = now().replace(/[:.]/g, "-");
31331
- const backupDir = join10(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
31434
+ const backupDir = join11(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
31332
31435
  const files = [];
31333
31436
  mkdirSync5(backupDir, { recursive: true });
31334
31437
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
31335
- if (!existsSync11(source))
31438
+ if (!existsSync12(source))
31336
31439
  continue;
31337
- const target = join10(backupDir, basename4(source));
31440
+ const target = join11(backupDir, basename4(source));
31338
31441
  copyFileSync(source, target);
31339
31442
  files.push(target);
31340
31443
  }
@@ -34095,7 +34198,7 @@ var init_agent_run_dispatcher = __esm(() => {
34095
34198
  });
34096
34199
 
34097
34200
  // src/lib/verification-providers.ts
34098
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
34201
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
34099
34202
  function normalizeName5(name) {
34100
34203
  const normalized = name.trim().toLowerCase();
34101
34204
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -34192,7 +34295,7 @@ function classifyLog(text) {
34192
34295
  async function sleep2(ms) {
34193
34296
  if (ms <= 0)
34194
34297
  return;
34195
- await new Promise((resolve12) => setTimeout(resolve12, ms));
34298
+ await new Promise((resolve13) => setTimeout(resolve13, ms));
34196
34299
  }
34197
34300
  async function runCommandProvider(provider, input) {
34198
34301
  const commandTemplate = input.command || provider.command;
@@ -34247,7 +34350,7 @@ Timed out after ${provider.timeout_ms}ms`);
34247
34350
  };
34248
34351
  }
34249
34352
  function runCiLogProvider(input) {
34250
- const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync8(input.log_path, "utf-8") : "");
34353
+ const text = input.log_text ?? (input.log_path && existsSync13(input.log_path) ? readFileSync8(input.log_path, "utf-8") : "");
34251
34354
  return {
34252
34355
  status: classifyLog(text),
34253
34356
  attempts: 1,
@@ -34259,7 +34362,7 @@ function runBrowserProvider(input) {
34259
34362
  if (!input.artifact_path) {
34260
34363
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
34261
34364
  }
34262
- if (!existsSync12(input.artifact_path)) {
34365
+ if (!existsSync13(input.artifact_path)) {
34263
34366
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
34264
34367
  }
34265
34368
  return {
@@ -36417,7 +36520,7 @@ var package_default;
36417
36520
  var init_package = __esm(() => {
36418
36521
  package_default = {
36419
36522
  name: "@hasna/todos",
36420
- version: "0.15.50",
36523
+ version: "0.15.52",
36421
36524
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
36422
36525
  type: "module",
36423
36526
  main: "dist/index.js",
@@ -36476,6 +36579,7 @@ var init_package = __esm(() => {
36476
36579
  files: [
36477
36580
  "dist",
36478
36581
  "dashboard/dist",
36582
+ "postinstall.js",
36479
36583
  "LICENSE",
36480
36584
  "README.md"
36481
36585
  ],
@@ -36502,7 +36606,7 @@ var init_package = __esm(() => {
36502
36606
  "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
36503
36607
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
36504
36608
  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"
36609
+ postinstall: "node postinstall.js"
36506
36610
  },
36507
36611
  keywords: [
36508
36612
  "todos",
@@ -36536,13 +36640,15 @@ var init_package = __esm(() => {
36536
36640
  author: "Andrei Hasna <andrei@hasna.com>",
36537
36641
  license: "Apache-2.0",
36538
36642
  dependencies: {
36539
- "@hasna/contracts": "0.14.0",
36643
+ "@hasna/contracts": "0.14.2",
36540
36644
  "@hasna/events": "^0.1.11",
36645
+ "@hasna/paths": "0.1.0",
36541
36646
  "@modelcontextprotocol/sdk": "^1.12.1",
36542
36647
  chalk: "^5.4.1",
36543
36648
  commander: "^13.1.0",
36544
36649
  ink: "^5.2.0",
36545
36650
  react: "^18.3.1",
36651
+ "signal-exit": "3.0.7",
36546
36652
  zod: "3.25.76"
36547
36653
  },
36548
36654
  overrides: {
@@ -36551,7 +36657,7 @@ var init_package = __esm(() => {
36551
36657
  zod: "3.25.76"
36552
36658
  },
36553
36659
  devDependencies: {
36554
- "@types/bun": "^1.2.4",
36660
+ "@types/bun": "1.3.14",
36555
36661
  "@types/react": "^18.3.18",
36556
36662
  "bun-types": "1.3.9",
36557
36663
  "hasna-deployment-contracts": "npm:@hasna/contracts@0.10.4",
@@ -37149,7 +37255,7 @@ var init_local_bridge = __esm(() => {
37149
37255
  // src/lib/local-backups.ts
37150
37256
  import { createHash as createHash7 } from "crypto";
37151
37257
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
37152
- import { dirname as dirname7, resolve as resolve12 } from "path";
37258
+ import { dirname as dirname7, resolve as resolve13 } from "path";
37153
37259
  import { mkdirSync as mkdirSync6 } from "fs";
37154
37260
  function stableJson2(value) {
37155
37261
  if (value === null || typeof value !== "object")
@@ -37251,14 +37357,14 @@ function createLocalBackup(options = {}, db) {
37251
37357
  return backup;
37252
37358
  }
37253
37359
  function writeLocalBackupFile(backup, outputPath) {
37254
- const path = resolve12(outputPath);
37360
+ const path = resolve13(outputPath);
37255
37361
  mkdirSync6(dirname7(path), { recursive: true });
37256
37362
  writeFileSync3(path, `${JSON.stringify(backup, null, 2)}
37257
37363
  `);
37258
37364
  return path;
37259
37365
  }
37260
37366
  function readLocalBackupFile(path) {
37261
- return JSON.parse(readFileSync9(resolve12(path), "utf-8"));
37367
+ return JSON.parse(readFileSync9(resolve13(path), "utf-8"));
37262
37368
  }
37263
37369
  function verifyLocalBackup(value, options = {}, db) {
37264
37370
  const verifiedAt = options.verified_at ?? now();
@@ -38710,8 +38816,8 @@ __export(exports_local_extensions, {
38710
38816
  discoverLocalExtensions: () => discoverLocalExtensions
38711
38817
  });
38712
38818
  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";
38819
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
38820
+ import { basename as basename5, join as join12, resolve as resolve14 } from "path";
38715
38821
  function isObject2(value) {
38716
38822
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
38717
38823
  }
@@ -38969,11 +39075,11 @@ function verifyExtensionSignature(input) {
38969
39075
  return verifier.verify(input.public_key, decodeSignature(input.signature));
38970
39076
  }
38971
39077
  function inspectExtensionSource(source3) {
38972
- const resolved = resolve13(source3);
38973
- if (!existsSync13(resolved))
39078
+ const resolved = resolve14(source3);
39079
+ if (!existsSync14(resolved))
38974
39080
  throw new Error(`extension source not found: ${source3}`);
38975
39081
  const stat = statSync6(resolved);
38976
- const manifestPath = stat.isDirectory() ? [join11(resolved, "todos.extension.json"), join11(resolved, "extension.json")].find(existsSync13) : resolved;
39082
+ const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync14) : resolved;
38977
39083
  if (!manifestPath)
38978
39084
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
38979
39085
  const raw = readFileSync10(manifestPath);
@@ -39067,26 +39173,26 @@ function testExtensionCompatibility(sourceOrManifest) {
39067
39173
  function projectExtensionSources(projectPath) {
39068
39174
  if (!projectPath)
39069
39175
  return [];
39070
- const root = resolve13(projectPath);
39176
+ const root = resolve14(projectPath);
39071
39177
  const candidates = [
39072
- join11(root, "todos.extension.json"),
39073
- join11(root, ".todos", "todos.extension.json")
39178
+ join12(root, "todos.extension.json"),
39179
+ join12(root, ".todos", "todos.extension.json")
39074
39180
  ];
39075
- const extensionDir = join11(root, ".todos", "extensions");
39076
- if (existsSync13(extensionDir)) {
39181
+ const extensionDir = join12(root, ".todos", "extensions");
39182
+ if (existsSync14(extensionDir)) {
39077
39183
  for (const entry of readdirSync3(extensionDir)) {
39078
39184
  if (entry.startsWith("."))
39079
39185
  continue;
39080
- const full = join11(extensionDir, entry);
39186
+ const full = join12(extensionDir, entry);
39081
39187
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
39082
39188
  candidates.push(full);
39083
39189
  }
39084
39190
  }
39085
- return candidates.filter(existsSync13);
39191
+ return candidates.filter(existsSync14);
39086
39192
  }
39087
39193
  function discoverLocalExtensions(options = {}) {
39088
39194
  const config = loadConfig();
39089
- const projectPath = options.project_path ? resolve13(options.project_path) : null;
39195
+ const projectPath = options.project_path ? resolve14(options.project_path) : null;
39090
39196
  const configuredSources = [
39091
39197
  ...config.extension_sources || [],
39092
39198
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -39094,7 +39200,7 @@ function discoverLocalExtensions(options = {}) {
39094
39200
  const sources = Array.from(new Set([
39095
39201
  ...configuredSources,
39096
39202
  ...projectExtensionSources(projectPath || undefined)
39097
- ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve13(projectPath, source3) : resolve13(source3));
39203
+ ])).map((source3) => projectPath && !source3.startsWith("/") ? resolve14(projectPath, source3) : resolve14(source3));
39098
39204
  const warnings = [];
39099
39205
  const discovered = [];
39100
39206
  for (const source3 of sources) {
@@ -43489,9 +43595,9 @@ __export(exports_extract, {
43489
43595
  buildCodebaseIndex: () => buildCodebaseIndex,
43490
43596
  EXTRACT_TAGS: () => EXTRACT_TAGS
43491
43597
  });
43492
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
43598
+ import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
43493
43599
  import { createHash as createHash12 } from "crypto";
43494
- import { relative as relative5, resolve as resolve14, join as join12 } from "path";
43600
+ import { relative as relative5, resolve as resolve15, join as join13 } from "path";
43495
43601
  function stableHash(value) {
43496
43602
  return createHash12("sha256").update(value).digest("hex");
43497
43603
  }
@@ -43499,9 +43605,9 @@ function normalizePathForMatch(value) {
43499
43605
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
43500
43606
  }
43501
43607
  function readGitignorePatterns(basePath) {
43502
- const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
43503
- const gitignorePath = join12(root, ".gitignore");
43504
- if (!existsSync14(gitignorePath))
43608
+ const root = statSync7(basePath).isFile() ? resolve15(basePath, "..") : basePath;
43609
+ const gitignorePath = join13(root, ".gitignore");
43610
+ if (!existsSync15(gitignorePath))
43505
43611
  return [];
43506
43612
  try {
43507
43613
  return readFileSync11(gitignorePath, "utf-8").split(`
@@ -43635,7 +43741,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
43635
43741
  return files.sort();
43636
43742
  }
43637
43743
  function buildCodebaseIndex(options) {
43638
- const basePath = resolve14(options.path);
43744
+ const basePath = resolve15(options.path);
43639
43745
  const tags = options.patterns || [...EXTRACT_TAGS];
43640
43746
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
43641
43747
  const excludes = options.exclude || [];
@@ -43643,10 +43749,10 @@ function buildCodebaseIndex(options) {
43643
43749
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
43644
43750
  const indexed = [];
43645
43751
  for (const file of files) {
43646
- const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
43752
+ const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
43647
43753
  try {
43648
43754
  const source3 = readFileSync11(fullPath, "utf-8");
43649
- const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
43755
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
43650
43756
  indexed.push({
43651
43757
  file: relPath,
43652
43758
  checksum: stableHash(source3).slice(0, 24),
@@ -43666,7 +43772,7 @@ function buildCodebaseIndex(options) {
43666
43772
  };
43667
43773
  }
43668
43774
  function extractTodos(options, db) {
43669
- const basePath = resolve14(options.path);
43775
+ const basePath = resolve15(options.path);
43670
43776
  const tags = options.patterns || [...EXTRACT_TAGS];
43671
43777
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
43672
43778
  const excludes = options.exclude || [];
@@ -43674,10 +43780,10 @@ function extractTodos(options, db) {
43674
43780
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
43675
43781
  const allComments = [];
43676
43782
  for (const file of files) {
43677
- const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
43783
+ const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
43678
43784
  try {
43679
43785
  const source3 = readFileSync11(fullPath, "utf-8");
43680
- const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
43786
+ const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
43681
43787
  const comments = extractFromSource(source3, relPath, tags);
43682
43788
  allComments.push(...comments);
43683
43789
  } catch {}
@@ -43771,7 +43877,7 @@ async function watchSourceTodos(options, onRun) {
43771
43877
  const interval = Math.max(100, options.interval_ms || 2000);
43772
43878
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
43773
43879
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
43774
- const root = resolve14(options.path);
43880
+ const root = resolve15(options.path);
43775
43881
  const runs = [];
43776
43882
  let previous = new Map;
43777
43883
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -44330,7 +44436,7 @@ Last seen: ${agent.last_seen_at}`
44330
44436
  `Suggested names: ${suggestions.slice(0, 8).join(", ")}`,
44331
44437
  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
44438
  `
44333
- To restrict names, configure agent_pool or project_pools in ~/.hasna/todos/config.json`
44439
+ To restrict names, configure agent_pool or project_pools in the todos data home config file (default <data home>/config.json)`
44334
44440
  ];
44335
44441
  return { content: [{ type: "text", text: lines2.join(`
44336
44442
  `) }] };
@@ -44633,7 +44739,7 @@ __export(exports_builtin_templates, {
44633
44739
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
44634
44740
  });
44635
44741
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
44636
- import { join as join13 } from "path";
44742
+ import { join as join14 } from "path";
44637
44743
  function templateMetadata(template) {
44638
44744
  return {
44639
44745
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -44692,7 +44798,7 @@ function writeBuiltinTemplateFiles(directory) {
44692
44798
  mkdirSync7(directory, { recursive: true });
44693
44799
  const files = [];
44694
44800
  for (const entry of exportBuiltinTemplateFiles()) {
44695
- const path = join13(directory, entry.filename);
44801
+ const path = join14(directory, entry.filename);
44696
44802
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
44697
44803
  `, "utf-8");
44698
44804
  files.push(path);
@@ -45218,16 +45324,16 @@ __export(exports_environment_snapshots, {
45218
45324
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
45219
45325
  });
45220
45326
  import { createHash as createHash13 } from "crypto";
45221
- import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
45327
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
45222
45328
  import { hostname as hostname2, platform, arch } from "os";
45223
- import { dirname as dirname8, join as join14, resolve as resolve15 } from "path";
45329
+ import { dirname as dirname8, join as join15, resolve as resolve16 } from "path";
45224
45330
  import { tmpdir as tmpdir3 } from "os";
45225
45331
  function sha2567(value) {
45226
45332
  return createHash13("sha256").update(value).digest("hex");
45227
45333
  }
45228
45334
  function fileRecord(root, relativePath) {
45229
- const path = join14(root, relativePath);
45230
- if (!existsSync15(path))
45335
+ const path = join15(root, relativePath);
45336
+ if (!existsSync16(path))
45231
45337
  return null;
45232
45338
  const stat = statSync8(path);
45233
45339
  if (!stat.isFile())
@@ -45239,7 +45345,7 @@ function manifestRecord(root, relativePath) {
45239
45345
  const base = fileRecord(root, relativePath);
45240
45346
  if (!base)
45241
45347
  return null;
45242
- const parsed = readJsonFile(join14(root, relativePath));
45348
+ const parsed = readJsonFile(join15(root, relativePath));
45243
45349
  if (!parsed)
45244
45350
  return { ...base, redacted: {} };
45245
45351
  const redacted = redactValue({
@@ -45334,15 +45440,15 @@ function commandEnv(env, includeValues) {
45334
45440
  function defaultSnapshotDir() {
45335
45441
  const dbPath = getDatabasePath();
45336
45442
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
45337
- return join14(tmpdir3(), "hasna-todos", "environment-snapshots");
45338
- return join14(dirname8(resolve15(dbPath)), "environment-snapshots");
45443
+ return join15(tmpdir3(), "hasna-todos", "environment-snapshots");
45444
+ return join15(dirname8(resolve16(dbPath)), "environment-snapshots");
45339
45445
  }
45340
45446
  function snapshotWithId(snapshot) {
45341
45447
  const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
45342
45448
  return { id: `env_${digest}`, ...snapshot };
45343
45449
  }
45344
45450
  function captureEnvironmentSnapshot(input = {}) {
45345
- const root = resolve15(input.root || process.cwd());
45451
+ const root = resolve16(input.root || process.cwd());
45346
45452
  const env = input.env || process.env;
45347
45453
  const warnings = [];
45348
45454
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -45382,13 +45488,13 @@ function captureEnvironmentSnapshot(input = {}) {
45382
45488
  });
45383
45489
  }
45384
45490
  function writeEnvironmentSnapshot(snapshot, outputPath) {
45385
- const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
45491
+ const path = outputPath ? resolve16(outputPath) : join15(defaultSnapshotDir(), `${snapshot.id}.json`);
45386
45492
  ensureDir(dirname8(path));
45387
45493
  writeJsonFile(path, snapshot);
45388
45494
  return path;
45389
45495
  }
45390
45496
  function readEnvironmentSnapshot(path) {
45391
- const snapshot = readJsonFile(resolve15(path));
45497
+ const snapshot = readJsonFile(resolve16(path));
45392
45498
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
45393
45499
  throw new Error(`Invalid environment snapshot: ${path}`);
45394
45500
  }
@@ -47360,7 +47466,7 @@ class TodosShadowOutbox {
47360
47466
  const remaining = deadline - Date.now();
47361
47467
  if (remaining <= 0)
47362
47468
  break;
47363
- await new Promise((resolve16) => setTimeout(resolve16, Math.min(200, remaining)));
47469
+ await new Promise((resolve17) => setTimeout(resolve17, Math.min(200, remaining)));
47364
47470
  }
47365
47471
  }
47366
47472
  return this.getStats();
@@ -50213,7 +50319,7 @@ async function retryOnTransientPostgresError(fn, attempts = 2, delayMs = 150) {
50213
50319
  lastError = error;
50214
50320
  if (!isTransientPostgresError(error) || attempt === attempts)
50215
50321
  throw error;
50216
- await new Promise((resolve16) => setTimeout(resolve16, delayMs * attempt));
50322
+ await new Promise((resolve17) => setTimeout(resolve17, delayMs * attempt));
50217
50323
  }
50218
50324
  }
50219
50325
  throw lastError;
@@ -56648,7 +56754,7 @@ var init_headless_boundaries = __esm(() => {
56648
56754
  });
56649
56755
 
56650
56756
  // src/server/routes.ts
56651
- import { join as join15, resolve as resolve16, sep as sep3 } from "path";
56757
+ import { join as join16, resolve as resolve17, sep as sep3 } from "path";
56652
56758
  function parseFieldsParam(url) {
56653
56759
  const fieldsParam = url.searchParams.get("fields");
56654
56760
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -57485,9 +57591,9 @@ function handleStaticFiles(path, method, ctx, json5, serveStaticFile2) {
57485
57591
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
57486
57592
  return null;
57487
57593
  if (path !== "/") {
57488
- const filePath = join15(ctx.dashboardDir, path);
57489
- const resolvedFile = resolve16(filePath);
57490
- const resolvedBase = resolve16(ctx.dashboardDir);
57594
+ const filePath = join16(ctx.dashboardDir, path);
57595
+ const resolvedFile = resolve17(filePath);
57596
+ const resolvedBase = resolve17(ctx.dashboardDir);
57491
57597
  if (!resolvedFile.startsWith(resolvedBase + sep3) && resolvedFile !== resolvedBase) {
57492
57598
  return json5({ error: "Forbidden" }, 403);
57493
57599
  }
@@ -57495,7 +57601,7 @@ function handleStaticFiles(path, method, ctx, json5, serveStaticFile2) {
57495
57601
  if (res2)
57496
57602
  return res2;
57497
57603
  }
57498
- const indexPath = join15(ctx.dashboardDir, "index.html");
57604
+ const indexPath = join16(ctx.dashboardDir, "index.html");
57499
57605
  const res = serveStaticFile2(indexPath);
57500
57606
  if (res)
57501
57607
  return res;
@@ -63476,8 +63582,8 @@ class SqlitePrGroupLedgerPersistence {
63476
63582
  async transaction(fn) {
63477
63583
  const previous = sqliteTransactionTails2.get(this.db) ?? Promise.resolve();
63478
63584
  let release;
63479
- const current = new Promise((resolve17) => {
63480
- release = resolve17;
63585
+ const current = new Promise((resolve18) => {
63586
+ release = resolve18;
63481
63587
  });
63482
63588
  sqliteTransactionTails2.set(this.db, current);
63483
63589
  await previous;
@@ -63575,27 +63681,27 @@ __export(exports_serve, {
63575
63681
  SECURITY_HEADERS: () => SECURITY_HEADERS,
63576
63682
  MIME_TYPES: () => MIME_TYPES
63577
63683
  });
63578
- import { existsSync as existsSync16 } from "fs";
63579
- import { join as join16, dirname as dirname9, extname } from "path";
63684
+ import { existsSync as existsSync17 } from "fs";
63685
+ import { join as join17, dirname as dirname9, extname } from "path";
63580
63686
  import { fileURLToPath } from "url";
63581
63687
  function resolveDashboardDir() {
63582
63688
  const candidates = [];
63583
63689
  try {
63584
63690
  const scriptDir = dirname9(fileURLToPath(import.meta.url));
63585
- candidates.push(join16(scriptDir, "..", "dashboard", "dist"));
63586
- candidates.push(join16(scriptDir, "..", "..", "dashboard", "dist"));
63691
+ candidates.push(join17(scriptDir, "..", "dashboard", "dist"));
63692
+ candidates.push(join17(scriptDir, "..", "..", "dashboard", "dist"));
63587
63693
  } catch {}
63588
63694
  if (process.argv[1]) {
63589
63695
  const mainDir = dirname9(process.argv[1]);
63590
- candidates.push(join16(mainDir, "..", "dashboard", "dist"));
63591
- candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
63696
+ candidates.push(join17(mainDir, "..", "dashboard", "dist"));
63697
+ candidates.push(join17(mainDir, "..", "..", "dashboard", "dist"));
63592
63698
  }
63593
- candidates.push(join16(process.cwd(), "dashboard", "dist"));
63699
+ candidates.push(join17(process.cwd(), "dashboard", "dist"));
63594
63700
  for (const candidate of candidates) {
63595
- if (existsSync16(candidate))
63701
+ if (existsSync17(candidate))
63596
63702
  return candidate;
63597
63703
  }
63598
- return join16(process.cwd(), "dashboard", "dist");
63704
+ return join17(process.cwd(), "dashboard", "dist");
63599
63705
  }
63600
63706
  function getProvidedApiKey(req) {
63601
63707
  const headerKey = req.headers.get("x-api-key");
@@ -63667,7 +63773,7 @@ function json4(data, status3 = 200, headers) {
63667
63773
  });
63668
63774
  }
63669
63775
  function serveStaticFile(filePath) {
63670
- if (!existsSync16(filePath))
63776
+ if (!existsSync17(filePath))
63671
63777
  return null;
63672
63778
  const ext = extname(filePath);
63673
63779
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -63768,7 +63874,7 @@ data: ${data}
63768
63874
  filteredSseClients.delete(client);
63769
63875
  }
63770
63876
  const dashboardDir = resolveDashboardDir();
63771
- const dashboardExists = existsSync16(dashboardDir);
63877
+ const dashboardExists = existsSync17(dashboardDir);
63772
63878
  if (!dashboardExists) {
63773
63879
  console.error(`
63774
63880
  Dashboard not found at: ${dashboardDir}`);