@hasna/todos 0.15.33 → 0.15.35

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.
Files changed (43) hide show
  1. package/dist/cli/cloud-router.d.ts +33 -47
  2. package/dist/cli/cloud-router.d.ts.map +1 -1
  3. package/dist/cli/commands/project-commands.d.ts.map +1 -1
  4. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  5. package/dist/cli/index.js +344 -127
  6. package/dist/cli/stage-a.d.ts +3 -1
  7. package/dist/cli/stage-a.d.ts.map +1 -1
  8. package/dist/contracts.js +115 -27
  9. package/dist/db/migrations.d.ts.map +1 -1
  10. package/dist/db/projects.d.ts +23 -0
  11. package/dist/db/projects.d.ts.map +1 -1
  12. package/dist/db/schema.d.ts.map +1 -1
  13. package/dist/index.js +275 -116
  14. package/dist/json-contracts.d.ts.map +1 -1
  15. package/dist/lib/assignee-validation.d.ts.map +1 -1
  16. package/dist/lib/import-export-bridge.d.ts.map +1 -1
  17. package/dist/lib/onboarding-fixtures.d.ts.map +1 -1
  18. package/dist/mcp/index.js +285 -113
  19. package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
  20. package/dist/mcp.js +3 -2
  21. package/dist/project-registration/authority.d.ts.map +1 -1
  22. package/dist/project-registration/sqlite.d.ts.map +1 -1
  23. package/dist/project-registration.js +185 -40
  24. package/dist/registry.js +115 -27
  25. package/dist/release-provenance.json +5 -5
  26. package/dist/sdk/v1.generated.d.ts +3 -0
  27. package/dist/sdk/v1.generated.d.ts.map +1 -1
  28. package/dist/server/index.js +1268 -7051
  29. package/dist/server/openapi.d.ts +13 -0
  30. package/dist/server/openapi.d.ts.map +1 -1
  31. package/dist/server/v1.d.ts.map +1 -1
  32. package/dist/storage/config.d.ts +24 -4
  33. package/dist/storage/config.d.ts.map +1 -1
  34. package/dist/storage/hybrid.d.ts +5 -3
  35. package/dist/storage/hybrid.d.ts.map +1 -1
  36. package/dist/storage.js +158 -34
  37. package/dist/task-manifest.js +3 -3
  38. package/dist/testing.d.ts +16 -7
  39. package/dist/testing.d.ts.map +1 -1
  40. package/dist/testing.js +10 -6
  41. package/dist/types/index.d.ts +5 -2
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/package.json +3 -2
@@ -18,7 +18,9 @@ export type TodosCliCommandOwner = "diagnostic" | "remote-http" | "local-only";
18
18
  * mutating the caller's environment. The executable applies an admitted local
19
19
  * redaction decision before importing command modules so any later
20
20
  * `getTodosCloudClient()` call cannot reconstruct hosted routing from the
21
- * ambient selector.
21
+ * ambient API pair. The retired storage-mode variables are never written here —
22
+ * they are banned (owner directive 2026-08-15), and the HTTP selector is
23
+ * HASNA_TODOS_API_URL + HASNA_TODOS_API_KEY, which the redaction blanks.
22
24
  */
23
25
  export declare function applyTodosCliAuthorityEnvironment(authority: TodosCliAuthorityInitialization, env?: Env): void;
