@hasna/todos 0.11.41 → 0.11.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -9
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1331 -264
- package/dist/cli-mcp-parity.d.ts +1 -1
- package/dist/cli-mcp-parity.d.ts.map +1 -1
- package/dist/contracts.js +73 -6
- package/dist/db/builtin-templates.d.ts +17 -0
- package/dist/db/builtin-templates.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +785 -168
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/local-bridge.d.ts +2 -0
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/saved-search-views.d.ts +60 -0
- package/dist/lib/saved-search-views.d.ts.map +1 -0
- package/dist/lib/todos-md.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1485 -99
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/templates.d.ts.map +1 -1
- package/dist/mcp.js +7 -1
- package/dist/registry.js +122 -8
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +24 -0
- package/dist/storage.js +24 -0
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -981,6 +981,19 @@ var init_migrations = __esm(() => {
|
|
|
981
981
|
);
|
|
982
982
|
CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at);
|
|
983
983
|
INSERT OR IGNORE INTO _migrations (id) VALUES (54);
|
|
984
|
+
`,
|
|
985
|
+
`
|
|
986
|
+
CREATE TABLE IF NOT EXISTS saved_search_views (
|
|
987
|
+
id TEXT PRIMARY KEY,
|
|
988
|
+
name TEXT NOT NULL UNIQUE,
|
|
989
|
+
description TEXT,
|
|
990
|
+
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
|
|
991
|
+
filters TEXT NOT NULL DEFAULT '{}',
|
|
992
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
993
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
994
|
+
);
|
|
995
|
+
CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope);
|
|
996
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (55);
|
|
984
997
|
`
|
|
985
998
|
];
|
|
986
999
|
});
|
|
@@ -1157,6 +1170,17 @@ function ensureSchema(db) {
|
|
|
1157
1170
|
PRIMARY KEY (handoff_id, agent_id)
|
|
1158
1171
|
)`);
|
|
1159
1172
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at)");
|
|
1173
|
+
ensureTable("saved_search_views", `
|
|
1174
|
+
CREATE TABLE saved_search_views (
|
|
1175
|
+
id TEXT PRIMARY KEY,
|
|
1176
|
+
name TEXT NOT NULL UNIQUE,
|
|
1177
|
+
description TEXT,
|
|
1178
|
+
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
|
|
1179
|
+
filters TEXT NOT NULL DEFAULT '{}',
|
|
1180
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1181
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1182
|
+
)`);
|
|
1183
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope)");
|
|
1160
1184
|
ensureTable("task_relationships", `
|
|
1161
1185
|
CREATE TABLE task_relationships (
|
|
1162
1186
|
id TEXT PRIMARY KEY,
|
|
@@ -4412,6 +4436,26 @@ var init_recurrence = __esm(() => {
|
|
|
4412
4436
|
});
|
|
4413
4437
|
|
|
4414
4438
|
// src/db/templates.ts
|
|
4439
|
+
var exports_templates = {};
|
|
4440
|
+
__export(exports_templates, {
|
|
4441
|
+
updateTemplate: () => updateTemplate,
|
|
4442
|
+
tasksFromTemplate: () => tasksFromTemplate,
|
|
4443
|
+
taskFromTemplate: () => taskFromTemplate,
|
|
4444
|
+
resolveVariables: () => resolveVariables,
|
|
4445
|
+
previewTemplate: () => previewTemplate,
|
|
4446
|
+
listTemplates: () => listTemplates,
|
|
4447
|
+
listTemplateVersions: () => listTemplateVersions,
|
|
4448
|
+
importTemplate: () => importTemplate,
|
|
4449
|
+
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
4450
|
+
getTemplateVersion: () => getTemplateVersion,
|
|
4451
|
+
getTemplateTasks: () => getTemplateTasks,
|
|
4452
|
+
getTemplate: () => getTemplate,
|
|
4453
|
+
exportTemplate: () => exportTemplate,
|
|
4454
|
+
evaluateCondition: () => evaluateCondition,
|
|
4455
|
+
deleteTemplate: () => deleteTemplate,
|
|
4456
|
+
createTemplate: () => createTemplate,
|
|
4457
|
+
addTemplateTasks: () => addTemplateTasks
|
|
4458
|
+
});
|
|
4415
4459
|
function rowToTemplate(row) {
|
|
4416
4460
|
return {
|
|
4417
4461
|
...row,
|
|
@@ -4422,9 +4466,42 @@ function rowToTemplate(row) {
|
|
|
4422
4466
|
version: row.version ?? 1
|
|
4423
4467
|
};
|
|
4424
4468
|
}
|
|
4469
|
+
function rowToTemplateTask(row) {
|
|
4470
|
+
return {
|
|
4471
|
+
...row,
|
|
4472
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
4473
|
+
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
4474
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
4475
|
+
priority: row.priority || "medium",
|
|
4476
|
+
condition: row.condition ?? null,
|
|
4477
|
+
include_template_id: row.include_template_id ?? null
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4425
4480
|
function resolveTemplateId(id, d) {
|
|
4426
4481
|
return resolvePartialId(d, "task_templates", id);
|
|
4427
4482
|
}
|
|
4483
|
+
function createTemplate(input, db) {
|
|
4484
|
+
const d = db || getDatabase();
|
|
4485
|
+
const id = uuid();
|
|
4486
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
|
|
4487
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4488
|
+
id,
|
|
4489
|
+
input.name,
|
|
4490
|
+
input.title_pattern,
|
|
4491
|
+
input.description || null,
|
|
4492
|
+
input.priority || "medium",
|
|
4493
|
+
JSON.stringify(input.tags || []),
|
|
4494
|
+
JSON.stringify(input.variables || []),
|
|
4495
|
+
input.project_id || null,
|
|
4496
|
+
input.plan_id || null,
|
|
4497
|
+
JSON.stringify(input.metadata || {}),
|
|
4498
|
+
now()
|
|
4499
|
+
]);
|
|
4500
|
+
if (input.tasks && input.tasks.length > 0) {
|
|
4501
|
+
addTemplateTasks(id, input.tasks, d);
|
|
4502
|
+
}
|
|
4503
|
+
return getTemplate(id, d);
|
|
4504
|
+
}
|
|
4428
4505
|
function getTemplate(id, db) {
|
|
4429
4506
|
const d = db || getDatabase();
|
|
4430
4507
|
const resolved = resolveTemplateId(id, d);
|
|
@@ -4433,6 +4510,80 @@ function getTemplate(id, db) {
|
|
|
4433
4510
|
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(resolved);
|
|
4434
4511
|
return row ? rowToTemplate(row) : null;
|
|
4435
4512
|
}
|
|
4513
|
+
function listTemplates(db) {
|
|
4514
|
+
const d = db || getDatabase();
|
|
4515
|
+
return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
|
|
4516
|
+
}
|
|
4517
|
+
function deleteTemplate(id, db) {
|
|
4518
|
+
const d = db || getDatabase();
|
|
4519
|
+
const resolved = resolveTemplateId(id, d);
|
|
4520
|
+
if (!resolved)
|
|
4521
|
+
return false;
|
|
4522
|
+
return d.run("DELETE FROM task_templates WHERE id = ?", [resolved]).changes > 0;
|
|
4523
|
+
}
|
|
4524
|
+
function updateTemplate(id, updates, db) {
|
|
4525
|
+
const d = db || getDatabase();
|
|
4526
|
+
const resolved = resolveTemplateId(id, d);
|
|
4527
|
+
if (!resolved)
|
|
4528
|
+
return null;
|
|
4529
|
+
const current = getTemplateWithTasks(resolved, d);
|
|
4530
|
+
if (current) {
|
|
4531
|
+
const snapshot = JSON.stringify({
|
|
4532
|
+
name: current.name,
|
|
4533
|
+
title_pattern: current.title_pattern,
|
|
4534
|
+
description: current.description,
|
|
4535
|
+
priority: current.priority,
|
|
4536
|
+
tags: current.tags,
|
|
4537
|
+
variables: current.variables,
|
|
4538
|
+
project_id: current.project_id,
|
|
4539
|
+
plan_id: current.plan_id,
|
|
4540
|
+
metadata: current.metadata,
|
|
4541
|
+
tasks: current.tasks
|
|
4542
|
+
});
|
|
4543
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
4544
|
+
}
|
|
4545
|
+
const sets = ["version = version + 1"];
|
|
4546
|
+
const values = [];
|
|
4547
|
+
if (updates.name !== undefined) {
|
|
4548
|
+
sets.push("name = ?");
|
|
4549
|
+
values.push(updates.name);
|
|
4550
|
+
}
|
|
4551
|
+
if (updates.title_pattern !== undefined) {
|
|
4552
|
+
sets.push("title_pattern = ?");
|
|
4553
|
+
values.push(updates.title_pattern);
|
|
4554
|
+
}
|
|
4555
|
+
if (updates.description !== undefined) {
|
|
4556
|
+
sets.push("description = ?");
|
|
4557
|
+
values.push(updates.description);
|
|
4558
|
+
}
|
|
4559
|
+
if (updates.priority !== undefined) {
|
|
4560
|
+
sets.push("priority = ?");
|
|
4561
|
+
values.push(updates.priority);
|
|
4562
|
+
}
|
|
4563
|
+
if (updates.tags !== undefined) {
|
|
4564
|
+
sets.push("tags = ?");
|
|
4565
|
+
values.push(JSON.stringify(updates.tags));
|
|
4566
|
+
}
|
|
4567
|
+
if (updates.variables !== undefined) {
|
|
4568
|
+
sets.push("variables = ?");
|
|
4569
|
+
values.push(JSON.stringify(updates.variables));
|
|
4570
|
+
}
|
|
4571
|
+
if (updates.project_id !== undefined) {
|
|
4572
|
+
sets.push("project_id = ?");
|
|
4573
|
+
values.push(updates.project_id);
|
|
4574
|
+
}
|
|
4575
|
+
if (updates.plan_id !== undefined) {
|
|
4576
|
+
sets.push("plan_id = ?");
|
|
4577
|
+
values.push(updates.plan_id);
|
|
4578
|
+
}
|
|
4579
|
+
if (updates.metadata !== undefined) {
|
|
4580
|
+
sets.push("metadata = ?");
|
|
4581
|
+
values.push(JSON.stringify(updates.metadata));
|
|
4582
|
+
}
|
|
4583
|
+
values.push(resolved);
|
|
4584
|
+
d.run(`UPDATE task_templates SET ${sets.join(", ")} WHERE id = ?`, values);
|
|
4585
|
+
return getTemplate(resolved, d);
|
|
4586
|
+
}
|
|
4436
4587
|
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
4437
4588
|
const t = getTemplate(templateId, db);
|
|
4438
4589
|
if (!t)
|
|
@@ -4449,6 +4600,291 @@ function taskFromTemplate(templateId, overrides = {}, db) {
|
|
|
4449
4600
|
...cleanOverrides
|
|
4450
4601
|
};
|
|
4451
4602
|
}
|
|
4603
|
+
function addTemplateTasks(templateId, tasks, db) {
|
|
4604
|
+
const d = db || getDatabase();
|
|
4605
|
+
const template = getTemplate(templateId, d);
|
|
4606
|
+
if (!template)
|
|
4607
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4608
|
+
d.run("DELETE FROM template_tasks WHERE template_id = ?", [templateId]);
|
|
4609
|
+
const results = [];
|
|
4610
|
+
for (let i = 0;i < tasks.length; i++) {
|
|
4611
|
+
const task = tasks[i];
|
|
4612
|
+
const id = uuid();
|
|
4613
|
+
d.run(`INSERT INTO template_tasks (id, template_id, position, title_pattern, description, priority, tags, task_type, condition, include_template_id, depends_on_positions, metadata, created_at)
|
|
4614
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4615
|
+
id,
|
|
4616
|
+
templateId,
|
|
4617
|
+
i,
|
|
4618
|
+
task.title_pattern,
|
|
4619
|
+
task.description || null,
|
|
4620
|
+
task.priority || "medium",
|
|
4621
|
+
JSON.stringify(task.tags || []),
|
|
4622
|
+
task.task_type || null,
|
|
4623
|
+
task.condition || null,
|
|
4624
|
+
task.include_template_id || null,
|
|
4625
|
+
JSON.stringify(task.depends_on || []),
|
|
4626
|
+
JSON.stringify(task.metadata || {}),
|
|
4627
|
+
now()
|
|
4628
|
+
]);
|
|
4629
|
+
const row = d.query("SELECT * FROM template_tasks WHERE id = ?").get(id);
|
|
4630
|
+
if (row)
|
|
4631
|
+
results.push(rowToTemplateTask(row));
|
|
4632
|
+
}
|
|
4633
|
+
return results;
|
|
4634
|
+
}
|
|
4635
|
+
function getTemplateWithTasks(id, db) {
|
|
4636
|
+
const d = db || getDatabase();
|
|
4637
|
+
const template = getTemplate(id, d);
|
|
4638
|
+
if (!template)
|
|
4639
|
+
return null;
|
|
4640
|
+
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(template.id);
|
|
4641
|
+
const tasks = rows.map(rowToTemplateTask);
|
|
4642
|
+
return { ...template, tasks };
|
|
4643
|
+
}
|
|
4644
|
+
function getTemplateTasks(templateId, db) {
|
|
4645
|
+
const d = db || getDatabase();
|
|
4646
|
+
const resolved = resolveTemplateId(templateId, d);
|
|
4647
|
+
if (!resolved)
|
|
4648
|
+
return [];
|
|
4649
|
+
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(resolved);
|
|
4650
|
+
return rows.map(rowToTemplateTask);
|
|
4651
|
+
}
|
|
4652
|
+
function evaluateCondition(condition, variables) {
|
|
4653
|
+
if (!condition || condition.trim() === "")
|
|
4654
|
+
return true;
|
|
4655
|
+
const trimmed = condition.trim();
|
|
4656
|
+
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
4657
|
+
if (eqMatch) {
|
|
4658
|
+
const varName = eqMatch[1];
|
|
4659
|
+
const expected = eqMatch[2].trim();
|
|
4660
|
+
return (variables[varName] ?? "") === expected;
|
|
4661
|
+
}
|
|
4662
|
+
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
4663
|
+
if (neqMatch) {
|
|
4664
|
+
const varName = neqMatch[1];
|
|
4665
|
+
const expected = neqMatch[2].trim();
|
|
4666
|
+
return (variables[varName] ?? "") !== expected;
|
|
4667
|
+
}
|
|
4668
|
+
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
4669
|
+
if (falsyMatch) {
|
|
4670
|
+
const varName = falsyMatch[1];
|
|
4671
|
+
const val = variables[varName];
|
|
4672
|
+
return !val || val === "" || val === "false";
|
|
4673
|
+
}
|
|
4674
|
+
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
4675
|
+
if (truthyMatch) {
|
|
4676
|
+
const varName = truthyMatch[1];
|
|
4677
|
+
const val = variables[varName];
|
|
4678
|
+
return !!val && val !== "" && val !== "false";
|
|
4679
|
+
}
|
|
4680
|
+
return true;
|
|
4681
|
+
}
|
|
4682
|
+
function exportTemplate(id, db) {
|
|
4683
|
+
const d = db || getDatabase();
|
|
4684
|
+
const template = getTemplateWithTasks(id, d);
|
|
4685
|
+
if (!template)
|
|
4686
|
+
throw new Error(`Template not found: ${id}`);
|
|
4687
|
+
return {
|
|
4688
|
+
name: template.name,
|
|
4689
|
+
title_pattern: template.title_pattern,
|
|
4690
|
+
description: template.description,
|
|
4691
|
+
priority: template.priority,
|
|
4692
|
+
tags: template.tags,
|
|
4693
|
+
variables: template.variables,
|
|
4694
|
+
project_id: template.project_id,
|
|
4695
|
+
plan_id: template.plan_id,
|
|
4696
|
+
metadata: template.metadata,
|
|
4697
|
+
tasks: template.tasks.map((t) => ({
|
|
4698
|
+
position: t.position,
|
|
4699
|
+
title_pattern: t.title_pattern,
|
|
4700
|
+
description: t.description,
|
|
4701
|
+
priority: t.priority,
|
|
4702
|
+
tags: t.tags,
|
|
4703
|
+
task_type: t.task_type,
|
|
4704
|
+
condition: t.condition,
|
|
4705
|
+
include_template_id: t.include_template_id,
|
|
4706
|
+
depends_on_positions: t.depends_on_positions,
|
|
4707
|
+
metadata: t.metadata
|
|
4708
|
+
}))
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4711
|
+
function importTemplate(json, db) {
|
|
4712
|
+
const d = db || getDatabase();
|
|
4713
|
+
const taskInputs = (json.tasks || []).map((t) => ({
|
|
4714
|
+
title_pattern: t.title_pattern,
|
|
4715
|
+
description: t.description ?? undefined,
|
|
4716
|
+
priority: t.priority,
|
|
4717
|
+
tags: t.tags,
|
|
4718
|
+
task_type: t.task_type ?? undefined,
|
|
4719
|
+
condition: t.condition ?? undefined,
|
|
4720
|
+
include_template_id: t.include_template_id ?? undefined,
|
|
4721
|
+
depends_on: t.depends_on_positions,
|
|
4722
|
+
metadata: t.metadata
|
|
4723
|
+
}));
|
|
4724
|
+
return createTemplate({
|
|
4725
|
+
name: json.name,
|
|
4726
|
+
title_pattern: json.title_pattern,
|
|
4727
|
+
description: json.description ?? undefined,
|
|
4728
|
+
priority: json.priority,
|
|
4729
|
+
tags: json.tags,
|
|
4730
|
+
variables: json.variables,
|
|
4731
|
+
project_id: json.project_id ?? undefined,
|
|
4732
|
+
plan_id: json.plan_id ?? undefined,
|
|
4733
|
+
metadata: json.metadata,
|
|
4734
|
+
tasks: taskInputs
|
|
4735
|
+
}, d);
|
|
4736
|
+
}
|
|
4737
|
+
function getTemplateVersion(id, version, db) {
|
|
4738
|
+
const d = db || getDatabase();
|
|
4739
|
+
const resolved = resolveTemplateId(id, d);
|
|
4740
|
+
if (!resolved)
|
|
4741
|
+
return null;
|
|
4742
|
+
const row = d.query("SELECT * FROM template_versions WHERE template_id = ? AND version = ?").get(resolved, version);
|
|
4743
|
+
return row || null;
|
|
4744
|
+
}
|
|
4745
|
+
function listTemplateVersions(id, db) {
|
|
4746
|
+
const d = db || getDatabase();
|
|
4747
|
+
const resolved = resolveTemplateId(id, d);
|
|
4748
|
+
if (!resolved)
|
|
4749
|
+
return [];
|
|
4750
|
+
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
4751
|
+
}
|
|
4752
|
+
function resolveVariables(templateVars, provided) {
|
|
4753
|
+
const merged = { ...provided };
|
|
4754
|
+
for (const v of templateVars) {
|
|
4755
|
+
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
4756
|
+
merged[v.name] = v.default;
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
const missing = [];
|
|
4760
|
+
for (const v of templateVars) {
|
|
4761
|
+
if (v.required && merged[v.name] === undefined) {
|
|
4762
|
+
missing.push(v.name);
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
if (missing.length > 0) {
|
|
4766
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
4767
|
+
}
|
|
4768
|
+
return merged;
|
|
4769
|
+
}
|
|
4770
|
+
function substituteVars(text, variables) {
|
|
4771
|
+
let result = text;
|
|
4772
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
4773
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
4774
|
+
}
|
|
4775
|
+
return result;
|
|
4776
|
+
}
|
|
4777
|
+
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
4778
|
+
const d = db || getDatabase();
|
|
4779
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
4780
|
+
if (!template)
|
|
4781
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4782
|
+
const visited = _visitedTemplateIds || new Set;
|
|
4783
|
+
if (visited.has(template.id)) {
|
|
4784
|
+
throw new Error(`Circular template reference detected: ${template.id}`);
|
|
4785
|
+
}
|
|
4786
|
+
visited.add(template.id);
|
|
4787
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
4788
|
+
if (template.tasks.length === 0) {
|
|
4789
|
+
const input = taskFromTemplate(templateId, { project_id: projectId, task_list_id: taskListId }, d);
|
|
4790
|
+
const task = createTask(input, d);
|
|
4791
|
+
return [task];
|
|
4792
|
+
}
|
|
4793
|
+
const createdTasks = [];
|
|
4794
|
+
const positionToId = new Map;
|
|
4795
|
+
const skippedPositions = new Set;
|
|
4796
|
+
for (const tt of template.tasks) {
|
|
4797
|
+
if (tt.include_template_id) {
|
|
4798
|
+
const includedTasks = tasksFromTemplate(tt.include_template_id, projectId, resolved, taskListId, d, visited);
|
|
4799
|
+
createdTasks.push(...includedTasks);
|
|
4800
|
+
if (includedTasks.length > 0) {
|
|
4801
|
+
positionToId.set(tt.position, includedTasks[0].id);
|
|
4802
|
+
} else {
|
|
4803
|
+
skippedPositions.add(tt.position);
|
|
4804
|
+
}
|
|
4805
|
+
continue;
|
|
4806
|
+
}
|
|
4807
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved)) {
|
|
4808
|
+
skippedPositions.add(tt.position);
|
|
4809
|
+
continue;
|
|
4810
|
+
}
|
|
4811
|
+
let title = tt.title_pattern;
|
|
4812
|
+
let desc = tt.description;
|
|
4813
|
+
title = substituteVars(title, resolved);
|
|
4814
|
+
if (desc)
|
|
4815
|
+
desc = substituteVars(desc, resolved);
|
|
4816
|
+
const task = createTask({
|
|
4817
|
+
title,
|
|
4818
|
+
description: desc ?? undefined,
|
|
4819
|
+
priority: tt.priority,
|
|
4820
|
+
tags: tt.tags,
|
|
4821
|
+
task_type: tt.task_type ?? undefined,
|
|
4822
|
+
project_id: projectId,
|
|
4823
|
+
task_list_id: taskListId,
|
|
4824
|
+
metadata: tt.metadata
|
|
4825
|
+
}, d);
|
|
4826
|
+
createdTasks.push(task);
|
|
4827
|
+
positionToId.set(tt.position, task.id);
|
|
4828
|
+
}
|
|
4829
|
+
for (const tt of template.tasks) {
|
|
4830
|
+
if (skippedPositions.has(tt.position))
|
|
4831
|
+
continue;
|
|
4832
|
+
if (tt.include_template_id)
|
|
4833
|
+
continue;
|
|
4834
|
+
const deps = tt.depends_on_positions;
|
|
4835
|
+
for (const depPos of deps) {
|
|
4836
|
+
if (skippedPositions.has(depPos))
|
|
4837
|
+
continue;
|
|
4838
|
+
const taskId = positionToId.get(tt.position);
|
|
4839
|
+
const depId = positionToId.get(depPos);
|
|
4840
|
+
if (taskId && depId) {
|
|
4841
|
+
addDependency(taskId, depId, d);
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
return createdTasks;
|
|
4846
|
+
}
|
|
4847
|
+
function previewTemplate(templateId, variables, db) {
|
|
4848
|
+
const d = db || getDatabase();
|
|
4849
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
4850
|
+
if (!template)
|
|
4851
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4852
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
4853
|
+
const tasks = [];
|
|
4854
|
+
if (template.tasks.length === 0) {
|
|
4855
|
+
tasks.push({
|
|
4856
|
+
position: 0,
|
|
4857
|
+
title: substituteVars(template.title_pattern, resolved),
|
|
4858
|
+
description: template.description ? substituteVars(template.description, resolved) : null,
|
|
4859
|
+
priority: template.priority,
|
|
4860
|
+
tags: template.tags,
|
|
4861
|
+
task_type: null,
|
|
4862
|
+
depends_on_positions: []
|
|
4863
|
+
});
|
|
4864
|
+
} else {
|
|
4865
|
+
for (const tt of template.tasks) {
|
|
4866
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved))
|
|
4867
|
+
continue;
|
|
4868
|
+
tasks.push({
|
|
4869
|
+
position: tt.position,
|
|
4870
|
+
title: substituteVars(tt.title_pattern, resolved),
|
|
4871
|
+
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
4872
|
+
priority: tt.priority,
|
|
4873
|
+
tags: tt.tags,
|
|
4874
|
+
task_type: tt.task_type,
|
|
4875
|
+
depends_on_positions: tt.depends_on_positions
|
|
4876
|
+
});
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
return {
|
|
4880
|
+
template_id: template.id,
|
|
4881
|
+
template_name: template.name,
|
|
4882
|
+
description: template.description,
|
|
4883
|
+
variables: template.variables,
|
|
4884
|
+
resolved_variables: resolved,
|
|
4885
|
+
tasks
|
|
4886
|
+
};
|
|
4887
|
+
}
|
|
4452
4888
|
var init_templates = __esm(() => {
|
|
4453
4889
|
init_database();
|
|
4454
4890
|
init_tasks();
|
|
@@ -6611,6 +7047,101 @@ var init_task_runs = __esm(() => {
|
|
|
6611
7047
|
init_tasks();
|
|
6612
7048
|
});
|
|
6613
7049
|
|
|
7050
|
+
// src/lib/local-fields.ts
|
|
7051
|
+
function normalizeList(values) {
|
|
7052
|
+
return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
7053
|
+
}
|
|
7054
|
+
function metadataFields(task) {
|
|
7055
|
+
const value = task.metadata[LOCAL_FIELDS_KEY];
|
|
7056
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7057
|
+
}
|
|
7058
|
+
function sameCustomValue(actual, expected) {
|
|
7059
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
7060
|
+
}
|
|
7061
|
+
function hasOwnField(fields, key) {
|
|
7062
|
+
return Object.prototype.hasOwnProperty.call(fields, key);
|
|
7063
|
+
}
|
|
7064
|
+
function getTaskLocalFields(taskId, db) {
|
|
7065
|
+
const d = db || getDatabase();
|
|
7066
|
+
const task = getTask(taskId, d);
|
|
7067
|
+
if (!task)
|
|
7068
|
+
throw new TaskNotFoundError(taskId);
|
|
7069
|
+
const fields = metadataFields(task);
|
|
7070
|
+
return {
|
|
7071
|
+
labels: normalizeList(fields.labels),
|
|
7072
|
+
priority: task.priority,
|
|
7073
|
+
severity: typeof fields.severity === "string" ? fields.severity : null,
|
|
7074
|
+
owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
|
|
7075
|
+
area: typeof fields.area === "string" ? fields.area : null,
|
|
7076
|
+
custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
|
|
7077
|
+
};
|
|
7078
|
+
}
|
|
7079
|
+
function setTaskLocalFields(taskId, input, db) {
|
|
7080
|
+
const d = db || getDatabase();
|
|
7081
|
+
const task = getTask(taskId, d);
|
|
7082
|
+
if (!task)
|
|
7083
|
+
throw new TaskNotFoundError(taskId);
|
|
7084
|
+
const currentFields = getTaskLocalFields(taskId, d);
|
|
7085
|
+
const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
|
|
7086
|
+
const custom2 = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
|
|
7087
|
+
const nextFields = {
|
|
7088
|
+
labels,
|
|
7089
|
+
priority: input.priority || task.priority,
|
|
7090
|
+
severity: input.severity !== undefined ? input.severity : currentFields.severity,
|
|
7091
|
+
owner: input.owner !== undefined ? input.owner : currentFields.owner,
|
|
7092
|
+
area: input.area !== undefined ? input.area : currentFields.area,
|
|
7093
|
+
custom: custom2
|
|
7094
|
+
};
|
|
7095
|
+
const nextMetadata = {
|
|
7096
|
+
...task.metadata,
|
|
7097
|
+
[LOCAL_FIELDS_KEY]: nextFields
|
|
7098
|
+
};
|
|
7099
|
+
const previousLabels = new Set(currentFields.labels);
|
|
7100
|
+
const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
|
|
7101
|
+
const updates = {
|
|
7102
|
+
version: task.version,
|
|
7103
|
+
priority: input.priority,
|
|
7104
|
+
tags: nextTags,
|
|
7105
|
+
metadata: nextMetadata
|
|
7106
|
+
};
|
|
7107
|
+
if (input.owner !== undefined)
|
|
7108
|
+
updates.assigned_to = nextFields.owner;
|
|
7109
|
+
return updateTask(taskId, updates, d);
|
|
7110
|
+
}
|
|
7111
|
+
function queryTasksByLocalFields(query, db) {
|
|
7112
|
+
const d = db || getDatabase();
|
|
7113
|
+
const tasks = listTasks({
|
|
7114
|
+
priority: query.priority,
|
|
7115
|
+
tags: query.labels,
|
|
7116
|
+
limit: 1e4
|
|
7117
|
+
}, d);
|
|
7118
|
+
const matches = tasks.filter((task) => {
|
|
7119
|
+
const fields = getTaskLocalFields(task.id, d);
|
|
7120
|
+
if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
|
|
7121
|
+
return false;
|
|
7122
|
+
if (query.severity && fields.severity !== query.severity)
|
|
7123
|
+
return false;
|
|
7124
|
+
if (query.owner && fields.owner !== query.owner)
|
|
7125
|
+
return false;
|
|
7126
|
+
if (query.area && fields.area !== query.area)
|
|
7127
|
+
return false;
|
|
7128
|
+
if (query.custom) {
|
|
7129
|
+
for (const [key, expected] of Object.entries(query.custom)) {
|
|
7130
|
+
if (!sameCustomValue(fields.custom[key], expected))
|
|
7131
|
+
return false;
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
return true;
|
|
7135
|
+
});
|
|
7136
|
+
return matches.slice(0, query.limit || 100);
|
|
7137
|
+
}
|
|
7138
|
+
var LOCAL_FIELDS_KEY = "local_fields";
|
|
7139
|
+
var init_local_fields = __esm(() => {
|
|
7140
|
+
init_database();
|
|
7141
|
+
init_tasks();
|
|
7142
|
+
init_types();
|
|
7143
|
+
});
|
|
7144
|
+
|
|
6614
7145
|
// src/db/task-relationships.ts
|
|
6615
7146
|
var exports_task_relationships = {};
|
|
6616
7147
|
__export(exports_task_relationships, {
|
|
@@ -6852,6 +7383,347 @@ var init_search = __esm(() => {
|
|
|
6852
7383
|
init_database();
|
|
6853
7384
|
});
|
|
6854
7385
|
|
|
7386
|
+
// src/lib/saved-search-views.ts
|
|
7387
|
+
var exports_saved_search_views = {};
|
|
7388
|
+
__export(exports_saved_search_views, {
|
|
7389
|
+
saveSearchView: () => saveSearchView,
|
|
7390
|
+
runSearchView: () => runSearchView,
|
|
7391
|
+
runSavedSearch: () => runSavedSearch,
|
|
7392
|
+
normalizeScope: () => normalizeScope,
|
|
7393
|
+
listSearchViews: () => listSearchViews,
|
|
7394
|
+
getSearchView: () => getSearchView,
|
|
7395
|
+
deleteSearchView: () => deleteSearchView
|
|
7396
|
+
});
|
|
7397
|
+
function parseFilters(value) {
|
|
7398
|
+
if (!value)
|
|
7399
|
+
return {};
|
|
7400
|
+
try {
|
|
7401
|
+
const parsed = JSON.parse(value);
|
|
7402
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
7403
|
+
} catch {
|
|
7404
|
+
return {};
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
function rowToSavedSearchView(row) {
|
|
7408
|
+
return {
|
|
7409
|
+
...row,
|
|
7410
|
+
scope: normalizeScope(row.scope),
|
|
7411
|
+
filters: parseFilters(row.filters)
|
|
7412
|
+
};
|
|
7413
|
+
}
|
|
7414
|
+
function normalizeScope(scope) {
|
|
7415
|
+
if (scope === "all" || scope === "tasks" || scope === "projects" || scope === "plans" || scope === "runs" || scope === "comments") {
|
|
7416
|
+
return scope;
|
|
7417
|
+
}
|
|
7418
|
+
return "tasks";
|
|
7419
|
+
}
|
|
7420
|
+
function normalizeName(name) {
|
|
7421
|
+
const normalized = name.trim();
|
|
7422
|
+
if (!normalized)
|
|
7423
|
+
throw new Error("Saved view name is required");
|
|
7424
|
+
return normalized;
|
|
7425
|
+
}
|
|
7426
|
+
function normalizeLimit(limit) {
|
|
7427
|
+
if (!Number.isFinite(limit) || !limit || limit <= 0)
|
|
7428
|
+
return 100;
|
|
7429
|
+
return Math.min(Math.trunc(limit), 1000);
|
|
7430
|
+
}
|
|
7431
|
+
function valuesList(value) {
|
|
7432
|
+
if (value === undefined)
|
|
7433
|
+
return [];
|
|
7434
|
+
return (Array.isArray(value) ? value : [value]).map((item) => item.trim()).filter(Boolean);
|
|
7435
|
+
}
|
|
7436
|
+
function addStatusFilter(sql, params, column, value) {
|
|
7437
|
+
const values = valuesList(value);
|
|
7438
|
+
if (values.length === 0)
|
|
7439
|
+
return sql;
|
|
7440
|
+
params.push(...values);
|
|
7441
|
+
return `${sql} AND ${column} IN (${values.map(() => "?").join(",")})`;
|
|
7442
|
+
}
|
|
7443
|
+
function addDateFilter(sql, params, column, value) {
|
|
7444
|
+
if (!value)
|
|
7445
|
+
return sql;
|
|
7446
|
+
params.push(value);
|
|
7447
|
+
return `${sql} AND ${column} > ?`;
|
|
7448
|
+
}
|
|
7449
|
+
function likePattern(query) {
|
|
7450
|
+
const trimmed = query?.trim();
|
|
7451
|
+
if (!trimmed || trimmed === "*")
|
|
7452
|
+
return null;
|
|
7453
|
+
return `%${trimmed}%`;
|
|
7454
|
+
}
|
|
7455
|
+
function parseJsonObject(value) {
|
|
7456
|
+
if (!value)
|
|
7457
|
+
return {};
|
|
7458
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
7459
|
+
return value;
|
|
7460
|
+
if (typeof value !== "string")
|
|
7461
|
+
return {};
|
|
7462
|
+
try {
|
|
7463
|
+
const parsed = JSON.parse(value);
|
|
7464
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
7465
|
+
} catch {
|
|
7466
|
+
return {};
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
function rowToTaskRun(row) {
|
|
7470
|
+
return { ...row, metadata: parseJsonObject(row.metadata) };
|
|
7471
|
+
}
|
|
7472
|
+
function taskMatchesSavedFilters(task, filters, db) {
|
|
7473
|
+
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
7474
|
+
return false;
|
|
7475
|
+
if (filters.tags && !filters.tags.every((tag) => task.tags.includes(tag)))
|
|
7476
|
+
return false;
|
|
7477
|
+
if (filters.depends_on) {
|
|
7478
|
+
const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(task.id, filters.depends_on);
|
|
7479
|
+
if (!row)
|
|
7480
|
+
return false;
|
|
7481
|
+
}
|
|
7482
|
+
if (filters.blocks) {
|
|
7483
|
+
const row = db.query("SELECT 1 FROM task_dependencies WHERE task_id = ? AND depends_on = ?").get(filters.blocks, task.id);
|
|
7484
|
+
if (!row)
|
|
7485
|
+
return false;
|
|
7486
|
+
}
|
|
7487
|
+
return true;
|
|
7488
|
+
}
|
|
7489
|
+
function searchTaskEntities(filters, db) {
|
|
7490
|
+
let tasks;
|
|
7491
|
+
if (filters.local_fields) {
|
|
7492
|
+
const localMatches = queryTasksByLocalFields({ ...filters.local_fields, limit: 1e4 }, db);
|
|
7493
|
+
const allowedIds = new Set(localMatches.map((task) => task.id));
|
|
7494
|
+
tasks = searchTasks({
|
|
7495
|
+
query: filters.query,
|
|
7496
|
+
project_id: filters.project_id,
|
|
7497
|
+
task_list_id: filters.task_list_id,
|
|
7498
|
+
status: filters.status,
|
|
7499
|
+
priority: filters.priority,
|
|
7500
|
+
assigned_to: filters.assigned_to,
|
|
7501
|
+
agent_id: filters.agent_id,
|
|
7502
|
+
created_after: filters.created_after,
|
|
7503
|
+
updated_after: filters.updated_after,
|
|
7504
|
+
has_dependencies: filters.has_dependencies,
|
|
7505
|
+
is_blocked: filters.is_blocked
|
|
7506
|
+
}, undefined, undefined, db).filter((task) => allowedIds.has(task.id));
|
|
7507
|
+
} else {
|
|
7508
|
+
tasks = searchTasks({
|
|
7509
|
+
query: filters.query,
|
|
7510
|
+
project_id: filters.project_id,
|
|
7511
|
+
task_list_id: filters.task_list_id,
|
|
7512
|
+
status: filters.status,
|
|
7513
|
+
priority: filters.priority,
|
|
7514
|
+
assigned_to: filters.assigned_to,
|
|
7515
|
+
agent_id: filters.agent_id,
|
|
7516
|
+
created_after: filters.created_after,
|
|
7517
|
+
updated_after: filters.updated_after,
|
|
7518
|
+
has_dependencies: filters.has_dependencies,
|
|
7519
|
+
is_blocked: filters.is_blocked
|
|
7520
|
+
}, undefined, undefined, db);
|
|
7521
|
+
}
|
|
7522
|
+
return tasks.filter((task) => taskMatchesSavedFilters(task, filters, db)).slice(0, normalizeLimit(filters.limit));
|
|
7523
|
+
}
|
|
7524
|
+
function searchProjects(filters, db) {
|
|
7525
|
+
const params = [];
|
|
7526
|
+
let sql = "SELECT * FROM projects WHERE 1=1";
|
|
7527
|
+
if (filters.project_id) {
|
|
7528
|
+
sql += " AND id = ?";
|
|
7529
|
+
params.push(filters.project_id);
|
|
7530
|
+
}
|
|
7531
|
+
const pattern = likePattern(filters.query);
|
|
7532
|
+
if (pattern) {
|
|
7533
|
+
sql += " AND (name LIKE ? OR description LIKE ? OR path LIKE ?)";
|
|
7534
|
+
params.push(pattern, pattern, pattern);
|
|
7535
|
+
}
|
|
7536
|
+
sql = addDateFilter(sql, params, "created_at", filters.created_after);
|
|
7537
|
+
sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
|
|
7538
|
+
sql += " ORDER BY name LIMIT ?";
|
|
7539
|
+
params.push(normalizeLimit(filters.limit));
|
|
7540
|
+
return db.query(sql).all(...params);
|
|
7541
|
+
}
|
|
7542
|
+
function searchPlans(filters, db) {
|
|
7543
|
+
const params = [];
|
|
7544
|
+
let sql = "SELECT * FROM plans WHERE 1=1";
|
|
7545
|
+
if (filters.project_id) {
|
|
7546
|
+
sql += " AND project_id = ?";
|
|
7547
|
+
params.push(filters.project_id);
|
|
7548
|
+
}
|
|
7549
|
+
if (filters.task_list_id) {
|
|
7550
|
+
sql += " AND task_list_id = ?";
|
|
7551
|
+
params.push(filters.task_list_id);
|
|
7552
|
+
}
|
|
7553
|
+
if (filters.agent_id) {
|
|
7554
|
+
sql += " AND agent_id = ?";
|
|
7555
|
+
params.push(filters.agent_id);
|
|
7556
|
+
}
|
|
7557
|
+
sql = addStatusFilter(sql, params, "status", filters.status);
|
|
7558
|
+
const pattern = likePattern(filters.query);
|
|
7559
|
+
if (pattern) {
|
|
7560
|
+
sql += " AND (name LIKE ? OR description LIKE ?)";
|
|
7561
|
+
params.push(pattern, pattern);
|
|
7562
|
+
}
|
|
7563
|
+
sql = addDateFilter(sql, params, "created_at", filters.created_after);
|
|
7564
|
+
sql = addDateFilter(sql, params, "updated_at", filters.updated_after);
|
|
7565
|
+
sql += " ORDER BY updated_at DESC, created_at DESC LIMIT ?";
|
|
7566
|
+
params.push(normalizeLimit(filters.limit));
|
|
7567
|
+
return db.query(sql).all(...params);
|
|
7568
|
+
}
|
|
7569
|
+
function searchRuns(filters, db) {
|
|
7570
|
+
const params = [];
|
|
7571
|
+
let sql = `SELECT r.* FROM task_runs r
|
|
7572
|
+
JOIN tasks t ON t.id = r.task_id
|
|
7573
|
+
WHERE 1=1`;
|
|
7574
|
+
if (filters.project_id) {
|
|
7575
|
+
sql += " AND t.project_id = ?";
|
|
7576
|
+
params.push(filters.project_id);
|
|
7577
|
+
}
|
|
7578
|
+
if (filters.task_list_id) {
|
|
7579
|
+
sql += " AND t.task_list_id = ?";
|
|
7580
|
+
params.push(filters.task_list_id);
|
|
7581
|
+
}
|
|
7582
|
+
if (filters.plan_id) {
|
|
7583
|
+
sql += " AND t.plan_id = ?";
|
|
7584
|
+
params.push(filters.plan_id);
|
|
7585
|
+
}
|
|
7586
|
+
if (filters.task_id) {
|
|
7587
|
+
sql += " AND r.task_id = ?";
|
|
7588
|
+
params.push(filters.task_id);
|
|
7589
|
+
}
|
|
7590
|
+
if (filters.agent_id) {
|
|
7591
|
+
sql += " AND r.agent_id = ?";
|
|
7592
|
+
params.push(filters.agent_id);
|
|
7593
|
+
}
|
|
7594
|
+
sql = addStatusFilter(sql, params, "r.status", filters.status);
|
|
7595
|
+
const pattern = likePattern(filters.query);
|
|
7596
|
+
if (pattern) {
|
|
7597
|
+
sql += " AND (r.title LIKE ? OR r.summary LIKE ? OR t.title LIKE ?)";
|
|
7598
|
+
params.push(pattern, pattern, pattern);
|
|
7599
|
+
}
|
|
7600
|
+
sql = addDateFilter(sql, params, "r.created_at", filters.created_after);
|
|
7601
|
+
sql = addDateFilter(sql, params, "r.updated_at", filters.updated_after);
|
|
7602
|
+
sql += " ORDER BY r.started_at DESC, r.created_at DESC LIMIT ?";
|
|
7603
|
+
params.push(normalizeLimit(filters.limit));
|
|
7604
|
+
return db.query(sql).all(...params).map(rowToTaskRun);
|
|
7605
|
+
}
|
|
7606
|
+
function searchComments(filters, db) {
|
|
7607
|
+
const params = [];
|
|
7608
|
+
let sql = `SELECT c.* FROM task_comments c
|
|
7609
|
+
JOIN tasks t ON t.id = c.task_id
|
|
7610
|
+
WHERE 1=1`;
|
|
7611
|
+
if (filters.project_id) {
|
|
7612
|
+
sql += " AND t.project_id = ?";
|
|
7613
|
+
params.push(filters.project_id);
|
|
7614
|
+
}
|
|
7615
|
+
if (filters.task_list_id) {
|
|
7616
|
+
sql += " AND t.task_list_id = ?";
|
|
7617
|
+
params.push(filters.task_list_id);
|
|
7618
|
+
}
|
|
7619
|
+
if (filters.plan_id) {
|
|
7620
|
+
sql += " AND t.plan_id = ?";
|
|
7621
|
+
params.push(filters.plan_id);
|
|
7622
|
+
}
|
|
7623
|
+
if (filters.task_id) {
|
|
7624
|
+
sql += " AND c.task_id = ?";
|
|
7625
|
+
params.push(filters.task_id);
|
|
7626
|
+
}
|
|
7627
|
+
if (filters.agent_id) {
|
|
7628
|
+
sql += " AND c.agent_id = ?";
|
|
7629
|
+
params.push(filters.agent_id);
|
|
7630
|
+
}
|
|
7631
|
+
const pattern = likePattern(filters.query);
|
|
7632
|
+
if (pattern) {
|
|
7633
|
+
sql += " AND (c.content LIKE ? OR t.title LIKE ?)";
|
|
7634
|
+
params.push(pattern, pattern);
|
|
7635
|
+
}
|
|
7636
|
+
sql = addDateFilter(sql, params, "c.created_at", filters.created_after);
|
|
7637
|
+
sql += " ORDER BY c.created_at DESC, c.id LIMIT ?";
|
|
7638
|
+
params.push(normalizeLimit(filters.limit));
|
|
7639
|
+
return db.query(sql).all(...params);
|
|
7640
|
+
}
|
|
7641
|
+
function toResults(entityType, rows) {
|
|
7642
|
+
return rows.map((entity) => ({ entity_type: entityType, entity }));
|
|
7643
|
+
}
|
|
7644
|
+
function runSavedSearch(filters = {}, scope = "tasks", db) {
|
|
7645
|
+
const d = db || getDatabase();
|
|
7646
|
+
const normalizedScope = normalizeScope(scope);
|
|
7647
|
+
const scopes = normalizedScope === "all" ? ["tasks", "projects", "plans", "runs", "comments"] : [normalizedScope];
|
|
7648
|
+
const results = [];
|
|
7649
|
+
for (const item of scopes) {
|
|
7650
|
+
if (item === "tasks")
|
|
7651
|
+
results.push(...toResults("tasks", searchTaskEntities(filters, d)));
|
|
7652
|
+
if (item === "projects")
|
|
7653
|
+
results.push(...toResults("projects", searchProjects(filters, d)));
|
|
7654
|
+
if (item === "plans")
|
|
7655
|
+
results.push(...toResults("plans", searchPlans(filters, d)));
|
|
7656
|
+
if (item === "runs")
|
|
7657
|
+
results.push(...toResults("runs", searchRuns(filters, d)));
|
|
7658
|
+
if (item === "comments")
|
|
7659
|
+
results.push(...toResults("comments", searchComments(filters, d)));
|
|
7660
|
+
}
|
|
7661
|
+
return {
|
|
7662
|
+
scope: normalizedScope,
|
|
7663
|
+
filters,
|
|
7664
|
+
count: results.length,
|
|
7665
|
+
results: results.slice(0, normalizeLimit(filters.limit))
|
|
7666
|
+
};
|
|
7667
|
+
}
|
|
7668
|
+
function saveSearchView(input, db) {
|
|
7669
|
+
const d = db || getDatabase();
|
|
7670
|
+
const name = normalizeName(input.name);
|
|
7671
|
+
const timestamp = now();
|
|
7672
|
+
const existing = getSearchView(name, d);
|
|
7673
|
+
if (existing) {
|
|
7674
|
+
d.run(`UPDATE saved_search_views
|
|
7675
|
+
SET description = ?, scope = ?, filters = ?, updated_at = ?
|
|
7676
|
+
WHERE id = ?`, [
|
|
7677
|
+
input.description ?? existing.description,
|
|
7678
|
+
normalizeScope(input.scope ?? existing.scope),
|
|
7679
|
+
JSON.stringify(input.filters ?? existing.filters),
|
|
7680
|
+
timestamp,
|
|
7681
|
+
existing.id
|
|
7682
|
+
]);
|
|
7683
|
+
return getSearchView(existing.id, d);
|
|
7684
|
+
}
|
|
7685
|
+
const id = uuid();
|
|
7686
|
+
d.run(`INSERT INTO saved_search_views (id, name, description, scope, filters, created_at, updated_at)
|
|
7687
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
7688
|
+
id,
|
|
7689
|
+
name,
|
|
7690
|
+
input.description ?? null,
|
|
7691
|
+
normalizeScope(input.scope),
|
|
7692
|
+
JSON.stringify(input.filters ?? {}),
|
|
7693
|
+
timestamp,
|
|
7694
|
+
timestamp
|
|
7695
|
+
]);
|
|
7696
|
+
return getSearchView(id, d);
|
|
7697
|
+
}
|
|
7698
|
+
function getSearchView(idOrName, db) {
|
|
7699
|
+
const d = db || getDatabase();
|
|
7700
|
+
const row = d.query("SELECT * FROM saved_search_views WHERE id = ? OR name = ?").get(idOrName, idOrName);
|
|
7701
|
+
return row ? rowToSavedSearchView(row) : null;
|
|
7702
|
+
}
|
|
7703
|
+
function listSearchViews(scope, db) {
|
|
7704
|
+
const d = db || getDatabase();
|
|
7705
|
+
const normalizedScope = scope ? normalizeScope(scope) : null;
|
|
7706
|
+
const rows = normalizedScope ? d.query("SELECT * FROM saved_search_views WHERE scope = ? ORDER BY name").all(normalizedScope) : d.query("SELECT * FROM saved_search_views ORDER BY name").all();
|
|
7707
|
+
return rows.map(rowToSavedSearchView);
|
|
7708
|
+
}
|
|
7709
|
+
function deleteSearchView(idOrName, db) {
|
|
7710
|
+
const d = db || getDatabase();
|
|
7711
|
+
const result = d.run("DELETE FROM saved_search_views WHERE id = ? OR name = ?", [idOrName, idOrName]);
|
|
7712
|
+
return result.changes > 0;
|
|
7713
|
+
}
|
|
7714
|
+
function runSearchView(idOrName, db) {
|
|
7715
|
+
const d = db || getDatabase();
|
|
7716
|
+
const view = getSearchView(idOrName, d);
|
|
7717
|
+
if (!view)
|
|
7718
|
+
throw new Error(`Saved search view not found: ${idOrName}`);
|
|
7719
|
+
return { ...runSavedSearch(view.filters, view.scope, d), view };
|
|
7720
|
+
}
|
|
7721
|
+
var init_saved_search_views = __esm(() => {
|
|
7722
|
+
init_database();
|
|
7723
|
+
init_local_fields();
|
|
7724
|
+
init_search();
|
|
7725
|
+
});
|
|
7726
|
+
|
|
6855
7727
|
// src/db/handoffs.ts
|
|
6856
7728
|
var exports_handoffs = {};
|
|
6857
7729
|
__export(exports_handoffs, {
|
|
@@ -8869,6 +9741,279 @@ var init_extract = __esm(() => {
|
|
|
8869
9741
|
]);
|
|
8870
9742
|
});
|
|
8871
9743
|
|
|
9744
|
+
// src/db/builtin-templates.ts
|
|
9745
|
+
var exports_builtin_templates = {};
|
|
9746
|
+
__export(exports_builtin_templates, {
|
|
9747
|
+
writeBuiltinTemplateFiles: () => writeBuiltinTemplateFiles,
|
|
9748
|
+
listBuiltinTemplates: () => listBuiltinTemplates,
|
|
9749
|
+
initBuiltinTemplates: () => initBuiltinTemplates,
|
|
9750
|
+
getBuiltinTemplate: () => getBuiltinTemplate,
|
|
9751
|
+
exportBuiltinTemplateFiles: () => exportBuiltinTemplateFiles,
|
|
9752
|
+
exportBuiltinTemplate: () => exportBuiltinTemplate,
|
|
9753
|
+
BUILTIN_TEMPLATE_LIBRARY_VERSION: () => BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9754
|
+
BUILTIN_TEMPLATE_LIBRARY_SOURCE: () => BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
9755
|
+
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
9756
|
+
});
|
|
9757
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
9758
|
+
import { join as join6 } from "path";
|
|
9759
|
+
function templateMetadata(template) {
|
|
9760
|
+
return {
|
|
9761
|
+
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
9762
|
+
library_version: template.version,
|
|
9763
|
+
category: template.category,
|
|
9764
|
+
template_file: `${template.name}.json`,
|
|
9765
|
+
local_only: true,
|
|
9766
|
+
marketplace_free: true
|
|
9767
|
+
};
|
|
9768
|
+
}
|
|
9769
|
+
function listBuiltinTemplates() {
|
|
9770
|
+
return BUILTIN_TEMPLATES.map((template) => ({
|
|
9771
|
+
...template,
|
|
9772
|
+
variables: template.variables.map((variable) => ({ ...variable })),
|
|
9773
|
+
tasks: template.tasks.map((task) => ({ ...task, tags: task.tags ? [...task.tags] : undefined }))
|
|
9774
|
+
}));
|
|
9775
|
+
}
|
|
9776
|
+
function getBuiltinTemplate(name) {
|
|
9777
|
+
return listBuiltinTemplates().find((template) => template.name === name) ?? null;
|
|
9778
|
+
}
|
|
9779
|
+
function exportBuiltinTemplate(name) {
|
|
9780
|
+
const template = getBuiltinTemplate(name);
|
|
9781
|
+
if (!template)
|
|
9782
|
+
throw new Error(`Built-in template not found: ${name}`);
|
|
9783
|
+
return {
|
|
9784
|
+
name: template.name,
|
|
9785
|
+
title_pattern: `${template.name}: {${template.variables[0]?.name ?? "name"}}`,
|
|
9786
|
+
description: template.description,
|
|
9787
|
+
priority: "medium",
|
|
9788
|
+
tags: [template.category, "local-template"],
|
|
9789
|
+
variables: template.variables,
|
|
9790
|
+
project_id: null,
|
|
9791
|
+
plan_id: null,
|
|
9792
|
+
metadata: templateMetadata(template),
|
|
9793
|
+
tasks: template.tasks.map((task) => ({
|
|
9794
|
+
position: task.position,
|
|
9795
|
+
title_pattern: task.title_pattern,
|
|
9796
|
+
description: task.description ?? null,
|
|
9797
|
+
priority: task.priority ?? "medium",
|
|
9798
|
+
tags: task.tags ?? [template.category],
|
|
9799
|
+
task_type: task.task_type ?? null,
|
|
9800
|
+
condition: task.condition ?? null,
|
|
9801
|
+
include_template_id: task.include_template_id ?? null,
|
|
9802
|
+
depends_on_positions: task.depends_on_positions ?? task.depends_on ?? [],
|
|
9803
|
+
metadata: task.metadata ?? { category: template.category }
|
|
9804
|
+
}))
|
|
9805
|
+
};
|
|
9806
|
+
}
|
|
9807
|
+
function exportBuiltinTemplateFiles() {
|
|
9808
|
+
return BUILTIN_TEMPLATES.map((template) => ({
|
|
9809
|
+
filename: `${template.name}.json`,
|
|
9810
|
+
template: exportBuiltinTemplate(template.name)
|
|
9811
|
+
}));
|
|
9812
|
+
}
|
|
9813
|
+
function writeBuiltinTemplateFiles(directory) {
|
|
9814
|
+
mkdirSync6(directory, { recursive: true });
|
|
9815
|
+
const files = [];
|
|
9816
|
+
for (const entry of exportBuiltinTemplateFiles()) {
|
|
9817
|
+
const path = join6(directory, entry.filename);
|
|
9818
|
+
writeFileSync3(path, `${JSON.stringify(entry.template, null, 2)}
|
|
9819
|
+
`, "utf-8");
|
|
9820
|
+
files.push(path);
|
|
9821
|
+
}
|
|
9822
|
+
return { directory, written: files.length, files };
|
|
9823
|
+
}
|
|
9824
|
+
function initBuiltinTemplates(db) {
|
|
9825
|
+
const d = db || getDatabase();
|
|
9826
|
+
const existing = listTemplates(d);
|
|
9827
|
+
const existingNames = new Set(existing.map((t) => t.name));
|
|
9828
|
+
let created = 0;
|
|
9829
|
+
let skipped = 0;
|
|
9830
|
+
const names = [];
|
|
9831
|
+
for (const bt of BUILTIN_TEMPLATES) {
|
|
9832
|
+
if (existingNames.has(bt.name)) {
|
|
9833
|
+
skipped++;
|
|
9834
|
+
continue;
|
|
9835
|
+
}
|
|
9836
|
+
const tasks = bt.tasks.map((t) => ({
|
|
9837
|
+
title_pattern: t.title_pattern,
|
|
9838
|
+
description: t.description,
|
|
9839
|
+
priority: t.priority,
|
|
9840
|
+
tags: t.tags,
|
|
9841
|
+
task_type: t.task_type,
|
|
9842
|
+
depends_on: t.depends_on_positions || t.depends_on,
|
|
9843
|
+
metadata: t.metadata
|
|
9844
|
+
}));
|
|
9845
|
+
createTemplate({
|
|
9846
|
+
name: bt.name,
|
|
9847
|
+
title_pattern: `${bt.name}: {${bt.variables[0]?.name || "name"}}`,
|
|
9848
|
+
description: bt.description,
|
|
9849
|
+
tags: [bt.category, "local-template"],
|
|
9850
|
+
variables: bt.variables,
|
|
9851
|
+
metadata: templateMetadata(bt),
|
|
9852
|
+
tasks
|
|
9853
|
+
}, d);
|
|
9854
|
+
created++;
|
|
9855
|
+
names.push(bt.name);
|
|
9856
|
+
}
|
|
9857
|
+
return { created, skipped, names };
|
|
9858
|
+
}
|
|
9859
|
+
var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21", BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library", BUILTIN_TEMPLATES;
|
|
9860
|
+
var init_builtin_templates = __esm(() => {
|
|
9861
|
+
init_database();
|
|
9862
|
+
init_templates();
|
|
9863
|
+
BUILTIN_TEMPLATES = [
|
|
9864
|
+
{
|
|
9865
|
+
name: "bug-fix",
|
|
9866
|
+
description: "Reproduce, diagnose, fix, test, and release a defect.",
|
|
9867
|
+
category: "bug-fix",
|
|
9868
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9869
|
+
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
9870
|
+
tasks: [
|
|
9871
|
+
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical", tags: ["bug", "repro"] },
|
|
9872
|
+
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", tags: ["bug", "diagnosis"], depends_on_positions: [0] },
|
|
9873
|
+
{ position: 2, title_pattern: "Write regression test for {bug}", priority: "high", tags: ["bug", "test"], depends_on_positions: [1] },
|
|
9874
|
+
{ position: 3, title_pattern: "Implement fix for {bug}", priority: "critical", tags: ["bug", "implementation"], depends_on_positions: [2] },
|
|
9875
|
+
{ position: 4, title_pattern: "Run full verification for {bug}", priority: "high", tags: ["bug", "verification"], depends_on_positions: [3] },
|
|
9876
|
+
{ position: 5, title_pattern: "Publish and smoke test fix for {bug}", priority: "high", tags: ["bug", "release"], depends_on_positions: [4] }
|
|
9877
|
+
]
|
|
9878
|
+
},
|
|
9879
|
+
{
|
|
9880
|
+
name: "feature-implementation",
|
|
9881
|
+
description: "Plan, build, test, document, and release a product feature.",
|
|
9882
|
+
category: "feature",
|
|
9883
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9884
|
+
variables: [
|
|
9885
|
+
{ name: "feature", required: true, description: "Feature name" },
|
|
9886
|
+
{ name: "scope", required: false, default: "medium", description: "Implementation size or risk" }
|
|
9887
|
+
],
|
|
9888
|
+
tasks: [
|
|
9889
|
+
{ position: 0, title_pattern: "Define acceptance criteria for {feature}", priority: "high", tags: ["feature", "spec"] },
|
|
9890
|
+
{ position: 1, title_pattern: "Design {scope} implementation plan for {feature}", priority: "high", tags: ["feature", "design"], depends_on_positions: [0] },
|
|
9891
|
+
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", tags: ["feature", "implementation"], depends_on_positions: [1] },
|
|
9892
|
+
{ position: 3, title_pattern: "Add tests for {feature}", priority: "high", tags: ["feature", "test"], depends_on_positions: [2] },
|
|
9893
|
+
{ position: 4, title_pattern: "Update docs for {feature}", priority: "medium", tags: ["feature", "docs"], depends_on_positions: [2] },
|
|
9894
|
+
{ position: 5, title_pattern: "Run release checks for {feature}", priority: "high", tags: ["feature", "verification"], depends_on_positions: [3, 4] }
|
|
9895
|
+
]
|
|
9896
|
+
},
|
|
9897
|
+
{
|
|
9898
|
+
name: "security-review",
|
|
9899
|
+
description: "Threat model, test, remediate, and report on security posture.",
|
|
9900
|
+
category: "security",
|
|
9901
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9902
|
+
variables: [{ name: "target", required: true, description: "System, package, or change under review" }],
|
|
9903
|
+
tasks: [
|
|
9904
|
+
{ position: 0, title_pattern: "Map trust boundaries for {target}", priority: "critical", tags: ["security", "threat-model"] },
|
|
9905
|
+
{ position: 1, title_pattern: "Scan {target} for vulnerabilities and secret exposure", priority: "critical", tags: ["security", "scan"], depends_on_positions: [0] },
|
|
9906
|
+
{ position: 2, title_pattern: "Review authz, data access, and dependency risks in {target}", priority: "critical", tags: ["security", "review"], depends_on_positions: [0] },
|
|
9907
|
+
{ position: 3, title_pattern: "Fix critical security findings in {target}", priority: "critical", tags: ["security", "fix"], depends_on_positions: [1, 2] },
|
|
9908
|
+
{ position: 4, title_pattern: "Retest {target} security fixes", priority: "high", tags: ["security", "verification"], depends_on_positions: [3] },
|
|
9909
|
+
{ position: 5, title_pattern: "Write local security review report for {target}", priority: "medium", tags: ["security", "report"], depends_on_positions: [4] }
|
|
9910
|
+
]
|
|
9911
|
+
},
|
|
9912
|
+
{
|
|
9913
|
+
name: "release",
|
|
9914
|
+
description: "Prepare, verify, publish, install, and smoke test a package release.",
|
|
9915
|
+
category: "release",
|
|
9916
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9917
|
+
variables: [
|
|
9918
|
+
{ name: "package", required: true, description: "Package name" },
|
|
9919
|
+
{ name: "version", required: false, default: "patch", description: "Release version or bump type" }
|
|
9920
|
+
],
|
|
9921
|
+
tasks: [
|
|
9922
|
+
{ position: 0, title_pattern: "Prepare {package} {version} release notes", priority: "medium", tags: ["release", "docs"] },
|
|
9923
|
+
{ position: 1, title_pattern: "Run full tests for {package}", priority: "critical", tags: ["release", "test"] },
|
|
9924
|
+
{ position: 2, title_pattern: "Run build and release verification for {package}", priority: "critical", tags: ["release", "verification"], depends_on_positions: [1] },
|
|
9925
|
+
{ position: 3, title_pattern: "Scan {package} release diff for secrets", priority: "critical", tags: ["release", "security"], depends_on_positions: [2] },
|
|
9926
|
+
{ position: 4, title_pattern: "Publish {package} {version}", priority: "high", tags: ["release", "publish"], depends_on_positions: [3] },
|
|
9927
|
+
{ position: 5, title_pattern: "Install and smoke test published {package}", priority: "high", tags: ["release", "smoke"], depends_on_positions: [4] }
|
|
9928
|
+
]
|
|
9929
|
+
},
|
|
9930
|
+
{
|
|
9931
|
+
name: "migration",
|
|
9932
|
+
description: "Plan, test, apply, and verify a schema or data migration.",
|
|
9933
|
+
category: "migration",
|
|
9934
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9935
|
+
variables: [{ name: "migration", required: true, description: "Migration name or objective" }],
|
|
9936
|
+
tasks: [
|
|
9937
|
+
{ position: 0, title_pattern: "Design migration plan for {migration}", priority: "critical", tags: ["migration", "plan"] },
|
|
9938
|
+
{ position: 1, title_pattern: "Write rollback plan for {migration}", priority: "critical", tags: ["migration", "rollback"], depends_on_positions: [0] },
|
|
9939
|
+
{ position: 2, title_pattern: "Implement migration {migration}", priority: "critical", tags: ["migration", "implementation"], depends_on_positions: [0] },
|
|
9940
|
+
{ position: 3, title_pattern: "Test migration {migration} on fixture data", priority: "high", tags: ["migration", "test"], depends_on_positions: [2] },
|
|
9941
|
+
{ position: 4, title_pattern: "Run migration {migration} verification and drift checks", priority: "high", tags: ["migration", "verification"], depends_on_positions: [1, 3] },
|
|
9942
|
+
{ position: 5, title_pattern: "Document migration {migration} evidence", priority: "medium", tags: ["migration", "docs"], depends_on_positions: [4] }
|
|
9943
|
+
]
|
|
9944
|
+
},
|
|
9945
|
+
{
|
|
9946
|
+
name: "incident",
|
|
9947
|
+
description: "Triage, mitigate, repair, verify, and retrospect an incident.",
|
|
9948
|
+
category: "incident",
|
|
9949
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9950
|
+
variables: [{ name: "incident", required: true, description: "Incident summary" }],
|
|
9951
|
+
tasks: [
|
|
9952
|
+
{ position: 0, title_pattern: "Triage incident: {incident}", priority: "critical", tags: ["incident", "triage"] },
|
|
9953
|
+
{ position: 1, title_pattern: "Mitigate customer impact for {incident}", priority: "critical", tags: ["incident", "mitigation"], depends_on_positions: [0] },
|
|
9954
|
+
{ position: 2, title_pattern: "Diagnose root cause for {incident}", priority: "critical", tags: ["incident", "diagnosis"], depends_on_positions: [0] },
|
|
9955
|
+
{ position: 3, title_pattern: "Implement durable repair for {incident}", priority: "critical", tags: ["incident", "repair"], depends_on_positions: [2] },
|
|
9956
|
+
{ position: 4, title_pattern: "Verify recovery from {incident}", priority: "high", tags: ["incident", "verification"], depends_on_positions: [1, 3] },
|
|
9957
|
+
{ position: 5, title_pattern: "Write retrospective for {incident}", priority: "medium", tags: ["incident", "retro"], depends_on_positions: [4] }
|
|
9958
|
+
]
|
|
9959
|
+
},
|
|
9960
|
+
{
|
|
9961
|
+
name: "docs-refresh",
|
|
9962
|
+
description: "Audit, update, validate, and publish documentation changes.",
|
|
9963
|
+
category: "docs",
|
|
9964
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9965
|
+
variables: [{ name: "area", required: true, description: "Documentation area or product surface" }],
|
|
9966
|
+
tasks: [
|
|
9967
|
+
{ position: 0, title_pattern: "Audit current docs for {area}", priority: "medium", tags: ["docs", "audit"] },
|
|
9968
|
+
{ position: 1, title_pattern: "Update examples and commands for {area}", priority: "medium", tags: ["docs", "examples"], depends_on_positions: [0] },
|
|
9969
|
+
{ position: 2, title_pattern: "Validate docs snippets for {area}", priority: "high", tags: ["docs", "verification"], depends_on_positions: [1] },
|
|
9970
|
+
{ position: 3, title_pattern: "Refresh screenshots or generated artifacts for {area}", priority: "medium", tags: ["docs", "assets"], depends_on_positions: [1] },
|
|
9971
|
+
{ position: 4, title_pattern: "Publish docs refresh for {area}", priority: "medium", tags: ["docs", "release"], depends_on_positions: [2, 3] }
|
|
9972
|
+
]
|
|
9973
|
+
},
|
|
9974
|
+
{
|
|
9975
|
+
name: "qa",
|
|
9976
|
+
description: "Build a focused QA plan, execute checks, file defects, and sign off.",
|
|
9977
|
+
category: "qa",
|
|
9978
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9979
|
+
variables: [{ name: "target", required: true, description: "Feature, release, or workflow under QA" }],
|
|
9980
|
+
tasks: [
|
|
9981
|
+
{ position: 0, title_pattern: "Create QA matrix for {target}", priority: "high", tags: ["qa", "plan"] },
|
|
9982
|
+
{ position: 1, title_pattern: "Run happy-path QA for {target}", priority: "high", tags: ["qa", "manual"], depends_on_positions: [0] },
|
|
9983
|
+
{ position: 2, title_pattern: "Run edge-case QA for {target}", priority: "high", tags: ["qa", "edge-cases"], depends_on_positions: [0] },
|
|
9984
|
+
{ position: 3, title_pattern: "File and dedupe QA defects for {target}", priority: "medium", tags: ["qa", "bugs"], depends_on_positions: [1, 2] },
|
|
9985
|
+
{ position: 4, title_pattern: "Verify QA fixes for {target}", priority: "high", tags: ["qa", "verification"], depends_on_positions: [3] },
|
|
9986
|
+
{ position: 5, title_pattern: "Record QA signoff for {target}", priority: "medium", tags: ["qa", "signoff"], depends_on_positions: [4] }
|
|
9987
|
+
]
|
|
9988
|
+
},
|
|
9989
|
+
{
|
|
9990
|
+
name: "open-source-project",
|
|
9991
|
+
description: "Full open-source project bootstrap \u2014 scaffold to publish",
|
|
9992
|
+
category: "open-source",
|
|
9993
|
+
version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
|
|
9994
|
+
variables: [
|
|
9995
|
+
{ name: "name", required: true, description: "Service name" },
|
|
9996
|
+
{ name: "org", required: false, default: "hasna", description: "GitHub org" }
|
|
9997
|
+
],
|
|
9998
|
+
tasks: [
|
|
9999
|
+
{ position: 0, title_pattern: "Scaffold {name} package structure", priority: "critical" },
|
|
10000
|
+
{ position: 1, title_pattern: "Create {name} SQLite database + migrations", priority: "critical", depends_on_positions: [0] },
|
|
10001
|
+
{ position: 2, title_pattern: "Implement {name} CRUD operations", priority: "high", depends_on_positions: [1] },
|
|
10002
|
+
{ position: 3, title_pattern: "Build {name} MCP server with standard tools", priority: "high", depends_on_positions: [2] },
|
|
10003
|
+
{ position: 4, title_pattern: "Build {name} CLI with Commander.js", priority: "high", depends_on_positions: [2] },
|
|
10004
|
+
{ position: 5, title_pattern: "Build {name} REST API", priority: "medium", depends_on_positions: [2] },
|
|
10005
|
+
{ position: 6, title_pattern: "Write unit tests for {name}", priority: "high", depends_on_positions: [2, 3, 4] },
|
|
10006
|
+
{ position: 7, title_pattern: "Add Apache 2.0 license and README", priority: "medium", depends_on_positions: [0] },
|
|
10007
|
+
{ position: 8, title_pattern: "Create GitHub repo {org}/{name}", priority: "medium", depends_on_positions: [7] },
|
|
10008
|
+
{ position: 9, title_pattern: "Add local backup and restore workflow for {name}", priority: "medium", depends_on_positions: [1] },
|
|
10009
|
+
{ position: 10, title_pattern: "Add agent tools (register_agent, heartbeat, set_focus, list_agents)", priority: "medium", depends_on_positions: [3] },
|
|
10010
|
+
{ position: 11, title_pattern: "Publish @hasna/{name} to npm", priority: "high", depends_on_positions: [6, 7, 8] },
|
|
10011
|
+
{ position: 12, title_pattern: "Install @hasna/{name} globally and verify", priority: "medium", depends_on_positions: [11] }
|
|
10012
|
+
]
|
|
10013
|
+
}
|
|
10014
|
+
];
|
|
10015
|
+
});
|
|
10016
|
+
|
|
8872
10017
|
// src/mcp/index.ts
|
|
8873
10018
|
init_agents();
|
|
8874
10019
|
init_database();
|
|
@@ -13169,6 +14314,10 @@ var MCP_TOOL_GROUPS = {
|
|
|
13169
14314
|
"request_task_review",
|
|
13170
14315
|
"reschedule_task",
|
|
13171
14316
|
"search_tasks",
|
|
14317
|
+
"save_search_view",
|
|
14318
|
+
"list_search_views",
|
|
14319
|
+
"run_search_view",
|
|
14320
|
+
"delete_search_view",
|
|
13172
14321
|
"standup",
|
|
13173
14322
|
"set_task_contract",
|
|
13174
14323
|
"task_context",
|
|
@@ -13358,10 +14507,12 @@ var MCP_TOOL_GROUPS = {
|
|
|
13358
14507
|
"export_template",
|
|
13359
14508
|
"import_template",
|
|
13360
14509
|
"init_templates",
|
|
14510
|
+
"list_template_library",
|
|
13361
14511
|
"list_templates",
|
|
13362
14512
|
"preview_template",
|
|
13363
14513
|
"template_history",
|
|
13364
|
-
"update_template"
|
|
14514
|
+
"update_template",
|
|
14515
|
+
"write_template_library"
|
|
13365
14516
|
],
|
|
13366
14517
|
webhooks: ["create_webhook", "delete_webhook", "list_webhooks"],
|
|
13367
14518
|
machines: [
|
|
@@ -14854,98 +16005,8 @@ function getLocalActivityTimeline(options = {}, db) {
|
|
|
14854
16005
|
};
|
|
14855
16006
|
}
|
|
14856
16007
|
|
|
14857
|
-
// src/
|
|
14858
|
-
|
|
14859
|
-
init_tasks();
|
|
14860
|
-
init_types();
|
|
14861
|
-
var LOCAL_FIELDS_KEY = "local_fields";
|
|
14862
|
-
function normalizeList(values) {
|
|
14863
|
-
return [...new Set((values || []).map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
14864
|
-
}
|
|
14865
|
-
function metadataFields(task) {
|
|
14866
|
-
const value = task.metadata[LOCAL_FIELDS_KEY];
|
|
14867
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
14868
|
-
}
|
|
14869
|
-
function sameCustomValue(actual, expected) {
|
|
14870
|
-
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
14871
|
-
}
|
|
14872
|
-
function hasOwnField(fields, key) {
|
|
14873
|
-
return Object.prototype.hasOwnProperty.call(fields, key);
|
|
14874
|
-
}
|
|
14875
|
-
function getTaskLocalFields(taskId, db) {
|
|
14876
|
-
const d = db || getDatabase();
|
|
14877
|
-
const task = getTask(taskId, d);
|
|
14878
|
-
if (!task)
|
|
14879
|
-
throw new TaskNotFoundError(taskId);
|
|
14880
|
-
const fields = metadataFields(task);
|
|
14881
|
-
return {
|
|
14882
|
-
labels: normalizeList(fields.labels),
|
|
14883
|
-
priority: task.priority,
|
|
14884
|
-
severity: typeof fields.severity === "string" ? fields.severity : null,
|
|
14885
|
-
owner: hasOwnField(fields, "owner") ? typeof fields.owner === "string" ? fields.owner : null : task.assigned_to,
|
|
14886
|
-
area: typeof fields.area === "string" ? fields.area : null,
|
|
14887
|
-
custom: fields.custom && typeof fields.custom === "object" && !Array.isArray(fields.custom) ? fields.custom : {}
|
|
14888
|
-
};
|
|
14889
|
-
}
|
|
14890
|
-
function setTaskLocalFields(taskId, input, db) {
|
|
14891
|
-
const d = db || getDatabase();
|
|
14892
|
-
const task = getTask(taskId, d);
|
|
14893
|
-
if (!task)
|
|
14894
|
-
throw new TaskNotFoundError(taskId);
|
|
14895
|
-
const currentFields = getTaskLocalFields(taskId, d);
|
|
14896
|
-
const labels = input.labels !== undefined ? normalizeList(input.labels) : currentFields.labels;
|
|
14897
|
-
const custom2 = input.custom !== undefined ? redactValue(input.merge_custom === false ? input.custom : { ...currentFields.custom, ...input.custom }) : currentFields.custom;
|
|
14898
|
-
const nextFields = {
|
|
14899
|
-
labels,
|
|
14900
|
-
priority: input.priority || task.priority,
|
|
14901
|
-
severity: input.severity !== undefined ? input.severity : currentFields.severity,
|
|
14902
|
-
owner: input.owner !== undefined ? input.owner : currentFields.owner,
|
|
14903
|
-
area: input.area !== undefined ? input.area : currentFields.area,
|
|
14904
|
-
custom: custom2
|
|
14905
|
-
};
|
|
14906
|
-
const nextMetadata = {
|
|
14907
|
-
...task.metadata,
|
|
14908
|
-
[LOCAL_FIELDS_KEY]: nextFields
|
|
14909
|
-
};
|
|
14910
|
-
const previousLabels = new Set(currentFields.labels);
|
|
14911
|
-
const nextTags = normalizeList([...task.tags.filter((tag) => !previousLabels.has(tag)), ...labels]);
|
|
14912
|
-
const updates = {
|
|
14913
|
-
version: task.version,
|
|
14914
|
-
priority: input.priority,
|
|
14915
|
-
tags: nextTags,
|
|
14916
|
-
metadata: nextMetadata
|
|
14917
|
-
};
|
|
14918
|
-
if (input.owner !== undefined)
|
|
14919
|
-
updates.assigned_to = nextFields.owner;
|
|
14920
|
-
return updateTask(taskId, updates, d);
|
|
14921
|
-
}
|
|
14922
|
-
function queryTasksByLocalFields(query, db) {
|
|
14923
|
-
const d = db || getDatabase();
|
|
14924
|
-
const tasks = listTasks({
|
|
14925
|
-
priority: query.priority,
|
|
14926
|
-
tags: query.labels,
|
|
14927
|
-
limit: 1e4
|
|
14928
|
-
}, d);
|
|
14929
|
-
const matches = tasks.filter((task) => {
|
|
14930
|
-
const fields = getTaskLocalFields(task.id, d);
|
|
14931
|
-
if (query.labels && !query.labels.every((label) => fields.labels.includes(label)))
|
|
14932
|
-
return false;
|
|
14933
|
-
if (query.severity && fields.severity !== query.severity)
|
|
14934
|
-
return false;
|
|
14935
|
-
if (query.owner && fields.owner !== query.owner)
|
|
14936
|
-
return false;
|
|
14937
|
-
if (query.area && fields.area !== query.area)
|
|
14938
|
-
return false;
|
|
14939
|
-
if (query.custom) {
|
|
14940
|
-
for (const [key, expected] of Object.entries(query.custom)) {
|
|
14941
|
-
if (!sameCustomValue(fields.custom[key], expected))
|
|
14942
|
-
return false;
|
|
14943
|
-
}
|
|
14944
|
-
}
|
|
14945
|
-
return true;
|
|
14946
|
-
});
|
|
14947
|
-
return matches.slice(0, query.limit || 100);
|
|
14948
|
-
}
|
|
16008
|
+
// src/mcp/tools/task-project-tools.ts
|
|
16009
|
+
init_local_fields();
|
|
14949
16010
|
|
|
14950
16011
|
// src/lib/task-dedupe.ts
|
|
14951
16012
|
init_audit();
|
|
@@ -16737,6 +17798,76 @@ ${lines.join(`
|
|
|
16737
17798
|
}
|
|
16738
17799
|
});
|
|
16739
17800
|
}
|
|
17801
|
+
if (shouldRegisterTool("save_search_view")) {
|
|
17802
|
+
server.tool("save_search_view", "Save a local search view for tasks, projects, plans, runs, comments, or all records.", {
|
|
17803
|
+
name: exports_external.string().describe("Saved view name"),
|
|
17804
|
+
query: exports_external.string().optional().describe("Search query"),
|
|
17805
|
+
scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional(),
|
|
17806
|
+
description: exports_external.string().optional(),
|
|
17807
|
+
project_id: exports_external.string().optional(),
|
|
17808
|
+
status: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
17809
|
+
priority: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
17810
|
+
assigned_to: exports_external.string().optional(),
|
|
17811
|
+
agent_id: exports_external.string().optional(),
|
|
17812
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
17813
|
+
limit: exports_external.number().optional()
|
|
17814
|
+
}, async ({ name, scope, description, ...filters }) => {
|
|
17815
|
+
try {
|
|
17816
|
+
const { saveSearchView: saveSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17817
|
+
const view = saveSearchView2({
|
|
17818
|
+
name,
|
|
17819
|
+
description,
|
|
17820
|
+
scope,
|
|
17821
|
+
filters: {
|
|
17822
|
+
...filters,
|
|
17823
|
+
project_id: filters.project_id ? resolveId(filters.project_id, "projects") : undefined
|
|
17824
|
+
}
|
|
17825
|
+
});
|
|
17826
|
+
return { content: [{ type: "text", text: JSON.stringify(view, null, 2) }] };
|
|
17827
|
+
} catch (e) {
|
|
17828
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17829
|
+
}
|
|
17830
|
+
});
|
|
17831
|
+
}
|
|
17832
|
+
if (shouldRegisterTool("list_search_views")) {
|
|
17833
|
+
server.tool("list_search_views", "List local saved search views.", {
|
|
17834
|
+
scope: exports_external.enum(["all", "tasks", "projects", "plans", "runs", "comments"]).optional()
|
|
17835
|
+
}, async ({ scope }) => {
|
|
17836
|
+
try {
|
|
17837
|
+
const { listSearchViews: listSearchViews2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17838
|
+
const views = listSearchViews2(scope);
|
|
17839
|
+
return { content: [{ type: "text", text: JSON.stringify(views, null, 2) }] };
|
|
17840
|
+
} catch (e) {
|
|
17841
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17842
|
+
}
|
|
17843
|
+
});
|
|
17844
|
+
}
|
|
17845
|
+
if (shouldRegisterTool("run_search_view")) {
|
|
17846
|
+
server.tool("run_search_view", "Run a local saved search view and return stable JSON results.", {
|
|
17847
|
+
name: exports_external.string().describe("Saved view name or id")
|
|
17848
|
+
}, async ({ name }) => {
|
|
17849
|
+
try {
|
|
17850
|
+
const { runSearchView: runSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17851
|
+
const result = runSearchView2(name);
|
|
17852
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
17853
|
+
} catch (e) {
|
|
17854
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17855
|
+
}
|
|
17856
|
+
});
|
|
17857
|
+
}
|
|
17858
|
+
if (shouldRegisterTool("delete_search_view")) {
|
|
17859
|
+
server.tool("delete_search_view", "Delete a local saved search view.", {
|
|
17860
|
+
name: exports_external.string().describe("Saved view name or id")
|
|
17861
|
+
}, async ({ name }) => {
|
|
17862
|
+
try {
|
|
17863
|
+
const { deleteSearchView: deleteSearchView2 } = (init_saved_search_views(), __toCommonJS(exports_saved_search_views));
|
|
17864
|
+
const deleted = deleteSearchView2(name);
|
|
17865
|
+
return { content: [{ type: "text", text: JSON.stringify({ deleted }, null, 2) }] };
|
|
17866
|
+
} catch (e) {
|
|
17867
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17868
|
+
}
|
|
17869
|
+
});
|
|
17870
|
+
}
|
|
16740
17871
|
}
|
|
16741
17872
|
|
|
16742
17873
|
// src/mcp/tools/task-workflow-tools.ts
|
|
@@ -17828,6 +18959,10 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
17828
18959
|
reschedule_task: "reschedule_task \u2014 Update deadline. Params: task_id, deadline, version",
|
|
17829
18960
|
prioritize_task: "prioritize_task \u2014 Set priority. Params: task_id, priority, version",
|
|
17830
18961
|
search_tasks: "search_tasks \u2014 Full-text search. Params: query, project_id, status, limit",
|
|
18962
|
+
save_search_view: "save_search_view \u2014 Save a local search view across tasks, projects, plans, runs, comments, or all records. Params: name, query, scope, description, project_id, status, priority, assigned_to, agent_id, tags, limit",
|
|
18963
|
+
list_search_views: "list_search_views \u2014 List local saved search views. Params: scope",
|
|
18964
|
+
run_search_view: "run_search_view \u2014 Run a local saved search view and return stable JSON results. Params: name",
|
|
18965
|
+
delete_search_view: "delete_search_view \u2014 Delete a local saved search view. Params: name",
|
|
17831
18966
|
get_my_tasks: "get_my_tasks \u2014 Get tasks for calling agent. Params: agent_id, status, project_id, limit",
|
|
17832
18967
|
get_next_task: "get_next_task \u2014 Get the next available task without claiming it. Params: agent_id, project_id, task_list_id, plan_id, tags",
|
|
17833
18968
|
claim_next_task: "claim_next_task \u2014 Atomically claim and start the next available task. Params: agent_id, project_id, task_list_id, plan_id, tags, steal_stale, stale_minutes",
|
|
@@ -18158,7 +19293,7 @@ var DEFAULT_CAPABILITIES = {
|
|
|
18158
19293
|
browser: ["browser", "screenshot", "artifact", "evidence"],
|
|
18159
19294
|
script: ["script", "command", "retry", "evidence"]
|
|
18160
19295
|
};
|
|
18161
|
-
function
|
|
19296
|
+
function normalizeName2(name) {
|
|
18162
19297
|
const normalized = name.trim().toLowerCase();
|
|
18163
19298
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
18164
19299
|
throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
|
|
@@ -18177,10 +19312,10 @@ function timeoutMs(value) {
|
|
|
18177
19312
|
return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
|
|
18178
19313
|
}
|
|
18179
19314
|
function getProvider(name) {
|
|
18180
|
-
return loadConfig().verification_providers?.[
|
|
19315
|
+
return loadConfig().verification_providers?.[normalizeName2(name)] || null;
|
|
18181
19316
|
}
|
|
18182
19317
|
function upsertVerificationProvider(input) {
|
|
18183
|
-
const name =
|
|
19318
|
+
const name = normalizeName2(input.name);
|
|
18184
19319
|
const config = loadConfig();
|
|
18185
19320
|
const existing = config.verification_providers?.[name];
|
|
18186
19321
|
const timestamp = new Date().toISOString();
|
|
@@ -18210,7 +19345,7 @@ function listVerificationProviders() {
|
|
|
18210
19345
|
return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
|
|
18211
19346
|
}
|
|
18212
19347
|
function removeVerificationProvider(name) {
|
|
18213
|
-
const normalized =
|
|
19348
|
+
const normalized = normalizeName2(name);
|
|
18214
19349
|
const config = loadConfig();
|
|
18215
19350
|
if (!config.verification_providers?.[normalized])
|
|
18216
19351
|
return false;
|
|
@@ -20468,15 +21603,265 @@ ID: ${updated.id}${taskNote}`
|
|
|
20468
21603
|
}
|
|
20469
21604
|
}
|
|
20470
21605
|
|
|
21606
|
+
// src/mcp/tools/templates.ts
|
|
21607
|
+
init_tasks();
|
|
21608
|
+
function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatError }) {
|
|
21609
|
+
if (shouldRegisterTool("create_template")) {
|
|
21610
|
+
server.tool("create_template", "Create a reusable task template. Optionally include a tasks array to define a multi-task template with dependencies and variable placeholders ({name} syntax). Use variables to define typed variable definitions with defaults and required flags.", {
|
|
21611
|
+
name: exports_external.string(),
|
|
21612
|
+
title_pattern: exports_external.string(),
|
|
21613
|
+
description: exports_external.string().optional(),
|
|
21614
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21615
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
21616
|
+
project_id: exports_external.string().optional(),
|
|
21617
|
+
plan_id: exports_external.string().optional(),
|
|
21618
|
+
variables: exports_external.array(exports_external.object({
|
|
21619
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
21620
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
21621
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
21622
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
21623
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
21624
|
+
tasks: exports_external.array(exports_external.object({
|
|
21625
|
+
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
21626
|
+
description: exports_external.string().optional(),
|
|
21627
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21628
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
21629
|
+
task_type: exports_external.string().optional(),
|
|
21630
|
+
depends_on: exports_external.array(exports_external.number()).optional().describe("Position indices (0-based) of tasks this task depends on"),
|
|
21631
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
21632
|
+
})).optional().describe("Multi-task template: ordered list of tasks to create together with dependencies")
|
|
21633
|
+
}, async (params) => {
|
|
21634
|
+
try {
|
|
21635
|
+
const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21636
|
+
const t = createTemplate2(params);
|
|
21637
|
+
const withTasks = getTemplateWithTasks2(t.id);
|
|
21638
|
+
const taskCount = withTasks?.tasks.length ?? 0;
|
|
21639
|
+
const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
|
|
21640
|
+
return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
|
|
21641
|
+
} catch (e) {
|
|
21642
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21643
|
+
}
|
|
21644
|
+
});
|
|
21645
|
+
}
|
|
21646
|
+
if (shouldRegisterTool("list_templates")) {
|
|
21647
|
+
server.tool("list_templates", "List all task templates", {}, async () => {
|
|
21648
|
+
try {
|
|
21649
|
+
const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21650
|
+
const templates = listTemplates2();
|
|
21651
|
+
if (templates.length === 0)
|
|
21652
|
+
return { content: [{ type: "text", text: "No templates." }] };
|
|
21653
|
+
const text = templates.map((t) => {
|
|
21654
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
21655
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
21656
|
+
}).join(`
|
|
21657
|
+
`);
|
|
21658
|
+
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
21659
|
+
${text}` }] };
|
|
21660
|
+
} catch (e) {
|
|
21661
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21662
|
+
}
|
|
21663
|
+
});
|
|
21664
|
+
}
|
|
21665
|
+
if (shouldRegisterTool("create_task_from_template")) {
|
|
21666
|
+
server.tool("create_task_from_template", "Create task(s) from a template. For multi-task templates, creates all tasks with dependencies wired. Supports {variable} substitution in titles/descriptions.", {
|
|
21667
|
+
template_id: exports_external.string(),
|
|
21668
|
+
title: exports_external.string().optional().describe("Override title (single-task templates only)"),
|
|
21669
|
+
description: exports_external.string().optional().describe("Override description (single-task templates only)"),
|
|
21670
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21671
|
+
assigned_to: exports_external.string().optional(),
|
|
21672
|
+
project_id: exports_external.string().optional(),
|
|
21673
|
+
task_list_id: exports_external.string().optional(),
|
|
21674
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders in multi-task templates")
|
|
21675
|
+
}, async (params) => {
|
|
21676
|
+
try {
|
|
21677
|
+
const { taskFromTemplate: taskFromTemplate2, getTemplateWithTasks: getTemplateWithTasks2, tasksFromTemplate: tasksFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21678
|
+
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
21679
|
+
const resolvedProjectId = params.project_id ? resolveId(params.project_id, "projects") : undefined;
|
|
21680
|
+
const resolvedTaskListId = params.task_list_id ? resolveId(params.task_list_id, "task_lists") : undefined;
|
|
21681
|
+
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
21682
|
+
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
21683
|
+
const effectiveProjectId = resolvedProjectId || templateWithTasks.project_id || undefined;
|
|
21684
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, resolvedTaskListId);
|
|
21685
|
+
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
21686
|
+
`);
|
|
21687
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
21688
|
+
${text}` }] };
|
|
21689
|
+
}
|
|
21690
|
+
const input = taskFromTemplate2(resolvedTemplateId, {
|
|
21691
|
+
title: params.title,
|
|
21692
|
+
description: params.description,
|
|
21693
|
+
priority: params.priority,
|
|
21694
|
+
assigned_to: params.assigned_to,
|
|
21695
|
+
project_id: resolvedProjectId,
|
|
21696
|
+
task_list_id: resolvedTaskListId
|
|
21697
|
+
});
|
|
21698
|
+
const task = createTask(input);
|
|
21699
|
+
return { content: [{ type: "text", text: `Task created from template:
|
|
21700
|
+
${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
21701
|
+
} catch (e) {
|
|
21702
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21703
|
+
}
|
|
21704
|
+
});
|
|
21705
|
+
}
|
|
21706
|
+
if (shouldRegisterTool("delete_template")) {
|
|
21707
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
21708
|
+
try {
|
|
21709
|
+
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21710
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
21711
|
+
const deleted = deleteTemplate2(resolvedId);
|
|
21712
|
+
return { content: [{ type: "text", text: deleted ? "Template deleted." : "Template not found." }] };
|
|
21713
|
+
} catch (e) {
|
|
21714
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21715
|
+
}
|
|
21716
|
+
});
|
|
21717
|
+
}
|
|
21718
|
+
if (shouldRegisterTool("update_template")) {
|
|
21719
|
+
server.tool("update_template", "Update a task template's name, title pattern, description, priority, tags, or other fields.", {
|
|
21720
|
+
id: exports_external.string(),
|
|
21721
|
+
name: exports_external.string().optional(),
|
|
21722
|
+
title_pattern: exports_external.string().optional(),
|
|
21723
|
+
description: exports_external.string().optional(),
|
|
21724
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21725
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
21726
|
+
project_id: exports_external.string().optional(),
|
|
21727
|
+
plan_id: exports_external.string().optional()
|
|
21728
|
+
}, async ({ id, ...updates }) => {
|
|
21729
|
+
try {
|
|
21730
|
+
const { updateTemplate: updateTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21731
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
21732
|
+
const t = updateTemplate2(resolvedId, updates);
|
|
21733
|
+
if (!t)
|
|
21734
|
+
return { content: [{ type: "text", text: `Template not found: ${id}` }], isError: true };
|
|
21735
|
+
return { content: [{ type: "text", text: `Template updated: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}` }] };
|
|
21736
|
+
} catch (e) {
|
|
21737
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21738
|
+
}
|
|
21739
|
+
});
|
|
21740
|
+
}
|
|
21741
|
+
if (shouldRegisterTool("init_templates")) {
|
|
21742
|
+
server.tool("init_templates", "Initialize the bundled local template library. Skips templates that already exist by name.", {}, async () => {
|
|
21743
|
+
try {
|
|
21744
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
21745
|
+
const result = initBuiltinTemplates2();
|
|
21746
|
+
if (result.created === 0)
|
|
21747
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
21748
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
21749
|
+
} catch (e) {
|
|
21750
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21751
|
+
}
|
|
21752
|
+
});
|
|
21753
|
+
}
|
|
21754
|
+
if (shouldRegisterTool("list_template_library")) {
|
|
21755
|
+
server.tool("list_template_library", "List the bundled marketplace-free local template library without mutating the database.", {}, async () => {
|
|
21756
|
+
try {
|
|
21757
|
+
const { listBuiltinTemplates: listBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
21758
|
+
const templates = listBuiltinTemplates2().map((template) => ({
|
|
21759
|
+
name: template.name,
|
|
21760
|
+
description: template.description,
|
|
21761
|
+
category: template.category,
|
|
21762
|
+
version: template.version,
|
|
21763
|
+
variables: template.variables,
|
|
21764
|
+
task_count: template.tasks.length
|
|
21765
|
+
}));
|
|
21766
|
+
return { content: [{ type: "text", text: JSON.stringify(templates, null, 2) }] };
|
|
21767
|
+
} catch (e) {
|
|
21768
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21769
|
+
}
|
|
21770
|
+
});
|
|
21771
|
+
}
|
|
21772
|
+
if (shouldRegisterTool("write_template_library")) {
|
|
21773
|
+
server.tool("write_template_library", "Write the bundled local template library to editable JSON files for review or import.", { directory: exports_external.string() }, async ({ directory }) => {
|
|
21774
|
+
try {
|
|
21775
|
+
const { writeBuiltinTemplateFiles: writeBuiltinTemplateFiles2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
21776
|
+
return { content: [{ type: "text", text: JSON.stringify(writeBuiltinTemplateFiles2(directory), null, 2) }] };
|
|
21777
|
+
} catch (e) {
|
|
21778
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21779
|
+
}
|
|
21780
|
+
});
|
|
21781
|
+
}
|
|
21782
|
+
if (shouldRegisterTool("preview_template")) {
|
|
21783
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
21784
|
+
template_id: exports_external.string(),
|
|
21785
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
21786
|
+
}, async (params) => {
|
|
21787
|
+
try {
|
|
21788
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21789
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
21790
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
21791
|
+
const lines = preview.tasks.map((t) => {
|
|
21792
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
21793
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
21794
|
+
});
|
|
21795
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
21796
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
21797
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
21798
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
21799
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
21800
|
+
${lines.join(`
|
|
21801
|
+
`)}` }] };
|
|
21802
|
+
} catch (e) {
|
|
21803
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21804
|
+
}
|
|
21805
|
+
});
|
|
21806
|
+
}
|
|
21807
|
+
if (shouldRegisterTool("export_template")) {
|
|
21808
|
+
server.tool("export_template", "Export a template as a full JSON object (template + tasks + variables). Useful for sharing or backup.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
21809
|
+
try {
|
|
21810
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21811
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
21812
|
+
const json = exportTemplate2(resolvedId);
|
|
21813
|
+
return { content: [{ type: "text", text: JSON.stringify(json, null, 2) }] };
|
|
21814
|
+
} catch (e) {
|
|
21815
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21816
|
+
}
|
|
21817
|
+
});
|
|
21818
|
+
}
|
|
21819
|
+
if (shouldRegisterTool("import_template")) {
|
|
21820
|
+
server.tool("import_template", "Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.", { json: exports_external.string().describe("JSON string of the template export") }, async ({ json }) => {
|
|
21821
|
+
try {
|
|
21822
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21823
|
+
const parsed = JSON.parse(json);
|
|
21824
|
+
const t = importTemplate2(parsed);
|
|
21825
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
21826
|
+
} catch (e) {
|
|
21827
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21828
|
+
}
|
|
21829
|
+
});
|
|
21830
|
+
}
|
|
21831
|
+
if (shouldRegisterTool("template_history")) {
|
|
21832
|
+
server.tool("template_history", "Show version history of a template. Each update creates a snapshot of the previous state.", { template_id: exports_external.string() }, async ({ template_id }) => {
|
|
21833
|
+
try {
|
|
21834
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21835
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
21836
|
+
const template = getTemplate2(resolvedId);
|
|
21837
|
+
if (!template)
|
|
21838
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
21839
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
21840
|
+
if (versions.length === 0)
|
|
21841
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
21842
|
+
const lines = versions.map((v) => {
|
|
21843
|
+
const snap = JSON.parse(v.snapshot);
|
|
21844
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
21845
|
+
});
|
|
21846
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
21847
|
+
${lines.join(`
|
|
21848
|
+
`)}` }] };
|
|
21849
|
+
} catch (e) {
|
|
21850
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21851
|
+
}
|
|
21852
|
+
});
|
|
21853
|
+
}
|
|
21854
|
+
}
|
|
21855
|
+
|
|
20471
21856
|
// src/lib/package-version.ts
|
|
20472
21857
|
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
20473
|
-
import { dirname as dirname7, join as
|
|
21858
|
+
import { dirname as dirname7, join as join7 } from "path";
|
|
20474
21859
|
import { fileURLToPath } from "url";
|
|
20475
21860
|
function getPackageVersion(fromUrl = import.meta.url) {
|
|
20476
21861
|
try {
|
|
20477
21862
|
let dir = dirname7(fileURLToPath(fromUrl));
|
|
20478
21863
|
for (let i = 0;i < 5; i++) {
|
|
20479
|
-
const pkgPath =
|
|
21864
|
+
const pkgPath = join7(dir, "package.json");
|
|
20480
21865
|
if (existsSync8(pkgPath)) {
|
|
20481
21866
|
return JSON.parse(readFileSync6(pkgPath, "utf-8")).version || "0.0.0";
|
|
20482
21867
|
}
|
|
@@ -20660,6 +22045,7 @@ registerTaskResources(server, toolContext);
|
|
|
20660
22045
|
registerTaskRelTools(server, toolContext);
|
|
20661
22046
|
registerCodeTools(server, toolContext);
|
|
20662
22047
|
registerAgentTools(server, { ...toolContext, agentFocusMap });
|
|
22048
|
+
registerTemplateTools(server, toolContext);
|
|
20663
22049
|
registerMachineTools(server, { shouldRegisterTool, formatError });
|
|
20664
22050
|
registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
|
|
20665
22051
|
async function main() {
|