@hasna/todos 0.13.6 → 0.13.8

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/index.js CHANGED
@@ -4336,6 +4336,7 @@ var init_database = __esm(() => {
4336
4336
 
4337
4337
  // src/lib/sync-utils.ts
4338
4338
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
4339
+ import { createHash } from "crypto";
4339
4340
  import { join as join2 } from "path";
4340
4341
  function getHomeDir() {
4341
4342
  return process.env["HOME"] || process.env["USERPROFILE"] || "~";
@@ -4391,7 +4392,40 @@ function appendSyncConflict(metadata, conflict, limit = 5) {
4391
4392
  const next = [conflict, ...current].slice(0, limit);
4392
4393
  return { ...metadata, sync_conflicts: next };
4393
4394
  }
4394
- var HOME;
4395
+ function canonicalize(value) {
4396
+ if (Array.isArray(value))
4397
+ return value.map(canonicalize);
4398
+ if (!value || typeof value !== "object")
4399
+ return value;
4400
+ const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).sort(([a], [b]) => a.localeCompare(b));
4401
+ return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalize(entryValue)]));
4402
+ }
4403
+ function withoutSyncFingerprintMetadata(metadata) {
4404
+ const { [TODO_SYNC_FINGERPRINT_KEY]: _fingerprint, ...rest } = metadata;
4405
+ return rest;
4406
+ }
4407
+ function syncFingerprint(record) {
4408
+ const metadata = withoutSyncFingerprintMetadata(record.metadata || {});
4409
+ const canonical = canonicalize({ ...record, metadata });
4410
+ return `sha256:${createHash("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
4411
+ }
4412
+ function withSyncFingerprint(record) {
4413
+ const metadata = withoutSyncFingerprintMetadata(record.metadata);
4414
+ return {
4415
+ ...record,
4416
+ metadata: {
4417
+ ...metadata,
4418
+ [TODO_SYNC_FINGERPRINT_KEY]: syncFingerprint({ ...record, metadata })
4419
+ }
4420
+ };
4421
+ }
4422
+ function hasSyncFingerprintChanged(record) {
4423
+ const stored = record.metadata?.[TODO_SYNC_FINGERPRINT_KEY];
4424
+ if (typeof stored !== "string" || stored.length === 0)
4425
+ return null;
4426
+ return stored !== syncFingerprint(record);
4427
+ }
4428
+ var TODO_SYNC_FINGERPRINT_KEY = "todos_sync_fingerprint", HOME;
4395
4429
  var init_sync_utils = __esm(() => {
4396
4430
  HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
4397
4431
  });
@@ -5502,7 +5536,7 @@ var init_runner_sandbox = __esm(() => {
5502
5536
  });
5503
5537
 
5504
5538
  // src/lib/event-hooks.ts
5505
- import { createHash, randomUUID } from "crypto";
5539
+ import { createHash as createHash2, randomUUID } from "crypto";
5506
5540
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
5507
5541
  import { dirname as dirname3, resolve as resolve6 } from "path";
5508
5542
  import { createConnection } from "net";
@@ -5569,7 +5603,7 @@ function buildEnvelope(type, payload, timestamp2 = new Date().toISOString()) {
5569
5603
  payload: redactValue(payload ?? {}),
5570
5604
  source: { package: "@hasna/todos", local_only: true }
5571
5605
  };
5572
- const digest = createHash("sha256").update(canonicalEvent(base)).digest("hex");
5606
+ const digest = createHash2("sha256").update(canonicalEvent(base)).digest("hex");
5573
5607
  return { ...base, integrity: { algorithm: "sha256", digest } };
5574
5608
  }
5575
5609
  function summarize(value) {
@@ -7479,6 +7513,14 @@ var init_checklists = __esm(() => {
7479
7513
  init_database();
7480
7514
  });
7481
7515
 
7516
+ // src/lib/creator-identity.ts
7517
+ function canonicalAgentRef(value) {
7518
+ return value.trim().toLowerCase();
7519
+ }
7520
+ var init_creator_identity = __esm(() => {
7521
+ init_sync_utils();
7522
+ });
7523
+
7482
7524
  // src/lib/recurrence.ts
7483
7525
  function parseRecurrenceRule(rule) {
7484
7526
  const normalized = rule.trim().toLowerCase();
@@ -8185,6 +8227,11 @@ __export(exports_task_lifecycle, {
8185
8227
  claimOrSteal: () => claimOrSteal,
8186
8228
  claimNextTask: () => claimNextTask
8187
8229
  });
8230
+ function sameHolder(stored, incoming) {
8231
+ if (!stored || !incoming)
8232
+ return false;
8233
+ return canonicalAgentRef(stored) === canonicalAgentRef(incoming);
8234
+ }
8188
8235
  function lockExpiresAt(lockedAt) {
8189
8236
  if (!lockedAt)
8190
8237
  return null;
@@ -8235,13 +8282,13 @@ function startTask(id, agentId, db) {
8235
8282
  const cutoff = lockExpiryCutoff();
8236
8283
  const timestamp2 = now();
8237
8284
  const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
8238
- WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
8285
+ WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR LOWER(TRIM(locked_by)) = LOWER(TRIM(?)) OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
8239
8286
  if (result.changes === 0) {
8240
8287
  const current = getTask(id, d);
8241
8288
  if (!current)
8242
8289
  throw new TaskNotFoundError(id);
8243
8290
  assertStartable(current, agentId);
8244
- if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
8291
+ if (current.locked_by && !sameHolder(current.locked_by, agentId) && !isLockExpired(current.locked_at)) {
8245
8292
  throw new LockError(id, current.locked_by);
8246
8293
  }
8247
8294
  throw new Error(`Task ${id} could not be started because it changed during claim`);
@@ -8266,7 +8313,7 @@ function completeTask(id, agentId, db, options) {
8266
8313
  if (task.status === "cancelled") {
8267
8314
  throw new Error(`Task ${id} is cancelled and cannot be completed`);
8268
8315
  }
8269
- if (agentId && task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
8316
+ if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
8270
8317
  throw new LockError(id, task.locked_by);
8271
8318
  }
8272
8319
  checkCompletionGuard(task, agentId || null, d);
@@ -8388,16 +8435,16 @@ function lockTask(id, agentId, db) {
8388
8435
  error: `Task is ${task.status} and cannot be locked`
8389
8436
  };
8390
8437
  }
8391
- if (task.locked_by === agentId && !isLockExpired(task.locked_at)) {
8438
+ if (sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
8392
8439
  const timestamp3 = now();
8393
- d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by = ?`, [timestamp3, timestamp3, id, agentId]);
8440
+ d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND LOWER(TRIM(locked_by)) = LOWER(TRIM(?))`, [timestamp3, timestamp3, id, agentId]);
8394
8441
  logTaskChange(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
8395
8442
  return { success: true, locked_by: agentId, locked_at: timestamp3, expires_at: lockExpiresAt(timestamp3) };
8396
8443
  }
8397
8444
  const cutoff = lockExpiryCutoff();
8398
8445
  const timestamp2 = now();
8399
8446
  const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
8400
- WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
8447
+ WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR LOWER(TRIM(locked_by)) = LOWER(TRIM(?)) OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
8401
8448
  if (result.changes === 0) {
8402
8449
  const current = getTask(id, d);
8403
8450
  if (!current)
@@ -8429,7 +8476,7 @@ function unlockTask(id, agentId, db) {
8429
8476
  const task = getTask(id, d);
8430
8477
  if (!task)
8431
8478
  throw new TaskNotFoundError(id);
8432
- if (agentId && task.locked_by && task.locked_by !== agentId) {
8479
+ if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId)) {
8433
8480
  throw new LockError(id, task.locked_by);
8434
8481
  }
8435
8482
  const timestamp2 = now();
@@ -8722,6 +8769,7 @@ var MAX_SPAWN_DEPTH = 10;
8722
8769
  var init_task_lifecycle = __esm(() => {
8723
8770
  init_types();
8724
8771
  init_database();
8772
+ init_creator_identity();
8725
8773
  init_completion_guard();
8726
8774
  init_event_emission_safety();
8727
8775
  init_event_hooks();
@@ -9177,6 +9225,10 @@ function updateTask(id, input, db) {
9177
9225
  sets.push("description = ?");
9178
9226
  params.push(input.description);
9179
9227
  }
9228
+ if (input.agent_id !== undefined) {
9229
+ sets.push("agent_id = ?");
9230
+ params.push(input.agent_id);
9231
+ }
9180
9232
  if (input.status !== undefined) {
9181
9233
  if (input.status === "completed") {
9182
9234
  checkCompletionGuard(task, task.assigned_to || task.agent_id || null, d);
@@ -10486,7 +10538,7 @@ var init_boards = __esm(() => {
10486
10538
  });
10487
10539
 
10488
10540
  // src/lib/artifact-store.ts
10489
- import { createHash as createHash2 } from "crypto";
10541
+ import { createHash as createHash3 } from "crypto";
10490
10542
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
10491
10543
  import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
10492
10544
  import { tmpdir as tmpdir2 } from "os";
@@ -10511,7 +10563,7 @@ function artifactStorePath(relativePath) {
10511
10563
  return join5(artifactStoreRoot(), normalized);
10512
10564
  }
10513
10565
  function sha256(buffer) {
10514
- return createHash2("sha256").update(buffer).digest("hex");
10566
+ return createHash3("sha256").update(buffer).digest("hex");
10515
10567
  }
10516
10568
  function isTextLike(buffer, path) {
10517
10569
  if (buffer.includes(0))
@@ -12034,7 +12086,7 @@ var init_dispatches = __esm(() => {
12034
12086
  // package.json
12035
12087
  var package_default = {
12036
12088
  name: "@hasna/todos",
12037
- version: "0.13.6",
12089
+ version: "0.13.8",
12038
12090
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12039
12091
  type: "module",
12040
12092
  main: "dist/index.js",
@@ -15749,7 +15801,7 @@ function importOnboardingFixture(options = {}) {
15749
15801
  }
15750
15802
  // src/lib/local-backups.ts
15751
15803
  init_database();
15752
- import { createHash as createHash3 } from "crypto";
15804
+ import { createHash as createHash4 } from "crypto";
15753
15805
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
15754
15806
  import { dirname as dirname5, resolve as resolve8 } from "path";
15755
15807
  import { mkdirSync as mkdirSync6 } from "fs";
@@ -15767,7 +15819,7 @@ function stableJson(value) {
15767
15819
  return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
15768
15820
  }
15769
15821
  function sha2562(value) {
15770
- return createHash3("sha256").update(stableJson(value)).digest("hex");
15822
+ return createHash4("sha256").update(stableJson(value)).digest("hex");
15771
15823
  }
15772
15824
  function sqliteIntegrity(db) {
15773
15825
  let quick = "unknown";
@@ -16045,7 +16097,7 @@ function checkLocalIntegrity(options = {}, db) {
16045
16097
  }
16046
16098
  // src/lib/local-snapshots.ts
16047
16099
  init_database();
16048
- import { createHash as createHash4 } from "crypto";
16100
+ import { createHash as createHash5 } from "crypto";
16049
16101
 
16050
16102
  // src/lib/activity-timeline.ts
16051
16103
  init_database();
@@ -16321,7 +16373,7 @@ function stable(value) {
16321
16373
  return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stable(item)]));
16322
16374
  }
16323
16375
  function sha2563(value) {
16324
- return createHash4("sha256").update(JSON.stringify(stable(value))).digest("hex");
16376
+ return createHash5("sha256").update(JSON.stringify(stable(value))).digest("hex");
16325
16377
  }
16326
16378
  function latestTimestamp(items, fallback) {
16327
16379
  const timestamps = [];
@@ -19533,20 +19585,20 @@ init_database();
19533
19585
  init_task_runs();
19534
19586
  init_config2();
19535
19587
  init_redaction();
19536
- import { createHash as createHash5 } from "crypto";
19588
+ import { createHash as createHash6 } from "crypto";
19537
19589
  var LOCAL_AUDIT_LEDGER_SCHEMA_VERSION = 1;
19538
19590
  var LOCAL_AUDIT_LEDGER_HASH_ALGORITHM = "sha256";
19539
19591
  var LOCAL_AUDIT_LEDGER_INITIAL_HASH = "0".repeat(64);
19540
- function canonicalize(value) {
19592
+ function canonicalize2(value) {
19541
19593
  if (value === null || typeof value !== "object")
19542
19594
  return JSON.stringify(value);
19543
19595
  if (Array.isArray(value))
19544
- return `[${value.map(canonicalize).join(",")}]`;
19596
+ return `[${value.map(canonicalize2).join(",")}]`;
19545
19597
  const object = value;
19546
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(object[key])}`).join(",")}}`;
19598
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize2(object[key])}`).join(",")}}`;
19547
19599
  }
19548
19600
  function hash(value) {
19549
- return createHash5("sha256").update(value).digest("hex");
19601
+ return createHash6("sha256").update(value).digest("hex");
19550
19602
  }
19551
19603
  function parsePayload2(value) {
19552
19604
  if (!value)
@@ -19679,7 +19731,7 @@ function toLedgerEntries(rows) {
19679
19731
  let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
19680
19732
  return ordered.map((row, index) => {
19681
19733
  const payload = parsePayload2(row.payload_json);
19682
- const payloadHash = hash(canonicalize({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
19734
+ const payloadHash = hash(canonicalize2({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
19683
19735
  const chainHash = hash(`${previous}