24
26
  export declare const TODOS_CLI_COMMAND_ALIASES: {
@@ -1 +1 @@
1
- {"version":3,"file":"stage-a.d.ts","sourceRoot":"","sources":["../../src/cli/stage-a.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,MAAM,WAAW,CAAC;AAC1C,OAAO,EAIL,KAAK,4BAA4B,EAClC,MAAM,mBAAmB,CAAC;AAE3B,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9C,MAAM,MAAM,+BAA+B,GACvC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,oBAAoB,CAAA;CAAE,GACzE;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,CAAC;AAE/E;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,+BAA+B,EAC1C,GAAG,GAAE,GAAwB,GAC5B,IAAI,CAIN;AAyBD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;CA8BgB,CAAC;AAkFvD,wBAAgB,kCAAkC,IAAI,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,4BAA4B,CAAa,GACxE,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,4BAA4B,CAAa,GACxE,IAAI,CAUN;AAED;;;;;;;;GAQG;AACH,wBAAgB,2CAA2C,CACzD,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,4BAA4B,CAAa,EACzE,IAAI,GAAE,SAAS,MAAM,EAAO,GAC3B,MAAM,GAAG,IAAI,CAYf;AA2XD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,GAAwB,GAC5B,+BAA+B,CAqBjC"}
1
+ {"version":3,"file":"stage-a.d.ts","sourceRoot":"","sources":["../../src/cli/stage-a.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,MAAM,WAAW,CAAC;AAC1C,OAAO,EAKL,KAAK,4BAA4B,EAClC,MAAM,mBAAmB,CAAC;AAE3B,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9C,MAAM,MAAM,+BAA+B,GACvC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,oBAAoB,CAAA;CAAE,GACzE;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,CAAC;AAE/E;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,+BAA+B,EAC1C,GAAG,GAAE,GAAwB,GAC5B,IAAI,CAMN;AAyBD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;CA8BgB,CAAC;AAkFvD,wBAAgB,kCAAkC,IAAI,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,4BAA4B,CAAa,GACxE,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,4BAA4B,CAAa,GACxE,IAAI,CAUN;AAED;;;;;;;;GAQG;AACH,wBAAgB,2CAA2C,CACzD,KAAK,EAAE,+BAA+B,CAAC,OAAO,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,4BAA4B,CAAa,EACzE,IAAI,GAAE,SAAS,MAAM,EAAO,GAC3B,MAAM,GAAG,IAAI,CAYf;AA2XD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,GAAE,MAAM,EAA0B,EACtC,GAAG,GAAE,GAAwB,GAC5B,+BAA+B,CAsCjC"}
package/dist/contracts.js CHANGED
@@ -1884,6 +1884,10 @@ var init_migrations = __esm(() => {
1884
1884
  );
1885
1885
  INSERT OR IGNORE INTO _migrations (id) VALUES (70);
1886
1886
  COMMIT;
1887
+ `,
1888
+ `BEGIN;
1889
+ INSERT OR IGNORE INTO _migrations (id) VALUES (71);
1890
+ COMMIT;
1887
1891
  `
1888
1892
  ];
1889
1893
  });
@@ -2557,6 +2561,8 @@ function ensureSchema(db) {
2557
2561
  ensureColumn("projects", "task_list_id", "TEXT");
2558
2562
  ensureColumn("projects", "task_prefix", "TEXT");
2559
2563
  ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
2564
+ ensureColumn("projects", "parent_id", "TEXT REFERENCES projects(id) ON DELETE SET NULL");
2565
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_projects_parent_id ON projects(parent_id)");
2560
2566
  ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
2561
2567
  ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
2562
2568
  ensureColumn("tasks", "short_id", "TEXT");
@@ -4177,6 +4183,7 @@ __export(exports_config, {
4177
4183
  TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
4178
4184
  TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
4179
4185
  STORAGE_TABLES: () => STORAGE_TABLES,
4186
+ REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
4180
4187
  CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
4181
4188
  CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
4182
4189
  CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
@@ -4238,25 +4245,36 @@ function assertTodosRemoteStorageConfig(config) {
4238
4245
  if (!isTodosPostgresBackend(config))
4239
4246
  return;
4240
4247
  if (!config.database?.url) {
4241
- throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} is required when ${TODOS_STORAGE_ENV.mode}=${config.mode}`);
4248
+ throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} is required when the postgresql backend is selected`);
4242
4249
  }
4243
4250
  }
4244
4251
  function parseStorageBackend(value) {
4245
4252
  const normalized = clean(value)?.toLowerCase();
4246
4253
  if (!normalized)
4247
4254
  return "sqlite";
4248
- if (normalized === "sqlite" || normalized === "postgres")
4249
- return normalized;
4250
- const legacy = LEGACY_BACKEND_TOKENS[normalized];
4251
- if (legacy)
4252
- return legacy;
4253
- throw new Error(`${TODOS_STORAGE_ENV.mode} must be sqlite or postgres (legacy values local and remote are accepted)`);
4255
+ if (normalized === "sqlite")
4256
+ return "sqlite";
4257
+ if (normalized === "postgres" || normalized === "postgresql")
4258
+ return "postgres";
4259
+ if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
4260
+ 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.`);
4261
+ }
4262
+ throw new Error(`Storage backend must be sqlite or postgres`);
4254
4263
  }
4255
4264
  function parseStorageMode(value) {
4256
4265
  return parseStorageBackend(value);
4257
4266
  }
