@hasna/todos 0.15.47 → 0.15.50
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/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/index.js +296 -81
- package/dist/contracts.js +56 -5
- package/dist/db/task-crud.d.ts +6 -0
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +187 -70
- package/dist/lib/dedupe-projection.d.ts +48 -0
- package/dist/lib/dedupe-projection.d.ts.map +1 -0
- package/dist/lib/redaction.d.ts.map +1 -1
- package/dist/lib/task-dedupe.d.ts +5 -9
- package/dist/lib/task-dedupe.d.ts.map +1 -1
- package/dist/mcp/index.js +246 -80
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +3 -2
- package/dist/project-registration.js +128 -68
- package/dist/registry.js +56 -5
- package/dist/release-provenance.json +5 -5
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +248 -82
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-errors.d.ts +15 -0
- package/dist/storage/postgres-errors.d.ts.map +1 -0
- package/dist/storage/postgres-sync.d.ts +7 -0
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage/shadow-outbox.d.ts.map +1 -1
- package/dist/storage/shadow.d.ts.map +1 -1
- package/dist/storage.js +139 -66
- package/dist/task-manifest.js +118 -47
- package/dist/task-subtree-transfer.js +326 -44
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -623,13 +623,15 @@ function upsertSecretSafetyConfig(input) {
|
|
|
623
623
|
saveConfig({ ...config, secret_safety: next });
|
|
624
624
|
return next;
|
|
625
625
|
}
|
|
626
|
-
var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS, REDACTION_PLACEHOLDER;
|
|
626
|
+
var XAI_PREFIX, DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS, REDACTION_PLACEHOLDER;
|
|
627
627
|
var init_redaction = __esm(() => {
|
|
628
628
|
init_config();
|
|
629
|
+
XAI_PREFIX = ["x", "ai", "-"].join("");
|
|
629
630
|
DEFAULT_SECRET_PATTERNS = [
|
|
630
631
|
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
631
632
|
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
632
633
|
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
634
|
+
{ name: `${XAI_PREFIX}token`, regex: /\bxai[-][A-Za-z0-9]{20,80}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
633
635
|
{ name: "npm-token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_NPM_TOKEN]" },
|
|
634
636
|
{ name: "github-fine-grained-token", regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
635
637
|
{ name: "github-token", regex: /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
@@ -9933,6 +9935,12 @@ function listTasks2(filter = {}, db) {
|
|
|
9933
9935
|
const d = db || getDatabase();
|
|
9934
9936
|
const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
|
|
9935
9937
|
clearExpiredLocks2(d);
|
|
9938
|
+
if (filter.limit !== undefined) {
|
|
9939
|
+
const limit = filter.limit;
|
|
9940
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || limit > MAX_TASK_LIST_LIMIT) {
|
|
9941
|
+
throw new TypeError(`listTasks limit must be a positive integer between 1 and ${MAX_TASK_LIST_LIMIT}; got ${String(limit)}`);
|
|
9942
|
+
}
|
|
9943
|
+
}
|
|
9936
9944
|
const conditions = [];
|
|
9937
9945
|
const params = [];
|
|
9938
9946
|
if (filter.project_id) {
|
|
@@ -10466,6 +10474,7 @@ function deleteTask(id, db) {
|
|
|
10466
10474
|
}
|
|
10467
10475
|
return result.changes > 0;
|
|
10468
10476
|
}
|
|
10477
|
+
var MAX_TASK_LIST_LIMIT = 1e5;
|
|
10469
10478
|
var init_task_crud = __esm(() => {
|
|
10470
10479
|
init_types();
|
|
10471
10480
|
init_database();
|
|
@@ -13074,7 +13083,7 @@ import { createHash as createHash7 } from "crypto";
|
|
|
13074
13083
|
// package.json
|
|
13075
13084
|
var package_default = {
|
|
13076
13085
|
name: "@hasna/todos",
|
|
13077
|
-
version: "0.15.
|
|
13086
|
+
version: "0.15.50",
|
|
13078
13087
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
13079
13088
|
type: "module",
|
|
13080
13089
|
main: "dist/index.js",
|
|
@@ -13138,6 +13147,7 @@ var package_default = {
|
|
|
13138
13147
|
],
|
|
13139
13148
|
scripts: {
|
|
13140
13149
|
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
13150
|
+
"build:js": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
13141
13151
|
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
13142
13152
|
migrate: "bun run src/server/index.ts migrate",
|
|
13143
13153
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
@@ -13192,7 +13202,7 @@ var package_default = {
|
|
|
13192
13202
|
author: "Andrei Hasna <andrei@hasna.com>",
|
|
13193
13203
|
license: "Apache-2.0",
|
|
13194
13204
|
dependencies: {
|
|
13195
|
-
"@hasna/contracts": "0.
|
|
13205
|
+
"@hasna/contracts": "0.14.0",
|
|
13196
13206
|
"@hasna/events": "^0.1.11",
|
|
13197
13207
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
13198
13208
|
chalk: "^5.4.1",
|
|
@@ -13341,6 +13351,30 @@ function assertPlanProjectLinkReceipt(value) {
|
|
|
13341
13351
|
// src/storage/postgres-adapter.ts
|
|
13342
13352
|
init_stale_lock_handoff();
|
|
13343
13353
|
|
|
13354
|
+
// src/storage/postgres-sync.ts
|
|
13355
|
+
init_types();
|
|
13356
|
+
|
|
13357
|
+
// src/storage/postgres-errors.ts
|
|
13358
|
+
function isPostgresUniqueViolation(error) {
|
|
13359
|
+
if (typeof error !== "object" || error === null)
|
|
13360
|
+
return false;
|
|
13361
|
+
const candidate = error;
|
|
13362
|
+
const states = [candidate.code, candidate.errno, candidate.sqlState, candidate.sqlstate];
|
|
13363
|
+
if (typeof candidate.cause === "object" && candidate.cause !== null) {
|
|
13364
|
+
const cause = candidate.cause;
|
|
13365
|
+
states.push(cause.code, cause.errno, cause.sqlState, cause.sqlstate);
|
|
13366
|
+
}
|
|
13367
|
+
return states.some((state) => String(state) === "23505");
|
|
13368
|
+
}
|
|
13369
|
+
function postgresConstraintName(error) {
|
|
13370
|
+
if (typeof error !== "object" || error === null)
|
|
13371
|
+
return "";
|
|
13372
|
+
const candidate = error;
|
|
13373
|
+
const cause = typeof candidate.cause === "object" && candidate.cause !== null ? candidate.cause : undefined;
|
|
13374
|
+
const constraint = candidate.constraint ?? candidate.constraint_name ?? cause?.constraint ?? cause?.constraint_name;
|
|
13375
|
+
return typeof constraint === "string" ? constraint : "";
|
|
13376
|
+
}
|
|
13377
|
+
|
|
13344
13378
|
// src/storage/postgres-sync.ts
|
|
13345
13379
|
var DEFAULT_TODOS_POSTGRES_SYNC_TABLE = "todos_sync_records";
|
|
13346
13380
|
var DEFAULT_TODOS_POSTGRES_CURSOR_TABLE = "todos_sync_cursors";
|
|
@@ -13543,53 +13577,98 @@ class PostgresTodosSyncStore {
|
|
|
13543
13577
|
if (routingErrors.length > 0) {
|
|
13544
13578
|
throw new Error(`Invalid snapshot routing metadata: ${routingErrors.join("; ")}`);
|
|
13545
13579
|
}
|
|
13546
|
-
const
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
const
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13580
|
+
const push = async (client) => {
|
|
13581
|
+
const existing = await client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
|
|
13582
|
+
FROM ${this.tableName}
|
|
13583
|
+
WHERE service = $1 AND object_type IN ($2, $3) AND deleted_at IS NULL`, [this.service, "projects", "task_lists"]);
|
|
13584
|
+
const existingProjects = [];
|
|
13585
|
+
const existingTaskLists = [];
|
|
13586
|
+
for (const row of existing.rows) {
|
|
13587
|
+
const payload = payloadRecord(row.payload);
|
|
13588
|
+
if (row.object_type === "projects")
|
|
13589
|
+
existingProjects.push(payload);
|
|
13590
|
+
if (row.object_type === "task_lists")
|
|
13591
|
+
existingTaskLists.push(payload);
|
|
13592
|
+
}
|
|
13593
|
+
const destinationErrors = validateSnapshotRoutingDestinationConflicts(snapshot.projects, snapshot.taskLists, existingProjects, existingTaskLists);
|
|
13594
|
+
if (destinationErrors.length > 0) {
|
|
13595
|
+
throw new ResourceConflictError("SNAPSHOT_DESTINATION_CONFLICT", `Snapshot routing conflicts with destination: ${destinationErrors.join("; ")}`);
|
|
13596
|
+
}
|
|
13597
|
+
const result = { records: 0, objectTypes: {} };
|
|
13598
|
+
const sourceMachineId = context.requestId ?? this.sourceMachineId ?? null;
|
|
13599
|
+
for (const entry of snapshotEntries(snapshot)) {
|
|
13600
|
+
if (entry.deletedAt === null)
|
|
13601
|
+
assertCanonicalScopedSlugEntry(entry);
|
|
13602
|
+
try {
|
|
13603
|
+
await client.query(`INSERT INTO ${this.tableName} (
|
|
13604
|
+
service, object_type, object_id, payload, updated_at,
|
|
13605
|
+
deleted_at, source_machine_id, version
|
|
13606
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, $6::timestamptz, $7, $8)
|
|
13607
|
+
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
13608
|
+
payload = EXCLUDED.payload,
|
|
13609
|
+
updated_at = EXCLUDED.updated_at,
|
|
13610
|
+
deleted_at = EXCLUDED.deleted_at,
|
|
13611
|
+
source_machine_id = EXCLUDED.source_machine_id,
|
|
13612
|
+
version = EXCLUDED.version
|
|
13613
|
+
WHERE ${this.tableName}.updated_at < EXCLUDED.updated_at
|
|
13614
|
+
OR (${this.tableName}.updated_at = EXCLUDED.updated_at
|
|
13615
|
+
AND COALESCE(${this.tableName}.version, 0) <= COALESCE(EXCLUDED.version, 0))`, [
|
|
13616
|
+
this.service,
|
|
13617
|
+
entry.type,
|
|
13618
|
+
entry.id,
|
|
13619
|
+
entry.payload,
|
|
13620
|
+
entry.updatedAt,
|
|
13621
|
+
entry.deletedAt,
|
|
13622
|
+
sourceMachineId,
|
|
13623
|
+
entry.version
|
|
13624
|
+
]);
|
|
13625
|
+
} catch (error) {
|
|
13626
|
+
await this.classifySyncInsertConflict(error, entry);
|
|
13627
|
+
}
|
|
13628
|
+
result.records += 1;
|
|
13629
|
+
result.objectTypes[entry.type] = (result.objectTypes[entry.type] ?? 0) + 1;
|
|
13630
|
+
}
|
|
13631
|
+
return result;
|
|
13632
|
+
};
|
|
13633
|
+
if (typeof this.client.transaction === "function") {
|
|
13634
|
+
return this.client.transaction((client) => push(client));
|
|
13591
13635
|
}
|
|
13592
|
-
return
|
|
13636
|
+
return push(this.client);
|
|
13637
|
+
}
|
|
13638
|
+
async classifySyncInsertConflict(error, entry) {
|
|
13639
|
+
if (!isPostgresUniqueViolation(error))
|
|
13640
|
+
throw error;
|
|
13641
|
+
const payload = entry.payload;
|
|
13642
|
+
const taskListSlug = String(payload["slug"] ?? "");
|
|
13643
|
+
const projectSlug = String(payload["task_list_id"] ?? "");
|
|
13644
|
+
const constraintName = postgresConstraintName(error);
|
|
13645
|
+
if (entry.type === "projects" && constraintName.includes("project_task_list_slug_uidx")) {
|
|
13646
|
+
throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Project slug "${projectSlug}" already exists`);
|
|
13647
|
+
}
|
|
13648
|
+
if (entry.type === "task_lists" && constraintName.includes("task_list_scope_slug_uidx")) {
|
|
13649
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${taskListSlug}" already exists in this scope`);
|
|
13650
|
+
}
|
|
13651
|
+
if (entry.type === "task_lists") {
|
|
13652
|
+
const scope = typeof payload["project_id"] === "string" ? payload["project_id"] : "";
|
|
13653
|
+
const conflict = await this.client.query(`/* todos:classify-sync-task-list-conflict */ SELECT EXISTS (
|
|
13654
|
+
SELECT 1 FROM ${this.tableName}
|
|
13655
|
+
WHERE service = $1 AND object_type = 'task_lists' AND object_id <> $2
|
|
13656
|
+
AND deleted_at IS NULL AND COALESCE(payload->>'project_id','') = $3 AND payload->>'slug' = $4
|
|
13657
|
+
) AS conflict`, [this.service, entry.id, scope, taskListSlug]);
|
|
13658
|
+
if (conflict.rows[0]?.conflict) {
|
|
13659
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${taskListSlug}" already exists in this scope`);
|
|
13660
|
+
}
|
|
13661
|
+
} else if (entry.type === "projects") {
|
|
13662
|
+
const conflict = await this.client.query(`/* todos:classify-sync-project-conflict */ SELECT EXISTS (
|
|
13663
|
+
SELECT 1 FROM ${this.tableName}
|
|
13664
|
+
WHERE service = $1 AND object_type = 'projects' AND object_id <> $2
|
|
13665
|
+
AND deleted_at IS NULL AND payload->>'task_list_id' = $3
|
|
13666
|
+
) AS conflict`, [this.service, entry.id, projectSlug]);
|
|
13667
|
+
if (conflict.rows[0]?.conflict) {
|
|
13668
|
+
throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Project slug "${projectSlug}" already exists`);
|
|
13669
|
+
}
|
|
13670
|
+
}
|
|
13671
|
+
throw error;
|
|
13593
13672
|
}
|
|
13594
13673
|
async pullSnapshot(options = {}) {
|
|
13595
13674
|
const params = [this.service];
|
|
@@ -16566,25 +16645,6 @@ async function retryOnTransientPostgresError(fn, attempts = 2, delayMs = 150) {
|
|
|
16566
16645
|
}
|
|
16567
16646
|
throw lastError;
|
|
16568
16647
|
}
|
|
16569
|
-
function isPostgresUniqueViolation(error) {
|
|
16570
|
-
if (typeof error !== "object" || error === null)
|
|
16571
|
-
return false;
|
|
16572
|
-
const candidate = error;
|
|
16573
|
-
const states = [candidate.code, candidate.errno, candidate.sqlState, candidate.sqlstate];
|
|
16574
|
-
if (typeof candidate.cause === "object" && candidate.cause !== null) {
|
|
16575
|
-
const cause = candidate.cause;
|
|
16576
|
-
states.push(cause.code, cause.errno, cause.sqlState, cause.sqlstate);
|
|
16577
|
-
}
|
|
16578
|
-
return states.some((state) => String(state) === "23505");
|
|
16579
|
-
}
|
|
16580
|
-
function postgresConstraintName(error) {
|
|
16581
|
-
if (typeof error !== "object" || error === null)
|
|
16582
|
-
return "";
|
|
16583
|
-
const candidate = error;
|
|
16584
|
-
const cause = typeof candidate.cause === "object" && candidate.cause !== null ? candidate.cause : undefined;
|
|
16585
|
-
const constraint = candidate.constraint ?? candidate.constraint_name ?? cause?.constraint ?? cause?.constraint_name;
|
|
16586
|
-
return typeof constraint === "string" ? constraint : "";
|
|
16587
|
-
}
|
|
16588
16648
|
// src/project-registration/types.ts
|
|
16589
16649
|
var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1";
|
|
16590
16650
|
var TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
|
package/dist/registry.js
CHANGED
|
@@ -5531,13 +5531,15 @@ function upsertSecretSafetyConfig(input) {
|
|
|
5531
5531
|
saveConfig({ ...config, secret_safety: next });
|
|
5532
5532
|
return next;
|
|
5533
5533
|
}
|
|
5534
|
-
var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS, REDACTION_PLACEHOLDER;
|
|
5534
|
+
var XAI_PREFIX, DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS, REDACTION_PLACEHOLDER;
|
|
5535
5535
|
var init_redaction = __esm(() => {
|
|
5536
5536
|
init_config2();
|
|
5537
|
+
XAI_PREFIX = ["x", "ai", "-"].join("");
|
|
5537
5538
|
DEFAULT_SECRET_PATTERNS = [
|
|
5538
5539
|
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
5539
5540
|
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
5540
5541
|
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
5542
|
+
{ name: `${XAI_PREFIX}token`, regex: /\bxai[-][A-Za-z0-9]{20,80}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
5541
5543
|
{ name: "npm-token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_NPM_TOKEN]" },
|
|
5542
5544
|
{ name: "github-fine-grained-token", regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
5543
5545
|
{ name: "github-token", regex: /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
@@ -9933,6 +9935,12 @@ function listTasks(filter = {}, db) {
|
|
|
9933
9935
|
const d = db || getDatabase();
|
|
9934
9936
|
const { clearExpiredLocks: clearExpiredLocks2 } = (init_database(), __toCommonJS(exports_database));
|
|
9935
9937
|
clearExpiredLocks2(d);
|
|
9938
|
+
if (filter.limit !== undefined) {
|
|
9939
|
+
const limit = filter.limit;
|
|
9940
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || limit > MAX_TASK_LIST_LIMIT) {
|
|
9941
|
+
throw new TypeError(`listTasks limit must be a positive integer between 1 and ${MAX_TASK_LIST_LIMIT}; got ${String(limit)}`);
|
|
9942
|
+
}
|
|
9943
|
+
}
|
|
9936
9944
|
const conditions = [];
|
|
9937
9945
|
const params = [];
|
|
9938
9946
|
if (filter.project_id) {
|
|
@@ -10466,6 +10474,7 @@ function deleteTask(id, db) {
|
|
|
10466
10474
|
}
|
|
10467
10475
|
return result.changes > 0;
|
|
10468
10476
|
}
|
|
10477
|
+
var MAX_TASK_LIST_LIMIT = 1e5;
|
|
10469
10478
|
var init_task_crud = __esm(() => {
|
|
10470
10479
|
init_types();
|
|
10471
10480
|
init_database();
|
|
@@ -13071,7 +13080,7 @@ var init_tasks = __esm(() => {
|
|
|
13071
13080
|
// package.json
|
|
13072
13081
|
var package_default = {
|
|
13073
13082
|
name: "@hasna/todos",
|
|
13074
|
-
version: "0.15.
|
|
13083
|
+
version: "0.15.50",
|
|
13075
13084
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
13076
13085
|
type: "module",
|
|
13077
13086
|
main: "dist/index.js",
|
|
@@ -13135,6 +13144,7 @@ var package_default = {
|
|
|
13135
13144
|
],
|
|
13136
13145
|
scripts: {
|
|
13137
13146
|
build: "rm -rf dist dashboard/dist && cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
13147
|
+
"build:js": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --root src --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
13138
13148
|
"build:server": "rm -rf dist && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/server/index.ts --outdir dist/server --target bun && bun build src/sdk/index.ts --outdir dist/sdk --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && bun build src/index.ts src/mcp.ts src/registry.ts src/contracts.ts src/storage.ts src/testing.ts src/project-registration.ts src/task-manifest.ts src/task-subtree-transfer.ts --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
13139
13149
|
migrate: "bun run src/server/index.ts migrate",
|
|
13140
13150
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
@@ -13189,7 +13199,7 @@ var package_default = {
|
|
|
13189
13199
|
author: "Andrei Hasna <andrei@hasna.com>",
|
|
13190
13200
|
license: "Apache-2.0",
|
|
13191
13201
|
dependencies: {
|
|
13192
|
-
"@hasna/contracts": "0.
|
|
13202
|
+
"@hasna/contracts": "0.14.0",
|
|
13193
13203
|
"@hasna/events": "^0.1.11",
|
|
13194
13204
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
13195
13205
|
chalk: "^5.4.1",
|
|
@@ -22016,6 +22026,47 @@ function autoDetectFileRelationships(taskId, db) {
|
|
|
22016
22026
|
|
|
22017
22027
|
// src/lib/task-dedupe.ts
|
|
22018
22028
|
init_tasks();
|
|
22029
|
+
|
|
22030
|
+
// src/lib/dedupe-projection.ts
|
|
22031
|
+
init_redaction();
|
|
22032
|
+
var DEDUPE_SOURCE_KEY_ALLOWLIST = [
|
|
22033
|
+
"github_url",
|
|
22034
|
+
"github_issue_url",
|
|
22035
|
+
"github_pr_url",
|
|
22036
|
+
"source_url",
|
|
22037
|
+
"url",
|
|
22038
|
+
"external_url",
|
|
22039
|
+
"issue_url",
|
|
22040
|
+
"github_owner",
|
|
22041
|
+
"github_repo",
|
|
22042
|
+
"github_number"
|
|
22043
|
+
];
|
|
22044
|
+
function projectTasksForDedupe(tasks) {
|
|
22045
|
+
return tasks.map((task2) => {
|
|
22046
|
+
const metadata = {};
|
|
22047
|
+
for (const key of DEDUPE_SOURCE_KEY_ALLOWLIST) {
|
|
22048
|
+
if (key in task2.metadata) {
|
|
22049
|
+
metadata[key] = redactValue(task2.metadata[key]);
|
|
22050
|
+
}
|
|
22051
|
+
}
|
|
22052
|
+
return {
|
|
22053
|
+
id: task2.id,
|
|
22054
|
+
short_id: task2.short_id,
|
|
22055
|
+
title: redactEvidenceText(task2.title),
|
|
22056
|
+
description: task2.description === null || task2.description === undefined ? null : redactEvidenceText(task2.description),
|
|
22057
|
+
status: task2.status,
|
|
22058
|
+
created_at: task2.created_at,
|
|
22059
|
+
updated_at: task2.updated_at,
|
|
22060
|
+
project_id: task2.project_id,
|
|
22061
|
+
task_list_id: task2.task_list_id,
|
|
22062
|
+
assigned_to: task2.assigned_to,
|
|
22063
|
+
priority: task2.priority,
|
|
22064
|
+
metadata
|
|
22065
|
+
};
|
|
22066
|
+
});
|
|
22067
|
+
}
|
|
22068
|
+
|
|
22069
|
+
// src/lib/task-dedupe.ts
|
|
22019
22070
|
var DEFAULT_THRESHOLD = 0.74;
|
|
22020
22071
|
var STOP_WORDS = new Set([
|
|
22021
22072
|
"a",
|
|
@@ -22208,10 +22259,10 @@ function olderFirst(left, right) {
|
|
|
22208
22259
|
function findDuplicateTasks(options = {}, db) {
|
|
22209
22260
|
const d = db || getDatabase();
|
|
22210
22261
|
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
22211
|
-
const fingerprints = listTasks({
|
|
22262
|
+
const fingerprints = projectTasksForDedupe(listTasks({
|
|
22212
22263
|
include_archived: Boolean(options.include_archived),
|
|
22213
22264
|
limit: options.limit ?? 1000
|
|
22214
|
-
}, d).map(fingerprint);
|
|
22265
|
+
}, d)).map(fingerprint);
|
|
22215
22266
|
const candidates = [];
|
|
22216
22267
|
for (let i = 0;i < fingerprints.length; i++) {
|
|
22217
22268
|
for (let j = i + 1;j < fingerprints.length; j++) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageName": "@hasna/todos",
|
|
3
|
-
"packageVersion": "0.15.
|
|
3
|
+
"packageVersion": "0.15.50",
|
|
4
4
|
"repository": "https://github.com/hasna/todos.git",
|
|
5
|
-
"gitCommit": "
|
|
6
|
-
"gitTree": "
|
|
7
|
-
"sourceTreeSha256": "
|
|
8
|
-
"generatedAt": "2026-08-
|
|
5
|
+
"gitCommit": "d1c3300ee75f88ed5c96669a646459e2fe75f7d0",
|
|
6
|
+
"gitTree": "95740bb8e3519601b1fc002199a875f122308a90",
|
|
7
|
+
"sourceTreeSha256": "d4a4e6676850b53226707c4a8720a411555ed37a56faacc2796399bec6afdf1a",
|
|
8
|
+
"generatedAt": "2026-08-25T21:08:07.000Z"
|
|
9
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/server/cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAwB,MAAM,uBAAuB,CAAC;AAG1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAKvD,OAAO,EAGL,KAAK,iCAAiC,EACvC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAGL,KAAK,iCAAiC,EACvC,MAAM,mCAAmC,CAAC;AAQ3C,OAAO,EAEL,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACpC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC7B,MAAM,kCAAkC,CAAC;AAE1C,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,uFAAuF;AACvF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAOhG;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAO7F;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEzF;
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/server/cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAwB,MAAM,uBAAuB,CAAC;AAG1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAKvD,OAAO,EAGL,KAAK,iCAAiC,EACvC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAGL,KAAK,iCAAiC,EACvC,MAAM,mCAAmC,CAAC;AAQ3C,OAAO,EAEL,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACpC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC7B,MAAM,kCAAkC,CAAC;AAE1C,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,uFAAuF;AACvF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAOhG;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAO7F;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEzF;AAgDD,4EAA4E;AAC5E,wBAAgB,sBAAsB,IAAI,mBAAmB,CAK5D;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,IAAI,aAAa,CAIrD;AAED,iFAAiF;AACjF,wBAAgB,oCAAoC,IAAI,iCAAiC,CAYxF;AAED,gFAAgF;AAChF,wBAAgB,6BAA6B,IAAI,0BAA0B,CAO1E;AAED,wFAAwF;AACxF,wBAAgB,oCAAoC,IAAI,iCAAiC,CAOxF;AAuBD,wBAAgB,cAAc,IAAI,WAAW,CAI5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,CAoBjD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiDvD;AAED;;;;GAIG;AACH,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnE;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAEjE;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAElE;AAED,uFAAuF;AACvF,wBAAsB,kCAAkC,IAAI,OAAO,CAAC,IAAI,CAAC,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS9D;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CAElC;AAED,iEAAiE;AACjE,wBAAsB,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAIlD;AAED,4BAA4B;AAC5B,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAahD"}
|