@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
@@ -1 +1 @@
1
- {"version":3,"file":"task-project-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/task-project-tools.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AA2JjD,UAAU,kBAAkB;IAC1B,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACzD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;IACnC,gBAAgB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACvE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAC3F;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,kBAAkB,QAy7FlF"}
1
+ {"version":3,"file":"task-project-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/task-project-tools.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AA2JjD,UAAU,kBAAkB;IAC1B,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACzD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;IACnC,gBAAgB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACvE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAC3F;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,kBAAkB,QA07FlF"}
package/dist/mcp.js CHANGED
@@ -41,7 +41,7 @@ var __require = import.meta.require;
41
41
  // package.json
42
42
  var package_default = {
43
43
  name: "@hasna/todos",
44
- version: "0.15.33",
44
+ version: "0.15.35",
45
45
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
46
46
  type: "module",
47
47
  main: "dist/index.js",
@@ -115,9 +115,10 @@ var package_default = {
115
115
  "verify:release": "bun run scripts/verify-public-release.ts --mode=review",
116
116
  "verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
117
117
  "verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
118
- "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts scripts/attested-container-compatibility-vector.test.ts",
118
+ "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
119
119
  "emit:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts emit",
120
120
  "verify:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts verify",
121
+ "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
121
122
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
122
123
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
123
124
  postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
@@ -1 +1 @@
1
- {"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/project-registration/authority.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAEV,+BAA+B,EAMhC,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,KAAK,8CAA8C,EACnD,KAAK,sCAAsC,EAC5C,MAAM,eAAe,CAAC;AAEvB,OAAO,EAIL,KAAK,iCAAiC,EACtC,KAAK,wCAAwC,EAE7C,KAAK,kCAAkC,EACvC,KAAK,iCAAiC,EAEtC,KAAK,2CAA2C,EAChD,KAAK,qCAAqC,EAC1C,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,oCAAoC,EACzC,KAAK,2CAA2C,EAChD,KAAK,wCAAwC,EAE7C,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,YAAY,CAAC;AAuBpB,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEvE;AAaD,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIrE;AAED,wBAAgB,4CAA4C,CAAC,KAAK,EAAE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,iCAAiC,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,MAAM,CAKT;AA0zBD,qBAAa,6CACb,YAAW,iCAAiC;IAOxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,QAAQ,CAAC,SAAS,EAAG,OAAO,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA4D;gBAGxE,OAAO,EAAE,+BAA+B,EACzD,OAAO,GAAE,wCAA6C;IAyBlD,UAAU,IAAI,OAAO,CAAC,kCAAkC,CAAC;YAOjD,KAAK;YAiBL,WAAW;YASX,YAAY;YAgBZ,WAAW;YAuBX,yBAAyB;YA2DzB,YAAY;IAsHpB,MAAM,CACV,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,+BAA+B,CAAC;YAsG7B,kBAAkB;IAiE1B,SAAS,CAAC,OAAO,EAAE;QACvB,aAAa,EAAE,oCAAoC,CAAC;QACpD,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,OAAO,CAAC;QAChB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC,8BAA8B,CAAC;IA0BrC,aAAa,CACjB,OAAO,EAAE,qCAAqC,GAC7C,OAAO,CAAC,oCAAoC,CAAC;IAkF1C,oBAAoB,CACxB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,wBAAwB,CAAC;IA2I9B,iCAAiC,CACrC,aAAa,EAAE,+BAA+B,EAC9C,aAAa,EAAE,+BAA+B,EAC9C,aAAa,EAAE,2CAA2C,GACzD,OAAO,CAAC,wCAAwC,CAAC;YAwMtC,qBAAqB;IAgC7B,UAAU,CACd,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,+BAA+B,CAAC;IAqLrC,aAAa,CACjB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,2CAA2C,CAAC;CAuDxD;AAED,wBAAgB,4CAA4C,CAC1D,EAAE,EAAE,QAAQ,EACZ,OAAO,GAAE,wCAA6C,GACrD,iCAAiC,CAKnC;AAED,wBAAgB,+CAA+C,CAC7D,MAAM,EAAE,sCAAsC,EAC9C,OAAO,GAAE,wCAAwC,GAC7C,8CAAmD,GACtD,iCAAiC,CAenC"}
1
+ {"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/project-registration/authority.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAEV,+BAA+B,EAMhC,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,KAAK,8CAA8C,EACnD,KAAK,sCAAsC,EAC5C,MAAM,eAAe,CAAC;AAEvB,OAAO,EAIL,KAAK,iCAAiC,EACtC,KAAK,wCAAwC,EAE7C,KAAK,kCAAkC,EACvC,KAAK,iCAAiC,EAEtC,KAAK,2CAA2C,EAChD,KAAK,qCAAqC,EAC1C,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,oCAAoC,EACzC,KAAK,2CAA2C,EAChD,KAAK,wCAAwC,EAE7C,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,YAAY,CAAC;AAuBpB,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEvE;AAaD,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIrE;AAED,wBAAgB,4CAA4C,CAAC,KAAK,EAAE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,iCAAiC,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,MAAM,CAKT;AAk0BD,qBAAa,6CACb,YAAW,iCAAiC;IAOxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,QAAQ,CAAC,SAAS,EAAG,OAAO,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA4D;gBAGxE,OAAO,EAAE,+BAA+B,EACzD,OAAO,GAAE,wCAA6C;IAyBlD,UAAU,IAAI,OAAO,CAAC,kCAAkC,CAAC;YAOjD,KAAK;YAiBL,WAAW;YASX,YAAY;YAgBZ,WAAW;YAuBX,yBAAyB;YA2DzB,YAAY;IAiJpB,MAAM,CACV,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,+BAA+B,CAAC;YAsG7B,kBAAkB;IAiE1B,SAAS,CAAC,OAAO,EAAE;QACvB,aAAa,EAAE,oCAAoC,CAAC;QACpD,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,OAAO,CAAC;QAChB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC,8BAA8B,CAAC;IA0BrC,aAAa,CACjB,OAAO,EAAE,qCAAqC,GAC7C,OAAO,CAAC,oCAAoC,CAAC;IAkF1C,oBAAoB,CACxB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,wBAAwB,CAAC;IA2I9B,iCAAiC,CACrC,aAAa,EAAE,+BAA+B,EAC9C,aAAa,EAAE,+BAA+B,EAC9C,aAAa,EAAE,2CAA2C,GACzD,OAAO,CAAC,wCAAwC,CAAC;YAwMtC,qBAAqB;IAgC7B,UAAU,CACd,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,+BAA+B,CAAC;IAqLrC,aAAa,CACjB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,2CAA2C,CAAC;CAuDxD;AAED,wBAAgB,4CAA4C,CAC1D,EAAE,EAAE,QAAQ,EACZ,OAAO,GAAE,wCAA6C,GACrD,iCAAiC,CAKnC;AAED,wBAAgB,+CAA+C,CAC7D,MAAM,EAAE,sCAAsC,EAC9C,OAAO,GAAE,wCAAwC,GAC7C,8CAAmD,GACtD,iCAAiC,CAenC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/project-registration/sqlite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQ3C,OAAO,KAAK,EAGV,OAAO,EACP,QAAQ,EAET,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,+BAA+B,EAC/B,0CAA0C,EAC1C,sCAAsC,EACtC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAElC,6BAA6B,EAC7B,0BAA0B,EAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,YAAY,CAAC;AAigCvE,qBAAa,qCACb,YAAW,+BAA+B;IAI5B,OAAO,CAAC,QAAQ,CAAC,EAAE;IAH/B,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;gBAEtC,EAAE,EAAE,QAAQ;IAKnC,WAAW,CAAC,CAAC,EACjB,EAAE,EAAE,CAAC,WAAW,EAAE,0CAA0C,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1E,OAAO,CAAC,CAAC,CAAC;IAmCb,mBAAmB,CACjB,QAAQ,EAAE,oCAAoC,GAC7C,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAIrD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAIrF,UAAU,CACR,KAAK,EAAE,sCAAsC,EAC7C,YAAY,EAAE,oCAAoC,EAClD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAIrD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAI/C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAI3C,oCAAoC,CAAC,KAAK,EAAE;QAChD,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;KAC1B,GAAG,OAAO,CAAC,MAAM,CAAC;IAsCb,6BAA6B,CAAC,KAAK,EAAE;QACzC,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;QACzB,KAAK,EAAE,0BAA0B,GAAG,IAAI,CAAC;QACzC,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,6BAA6B,EAAE,CAAC;CAwC7C"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/project-registration/sqlite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQ3C,OAAO,KAAK,EAGV,OAAO,EACP,QAAQ,EAET,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,+BAA+B,EAC/B,0CAA0C,EAC1C,sCAAsC,EACtC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAElC,6BAA6B,EAC7B,0BAA0B,EAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,YAAY,CAAC;AAmgCvE,qBAAa,qCACb,YAAW,+BAA+B;IAI5B,OAAO,CAAC,QAAQ,CAAC,EAAE;IAH/B,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;gBAEtC,EAAE,EAAE,QAAQ;IAKnC,WAAW,CAAC,CAAC,EACjB,EAAE,EAAE,CAAC,WAAW,EAAE,0CAA0C,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1E,OAAO,CAAC,CAAC,CAAC;IAmCb,mBAAmB,CACjB,QAAQ,EAAE,oCAAoC,GAC7C,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAIrD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAIrF,UAAU,CACR,KAAK,EAAE,sCAAsC,EAC7C,YAAY,EAAE,oCAAoC,EAClD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;IAIrD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAI/C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAI3C,oCAAoC,CAAC,KAAK,EAAE;QAChD,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;KAC1B,GAAG,OAAO,CAAC,MAAM,CAAC;IAsCb,6BAA6B,CAAC,KAAK,EAAE;QACzC,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;QACzB,KAAK,EAAE,0BAA0B,GAAG,IAAI,CAAC;QACzC,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,6BAA6B,EAAE,CAAC;CAwC7C"}
@@ -757,8 +757,8 @@ var init_secret_redaction = __esm(() => {
757
757
  SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
758
758
  DEFAULT_PATTERNS = [
759
759
  { name: "openai_sk", pattern: /\bsk-[a-zA-Z0-9]{10,}\b/g },
760
- { name: "github_pat", pattern: /\bghp_[a-zA-Z0-9]{20,}\b/g },
761
- { name: "github_oauth", pattern: /\bgho_[a-zA-Z0-9]{20,}\b/g },
760
+ { name: "github_pat", pattern: /\bgh[p]_[a-zA-Z0-9]{20,}\b/g },
761
+ { name: "github_oauth", pattern: /\bgh[o]_[a-zA-Z0-9]{20,}\b/g },
762
762
  { name: "aws_access_key", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
763
763
  { name: "bearer_token", pattern: /\bBearer\s+[a-zA-Z0-9\-._~+/]+=*\b/gi },
764
764
  { name: "jwt", pattern: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g },
@@ -770,7 +770,7 @@ var init_secret_redaction = __esm(() => {
770
770
  /example\.com/i,
771
771
  /your-api-key-here/i,
772
772
  /sk-test/i,
773
- /ghp_xxx/i
773
+ /gh[p]_xxx/i
774
774
  ];
775
775
  customRedactors = [];
776
776
  });
@@ -2900,6 +2900,10 @@ var init_migrations = __esm(() => {
2900
2900
  );
2901
2901
  INSERT OR IGNORE INTO _migrations (id) VALUES (70);
2902
2902
  COMMIT;
2903
+ `,
2904
+ `BEGIN;
2905
+ INSERT OR IGNORE INTO _migrations (id) VALUES (71);
2906
+ COMMIT;
2903
2907
  `
2904
2908
  ];
2905
2909
  });
@@ -3573,6 +3577,8 @@ function ensureSchema(db) {
3573
3577
  ensureColumn("projects", "task_list_id", "TEXT");
3574
3578
  ensureColumn("projects", "task_prefix", "TEXT");
3575
3579
  ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
3580
+ ensureColumn("projects", "parent_id", "TEXT REFERENCES projects(id) ON DELETE SET NULL");
3581
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_projects_parent_id ON projects(parent_id)");
3576
3582
  ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
3577
3583
  ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
3578
3584
  ensureColumn("tasks", "short_id", "TEXT");
@@ -4883,6 +4889,7 @@ __export(exports_config, {
4883
4889
  TODOS_STORAGE_FALLBACK_ENV: () => TODOS_STORAGE_FALLBACK_ENV,
4884
4890
  TODOS_STORAGE_ENV: () => TODOS_STORAGE_ENV,
4885
4891
  STORAGE_TABLES: () => STORAGE_TABLES,
4892
+ REMOVED_STORAGE_MODE_ENV_KEYS: () => REMOVED_STORAGE_MODE_ENV_KEYS,
4886
4893
  CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV: () => CANONICAL_TODOS_RDS_RUNTIME_PATH_ENV,
4887
4894
  CANONICAL_TODOS_RDS_DATABASE: () => CANONICAL_TODOS_RDS_DATABASE,
4888
4895
  CANONICAL_TODOS_RDS_CLUSTER_ENV: () => CANONICAL_TODOS_RDS_CLUSTER_ENV
@@ -4944,25 +4951,36 @@ function assertTodosRemoteStorageConfig(config) {
4944
4951
  if (!isTodosPostgresBackend(config))
4945
4952
  return;
4946
4953
  if (!config.database?.url) {
4947
- throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} is required when ${TODOS_STORAGE_ENV.mode}=${config.mode}`);
4954
+ throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} is required when the postgresql backend is selected`);
4948
4955
  }
4949
4956
  }
4950
4957
  function parseStorageBackend(value) {
4951
4958
  const normalized = clean(value)?.toLowerCase();
4952
4959
  if (!normalized)
4953
4960
  return "sqlite";
4954
- if (normalized === "sqlite" || normalized === "postgres")
4955
- return normalized;
4956
- const legacy = LEGACY_BACKEND_TOKENS[normalized];
4957
- if (legacy)
4958
- return legacy;
4959
- throw new Error(`${TODOS_STORAGE_ENV.mode} must be sqlite or postgres (legacy values local and remote are accepted)`);
4961
+ if (normalized === "sqlite")
4962
+ return "sqlite";
4963
+ if (normalized === "postgres" || normalized === "postgresql")
4964
+ return "postgres";
4965
+ if (["local", "remote", "cloud", "hybrid", "self_hosted"].includes(normalized)) {
4966
+ throw new Error(`${TODOS_STORAGE_ENV.databaseUrl} selects the backend. Deployment modes no longer exist: ` + `delete the storage-mode variable and set ${TODOS_STORAGE_ENV.databaseUrl} to select the ` + `postgresql backend, or leave it unset for sqlite.`);
4967
+ }
4968
+ throw new Error(`Storage backend must be sqlite or postgres`);
4960
4969
  }
4961
4970
  function parseStorageMode(value) {
4962
4971
  return parseStorageBackend(value);
4963
4972
  }
4964
4973
  function getTodosStorageBackend(env = process.env) {
4965
- return parseStorageBackend(readStorageEnv(env, "mode").value);
4974
+ for (const key of REMOVED_STORAGE_MODE_ENV_KEYS) {
4975
+ if (Object.hasOwn(env, key) && env[key] !== undefined) {
4976
+ throw new Error(`${key} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `Set ${TODOS_STORAGE_ENV.databaseUrl} to select the postgresql backend, ` + `or leave it unset for sqlite.`);
4977
+ }
4978
+ }
4979
+ if (!getTodosStorageDatabaseUrl(env))
4980
+ return "sqlite";
4981
+ if (isTodosShadowEnabled(env))
4982
+ return "sqlite";
4983
+ return "postgres";
4966
4984
  }
4967
4985
  function getTodosStorageMode(env = process.env) {
4968
4986
  return getTodosStorageBackend(env);
@@ -5036,7 +5054,7 @@ function parsePositiveInteger(value, fallback) {
5036
5054
  }
5037
5055
  return parsed;
5038
5056
  }
5039
- 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;
5057
+ 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";
5040
5058
  var init_config2 = __esm(() => {
5041
5059
  TODOS_STORAGE_TABLES = [
5042
5060
  "todos_sync_records",
@@ -5044,7 +5062,6 @@ var init_config2 = __esm(() => {
5044
5062
  ];
5045
5063
  STORAGE_TABLES = TODOS_STORAGE_TABLES;
5046
5064
  TODOS_STORAGE_ENV = {
5047
- mode: "HASNA_TODOS_STORAGE_MODE",
5048
5065
  shadow: "HASNA_TODOS_SHADOW",
5049
5066
  databaseUrl: "HASNA_TODOS_DATABASE_URL",
5050
5067
  databaseSsl: "HASNA_TODOS_DATABASE_SSL",
@@ -5061,7 +5078,6 @@ var init_config2 = __esm(() => {
5061
5078
  syncDryRun: "HASNA_TODOS_SYNC_DRY_RUN"
5062
5079
  };
5063
5080
  TODOS_STORAGE_FALLBACK_ENV = {
5064
- mode: "TODOS_STORAGE_MODE",
5065
5081
  shadow: "TODOS_SHADOW",
5066
5082
  databaseUrl: "TODOS_DATABASE_URL",
5067
5083
  databaseSsl: "TODOS_DATABASE_SSL",
@@ -5077,14 +5093,12 @@ var init_config2 = __esm(() => {
5077
5093
  syncBatchSize: "TODOS_SYNC_BATCH_SIZE",
5078
5094
  syncDryRun: "TODOS_SYNC_DRY_RUN"
5079
5095
  };
5080
- LEGACY_BACKEND_TOKENS = {
5081
- local: "sqlite",
5082
- remote: "postgres",
5083
- postgresql: "postgres",
5084
- hybrid: "postgres",
5085
- self_hosted: "postgres",
5086
- cloud: "postgres"
5087
- };
5096
+ REMOVED_STORAGE_MODE_ENV_KEYS = [
5097
+ "HASNA_TODOS_STORAGE_MODE",
5098
+ "HASNA_TODOS_MODE",
5099
+ "TODOS_STORAGE_MODE",
5100
+ "TODOS_MODE"
5101
+ ];
5088
5102
  });
5089
5103
 
5090
5104
  // src/storage/shadow-outbox-schema.ts
@@ -5566,17 +5580,24 @@ function createProject2(input, db) {
5566
5580
  const id = uuid();
5567
5581
  const timestamp2 = now();
5568
5582
  const derivedSlug = slugify(input.name);
5569
- const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : slugify(input.task_list_id);
5583
+ const taskListId = input.task_list_id === undefined ? derivedSlug : slugify(input.task_list_id);
5570
5584
  if (!derivedSlug || !taskListId)
5571
5585
  throw new Error("Project name and task-list slug must be non-empty");
5572
5586
  const slugConflict = d.query("SELECT id FROM projects WHERE task_list_id = ? LIMIT 1").get(taskListId);
5573
5587
  if (slugConflict || !claimCanonicalSlug("project", "global", taskListId, id, d)) {
5574
5588
  throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Project slug "${taskListId}" already exists`);
5575
5589
  }
5590
+ const parentId = input.parent_id ?? null;
5591
+ if (parentId !== null) {
5592
+ const parent = getProject(parentId, d);
5593
+ if (!parent)
5594
+ throw new ProjectNotFoundError(parentId);
5595
+ assertNotProjectAncestor(id, parentId, d);
5596
+ }
5576
5597
  const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
5577
5598
  const machineId = currentStorageMachineId(d);
5578
- d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
5579
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp2, timestamp2, machineId]);
5599
+ d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, parent_id, created_at, updated_at, machine_id)
5600
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, parentId, timestamp2, timestamp2, machineId]);
5580
5601
  return getProject(id, d);
5581
5602
  })();
5582
5603
  }
@@ -5601,6 +5622,60 @@ function listProjects(db) {
5601
5622
  const d = db || getDatabase();
5602
5623
  return d.query("SELECT * FROM projects ORDER BY name").all();
5603
5624
  }
5625
+ function orderProjectsParentFirst(projects) {
5626
+ const projectId = (project) => {
5627
+ const id = project.id;
5628
+ return typeof id === "string" && id.length > 0 ? id : null;
5629
+ };
5630
+ const parentId = (project) => {
5631
+ const parent = project.parent_id;
5632
+ return parent == null ? null : String(parent);
5633
+ };
5634
+ const byId = new Set;
5635
+ for (const project of projects) {
5636
+ const id = projectId(project);
5637
+ if (id !== null)
5638
+ byId.add(id);
5639
+ }
5640
+ const ordered = [];
5641
+ const emitted = new Set;
5642
+ let remaining = [...projects];
5643
+ let progress = true;
5644
+ while (progress && remaining.length > 0) {
5645
+ progress = false;
5646
+ const deferred = [];
5647
+ for (const project of remaining) {
5648
+ const parent = parentId(project);
5649
+ if (parent === null || emitted.has(parent) || !byId.has(parent)) {
5650
+ ordered.push(project);
5651
+ const id = projectId(project);
5652
+ if (id !== null)
5653
+ emitted.add(id);
5654
+ progress = true;
5655
+ } else {
5656
+ deferred.push(project);
5657
+ }
5658
+ }
5659
+ remaining = deferred;
5660
+ }
5661
+ ordered.push(...remaining);
5662
+ return ordered;
5663
+ }
5664
+ function assertNotProjectAncestor(projectId, ancestorId, db) {
5665
+ const d = db || getDatabase();
5666
+ let cursor = ancestorId;
5667
+ const seen = new Set;
5668
+ while (cursor !== null) {
5669
+ if (cursor === projectId) {
5670
+ throw new ResourceConflictError("PROJECT_PARENT_CYCLE", `Project "${projectId}" cannot be placed under its own descendant`);
5671
+ }
5672
+ if (seen.has(cursor))
5673
+ break;
5674
+ seen.add(cursor);
5675
+ const row = d.query("SELECT parent_id FROM projects WHERE id = ?").get(cursor);
5676
+ cursor = row?.parent_id ?? null;
5677
+ }
5678
+ }
5604
5679
  function updateProject2(id, input, db) {
5605
5680
  const d = db || getDatabase();
5606
5681
  const project = getProject(id, d);
@@ -5623,6 +5698,16 @@ function updateProject2(id, input, db) {
5623
5698
  sets.push("path = ?");
5624
5699
  params.push(input.path);
5625
5700
  }
5701
+ if (input.parent_id !== undefined) {
5702
+ if (input.parent_id !== null) {
5703
+ const parent = getProject(input.parent_id, d);
5704
+ if (!parent)
5705
+ throw new ProjectNotFoundError(input.parent_id);
5706
+ assertNotProjectAncestor(id, input.parent_id, d);
5707
+ }
5708
+ sets.push("parent_id = ?");
5709
+ params.push(input.parent_id);
5710
+ }
5626
5711
  params.push(id);
5627
5712
  d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
5628
5713
  return getProject(id, d);
@@ -12723,7 +12808,7 @@ import { createHash as createHash7 } from "crypto";
12723
12808
  // package.json
12724
12809
  var package_default = {
12725
12810
  name: "@hasna/todos",
12726
- version: "0.15.33",
12811
+ version: "0.15.35",
12727
12812
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12728
12813
  type: "module",
12729
12814
  main: "dist/index.js",
@@ -12797,9 +12882,10 @@ var package_default = {
12797
12882
  "verify:release": "bun run scripts/verify-public-release.ts --mode=review",
12798
12883
  "verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
12799
12884
  "verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
12800
- "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts scripts/attested-container-compatibility-vector.test.ts",
12885
+ "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
12801
12886
  "emit:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts emit",
12802
12887
  "verify:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts verify",
12888
+ "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
12803
12889
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
12804
12890
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
12805
12891
  postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
@@ -14246,8 +14332,12 @@ class PostgresJsonRecordStore {
14246
14332
  OR ($11::text <> $2
14247
14333
  AND NOT EXISTS (SELECT 1 FROM parent_chain WHERE object_id = $2)
14248
14334
  AND NOT EXISTS (SELECT 1 FROM parent_chain WHERE cycle))) AS parent_acyclic,
14249
- (SELECT count(*) FROM locked_plans) = jsonb_array_length($7::jsonb) AS all_plans_found,
14250
- ($8::text IS NULL OR EXISTS (SELECT 1 FROM locked_plans WHERE object_id = $8)) AS target_plan_found,
14335
+ (COALESCE((SELECT payload->>'plan_id' FROM locked_task), '')
14336
+ IS DISTINCT FROM COALESCE($3::jsonb->>'plan_id', '')) AS membership_changed,
14337
+ (NOT (COALESCE((SELECT payload->>'plan_id' FROM locked_task), '')
14338
+ IS DISTINCT FROM COALESCE($3::jsonb->>'plan_id', ''))
14339
+ OR $8::text IS NULL
14340
+ OR EXISTS (SELECT 1 FROM locked_plans WHERE object_id = $8)) AS target_plan_found,
14251
14341
  (SELECT payload->>'project_id' FROM locked_plans WHERE object_id = $8) AS target_project_id
14252
14342
  ), guarded AS (
14253
14343
  SELECT
@@ -14270,7 +14360,6 @@ class PostgresJsonRecordStore {
14270
14360
  AND guarded.version_matches
14271
14361
  AND guarded.parent_found
14272
14362
  AND guarded.parent_acyclic
14273
- AND guarded.all_plans_found
14274
14363
  AND guarded.target_plan_found
14275
14364
  AND NOT guarded.project_conflict
14276
14365
  ON CONFLICT (service, object_type, object_id) DO UPDATE SET
@@ -14286,7 +14375,7 @@ class PostgresJsonRecordStore {
14286
14375
  RETURNING payload
14287
14376
  )
14288
14377
  SELECT guarded.task_found, guarded.version_matches, guarded.parent_found, guarded.parent_acyclic,
14289
- guarded.all_plans_found, guarded.target_plan_found, guarded.project_conflict,
14378
+ guarded.membership_changed, guarded.target_plan_found, guarded.project_conflict,
14290
14379
  (SELECT payload FROM stored) AS payload,
14291
14380
  (SELECT payload FROM locked_task) AS current_payload
14292
14381
  FROM guarded`, [
@@ -14318,7 +14407,7 @@ class PostgresJsonRecordStore {
14318
14407
  if (parentGuard && !row?.parent_acyclic && parentGuard.parentId) {
14319
14408
  throw new ResourceConflictError("TASK_PARENT_CYCLE", `TASK_PARENT_CYCLE: assigning parent ${parentGuard.parentId} to task ${value.id} would create or retain a parent cycle`);
14320
14409
  }
14321
- if (!row?.all_plans_found || !row.target_plan_found) {
14410
+ if (!row?.target_plan_found) {
14322
14411
  throw new PlanProjectLinkError("PLAN_PROJECT_LINK_PLAN_NOT_FOUND", `Plan membership changed through a missing plan: ${targetPlanId ?? planIds.join(", ")}`, { plan_ids: planIds, target_plan_id: targetPlanId });
14323
14412
  }
14324
14413
  if (row.project_conflict) {
@@ -15530,17 +15619,26 @@ async function getChangedSince(since, filters, store) {
15530
15619
  async function createProject(input, store, context) {
15531
15620
  const timestamp = new Date().toISOString();
15532
15621
  const derivedSlug = slugifyRaw(input.name);
15533
- const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : slugifyRaw(input.task_list_id);
15622
+ const taskListId = input.task_list_id === undefined ? derivedSlug : slugifyRaw(input.task_list_id);
15534
15623
  if (!derivedSlug || !taskListId)
15535
15624
  throw new Error("Project name and task-list slug must be non-empty");
15625
+ const parentId = input.parent_id ?? null;
15626
+ const id = randomUUID();
15627
+ if (parentId !== null) {
15628
+ const parent = await store.get("projects", parentId);
15629
+ if (!parent)
15630
+ throw new ProjectNotFoundError(parentId);
15631
+ await assertNotProjectAncestorPostgres(id, parentId, store);
15632
+ }
15536
15633
  const project = {
15537
- id: randomUUID(),
15634
+ id,
15538
15635
  name: input.name,
15539
15636
  path: input.path,
15540
15637
  description: input.description ?? null,
15541
15638
  task_list_id: taskListId,
15542
15639
  task_prefix: input.task_prefix ?? await generateProjectPrefix(input.name, store),
15543
15640
  task_counter: 0,
15641
+ parent_id: parentId,
15544
15642
  created_at: timestamp,
15545
15643
  updated_at: timestamp,
15546
15644
  machine_id: store.machineId(context),
@@ -15548,12 +15646,38 @@ async function createProject(input, store, context) {
15548
15646
  };
15549
15647
  return store.upsert("projects", project, context);
15550
15648
  }
15649
+ async function assertNotProjectAncestorPostgres(projectId, candidateParentId, store) {
15650
+ const all = await store.list("projects");
15651
+ const byId = new Map(all.map((project) => [project.id, project.parent_id ?? null]));
15652
+ let cursor = candidateParentId;
15653
+ const seen = new Set;
15654
+ while (cursor !== null) {
15655
+ if (cursor === projectId) {
15656
+ throw new ResourceConflictError("PROJECT_PARENT_CYCLE", `Project "${projectId}" cannot be placed under its own descendant`);
15657
+ }
15658
+ if (seen.has(cursor))
15659
+ break;
15660
+ seen.add(cursor);
15661
+ cursor = byId.get(cursor) ?? null;
15662
+ }
15663
+ }
15551
15664
  async function updateProject(id, input, store) {
15552
15665
  if ("task_list_id" in input) {
15553
15666
  throw new Error("task_list_id cannot be changed by updateProject; use renameProject for an atomic canonical rename");
15554
15667
  }
15555
15668
  const project = await requireRecord("projects", id, store);
15556
- const updated = { ...project, ...definedPatch(input), updated_at: new Date().toISOString() };
15669
+ if (input.parent_id !== undefined && input.parent_id !== null) {
15670
+ const parent = await store.get("projects", input.parent_id);
15671
+ if (!parent)
15672
+ throw new ProjectNotFoundError(input.parent_id);
15673
+ await assertNotProjectAncestorPostgres(id, input.parent_id, store);
15674
+ }
15675
+ const updated = {
15676
+ ...project,
15677
+ ...definedPatch(input),
15678
+ ...input.parent_id !== undefined ? { parent_id: input.parent_id } : {},
15679
+ updated_at: new Date().toISOString()
15680
+ };
15557
15681
  return store.upsert("projects", updated);
15558
15682
  }
15559
15683
  async function createPlan(input, store, context) {
@@ -17564,6 +17688,7 @@ var PROJECT_COLUMNS = [
17564
17688
  "task_list_id",
17565
17689
  "task_prefix",
17566
17690
  "task_counter",
17691
+ "parent_id",
17567
17692
  "created_at",
17568
17693
  "updated_at",
17569
17694
  "machine_id",
@@ -17783,7 +17908,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
17783
17908
  }
17784
17909
  }
17785
17910
  };
17786
- applyRows("projects", "projects", PROJECT_COLUMNS, snapshot.projects, "updated_at");
17911
+ applyRows("projects", "projects", PROJECT_COLUMNS, orderProjectsParentFirst(snapshot.projects), "updated_at");
17787
17912
  applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
17788
17913
  applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
17789
17914
  applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
@@ -18613,7 +18738,7 @@ class StagedSqliteTodosProjectRegistrationTransaction {
18613
18738
  }
18614
18739
  async createProject(input) {
18615
18740
  const derivedSlug = normalizeSlug(input.name);
18616
- const taskListId = input.task_list_id === undefined ? `todos-${derivedSlug}` : normalizeSlug(input.task_list_id);
18741
+ const taskListId = input.task_list_id === undefined ? derivedSlug : normalizeSlug(input.task_list_id);
18617
18742
  if (!derivedSlug || !taskListId) {
18618
18743
  throw new Error("Project name and task-list slug must be non-empty");
18619
18744
  }
@@ -18625,6 +18750,7 @@ class StagedSqliteTodosProjectRegistrationTransaction {
18625
18750
  task_list_id: taskListId,
18626
18751
  task_prefix: input.task_prefix ?? this.availableProjectPrefix(input.name),
18627
18752
  task_counter: 0,
18753
+ parent_id: input.parent_id ?? null,
18628
18754
  created_at: now(),
18629
18755
  updated_at: now(),
18630
18756
  machine_id: currentStorageMachineId(this.db)
@@ -18635,14 +18761,15 @@ class StagedSqliteTodosProjectRegistrationTransaction {
18635
18761
  try {
18636
18762
  const result = this.db.run(`INSERT INTO projects (
18637
18763
  id, name, path, description, task_list_id, task_prefix,
18638
- task_counter, created_at, updated_at, machine_id
18639
- ) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)`, [
18764
+ task_counter, parent_id, created_at, updated_at, machine_id
18765
+ ) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)`, [
18640
18766
  project.id,
18641
18767
  project.name,
18642
18768
  project.path,
18643
18769
  project.description,
18644
18770
  project.task_list_id,
18645
18771
  project.task_prefix,
18772
+ project.parent_id,
18646
18773
  project.created_at,
18647
18774
  project.updated_at,
18648
18775
  project.machine_id ?? null
@@ -19038,7 +19165,10 @@ function taskListSlug(projectSlug) {
19038
19165
  if (!slug || slug !== projectSlug) {
19039
19166
  throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "project_slug must be canonical kebab-case");
19040
19167
  }
19041
- return `todos-${slug}`;
19168
+ return slug;
19169
+ }
19170
+ function legacyTaskListSlug(projectSlug) {
19171
+ return `todos-${normalizeSlug(projectSlug)}`;
19042
19172
  }
19043
19173
  function deterministicTaskPrefix(projectSlug) {
19044
19174
  const letters = projectSlug.replace(/[^a-z0-9]/gi, "").toUpperCase();
@@ -19557,6 +19687,12 @@ class PackageOwnedTodosProjectRegistrationAuthority {
19557
19687
  created_by_operation: false
19558
19688
  };
19559
19689
  }
19690
+ if (request.bind_existing === true && conflict2.path === path && conflict2.task_list_id === legacyTaskListSlug(request.project_slug)) {
19691
+ return {
19692
+ record: boundExistingProjectRecord(conflict2),
19693
+ created_by_operation: false
19694
+ };
19695
+ }
19560
19696
  return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
19561
19697
  }
19562
19698
  await this.fault("before_object_write", request);
@@ -19593,6 +19729,15 @@ class PackageOwnedTodosProjectRegistrationAuthority {
19593
19729
  }
19594
19730
  return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
19595
19731
  }
19732
+ if (request.bind_existing === true) {
19733
+ const legacy = await transaction.findTaskListConflict(todosProjectId, legacyTaskListSlug(request.project_slug));
19734
+ if (legacy && legacy.project_id === todosProjectId) {
19735
+ return {
19736
+ record: boundExistingTaskListRecord(legacy),
19737
+ created_by_operation: false
19738
+ };
19739
+ }
19740
+ }
19596
19741
  await this.fault("before_object_write", request);
19597
19742
  const taskList = await transaction.createTaskList({
19598
19743
  name: request.project_name,