@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.
- package/dist/cli/cloud-router.d.ts +33 -47
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +344 -127
- package/dist/cli/stage-a.d.ts +3 -1
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.js +115 -27
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/projects.d.ts +23 -0
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.js +275 -116
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/assignee-validation.d.ts.map +1 -1
- package/dist/lib/import-export-bridge.d.ts.map +1 -1
- package/dist/lib/onboarding-fixtures.d.ts.map +1 -1
- package/dist/mcp/index.js +285 -113
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp.js +3 -2
- package/dist/project-registration/authority.d.ts.map +1 -1
- package/dist/project-registration/sqlite.d.ts.map +1 -1
- package/dist/project-registration.js +185 -40
- package/dist/registry.js +115 -27
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/v1.generated.d.ts +3 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +1268 -7051
- package/dist/server/openapi.d.ts +13 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/config.d.ts +24 -4
- package/dist/storage/config.d.ts.map +1 -1
- package/dist/storage/hybrid.d.ts +5 -3
- package/dist/storage/hybrid.d.ts.map +1 -1
- package/dist/storage.js +158 -34
- package/dist/task-manifest.js +3 -3
- package/dist/testing.d.ts +16 -7
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +10 -6
- package/dist/types/index.d.ts +5 -2
- package/dist/types/index.d.ts.map +1 -1
- 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,
|
|
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.
|
|
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
|
|
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;
|
|
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;
|
|
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: /\
|
|
761
|
-
{ name: "github_oauth", pattern: /\
|
|
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
|
-
/
|
|
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
|
|
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"
|
|
4955
|
-
return
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
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 ?
|
|
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.
|
|
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
|
|
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
|
|
14250
|
-
|
|
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.
|
|
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?.
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
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,
|