@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/registry.js CHANGED
@@ -4075,23 +4075,118 @@ var init_identity_mapping = __esm(() => {
4075
4075
  });
4076
4076
  });
4077
4077
 
4078
- // src/lib/sync-utils.ts
4079
- import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
4080
- import { createHash } from "crypto";
4078
+ // node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
4081
4079
  import { homedir } from "os";
4082
4080
  import { join } from "path";
4081
+ function assertApp(app) {
4082
+ if (typeof app !== "string" || app.length === 0) {
4083
+ throw new TypeError("paths: app must be a non-empty string");
4084
+ }
4085
+ if (!APP_SLUG_RE.test(app)) {
4086
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
4087
+ }
4088
+ }
4089
+ function envOf(options) {
4090
+ return options.env ?? process.env;
4091
+ }
4092
+ function envValue(options, kind) {
4093
+ const value = envOf(options)[KIND_ENV[kind]];
4094
+ return typeof value === "string" && value.length > 0 ? value : undefined;
4095
+ }
4096
+ function isMacOS(platform) {
4097
+ return platform === "darwin";
4098
+ }
4099
+ function baseDir(kind, options) {
4100
+ const override = envValue(options, kind);
4101
+ if (override)
4102
+ return override;
4103
+ const home = options.home ?? homedir();
4104
+ const platform = options.platform ?? process.platform;
4105
+ if (isMacOS(platform)) {
4106
+ switch (kind) {
4107
+ case "config":
4108
+ case "data":
4109
+ return join(home, "Library", "Application Support", "Hasna");
4110
+ case "cache":
4111
+ return join(home, "Library", "Caches", "Hasna");
4112
+ case "state":
4113
+ return join(home, "Library", "Logs", "Hasna");
4114
+ }
4115
+ }
4116
+ switch (kind) {
4117
+ case "config":
4118
+ return join(home, ".config", "hasna");
4119
+ case "data":
4120
+ return join(home, ".local", "share", "hasna");
4121
+ case "state":
4122
+ return join(home, ".local", "state", "hasna");
4123
+ case "cache":
4124
+ return join(home, ".cache", "hasna");
4125
+ }
4126
+ }
4127
+ function resolvePath(kind, options) {
4128
+ assertApp(options.app);
4129
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
4130
+ return join(baseDir(kind, options), appSegment);
4131
+ }
4132
+ function dataDir(options) {
4133
+ return resolvePath("data", options);
4134
+ }
4135
+ var KIND_ENV, APP_SLUG_RE;
4136
+ var init_dist = __esm(() => {
4137
+ KIND_ENV = {
4138
+ config: "HASNA_CONFIG_HOME",
4139
+ data: "HASNA_DATA_HOME",
4140
+ state: "HASNA_STATE_HOME",
4141
+ cache: "HASNA_CACHE_HOME"
4142
+ };
4143
+ APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4144
+ });
4145
+
4146
+ // src/lib/paths.ts
4147
+ import { existsSync as existsSync2 } from "fs";
4148
+ import { homedir as homedir2 } from "os";
4149
+ import { join as join2, resolve as resolve2 } from "path";
4150
+ function effectiveHome(env = process.env) {
4151
+ return env.HOME || env.USERPROFILE || homedir2();
4152
+ }
4153
+ function legacyHomeDir(env = process.env) {
4154
+ return join2(effectiveHome(env), ".hasna", "todos");
4155
+ }
4156
+ function resolverHome(env = process.env) {
4157
+ return dataDir({ app: "todos", home: effectiveHome(env), env });
4158
+ }
4159
+ function adoptResolverHome(resolved, env = process.env) {
4160
+ const dataOverride = env.HASNA_DATA_HOME;
4161
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
4162
+ return true;
4163
+ return existsSync2(join2(resolved, "todos.db")) || existsSync2(join2(resolved, "config.json"));
4164
+ }
4165
+ function getTodosDir(env = process.env) {
4166
+ const resolved = resolverHome(env);
4167
+ return resolve2(adoptResolverHome(resolved, env) ? resolved : legacyHomeDir(env));
4168
+ }
4169
+ var init_paths = __esm(() => {
4170
+ init_dist();
4171
+ });
4172
+
4173
+ // src/lib/sync-utils.ts
4174
+ import { existsSync as existsSync3, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
4175
+ import { createHash } from "crypto";
4176
+ import { homedir as homedir3 } from "os";
4177
+ import { join as join3 } from "path";
4083
4178
  function getHomeDir() {
4084
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
4179
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir3();
4085
4180
  }
4086
4181
  function getTodosGlobalDir() {
4087
- return join(getHomeDir(), ".hasna", "todos");
4182
+ return getTodosDir();
4088
4183
  }
4089
4184
  function ensureDir(dir) {
4090
- if (!existsSync2(dir))
4185
+ if (!existsSync3(dir))
4091
4186
  mkdirSync(dir, { recursive: true });
4092
4187
  }
4093
4188
  function listJsonFiles(dir) {
4094
- if (!existsSync2(dir))
4189
+ if (!existsSync3(dir))
4095
4190
  return [];
4096
4191
  return readdirSync(dir).filter((f) => f.endsWith(".json"));
4097
4192
  }
@@ -4107,14 +4202,14 @@ function writeJsonFile(path, data) {
4107
4202
  `);
4108
4203
  }
4109
4204
  function readHighWaterMark(dir) {
4110
- const path = join(dir, ".highwatermark");
4111
- if (!existsSync2(path))
4205
+ const path = join3(dir, ".highwatermark");
4206
+ if (!existsSync3(path))
4112
4207
  return 1;
4113
4208
  const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
4114
4209
  return isNaN(val) ? 1 : val;
4115
4210
  }
4116
4211
  function writeHighWaterMark(dir, value) {
4117
- writeFileSync(join(dir, ".highwatermark"), String(value));
4212
+ writeFileSync(join3(dir, ".highwatermark"), String(value));
4118
4213
  }
4119
4214
  function getFileMtimeMs(path) {
4120
4215
  try {
@@ -4169,6 +4264,7 @@ function hasSyncFingerprintChanged(record) {
4169
4264
  }
4170
4265
  var TODO_SYNC_FINGERPRINT_KEY = "todos_sync_fingerprint", HOME;
4171
4266
  var init_sync_utils = __esm(() => {
4267
+ init_paths();
4172
4268
  HOME = getHomeDir();
4173
4269
  });
4174
4270
 
@@ -4479,18 +4575,18 @@ __export(exports_database, {
4479
4575
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
4480
4576
  });
4481
4577
  import { Database } from "bun:sqlite";
4482
- import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
4483
- import { dirname, join as join2, resolve as resolve2 } from "path";
4578
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
4579
+ import { dirname, join as join4, resolve as resolve3 } from "path";
4484
4580
  function isInMemoryDb(path) {
4485
4581
  return path === ":memory:" || path.startsWith("file::memory:");
4486
4582
  }
4487
4583
  function findNearestProjectDb(startDir) {
4488
4584
  const gitRoot = findGitRoot(startDir);
4489
- const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
4490
- let dir = resolve2(startDir);
4585
+ const stopAt = gitRoot ? resolve3(gitRoot) : resolve3(startDir);
4586
+ let dir = resolve3(startDir);
4491
4587
  while (true) {
4492
- const candidate = join2(dir, ".hasna", "todos", "todos.db");
4493
- if (existsSync3(candidate))
4588
+ const candidate = join4(dir, ".hasna", "todos", "todos.db");
4589
+ if (existsSync4(candidate))
4494
4590
  return candidate;
4495
4591
  if (dir === stopAt)
4496
4592
  break;
@@ -4502,9 +4598,9 @@ function findNearestProjectDb(startDir) {
4502
4598
  return null;
4503
4599
  }
4504
4600
  function findGitRoot(startDir) {
4505
- let dir = resolve2(startDir);
4601
+ let dir = resolve3(startDir);
4506
4602
  while (true) {
4507
- if (existsSync3(join2(dir, ".git")))
4603
+ if (existsSync4(join4(dir, ".git")))
4508
4604
  return dir;
4509
4605
  const parent = dirname(dir);
4510
4606
  if (parent === dir)
@@ -4514,7 +4610,7 @@ function findGitRoot(startDir) {
4514
4610
  return null;
4515
4611
  }
4516
4612
  function getGlobalDbPath() {
4517
- return join2(getHomeDir(), ".hasna", "todos", "todos.db");
4613
+ return join4(getTodosGlobalDir(), "todos.db");
4518
4614
  }
4519
4615
  function hasExplicitProjectArg(args = process.argv.slice(2)) {
4520
4616
  return args.some((arg) => arg === "--project" || arg.startsWith("--project="));
@@ -4552,7 +4648,7 @@ function getDbPath() {
4552
4648
  if (process.env["TODOS_DB_SCOPE"] === "project") {
4553
4649
  const gitRoot = findGitRoot(cwd);
4554
4650
  if (gitRoot && canCreateScopedProjectDb()) {
4555
- return join2(gitRoot, ".hasna", "todos", "todos.db");
4651
+ return join4(gitRoot, ".hasna", "todos", "todos.db");
4556
4652
  }
4557
4653
  }
4558
4654
  return getGlobalDbPath();
@@ -4563,8 +4659,8 @@ function getDatabasePath() {
4563
4659
  function ensureDir2(filePath) {
4564
4660
  if (isInMemoryDb(filePath))
4565
4661
  return;
4566
- const dir = dirname(resolve2(filePath));
4567
- if (!existsSync3(dir)) {
4662
+ const dir = dirname(resolve3(filePath));
4663
+ if (!existsSync4(dir)) {
4568
4664
  mkdirSync2(dir, { recursive: true });
4569
4665
  }
4570
4666
  }
@@ -4741,10 +4837,10 @@ var init_database = __esm(() => {
4741
4837
  });
4742
4838
 
4743
4839
  // src/lib/config.ts
4744
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
4745
- import { dirname as dirname2, join as join3 } from "path";
4840
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
4841
+ import { dirname as dirname2, join as join5 } from "path";
4746
4842
  function getConfigPath() {
4747
- return join3(getTodosGlobalDir(), "config.json");
4843
+ return join5(getTodosGlobalDir(), "config.json");
4748
4844
  }
4749
4845
  function normalizeAgent(agent) {
4750
4846
  return agent.trim().toLowerCase();
@@ -4752,7 +4848,7 @@ function normalizeAgent(agent) {
4752
4848
  function loadConfig() {
4753
4849
  if (cached)
4754
4850
  return cached;
4755
- if (!existsSync4(getConfigPath())) {
4851
+ if (!existsSync5(getConfigPath())) {
4756
4852
  cached = {};
4757
4853
  return cached;
4758
4854
  }
@@ -4775,7 +4871,7 @@ function updateConfig(patch) {
4775
4871
  }
4776
4872
  function getTodosAiConfig() {
4777
4873
  const configPath = getConfigPath();
4778
- if (!existsSync4(configPath))
4874
+ if (!existsSync5(configPath))
4779
4875
  return {};
4780
4876
  const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
4781
4877
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -5393,14 +5489,14 @@ var init_completion_guard = __esm(() => {
5393
5489
 
5394
5490
  // src/lib/event-emission-safety.ts
5395
5491
  import { tmpdir } from "os";
5396
- import { resolve as resolve3, sep } from "path";
5492
+ import { resolve as resolve4, sep } from "path";
5397
5493
  function envFlag(name) {
5398
5494
  const value = process.env[name]?.trim().toLowerCase();
5399
5495
  return value === "1" || value === "true" || value === "yes" || value === "on";
5400
5496
  }
5401
5497
  function isUnder(parent, child) {
5402
- const normalizedParent = resolve3(parent);
5403
- const normalizedChild = resolve3(child);
5498
+ const normalizedParent = resolve4(parent);
5499
+ const normalizedChild = resolve4(child);
5404
5500
  return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
5405
5501
  }
5406
5502
  function databasePathFromDatabase(db) {
@@ -5561,9 +5657,9 @@ var init_redaction = __esm(() => {
5561
5657
  });
5562
5658
 
5563
5659
  // src/lib/workspace-trust.ts
5564
- import { relative, resolve as resolve4 } from "path";
5660
+ import { relative, resolve as resolve5 } from "path";
5565
5661
  function normalizePath(path) {
5566
- return resolve4(path);
5662
+ return resolve5(path);
5567
5663
  }
5568
5664
  function unique2(values) {
5569
5665
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -5745,9 +5841,9 @@ var init_workspace_trust = __esm(() => {
5745
5841
  });
5746
5842
 
5747
5843
  // src/lib/runner-sandbox.ts
5748
- import { relative as relative2, resolve as resolve5 } from "path";
5844
+ import { relative as relative2, resolve as resolve6 } from "path";
5749
5845
  function normalizePath2(path) {
5750
- return resolve5(path);
5846
+ return resolve6(path);
5751
5847
  }
5752
5848
  function unique3(values) {
5753
5849
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -5940,7 +6036,7 @@ var init_runner_sandbox = __esm(() => {
5940
6036
  // src/lib/event-hooks.ts
5941
6037
  import { createHash as createHash2, randomUUID } from "crypto";
5942
6038
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
5943
- import { dirname as dirname3, resolve as resolve6 } from "path";
6039
+ import { dirname as dirname3, resolve as resolve7 } from "path";
5944
6040
  import { createConnection } from "net";
5945
6041
  function safeName(name) {
5946
6042
  const trimmed = name.trim();
@@ -6078,7 +6174,7 @@ async function deliverHook(hook, envelope) {
6078
6174
  if (hook.target === "stdout") {
6079
6175
  output = line.trim();
6080
6176
  } else if (hook.target === "file") {
6081
- const filePath = resolve6(hook.file_path);
6177
+ const filePath = resolve7(hook.file_path);
6082
6178
  mkdirSync3(dirname3(filePath), { recursive: true });
6083
6179
  appendFileSync(filePath, line);
6084
6180
  } else if (hook.target === "socket") {
@@ -6199,9 +6295,9 @@ var init_event_hooks = __esm(() => {
6199
6295
  // node_modules/.bun/@hasna+events@0.1.16/node_modules/@hasna/events/dist/index.js
6200
6296
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
6201
6297
  import { Buffer as Buffer2 } from "buffer";
6202
- import { existsSync as existsSync5 } from "fs";
6203
- import { homedir as homedir2 } from "os";
6204
- import { join as join4 } from "path";
6298
+ import { existsSync as existsSync6 } from "fs";
6299
+ import { homedir as homedir4 } from "os";
6300
+ import { join as join6 } from "path";
6205
6301
  import { createHmac, timingSafeEqual } from "crypto";
6206
6302
  import { randomUUID as randomUUID2 } from "crypto";
6207
6303
  import { spawn } from "child_process";
@@ -6302,7 +6398,7 @@ function channelMatchesEvent(channel, event) {
6302
6398
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
6303
6399
  }
6304
6400
  function getEventsDataDir(override) {
6305
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
6401
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join6(homedir4(), ".hasna", "events");
6306
6402
  }
6307
6403
 
6308
6404
  class JsonEventsStore {
@@ -6311,12 +6407,12 @@ class JsonEventsStore {
6311
6407
  channelsPath;
6312
6408
  eventsPath;
6313
6409
  deliveriesPath;
6314
- constructor(dataDir = getEventsDataDir()) {
6315
- this.dataDir = dataDir;
6316
- this.runtime = localJsonRuntime(dataDir);
6317
- this.channelsPath = join4(dataDir, "channels.json");
6318
- this.eventsPath = join4(dataDir, "events.json");
6319
- this.deliveriesPath = join4(dataDir, "deliveries.json");
6410
+ constructor(dataDir2 = getEventsDataDir()) {
6411
+ this.dataDir = dataDir2;
6412
+ this.runtime = localJsonRuntime(dataDir2);
6413
+ this.channelsPath = join6(dataDir2, "channels.json");
6414
+ this.eventsPath = join6(dataDir2, "events.json");
6415
+ this.deliveriesPath = join6(dataDir2, "deliveries.json");
6320
6416
  }
6321
6417
  async init() {
6322
6418
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -6433,7 +6529,7 @@ class JsonEventsStore {
6433
6529
  };
6434
6530
  }
6435
6531
  async ensureArrayFile(path) {
6436
- if (!existsSync5(path)) {
6532
+ if (!existsSync6(path)) {
6437
6533
  await writeFile(path, `[]
6438
6534
  `, { encoding: "utf-8", mode: 384 });
6439
6535
  }
@@ -6463,7 +6559,7 @@ class JsonEventsStore {
6463
6559
  });
6464
6560
  }
6465
6561
  }
6466
- function localJsonRuntime(dataDir = getEventsDataDir()) {
6562
+ function localJsonRuntime(dataDir2 = getEventsDataDir()) {
6467
6563
  return {
6468
6564
  mode: "local-files",
6469
6565
  name: "json-events-store",
@@ -6476,7 +6572,7 @@ function localJsonRuntime(dataDir = getEventsDataDir()) {
6476
6572
  durable: true,
6477
6573
  idempotency: "best-effort-local",
6478
6574
  replayCursors: true,
6479
- description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
6575
+ description: `Local JSON files in ${dataDir2}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
6480
6576
  };
6481
6577
  }
