@hasna/todos 0.15.34 → 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 +1 -1
- 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 +255 -43
- package/dist/contracts.js +86 -4
- 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 +209 -39
- package/dist/json-contracts.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 +221 -39
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp.js +4 -1
- 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 +156 -17
- package/dist/registry.js +86 -4
- 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 +221 -39
- 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.js +128 -12
- package/dist/types/index.d.ts +5 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -1
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");
|
|
@@ -5045,17 +5051,24 @@ function createProject(input, db) {
|
|
|
5045
5051
|
const id = uuid();
|
|
5046
5052
|
const timestamp2 = now();
|
|
5047
5053
|
const derivedSlug = slugify(input.name);
|
|
5048
|
-
const taskListId = input.task_list_id === undefined ?
|
|
5054
|
+
const taskListId = input.task_list_id === undefined ? derivedSlug : slugify(input.task_list_id);
|
|
5049
5055
|
if (!derivedSlug || !taskListId)
|
|
5050
5056
|
throw new Error("Project name and task-list slug must be non-empty");
|
|
5051
5057
|
const slugConflict = d.query("SELECT id FROM projects WHERE task_list_id = ? LIMIT 1").get(taskListId);
|
|
5052
5058
|
if (slugConflict || !claimCanonicalSlug("project", "global", taskListId, id, d)) {
|
|
5053
5059
|
throw new ResourceConflictError("PROJECT_SLUG_CONFLICT", `Project slug "${taskListId}" already exists`);
|
|
5054
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
|
+
}
|
|
5055
5068
|
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
5056
5069
|
const machineId = currentStorageMachineId(d);
|
|
5057
|
-
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at, machine_id)
|
|
5058
|
-
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]);
|
|
5059
5072
|
return getProject(id, d);
|
|
5060
5073
|
})();
|
|
5061
5074
|
}
|
|
@@ -5080,6 +5093,60 @@ function listProjects(db) {
|
|
|
5080
5093
|
const d = db || getDatabase();
|
|
5081
5094
|
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
5082
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
|
+
}
|
|
5083
5150
|
function updateProject(id, input, db) {
|
|
5084
5151
|
const d = db || getDatabase();
|
|
5085
5152
|
const project = getProject(id, d);
|
|
@@ -5102,6 +5169,16 @@ function updateProject(id, input, db) {
|
|
|
5102
5169
|
sets.push("path = ?");
|
|
5103
5170
|
params.push(input.path);
|
|
5104
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
|
+
}
|
|
5105
5182
|
params.push(id);
|
|
5106
5183
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5107
5184
|
return getProject(id, d);
|
|
@@ -12728,7 +12805,7 @@ var init_tasks = __esm(() => {
|
|
|
12728
12805
|
// package.json
|
|
12729
12806
|
var package_default = {
|
|
12730
12807
|
name: "@hasna/todos",
|
|
12731
|
-
version: "0.15.
|
|
12808
|
+
version: "0.15.35",
|
|
12732
12809
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12733
12810
|
type: "module",
|
|
12734
12811
|
main: "dist/index.js",
|
|
@@ -12803,6 +12880,9 @@ var package_default = {
|
|
|
12803
12880
|
"verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
|
|
12804
12881
|
"verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
|
|
12805
12882
|
"test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
|
|
12883
|
+
"emit:iapp-deployment-compatibility-vector": "bun run scripts/attested-container-compatibility-vector.ts emit",
|
|
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",
|
|
12806
12886
|
"issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
|
|
12807
12887
|
prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
|
|
12808
12888
|
postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
|
|
@@ -13491,6 +13571,7 @@ var TODOS_JSON_CONTRACTS = [
|
|
|
13491
13571
|
task_list_id: nullableIdField,
|
|
13492
13572
|
task_prefix: field(["string", "null"], "Optional task prefix.", true),
|
|
13493
13573
|
task_counter: field("integer", "Monotonic project task counter."),
|
|
13574
|
+
parent_id: field(["string", "null"], "Optional parent project id; null means top-level.", true),
|
|
13494
13575
|
created_at: isoDateField,
|
|
13495
13576
|
updated_at: isoDateField
|
|
13496
13577
|
},
|
|
@@ -16776,6 +16857,7 @@ function createAgentProjectDemoBundle() {
|
|
|
16776
16857
|
task_list_id: ids.list,
|
|
16777
16858
|
task_prefix: "DEMO",
|
|
16778
16859
|
task_counter: 4,
|
|
16860
|
+
parent_id: null,
|
|
16779
16861
|
created_at: createdAt,
|
|
16780
16862
|
updated_at: completedAt
|
|
16781
16863
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,UAwrDtB,CAAC"}
|
package/dist/db/projects.d.ts
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"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"}
|
package/dist/db/schema.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|