4258
4267
  function getTodosStorageBackend(env = process.env) {
4259
- return parseStorageBackend(readStorageEnv(env, "mode").value);
4268
+ for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
4269
+ if (Object.hasOwn(env, key) && env[key] !== undefined) {
4270
+ 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.`);
4271
+ }
4272
+ }
4273
+ if (!getTodosStorageDatabaseUrl(env))
4274
+ return "sqlite";
4275
+ if (isTodosShadowEnabled(env))
4276
+ return "sqlite";
4277
+ return "postgres";
4260
4278
  }
4261
4279
  function getTodosStorageMode(env = process.env) {
4262
4280
  return getTodosStorageBackend(env);
@@ -4330,7 +4348,7 @@ function parsePositiveInteger(value, fallback) {
4330
4348
  }
4331
4349
  return parsed;
4332
4350
  }
4333
- 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;
4351
+ var TODOS_STORAGE_TABLES, STORAGE_TABLES, TODOS_STORAGE_ENV, TODOS_STORAGE_FALLBACK_ENV, REMOVED_STORAGE_MODE_ENV_KEYS, 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";
4334
4352
  var init_config = __esm(() => {
4335
4353
  TODOS_STORAGE_TABLES = [
4336
4354
  "todos_sync_records",
@@ -4338,7 +4356,6 @@ var init_config = __esm(() => {
4338
4356
  ];
4339
4357
  STORAGE_TABLES = TODOS_STORAGE_TABLES;
4340
4358
  TODOS_STORAGE_ENV = {
4341
- mode: "HASNA_TODOS_STORAGE_MODE",
4342
4359
  shadow: "HASNA_TODOS_SHADOW",
4343
4360
  databaseUrl: "HASNA_TODOS_DATABASE_URL",
4344
4361
  databaseSsl: "HASNA_TODOS_DATABASE_SSL",
@@ -4355,7 +4372,6 @@ var init_config = __esm(() => {
4355
4372
  syncDryRun: "HASNA_TODOS_SYNC_DRY_RUN"
4356
4373
  };
4357
4374
  TODOS_STORAGE_FALLBACK_ENV = {
4358
- mode: "TODOS_STORAGE_MODE",
4359
4375
  shadow: "TODOS_SHADOW",
4360
4376
  databaseUrl: "TODOS_DATABASE_URL",
4361
4377
  databaseSsl: "TODOS_DATABASE_SSL",
@@ -4371,14 +4387,12 @@ var init_config = __esm(() => {
4371
4387
  syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
4372
4388
  syncDryRun: "TODOS_SYNC_DRY_RUN"
4373
4389
  };
4374
- LEGACY_BACKEND_TOKENS = {
4375
- local: "sqlite",
4376
- remote: "postgres",
4377
- postgresql: "postgres",
4378
- hybrid: "postgres",
4379
- self_hosted: "postgres",
4380
- cloud: "postgres"
4381
- };
4390
+ REMOVED_STORAGE_MODE_ENV_KEYS = [
4391
+ "HASNA_TODOS_STORAGE_MODE",
4392
+ "HASNA_TODOS_MODE",
4393
+ "TODOS_STORAGE_MODE",
4394
+ "TODOS_MODE"
4395
+ ];
4382
4396
  });
4383
4397
 
4384
4398
  // src/storage/shadow-outbox-schema.ts
@@ -5037,17 +5051,24 @@ function createProject(input, db) {
5037
5051
  const id = uuid();
5038
5052
  const timestamp2 = now();
5039
5053
  const derivedSlug = slugify(input.name);
5040
- const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : slugify(input.task_list_id);
5054
+ const taskListId = input.task_list_id === undefined ? derivedSlug : slugify(input.task_list_id);
5041
5055
  if (!derivedSlug || !taskListId)
5042
5056
  throw new Error("Project name and task-list slug must be non-empty");
5043
5057
  const slugConflict = d.query("SELECT id FROM projects WHERE task_list_id = ? LIMIT 1").get(taskListId);
5044
5058
  if (slugConflict || !claimCanonicalSlug("project", "global", taskListId, id, d)) {
5045
5059
  throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Project slug "${taskListId}" already exists`);
5046
5060
  }
5061
+ const parentId = input.parent_id ?? null;
5062
+ if (parentId !== null) {
5063
+ const parent = getProject(parentId, d);
5064
+ if (!parent)
5065
+ throw new ProjectNotFoundError(parentId);
5066
+ assertNotProjectAncestor(id, parentId, d);
5067
+ }
5047
5068
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
5048
5069
  const machineId = currentStorageMachineId(d);
5049
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
5050
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp2, timestamp2, machineId]);
5070
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, parent_id, created_at, updated_at, machine_id)
5071
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, parentId, timestamp2, timestamp2, machineId]);
5051
5072
  return getProject(id, d);
5052
5073
  })();
5053
5074
  }