6482
6578
  function encodeLocalJsonEventCursor(offset, options = {}) {
@@ -6655,7 +6751,7 @@ async function dispatchCommand(event, channel) {
6655
6751
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
6656
6752
  HASNA_EVENT_JSON: eventJson
6657
6753
  };
6658
- return new Promise((resolve7) => {
6754
+ return new Promise((resolve8) => {
6659
6755
  const child = spawn(channel.command.command, channel.command.args ?? [], {
6660
6756
  cwd: channel.command.cwd,
6661
6757
  env,
@@ -6673,7 +6769,7 @@ async function dispatchCommand(event, channel) {
6673
6769
  });
6674
6770
  child.on("error", (error) => {
6675
6771
  clearTimeout(timeout);
6676
- resolve7({
6772
+ resolve8({
6677
6773
  attempt: 1,
6678
6774
  status: "failed",
6679
6775
  startedAt,
@@ -6686,7 +6782,7 @@ async function dispatchCommand(event, channel) {
6686
6782
  child.on("close", (code, signal) => {
6687
6783
  clearTimeout(timeout);
6688
6784
  const success = code === 0;
6689
- resolve7({
6785
+ resolve8({
6690
6786
  attempt: 1,
6691
6787
  status: success ? "success" : "failed",
6692
6788
  startedAt,
@@ -7028,7 +7124,7 @@ function normalizeRetryPolicy(policy) {
7028
7124
  };
7029
7125
  }
7030
7126
  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;
7031
- var init_dist = __esm(() => {
7127
+ var init_dist2 = __esm(() => {
7032
7128
  DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
7033
7129
  EventValidationError = class EventValidationError extends Error {
7034
7130
  eventType;
@@ -7451,7 +7547,7 @@ function emitSharedTaskEventQuiet(input) {
7451
7547
  }
7452
7548
  var SOURCE = "todos";
7453
7549
  var init_shared_events = __esm(() => {
7454
- init_dist();
7550
+ init_dist2();
7455
7551
  init_database();
7456
7552
  init_projects();
7457
7553
  init_task_lists();
@@ -7459,7 +7555,7 @@ var init_shared_events = __esm(() => {
7459
7555
  });
7460
7556
 
7461
7557
  // src/lib/secret-redaction.ts
7462
- import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
7558
+ import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
7463
7559
  function registerCustomRedactor(fn) {
7464
7560
  customRedactors.push(fn);
7465
7561
  }
@@ -7524,7 +7620,7 @@ function scanAndRedactText(text, options = {}) {
7524
7620
  };
7525
7621
  }
7526
7622
  function scanFileForSecrets(path, options = {}) {
7527
- if (!existsSync6(path))
7623
+ if (!existsSync7(path))
7528
7624
  throw new Error(`File not found: ${path}`);
7529
7625
  const content = readFileSync3(path, "utf8");
7530
7626
  return scanAndRedactText(content, options);
@@ -9782,17 +9878,17 @@ function sanitizeCreateTaskInput(input) {
9782
9878
  return {
9783
9879
  ...input,
9784
9880
  title: sanitizePreWriteText(input.title, "task.title"),
9785
- description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
9881
+ description: input.description == null ? input.description : sanitizePreWriteText(input.description, "task.description"),
9786
9882
  tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
9787
9883
  metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined,
9788
- reason: input.reason !== undefined ? sanitizePreWriteText(input.reason, "task.reason") : undefined
9884
+ reason: input.reason == null ? input.reason : sanitizePreWriteText(input.reason, "task.reason")
9789
9885
  };
9790
9886
  }
9791
9887
  function sanitizeUpdateTaskInput(input) {
9792
9888
  return {
9793
9889
  ...input,
9794
9890
  title: input.title !== undefined ? sanitizePreWriteText(input.title, "task.title") : undefined,
9795
- description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
9891
+ description: input.description == null ? input.description : sanitizePreWriteText(input.description, "task.description"),
9796
9892
  tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
9797
9893
  metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined
9798
9894
  };
@@ -11646,28 +11742,28 @@ var init_boards = __esm(() => {
11646
11742
 
11647
11743
  // src/lib/artifact-store.ts
11648
11744
  import { createHash as createHash3 } from "crypto";
11649
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11650
- import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
11745
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11746
+ import { basename, dirname as dirname4, join as join7, resolve as resolve8 } from "path";
11651
11747
  import { tmpdir as tmpdir2 } from "os";
11652
11748
  function isInMemoryDb2(path) {
11653
11749
  return path === ":memory:" || path.startsWith("file::memory:");
11654
11750
  }
11655
11751
  function artifactStoreRoot() {
11656
11752
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11657
- return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11753
+ return resolve8(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11658
11754
  if (process.env["TODOS_ARTIFACTS_DIR"])
11659
- return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11755
+ return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
11660
11756
  const dbPath = getDatabasePath();
11661
11757
  if (isInMemoryDb2(dbPath))
11662
- return join5(tmpdir2(), "hasna-todos-artifacts");
11663
- return join5(dirname4(resolve7(dbPath)), "artifacts");
11758
+ return join7(tmpdir2(), "hasna-todos-artifacts");
11759
+ return join7(dirname4(resolve8(dbPath)), "artifacts");
11664
11760
  }
11665
11761
  function artifactStorePath(relativePath) {
11666
11762
  const normalized = relativePath.replace(/\\/g, "/");
11667
11763
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
11668
11764
  throw new Error("Invalid artifact store path");
11669
11765
  }
11670
- return join5(artifactStoreRoot(), normalized);
11766
+ return join7(artifactStoreRoot(), normalized);
11671
11767
  }
11672
11768
  function sha256(buffer) {
11673
11769
  return createHash3("sha256").update(buffer).digest("hex");
@@ -11707,8 +11803,8 @@ function mediaTypeFor(path, textLike) {
11707
11803
  return "application/octet-stream";
11708
11804
  }
11709
11805
  function storeArtifactContent(input) {
11710
- const sourcePath = resolve7(input.path);
11711
- if (!existsSync7(sourcePath))
11806
+ const sourcePath = resolve8(input.path);
11807
+ if (!existsSync8(sourcePath))
11712
11808
  return null;
11713
11809
  const sourceStat = statSync2(sourcePath);
11714
11810
  if (!sourceStat.isFile())
@@ -11725,9 +11821,9 @@ function storeArtifactContent(input) {
11725
11821
  redactionStatus = "redacted";
11726
11822
  }
11727
11823
  const storedSha = sha256(storedBuffer);
11728
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
11824
+ const relativePath = join7("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
11729
11825
  const destination = artifactStorePath(relativePath);
11730
- if (!existsSync7(destination)) {
11826
+ if (!existsSync8(destination)) {
11731
11827
  mkdirSync4(dirname4(destination), { recursive: true });
11732
11828
  writeFileSync2(destination, storedBuffer);
11733
11829
  }
@@ -11787,7 +11883,7 @@ function verifyStoredArtifact(input) {
11787
11883
  };
11788
11884
  }
11789
11885
  const storedPath = artifactStorePath(store.relative_path);
11790
- if (!existsSync7(storedPath)) {
11886
+ if (!existsSync8(storedPath)) {
11791
11887
  return {
11792
11888
  id: input.id,
11793
11889
  path: input.path,
@@ -11862,20 +11958,20 @@ function importStoredArtifactContent(content) {
11862
11958
  }
11863
11959
  function getArtifactStoreRoot(dbPath) {
11864
11960
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11865
- return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11961
+ return resolve8(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11866
11962
  if (process.env["TODOS_ARTIFACTS_DIR"])
11867
- return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11963
+ return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
11868
11964
  const path = dbPath ?? getDatabasePath();
11869
11965
  if (isInMemoryDb2(path))
11870
- return join5(tmpdir2(), "hasna-todos-artifacts");
11871
- return join5(dirname4(resolve7(path)), "artifacts");
11966
+ return join7(tmpdir2(), "hasna-todos-artifacts");
11967
+ return join7(dirname4(resolve8(path)), "artifacts");
11872
11968
  }
11873
11969
  function computeContentHash(path) {
11874
- return sha256(readFileSync4(resolve7(path)));
11970
+ return sha256(readFileSync4(resolve8(path)));
11875
11971
  }
11876
11972
  function storeArtifactFile(input) {
11877
- const sourcePath = resolve7(input.sourcePath);
11878
- if (!existsSync7(sourcePath)) {
11973
+ const sourcePath = resolve8(input.sourcePath);
11974
+ if (!existsSync8(sourcePath)) {
11879
11975
  throw new Error(`Source file not found: ${input.sourcePath}`);
11880
11976
  }
11881
11977
  if (!statSync2(sourcePath).isFile()) {
@@ -11888,7 +11984,7 @@ function storeArtifactFile(input) {
11888
11984
  let localPath = sourcePath;
11889
11985
  if (storageMode === "copy") {
11890
11986
  const fileName = input.name && input.name.trim().length > 0 ? basename(input.name) : basename(sourcePath);
11891
- const destination = join5(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
11987
+ const destination = join7(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
11892
11988
  mkdirSync4(dirname4(destination), { recursive: true });
11893
11989
  writeFileSync2(destination, buffer);
11894
11990
  localPath = destination;
@@ -11898,7 +11994,7 @@ function storeArtifactFile(input) {
11898
11994
  function deleteStoredArtifactFile(localPath, storageMode, _dbPath2) {
11899
11995
  if (storageMode === "reference")
11900
11996
  return false;
11901
- if (!localPath || !existsSync7(localPath))
11997
+ if (!localPath || !existsSync8(localPath))
11902
11998
  return false;
11903
11999
  rmSync(localPath, { force: true });
11904
12000
  try {
@@ -11923,7 +12019,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
11923
12019
  };
11924
12020
  }
11925
12021
  function writeArtifactExportManifest(manifest, outputPath) {
11926
- const destination = resolve7(outputPath);
12022
+ const destination = resolve8(outputPath);
11927
12023
  mkdirSync4(dirname4(destination), { recursive: true });
11928
12024
  writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
11929
12025
  `);
@@ -13080,7 +13176,7 @@ var init_tasks = __esm(() => {
13080
13176
  // package.json
13081
13177
  var package_default = {
13082
13178
  name: "@hasna/todos",
13083
- version: "0.15.50",
13179
+ version: "0.15.52",
13084
13180
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
13085
13181
  type: "module",
13086
13182
  main: "dist/index.js",
@@ -13139,6 +13235,7 @@ var package_default = {
13139
13235
  files: [
13140
13236
  "dist",
13141
13237
  "dashboard/dist",
13238
+ "postinstall.js",
13142
13239
  "LICENSE",
13143
13240
  "README.md"
13144
13241
  ],
@@ -13165,7 +13262,7 @@ var package_default = {
13165
13262
  "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
13166
13263
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
13167
13264
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
13168
- postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
13265
+ postinstall: "node postinstall.js"
13169
13266
  },
13170
13267
  keywords: [
13171
13268
  "todos",
@@ -13199,13 +13296,15 @@ var package_default = {
13199
13296
  author: "Andrei Hasna <andrei@hasna.com>",
13200
13297
  license: "Apache-2.0",
13201
13298
  dependencies: {
13202
- "@hasna/contracts": "0.14.0",
13299
+ "@hasna/contracts": "0.14.2",
13203
13300
  "@hasna/events": "^0.1.11",
13301
+ "@hasna/paths": "0.1.0",
13204
13302
  "@modelcontextprotocol/sdk": "^1.12.1",
13205
13303
  chalk: "^5.4.1",
13206
13304
  commander: "^13.1.0",
13207
13305
  ink: "^5.2.0",
13208
13306
  react: "^18.3.1",
13307
+ "signal-exit": "3.0.7",
13209
13308
  zod: "3.25.76"
13210
13309
  },
13211
13310
  overrides: {
@@ -13214,7 +13313,7 @@ var package_default = {
13214
13313
  zod: "3.25.76"
13215
13314
  },
13216
13315
  devDependencies: {
13217
- "@types/bun": "^1.2.4",
13316
+ "@types/bun": "1.3.14",
13218
13317
  "@types/react": "^18.3.18",
13219
13318
  "bun-types": "1.3.9",
13220
13319
  "hasna-deployment-contracts": "npm:@hasna/contracts@0.10.4",
@@ -16440,7 +16539,7 @@ function todosAiExitCodeForResult(result) {
16440
16539
  }
16441
16540
  // src/lib/onboarding-fixtures.ts
16442
16541
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
16443
- import { join as join6 } from "path";
16542
+ import { join as join8 } from "path";
16444
16543
 
16445
16544
  // src/lib/local-bridge.ts
16446
16545
  init_database();
@@ -17377,7 +17476,7 @@ function writeOnboardingFixtureFiles(directory) {
17377
17476
  mkdirSync5(directory, { recursive: true });
17378
17477
  const files = [];
17379
17478
  for (const fixture of allFixtures()) {
17380
- const path = join6(directory, `${fixture.summary.name}.bridge.json`);
17479
+ const path = join8(directory, `${fixture.summary.name}.bridge.json`);
17381
17480
  writeFileSync3(path, `${JSON.stringify(fixture.bundle, null, 2)}
17382
17481
  `, "utf-8");
17383
17482
  files.push(path);
@@ -17395,7 +17494,7 @@ function importOnboardingFixture(options = {}) {
17395
17494
  init_database();
17396
17495
  import { createHash as createHash4 } from "crypto";
17397
17496
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
17398
- import { dirname as dirname5, resolve as resolve8 } from "path";
17497
+ import { dirname as dirname5, resolve as resolve9 } from "path";
17399
17498
  import { mkdirSync as mkdirSync6 } from "fs";
17400
17499
  var TODOS_LOCAL_BACKUP_KIND = "hasna.todos.local-backup";
17401
17500
  var TODOS_LOCAL_BACKUP_SCHEMA_VERSION = 1;
@@ -17502,14 +17601,14 @@ function createLocalBackup(options = {}, db) {
17502
17601
  return backup;
17503
17602
  }
17504
17603
  function writeLocalBackupFile(backup, outputPath) {
17505
- const path = resolve8(outputPath);
17604
+ const path = resolve9(outputPath);
17506
17605
  mkdirSync6(dirname5(path), { recursive: true });
17507
17606
  writeFileSync4(path, `${JSON.stringify(backup, null, 2)}
17508
17607
  `);
17509
17608
  return path;
17510
17609
  }
17511
17610
  function readLocalBackupFile(path) {
17512
- return JSON.parse(readFileSync5(resolve8(path), "utf-8"));
17611
+ return JSON.parse(readFileSync5(resolve9(path), "utf-8"));
17513
17612
  }
17514
17613
  function verifyLocalBackup(value, options = {}, db) {
17515
17614
  const verifiedAt = options.verified_at ?? now();
@@ -18203,7 +18302,7 @@ function renderLocalSnapshotMarkdown(snapshot) {
18203
18302
  }
18204
18303
  // src/lib/sdk-integration-fixtures.ts
18205
18304
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
18206
- import { join as join7 } from "path";
18305
+ import { join as join9 } from "path";
18207
18306
 
18208
18307
  // src/cli-mcp-parity.ts
18209
18308
  function source4(version) {
@@ -20174,7 +20273,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
20174
20273
  ];
20175
20274
  const written = [];
20176
20275
  for (const [name, payload] of files) {
20177
- const file = join7(directory, name);
20276
+ const file = join9(directory, name);
20178
20277
  writeFileSync5(file, `${JSON.stringify(payload, null, 2)}
20179
20278
  `, "utf-8");
20180
20279
  written.push(file);
@@ -21457,7 +21556,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
21457
21556
  init_migrations();
21458
21557
  init_schema();
21459
21558
  import { readFileSync as readFileSync6 } from "fs";
21460
- import { join as join8, resolve as resolve9 } from "path";
21559
+ import { join as join10, resolve as resolve10 } from "path";
21461
21560
  import { Database as Database2 } from "bun:sqlite";
21462
21561
  var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
21463
21562
  var EXPECTED_PACKAGE_NAME = "@hasna/todos";
@@ -21503,7 +21602,7 @@ function warn(id, message, details) {
21503
21602
  return { id, status: "warning", message, details };
21504
21603
  }
21505
21604
  function readPackageJson(root) {
21506
- return JSON.parse(readFileSync6(join8(root, "package.json"), "utf8"));
21605
+ return JSON.parse(readFileSync6(join10(root, "package.json"), "utf8"));
21507
21606
  }
21508
21607
  function sortedKeys(value) {
21509
21608
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -21599,7 +21698,7 @@ function checkChangelog() {
21599
21698
  ];
21600
21699
  }
21601
21700
  function createReleaseCompatibilityReport(options = {}) {
21602
- const root = resolve9(options.root ?? process.cwd());
21701
+ const root = resolve10(options.root ?? process.cwd());
21603
21702
  const packageJson = readPackageJson(root);
21604
21703
  const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
21605
21704
  const checks = [