@hasna/todos 0.13.7 → 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/cli/assignee-guard.d.ts +13 -0
- package/dist/cli/assignee-guard.d.ts.map +1 -0
- package/dist/cli/claim-guard.d.ts +3 -0
- package/dist/cli/claim-guard.d.ts.map +1 -0
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +3226 -3020
- package/dist/contracts.js +77 -29
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/index.js +110 -58
- package/dist/lib/agent-tasks.d.ts.map +1 -1
- package/dist/lib/assignee-context.d.ts +40 -0
- package/dist/lib/assignee-context.d.ts.map +1 -0
- package/dist/lib/assignee-validation.d.ts +72 -0
- package/dist/lib/assignee-validation.d.ts.map +1 -0
- package/dist/lib/claude-tasks.d.ts.map +1 -1
- package/dist/lib/sync-utils.d.ts +7 -0
- package/dist/lib/sync-utils.d.ts.map +1 -1
- package/dist/mcp/index.js +234 -115
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +77 -29
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +238 -119
- package/dist/storage.js +62 -14
- package/package.json +1 -1
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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();
|
|
@@ -10490,7 +10538,7 @@ var init_boards = __esm(() => {
|
|
|
10490
10538
|
});
|
|
10491
10539
|
|
|
10492
10540
|
// src/lib/artifact-store.ts
|
|
10493
|
-
import { createHash as
|
|
10541
|
+
import { createHash as createHash3 } from "crypto";
|
|
10494
10542
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10495
10543
|
import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
|
|
10496
10544
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -10515,7 +10563,7 @@ function artifactStorePath(relativePath) {
|
|
|
10515
10563
|
return join5(artifactStoreRoot(), normalized);
|
|
10516
10564
|
}
|
|
10517
10565
|
function sha256(buffer) {
|
|
10518
|
-
return
|
|
10566
|
+
return createHash3("sha256").update(buffer).digest("hex");
|
|
10519
10567
|
}
|
|
10520
10568
|
function isTextLike(buffer, path) {
|
|
10521
10569
|
if (buffer.includes(0))
|
|
@@ -12038,7 +12086,7 @@ var init_dispatches = __esm(() => {
|
|
|
12038
12086
|
// package.json
|
|
12039
12087
|
var package_default = {
|
|
12040
12088
|
name: "@hasna/todos",
|
|
12041
|
-
version: "0.13.
|
|
12089
|
+
version: "0.13.8",
|
|
12042
12090
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12043
12091
|
type: "module",
|
|
12044
12092
|
main: "dist/index.js",
|
|
@@ -15753,7 +15801,7 @@ function importOnboardingFixture(options = {}) {
|
|
|
15753
15801
|
}
|
|
15754
15802
|
// src/lib/local-backups.ts
|
|
15755
15803
|
init_database();
|
|
15756
|
-
import { createHash as
|
|
15804
|
+
import { createHash as createHash4 } from "crypto";
|
|
15757
15805
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
15758
15806
|
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
15759
15807
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
@@ -15771,7 +15819,7 @@ function stableJson(value) {
|
|
|
15771
15819
|
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
15772
15820
|
}
|
|
15773
15821
|
function sha2562(value) {
|
|
15774
|
-
return
|
|
15822
|
+
return createHash4("sha256").update(stableJson(value)).digest("hex");
|
|
15775
15823
|
}
|
|
15776
15824
|
function sqliteIntegrity(db) {
|
|
15777
15825
|
let quick = "unknown";
|
|
@@ -16049,7 +16097,7 @@ function checkLocalIntegrity(options = {}, db) {
|
|
|
16049
16097
|
}
|
|
16050
16098
|
// src/lib/local-snapshots.ts
|
|
16051
16099
|
init_database();
|
|
16052
|
-
import { createHash as
|
|
16100
|
+
import { createHash as createHash5 } from "crypto";
|
|
16053
16101
|
|
|
16054
16102
|
// src/lib/activity-timeline.ts
|
|
16055
16103
|
init_database();
|
|
@@ -16325,7 +16373,7 @@ function stable(value) {
|
|
|
16325
16373
|
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stable(item)]));
|
|
16326
16374
|
}
|
|
16327
16375
|
function sha2563(value) {
|
|
16328
|
-
return
|
|
16376
|
+
return createHash5("sha256").update(JSON.stringify(stable(value))).digest("hex");
|
|
16329
16377
|
}
|
|
16330
16378
|
function latestTimestamp(items, fallback) {
|
|
16331
16379
|
const timestamps = [];
|
|
@@ -19537,20 +19585,20 @@ init_database();
|
|
|
19537
19585
|
init_task_runs();
|
|
19538
19586
|
init_config2();
|
|
19539
19587
|
init_redaction();
|
|
19540
|
-
import { createHash as
|
|
19588
|
+
import { createHash as createHash6 } from "crypto";
|
|
19541
19589
|
var LOCAL_AUDIT_LEDGER_SCHEMA_VERSION = 1;
|
|
19542
19590
|
var LOCAL_AUDIT_LEDGER_HASH_ALGORITHM = "sha256";
|
|
19543
19591
|
var LOCAL_AUDIT_LEDGER_INITIAL_HASH = "0".repeat(64);
|
|
19544
|
-
function
|
|
19592
|
+
function canonicalize2(value) {
|
|
19545
19593
|
if (value === null || typeof value !== "object")
|
|
19546
19594
|
return JSON.stringify(value);
|
|
19547
19595
|
if (Array.isArray(value))
|
|
19548
|
-
return `[${value.map(
|
|
19596
|
+
return `[${value.map(canonicalize2).join(",")}]`;
|
|
19549
19597
|
const object = value;
|
|
19550
|
-
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${
|
|
19598
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize2(object[key])}`).join(",")}}`;
|
|
19551
19599
|
}
|
|
19552
19600
|
function hash(value) {
|
|
19553
|
-
return
|
|
19601
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
19554
19602
|
}
|
|
19555
19603
|
function parsePayload2(value) {
|
|
19556
19604
|
if (!value)
|
|
@@ -19683,7 +19731,7 @@ function toLedgerEntries(rows) {
|
|
|
19683
19731
|
let previous = LOCAL_AUDIT_LEDGER_INITIAL_HASH;
|
|
19684
19732
|
return ordered.map((row, index) => {
|
|
19685
19733
|
const payload = parsePayload2(row.payload_json);
|
|
19686
|
-
const payloadHash = hash(
|
|
19734
|
+
const payloadHash = hash(canonicalize2({ source: row.source, source_id: row.source_id, created_at: row.created_at, payload }));
|
|
19687
19735
|
const chainHash = hash(`${previous}
|
|
19688
19736
|
${payloadHash}`);
|
|
19689
19737
|
const entry = {
|
|
@@ -20070,7 +20118,7 @@ function renderReleaseCompatibilityMarkdown(report) {
|
|
|
20070
20118
|
`);
|
|
20071
20119
|
}
|
|
20072
20120
|
// src/db/inbox.ts
|
|
20073
|
-
import { createHash as
|
|
20121
|
+
import { createHash as createHash7 } from "crypto";
|
|
20074
20122
|
|
|
20075
20123
|
// src/lib/github.ts
|
|
20076
20124
|
import { execFileSync } from "child_process";
|
|
@@ -20155,7 +20203,7 @@ function compactWhitespace(value) {
|
|
|
20155
20203
|
function fingerprintInboxInput(input) {
|
|
20156
20204
|
const sourceType = input.source_type || detectInboxSourceType(input.body, input.source_url);
|
|
20157
20205
|
const normalized = compactWhitespace(sanitizePreWriteText(input.body, "inbox.fingerprint")).slice(0, 8000);
|
|
20158
|
-
return
|
|
20206
|
+
return createHash7("sha256").update(`${sourceType}
|
|
20159
20207
|
${input.source_url || ""}
|
|
20160
20208
|
${normalized}`).digest("hex");
|
|
20161
20209
|
}
|
|
@@ -21272,7 +21320,7 @@ function importExternalIssues(input, db) {
|
|
|
21272
21320
|
init_database();
|
|
21273
21321
|
init_tasks();
|
|
21274
21322
|
init_redaction();
|
|
21275
|
-
import { createHash as
|
|
21323
|
+
import { createHash as createHash8 } from "crypto";
|
|
21276
21324
|
var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1";
|
|
21277
21325
|
var TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1";
|
|
21278
21326
|
var TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1";
|
|
@@ -21461,7 +21509,7 @@ function fingerprintTesterIssueReport(report) {
|
|
|
21461
21509
|
normalizeText2(report.failure?.message || report.summary || report.title).slice(0, 240),
|
|
21462
21510
|
normalizeText2(stackTop).slice(0, 160)
|
|
21463
21511
|
].join("::");
|
|
21464
|
-
return `testers:${
|
|
21512
|
+
return `testers:${createHash8("sha256").update(raw).digest("hex").slice(0, 16)}`;
|
|
21465
21513
|
}
|
|
21466
21514
|
function priorityForSeverity(severity, fallback) {
|
|
21467
21515
|
return PRIORITIES3.includes(severity) ? severity : fallback;
|
|
@@ -22913,7 +22961,7 @@ function renderLocalReportMarkdown(report) {
|
|
|
22913
22961
|
init_config2();
|
|
22914
22962
|
init_redaction();
|
|
22915
22963
|
init_prewrite_secrets();
|
|
22916
|
-
import { createCipheriv, createDecipheriv, createHash as
|
|
22964
|
+
import { createCipheriv, createDecipheriv, createHash as createHash9, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
22917
22965
|
var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value";
|
|
22918
22966
|
var TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge";
|
|
22919
22967
|
var TODOS_ENCRYPTION_SCHEMA_VERSION = 1;
|
|
@@ -22939,7 +22987,7 @@ function now3() {
|
|
|
22939
22987
|
return new Date().toISOString();
|
|
22940
22988
|
}
|
|
22941
22989
|
function sha2564(value) {
|
|
22942
|
-
return
|
|
22990
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
22943
22991
|
}
|
|
22944
22992
|
function normalizeProfileName(value) {
|
|
22945
22993
|
const name = (value || DEFAULT_ENCRYPTION_PROFILE).trim();
|
|
@@ -28253,7 +28301,7 @@ function isCommentRedactionBackfillComplete(result) {
|
|
|
28253
28301
|
return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
|
|
28254
28302
|
}
|
|
28255
28303
|
// src/storage/s3-artifacts.ts
|
|
28256
|
-
import { createHash as
|
|
28304
|
+
import { createHash as createHash10, createHmac as createHmac2 } from "crypto";
|
|
28257
28305
|
function createTodosS3ArtifactStore(options) {
|
|
28258
28306
|
const requestFetch = options.fetch ?? fetch;
|
|
28259
28307
|
const now4 = options.now ?? (() => new Date);
|
|
@@ -28425,7 +28473,7 @@ function toAmzDate(date) {
|
|
|
28425
28473
|
return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
28426
28474
|
}
|
|
28427
28475
|
function sha256Hex(value) {
|
|
28428
|
-
return
|
|
28476
|
+
return createHash10("sha256").update(value).digest("hex");
|
|
28429
28477
|
}
|
|
28430
28478
|
function hmac(key, value) {
|
|
28431
28479
|
return createHmac2("sha256", key).update(value).digest();
|
|
@@ -29272,7 +29320,7 @@ function createClient(options) {
|
|
|
29272
29320
|
init_database();
|
|
29273
29321
|
|
|
29274
29322
|
// src/pr-groups/ledger.ts
|
|
29275
|
-
import { createHash as
|
|
29323
|
+
import { createHash as createHash11 } from "crypto";
|
|
29276
29324
|
|
|
29277
29325
|
// src/pr-groups/types.ts
|
|
29278
29326
|
var PR_GROUP_LEDGER_SCHEMA_VERSION = 1;
|
|
@@ -29335,7 +29383,7 @@ var RECEIPT_EVENT_TYPES = new Set([
|
|
|
29335
29383
|
"merge_outcome"
|
|
29336
29384
|
]);
|
|
29337
29385
|
function sha2565(value) {
|
|
29338
|
-
return
|
|
29386
|
+
return createHash11("sha256").update(value).digest("hex");
|
|
29339
29387
|
}
|
|
29340
29388
|
function stableValue(value) {
|
|
29341
29389
|
if (Array.isArray(value))
|
|
@@ -31853,7 +31901,7 @@ init_task_lifecycle();
|
|
|
31853
31901
|
init_task_crud();
|
|
31854
31902
|
init_redaction();
|
|
31855
31903
|
import { Database as Database3 } from "bun:sqlite";
|
|
31856
|
-
import { createHash as
|
|
31904
|
+
import { createHash as createHash12 } from "crypto";
|
|
31857
31905
|
import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
|
|
31858
31906
|
import { basename as basename2, dirname as dirname6, join as join9, resolve as resolve10 } from "path";
|
|
31859
31907
|
|
|
@@ -32136,7 +32184,7 @@ function normalizePath3(input) {
|
|
|
32136
32184
|
return resolve10(input);
|
|
32137
32185
|
}
|
|
32138
32186
|
function sourceStoreId(sourceDbPath) {
|
|
32139
|
-
const digest =
|
|
32187
|
+
const digest = createHash12("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
|
|
32140
32188
|
return `sqlite:${digest}`;
|
|
32141
32189
|
}
|
|
32142
32190
|
function inferSourceRepoPath(sourceDbPath) {
|
|
@@ -34098,7 +34146,7 @@ init_comments();
|
|
|
34098
34146
|
|
|
34099
34147
|
// src/db/api-keys.ts
|
|
34100
34148
|
init_database();
|
|
34101
|
-
import { createHash as
|
|
34149
|
+
import { createHash as createHash13, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
34102
34150
|
function rowToRecord(row) {
|
|
34103
34151
|
return {
|
|
34104
34152
|
id: row.id,
|
|
@@ -34112,7 +34160,7 @@ function rowToRecord(row) {
|
|
|
34112
34160
|
};
|
|
34113
34161
|
}
|
|
34114
34162
|
function hashApiKey(key) {
|
|
34115
|
-
return
|
|
34163
|
+
return createHash13("sha256").update(key).digest("hex");
|
|
34116
34164
|
}
|
|
34117
34165
|
function safeEqualHex(a, b) {
|
|
34118
34166
|
if (a.length !== b.length)
|
|
@@ -38557,7 +38605,7 @@ init_database();
|
|
|
38557
38605
|
init_tasks();
|
|
38558
38606
|
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
38559
38607
|
import { basename as basename5 } from "path";
|
|
38560
|
-
import { createHash as
|
|
38608
|
+
import { createHash as createHash14 } from "crypto";
|
|
38561
38609
|
init_secret_redaction();
|
|
38562
38610
|
var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
|
|
38563
38611
|
var INTAKE_SOURCE_TYPES = [
|
|
@@ -38570,7 +38618,7 @@ var INTAKE_SOURCE_TYPES = [
|
|
|
38570
38618
|
];
|
|
38571
38619
|
var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
|
|
38572
38620
|
function fingerprint2(text) {
|
|
38573
|
-
return
|
|
38621
|
+
return createHash14("sha256").update(text).digest("hex").slice(0, 16);
|
|
38574
38622
|
}
|
|
38575
38623
|
function loadRawContent(input) {
|
|
38576
38624
|
if (input.github_url) {
|
|
@@ -43110,7 +43158,7 @@ var CLI_COMMAND_GROUPS = [
|
|
|
43110
43158
|
commands: [
|
|
43111
43159
|
{ name: "completion", summary: "Shell completions", usage: "todos completion <bash|zsh|fish>", example: "todos completion bash >> ~/.bashrc" },
|
|
43112
43160
|
{ name: "docs", summary: "CLI reference and adapter docs", example: "todos docs cli" },
|
|
43113
|
-
{ name: "mcp", summary: "Register MCP server", flags: ["--
|
|
43161
|
+
{ name: "mcp", summary: "Register MCP server", flags: ["--register <agent>", "--unregister <agent>", "--global"] }
|
|
43114
43162
|
]
|
|
43115
43163
|
}
|
|
43116
43164
|
];
|
|
@@ -44030,7 +44078,7 @@ init_database();
|
|
|
44030
44078
|
init_tasks();
|
|
44031
44079
|
init_redaction();
|
|
44032
44080
|
init_sync_utils();
|
|
44033
|
-
import { createHash as
|
|
44081
|
+
import { createHash as createHash15 } from "crypto";
|
|
44034
44082
|
import { existsSync as existsSync22, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
|
|
44035
44083
|
import { hostname as hostname3, platform, arch } from "os";
|
|
44036
44084
|
import { dirname as dirname15, join as join18, resolve as resolve17 } from "path";
|
|
@@ -44052,7 +44100,7 @@ var CONFIG_FILES = [
|
|
|
44052
44100
|
"dashboard/vite.config.ts"
|
|
44053
44101
|
];
|
|
44054
44102
|
function sha2566(value) {
|
|
44055
|
-
return
|
|
44103
|
+
return createHash15("sha256").update(value).digest("hex");
|
|
44056
44104
|
}
|
|
44057
44105
|
function fileRecord(root, relativePath) {
|
|
44058
44106
|
const path = join18(root, relativePath);
|
|
@@ -44304,7 +44352,7 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
|
|
|
44304
44352
|
init_database();
|
|
44305
44353
|
init_projects();
|
|
44306
44354
|
init_plans();
|
|
44307
|
-
import { createHash as
|
|
44355
|
+
import { createHash as createHash16 } from "crypto";
|
|
44308
44356
|
import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
44309
44357
|
import { dirname as dirname16, join as join19 } from "path";
|
|
44310
44358
|
var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
|
|
@@ -44360,7 +44408,7 @@ function rowToDecisionRecord(row) {
|
|
|
44360
44408
|
}
|
|
44361
44409
|
function stableSnapshotHash(payload) {
|
|
44362
44410
|
const { captured_at: _capturedAt, ...rest } = payload;
|
|
44363
|
-
return
|
|
44411
|
+
return createHash16("sha256").update(JSON.stringify(rest)).digest("hex");
|
|
44364
44412
|
}
|
|
44365
44413
|
function createDecisionRecord(input, db) {
|
|
44366
44414
|
const d = db || getDatabase();
|
|
@@ -48409,15 +48457,17 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
48409
48457
|
const lastSyncedAt = parseTimestamp(existing.task.metadata?.["todos_updated_at"]);
|
|
48410
48458
|
const localUpdatedAt = parseTimestamp(task2.updated_at);
|
|
48411
48459
|
const remoteUpdatedAt = existing.mtimeMs;
|
|
48460
|
+
const remoteChanged = hasSyncFingerprintChanged(existing.task);
|
|
48461
|
+
const remoteChangedSinceSync = remoteChanged ?? Boolean(lastSyncedAt && remoteUpdatedAt && remoteUpdatedAt > lastSyncedAt);
|
|
48412
48462
|
let recordConflict = false;
|
|
48413
|
-
if (lastSyncedAt && localUpdatedAt &&
|
|
48463
|
+
if (lastSyncedAt && localUpdatedAt && localUpdatedAt > lastSyncedAt && remoteChangedSinceSync) {
|
|
48414
48464
|
if (prefer === "remote") {
|
|
48415
48465
|
const conflict = {
|
|
48416
48466
|
agent: "claude",
|
|
48417
48467
|
direction: "push",
|
|
48418
48468
|
prefer,
|
|
48419
48469
|
local_updated_at: task2.updated_at,
|
|
48420
|
-
remote_updated_at: new Date(remoteUpdatedAt).toISOString(),
|
|
48470
|
+
remote_updated_at: remoteUpdatedAt ? new Date(remoteUpdatedAt).toISOString() : undefined,
|
|
48421
48471
|
detected_at: new Date().toISOString()
|
|
48422
48472
|
};
|
|
48423
48473
|
const newMeta = appendSyncConflict(task2.metadata, conflict);
|
|
@@ -48431,7 +48481,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
48431
48481
|
updated.blocks = existing.task.blocks;
|
|
48432
48482
|
updated.blockedBy = existing.task.blockedBy;
|
|
48433
48483
|
updated.activeForm = existing.task.activeForm;
|
|
48434
|
-
writeClaudeTask(dir, updated);
|
|
48484
|
+
writeClaudeTask(dir, withSyncFingerprint(updated));
|
|
48435
48485
|
if (recordConflict) {
|
|
48436
48486
|
const latest = getTask(task2.id);
|
|
48437
48487
|
if (latest) {
|
|
@@ -48455,7 +48505,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
48455
48505
|
prefixCounter++;
|
|
48456
48506
|
ct.subject = formatPrefixedSubject(task2.title, prefixConfig.prefix, prefixCounter);
|
|
48457
48507
|
}
|
|
48458
|
-
writeClaudeTask(dir, ct);
|
|
48508
|
+
writeClaudeTask(dir, withSyncFingerprint(ct));
|
|
48459
48509
|
const current = getTask(task2.id);
|
|
48460
48510
|
if (current) {
|
|
48461
48511
|
const newMeta = { ...current.metadata, claude_task_id: claudeId };
|
|
@@ -48581,7 +48631,7 @@ function writeAgentTask(dir, task2) {
|
|
|
48581
48631
|
writeJsonFile(join24(dir, `${task2.id}.json`), task2);
|
|
48582
48632
|
}
|
|
48583
48633
|
function taskToAgentTask(task2, externalId, existingMeta) {
|
|
48584
|
-
return {
|
|
48634
|
+
return withSyncFingerprint({
|
|
48585
48635
|
id: externalId,
|
|
48586
48636
|
title: task2.title,
|
|
48587
48637
|
description: task2.description || "",
|
|
@@ -48596,7 +48646,7 @@ function taskToAgentTask(task2, externalId, existingMeta) {
|
|
|
48596
48646
|
todos_updated_at: task2.updated_at,
|
|
48597
48647
|
todos_version: task2.version
|
|
48598
48648
|
}
|
|
48599
|
-
};
|
|
48649
|
+
});
|
|
48600
48650
|
}
|
|
48601
48651
|
function metadataKey(agent) {
|
|
48602
48652
|
return `${agent}_task_id`;
|
|
@@ -48630,15 +48680,17 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
48630
48680
|
const lastSyncedAt = parseTimestamp(existing.task.metadata?.["todos_updated_at"]);
|
|
48631
48681
|
const localUpdatedAt = parseTimestamp(task2.updated_at);
|
|
48632
48682
|
const remoteUpdatedAt = existing.mtimeMs;
|
|
48683
|
+
const remoteChanged = hasSyncFingerprintChanged(existing.task);
|
|
48684
|
+
const remoteChangedSinceSync = remoteChanged ?? Boolean(lastSyncedAt && remoteUpdatedAt && remoteUpdatedAt > lastSyncedAt);
|
|
48633
48685
|
let recordConflict = false;
|
|
48634
|
-
if (lastSyncedAt && localUpdatedAt &&
|
|
48686
|
+
if (lastSyncedAt && localUpdatedAt && localUpdatedAt > lastSyncedAt && remoteChangedSinceSync) {
|
|
48635
48687
|
if (prefer === "remote") {
|
|
48636
48688
|
const conflict = {
|
|
48637
48689
|
agent,
|
|
48638
48690
|
direction: "push",
|
|
48639
48691
|
prefer,
|
|
48640
48692
|
local_updated_at: task2.updated_at,
|
|
48641
|
-
remote_updated_at: new Date(remoteUpdatedAt).toISOString(),
|
|
48693
|
+
remote_updated_at: remoteUpdatedAt ? new Date(remoteUpdatedAt).toISOString() : undefined,
|
|
48642
48694
|
detected_at: new Date().toISOString()
|
|
48643
48695
|
};
|
|
48644
48696
|
const newMeta = appendSyncConflict(task2.metadata, conflict);
|
|
@@ -48846,7 +48898,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
48846
48898
|
init_tasks();
|
|
48847
48899
|
init_task_files();
|
|
48848
48900
|
import { existsSync as existsSync27, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
|
|
48849
|
-
import { createHash as
|
|
48901
|
+
import { createHash as createHash17 } from "crypto";
|
|
48850
48902
|
import { relative as relative6, resolve as resolve18, join as join25 } from "path";
|
|
48851
48903
|
var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
|
|
48852
48904
|
var DEFAULT_EXTENSIONS = new Set([
|
|
@@ -48911,7 +48963,7 @@ var SKIP_DIRS2 = new Set([
|
|
|
48911
48963
|
".parcel-cache"
|
|
48912
48964
|
]);
|
|
48913
48965
|
function stableHash(value) {
|
|
48914
|
-
return
|
|
48966
|
+
return createHash17("sha256").update(value).digest("hex");
|
|
48915
48967
|
}
|
|
48916
48968
|
function normalizePathForMatch(value) {
|
|
48917
48969
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -49807,7 +49859,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
|
|
|
49807
49859
|
}
|
|
49808
49860
|
// src/lib/agent-replay-simulator.ts
|
|
49809
49861
|
init_redaction();
|
|
49810
|
-
import { createHash as
|
|
49862
|
+
import { createHash as createHash18 } from "crypto";
|
|
49811
49863
|
import { readFileSync as readFileSync24 } from "fs";
|
|
49812
49864
|
function isObject(value) {
|
|
49813
49865
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -49829,7 +49881,7 @@ function stable2(value) {
|
|
|
49829
49881
|
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
|
|
49830
49882
|
}
|
|
49831
49883
|
function fingerprint3(value) {
|
|
49832
|
-
return
|
|
49884
|
+
return createHash18("sha256").update(JSON.stringify(stable2(value))).digest("hex");
|
|
49833
49885
|
}
|
|
49834
49886
|
function unpackFixture(input) {
|
|
49835
49887
|
if (!isObject(input))
|
|
@@ -50068,7 +50120,7 @@ function renderAgentReplaySimulationMarkdown(simulation) {
|
|
|
50068
50120
|
}
|
|
50069
50121
|
// src/lib/local-extensions.ts
|
|
50070
50122
|
init_config2();
|
|
50071
|
-
import { createHash as
|
|
50123
|
+
import { createHash as createHash19, createVerify } from "crypto";
|
|
50072
50124
|
import { existsSync as existsSync28, readdirSync as readdirSync6, readFileSync as readFileSync25, statSync as statSync11 } from "fs";
|
|
50073
50125
|
import { basename as basename6, join as join26, resolve as resolve19 } from "path";
|
|
50074
50126
|
init_redaction();
|
|
@@ -50156,7 +50208,7 @@ function parseJson2(path) {
|
|
|
50156
50208
|
return JSON.parse(readFileSync25(path, "utf8"));
|
|
50157
50209
|
}
|
|
50158
50210
|
function sha2567(bytes) {
|
|
50159
|
-
return `sha256:${
|
|
50211
|
+
return `sha256:${createHash19("sha256").update(bytes).digest("hex")}`;
|
|
50160
50212
|
}
|
|
50161
50213
|
function compareVersions(a, b) {
|
|
50162
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":"
|
|
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"}
|