@@ -5072,6 +5093,60 @@ function listProjects(db) {
5072
5093
  const d = db || getDatabase();
5073
5094
  return d.query("SELECT * FROM projects ORDER BY name").all();
5074
5095
  }
5096
+ function orderProjectsParentFirst(projects) {
5097
+ const projectId = (project) => {
5098
+ const id = project.id;
5099
+ return typeof id === "string" && id.length > 0 ? id : null;
5100
+ };
5101
+ const parentId = (project) => {
5102
+ const parent = project.parent_id;
5103
+ return parent == null ? null : String(parent);
5104
+ };
5105
+ const byId = new Set;
5106
+ for (const project of projects) {
5107
+ const id = projectId(project);
5108
+ if (id !== null)
5109
+ byId.add(id);
5110
+ }
5111
+ const ordered = [];
5112
+ const emitted = new Set;
5113
+ let remaining = [...projects];
5114
+ let progress = true;
5115
+ while (progress && remaining.length > 0) {
5116
+ progress = false;
5117
+ const deferred = [];
5118
+ for (const project of remaining) {
5119
+ const parent = parentId(project);
5120
+ if (parent === null || emitted.has(parent) || !byId.has(parent)) {
5121
+ ordered.push(project);
5122
+ const id = projectId(project);
5123
+ if (id !== null)
5124
+ emitted.add(id);
5125
+ progress = true;
5126
+ } else {
5127
+ deferred.push(project);
5128
+ }
5129
+ }
5130
+ remaining = deferred;
5131
+ }
5132
+ ordered.push(...remaining);
5133
+ return ordered;
5134
+ }
5135
+ function assertNotProjectAncestor(projectId, ancestorId, db) {
5136
+ const d = db || getDatabase();
5137
+ let cursor = ancestorId;
5138
+ const seen = new Set;
5139
+ while (cursor !== null) {
5140
+ if (cursor === projectId) {
5141
+ throw new ResourceConflictError("PROJECT_PARENT_CYCLE", `Project "${projectId}" cannot be placed under its own descendant`);
5142
+ }
5143
+ if (seen.has(cursor))
5144
+ break;
5145
+ seen.add(cursor);
5146
+ const row = d.query("SELECT parent_id FROM projects WHERE id = ?").get(cursor);
5147
+ cursor = row?.parent_id ?? null;
5148
+ }
5149
+ }
5075
5150
  function updateProject(id, input, db) {
5076
5151
  const d = db || getDatabase();
5077
5152
  const project = getProject(id, d);
@@ -5094,6 +5169,16 @@ function updateProject(id, input, db) {
5094
5169
  sets.push("path = ?");
5095
5170
  params.push(input.path);
5096
5171
  }
5172
+ if (input.parent_id !== undefined) {
5173
+ if (input.parent_id !== null) {
5174
+ const parent = getProject(input.parent_id, d);
5175
+ if (!parent)
5176
+ throw new ProjectNotFoundError(input.parent_id);
5177
+ assertNotProjectAncestor(id, input.parent_id, d);
5178
+ }
5179
+ sets.push("parent_id = ?");
5180
+ params.push(input.parent_id);
5181
+ }
5097
5182
  params.push(id);
5098
5183
  d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
5099
5184
  return getProject(id, d);
@@ -7227,8 +7312,8 @@ var init_secret_redaction = __esm(() => {
7227
7312
  SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
7228
7313
  DEFAULT_PATTERNS = [
7229
7314
  { name: "openai_sk", pattern: /\bsk-[a-zA-Z0-9]{10,}\b/g },
7230
- { name: "github_pat", pattern: /\bghp_[a-zA-Z0-9]{20,}\b/g },
7231
- { name: "github_oauth", pattern: /\bgho_[a-zA-Z0-9]{20,}\b/g },
7315
+ { name: "github_pat", pattern: /\bgh[p]_[a-zA-Z0-9]{20,}\b/g },
7316
+ { name: "github_oauth", pattern: /\bgh[o]_[a-zA-Z0-9]{20,}\b/g },
7232
7317
  { name: "aws_access_key", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
7233
7318
  { name: "bearer_token", pattern: /\bBearer\s+[a-zA-Z0-9\-._~+/]+=*\b/gi },
7234
7319
  { name: "jwt", pattern: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g },
@@ -7240,7 +7325,7 @@ var init_secret_redaction = __esm(() => {
7240
7325
  /example\.com/i,
7241
7326
  /your-api-key-here/i,
7242
7327
  /sk-test/i,
7243
- /ghp_xxx/i
7328
+ /gh[p]_xxx/i
7244
7329
  ];
7245
7330
  customRedactors = [];
7246
7331
  });
@@ -12720,7 +12805,7 @@ var init_tasks = __esm(() => {
12720
12805
  // package.json
12721
12806
  var package_default = {
12722
12807
  name: "@hasna/todos",
12723
- version: "0.15.33",
12808
+ version: "0.15.35",
12724
12809
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12725
12810
  type: "module",
12726
12811
  main: "dist/index.js",
@@ -12794,9 +12879,10 @@ var package_default = {
12794
12879
  "verify:release": "bun run scripts/verify-public-release.ts --mode=review",
12795
12880
  "verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
12796
12881
  "verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
12797
- "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts scripts/attested-container-compatibility-vector.test.ts",
12882
+ "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
12798
12883
  "emit:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts emit",
12799
12884
  "verify:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts verify",
12885
+ "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
12800
12886
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
12801
12887
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
12802
12888
  postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
@@ -13485,6 +13571,7 @@ var TODOS_JSON_CONTRACTS = [
13485
13571
  task_list_id: nullableIdField,
13486
13572
  task_prefix: field(["string", "null"], "Optional task prefix.", true),
13487
13573
  task_counter: field("integer", "Monotonic project task counter."),
13574
+ parent_id: field(["string", "null"], "Optional parent project id; null means top-level.", true),
13488
13575
  created_at: isoDateField,
13489
13576
  updated_at: isoDateField
13490
13577
  },
@@ -16770,6 +16857,7 @@ function createAgentProjectDemoBundle() {
16770
16857
  task_list_id: ids.list,
16771
16858
  task_prefix: "DEMO",
16772
16859
  task_counter: 4,
16860
+ parent_id: null,
16773
16861
  created_at: createdAt,
16774
16862
  updated_at: completedAt
16775
16863
  });
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,UA0qDtB,CAAC"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,UAwrDtB,CAAC"}
@@ -5,6 +5,29 @@ export declare function createProject(input: CreateProjectInput, db?: Database):
5
5
  export declare function getProject(id: string, db?: Database): Project | null;
6
6
  export declare function getProjectByPath(path: string, db?: Database): Project | null;
7
7
  export declare function listProjects(db?: Database): Project[];
8
+ export declare function listChildProjects(parentId: string, db?: Database): Project[];
9
+ /**
10
+ * Order projects so every parent precedes its children, regardless of name.
11
+ *
12
+ * projects.parent_id is an immediate foreign key, so importing a child before
13
+ * its parent fails the constraint and drops the child — and everything that
14
+ * points at it, such as its task rows — into the import's error list. Both
15
+ * round-trip import paths (SQLite snapshot restore and bundle import) rely on
16
+ * this ordering and must not depend on the export side's `ORDER BY name`.
17
+ *
18
+ * Stable and cycle-safe: rows whose parent is null, already emitted, or
19
+ * outside the given set keep their original relative order; a malformed input
20
+ * whose parent chain loops falls back to original order instead of hanging or
21
+ * dropping rows silently — the FK constraint then rejects the offending rows
22
+ * per-row and the import records the errors.
23
+ */
24
+ export declare function orderProjectsParentFirst<T extends object>(projects: readonly T[]): T[];
25
+ /**
26
+ * Reject making `projectId` a descendant of `ancestorId`. Walks the ancestor
27
+ * chain of the candidate parent; if `projectId` appears anywhere in it, the
28
+ * new link would create a cycle (a project that is its own ancestor).
29
+ */
30
+ export declare function assertNotProjectAncestor(projectId: string, ancestorId: string, db?: Database): void;
8
31
  export declare function updateProject(id: string, input: UpdateProjectInput, db?: Database): Project;
9
32
  /**
10
33
  * Rename a project: update name and/or task_list_id (slug).
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,OAAO,EAAE,aAAa,EAAoB,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAQpJ,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAyBD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,kBAAkB,EACzB,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAsBT;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAIpE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAc5E;AAED,wBAAgB,YAAY,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,EAAE,CAKrD;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,kBAAkB,EACzB,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CA4BT;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,EAAE,CAAC,EAAE,QAAQ,GACZ;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAkDlD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAahE;AAWD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,wBAAwB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,aAAa,CAW9F;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAItE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,aAAa,EAAE,CAIpF;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAM/E;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAS/E;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAiBT;AAID,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,kBAAkB,CAwBtG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAWnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,kBAAkB,EAAE,CAK5F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAiBpG"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAoChD;AA8BD,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAs/C/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA4BnD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAoChD;AA8BD,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAw/C/C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA4BnD"}