19684
19736
  ${payloadHash}`);
19685
19737
  const entry = {
@@ -20066,7 +20118,7 @@ function renderReleaseCompatibilityMarkdown(report) {
20066
20118
  `);
20067
20119
  }
20068
20120
  // src/db/inbox.ts
20069
- import { createHash as createHash6 } from "crypto";
20121
+ import { createHash as createHash7 } from "crypto";
20070
20122
 
20071
20123
  // src/lib/github.ts
20072
20124
  import { execFileSync } from "child_process";
@@ -20151,7 +20203,7 @@ function compactWhitespace(value) {
20151
20203
  function fingerprintInboxInput(input) {
20152
20204
  const sourceType = input.source_type || detectInboxSourceType(input.body, input.source_url);
20153
20205
  const normalized = compactWhitespace(sanitizePreWriteText(input.body, "inbox.fingerprint")).slice(0, 8000);
20154
- return createHash6("sha256").update(`${sourceType}
20206
+ return createHash7("sha256").update(`${sourceType}
20155
20207
  ${input.source_url || ""}
20156
20208
  ${normalized}`).digest("hex");
20157
20209
  }
@@ -21268,7 +21320,7 @@ function importExternalIssues(input, db) {
21268
21320
  init_database();
21269
21321
  init_tasks();
21270
21322
  init_redaction();
21271
- import { createHash as createHash7 } from "crypto";
21323
+ import { createHash as createHash8 } from "crypto";
21272
21324
  var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1";
21273
21325
  var TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1";
21274
21326
  var TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1";
@@ -21457,7 +21509,7 @@ function fingerprintTesterIssueReport(report) {
21457
21509
  normalizeText2(report.failure?.message || report.summary || report.title).slice(0, 240),
21458
21510
  normalizeText2(stackTop).slice(0, 160)
21459
21511
  ].join("::");
21460
- return `testers:${createHash7("sha256").update(raw).digest("hex").slice(0, 16)}`;
21512
+ return `testers:${createHash8("sha256").update(raw).digest("hex").slice(0, 16)}`;
21461
21513
  }
21462
21514
  function priorityForSeverity(severity, fallback) {
21463
21515
  return PRIORITIES3.includes(severity) ? severity : fallback;
@@ -22909,7 +22961,7 @@ function renderLocalReportMarkdown(report) {
22909
22961
  init_config2();
22910
22962
  init_redaction();
22911
22963
  init_prewrite_secrets();
22912
- import { createCipheriv, createDecipheriv, createHash as createHash8, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
22964
+ import { createCipheriv, createDecipheriv, createHash as createHash9, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
22913
22965
  var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value";
22914
22966
  var TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge";
22915
22967
  var TODOS_ENCRYPTION_SCHEMA_VERSION = 1;
@@ -22935,7 +22987,7 @@ function now3() {
22935
22987
  return new Date().toISOString();
22936
22988
  }
22937
22989
  function sha2564(value) {
22938
- return createHash8("sha256").update(value).digest("hex");
22990
+ return createHash9("sha256").update(value).digest("hex");
22939
22991
  }
22940
22992
  function normalizeProfileName(value) {
22941
22993
  const name = (value || DEFAULT_ENCRYPTION_PROFILE).trim();
@@ -28249,7 +28301,7 @@ function isCommentRedactionBackfillComplete(result) {
28249
28301
  return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
28250
28302
  }
28251
28303
  // src/storage/s3-artifacts.ts
28252
- import { createHash as createHash9, createHmac as createHmac2 } from "crypto";
28304
+ import { createHash as createHash10, createHmac as createHmac2 } from "crypto";
28253
28305
  function createTodosS3ArtifactStore(options) {
28254
28306
  const requestFetch = options.fetch ?? fetch;
28255
28307
  const now4 = options.now ?? (() => new Date);
@@ -28421,7 +28473,7 @@ function toAmzDate(date) {
28421
28473
  return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
28422
28474
  }
28423
28475
  function sha256Hex(value) {
28424
- return createHash9("sha256").update(value).digest("hex");
28476
+ return createHash10("sha256").update(value).digest("hex");
28425
28477
  }
28426
28478
  function hmac(key, value) {
28427
28479
  return createHmac2("sha256", key).update(value).digest();
@@ -29268,7 +29320,7 @@ function createClient(options) {
29268
29320
  init_database();
29269
29321
 
29270
29322
  // src/pr-groups/ledger.ts
29271
- import { createHash as createHash10 } from "crypto";
29323
+ import { createHash as createHash11 } from "crypto";
29272
29324
 
29273
29325
  // src/pr-groups/types.ts
29274
29326
  var PR_GROUP_LEDGER_SCHEMA_VERSION = 1;
@@ -29331,7 +29383,7 @@ var RECEIPT_EVENT_TYPES = new Set([
29331
29383
  "merge_outcome"
29332
29384
  ]);
29333
29385
  function sha2565(value) {
29334
- return createHash10("sha256").update(value).digest("hex");
29386
+ return createHash11("sha256").update(value).digest("hex");
29335
29387
  }
29336
29388
  function stableValue(value) {
29337
29389
  if (Array.isArray(value))
@@ -31849,7 +31901,7 @@ init_task_lifecycle();
31849
31901
  init_task_crud();
31850
31902
  init_redaction();
31851
31903
  import { Database as Database3 } from "bun:sqlite";
31852
- import { createHash as createHash11 } from "crypto";
31904
+ import { createHash as createHash12 } from "crypto";
31853
31905
  import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
31854
31906
  import { basename as basename2, dirname as dirname6, join as join9, resolve as resolve10 } from "path";
31855
31907
 
@@ -32132,7 +32184,7 @@ function normalizePath3(input) {
32132
32184
  return resolve10(input);
32133
32185
  }
32134
32186
  function sourceStoreId(sourceDbPath) {
32135
- const digest = createHash11("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
32187
+ const digest = createHash12("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
32136
32188
  return `sqlite:${digest}`;
32137
32189
  }
32138
32190
  function inferSourceRepoPath(sourceDbPath) {
@@ -34094,7 +34146,7 @@ init_comments();
34094
34146
 
34095
34147
  // src/db/api-keys.ts
34096
34148
  init_database();
34097
- import { createHash as createHash12, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
34149
+ import { createHash as createHash13, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
34098
34150
  function rowToRecord(row) {
34099
34151
  return {
34100
34152
  id: row.id,
@@ -34108,7 +34160,7 @@ function rowToRecord(row) {
34108
34160
  };
34109
34161
  }
34110
34162
  function hashApiKey(key) {
34111
- return createHash12("sha256").update(key).digest("hex");
34163
+ return createHash13("sha256").update(key).digest("hex");
34112
34164
  }
34113
34165
  function safeEqualHex(a, b) {
34114
34166
  if (a.length !== b.length)
@@ -38553,7 +38605,7 @@ init_database();
38553
38605
  init_tasks();
38554
38606
  import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
38555
38607
  import { basename as basename5 } from "path";
38556
- import { createHash as createHash13 } from "crypto";
38608
+ import { createHash as createHash14 } from "crypto";
38557
38609
  init_secret_redaction();
38558
38610
  var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
38559
38611
  var INTAKE_SOURCE_TYPES = [
@@ -38566,7 +38618,7 @@ var INTAKE_SOURCE_TYPES = [
38566
38618
  ];
38567
38619
  var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
38568
38620
  function fingerprint2(text) {
38569
- return createHash13("sha256").update(text).digest("hex").slice(0, 16);
38621
+ return createHash14("sha256").update(text).digest("hex").slice(0, 16);
38570
38622
  }
38571
38623
  function loadRawContent(input) {
38572
38624
  if (input.github_url) {
@@ -43106,7 +43158,7 @@ var CLI_COMMAND_GROUPS = [
43106
43158
  commands: [
43107
43159
  { name: "completion", summary: "Shell completions", usage: "todos completion <bash|zsh|fish>", example: "todos completion bash >> ~/.bashrc" },
43108
43160
  { name: "docs", summary: "CLI reference and adapter docs", example: "todos docs cli" },
43109
- { name: "mcp", summary: "Register MCP server", flags: ["--claude", "--codex"] }
43161
+ { name: "mcp", summary: "Register MCP server", flags: ["--register <agent>", "--unregister <agent>", "--global"] }
43110
43162
  ]
43111
43163
  }
43112
43164
  ];
@@ -44026,7 +44078,7 @@ init_database();
44026
44078
  init_tasks();
44027
44079
  init_redaction();
44028
44080
  init_sync_utils();
44029
- import { createHash as createHash14 } from "crypto";
44081
+ import { createHash as createHash15 } from "crypto";
44030
44082
  import { existsSync as existsSync22, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
44031
44083
  import { hostname as hostname3, platform, arch } from "os";
44032
44084
  import { dirname as dirname15, join as join18, resolve as resolve17 } from "path";
@@ -44048,7 +44100,7 @@ var CONFIG_FILES = [
44048
44100
  "dashboard/vite.config.ts"
44049
44101
  ];
44050
44102
  function sha2566(value) {
44051
- return createHash14("sha256").update(value).digest("hex");
44103
+ return createHash15("sha256").update(value).digest("hex");
44052
44104
  }
44053
44105
  function fileRecord(root, relativePath) {
44054
44106
  const path = join18(root, relativePath);
@@ -44300,7 +44352,7 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
44300
44352
  init_database();
44301
44353
  init_projects();
44302
44354
  init_plans();
44303
- import { createHash as createHash15 } from "crypto";
44355
+ import { createHash as createHash16 } from "crypto";
44304
44356
  import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
44305
44357
  import { dirname as dirname16, join as join19 } from "path";
44306
44358
  var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
@@ -44356,7 +44408,7 @@ function rowToDecisionRecord(row) {
44356
44408
  }
44357
44409
  function stableSnapshotHash(payload) {
44358
44410
  const { captured_at: _capturedAt, ...rest } = payload;
44359
- return createHash15("sha256").update(JSON.stringify(rest)).digest("hex");
44411
+ return createHash16("sha256").update(JSON.stringify(rest)).digest("hex");
44360
44412
  }
44361
44413
  function createDecisionRecord(input, db) {
44362
44414
  const d = db || getDatabase();
@@ -48405,15 +48457,17 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
48405
48457
  const lastSyncedAt = parseTimestamp(existing.task.metadata?.["todos_updated_at"]);
48406
48458
  const localUpdatedAt = parseTimestamp(task2.updated_at);
48407
48459
  const remoteUpdatedAt = existing.mtimeMs;
48460
+ const remoteChanged = hasSyncFingerprintChanged(existing.task);
48461
+ const remoteChangedSinceSync = remoteChanged ?? Boolean(lastSyncedAt && remoteUpdatedAt && remoteUpdatedAt > lastSyncedAt);
48408
48462
  let recordConflict = false;
48409
- if (lastSyncedAt && localUpdatedAt && remoteUpdatedAt && localUpdatedAt > lastSyncedAt && remoteUpdatedAt > lastSyncedAt) {
48463
+ if (lastSyncedAt && localUpdatedAt && localUpdatedAt > lastSyncedAt && remoteChangedSinceSync) {
48410
48464
  if (prefer === "remote") {
48411
48465
  const conflict = {
48412
48466
  agent: "claude",
48413
48467
  direction: "push",
48414
48468
  prefer,
48415
48469
  local_updated_at: task2.updated_at,
48416
- remote_updated_at: new Date(remoteUpdatedAt).toISOString(),
48470
+ remote_updated_at: remoteUpdatedAt ? new Date(remoteUpdatedAt).toISOString() : undefined,
48417
48471
  detected_at: new Date().toISOString()
48418
48472
  };
48419
48473
  const newMeta = appendSyncConflict(task2.metadata, conflict);
@@ -48427,7 +48481,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
48427
48481
  updated.blocks = existing.task.blocks;
48428
48482
  updated.blockedBy = existing.task.blockedBy;
48429
48483
  updated.activeForm = existing.task.activeForm;
48430
- writeClaudeTask(dir, updated);
48484
+ writeClaudeTask(dir, withSyncFingerprint(updated));
48431
48485
  if (recordConflict) {
48432
48486
  const latest = getTask(task2.id);
48433
48487
  if (latest) {
@@ -48451,7 +48505,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
48451
48505
  prefixCounter++;
48452
48506
  ct.subject = formatPrefixedSubject(task2.title, prefixConfig.prefix, prefixCounter);
48453
48507
  }
48454
- writeClaudeTask(dir, ct);
48508
+ writeClaudeTask(dir, withSyncFingerprint(ct));
48455
48509
  const current = getTask(task2.id);
48456
48510
  if (current) {
48457
48511
  const newMeta = { ...current.metadata, claude_task_id: claudeId };
@@ -48577,7 +48631,7 @@ function writeAgentTask(dir, task2) {
48577
48631
  writeJsonFile(join24(dir, `${task2.id}.json`), task2);
48578
48632
  }
48579
48633
  function taskToAgentTask(task2, externalId, existingMeta) {
48580
- return {
48634
+ return withSyncFingerprint({
48581
48635
  id: externalId,
48582
48636
  title: task2.title,
48583
48637
  description: task2.description || "",
@@ -48592,7 +48646,7 @@ function taskToAgentTask(task2, externalId, existingMeta) {
48592
48646
  todos_updated_at: task2.updated_at,
48593
48647
  todos_version: task2.version
48594
48648
  }
48595
- };
48649
+ });
48596
48650
  }
48597
48651
  function metadataKey(agent) {
48598
48652
  return `${agent}_task_id`;
@@ -48626,15 +48680,17 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
48626
48680
  const lastSyncedAt = parseTimestamp(existing.task.metadata?.["todos_updated_at"]);
48627
48681
  const localUpdatedAt = parseTimestamp(task2.updated_at);
48628
48682
  const remoteUpdatedAt = existing.mtimeMs;
48683
+ const remoteChanged = hasSyncFingerprintChanged(existing.task);
48684
+ const remoteChangedSinceSync = remoteChanged ?? Boolean(lastSyncedAt && remoteUpdatedAt && remoteUpdatedAt > lastSyncedAt);
48629
48685
  let recordConflict = false;
48630
- if (lastSyncedAt && localUpdatedAt && remoteUpdatedAt && localUpdatedAt > lastSyncedAt && remoteUpdatedAt > lastSyncedAt) {
48686
+ if (lastSyncedAt && localUpdatedAt && localUpdatedAt > lastSyncedAt && remoteChangedSinceSync) {
48631
48687
  if (prefer === "remote") {
48632
48688
  const conflict = {
48633
48689
  agent,
48634
48690
  direction: "push",
48635
48691
  prefer,
48636
48692
  local_updated_at: task2.updated_at,
48637
- remote_updated_at: new Date(remoteUpdatedAt).toISOString(),
48693
+ remote_updated_at: remoteUpdatedAt ? new Date(remoteUpdatedAt).toISOString() : undefined,
48638
48694
  detected_at: new Date().toISOString()
48639
48695
  };
48640
48696
  const newMeta = appendSyncConflict(task2.metadata, conflict);
@@ -48842,7 +48898,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
48842
48898
  init_tasks();
48843
48899
  init_task_files();
48844
48900
  import { existsSync as existsSync27, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
48845
- import { createHash as createHash16 } from "crypto";
48901
+ import { createHash as createHash17 } from "crypto";
48846
48902
  import { relative as relative6, resolve as resolve18, join as join25 } from "path";
48847
48903
  var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
48848
48904
  var DEFAULT_EXTENSIONS = new Set([
@@ -48907,7 +48963,7 @@ var SKIP_DIRS2 = new Set([
48907
48963
  ".parcel-cache"
48908
48964
  ]);
48909
48965
  function stableHash(value) {
48910
- return createHash16("sha256").update(value).digest("hex");
48966
+ return createHash17("sha256").update(value).digest("hex");
48911
48967
  }
48912
48968
  function normalizePathForMatch(value) {
48913
48969
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -49803,7 +49859,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
49803
49859
  }
49804
49860
  // src/lib/agent-replay-simulator.ts
49805
49861
  init_redaction();
49806
- import { createHash as createHash17 } from "crypto";
49862
+ import { createHash as createHash18 } from "crypto";
49807
49863
  import { readFileSync as readFileSync24 } from "fs";
49808
49864
  function isObject(value) {
49809
49865
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -49825,7 +49881,7 @@ function stable2(value) {
49825
49881
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
49826
49882
  }
49827
49883
  function fingerprint3(value) {
49828
- return createHash17("sha256").update(JSON.stringify(stable2(value))).digest("hex");
49884
+ return createHash18("sha256").update(JSON.stringify(stable2(value))).digest("hex");
49829
49885
  }
49830
49886
  function unpackFixture(input) {
49831
49887
  if (!isObject(input))
@@ -50064,7 +50120,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
50064
50120
  }
50065
50121
  // src/lib/local-extensions.ts
50066
50122
  init_config2();
50067
- import { createHash as createHash18, createVerify } from "crypto";
50123
+ import { createHash as createHash19, createVerify } from "crypto";
50068
50124
  import { existsSync as existsSync28, readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync11 } from "fs";
50069
50125
  import { basename as basename6, join as join26, resolve as resolve19 } from "path";
50070
50126
  init_redaction();
@@ -50152,7 +50208,7 @@ function parseJson2(path) {
50152
50208
  return JSON.parse(readFileSync25(path, "utf8"));
50153
50209
  }
50154
50210
  function sha2567(bytes) {
50155
- return `sha256:${createHash18("sha256").update(bytes).digest("hex")}`;
50211
+ return `sha256:${createHash19("sha256").update(bytes).digest("hex")}`;
50156
50212
  }
50157
50213
  function compareVersions(a, b) {
50158
50214
  const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tasks.d.ts","sourceRoot":"","sources":["../../src/lib/agent-tasks.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwD9D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAuFZ;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAoFZ;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAQZ"}
1
+ {"version":3,"file":"agent-tasks.d.ts","sourceRoot":"","sources":["../../src/lib/agent-tasks.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwD9D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CA2FZ;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAoFZ;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,UAAU,CAAA;CAAO,GACpC,UAAU,CAQZ"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Builds the {@link AssigneeContext} that `validateAssignee` consumes, and
3
+ * caches it briefly.
4
+ *
5
+ * `assignee-validation.ts` is deliberately pure and does no IO. This module is
6
+ * the IO half, kept separate so the validator stays trivially testable.
7
+ *
8
+ * WHY THE CACHE EXISTS. Validation needs the agent roster, and on this fleet
9
+ * that is a ~710KB / ~2.4s fetch against the cloud store. The one-shot CLI pays
10
+ * it once and does not care. The MCP server is LONG-LIVED and validates on
11
+ * every create/update/upsert, so a 50-task bulk upsert — the loop-driven path —
12
+ * would refetch ~35MB. Found in adversarial review of todos 056f3597.
13
+ *
14
+ * The TTL is deliberately short. An agent that registers during the window is
15
+ * invisible for at most that long, and the only consequence is a WARNING on an
16
+ * assignee that is in fact valid — never a refusal, because the refusing tiers
17
+ * (seat, ambiguous) cannot be created by a registration the cache missed:
18
+ * a new seat requires an owner ruling and a roster edit, and a name becoming
19
+ * ambiguous only ever ADDS a row, which at worst delays the refusal by the TTL.
20
+ */
21
+ import type { AssigneeContext } from "./assignee-validation.js";
22
+ /** Drop the cache. For tests, and for any caller that has just mutated agents. */
23
+ export declare function resetAssigneeContextCache(): void;
24
+ /**
25
+ * Fetch (or reuse) the agent roster and seat list.
26
+ *
27
+ * `listAgentsFn` is injected rather than imported so this module does not pull
28
+ * `bun:sqlite` into every consumer's module graph — the same constraint that
29
+ * keeps `agent-name-normalize.ts` import-free.
30
+ */
31
+ export declare function loadAssigneeContext(listAgentsFn: () => Promise<Array<{
32
+ id: string;
33
+ name: string;
34
+ }>> | Array<{
35
+ id: string;
36
+ name: string;
37
+ }>, allowSeat: boolean, nowMs?: number): Promise<AssigneeContext & {
38
+ degraded: boolean;
39
+ }>;
40
+ //# sourceMappingURL=assignee-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignee-context.d.ts","sourceRoot":"","sources":["../../src/lib/assignee-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,0BAA0B,CAAC;AAgB5E,kFAAkF;AAClF,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GAAG,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACtG,SAAS,EAAE,OAAO,EAClB,KAAK,GAAE,MAAmB,GACzB,OAAO,CAAC,eAAe,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAkClD"}