@hasna/todos 0.13.2 → 0.13.4
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/README.md +78 -14
- package/dist/cli/cloud-router.d.ts +100 -7
- 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/query-commands.d.ts.map +1 -1
- package/dist/cli/doctor-integrity.d.ts +106 -0
- package/dist/cli/doctor-integrity.d.ts.map +1 -0
- package/dist/cli/helpers.d.ts +2 -2
- package/dist/cli/index.js +1509 -786
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +118 -30
- package/dist/db/agent-names.d.ts +2 -1
- package/dist/db/agent-names.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/findings.d.ts +1 -0
- package/dist/db/findings.d.ts.map +1 -1
- package/dist/db/integrity.d.ts +20 -0
- package/dist/db/integrity.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +584 -140
- package/dist/lib/agent-name-normalize.d.ts +17 -0
- package/dist/lib/agent-name-normalize.d.ts.map +1 -0
- package/dist/lib/context-packs.d.ts +1 -0
- package/dist/lib/context-packs.d.ts.map +1 -1
- package/dist/lib/dependency-graph.d.ts +1 -1
- package/dist/lib/doctor.d.ts +20 -0
- package/dist/lib/doctor.d.ts.map +1 -1
- package/dist/lib/integrity.d.ts +215 -0
- package/dist/lib/integrity.d.ts.map +1 -0
- package/dist/lib/native-storage-status.d.ts +2 -2
- package/dist/lib/native-storage-status.d.ts.map +1 -1
- package/dist/lib/release-compatibility.d.ts.map +1 -1
- package/dist/lib/task-dedupe.d.ts +15 -0
- package/dist/lib/task-dedupe.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +554 -92
- package/dist/mcp.js +7 -3
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +125 -30
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/types.d.ts +42 -0
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/v1.generated.d.ts +1 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +8 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +560 -98
- package/dist/server/openapi.d.ts +12 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/config.d.ts +23 -5
- package/dist/storage/config.d.ts.map +1 -1
- package/dist/storage/factory.d.ts +9 -3
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/hybrid.d.ts +7 -0
- package/dist/storage/hybrid.d.ts.map +1 -1
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +29 -8
- 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.d.ts +3 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +623 -258
- package/dist/testing.d.ts +79 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +107 -0
- package/dist/types/index.d.ts +12 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -3427,7 +3427,7 @@ var init_machines = __esm(() => {
|
|
|
3427
3427
|
function isBlockingDependencyStatus(status) {
|
|
3428
3428
|
return status !== "completed" && status !== "cancelled";
|
|
3429
3429
|
}
|
|
3430
|
-
var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
|
|
3430
|
+
var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
|
|
3431
3431
|
var init_types = __esm(() => {
|
|
3432
3432
|
TASK_STATUSES = [
|
|
3433
3433
|
"pending",
|
|
@@ -3467,6 +3467,22 @@ var init_types = __esm(() => {
|
|
|
3467
3467
|
this.name = "TaskNotFoundError";
|
|
3468
3468
|
}
|
|
3469
3469
|
};
|
|
3470
|
+
TaskReferenceAmbiguousError = class TaskReferenceAmbiguousError extends Error {
|
|
3471
|
+
reference;
|
|
3472
|
+
static code = "TASK_REFERENCE_AMBIGUOUS";
|
|
3473
|
+
candidateTaskIds;
|
|
3474
|
+
candidateProjectIds;
|
|
3475
|
+
constructor(reference, candidates) {
|
|
3476
|
+
const candidateTaskIds = [...new Set(candidates.map((candidate) => candidate.task_id))].sort();
|
|
3477
|
+
const candidateProjectIds = [...new Set(candidates.map((candidate) => candidate.project_id).filter((projectId) => typeof projectId === "string" && projectId.length > 0))].sort();
|
|
3478
|
+
const projectList = candidateProjectIds.length > 0 ? candidateProjectIds.join(", ") : "(unscoped)";
|
|
3479
|
+
super(`Task reference is ambiguous: "${reference}". Candidate project IDs: ${projectList}. ` + "Use a full task UUID.");
|
|
3480
|
+
this.reference = reference;
|
|
3481
|
+
this.name = "TaskReferenceAmbiguousError";
|
|
3482
|
+
this.candidateTaskIds = candidateTaskIds;
|
|
3483
|
+
this.candidateProjectIds = candidateProjectIds;
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3470
3486
|
ProjectNotFoundError = class ProjectNotFoundError extends Error {
|
|
3471
3487
|
projectId;
|
|
3472
3488
|
static code = "PROJECT_NOT_FOUND";
|
|
@@ -3758,15 +3774,18 @@ var init_identity_mapping = __esm(() => {
|
|
|
3758
3774
|
var exports_config = {};
|
|
3759
3775
|
__export(exports_config, {
|
|
3760
3776
|
parseStorageMode: () => parseStorageMode,
|
|
3777
|
+
parseStorageBackend: () => parseStorageBackend,
|
|
3761
3778
|
loadTodosStorageConfig: () => loadTodosStorageConfig,
|
|
3762
3779
|
loadStorageConfig: () => loadStorageConfig,
|
|
3763
3780
|
isTodosShadowEnabled: () => isTodosShadowEnabled,
|
|
3764
3781
|
isTodosRemoteStorageEnabled: () => isTodosRemoteStorageEnabled,
|
|
3782
|
+
isTodosPostgresBackend: () => isTodosPostgresBackend,
|
|
3765
3783
|
getTodosStorageShadowEnvName: () => getTodosStorageShadowEnvName,
|
|
3766
3784
|
getTodosStorageMode: () => getTodosStorageMode,
|
|
3767
3785
|
getTodosStorageEnvName: () => getTodosStorageEnvName,
|
|
3768
3786
|
getTodosStorageDatabaseUrl: () => getTodosStorageDatabaseUrl,
|
|
3769
3787
|
getTodosStorageDatabaseEnv: () => getTodosStorageDatabaseEnv,
|
|
3788
|
+
getTodosStorageBackend: () => getTodosStorageBackend,
|
|
3770
3789
|
getStorageMode: () => getStorageMode,
|
|
3771
3790
|
getStorageDatabaseUrl: () => getStorageDatabaseUrl,
|
|
3772
3791
|
getStorageDatabaseEnv: () => getStorageDatabaseEnv,
|
|
@@ -3791,7 +3810,7 @@ function getCanonicalTodosRdsConfig(env = process.env) {
|
|
|
3791
3810
|
};
|
|
3792
3811
|
}
|
|
3793
3812
|
function loadTodosStorageConfig(env = process.env) {
|
|
3794
|
-
const mode =
|
|
3813
|
+
const mode = getTodosStorageBackend(env);
|
|
3795
3814
|
const databaseUrl = getTodosStorageDatabaseUrl(env);
|
|
3796
3815
|
const bucket = readStorageEnv(env, "s3Bucket").value;
|
|
3797
3816
|
const prefix = readStorageEnv(env, "s3Prefix").value ?? "todos/";
|
|
@@ -3828,29 +3847,41 @@ function loadTodosStorageConfig(env = process.env) {
|
|
|
3828
3847
|
function loadStorageConfig(env = process.env) {
|
|
3829
3848
|
return loadTodosStorageConfig(env);
|
|
3830
3849
|
}
|
|
3850
|
+
function isTodosPostgresBackend(config) {
|
|
3851
|
+
return config.mode === "postgres";
|
|
3852
|
+
}
|
|
3831
3853
|
function isTodosRemoteStorageEnabled(config) {
|
|
3832
|
-
return config
|
|
3854
|
+
return isTodosPostgresBackend(config);
|
|
3833
3855
|
}
|
|
3834
3856
|
function assertTodosRemoteStorageConfig(config) {
|
|
3835
|
-
if (!
|
|
3857
|
+
if (!isTodosPostgresBackend(config))
|
|
3836
3858
|
return;
|
|
3837
3859
|
if (!config.database?.url) {
|
|
3838
3860
|
throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} is required when ${TODOS_STORAGE_ENV.mode}=${config.mode}`);
|
|
3839
3861
|
}
|
|
3840
3862
|
}
|
|
3841
|
-
function
|
|
3863
|
+
function parseStorageBackend(value) {
|
|
3842
3864
|
const normalized = clean(value)?.toLowerCase();
|
|
3843
3865
|
if (!normalized)
|
|
3844
|
-
return "
|
|
3845
|
-
if (normalized === "
|
|
3866
|
+
return "sqlite";
|
|
3867
|
+
if (normalized === "sqlite" || normalized === "postgres")
|
|
3846
3868
|
return normalized;
|
|
3847
|
-
|
|
3869
|
+
const legacy = LEGACY_BACKEND_TOKENS[normalized];
|
|
3870
|
+
if (legacy)
|
|
3871
|
+
return legacy;
|
|
3872
|
+
throw new Error(`${TODOS_STORAGE_ENV.mode} must be sqlite or postgres (legacy values local and remote are accepted)`);
|
|
3873
|
+
}
|
|
3874
|
+
function parseStorageMode(value) {
|
|
3875
|
+
return parseStorageBackend(value);
|
|
3876
|
+
}
|
|
3877
|
+
function getTodosStorageBackend(env = process.env) {
|
|
3878
|
+
return parseStorageBackend(readStorageEnv(env, "mode").value);
|
|
3848
3879
|
}
|
|
3849
3880
|
function getTodosStorageMode(env = process.env) {
|
|
3850
|
-
return
|
|
3881
|
+
return getTodosStorageBackend(env);
|
|
3851
3882
|
}
|
|
3852
3883
|
function getStorageMode(env = process.env) {
|
|
3853
|
-
return
|
|
3884
|
+
return getTodosStorageBackend(env);
|
|
3854
3885
|
}
|
|
3855
3886
|
function isTodosShadowEnabled(env = process.env) {
|
|
3856
3887
|
return parseBoolean(readStorageEnv(env, "shadow").value, false);
|
|
@@ -3861,8 +3892,8 @@ function getTodosStorageShadowEnvName(env = process.env) {
|
|
|
3861
3892
|
function assertTodosShadowConfig(config, env = process.env) {
|
|
3862
3893
|
if (!isTodosShadowEnabled(env))
|
|
3863
3894
|
return;
|
|
3864
|
-
if (config.mode !== "
|
|
3865
|
-
throw new Error(`${readStorageEnv(env, "shadow").name} shadow mirror requires
|
|
3895
|
+
if (config.mode !== "sqlite") {
|
|
3896
|
+
throw new Error(`${readStorageEnv(env, "shadow").name} shadow mirror requires the sqlite backend (got ${config.mode})`);
|
|
3866
3897
|
}
|
|
3867
3898
|
if (!config.database?.url) {
|
|
3868
3899
|
throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} is required when ${readStorageEnv(env, "shadow").name} is enabled`);
|
|
@@ -3918,7 +3949,7 @@ function parsePositiveInteger(value, fallback) {
|
|
|
3918
3949
|
}
|
|
3919
3950
|
return parsed;
|
|
3920
3951
|
}
|
|
3921
|
-
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";
|
|
3952
|
+
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", LEGACY_BACKEND_TOKENS;
|
|
3922
3953
|
var init_config = __esm(() => {
|
|
3923
3954
|
TODOS_STORAGE_TABLES = [
|
|
3924
3955
|
"todos_sync_records",
|
|
@@ -3959,6 +3990,14 @@ var init_config = __esm(() => {
|
|
|
3959
3990
|
syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
|
|
3960
3991
|
syncDryRun: "TODOS_SYNC_DRY_RUN"
|
|
3961
3992
|
};
|
|
3993
|
+
LEGACY_BACKEND_TOKENS = {
|
|
3994
|
+
local: "sqlite",
|
|
3995
|
+
remote: "postgres",
|
|
3996
|
+
postgresql: "postgres",
|
|
3997
|
+
hybrid: "postgres",
|
|
3998
|
+
self_hosted: "postgres",
|
|
3999
|
+
cloud: "postgres"
|
|
4000
|
+
};
|
|
3962
4001
|
});
|
|
3963
4002
|
|
|
3964
4003
|
// src/storage/shadow-outbox-schema.ts
|
|
@@ -4092,6 +4131,30 @@ function findGitRoot(startDir) {
|
|
|
4092
4131
|
}
|
|
4093
4132
|
return null;
|
|
4094
4133
|
}
|
|
4134
|
+
function getGlobalDbPath() {
|
|
4135
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4136
|
+
return join(home, ".hasna", "todos", "todos.db");
|
|
4137
|
+
}
|
|
4138
|
+
function hasExplicitProjectArg(args = process.argv.slice(2)) {
|
|
4139
|
+
return args.some((arg) => arg === "--project" || arg.startsWith("--project="));
|
|
4140
|
+
}
|
|
4141
|
+
function getCliCommand(args = process.argv.slice(2)) {
|
|
4142
|
+
const globalOptionsWithValues = new Set(["--project", "--agent", "--session"]);
|
|
4143
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
4144
|
+
const arg = args[index];
|
|
4145
|
+
if (globalOptionsWithValues.has(arg)) {
|
|
4146
|
+
index += 1;
|
|
4147
|
+
continue;
|
|
4148
|
+
}
|
|
4149
|
+
if (arg.startsWith("-"))
|
|
4150
|
+
continue;
|
|
4151
|
+
return arg;
|
|
4152
|
+
}
|
|
4153
|
+
return null;
|
|
4154
|
+
}
|
|
4155
|
+
function canCreateScopedProjectDb(args = process.argv.slice(2)) {
|
|
4156
|
+
return getCliCommand(args) === "project-bootstrap" && !args.some((arg) => arg === "--dry-run" || arg.startsWith("--dry-run="));
|
|
4157
|
+
}
|
|
4095
4158
|
function getDbPath() {
|
|
4096
4159
|
if (process.env["HASNA_TODOS_DB_PATH"]) {
|
|
4097
4160
|
return process.env["HASNA_TODOS_DB_PATH"];
|
|
@@ -4099,18 +4162,19 @@ function getDbPath() {
|
|
|
4099
4162
|
if (process.env["TODOS_DB_PATH"]) {
|
|
4100
4163
|
return process.env["TODOS_DB_PATH"];
|
|
4101
4164
|
}
|
|
4165
|
+
if (hasExplicitProjectArg())
|
|
4166
|
+
return getGlobalDbPath();
|
|
4102
4167
|
const cwd = process.cwd();
|
|
4103
4168
|
const nearest = findNearestProjectDb(cwd);
|
|
4104
4169
|
if (nearest)
|
|
4105
4170
|
return nearest;
|
|
4106
4171
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
4107
4172
|
const gitRoot = findGitRoot(cwd);
|
|
4108
|
-
if (gitRoot) {
|
|
4173
|
+
if (gitRoot && canCreateScopedProjectDb()) {
|
|
4109
4174
|
return join(gitRoot, ".hasna", "todos", "todos.db");
|
|
4110
4175
|
}
|
|
4111
4176
|
}
|
|
4112
|
-
|
|
4113
|
-
return join(home, ".hasna", "todos", "todos.db");
|
|
4177
|
+
return getGlobalDbPath();
|
|
4114
4178
|
}
|
|
4115
4179
|
function getDatabasePath() {
|
|
4116
4180
|
return getDbPath();
|
|
@@ -4213,10 +4277,13 @@ function resolvePartialId(db, table, partialId) {
|
|
|
4213
4277
|
return null;
|
|
4214
4278
|
}
|
|
4215
4279
|
if (table === "tasks") {
|
|
4216
|
-
const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
|
|
4280
|
+
const shortIdRows = db.query("SELECT id, project_id FROM tasks WHERE LOWER(short_id) = LOWER(?) ORDER BY project_id, id").all(partialId);
|
|
4217
4281
|
if (shortIdRows.length === 1) {
|
|
4218
4282
|
return shortIdRows[0].id;
|
|
4219
4283
|
}
|
|
4284
|
+
if (shortIdRows.length > 1) {
|
|
4285
|
+
throw new TaskReferenceAmbiguousError(partialId, shortIdRows.map((row) => ({ task_id: row.id, project_id: row.project_id })));
|
|
4286
|
+
}
|
|
4220
4287
|
}
|
|
4221
4288
|
if (table === "task_lists") {
|
|
4222
4289
|
const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
|
|
@@ -11946,7 +12013,7 @@ var init_dispatches = __esm(() => {
|
|
|
11946
12013
|
// package.json
|
|
11947
12014
|
var package_default = {
|
|
11948
12015
|
name: "@hasna/todos",
|
|
11949
|
-
version: "0.13.
|
|
12016
|
+
version: "0.13.4",
|
|
11950
12017
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
11951
12018
|
type: "module",
|
|
11952
12019
|
main: "dist/index.js",
|
|
@@ -11980,6 +12047,10 @@ var package_default = {
|
|
|
11980
12047
|
"./storage": {
|
|
11981
12048
|
types: "./dist/storage.d.ts",
|
|
11982
12049
|
import: "./dist/storage.js"
|
|
12050
|
+
},
|
|
12051
|
+
"./testing": {
|
|
12052
|
+
types: "./dist/testing.d.ts",
|
|
12053
|
+
import: "./dist/testing.js"
|
|
11983
12054
|
}
|
|
11984
12055
|
},
|
|
11985
12056
|
workspaces: [
|
|
@@ -11992,8 +12063,8 @@ var package_default = {
|
|
|
11992
12063
|
"README.md"
|
|
11993
12064
|
],
|
|
11994
12065
|
scripts: {
|
|
11995
|
-
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 --external '@hasna/contracts' --external '@hasna/contracts/*' && 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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
11996
|
-
"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 --external '@hasna/contracts' --external '@hasna/contracts/*' && 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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
12066
|
+
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 --external '@hasna/contracts' --external '@hasna/contracts/*' && 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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*' && tsc --emitDeclarationOnly --outDir dist",
|
|
12067
|
+
"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 --external '@hasna/contracts' --external '@hasna/contracts/*' && 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 --outdir dist --target bun --external '@hasna/contracts' --external '@hasna/contracts/*'",
|
|
11997
12068
|
migrate: "bun run src/server/index.ts migrate",
|
|
11998
12069
|
"backfill:comment-redaction": "bun run src/server/index.ts redact-comments",
|
|
11999
12070
|
"generate:sdk": "bun run scripts/generate-sdk.ts",
|
|
@@ -17740,11 +17811,12 @@ var DEFAULT_LIMITS = {
|
|
|
17740
17811
|
verification_limit: 10,
|
|
17741
17812
|
run_limit: 3,
|
|
17742
17813
|
dependency_limit: 12,
|
|
17743
|
-
plan_task_limit:
|
|
17814
|
+
plan_task_limit: 24,
|
|
17744
17815
|
max_text_chars: 6000,
|
|
17745
17816
|
summary_char_limit: 480,
|
|
17746
17817
|
stale_after_hours: 72
|
|
17747
17818
|
};
|
|
17819
|
+
var MAX_CONTEXT_TASKS = 24;
|
|
17748
17820
|
var ALL_CONTEXT_SECTIONS = [
|
|
17749
17821
|
"project",
|
|
17750
17822
|
"plan",
|
|
@@ -17777,7 +17849,7 @@ function limits(input) {
|
|
|
17777
17849
|
verification_limit: clamp(input.verification_limit, DEFAULT_LIMITS.verification_limit, 100),
|
|
17778
17850
|
run_limit: clamp(input.run_limit, DEFAULT_LIMITS.run_limit, 20),
|
|
17779
17851
|
dependency_limit: clamp(input.dependency_limit, DEFAULT_LIMITS.dependency_limit, 100),
|
|
17780
|
-
plan_task_limit: clamp(input.plan_task_limit, DEFAULT_LIMITS.plan_task_limit,
|
|
17852
|
+
plan_task_limit: clamp(input.plan_task_limit, DEFAULT_LIMITS.plan_task_limit, MAX_CONTEXT_TASKS),
|
|
17781
17853
|
max_text_chars: clamp(input.max_text_chars, DEFAULT_LIMITS.max_text_chars, 50000),
|
|
17782
17854
|
summary_char_limit: clamp(input.summary_char_limit, DEFAULT_LIMITS.summary_char_limit, 4000),
|
|
17783
17855
|
stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
|
|
@@ -17800,6 +17872,13 @@ function taskSummary(task2) {
|
|
|
17800
17872
|
priority: task2.priority
|
|
17801
17873
|
};
|
|
17802
17874
|
}
|
|
17875
|
+
function latestPlanTasks(planId, limit, db) {
|
|
17876
|
+
const rows = db.query(`SELECT * FROM tasks
|
|
17877
|
+
WHERE plan_id = ? AND archived_at IS NULL
|
|
17878
|
+
ORDER BY updated_at DESC, created_at DESC, id ASC
|
|
17879
|
+
LIMIT ?`).all(planId, limit);
|
|
17880
|
+
return rows.map(rowToTask);
|
|
17881
|
+
}
|
|
17803
17882
|
function acceptanceCriteria(task2, maxText) {
|
|
17804
17883
|
const metadata = task2.metadata || {};
|
|
17805
17884
|
const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
|
|
@@ -17897,7 +17976,7 @@ function summarizeSection(pack, section, maxChars) {
|
|
|
17897
17976
|
return "No plan was attached.";
|
|
17898
17977
|
return summarizeStrings([
|
|
17899
17978
|
`Plan ${pack.plan.name} is ${pack.plan.status}`,
|
|
17900
|
-
`${pack.plan.tasks.length} listed plan
|
|
17979
|
+
`${pack.plan.tasks.length} of ${pack.plan.total_tasks} plan tasks listed; ${pack.plan.omitted_tasks} omitted`,
|
|
17901
17980
|
...pack.plan.tasks.slice(0, 4).map((task2) => `${task2.status} ${task2.short_id || task2.id.slice(0, 8)} ${task2.title}`)
|
|
17902
17981
|
], maxChars);
|
|
17903
17982
|
}
|
|
@@ -18047,7 +18126,8 @@ function createAgentContextPack(input, db) {
|
|
|
18047
18126
|
const limit = limits(input);
|
|
18048
18127
|
const project = task2.project_id ? getProject(task2.project_id, d) : null;
|
|
18049
18128
|
const plan = task2.plan_id ? getPlan(task2.plan_id, d) : null;
|
|
18050
|
-
const planTasks = task2.plan_id ?
|
|
18129
|
+
const planTasks = task2.plan_id ? latestPlanTasks(task2.plan_id, limit.plan_task_limit, d) : [];
|
|
18130
|
+
const totalPlanTasks = task2.plan_id ? countTasks({ plan_id: task2.plan_id }, d) : 0;
|
|
18051
18131
|
const upstream = getTaskDependencies(task2.id, d).map((dep) => taskSummary(getTask(dep.depends_on, d))).filter((item) => Boolean(item));
|
|
18052
18132
|
const downstream = getTaskDependents(task2.id, d).map((dep) => taskSummary(getTask(dep.task_id, d))).filter((item) => Boolean(item));
|
|
18053
18133
|
const comments = listComments(task2.id, d);
|
|
@@ -18090,6 +18170,9 @@ function createAgentContextPack(input, db) {
|
|
|
18090
18170
|
warnings.push(`${runs.length - selectedRuns.length} older runs omitted`);
|
|
18091
18171
|
if (fileMap.size > relevantFiles.length)
|
|
18092
18172
|
warnings.push(`${fileMap.size - relevantFiles.length} relevant files omitted`);
|
|
18173
|
+
if (totalPlanTasks > planTasks.length && plan) {
|
|
18174
|
+
warnings.push(`${totalPlanTasks - planTasks.length} plan tasks omitted (${totalPlanTasks} total); load more on demand with "todos plans --show ${plan.id}" and inspect task details with "todos show <task-id>" or "todos context-pack <task-id>"`);
|
|
18175
|
+
}
|
|
18093
18176
|
const contextTask = {
|
|
18094
18177
|
id: task2.id,
|
|
18095
18178
|
short_id: task2.short_id,
|
|
@@ -18121,8 +18204,9 @@ function createAgentContextPack(input, db) {
|
|
|
18121
18204
|
description: truncate2(plan.description, limit.max_text_chars),
|
|
18122
18205
|
status: plan.status,
|
|
18123
18206
|
agent_id: plan.agent_id,
|
|
18124
|
-
tasks: planTasks.
|
|
18125
|
-
|
|
18207
|
+
tasks: planTasks.map(taskSummary).filter((item) => Boolean(item)),
|
|
18208
|
+
total_tasks: totalPlanTasks,
|
|
18209
|
+
omitted_tasks: Math.max(0, totalPlanTasks - planTasks.length)
|
|
18126
18210
|
} : null,
|
|
18127
18211
|
acceptance_criteria: acceptanceCriteria(task2, limit.max_text_chars),
|
|
18128
18212
|
dependencies: {
|
|
@@ -18232,6 +18316,8 @@ ${pack.task.description}` : null,
|
|
|
18232
18316
|
"## Project And Plan",
|
|
18233
18317
|
pack.project ? `- Project: ${pack.project.name} (${pack.project.path})` : "- Project: none",
|
|
18234
18318
|
pack.plan ? `- Plan: ${pack.plan.name} (${pack.plan.status})` : "- Plan: none",
|
|
18319
|
+
pack.plan ? `- Plan tasks: ${pack.plan.tasks.length} shown of ${pack.plan.total_tasks} total (${pack.plan.omitted_tasks} omitted)` : null,
|
|
18320
|
+
pack.plan && pack.plan.omitted_tasks > 0 ? `- Load more on demand: \`todos plans --show ${pack.plan.id}\`; inspect one task with \`todos show <task-id>\` or \`todos context-pack <task-id>\`.` : null,
|
|
18235
18321
|
pack.plan && pack.plan.tasks.length > 0 ? bullet(pack.plan.tasks.map((task2) => `${task2.status} ${task2.short_id || task2.id.slice(0, 8)} ${task2.title}`)) : null,
|
|
18236
18322
|
"",
|
|
18237
18323
|
"## Acceptance Criteria",
|
|
@@ -19900,7 +19986,7 @@ function createReleaseCompatibilityReport(options = {}) {
|
|
|
19900
19986
|
"todos --help",
|
|
19901
19987
|
"todos-mcp --help",
|
|
19902
19988
|
"todos-serve --help",
|
|
19903
|
-
"todos doctor"
|
|
19989
|
+
"todos doctor --no-fail-on-findings"
|
|
19904
19990
|
],
|
|
19905
19991
|
smoke_tests: [
|
|
19906
19992
|
"command -v todos",
|
|
@@ -19908,7 +19994,7 @@ function createReleaseCompatibilityReport(options = {}) {
|
|
|
19908
19994
|
"command -v todos-serve",
|
|
19909
19995
|
"todos --version",
|
|
19910
19996
|
"todos --help",
|
|
19911
|
-
"todos doctor"
|
|
19997
|
+
"todos doctor --no-fail-on-findings"
|
|
19912
19998
|
],
|
|
19913
19999
|
rollback: [
|
|
19914
20000
|
"npm view @hasna/todos versions --json",
|
|
@@ -20402,12 +20488,11 @@ ${task2.description || ""}`.split(/\r?\n/);
|
|
|
20402
20488
|
function normalizeStackLocation(value) {
|
|
20403
20489
|
return value.replace(/:\d+(?::\d+)?/g, "").replace(/^file:\/\//, "").toLowerCase().trim();
|
|
20404
20490
|
}
|
|
20405
|
-
function
|
|
20491
|
+
function fingerprintContent(task2) {
|
|
20406
20492
|
const body = task2.description || "";
|
|
20407
20493
|
const text = `${task2.title}
|
|
20408
20494
|
${body}`;
|
|
20409
20495
|
return {
|
|
20410
|
-
task: task2,
|
|
20411
20496
|
title: normalizeText(task2.title),
|
|
20412
20497
|
body: normalizeText(body),
|
|
20413
20498
|
text: normalizeText(text),
|
|
@@ -20416,6 +20501,9 @@ ${body}`;
|
|
|
20416
20501
|
stackKeys: stackKeysFor(task2)
|
|
20417
20502
|
};
|
|
20418
20503
|
}
|
|
20504
|
+
function fingerprint(task2) {
|
|
20505
|
+
return { task: task2, ...fingerprintContent(task2) };
|
|
20506
|
+
}
|
|
20419
20507
|
function intersects(left, right) {
|
|
20420
20508
|
for (const value of left)
|
|
20421
20509
|
if (right.has(value))
|
|
@@ -23602,6 +23690,13 @@ var TODOS_PACKAGE_EXPORTS = [
|
|
|
23602
23690
|
types: "./dist/storage.d.ts",
|
|
23603
23691
|
description: "Storage and service adapter contracts for local SQLite implementations.",
|
|
23604
23692
|
stability: "stable"
|
|
23693
|
+
},
|
|
23694
|
+
{
|
|
23695
|
+
subpath: "./testing",
|
|
23696
|
+
import: "./dist/testing.js",
|
|
23697
|
+
types: "./dist/testing.d.ts",
|
|
23698
|
+
description: "Test-store isolation helpers that keep a consumer's test suite off a shared todos store.",
|
|
23699
|
+
stability: "stable"
|
|
23605
23700
|
}
|
|
23606
23701
|
];
|
|
23607
23702
|
function source8(version) {
|
|
@@ -23633,6 +23728,9 @@ var TODOS_REGISTRY = createTodosRegistry({
|
|
|
23633
23728
|
// src/storage/index.ts
|
|
23634
23729
|
init_config();
|
|
23635
23730
|
|
|
23731
|
+
// src/storage/local-sqlite.ts
|
|
23732
|
+
init_types();
|
|
23733
|
+
|
|
23636
23734
|
// src/lib/search.ts
|
|
23637
23735
|
init_database();
|
|
23638
23736
|
function rowToTask3(row) {
|
|
@@ -23798,6 +23896,11 @@ init_database();
|
|
|
23798
23896
|
init_identity_mapping();
|
|
23799
23897
|
init_storage_tombstones();
|
|
23800
23898
|
|
|
23899
|
+
// src/lib/agent-name-normalize.ts
|
|
23900
|
+
function normalizeAgentNameInput(name) {
|
|
23901
|
+
return name.trim().toLowerCase();
|
|
23902
|
+
}
|
|
23903
|
+
|
|
23801
23904
|
// src/db/agent-names.ts
|
|
23802
23905
|
class InvalidAgentNameError extends Error {
|
|
23803
23906
|
suggestions;
|
|
@@ -23942,9 +24045,6 @@ var RESERVED_GENERIC_NAMES = new Set([
|
|
|
23942
24045
|
]);
|
|
23943
24046
|
var NUMERIC_SUFFIX_RE = /[-_]\d+$/;
|
|
23944
24047
|
var ONE_WORD_NAME_RE = /^[a-z]+$/;
|
|
23945
|
-
function normalizeAgentNameInput(name) {
|
|
23946
|
-
return name.trim().toLowerCase();
|
|
23947
|
-
}
|
|
23948
24048
|
function hasGeneratedNumericSuffix(name) {
|
|
23949
24049
|
return NUMERIC_SUFFIX_RE.test(normalizeAgentNameInput(name));
|
|
23950
24050
|
}
|
|
@@ -24449,6 +24549,267 @@ init_audit();
|
|
|
24449
24549
|
init_comments();
|
|
24450
24550
|
init_database();
|
|
24451
24551
|
|
|
24552
|
+
// src/db/integrity.ts
|
|
24553
|
+
init_database();
|
|
24554
|
+
|
|
24555
|
+
// src/lib/integrity.ts
|
|
24556
|
+
init_types();
|
|
24557
|
+
var TODOS_INTEGRITY_SCHEMA_VERSION = "todos.integrity.v1";
|
|
24558
|
+
var TERMINAL_TASK_STATUSES = ["completed", "failed", "cancelled"];
|
|
24559
|
+
var OPEN_TASK_STATUSES = TASK_STATUSES.filter((status) => !TERMINAL_TASK_STATUSES.includes(status));
|
|
24560
|
+
var INTEGRITY_CONDITIONS = [
|
|
24561
|
+
{
|
|
24562
|
+
id: "tasks_without_project",
|
|
24563
|
+
entity: "task",
|
|
24564
|
+
field: "project_id",
|
|
24565
|
+
target: "project",
|
|
24566
|
+
kind: "missing",
|
|
24567
|
+
base_severity: "warn",
|
|
24568
|
+
escalate_when_open: true,
|
|
24569
|
+
impact: "invisible to every project-scoped read (list, status, next, claim)"
|
|
24570
|
+
},
|
|
24571
|
+
{
|
|
24572
|
+
id: "tasks_without_task_list",
|
|
24573
|
+
entity: "task",
|
|
24574
|
+
field: "task_list_id",
|
|
24575
|
+
target: "task_list",
|
|
24576
|
+
kind: "missing",
|
|
24577
|
+
base_severity: "warn",
|
|
24578
|
+
escalate_when_open: true,
|
|
24579
|
+
impact: "invisible to every task-list read \u2014 the list reports zero open work"
|
|
24580
|
+
},
|
|
24581
|
+
{
|
|
24582
|
+
id: "tasks_with_unregistered_project",
|
|
24583
|
+
entity: "task",
|
|
24584
|
+
field: "project_id",
|
|
24585
|
+
target: "project",
|
|
24586
|
+
kind: "dangling",
|
|
24587
|
+
base_severity: "error",
|
|
24588
|
+
escalate_when_open: false,
|
|
24589
|
+
impact: "points at a project id that does not exist; the reference can never resolve"
|
|
24590
|
+
},
|
|
24591
|
+
{
|
|
24592
|
+
id: "tasks_with_unregistered_task_list",
|
|
24593
|
+
entity: "task",
|
|
24594
|
+
field: "task_list_id",
|
|
24595
|
+
target: "task_list",
|
|
24596
|
+
kind: "dangling",
|
|
24597
|
+
base_severity: "error",
|
|
24598
|
+
escalate_when_open: false,
|
|
24599
|
+
impact: "points at a task-list id that does not exist; the reference can never resolve"
|
|
24600
|
+
},
|
|
24601
|
+
{
|
|
24602
|
+
id: "task_lists_without_project",
|
|
24603
|
+
entity: "task_list",
|
|
24604
|
+
field: "project_id",
|
|
24605
|
+
target: "project",
|
|
24606
|
+
kind: "missing",
|
|
24607
|
+
base_severity: "warn",
|
|
24608
|
+
escalate_when_open: false,
|
|
24609
|
+
impact: "unbound list \u2014 unreachable from any project, so its tasks are unroutable"
|
|
24610
|
+
},
|
|
24611
|
+
{
|
|
24612
|
+
id: "task_lists_with_unregistered_project",
|
|
24613
|
+
entity: "task_list",
|
|
24614
|
+
field: "project_id",
|
|
24615
|
+
target: "project",
|
|
24616
|
+
kind: "dangling",
|
|
24617
|
+
base_severity: "error",
|
|
24618
|
+
escalate_when_open: false,
|
|
24619
|
+
impact: "points at a project id that does not exist; the list can never be reached"
|
|
24620
|
+
}
|
|
24621
|
+
];
|
|
24622
|
+
function integrityCondition(id) {
|
|
24623
|
+
const spec = INTEGRITY_CONDITIONS.find((condition) => condition.id === id);
|
|
24624
|
+
if (!spec)
|
|
24625
|
+
throw new Error(`unknown integrity condition: ${id}`);
|
|
24626
|
+
return spec;
|
|
24627
|
+
}
|
|
24628
|
+
function resolveIntegritySeverity(spec, measurement) {
|
|
24629
|
+
if (spec.kind === "dangling")
|
|
24630
|
+
return "error";
|
|
24631
|
+
if (spec.escalate_when_open && (measurement.open_count ?? 0) > 0)
|
|
24632
|
+
return "error";
|
|
24633
|
+
return spec.base_severity;
|
|
24634
|
+
}
|
|
24635
|
+
var ENTITY_LABEL = {
|
|
24636
|
+
task: ["task", "tasks"],
|
|
24637
|
+
task_list: ["task list", "task lists"]
|
|
24638
|
+
};
|
|
24639
|
+
function plural(entity, count2) {
|
|
24640
|
+
const [one, many] = ENTITY_LABEL[entity];
|
|
24641
|
+
return count2 === 1 ? one : many;
|
|
24642
|
+
}
|
|
24643
|
+
function formatIntegrityMessage(spec, measurement) {
|
|
24644
|
+
const noun = plural(spec.entity, measurement.count);
|
|
24645
|
+
const verb = measurement.count === 1 ? ["has", "references"] : ["have", "reference"];
|
|
24646
|
+
const open = measurement.open_count === null || measurement.open_count === 0 ? "" : ` (${measurement.open_count} still open)`;
|
|
24647
|
+
return spec.kind === "missing" ? `${measurement.count} ${noun} ${verb[0]} no ${spec.field}${open}` : `${measurement.count} ${noun} ${verb[1]} a ${spec.target.replace("_", " ")} that is not registered${open}`;
|
|
24648
|
+
}
|
|
24649
|
+
function measuredCondition(spec, measurement, source9) {
|
|
24650
|
+
return {
|
|
24651
|
+
id: spec.id,
|
|
24652
|
+
entity: spec.entity,
|
|
24653
|
+
field: spec.field,
|
|
24654
|
+
kind: spec.kind,
|
|
24655
|
+
count: measurement.count,
|
|
24656
|
+
open_count: measurement.open_count,
|
|
24657
|
+
severity: measurement.count > 0 ? resolveIntegritySeverity(spec, measurement) : null,
|
|
24658
|
+
verified: true,
|
|
24659
|
+
source: source9,
|
|
24660
|
+
message: formatIntegrityMessage(spec, measurement),
|
|
24661
|
+
impact: spec.impact
|
|
24662
|
+
};
|
|
24663
|
+
}
|
|
24664
|
+
function unverifiedCondition(spec, reason) {
|
|
24665
|
+
return {
|
|
24666
|
+
id: spec.id,
|
|
24667
|
+
entity: spec.entity,
|
|
24668
|
+
field: spec.field,
|
|
24669
|
+
kind: spec.kind,
|
|
24670
|
+
count: null,
|
|
24671
|
+
open_count: null,
|
|
24672
|
+
severity: null,
|
|
24673
|
+
verified: false,
|
|
24674
|
+
source: "unverified",
|
|
24675
|
+
unverified_reason: reason,
|
|
24676
|
+
message: `${spec.id}: NOT CHECKED \u2014 ${reason}`,
|
|
24677
|
+
impact: spec.impact
|
|
24678
|
+
};
|
|
24679
|
+
}
|
|
24680
|
+
function summarizeIntegrity(conditions) {
|
|
24681
|
+
const measured = conditions.filter((condition) => condition.verified && condition.count !== null);
|
|
24682
|
+
const findings = measured.filter((condition) => (condition.count ?? 0) > 0);
|
|
24683
|
+
const unverified = conditions.length - measured.length;
|
|
24684
|
+
return {
|
|
24685
|
+
ok: unverified === 0 && findings.length === 0,
|
|
24686
|
+
findings: findings.length,
|
|
24687
|
+
rows: findings.reduce((total, condition) => total + (condition.count ?? 0), 0),
|
|
24688
|
+
errors: findings.filter((condition) => condition.severity === "error").length,
|
|
24689
|
+
warnings: findings.filter((condition) => condition.severity === "warn").length,
|
|
24690
|
+
unverified,
|
|
24691
|
+
complete: unverified === 0
|
|
24692
|
+
};
|
|
24693
|
+
}
|
|
24694
|
+
function buildIntegrityReport(conditions, generatedAt) {
|
|
24695
|
+
const measuredSources = [...new Set(conditions.map((condition) => condition.source))].filter((source9) => source9 !== "unverified");
|
|
24696
|
+
return {
|
|
24697
|
+
schema_version: TODOS_INTEGRITY_SCHEMA_VERSION,
|
|
24698
|
+
generated_at: generatedAt,
|
|
24699
|
+
source: measuredSources.length === 0 ? "unverified" : measuredSources.length === 1 ? measuredSources[0] : "remote-derived",
|
|
24700
|
+
conditions,
|
|
24701
|
+
summary: summarizeIntegrity(conditions)
|
|
24702
|
+
};
|
|
24703
|
+
}
|
|
24704
|
+
function adoptRemoteIntegrityReport(raw, fallbackGeneratedAt) {
|
|
24705
|
+
const received = new Map;
|
|
24706
|
+
if (Array.isArray(raw.conditions)) {
|
|
24707
|
+
for (const entry of raw.conditions) {
|
|
24708
|
+
if (entry && typeof entry["id"] === "string")
|
|
24709
|
+
received.set(entry["id"], entry);
|
|
24710
|
+
}
|
|
24711
|
+
}
|
|
24712
|
+
const conditions = INTEGRITY_CONDITIONS.map((spec) => {
|
|
24713
|
+
const entry = received.get(spec.id);
|
|
24714
|
+
const count2 = entry?.["count"];
|
|
24715
|
+
const verified = entry?.["verified"];
|
|
24716
|
+
if (!entry || verified === false || typeof count2 !== "number" || !Number.isFinite(count2)) {
|
|
24717
|
+
return unverifiedCondition(spec, entry ? typeof entry["unverified_reason"] === "string" ? String(entry["unverified_reason"]) : "authority reported this condition without a usable count" : "authority did not report this condition");
|
|
24718
|
+
}
|
|
24719
|
+
const openRaw = entry["open_count"];
|
|
24720
|
+
return measuredCondition(spec, {
|
|
24721
|
+
count: count2,
|
|
24722
|
+
open_count: spec.entity === "task" ? typeof openRaw === "number" && Number.isFinite(openRaw) ? openRaw : 0 : null
|
|
24723
|
+
}, typeof raw.source === "string" && raw.source === "postgres" ? "postgres" : "remote-authority");
|
|
24724
|
+
});
|
|
24725
|
+
const report = buildIntegrityReport(conditions, typeof raw.generated_at === "string" ? raw.generated_at : fallbackGeneratedAt);
|
|
24726
|
+
return typeof raw.source === "string" && (raw.source === "sqlite" || raw.source === "postgres") ? { ...report, source: raw.source } : report;
|
|
24727
|
+
}
|
|
24728
|
+
var SQLITE_TABLE = {
|
|
24729
|
+
task: "tasks",
|
|
24730
|
+
task_list: "task_lists",
|
|
24731
|
+
project: "projects"
|
|
24732
|
+
};
|
|
24733
|
+
function sqliteOpenStatusList() {
|
|
24734
|
+
return OPEN_TASK_STATUSES.map((status) => `'${status}'`).join(", ");
|
|
24735
|
+
}
|
|
24736
|
+
function buildSqliteIntegritySql(spec) {
|
|
24737
|
+
const table = SQLITE_TABLE[spec.entity];
|
|
24738
|
+
const target = SQLITE_TABLE[spec.target];
|
|
24739
|
+
const column = `t."${spec.field}"`;
|
|
24740
|
+
const predicate = spec.kind === "missing" ? `(${column} IS NULL OR ${column} = '')` : `${column} IS NOT NULL AND ${column} <> '' ` + `AND NOT EXISTS (SELECT 1 FROM "${target}" r WHERE r."id" = ${column})`;
|
|
24741
|
+
const openExpr = spec.entity === "task" ? `SUM(CASE WHEN t."status" IN (${sqliteOpenStatusList()}) THEN 1 ELSE 0 END)` : "NULL";
|
|
24742
|
+
return `SELECT COUNT(*) AS count, ${openExpr} AS open_count FROM "${table}" t WHERE ${predicate}`;
|
|
24743
|
+
}
|
|
24744
|
+
var RECORD_TYPE = {
|
|
24745
|
+
task: "tasks",
|
|
24746
|
+
task_list: "task_lists",
|
|
24747
|
+
project: "projects"
|
|
24748
|
+
};
|
|
24749
|
+
function buildPostgresIntegritySql(spec, options) {
|
|
24750
|
+
const params = [options.service, RECORD_TYPE[spec.entity]];
|
|
24751
|
+
const p = (value) => {
|
|
24752
|
+
params.push(value);
|
|
24753
|
+
return `$${params.length}`;
|
|
24754
|
+
};
|
|
24755
|
+
const column = `t.payload->>'${spec.field}'`;
|
|
24756
|
+
const predicate = spec.kind === "missing" ? `(${column} IS NULL OR ${column} = '')` : `${column} IS NOT NULL AND ${column} <> '' AND NOT EXISTS (` + `SELECT 1 FROM ${options.table} r WHERE r.service = $1 AND r.object_type = ${p(RECORD_TYPE[spec.target])} ` + `AND r.deleted_at IS NULL AND r.object_id = ${column})`;
|
|
24757
|
+
const openExpr = spec.entity === "task" ? `COUNT(*) FILTER (WHERE t.payload->>'status' IN (${OPEN_TASK_STATUSES.map((status) => p(status)).join(", ")}))::int` : "NULL::int";
|
|
24758
|
+
const sql = `/* todos:integrity-${spec.id} */ SELECT COUNT(*)::int AS count, ${openExpr} AS open_count ` + `FROM ${options.table} t WHERE t.service = $1 AND t.object_type = $2 AND t.deleted_at IS NULL AND ${predicate}`;
|
|
24759
|
+
return { sql, params };
|
|
24760
|
+
}
|
|
24761
|
+
function referenceOf(row, field2) {
|
|
24762
|
+
const raw = row[field2];
|
|
24763
|
+
if (typeof raw !== "string")
|
|
24764
|
+
return null;
|
|
24765
|
+
const trimmed = raw.trim();
|
|
24766
|
+
return trimmed === "" ? null : trimmed;
|
|
24767
|
+
}
|
|
24768
|
+
function measureIntegrityRows(spec, sets) {
|
|
24769
|
+
const rows = spec.entity === "task" ? sets.tasks : sets.taskLists;
|
|
24770
|
+
if (!rows)
|
|
24771
|
+
return null;
|
|
24772
|
+
const registered = spec.kind === "dangling" ? spec.target === "project" ? sets.projectIds : sets.taskListIds : undefined;
|
|
24773
|
+
if (spec.kind === "dangling" && !registered)
|
|
24774
|
+
return null;
|
|
24775
|
+
let count2 = 0;
|
|
24776
|
+
let open = 0;
|
|
24777
|
+
for (const row of rows) {
|
|
24778
|
+
const reference = referenceOf(row, spec.field);
|
|
24779
|
+
const matches = spec.kind === "missing" ? reference === null : reference !== null && !registered.has(reference);
|
|
24780
|
+
if (!matches)
|
|
24781
|
+
continue;
|
|
24782
|
+
count2++;
|
|
24783
|
+
if (spec.entity === "task") {
|
|
24784
|
+
const status = row.status;
|
|
24785
|
+
if (typeof status === "string" && OPEN_TASK_STATUSES.includes(status))
|
|
24786
|
+
open++;
|
|
24787
|
+
}
|
|
24788
|
+
}
|
|
24789
|
+
return { count: count2, open_count: spec.entity === "task" ? open : null };
|
|
24790
|
+
}
|
|
24791
|
+
|
|
24792
|
+
// src/db/integrity.ts
|
|
24793
|
+
var REQUIRED_TABLES2 = { tasks: true, task_lists: true, projects: true };
|
|
24794
|
+
function tableExists(db, table) {
|
|
24795
|
+
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
24796
|
+
}
|
|
24797
|
+
function scanSqliteIntegrity(db = getDatabase()) {
|
|
24798
|
+
const missing = Object.keys(REQUIRED_TABLES2).filter((table) => !tableExists(db, table));
|
|
24799
|
+
const conditions = INTEGRITY_CONDITIONS.map((spec) => {
|
|
24800
|
+
if (missing.length > 0) {
|
|
24801
|
+
return unverifiedCondition(spec, `local schema is missing table(s): ${missing.join(", ")}`);
|
|
24802
|
+
}
|
|
24803
|
+
try {
|
|
24804
|
+
const row = db.query(buildSqliteIntegritySql(spec)).get();
|
|
24805
|
+
return measuredCondition(spec, { count: Number(row?.count ?? 0), open_count: spec.entity === "task" ? Number(row?.open_count ?? 0) : null }, "sqlite");
|
|
24806
|
+
} catch (error) {
|
|
24807
|
+
return unverifiedCondition(spec, error instanceof Error ? error.message : String(error));
|
|
24808
|
+
}
|
|
24809
|
+
});
|
|
24810
|
+
return buildIntegrityReport(conditions, now());
|
|
24811
|
+
}
|
|
24812
|
+
|
|
24452
24813
|
// src/storage/sqlite-snapshot.ts
|
|
24453
24814
|
init_database();
|
|
24454
24815
|
init_audit();
|
|
@@ -24840,15 +25201,15 @@ function resolveTaskRefLocal(db, ref) {
|
|
|
24840
25201
|
return null;
|
|
24841
25202
|
if (TASK_UUID_RE.test(raw))
|
|
24842
25203
|
return getTask(raw, db);
|
|
24843
|
-
const prefixRows = db.query("SELECT id FROM tasks WHERE LOWER(id) LIKE ? ESCAPE '\\' LIMIT 2").all(`${raw.replace(/[\\%_]/g, (c) => `\\${c}`)}%`);
|
|
25204
|
+
const prefixRows = db.query("SELECT id, project_id FROM tasks WHERE LOWER(id) LIKE ? ESCAPE '\\' ORDER BY project_id, id LIMIT 2").all(`${raw.replace(/[\\%_]/g, (c) => `\\${c}`)}%`);
|
|
24844
25205
|
if (prefixRows.length > 1) {
|
|
24845
|
-
throw new
|
|
25206
|
+
throw new TaskReferenceAmbiguousError(ref, prefixRows.map((row) => ({ task_id: row.id, project_id: row.project_id })));
|
|
24846
25207
|
}
|
|
24847
25208
|
if (prefixRows.length === 1)
|
|
24848
25209
|
return getTask(prefixRows[0].id, db);
|
|
24849
|
-
const shortIdRows = db.query("SELECT id FROM tasks WHERE LOWER(short_id) = ? LIMIT 2").all(raw);
|
|
25210
|
+
const shortIdRows = db.query("SELECT id, project_id FROM tasks WHERE LOWER(short_id) = ? ORDER BY project_id, id LIMIT 2").all(raw);
|
|
24850
25211
|
if (shortIdRows.length > 1) {
|
|
24851
|
-
throw new
|
|
25212
|
+
throw new TaskReferenceAmbiguousError(ref, shortIdRows.map((row) => ({ task_id: row.id, project_id: row.project_id })));
|
|
24852
25213
|
}
|
|
24853
25214
|
if (shortIdRows.length === 1)
|
|
24854
25215
|
return getTask(shortIdRows[0].id, db);
|
|
@@ -24997,6 +25358,9 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
24997
25358
|
exportSnapshot: () => exportSqliteTodosStorageSnapshot(database()),
|
|
24998
25359
|
importSnapshot: (snapshot) => importSqliteTodosStorageSnapshot(snapshot, database())
|
|
24999
25360
|
},
|
|
25361
|
+
integrity: {
|
|
25362
|
+
report: () => scanSqliteIntegrity(database())
|
|
25363
|
+
},
|
|
25000
25364
|
transaction: (fn) => {
|
|
25001
25365
|
const tx = database().transaction(() => fn(adapter));
|
|
25002
25366
|
return tx();
|
|
@@ -25005,6 +25369,10 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
25005
25369
|
return adapter;
|
|
25006
25370
|
}
|
|
25007
25371
|
|
|
25372
|
+
// src/storage/postgres-adapter.ts
|
|
25373
|
+
init_types();
|
|
25374
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
25375
|
+
|
|
25008
25376
|
// src/storage/postgres-sync.ts
|
|
25009
25377
|
var DEFAULT_TODOS_POSTGRES_SYNC_TABLE = "todos_sync_records";
|
|
25010
25378
|
var DEFAULT_TODOS_POSTGRES_CURSOR_TABLE = "todos_sync_cursors";
|
|
@@ -25394,54 +25762,7 @@ function assertSafeIdentifier(value) {
|
|
|
25394
25762
|
throw new Error(`Unsafe Postgres identifier: ${value}`);
|
|
25395
25763
|
}
|
|
25396
25764
|
|
|
25397
|
-
// src/storage/hybrid.ts
|
|
25398
|
-
function createHybridTodosStorageAdapter(options) {
|
|
25399
|
-
const local = options.localAdapter ?? createLocalSqliteTodosStorageAdapter(options.local);
|
|
25400
|
-
const syncStore = options.syncStore ?? (options.postgresClient ? createPostgresTodosSyncStore(options.postgresClient, { sourceMachineId: options.sourceMachineId }) : null);
|
|
25401
|
-
if (!syncStore)
|
|
25402
|
-
throw new Error("hybrid storage requires a Postgres sync store or query client");
|
|
25403
|
-
if (!local.sync.exportSnapshot || !local.sync.importSnapshot) {
|
|
25404
|
-
throw new Error("hybrid storage requires local snapshot export/import support");
|
|
25405
|
-
}
|
|
25406
|
-
const exportSnapshot = async (context) => await local.sync.exportSnapshot(context);
|
|
25407
|
-
const importSnapshot = async (snapshot, context) => await local.sync.importSnapshot(snapshot, context);
|
|
25408
|
-
const remote = {
|
|
25409
|
-
ensureSchema: () => syncStore.ensureSchema(),
|
|
25410
|
-
async pushSnapshot(context) {
|
|
25411
|
-
const snapshot = await exportSnapshot(context);
|
|
25412
|
-
return syncStore.pushSnapshot(snapshot, context);
|
|
25413
|
-
},
|
|
25414
|
-
async pullSnapshot(options2, context) {
|
|
25415
|
-
const snapshot = await syncStore.pullSnapshot(options2);
|
|
25416
|
-
return importSnapshot(snapshot, context);
|
|
25417
|
-
},
|
|
25418
|
-
async syncOnce(options2, context) {
|
|
25419
|
-
const pulled = await remote.pullSnapshot(options2, context);
|
|
25420
|
-
const pushed = await remote.pushSnapshot(context);
|
|
25421
|
-
return { pulled, pushed };
|
|
25422
|
-
}
|
|
25423
|
-
};
|
|
25424
|
-
return {
|
|
25425
|
-
...local,
|
|
25426
|
-
kind: "hybrid",
|
|
25427
|
-
capabilities: {
|
|
25428
|
-
...local.capabilities,
|
|
25429
|
-
localPersistence: true,
|
|
25430
|
-
remotePersistence: true,
|
|
25431
|
-
sync: true
|
|
25432
|
-
},
|
|
25433
|
-
sync: {
|
|
25434
|
-
...local.sync,
|
|
25435
|
-
exportSnapshot,
|
|
25436
|
-
importSnapshot
|
|
25437
|
-
},
|
|
25438
|
-
remote
|
|
25439
|
-
};
|
|
25440
|
-
}
|
|
25441
|
-
|
|
25442
25765
|
// src/storage/postgres-adapter.ts
|
|
25443
|
-
init_types();
|
|
25444
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
25445
25766
|
init_redaction();
|
|
25446
25767
|
function createPostgresTodosStorageAdapter(options) {
|
|
25447
25768
|
const store = new PostgresJsonRecordStore(options);
|
|
@@ -25512,7 +25833,7 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
25512
25833
|
agents: {
|
|
25513
25834
|
register: (input, context) => registerAgent2(input, store, context),
|
|
25514
25835
|
get: (id) => store.get("agents", id),
|
|
25515
|
-
getByName: async (name) => (await store.list("agents")
|
|
25836
|
+
getByName: async (name) => matchAgentByName(await store.list("agents"), name),
|
|
25516
25837
|
list: async (options2) => (await store.list("agents")).filter((agent) => options2?.include_archived || agent.status !== "archived").sort((a, b) => a.name.localeCompare(b.name)),
|
|
25517
25838
|
update: (id, input) => updateAgent2(id, input, store),
|
|
25518
25839
|
heartbeat: (idOrName, context) => heartbeatAgent(idOrName, store, context),
|
|
@@ -25569,6 +25890,9 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
25569
25890
|
exportSnapshot: () => exportSnapshot(store),
|
|
25570
25891
|
importSnapshot: (snapshot, context) => importSnapshot(snapshot, store, context)
|
|
25571
25892
|
},
|
|
25893
|
+
integrity: {
|
|
25894
|
+
report: () => store.integrityReport()
|
|
25895
|
+
},
|
|
25572
25896
|
transaction: (fn) => fn(adapter)
|
|
25573
25897
|
};
|
|
25574
25898
|
return adapter;
|
|
@@ -25675,8 +25999,9 @@ class PostgresJsonRecordStore {
|
|
|
25675
25999
|
conds.push(`payload->>'agent_id' = ${p(filter.agent_id)}`);
|
|
25676
26000
|
if (filter.session_id !== undefined)
|
|
25677
26001
|
conds.push(`payload->>'session_id' = ${p(filter.session_id)}`);
|
|
25678
|
-
if (filter.tags?.length)
|
|
25679
|
-
conds.push(`payload->'tags'
|
|
26002
|
+
if (filter.tags?.length) {
|
|
26003
|
+
conds.push(`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(payload->'tags', '[]'::jsonb)) AS task_tags(tag) ` + `WHERE ${inClause("task_tags.tag", filter.tags)})`);
|
|
26004
|
+
}
|
|
25680
26005
|
if (filter.has_recurrence !== undefined) {
|
|
25681
26006
|
conds.push(`(COALESCE(payload->>'recurrence_rule', '') <> '') = ${p(filter.has_recurrence)}`);
|
|
25682
26007
|
}
|
|
@@ -25737,7 +26062,8 @@ class PostgresJsonRecordStore {
|
|
|
25737
26062
|
AND object_id COLLATE "C" >= $2 AND object_id COLLATE "C" < $3
|
|
25738
26063
|
LIMIT 2`, [this.service, raw, upper]);
|
|
25739
26064
|
if (prefixResult.rows.length > 1) {
|
|
25740
|
-
|
|
26065
|
+
const candidates = prefixResult.rows.map((row) => payloadRecord2(row.payload));
|
|
26066
|
+
throw new TaskReferenceAmbiguousError(ref, candidates.map((task2) => ({ task_id: task2.id, project_id: task2.project_id })));
|
|
25741
26067
|
}
|
|
25742
26068
|
if (prefixResult.rows.length === 1) {
|
|
25743
26069
|
return payloadRecord2(prefixResult.rows[0].payload);
|
|
@@ -25748,7 +26074,8 @@ class PostgresJsonRecordStore {
|
|
|
25748
26074
|
AND LOWER(payload->>'short_id') = $2
|
|
25749
26075
|
LIMIT 2`, [this.service, raw]);
|
|
25750
26076
|
if (shortIdResult.rows.length > 1) {
|
|
25751
|
-
|
|
26077
|
+
const candidates = shortIdResult.rows.map((row) => payloadRecord2(row.payload));
|
|
26078
|
+
throw new TaskReferenceAmbiguousError(ref, candidates.map((task2) => ({ task_id: task2.id, project_id: task2.project_id })));
|
|
25752
26079
|
}
|
|
25753
26080
|
if (shortIdResult.rows.length === 1) {
|
|
25754
26081
|
return payloadRecord2(shortIdResult.rows[0].payload);
|
|
@@ -25762,6 +26089,24 @@ class PostgresJsonRecordStore {
|
|
|
25762
26089
|
const result = await this.options.client.query(sql, params);
|
|
25763
26090
|
return Number(result.rows[0]?.count ?? 0);
|
|
25764
26091
|
}
|
|
26092
|
+
async integrityReport() {
|
|
26093
|
+
await this.ensureSchema();
|
|
26094
|
+
const conditions = [];
|
|
26095
|
+
for (const spec of INTEGRITY_CONDITIONS) {
|
|
26096
|
+
const { sql, params } = buildPostgresIntegritySql(spec, { table: this.tableName, service: this.service });
|
|
26097
|
+
try {
|
|
26098
|
+
const result = await this.options.client.query(sql, params);
|
|
26099
|
+
const row = result.rows[0];
|
|
26100
|
+
conditions.push(measuredCondition(spec, {
|
|
26101
|
+
count: Number(row?.count ?? 0),
|
|
26102
|
+
open_count: spec.entity === "task" ? Number(row?.open_count ?? 0) : null
|
|
26103
|
+
}, "postgres"));
|
|
26104
|
+
} catch (error) {
|
|
26105
|
+
conditions.push(unverifiedCondition(spec, error instanceof Error ? error.message : String(error)));
|
|
26106
|
+
}
|
|
26107
|
+
}
|
|
26108
|
+
return buildIntegrityReport(conditions, new Date().toISOString());
|
|
26109
|
+
}
|
|
25765
26110
|
async listTombstones() {
|
|
25766
26111
|
await this.ensureSchema();
|
|
25767
26112
|
const result = await this.options.client.query(`SELECT object_type, object_id, payload, updated_at, deleted_at, source_machine_id, version
|
|
@@ -26237,7 +26582,7 @@ async function startTask2(id, agentId, store) {
|
|
|
26237
26582
|
if (task2.status !== "pending" && task2.status !== "in_progress") {
|
|
26238
26583
|
throw new Error(`Task is ${task2.status} and cannot be started by ${agentId}`);
|
|
26239
26584
|
}
|
|
26240
|
-
|
|
26585
|
+
const started = await patchTask(task2, {
|
|
26241
26586
|
status: "in_progress",
|
|
26242
26587
|
assigned_to: task2.assigned_to ?? agentId,
|
|
26243
26588
|
agent_id: task2.agent_id ?? agentId,
|
|
@@ -26245,6 +26590,8 @@ async function startTask2(id, agentId, store) {
|
|
|
26245
26590
|
locked_at: new Date().toISOString(),
|
|
26246
26591
|
started_at: task2.started_at ?? new Date().toISOString()
|
|
26247
26592
|
}, store);
|
|
26593
|
+
await logTaskChange2(task2.id, "start", "status", task2.status, "in_progress", agentId, store);
|
|
26594
|
+
return started;
|
|
26248
26595
|
}
|
|
26249
26596
|
async function completeTask2(id, agentId, options, store) {
|
|
26250
26597
|
const task2 = await store.completeTask(id, agentId, options);
|
|
@@ -26550,15 +26897,27 @@ async function updatePlan2(id, input, store) {
|
|
|
26550
26897
|
}
|
|
26551
26898
|
return store.upsert("plans", { ...plan, ...patch, updated_at: new Date().toISOString() });
|
|
26552
26899
|
}
|
|
26900
|
+
function matchAgentByName(agents, name, options) {
|
|
26901
|
+
const target = normalizeAgentNameInput(name);
|
|
26902
|
+
if (!target)
|
|
26903
|
+
return null;
|
|
26904
|
+
const matches = agents.filter((agent) => normalizeAgentNameInput(agent.name) === target && (options?.includeArchived !== false || agent.status !== "archived"));
|
|
26905
|
+
if (matches.length === 0)
|
|
26906
|
+
return null;
|
|
26907
|
+
return matches.reduce((freshest, candidate) => new Date(candidate.last_seen_at).getTime() > new Date(freshest.last_seen_at).getTime() ? candidate : freshest);
|
|
26908
|
+
}
|
|
26553
26909
|
async function registerAgent2(input, store, context) {
|
|
26554
|
-
const
|
|
26910
|
+
const canonicalName = normalizeAgentNameInput(input.name);
|
|
26911
|
+
const existing = matchAgentByName(await store.list("agents"), canonicalName, {
|
|
26912
|
+
includeArchived: false
|
|
26913
|
+
});
|
|
26555
26914
|
if (existing && !input.force && existing.session_id && existing.session_id !== input.session_id) {
|
|
26556
|
-
return { conflict: true, message: `Agent name '${
|
|
26915
|
+
return { conflict: true, message: `Agent name '${canonicalName}' is already active` };
|
|
26557
26916
|
}
|
|
26558
26917
|
const timestamp3 = new Date().toISOString();
|
|
26559
26918
|
const agent = {
|
|
26560
26919
|
id: existing?.id ?? randomUUID3().slice(0, 8),
|
|
26561
|
-
name:
|
|
26920
|
+
name: canonicalName,
|
|
26562
26921
|
description: input.description ?? existing?.description ?? null,
|
|
26563
26922
|
role: input.role ?? existing?.role ?? null,
|
|
26564
26923
|
title: input.title ?? existing?.title ?? null,
|
|
@@ -26596,7 +26955,7 @@ async function resolveAgent(idOrName, store) {
|
|
|
26596
26955
|
const byId = await store.get("agents", idOrName);
|
|
26597
26956
|
if (byId)
|
|
26598
26957
|
return byId;
|
|
26599
|
-
return (await store.list("agents")
|
|
26958
|
+
return matchAgentByName(await store.list("agents"), idOrName);
|
|
26600
26959
|
}
|
|
26601
26960
|
async function heartbeatAgent(idOrName, store, context) {
|
|
26602
26961
|
const agent = await resolveAgent(idOrName, store);
|
|
@@ -27353,17 +27712,17 @@ function createTodosCloudQueryClientFromEnv(env = process.env, options = {}) {
|
|
|
27353
27712
|
init_config();
|
|
27354
27713
|
function createTodosStorageAdapter(options = {}) {
|
|
27355
27714
|
const config = options.config ?? loadTodosStorageConfig(options.env);
|
|
27356
|
-
if (config.mode === "
|
|
27715
|
+
if (config.mode === "sqlite") {
|
|
27357
27716
|
if (isTodosShadowEnabled(options.env))
|
|
27358
27717
|
return createShadowAdapter(options, config);
|
|
27359
27718
|
return createLocalSqliteTodosStorageAdapter(options.local);
|
|
27360
27719
|
}
|
|
27361
27720
|
assertTodosRemoteStorageConfig(config);
|
|
27362
|
-
const adapter =
|
|
27721
|
+
const adapter = options.remoteAdapter ?? createImplicitPostgresAdapter(options);
|
|
27363
27722
|
if (!adapter) {
|
|
27364
|
-
throw new Error(
|
|
27723
|
+
throw new Error("postgres storage requires a repo-native Postgres adapter. " + "Pass remoteAdapter or postgresClient after wiring Postgres RDS support.");
|
|
27365
27724
|
}
|
|
27366
|
-
|
|
27725
|
+
assertPostgresAdapterCapabilities(adapter);
|
|
27367
27726
|
return adapter;
|
|
27368
27727
|
}
|
|
27369
27728
|
function createShadowAdapter(options, config) {
|
|
@@ -27377,7 +27736,7 @@ function createShadowAdapter(options, config) {
|
|
|
27377
27736
|
...options.local ? { local: options.local } : {},
|
|
27378
27737
|
...options.postgresSyncStore ? { syncStore: options.postgresSyncStore } : {},
|
|
27379
27738
|
...postgresClient ? { postgresClient } : {},
|
|
27380
|
-
...options.
|
|
27739
|
+
...options.sourceMachineId ? { sourceMachineId: options.sourceMachineId } : {}
|
|
27381
27740
|
});
|
|
27382
27741
|
}
|
|
27383
27742
|
function createImplicitPostgresAdapter(options) {
|
|
@@ -27385,25 +27744,12 @@ function createImplicitPostgresAdapter(options) {
|
|
|
27385
27744
|
return null;
|
|
27386
27745
|
return createPostgresTodosStorageAdapter({
|
|
27387
27746
|
client: options.postgresClient,
|
|
27388
|
-
...options.
|
|
27389
|
-
});
|
|
27390
|
-
}
|
|
27391
|
-
function createImplicitHybridAdapter(options) {
|
|
27392
|
-
if (!options.postgresClient && !options.postgresSyncStore)
|
|
27393
|
-
return null;
|
|
27394
|
-
return createHybridTodosStorageAdapter({
|
|
27395
|
-
...options.hybrid ?? {},
|
|
27396
|
-
local: options.local,
|
|
27397
|
-
postgresClient: options.postgresClient,
|
|
27398
|
-
syncStore: options.postgresSyncStore
|
|
27747
|
+
...options.sourceMachineId ? { sourceMachineId: options.sourceMachineId } : {}
|
|
27399
27748
|
});
|
|
27400
27749
|
}
|
|
27401
|
-
function
|
|
27750
|
+
function assertPostgresAdapterCapabilities(adapter) {
|
|
27402
27751
|
if (!adapter.capabilities.remotePersistence) {
|
|
27403
|
-
throw new Error(
|
|
27404
|
-
}
|
|
27405
|
-
if (mode === "hybrid" && !adapter.capabilities.localPersistence) {
|
|
27406
|
-
throw new Error("hybrid storage adapter must also set capabilities.localPersistence=true");
|
|
27752
|
+
throw new Error("postgres storage adapter must set capabilities.remotePersistence=true");
|
|
27407
27753
|
}
|
|
27408
27754
|
}
|
|
27409
27755
|
// src/storage/shadow-outbox.ts
|
|
@@ -27730,6 +28076,50 @@ async function closeRuntimeShadowCloud() {
|
|
|
27730
28076
|
await cloud.close().catch(() => {});
|
|
27731
28077
|
}
|
|
27732
28078
|
}
|
|
28079
|
+
// src/storage/hybrid.ts
|
|
28080
|
+
function createHybridTodosStorageAdapter(options) {
|
|
28081
|
+
const local = options.localAdapter ?? createLocalSqliteTodosStorageAdapter(options.local);
|
|
28082
|
+
const syncStore = options.syncStore ?? (options.postgresClient ? createPostgresTodosSyncStore(options.postgresClient, { sourceMachineId: options.sourceMachineId }) : null);
|
|
28083
|
+
if (!syncStore)
|
|
28084
|
+
throw new Error("hybrid storage requires a Postgres sync store or query client");
|
|
28085
|
+
if (!local.sync.exportSnapshot || !local.sync.importSnapshot) {
|
|
28086
|
+
throw new Error("hybrid storage requires local snapshot export/import support");
|
|
28087
|
+
}
|
|
28088
|
+
const exportSnapshot2 = async (context) => await local.sync.exportSnapshot(context);
|
|
28089
|
+
const importSnapshot2 = async (snapshot, context) => await local.sync.importSnapshot(snapshot, context);
|
|
28090
|
+
const remote = {
|
|
28091
|
+
ensureSchema: () => syncStore.ensureSchema(),
|
|
28092
|
+
async pushSnapshot(context) {
|
|
28093
|
+
const snapshot = await exportSnapshot2(context);
|
|
28094
|
+
return syncStore.pushSnapshot(snapshot, context);
|
|
28095
|
+
},
|
|
28096
|
+
async pullSnapshot(options2, context) {
|
|
28097
|
+
const snapshot = await syncStore.pullSnapshot(options2);
|
|
28098
|
+
return importSnapshot2(snapshot, context);
|
|
28099
|
+
},
|
|
28100
|
+
async syncOnce(options2, context) {
|
|
28101
|
+
const pulled = await remote.pullSnapshot(options2, context);
|
|
28102
|
+
const pushed = await remote.pushSnapshot(context);
|
|
28103
|
+
return { pulled, pushed };
|
|
28104
|
+
}
|
|
28105
|
+
};
|
|
28106
|
+
return {
|
|
28107
|
+
...local,
|
|
28108
|
+
kind: "hybrid",
|
|
28109
|
+
capabilities: {
|
|
28110
|
+
...local.capabilities,
|
|
28111
|
+
localPersistence: true,
|
|
28112
|
+
remotePersistence: true,
|
|
28113
|
+
sync: true
|
|
28114
|
+
},
|
|
28115
|
+
sync: {
|
|
28116
|
+
...local.sync,
|
|
28117
|
+
exportSnapshot: exportSnapshot2,
|
|
28118
|
+
importSnapshot: importSnapshot2
|
|
28119
|
+
},
|
|
28120
|
+
remote
|
|
28121
|
+
};
|
|
28122
|
+
}
|
|
27733
28123
|
// src/storage/comment-redaction-backfill.ts
|
|
27734
28124
|
init_redaction();
|
|
27735
28125
|
var COMMENT_REDACTION_BACKFILL_CONFIRMATION = "REDACT_STORED_TODOS_COMMENTS";
|
|
@@ -31308,10 +31698,10 @@ function getNativeStorageStatus(env = process.env) {
|
|
|
31308
31698
|
} catch (error) {
|
|
31309
31699
|
issues.push(error instanceof Error ? error.message : String(error));
|
|
31310
31700
|
}
|
|
31311
|
-
const remoteEnabled =
|
|
31701
|
+
const remoteEnabled = isTodosPostgresBackend(config);
|
|
31312
31702
|
const remoteFieldsConfigured = Boolean(config.database || config.objectStorage);
|
|
31313
31703
|
if (!remoteEnabled && remoteFieldsConfigured) {
|
|
31314
|
-
warnings.push(
|
|
31704
|
+
warnings.push(`the sqlite backend (${TODOS_STORAGE_ENV.mode}) ignores configured remote storage fields`);
|
|
31315
31705
|
}
|
|
31316
31706
|
if (remoteEnabled && !config.objectStorage) {
|
|
31317
31707
|
warnings.push(`${TODOS_STORAGE_ENV.s3Bucket} is not configured, so artifact sync will stay local`);
|
|
@@ -31320,7 +31710,7 @@ function getNativeStorageStatus(env = process.env) {
|
|
|
31320
31710
|
ok: issues.length === 0,
|
|
31321
31711
|
service: "todos",
|
|
31322
31712
|
mode: config.mode,
|
|
31323
|
-
local_default: config.mode === "
|
|
31713
|
+
local_default: config.mode === "sqlite",
|
|
31324
31714
|
remote_enabled: remoteEnabled,
|
|
31325
31715
|
database: {
|
|
31326
31716
|
configured: Boolean(config.database),
|
|
@@ -31402,8 +31792,12 @@ function storageEnvStatus(env) {
|
|
|
31402
31792
|
]));
|
|
31403
31793
|
}
|
|
31404
31794
|
function fallbackConfig(env) {
|
|
31405
|
-
|
|
31406
|
-
|
|
31795
|
+
let mode;
|
|
31796
|
+
try {
|
|
31797
|
+
mode = parseStorageBackend(clean2(env[TODOS_STORAGE_ENV.mode]));
|
|
31798
|
+
} catch {
|
|
31799
|
+
mode = "sqlite";
|
|
31800
|
+
}
|
|
31407
31801
|
return {
|
|
31408
31802
|
service: "todos",
|
|
31409
31803
|
mode,
|
|
@@ -50582,7 +50976,7 @@ function parseObject4(value) {
|
|
|
50582
50976
|
function normalizeKey(value) {
|
|
50583
50977
|
return value.trim().toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
50584
50978
|
}
|
|
50585
|
-
function
|
|
50979
|
+
function normalizeTaskFindingFingerprint(value) {
|
|
50586
50980
|
const normalized = normalizeKey(value);
|
|
50587
50981
|
if (!normalized)
|
|
50588
50982
|
throw new Error("finding fingerprint is required");
|
|
@@ -50691,7 +51085,7 @@ function assertTask(taskId, db) {
|
|
|
50691
51085
|
throw new TaskNotFoundError(taskId);
|
|
50692
51086
|
}
|
|
50693
51087
|
function nextFinding(input, db) {
|
|
50694
|
-
const fingerprint4 =
|
|
51088
|
+
const fingerprint4 = normalizeTaskFindingFingerprint(input.fingerprint);
|
|
50695
51089
|
const title = redactOptional(input.title, 300);
|
|
50696
51090
|
if (!title)
|
|
50697
51091
|
throw new Error("finding title is required");
|
|
@@ -50839,7 +51233,7 @@ function resolveMissingTaskFindings(input, db) {
|
|
|
50839
51233
|
const timestamp3 = input.resolved_at || now();
|
|
50840
51234
|
const status = normalizeResolutionStatus(input.status);
|
|
50841
51235
|
const runId = resolveRunForTask(input.run_id, input.task_id, d);
|
|
50842
|
-
const present = new Set(input.fingerprints.map(
|
|
51236
|
+
const present = new Set(input.fingerprints.map(normalizeTaskFindingFingerprint));
|
|
50843
51237
|
const warnings = [];
|
|
50844
51238
|
const conditions = ["task_id = ?", "status = 'open'"];
|
|
50845
51239
|
const params = [input.task_id];
|
|
@@ -51366,12 +51760,12 @@ init_artifact_store();
|
|
|
51366
51760
|
|
|
51367
51761
|
// src/lib/doctor.ts
|
|
51368
51762
|
init_database();
|
|
51763
|
+
import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync22, statSync as statSync12 } from "fs";
|
|
51764
|
+
import { basename as basename7, dirname as dirname18, join as join27 } from "path";
|
|
51369
51765
|
init_migrations();
|
|
51370
51766
|
init_schema();
|
|
51371
51767
|
init_recurrence();
|
|
51372
|
-
|
|
51373
|
-
import { basename as basename7, dirname as dirname18, join as join27 } from "path";
|
|
51374
|
-
var REQUIRED_TABLES2 = [
|
|
51768
|
+
var REQUIRED_TABLES3 = [
|
|
51375
51769
|
"_migrations",
|
|
51376
51770
|
"projects",
|
|
51377
51771
|
"tasks",
|
|
@@ -51384,7 +51778,7 @@ var REQUIRED_TABLES2 = [
|
|
|
51384
51778
|
"task_run_commands",
|
|
51385
51779
|
"task_run_artifacts"
|
|
51386
51780
|
];
|
|
51387
|
-
function
|
|
51781
|
+
function tableExists2(db, table) {
|
|
51388
51782
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
51389
51783
|
}
|
|
51390
51784
|
function quoteIdent(identifier) {
|
|
@@ -51468,7 +51862,7 @@ function findDuplicateIndexes(db) {
|
|
|
51468
51862
|
return duplicates;
|
|
51469
51863
|
}
|
|
51470
51864
|
function findMissingProjectRoots(db) {
|
|
51471
|
-
if (!
|
|
51865
|
+
if (!tableExists2(db, "projects"))
|
|
51472
51866
|
return 0;
|
|
51473
51867
|
let missing = 0;
|
|
51474
51868
|
const rows = db.query("SELECT path FROM projects WHERE path IS NOT NULL AND path != ''").all();
|
|
@@ -51483,7 +51877,7 @@ function findMissingProjectRoots(db) {
|
|
|
51483
51877
|
return missing;
|
|
51484
51878
|
}
|
|
51485
51879
|
function addTaskStateChecks(db, checks) {
|
|
51486
|
-
if (!
|
|
51880
|
+
if (!tableExists2(db, "tasks"))
|
|
51487
51881
|
return;
|
|
51488
51882
|
const columns = getTableColumns(db, "tasks");
|
|
51489
51883
|
const staleCutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
@@ -51555,15 +51949,42 @@ function createBackup(dbPath) {
|
|
|
51555
51949
|
function pushRepair(repairs, type, message, applied, count3) {
|
|
51556
51950
|
repairs.push({ type, message, applied, count: count3 });
|
|
51557
51951
|
}
|
|
51558
|
-
function summarize3(checks, repairs) {
|
|
51952
|
+
function summarize3(checks, repairs, integrity) {
|
|
51559
51953
|
return {
|
|
51560
51954
|
errors: checks.filter((check) => check.severity === "error").length,
|
|
51561
51955
|
warnings: checks.filter((check) => check.severity === "warn").length,
|
|
51562
51956
|
infos: checks.filter((check) => check.severity === "info").length,
|
|
51563
51957
|
repairable: checks.filter((check) => check.repairable).length,
|
|
51564
|
-
applied: repairs.filter((repair) => repair.applied).length
|
|
51958
|
+
applied: repairs.filter((repair) => repair.applied).length,
|
|
51959
|
+
integrity_findings: integrity.summary.findings,
|
|
51960
|
+
integrity_rows: integrity.summary.rows,
|
|
51961
|
+
integrity_unverified: integrity.summary.unverified
|
|
51565
51962
|
};
|
|
51566
51963
|
}
|
|
51964
|
+
function addIntegrityChecks(integrity, checks) {
|
|
51965
|
+
for (const condition of integrity.conditions) {
|
|
51966
|
+
if (!condition.verified) {
|
|
51967
|
+
addCheck(checks, {
|
|
51968
|
+
severity: "warn",
|
|
51969
|
+
type: `${condition.id}_unverified`,
|
|
51970
|
+
message: condition.message,
|
|
51971
|
+
repairable: false,
|
|
51972
|
+
integrity: true
|
|
51973
|
+
});
|
|
51974
|
+
continue;
|
|
51975
|
+
}
|
|
51976
|
+
if ((condition.count ?? 0) === 0)
|
|
51977
|
+
continue;
|
|
51978
|
+
addCheck(checks, {
|
|
51979
|
+
severity: condition.severity ?? "warn",
|
|
51980
|
+
type: condition.id,
|
|
51981
|
+
message: `${condition.message} \u2014 ${condition.impact}`,
|
|
51982
|
+
count: condition.count ?? undefined,
|
|
51983
|
+
repairable: false,
|
|
51984
|
+
integrity: true
|
|
51985
|
+
});
|
|
51986
|
+
}
|
|
51987
|
+
}
|
|
51567
51988
|
function deleteOrphans(db, table, where) {
|
|
51568
51989
|
const before = countQuery2(db, `SELECT COUNT(*) as count FROM ${table} WHERE ${where}`);
|
|
51569
51990
|
if (before > 0)
|
|
@@ -51592,7 +52013,7 @@ function runTodosDoctor(options = {}) {
|
|
|
51592
52013
|
message: `Schema at migration ${migrationCurrent}`
|
|
51593
52014
|
});
|
|
51594
52015
|
}
|
|
51595
|
-
const missingTables =
|
|
52016
|
+
const missingTables = REQUIRED_TABLES3.filter((table) => !tableExists2(db, table));
|
|
51596
52017
|
if (missingTables.length > 0) {
|
|
51597
52018
|
addCheck(checks, {
|
|
51598
52019
|
severity: "error",
|
|
@@ -51602,7 +52023,7 @@ function runTodosDoctor(options = {}) {
|
|
|
51602
52023
|
repairable: true
|
|
51603
52024
|
});
|
|
51604
52025
|
}
|
|
51605
|
-
const orphanedParents =
|
|
52026
|
+
const orphanedParents = tableExists2(db, "tasks") ? countQuery2(db, "SELECT COUNT(*) as count FROM tasks t WHERE t.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = t.parent_id)") : 0;
|
|
51606
52027
|
if (orphanedParents > 0) {
|
|
51607
52028
|
addCheck(checks, {
|
|
51608
52029
|
severity: "error",
|
|
@@ -51612,7 +52033,7 @@ function runTodosDoctor(options = {}) {
|
|
|
51612
52033
|
repairable: true
|
|
51613
52034
|
});
|
|
51614
52035
|
}
|
|
51615
|
-
const orphanedDependencies =
|
|
52036
|
+
const orphanedDependencies = tableExists2(db, "task_dependencies") && tableExists2(db, "tasks") ? countQuery2(db, "SELECT COUNT(*) as count FROM task_dependencies d WHERE NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = d.task_id) OR NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = d.depends_on)") : 0;
|
|
51616
52037
|
if (orphanedDependencies > 0) {
|
|
51617
52038
|
addCheck(checks, {
|
|
51618
52039
|
severity: "error",
|
|
@@ -51631,7 +52052,7 @@ function runTodosDoctor(options = {}) {
|
|
|
51631
52052
|
];
|
|
51632
52053
|
let orphanedRows2 = 0;
|
|
51633
52054
|
for (const [table, where] of orphanTables) {
|
|
51634
|
-
if (!
|
|
52055
|
+
if (!tableExists2(db, table))
|
|
51635
52056
|
continue;
|
|
51636
52057
|
orphanedRows2 += countQuery2(db, `SELECT COUNT(*) as count FROM ${table} WHERE ${where}`);
|
|
51637
52058
|
}
|
|
@@ -51645,6 +52066,8 @@ function runTodosDoctor(options = {}) {
|
|
|
51645
52066
|
});
|
|
51646
52067
|
}
|
|
51647
52068
|
addTaskStateChecks(db, checks);
|
|
52069
|
+
const integrity = scanSqliteIntegrity(db);
|
|
52070
|
+
addIntegrityChecks(integrity, checks);
|
|
51648
52071
|
const corruptJson = findCorruptJsonMetadata(db);
|
|
51649
52072
|
if (corruptJson.length > 0) {
|
|
51650
52073
|
addCheck(checks, {
|
|
@@ -51703,12 +52126,12 @@ function runTodosDoctor(options = {}) {
|
|
|
51703
52126
|
db.run("UPDATE tasks SET parent_id = NULL WHERE parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = tasks.parent_id)");
|
|
51704
52127
|
pushRepair(repairs, "orphaned_task_parents", "Cleared missing parent references", true, orphanedParents);
|
|
51705
52128
|
}
|
|
51706
|
-
if (orphanedDependencies > 0 &&
|
|
52129
|
+
if (orphanedDependencies > 0 && tableExists2(db, "task_dependencies")) {
|
|
51707
52130
|
const count3 = deleteOrphans(db, "task_dependencies", "NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_dependencies.task_id) OR NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_dependencies.depends_on)");
|
|
51708
52131
|
pushRepair(repairs, "orphaned_task_dependencies", "Deleted dependency rows referencing missing tasks", true, count3);
|
|
51709
52132
|
}
|
|
51710
52133
|
for (const [table, where] of orphanTables) {
|
|
51711
|
-
if (!
|
|
52134
|
+
if (!tableExists2(db, table))
|
|
51712
52135
|
continue;
|
|
51713
52136
|
const count3 = deleteOrphans(db, table, where);
|
|
51714
52137
|
if (count3 > 0)
|
|
@@ -51737,16 +52160,19 @@ function runTodosDoctor(options = {}) {
|
|
|
51737
52160
|
}
|
|
51738
52161
|
}
|
|
51739
52162
|
}
|
|
51740
|
-
const
|
|
51741
|
-
const
|
|
52163
|
+
const reread = apply && hasRepairableIssue ? runTodosDoctor({ db, dbPath, apply: false }) : undefined;
|
|
52164
|
+
const finalChecks = reread?.checks ?? checks;
|
|
52165
|
+
const finalIntegrity = reread?.integrity ?? integrity;
|
|
52166
|
+
const summary = summarize3(finalChecks, repairs, finalIntegrity);
|
|
51742
52167
|
return {
|
|
51743
|
-
ok: !finalChecks.some((check) => check.severity === "error"),
|
|
52168
|
+
ok: !finalChecks.some((check) => check.severity === "error") && finalIntegrity.summary.ok,
|
|
51744
52169
|
dry_run: !apply,
|
|
51745
52170
|
database_path: dbPath,
|
|
51746
52171
|
migration: { current: getMigrationLevel(db), expected: migrationExpected },
|
|
51747
52172
|
backup,
|
|
51748
52173
|
checks: finalChecks,
|
|
51749
52174
|
repairs,
|
|
52175
|
+
integrity: finalIntegrity,
|
|
51750
52176
|
summary
|
|
51751
52177
|
};
|
|
51752
52178
|
}
|
|
@@ -52292,6 +52718,7 @@ export {
|
|
|
52292
52718
|
updateArtifactRedaction,
|
|
52293
52719
|
updateAgentActivity,
|
|
52294
52720
|
updateAgent,
|
|
52721
|
+
unverifiedCondition,
|
|
52295
52722
|
unsubscribeResource,
|
|
52296
52723
|
unlockTask,
|
|
52297
52724
|
unlinkTaskCommit,
|
|
@@ -52312,6 +52739,7 @@ export {
|
|
|
52312
52739
|
summarizeTaskRun,
|
|
52313
52740
|
summarizeRoadmap,
|
|
52314
52741
|
summarizeMilestone,
|
|
52742
|
+
summarizeIntegrity,
|
|
52315
52743
|
suggestAgentNames,
|
|
52316
52744
|
subscribeResource,
|
|
52317
52745
|
storeArtifactContent,
|
|
@@ -52352,6 +52780,7 @@ export {
|
|
|
52352
52780
|
scorePlanHealth,
|
|
52353
52781
|
scheduleTask,
|
|
52354
52782
|
scanTextForSecrets,
|
|
52783
|
+
scanSqliteIntegrity,
|
|
52355
52784
|
scanSourceForForbiddenWebPatterns,
|
|
52356
52785
|
scanReminders,
|
|
52357
52786
|
scanPreWriteText,
|
|
@@ -52399,6 +52828,7 @@ export {
|
|
|
52399
52828
|
resolvePartialId,
|
|
52400
52829
|
resolveMissingTaskFindings,
|
|
52401
52830
|
resolveMentions,
|
|
52831
|
+
resolveIntegritySeverity,
|
|
52402
52832
|
resolveGitRoot,
|
|
52403
52833
|
resolveCommandQuery,
|
|
52404
52834
|
resolveAccessProfile,
|
|
@@ -52513,6 +52943,7 @@ export {
|
|
|
52513
52943
|
patrolTasks,
|
|
52514
52944
|
parseTmuxTarget,
|
|
52515
52945
|
parseStorageMode,
|
|
52946
|
+
parseStorageBackend,
|
|
52516
52947
|
parseRecurrenceRule,
|
|
52517
52948
|
parseQuietHours,
|
|
52518
52949
|
parsePlanArtifactMarkdown,
|
|
@@ -52541,6 +52972,8 @@ export {
|
|
|
52541
52972
|
migrationDryRun,
|
|
52542
52973
|
migrateWorkflowStates,
|
|
52543
52974
|
mergeDuplicateTask,
|
|
52975
|
+
measuredCondition,
|
|
52976
|
+
measureIntegrityRows,
|
|
52544
52977
|
materializePlanSteps,
|
|
52545
52978
|
matchCapabilities,
|
|
52546
52979
|
looksSensitiveKey,
|
|
@@ -52659,12 +53092,14 @@ export {
|
|
|
52659
53092
|
isWorktreePath,
|
|
52660
53093
|
isValidRecurrenceRule,
|
|
52661
53094
|
isTodosRemoteStorageEnabled,
|
|
53095
|
+
isTodosPostgresBackend,
|
|
52662
53096
|
isSnapshotStale,
|
|
52663
53097
|
isEncryptedValue,
|
|
52664
53098
|
isEncryptedBridgeBundle,
|
|
52665
53099
|
isArtifactExpired,
|
|
52666
53100
|
isAllowedLocalApiUrl,
|
|
52667
53101
|
isAgentConflict,
|
|
53102
|
+
integrityCondition,
|
|
52668
53103
|
installTemplateLibrary,
|
|
52669
53104
|
installLocalExtension,
|
|
52670
53105
|
inspectPlanArtifact,
|
|
@@ -52707,6 +53142,7 @@ export {
|
|
|
52707
53142
|
getTodosStorageEnvName,
|
|
52708
53143
|
getTodosStorageDatabaseUrl,
|
|
52709
53144
|
getTodosStorageDatabaseEnv,
|
|
53145
|
+
getTodosStorageBackend,
|
|
52710
53146
|
getTimeReport,
|
|
52711
53147
|
getTimeLogs,
|
|
52712
53148
|
getTerminalNotificationRule,
|
|
@@ -52914,6 +53350,7 @@ export {
|
|
|
52914
53350
|
formatLockConflict,
|
|
52915
53351
|
formatKnowledgeSnapshotMarkdown,
|
|
52916
53352
|
formatIssueImportPreviewText,
|
|
53353
|
+
formatIntegrityMessage,
|
|
52917
53354
|
formatIntakePreviewText,
|
|
52918
53355
|
formatHandoffPacket,
|
|
52919
53356
|
formatGoalHandoff,
|
|
@@ -53144,16 +53581,19 @@ export {
|
|
|
53144
53581
|
bulkAddTaskFiles,
|
|
53145
53582
|
buildTaskDependencyEdges,
|
|
53146
53583
|
buildTaskBoardSnapshot,
|
|
53584
|
+
buildSqliteIntegritySql,
|
|
53147
53585
|
buildS3ObjectUrl,
|
|
53148
53586
|
buildS3ObjectKey,
|
|
53149
53587
|
buildRunReplayBundle,
|
|
53150
53588
|
buildResourceSnapshot,
|
|
53151
53589
|
buildReportExportData,
|
|
53152
53590
|
buildProjectDependencyGraph,
|
|
53591
|
+
buildPostgresIntegritySql,
|
|
53153
53592
|
buildPlanArtifactSnapshot,
|
|
53154
53593
|
buildMcpToolGroups,
|
|
53155
53594
|
buildMachineTopologyReport,
|
|
53156
53595
|
buildKnowledgeSnapshotPayload,
|
|
53596
|
+
buildIntegrityReport,
|
|
53157
53597
|
buildHandoffPacket,
|
|
53158
53598
|
buildFeatureManifest,
|
|
53159
53599
|
buildFailureTriageReport,
|
|
@@ -53188,6 +53628,7 @@ export {
|
|
|
53188
53628
|
appendRunCommand,
|
|
53189
53629
|
analyzeDependencyGraph,
|
|
53190
53630
|
agentClaimNextSafe,
|
|
53631
|
+
adoptRemoteIntegrityReport,
|
|
53191
53632
|
addTemplateTasks,
|
|
53192
53633
|
addTaskRunFile,
|
|
53193
53634
|
addTaskRunEvent,
|
|
@@ -53234,6 +53675,7 @@ export {
|
|
|
53234
53675
|
TODOS_LOCAL_BACKUP_KIND,
|
|
53235
53676
|
TODOS_JSON_CONTRACTS_MANIFEST,
|
|
53236
53677
|
TODOS_JSON_CONTRACTS,
|
|
53678
|
+
TODOS_INTEGRITY_SCHEMA_VERSION,
|
|
53237
53679
|
TODOS_ERROR_CODES,
|
|
53238
53680
|
TODOS_ENCRYPTION_SCHEMA_VERSION,
|
|
53239
53681
|
TODOS_ENCRYPTED_VALUE_KIND,
|
|
@@ -53290,6 +53732,7 @@ export {
|
|
|
53290
53732
|
PLAN_EXECUTION_SCHEMA,
|
|
53291
53733
|
PLAN_EXECUTION_MODES,
|
|
53292
53734
|
PARITY_SCHEMA_VERSION,
|
|
53735
|
+
OPEN_TASK_STATUSES,
|
|
53293
53736
|
NOTIFICATION_REMINDERS_SCHEMA,
|
|
53294
53737
|
NL_INTAKE_SCHEMA,
|
|
53295
53738
|
NESTED_SUBCOMMANDS,
|
|
@@ -53318,6 +53761,7 @@ export {
|
|
|
53318
53761
|
JSON_OUTPUT_CONTRACT,
|
|
53319
53762
|
ISSUE_SOURCES,
|
|
53320
53763
|
ISSUE_IMPORT_SCHEMA,
|
|
53764
|
+
INTEGRITY_CONDITIONS,
|
|
53321
53765
|
INTAKE_TRIAGE_STATUSES,
|
|
53322
53766
|
INTAKE_SOURCE_TYPES,
|
|
53323
53767
|
INBOX_INTAKE_SCHEMA,
|