@hasna/todos 0.15.35 → 0.15.41
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/cloud-router.d.ts +52 -18
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-subtree-transfer-commands.d.ts +3 -0
- package/dist/cli/commands/task-subtree-transfer-commands.d.ts.map +1 -0
- package/dist/cli/index.js +3299 -796
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +62 -54
- package/dist/db/plan-comments.d.ts +14 -0
- package/dist/db/plan-comments.d.ts.map +1 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-lists.d.ts.map +1 -1
- package/dist/db/task-relations.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1787 -309
- package/dist/lib/comment-cursor.d.ts +19 -0
- package/dist/lib/comment-cursor.d.ts.map +1 -1
- package/dist/lib/import-export-bridge.d.ts.map +1 -1
- package/dist/mcp/index.js +2273 -471
- package/dist/mcp.js +8 -4
- package/dist/project-registration.js +207 -63
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +69 -54
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +35 -0
- package/dist/sdk/v1.generated.d.ts +141 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +12 -2
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +5171 -8535
- package/dist/server/openapi.d.ts +2714 -1937
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +2 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/config.d.ts +12 -30
- package/dist/storage/config.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +13 -2
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/local-sqlite.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/timestamp-backfill.d.ts +62 -0
- package/dist/storage/timestamp-backfill.d.ts.map +1 -0
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +199 -63
- package/dist/task-subtree-transfer/authority.d.ts +27 -0
- package/dist/task-subtree-transfer/authority.d.ts.map +1 -0
- package/dist/task-subtree-transfer/backend.d.ts +9 -0
- package/dist/task-subtree-transfer/backend.d.ts.map +1 -0
- package/dist/task-subtree-transfer/canonical.d.ts +14 -0
- package/dist/task-subtree-transfer/canonical.d.ts.map +1 -0
- package/dist/task-subtree-transfer/http.d.ts +16 -0
- package/dist/task-subtree-transfer/http.d.ts.map +1 -0
- package/dist/task-subtree-transfer/index.d.ts +6 -0
- package/dist/task-subtree-transfer/index.d.ts.map +1 -0
- package/dist/task-subtree-transfer/planner.d.ts +24 -0
- package/dist/task-subtree-transfer/planner.d.ts.map +1 -0
- package/dist/task-subtree-transfer/postgres.d.ts +19 -0
- package/dist/task-subtree-transfer/postgres.d.ts.map +1 -0
- package/dist/task-subtree-transfer/schema-sql.d.ts +5 -0
- package/dist/task-subtree-transfer/schema-sql.d.ts.map +1 -0
- package/dist/task-subtree-transfer/sqlite.d.ts +16 -0
- package/dist/task-subtree-transfer/sqlite.d.ts.map +1 -0
- package/dist/task-subtree-transfer/types.d.ts +148 -0
- package/dist/task-subtree-transfer/types.d.ts.map +1 -0
- package/dist/task-subtree-transfer.d.ts +2 -0
- package/dist/task-subtree-transfer.d.ts.map +1 -0
- package/dist/task-subtree-transfer.js +1869 -0
- package/dist/testing.d.ts +41 -7
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +44 -1
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +8 -4
package/dist/mcp.js
CHANGED
|
@@ -41,7 +41,7 @@ var __require = import.meta.require;
|
|
|
41
41
|
// package.json
|
|
42
42
|
var package_default = {
|
|
43
43
|
name: "@hasna/todos",
|
|
44
|
-
version: "0.15.
|
|
44
|
+
version: "0.15.41",
|
|
45
45
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
46
46
|
type: "module",
|
|
47
47
|
main: "dist/index.js",
|
|
@@ -87,6 +87,10 @@ var package_default = {
|
|
|
87
87
|
"./task-manifest": {
|
|
88
88
|
types: "./dist/task-manifest.d.ts",
|
|
89
89
|
import: "./dist/task-manifest.js"
|
|
90
|
+
},
|
|
91
|
+
"./task-subtree-transfer": {
|
|
92
|
+
types: "./dist/task-subtree-transfer.d.ts",
|
|
93
|
+
import: "./dist/task-subtree-transfer.js"
|
|
90
94
|
}
|
|
91
95
|
},
|
|
92
96
|
workspaces: [
|
|
@@ -100,8 +104,8 @@ var package_default = {
|
|
|
100
104
|
"README.md"
|
|
101
105
|
],
|
|
102
106
|
scripts: {
|
|
103
|
-
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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
104
|
-
"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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
107
|
+
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",
|
|
108
|
+
"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/*'",
|
|
105
109
|
migrate: "bun run src/server/index.ts migrate",
|
|
106
110
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
107
111
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -155,7 +159,7 @@ var package_default = {
|
|
|
155
159
|
author: "Andrei Hasna <andrei@hasna.com>",
|
|
156
160
|
license: "Apache-2.0",
|
|
157
161
|
dependencies: {
|
|
158
|
-
"@hasna/contracts": "0.
|
|
162
|
+
"@hasna/contracts": "0.13.3",
|
|
159
163
|
"@hasna/events": "^0.1.11",
|
|
160
164
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
161
165
|
chalk: "^5.4.1",
|
|
@@ -3692,6 +3692,20 @@ function ensureSchema(db) {
|
|
|
3692
3692
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
3693
3693
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
3694
3694
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
3695
|
+
ensureTable("plan_comments", `
|
|
3696
|
+
CREATE TABLE plan_comments (
|
|
3697
|
+
id TEXT PRIMARY KEY,
|
|
3698
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
3699
|
+
agent_id TEXT,
|
|
3700
|
+
session_id TEXT,
|
|
3701
|
+
content TEXT NOT NULL,
|
|
3702
|
+
type TEXT DEFAULT 'comment' CHECK(type IN ('comment', 'progress', 'note')),
|
|
3703
|
+
progress_pct INTEGER CHECK(progress_pct IS NULL OR (progress_pct >= 0 AND progress_pct <= 100)),
|
|
3704
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3705
|
+
machine_id TEXT,
|
|
3706
|
+
synced_at TEXT
|
|
3707
|
+
)`);
|
|
3708
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plan_comments_plan ON plan_comments(plan_id)");
|
|
3695
3709
|
ensureColumn("projects", "machine_id", "TEXT");
|
|
3696
3710
|
ensureColumn("projects", "synced_at", "TEXT");
|
|
3697
3711
|
ensureColumn("tasks", "machine_id", "TEXT");
|
|
@@ -4673,6 +4687,7 @@ var init_machines = __esm(() => {
|
|
|
4673
4687
|
"task_lists",
|
|
4674
4688
|
"plans",
|
|
4675
4689
|
"task_comments",
|
|
4690
|
+
"plan_comments",
|
|
4676
4691
|
"sessions",
|
|
4677
4692
|
"task_history",
|
|
4678
4693
|
"webhooks",
|
|
@@ -4866,20 +4881,16 @@ var init_identity_mapping = __esm(() => {
|
|
|
4866
4881
|
// src/storage/config.ts
|
|
4867
4882
|
var exports_config = {};
|
|
4868
4883
|
__export(exports_config, {
|
|
4869
|
-
parseStorageMode: () => parseStorageMode,
|
|
4870
4884
|
parseStorageBackend: () => parseStorageBackend,
|
|
4871
4885
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
4872
4886
|
loadStorageConfig: () => loadStorageConfig,
|
|
4873
4887
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
4874
|
-
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
4875
4888
|
isTodosPostgresBackend: () => isTodosPostgresBackend,
|
|
4876
4889
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
4877
|
-
getTodosStorageMode: () => getTodosStorageMode,
|
|
4878
4890
|
getTodosStorageEnvName: () => getTodosStorageEnvName,
|
|
4879
4891
|
getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
|
|
4880
4892
|
getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
|
|
4881
4893
|
getTodosStorageBackend: () => getTodosStorageBackend,
|
|
4882
|
-
getStorageMode: () => getStorageMode,
|
|
4883
4894
|
getStorageDatabaseUrl: () => getStorageDatabaseUrl,
|
|
4884
4895
|
getStorageDatabaseEnv: () => getStorageDatabaseEnv,
|
|
4885
4896
|
getCanonicalTodosRdsConfig: () => getCanonicalTodosRdsConfig,
|
|
@@ -4889,7 +4900,6 @@ __export(exports_config, {
|
|
|
4889
4900
|
TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
|
|
4890
4901
|
TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
|
|
4891
4902
|
STORAGE_TABLES: () => STORAGE_TABLES,
|
|
4892
|
-
REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
|
|
4893
4903
|
CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
|
|
4894
4904
|
CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
|
|
4895
4905
|
CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
|
|
@@ -4944,9 +4954,6 @@ function loadStorageConfig(env = process.env) {
|
|
|
4944
4954
|
function isTodosPostgresBackend(config) {
|
|
4945
4955
|
return config.mode === "postgres";
|
|
4946
4956
|
}
|
|
4947
|
-
function isTodosRemoteStorageEnabled(config) {
|
|
4948
|
-
return isTodosPostgresBackend(config);
|
|
4949
|
-
}
|
|
4950
4957
|
function assertTodosRemoteStorageConfig(config) {
|
|
4951
4958
|
if (!isTodosPostgresBackend(config))
|
|
4952
4959
|
return;
|
|
@@ -4962,32 +4969,15 @@ function parseStorageBackend(value) {
|
|
|
4962
4969
|
return "sqlite";
|
|
4963
4970
|
if (normalized === "postgres" || normalized === "postgresql")
|
|
4964
4971
|
return "postgres";
|
|
4965
|
-
if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
|
|
4966
|
-
throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} selects the backend. Deployment modes no longer exist: ` + `delete the storage-mode variable and set ${TODOS_STORAGE_ENV.databaseUrl} to select the ` + `postgresql backend, or leave it unset for sqlite.`);
|
|
4967
|
-
}
|
|
4968
4972
|
throw new Error(`Storage backend must be sqlite or postgres`);
|
|
4969
4973
|
}
|
|
4970
|
-
function parseStorageMode(value) {
|
|
4971
|
-
return parseStorageBackend(value);
|
|
4972
|
-
}
|
|
4973
4974
|
function getTodosStorageBackend(env = process.env) {
|
|
4974
|
-
for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
|
|
4975
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined) {
|
|
4976
|
-
throw new Error(`${key} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `Set ${TODOS_STORAGE_ENV.databaseUrl} to select the postgresql backend, ` + `or leave it unset for sqlite.`);
|
|
4977
|
-
}
|
|
4978
|
-
}
|
|
4979
4975
|
if (!getTodosStorageDatabaseUrl(env))
|
|
4980
4976
|
return "sqlite";
|
|
4981
4977
|
if (isTodosShadowEnabled(env))
|
|
4982
4978
|
return "sqlite";
|
|
4983
4979
|
return "postgres";
|
|
4984
4980
|
}
|
|
4985
|
-
function getTodosStorageMode(env = process.env) {
|
|
4986
|
-
return getTodosStorageBackend(env);
|
|
4987
|
-
}
|
|
4988
|
-
function getStorageMode(env = process.env) {
|
|
4989
|
-
return getTodosStorageBackend(env);
|
|
4990
|
-
}
|
|
4991
4981
|
function isTodosShadowEnabled(env = process.env) {
|
|
4992
4982
|
return parseBoolean(readStorageEnv(env, "shadow").value, false);
|
|
4993
4983
|
}
|
|
@@ -5054,7 +5044,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
5054
5044
|
}
|
|
5055
5045
|
return parsed;
|
|
5056
5046
|
}
|
|
5057
|
-
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV,
|
|
5047
|
+
var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, CANONICAL_TODOS_RDS_CLUSTER_ENV = "HASNA_TODOS_RDS_CLUSTER", CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV = "HASNA_TODOS_RDS_RUNTIME_PATH", CANONICAL_TODOS_RDS_DATABASE = "todos";
|
|
5058
5048
|
var init_config2 = __esm(() => {
|
|
5059
5049
|
TODOS_STORAGE_TABLES = [
|
|
5060
5050
|
"todos_sync_records",
|
|
@@ -5093,12 +5083,6 @@ var init_config2 = __esm(() => {
|
|
|
5093
5083
|
syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
|
|
5094
5084
|
syncDryRun: "TODOS_SYNC_DRY_RUN"
|
|
5095
5085
|
};
|
|
5096
|
-
REMOVED_STORAGE_MODE_ENV_KEYS = [
|
|
5097
|
-
"HASNA_TODOS_STORAGE_MODE",
|
|
5098
|
-
"HASNA_TODOS_MODE",
|
|
5099
|
-
"TODOS_STORAGE_MODE",
|
|
5100
|
-
"TODOS_MODE"
|
|
5101
|
-
];
|
|
5102
5086
|
});
|
|
5103
5087
|
|
|
5104
5088
|
// src/storage/shadow-outbox-schema.ts
|
|
@@ -7270,22 +7254,31 @@ function updateTaskList2(id, input, db) {
|
|
|
7270
7254
|
throw new TaskListNotFoundError(id);
|
|
7271
7255
|
const sets = ["updated_at = ?"];
|
|
7272
7256
|
const params = [now()];
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7257
|
+
const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? existing.project_id : input.project_id;
|
|
7258
|
+
const scopeChanged = projectId !== existing.project_id;
|
|
7259
|
+
const effectiveSlug = input.slug !== undefined ? slugify(input.slug) : existing.slug;
|
|
7260
|
+
if (input.slug !== undefined && !effectiveSlug) {
|
|
7261
|
+
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
7262
|
+
}
|
|
7263
|
+
if (scopeChanged || input.slug !== undefined && effectiveSlug !== existing.slug) {
|
|
7264
|
+
const duplicate = projectId ? d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ? AND id != ?").get(projectId, effectiveSlug, id) : d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ? AND id != ?").get(effectiveSlug, id);
|
|
7278
7265
|
if (duplicate) {
|
|
7279
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
7266
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
7280
7267
|
}
|
|
7281
|
-
if (
|
|
7268
|
+
if (scopeChanged || effectiveSlug !== existing.slug) {
|
|
7282
7269
|
releaseCanonicalSlugClaims("task_list", id, d);
|
|
7283
|
-
if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(
|
|
7284
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
7270
|
+
if (!claimCanonicalSlug("task_list", taskListSlugScopeKey(projectId), effectiveSlug, id, d)) {
|
|
7271
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
7285
7272
|
}
|
|
7286
7273
|
}
|
|
7274
|
+
}
|
|
7275
|
+
if (input.slug !== undefined) {
|
|
7287
7276
|
sets.push("slug = ?");
|
|
7288
|
-
params.push(
|
|
7277
|
+
params.push(effectiveSlug);
|
|
7278
|
+
}
|
|
7279
|
+
if (projectId !== existing.project_id) {
|
|
7280
|
+
sets.push("project_id = ?");
|
|
7281
|
+
params.push(projectId);
|
|
7289
7282
|
}
|
|
7290
7283
|
if (input.name !== undefined) {
|
|
7291
7284
|
sets.push("name = ?");
|
|
@@ -9321,8 +9314,8 @@ function failTask2(id, agentId, reason, options, db) {
|
|
|
9321
9314
|
const safeMeta = sanitizePreWriteValue(meta, "task.failure.metadata");
|
|
9322
9315
|
const timestamp2 = now();
|
|
9323
9316
|
const failTx = d.transaction(() => {
|
|
9324
|
-
const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
9325
|
-
WHERE id = ? AND version = ?`, [safeReason, JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
9317
|
+
const res = d.run(`UPDATE tasks SET status = 'failed', reason = ?, locked_by = NULL, locked_at = NULL, completed_at = COALESCE(completed_at, ?), metadata = ?, version = version + 1, updated_at = ?
|
|
9318
|
+
WHERE id = ? AND version = ?`, [safeReason, timestamp2, JSON.stringify(safeMeta), timestamp2, id, task.version]);
|
|
9326
9319
|
if (res.changes === 0) {
|
|
9327
9320
|
const current = getTask(id, d);
|
|
9328
9321
|
throw new VersionConflictError(id, task.version, current?.version ?? -1);
|
|
@@ -9334,6 +9327,7 @@ function failTask2(id, agentId, reason, options, db) {
|
|
|
9334
9327
|
status: "failed",
|
|
9335
9328
|
locked_by: null,
|
|
9336
9329
|
locked_at: null,
|
|
9330
|
+
completed_at: task.completed_at ?? timestamp2,
|
|
9337
9331
|
reason: safeReason,
|
|
9338
9332
|
metadata: safeMeta,
|
|
9339
9333
|
version: task.version + 1,
|
|
@@ -9982,13 +9976,20 @@ function updateTaskStored(id, input, db) {
|
|
|
9982
9976
|
if (isTerminalStatus(input.status)) {
|
|
9983
9977
|
sets.push("locked_by = NULL");
|
|
9984
9978
|
sets.push("locked_at = NULL");
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9979
|
+
if (input.status === "completed") {
|
|
9980
|
+
sets.push("completed_at = ?");
|
|
9981
|
+
params.push(completionTimestamp);
|
|
9982
|
+
} else {
|
|
9983
|
+
sets.push("completed_at = COALESCE(completed_at, ?)");
|
|
9984
|
+
params.push(completionTimestamp);
|
|
9985
|
+
}
|
|
9989
9986
|
} else if (task.status === "completed" && input.completed_at === undefined) {
|
|
9990
9987
|
sets.push("completed_at = NULL");
|
|
9991
9988
|
}
|
|
9989
|
+
if (input.status === "in_progress") {
|
|
9990
|
+
sets.push("started_at = COALESCE(started_at, ?)");
|
|
9991
|
+
params.push(timestamp2);
|
|
9992
|
+
}
|
|
9992
9993
|
}
|
|
9993
9994
|
if (input.priority !== undefined) {
|
|
9994
9995
|
sets.push("priority = ?");
|
|
@@ -10126,7 +10127,6 @@ function updateTaskStored(id, input, db) {
|
|
|
10126
10127
|
if (input.approved_by !== undefined)
|
|
10127
10128
|
logTaskChange2(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
10128
10129
|
const reopened = input.status !== undefined && input.status !== "completed" && task.status === "completed" && input.completed_at === undefined;
|
|
10129
|
-
const completedNow = input.status === "completed";
|
|
10130
10130
|
const terminalNow = input.status !== undefined && isTerminalStatus(input.status);
|
|
10131
10131
|
const updatedTask = {
|
|
10132
10132
|
...task,
|
|
@@ -10137,7 +10137,8 @@ function updateTaskStored(id, input, db) {
|
|
|
10137
10137
|
updated_at: timestamp2,
|
|
10138
10138
|
locked_by: terminalNow ? null : task.locked_by,
|
|
10139
10139
|
locked_at: terminalNow ? null : task.locked_at,
|
|
10140
|
-
completed_at:
|
|
10140
|
+
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : terminalNow ? task.completed_at ?? completionTimestamp : reopened ? null : task.completed_at,
|
|
10141
|
+
started_at: input.status === "in_progress" ? task.started_at ?? timestamp2 : task.started_at,
|
|
10141
10142
|
sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
|
|
10142
10143
|
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
10143
10144
|
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
@@ -10198,6 +10199,9 @@ function deleteTask(id, db) {
|
|
|
10198
10199
|
version: row.version
|
|
10199
10200
|
}, d);
|
|
10200
10201
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
10202
|
+
if (result.changes > 0) {
|
|
10203
|
+
d.run("DELETE FROM task_dependencies WHERE task_id = ? OR depends_on = ?", [id, id]);
|
|
10204
|
+
}
|
|
10201
10205
|
return result.changes > 0;
|
|
10202
10206
|
}
|
|
10203
10207
|
var init_task_crud = __esm(() => {
|
|
@@ -12808,7 +12812,7 @@ import { createHash as createHash7 } from "crypto";
|
|
|
12808
12812
|
// package.json
|
|
12809
12813
|
var package_default = {
|
|
12810
12814
|
name: "@hasna/todos",
|
|
12811
|
-
version: "0.15.
|
|
12815
|
+
version: "0.15.41",
|
|
12812
12816
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12813
12817
|
type: "module",
|
|
12814
12818
|
main: "dist/index.js",
|
|
@@ -12854,6 +12858,10 @@ var package_default = {
|
|
|
12854
12858
|
"./task-manifest": {
|
|
12855
12859
|
types: "./dist/task-manifest.d.ts",
|
|
12856
12860
|
import: "./dist/task-manifest.js"
|
|
12861
|
+
},
|
|
12862
|
+
"./task-subtree-transfer": {
|
|
12863
|
+
types: "./dist/task-subtree-transfer.d.ts",
|
|
12864
|
+
import: "./dist/task-subtree-transfer.js"
|
|
12857
12865
|
}
|
|
12858
12866
|
},
|
|
12859
12867
|
workspaces: [
|
|
@@ -12867,8 +12875,8 @@ var package_default = {
|
|
|
12867
12875
|
"README.md"
|
|
12868
12876
|
],
|
|
12869
12877
|
scripts: {
|
|
12870
|
-
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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
12871
|
-
"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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
12878
|
+
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",
|
|
12879
|
+
"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/*'",
|
|
12872
12880
|
migrate: "bun run src/server/index.ts migrate",
|
|
12873
12881
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
12874
12882
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -12922,7 +12930,7 @@ var package_default = {
|
|
|
12922
12930
|
author: "Andrei Hasna <andrei@hasna.com>",
|
|
12923
12931
|
license: "Apache-2.0",
|
|
12924
12932
|
dependencies: {
|
|
12925
|
-
"@hasna/contracts": "0.
|
|
12933
|
+
"@hasna/contracts": "0.13.3",
|
|
12926
12934
|
"@hasna/events": "^0.1.11",
|
|
12927
12935
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
12928
12936
|
chalk: "^5.4.1",
|
|
@@ -13783,7 +13791,26 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
13783
13791
|
list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
|
|
13784
13792
|
update: (id, input) => updatePlan(id, input, store),
|
|
13785
13793
|
completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
|
|
13786
|
-
delete: (id, context) => store.deletePlan(id, context)
|
|
13794
|
+
delete: (id, context) => store.deletePlan(id, context),
|
|
13795
|
+
addComment: (input, context) => addPlanComment(input, store, context),
|
|
13796
|
+
getComments: async (planId) => {
|
|
13797
|
+
const pages = [];
|
|
13798
|
+
let before;
|
|
13799
|
+
while (true) {
|
|
13800
|
+
const page = await store.listPlanComments(planId, { limit: 1000, ...before ? { before } : {} });
|
|
13801
|
+
if (page.length === 0)
|
|
13802
|
+
break;
|
|
13803
|
+
pages.unshift(page);
|
|
13804
|
+
if (page.length < 1000)
|
|
13805
|
+
break;
|
|
13806
|
+
const oldest = page[0];
|
|
13807
|
+
before = { created_at: oldest.created_at, id: oldest.id };
|
|
13808
|
+
}
|
|
13809
|
+
return pages.flat().map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
13810
|
+
},
|
|
13811
|
+
getCommentsPage: async (planId, options2) => {
|
|
13812
|
+
return (await store.listPlanComments(planId, options2)).map(redactPlanComment).sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id));
|
|
13813
|
+
}
|
|
13787
13814
|
},
|
|
13788
13815
|
planProjectLinks: {
|
|
13789
13816
|
apply: (input, context) => store.applyPlanProjectLink(input, context),
|
|
@@ -14001,6 +14028,27 @@ class PostgresJsonRecordStore {
|
|
|
14001
14028
|
LIMIT $${params.length}`, params);
|
|
14002
14029
|
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
14003
14030
|
}
|
|
14031
|
+
async listPlanComments(planId, options = {}) {
|
|
14032
|
+
await this.ensureSchema();
|
|
14033
|
+
const limit = options.limit ?? 100;
|
|
14034
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1001) {
|
|
14035
|
+
throw new Error("Postgres plan comment limit must be an integer between 1 and 1001");
|
|
14036
|
+
}
|
|
14037
|
+
const params = [this.service, planId];
|
|
14038
|
+
let cursorPredicate = "";
|
|
14039
|
+
if (options.before) {
|
|
14040
|
+
params.push(options.before.created_at, options.before.id);
|
|
14041
|
+
cursorPredicate = `AND (payload->>'created_at', object_id) < ($3, $4)`;
|
|
14042
|
+
}
|
|
14043
|
+
params.push(limit);
|
|
14044
|
+
const result = await this.options.client.query(`/* todos:list-plan-comments */ SELECT payload FROM ${this.tableName}
|
|
14045
|
+
WHERE service = $1 AND object_type = 'plan_comments' AND deleted_at IS NULL
|
|
14046
|
+
AND payload->>'plan_id' = $2
|
|
14047
|
+
${cursorPredicate}
|
|
14048
|
+
ORDER BY payload->>'created_at' DESC, object_id DESC
|
|
14049
|
+
LIMIT $${params.length}`, params);
|
|
14050
|
+
return result.rows.map((row) => payloadRecord2(row.payload)).reverse();
|
|
14051
|
+
}
|
|
14004
14052
|
async listRecords(type) {
|
|
14005
14053
|
await this.ensureSchema();
|
|
14006
14054
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at
|
|
@@ -15162,6 +15210,15 @@ class PostgresJsonRecordStore {
|
|
|
15162
15210
|
WHERE r.service = $1 AND r.object_type = 'tasks' AND r.deleted_at IS NULL
|
|
15163
15211
|
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
|
|
15164
15212
|
RETURNING 1
|
|
15213
|
+
), deleted_plan_comments AS (
|
|
15214
|
+
UPDATE ${this.tableName} r SET
|
|
15215
|
+
deleted_at = $3::timestamptz,
|
|
15216
|
+
updated_at = $3::timestamptz,
|
|
15217
|
+
source_machine_id = COALESCE($4, r.source_machine_id),
|
|
15218
|
+
version = COALESCE(r.version, 0) + 1
|
|
15219
|
+
WHERE r.service = $1 AND r.object_type = 'plan_comments' AND r.deleted_at IS NULL
|
|
15220
|
+
AND r.payload->>'plan_id' = $2 AND EXISTS (SELECT 1 FROM target)
|
|
15221
|
+
RETURNING 1
|
|
15165
15222
|
), deleted_plan AS (
|
|
15166
15223
|
UPDATE ${this.tableName} r SET
|
|
15167
15224
|
deleted_at = $3::timestamptz,
|
|
@@ -15337,7 +15394,8 @@ async function updateTask(id, input, store, context) {
|
|
|
15337
15394
|
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
|
|
15338
15395
|
parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
|
|
15339
15396
|
created_by: existing.created_by,
|
|
15340
|
-
completed_at:
|
|
15397
|
+
completed_at: input.status === "completed" ? input.completed_at ?? new Date().toISOString() : input.completed_at !== undefined ? input.completed_at : input.status !== undefined && isTerminalStatus(input.status) ? existing.completed_at ?? new Date().toISOString() : reopened ? null : existing.completed_at,
|
|
15398
|
+
started_at: input.status === "in_progress" ? existing.started_at ?? new Date().toISOString() : existing.started_at
|
|
15341
15399
|
};
|
|
15342
15400
|
const storedTask = await store.upsertTaskWithPlanMembershipGuard(task, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
|
|
15343
15401
|
operation: "update",
|
|
@@ -15382,7 +15440,8 @@ async function failTask(id, agentId, reason, options, store) {
|
|
|
15382
15440
|
status: "failed",
|
|
15383
15441
|
assigned_to: task.assigned_to ?? agentId ?? null,
|
|
15384
15442
|
reason: reason ?? task.reason,
|
|
15385
|
-
retry_after: options?.retry_after ?? task.retry_after
|
|
15443
|
+
retry_after: options?.retry_after ?? task.retry_after,
|
|
15444
|
+
completed_at: task.completed_at ?? new Date().toISOString()
|
|
15386
15445
|
}, store);
|
|
15387
15446
|
if (!options?.retry)
|
|
15388
15447
|
return { task: failed };
|
|
@@ -15830,16 +15889,22 @@ async function createTaskList(input, store, context) {
|
|
|
15830
15889
|
async function updateTaskList(id, input, store) {
|
|
15831
15890
|
const list = await requireRecord("task_lists", id, store);
|
|
15832
15891
|
const patch = definedPatch(input);
|
|
15892
|
+
const projectId = input.project_id === "" || input.project_id === null ? null : input.project_id === undefined ? list.project_id : input.project_id;
|
|
15833
15893
|
if (input.slug !== undefined) {
|
|
15834
15894
|
const slug = slugifyRaw(input.slug);
|
|
15835
15895
|
if (!slug)
|
|
15836
15896
|
throw new Error("Invalid task-list slug \u2014 must be non-empty kebab-case");
|
|
15837
|
-
|
|
15897
|
+
patch.slug = slug;
|
|
15898
|
+
}
|
|
15899
|
+
const effectiveSlug = patch.slug ?? list.slug;
|
|
15900
|
+
if (projectId !== list.project_id || effectiveSlug !== list.slug) {
|
|
15901
|
+
const duplicate = (await store.list("task_lists")).find((candidate) => candidate.id !== id && candidate.project_id === projectId && candidate.slug === effectiveSlug);
|
|
15838
15902
|
if (duplicate) {
|
|
15839
|
-
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${
|
|
15903
|
+
throw new ResourceConflictError("TASK_LIST_SLUG_CONFLICT", `Task list with slug "${effectiveSlug}" already exists in this scope`);
|
|
15840
15904
|
}
|
|
15841
|
-
patch.slug = slug;
|
|
15842
15905
|
}
|
|
15906
|
+
if (projectId !== list.project_id)
|
|
15907
|
+
patch.project_id = projectId;
|
|
15843
15908
|
return store.upsert("task_lists", {
|
|
15844
15909
|
...list,
|
|
15845
15910
|
...patch,
|
|
@@ -15932,6 +15997,22 @@ async function addComment(input, store, context) {
|
|
|
15932
15997
|
function redactComment(comment) {
|
|
15933
15998
|
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
15934
15999
|
}
|
|
16000
|
+
async function addPlanComment(input, store, context) {
|
|
16001
|
+
const comment = {
|
|
16002
|
+
id: randomUUID(),
|
|
16003
|
+
plan_id: input.plan_id,
|
|
16004
|
+
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
16005
|
+
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
16006
|
+
content: redactEvidenceText(input.content),
|
|
16007
|
+
type: input.type ?? "comment",
|
|
16008
|
+
progress_pct: input.progress_pct ?? null,
|
|
16009
|
+
created_at: new Date().toISOString()
|
|
16010
|
+
};
|
|
16011
|
+
return store.upsert("plan_comments", comment, context);
|
|
16012
|
+
}
|
|
16013
|
+
function redactPlanComment(comment) {
|
|
16014
|
+
return { ...comment, content: redactEvidenceText(comment.content) };
|
|
16015
|
+
}
|
|
15935
16016
|
async function exportSnapshot(store) {
|
|
15936
16017
|
return {
|
|
15937
16018
|
exportedAt: new Date().toISOString(),
|
|
@@ -16817,6 +16898,40 @@ init_tasks();
|
|
|
16817
16898
|
init_projects();
|
|
16818
16899
|
init_plans();
|
|
16819
16900
|
|
|
16901
|
+
// src/db/plan-comments.ts
|
|
16902
|
+
init_types();
|
|
16903
|
+
init_database();
|
|
16904
|
+
init_plans();
|
|
16905
|
+
init_prewrite_secrets();
|
|
16906
|
+
function addPlanComment2(input, db) {
|
|
16907
|
+
const d = db || getDatabase();
|
|
16908
|
+
if (!getPlan(input.plan_id, d)) {
|
|
16909
|
+
throw new PlanNotFoundError(input.plan_id);
|
|
16910
|
+
}
|
|
16911
|
+
const id = uuid();
|
|
16912
|
+
const timestamp2 = now();
|
|
16913
|
+
d.run(`INSERT INTO plan_comments (id, plan_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
16914
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
16915
|
+
id,
|
|
16916
|
+
input.plan_id,
|
|
16917
|
+
input.agent_id || null,
|
|
16918
|
+
input.session_id || null,
|
|
16919
|
+
sanitizePreWriteText(input.content, "comment.content"),
|
|
16920
|
+
input.type || "comment",
|
|
16921
|
+
input.progress_pct ?? null,
|
|
16922
|
+
timestamp2
|
|
16923
|
+
]);
|
|
16924
|
+
return getPlanComment(id, d);
|
|
16925
|
+
}
|
|
16926
|
+
function getPlanComment(id, db) {
|
|
16927
|
+
const d = db || getDatabase();
|
|
16928
|
+
return d.query("SELECT * FROM plan_comments WHERE id = ?").get(id);
|
|
16929
|
+
}
|
|
16930
|
+
function listPlanComments(planId, db) {
|
|
16931
|
+
const d = db || getDatabase();
|
|
16932
|
+
return d.query("SELECT * FROM plan_comments WHERE plan_id = ? ORDER BY created_at, rowid").all(planId);
|
|
16933
|
+
}
|
|
16934
|
+
|
|
16820
16935
|
// src/db/agents.ts
|
|
16821
16936
|
init_types();
|
|
16822
16937
|
init_database();
|
|
@@ -17469,6 +17584,20 @@ init_task_lists();
|
|
|
17469
17584
|
init_templates();
|
|
17470
17585
|
init_audit();
|
|
17471
17586
|
init_comments();
|
|
17587
|
+
|
|
17588
|
+
// src/lib/comment-cursor.ts
|
|
17589
|
+
function isStrictlyOlder(comment, before) {
|
|
17590
|
+
return comment.created_at < before.created_at || comment.created_at === before.created_at && comment.id < before.id;
|
|
17591
|
+
}
|
|
17592
|
+
function compareCommentKeyset(left, right) {
|
|
17593
|
+
if (isStrictlyOlder(left, right))
|
|
17594
|
+
return -1;
|
|
17595
|
+
if (isStrictlyOlder(right, left))
|
|
17596
|
+
return 1;
|
|
17597
|
+
return 0;
|
|
17598
|
+
}
|
|
17599
|
+
|
|
17600
|
+
// src/storage/local-sqlite.ts
|
|
17472
17601
|
init_database();
|
|
17473
17602
|
|
|
17474
17603
|
// src/db/integrity.ts
|
|
@@ -18240,7 +18369,22 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
18240
18369
|
list: (projectId) => listPlans(projectId, database()),
|
|
18241
18370
|
update: (id, input) => updatePlan2(id, input, database()),
|
|
18242
18371
|
completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
|
|
18243
|
-
delete: (id) => deletePlan(id, database())
|
|
18372
|
+
delete: (id) => deletePlan(id, database()),
|
|
18373
|
+
addComment: (input) => addPlanComment2(input, database()),
|
|
18374
|
+
getComments: (planId) => listPlanComments(planId, database()),
|
|
18375
|
+
getCommentsPage: (planId, options2) => {
|
|
18376
|
+
if (options2?.limit !== undefined && (!Number.isSafeInteger(options2.limit) || options2.limit < 1 || options2.limit > 1001)) {
|
|
18377
|
+
throw new Error("Plan comment limit must be an integer between 1 and 1001");
|
|
18378
|
+
}
|
|
18379
|
+
let comments = listPlanComments(planId, database());
|
|
18380
|
+
comments = comments.sort(compareCommentKeyset);
|
|
18381
|
+
if (options2?.before) {
|
|
18382
|
+
const before = options2.before;
|
|
18383
|
+
comments = comments.filter((comment) => isStrictlyOlder(comment, before));
|
|
18384
|
+
}
|
|
18385
|
+
const limit = options2?.limit ?? 100;
|
|
18386
|
+
return comments.slice(-limit);
|
|
18387
|
+
}
|
|
18244
18388
|
},
|
|
18245
18389
|
planProjectLinks: {
|
|
18246
18390
|
apply: (input) => applyPlanProjectLinkSqlite(input, database()),
|
|
@@ -18281,10 +18425,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
18281
18425
|
throw new Error("Comment limit must be an integer between 1 and 1001");
|
|
18282
18426
|
}
|
|
18283
18427
|
let comments = listComments(taskId, database());
|
|
18284
|
-
comments = comments.sort(
|
|
18428
|
+
comments = comments.sort(compareCommentKeyset);
|
|
18285
18429
|
if (options2?.before) {
|
|
18286
18430
|
const before = options2.before;
|
|
18287
|
-
comments = comments.filter((comment) => comment
|
|
18431
|
+
comments = comments.filter((comment) => isStrictlyOlder(comment, before));
|
|
18288
18432
|
}
|
|
18289
18433
|
if (options2?.limit !== undefined)
|
|
18290
18434
|
comments = comments.slice(-options2.limit);
|
package/dist/registry.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface TodosPackageSource {
|
|
|
12
12
|
version: string;
|
|
13
13
|
}
|
|
14
14
|
export interface TodosPackageExportContract {
|
|
15
|
-
subpath: "." | "./sdk" | "./mcp" | "./registry" | "./contracts" | "./storage" | "./testing" | "./project-registration" | "./task-manifest";
|
|
15
|
+
subpath: "." | "./sdk" | "./mcp" | "./registry" | "./contracts" | "./storage" | "./testing" | "./project-registration" | "./task-manifest" | "./task-subtree-transfer";
|
|
16
16
|
import: string;
|
|
17
17
|
types: string;
|
|
18
18
|
description: string;
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,cAAc,CAAC;IAC5B,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EACH,GAAG,GACH,OAAO,GACP,OAAO,GACP,YAAY,GACZ,aAAa,GACb,WAAW,GACX,WAAW,GACX,wBAAwB,GACxB,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,cAAc,CAAC;IAC5B,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EACH,GAAG,GACH,OAAO,GACP,OAAO,GACP,YAAY,GACZ,aAAa,GACb,WAAW,GACX,WAAW,GACX,wBAAwB,GACxB,iBAAiB,GACjB,yBAAyB,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,CAAC,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,0BAA0B,EAAE,CAAC;IACtC,oBAAoB,EAAE,wBAAwB,CAAC;IAC/C,YAAY,EAAE,uBAAuB,CAAC;IACtC,SAAS,EAAE,sBAAsB,CAAC;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,YAAY,EAAE,yBAAyB,CAAC;CACzC;AAED,eAAO,MAAM,qBAAqB,EAAE,0BAA0B,EAuE7D,CAAC;AAUF,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,aAAa,CAc3F;AAED,eAAO,MAAM,cAAc,eAEzB,CAAC"}
|