@hasna/todos 0.11.42 → 0.11.44
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 +17 -9
- package/dist/cli/commands/environment-snapshots.d.ts +3 -0
- package/dist/cli/commands/environment-snapshots.d.ts.map +1 -0
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1017 -137
- package/dist/cli-mcp-parity.d.ts +1 -1
- package/dist/cli-mcp-parity.d.ts.map +1 -1
- package/dist/contracts.js +44 -0
- package/dist/db/builtin-templates.d.ts +17 -0
- package/dist/db/builtin-templates.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +602 -65
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/environment-snapshots.d.ts +111 -0
- package/dist/lib/environment-snapshots.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1274 -8
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/environment-snapshots.d.ts +8 -0
- package/dist/mcp/tools/environment-snapshots.d.ts.map +1 -0
- package/dist/mcp/tools/templates.d.ts.map +1 -1
- package/dist/mcp.js +5 -1
- package/dist/registry.js +94 -1
- package/dist/release-provenance.json +3 -3
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -4436,6 +4436,26 @@ var init_recurrence = __esm(() => {
|
|
|
4436
4436
|
});
|
|
4437
4437
|
|
|
4438
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
|
+
});
|
|
4439
4459
|
function rowToTemplate(row) {
|
|
4440
4460
|
return {
|
|
4441
4461
|
...row,
|
|
@@ -4446,9 +4466,42 @@ function rowToTemplate(row) {
|
|
|
4446
4466
|
version: row.version ?? 1
|
|
4447
4467
|
};
|
|
4448
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
|
+
}
|
|
4449
4480
|
function resolveTemplateId(id, d) {
|
|
4450
4481
|
return resolvePartialId(d, "task_templates", id);
|
|
4451
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
|
+
}
|
|
4452
4505
|
function getTemplate(id, db) {
|
|
4453
4506
|
const d = db || getDatabase();
|
|
4454
4507
|
const resolved = resolveTemplateId(id, d);
|
|
@@ -4457,6 +4510,80 @@ function getTemplate(id, db) {
|
|
|
4457
4510
|
const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(resolved);
|
|
4458
4511
|
return row ? rowToTemplate(row) : null;
|
|
4459
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
|
+
}
|
|
4460
4587
|
function taskFromTemplate(templateId, overrides = {}, db) {
|
|
4461
4588
|
const t = getTemplate(templateId, db);
|
|
4462
4589
|
if (!t)
|
|
@@ -4473,6 +4600,291 @@ function taskFromTemplate(templateId, overrides = {}, db) {
|
|
|
4473
4600
|
...cleanOverrides
|
|
4474
4601
|
};
|
|
4475
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
|
+
}
|
|
4476
4888
|
var init_templates = __esm(() => {
|
|
4477
4889
|
init_database();
|
|
4478
4890
|
init_tasks();
|
|
@@ -9329,6 +9741,568 @@ var init_extract = __esm(() => {
|
|
|
9329
9741
|
]);
|
|
9330
9742
|
});
|
|
9331
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
|
+
|
|
10017
|
+
// src/lib/environment-snapshots.ts
|
|
10018
|
+
var exports_environment_snapshots = {};
|
|
10019
|
+
__export(exports_environment_snapshots, {
|
|
10020
|
+
writeEnvironmentSnapshot: () => writeEnvironmentSnapshot,
|
|
10021
|
+
recordEnvironmentSnapshot: () => recordEnvironmentSnapshot,
|
|
10022
|
+
readEnvironmentSnapshot: () => readEnvironmentSnapshot,
|
|
10023
|
+
compareEnvironmentSnapshots: () => compareEnvironmentSnapshots,
|
|
10024
|
+
compareEnvironmentSnapshotFiles: () => compareEnvironmentSnapshotFiles,
|
|
10025
|
+
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
10026
|
+
});
|
|
10027
|
+
import { createHash as createHash5 } from "crypto";
|
|
10028
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync6 } from "fs";
|
|
10029
|
+
import { hostname, platform, arch } from "os";
|
|
10030
|
+
import { dirname as dirname7, join as join7, resolve as resolve9 } from "path";
|
|
10031
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10032
|
+
function sha2563(value) {
|
|
10033
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
10034
|
+
}
|
|
10035
|
+
function fileRecord(root, relativePath) {
|
|
10036
|
+
const path = join7(root, relativePath);
|
|
10037
|
+
if (!existsSync8(path))
|
|
10038
|
+
return null;
|
|
10039
|
+
const stat = statSync6(path);
|
|
10040
|
+
if (!stat.isFile())
|
|
10041
|
+
return null;
|
|
10042
|
+
const content = readFileSync6(path);
|
|
10043
|
+
return { path: relativePath, sha256: sha2563(content), size_bytes: content.length };
|
|
10044
|
+
}
|
|
10045
|
+
function manifestRecord(root, relativePath) {
|
|
10046
|
+
const base = fileRecord(root, relativePath);
|
|
10047
|
+
if (!base)
|
|
10048
|
+
return null;
|
|
10049
|
+
const parsed = readJsonFile(join7(root, relativePath));
|
|
10050
|
+
if (!parsed)
|
|
10051
|
+
return { ...base, redacted: {} };
|
|
10052
|
+
const redacted = redactValue({
|
|
10053
|
+
name: parsed["name"] ?? null,
|
|
10054
|
+
version: parsed["version"] ?? null,
|
|
10055
|
+
packageManager: parsed["packageManager"] ?? null,
|
|
10056
|
+
scripts: parsed["scripts"] ?? {},
|
|
10057
|
+
dependencies: parsed["dependencies"] ?? {},
|
|
10058
|
+
devDependencies: parsed["devDependencies"] ?? {},
|
|
10059
|
+
peerDependencies: parsed["peerDependencies"] ?? {},
|
|
10060
|
+
optionalDependencies: parsed["optionalDependencies"] ?? {}
|
|
10061
|
+
});
|
|
10062
|
+
return { ...base, redacted };
|
|
10063
|
+
}
|
|
10064
|
+
function runLocalCommand(root, args) {
|
|
10065
|
+
const result = Bun.spawnSync({
|
|
10066
|
+
cmd: args,
|
|
10067
|
+
cwd: root,
|
|
10068
|
+
stdout: "pipe",
|
|
10069
|
+
stderr: "pipe",
|
|
10070
|
+
env: { PATH: process.env["PATH"] || "" }
|
|
10071
|
+
});
|
|
10072
|
+
return {
|
|
10073
|
+
exitCode: result.exitCode,
|
|
10074
|
+
stdout: redactEvidenceText(result.stdout.toString("utf8").trim()),
|
|
10075
|
+
stderr: redactEvidenceText(result.stderr.toString("utf8").trim())
|
|
10076
|
+
};
|
|
10077
|
+
}
|
|
10078
|
+
function summarizeGitStatus(lines) {
|
|
10079
|
+
const summary = { added: 0, modified: 0, deleted: 0, renamed: 0, untracked: 0 };
|
|
10080
|
+
for (const line of lines) {
|
|
10081
|
+
if (line.startsWith("??"))
|
|
10082
|
+
summary.untracked += 1;
|
|
10083
|
+
else if (line.includes("R"))
|
|
10084
|
+
summary.renamed += 1;
|
|
10085
|
+
else if (line.includes("D"))
|
|
10086
|
+
summary.deleted += 1;
|
|
10087
|
+
else if (line.includes("A"))
|
|
10088
|
+
summary.added += 1;
|
|
10089
|
+
else if (line.includes("M"))
|
|
10090
|
+
summary.modified += 1;
|
|
10091
|
+
}
|
|
10092
|
+
return summary;
|
|
10093
|
+
}
|
|
10094
|
+
function captureGit(root, warnings) {
|
|
10095
|
+
const inside = runLocalCommand(root, ["git", "rev-parse", "--is-inside-work-tree"]);
|
|
10096
|
+
if (inside.exitCode !== 0 || inside.stdout !== "true") {
|
|
10097
|
+
return { present: false, branch: null, commit: null, is_dirty: false, status_porcelain: [], status_summary: summarizeGitStatus([]) };
|
|
10098
|
+
}
|
|
10099
|
+
const branch = runLocalCommand(root, ["git", "branch", "--show-current"]);
|
|
10100
|
+
const commit = runLocalCommand(root, ["git", "rev-parse", "HEAD"]);
|
|
10101
|
+
const status = runLocalCommand(root, ["git", "status", "--porcelain=v1"]);
|
|
10102
|
+
if (commit.exitCode !== 0)
|
|
10103
|
+
warnings.push(`git commit unavailable: ${commit.stderr || commit.stdout || "unknown error"}`);
|
|
10104
|
+
if (status.exitCode !== 0)
|
|
10105
|
+
warnings.push(`git status unavailable: ${status.stderr || status.stdout || "unknown error"}`);
|
|
10106
|
+
const lines = status.stdout ? status.stdout.split(/\r?\n/).filter(Boolean) : [];
|
|
10107
|
+
return {
|
|
10108
|
+
present: true,
|
|
10109
|
+
branch: branch.stdout || null,
|
|
10110
|
+
commit: commit.stdout || null,
|
|
10111
|
+
is_dirty: lines.length > 0,
|
|
10112
|
+
status_porcelain: lines,
|
|
10113
|
+
status_summary: summarizeGitStatus(lines)
|
|
10114
|
+
};
|
|
10115
|
+
}
|
|
10116
|
+
function packageManager(env, lockfiles) {
|
|
10117
|
+
const userAgent = (env["npm_config_user_agent"] || "").toLowerCase();
|
|
10118
|
+
if (userAgent.includes("bun"))
|
|
10119
|
+
return "bun";
|
|
10120
|
+
if (lockfiles.some((file) => file.path.startsWith("bun.lock")))
|
|
10121
|
+
return "bun";
|
|
10122
|
+
if (userAgent.includes("npm") || lockfiles.some((file) => file.path.includes("package-lock")))
|
|
10123
|
+
return "npm";
|
|
10124
|
+
return "unknown";
|
|
10125
|
+
}
|
|
10126
|
+
function isSecretEnvKey(key) {
|
|
10127
|
+
return /api[_-]?key|token|secret|password|credential|private|session|cookie/i.test(key);
|
|
10128
|
+
}
|
|
10129
|
+
function commandEnv(env, includeValues) {
|
|
10130
|
+
const keys = Object.keys(env).sort();
|
|
10131
|
+
const interesting = keys.filter((key) => isSecretEnvKey(key) || ["CI", "NODE_ENV", "BUN_ENV", "SHELL", "TERM", "PATH", "PWD", "USER", "npm_config_user_agent"].includes(key) || key.startsWith("TODOS_") || key.startsWith("BUN_"));
|
|
10132
|
+
const redactedKeys = interesting.filter(isSecretEnvKey);
|
|
10133
|
+
const values = includeValues ? Object.fromEntries(interesting.map((key) => [key, isSecretEnvKey(key) ? "[REDACTED]" : redactEvidenceText(String(env[key] ?? ""))])) : null;
|
|
10134
|
+
return {
|
|
10135
|
+
command: null,
|
|
10136
|
+
env_keys: interesting,
|
|
10137
|
+
env: values,
|
|
10138
|
+
redacted_keys: redactedKeys
|
|
10139
|
+
};
|
|
10140
|
+
}
|
|
10141
|
+
function defaultSnapshotDir() {
|
|
10142
|
+
const dbPath = getDatabasePath();
|
|
10143
|
+
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
10144
|
+
return join7(tmpdir2(), "hasna-todos", "environment-snapshots");
|
|
10145
|
+
return join7(dirname7(resolve9(dbPath)), "environment-snapshots");
|
|
10146
|
+
}
|
|
10147
|
+
function snapshotWithId(snapshot) {
|
|
10148
|
+
const digest = sha2563(JSON.stringify(snapshot)).slice(0, 24);
|
|
10149
|
+
return { id: `env_${digest}`, ...snapshot };
|
|
10150
|
+
}
|
|
10151
|
+
function captureEnvironmentSnapshot(input = {}) {
|
|
10152
|
+
const root = resolve9(input.root || process.cwd());
|
|
10153
|
+
const env = input.env || process.env;
|
|
10154
|
+
const warnings = [];
|
|
10155
|
+
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
10156
|
+
const lockfiles = LOCKFILES.map((file) => fileRecord(root, file)).filter((file) => Boolean(file));
|
|
10157
|
+
const configHashes = CONFIG_FILES.map((file) => fileRecord(root, file)).filter((file) => Boolean(file));
|
|
10158
|
+
const commandMetadata = commandEnv(env, Boolean(input.include_env_values));
|
|
10159
|
+
commandMetadata.command = input.command ? redactEvidenceText(input.command) : null;
|
|
10160
|
+
if (manifests.length === 0)
|
|
10161
|
+
warnings.push("no package manifest found");
|
|
10162
|
+
if (lockfiles.length === 0)
|
|
10163
|
+
warnings.push("no package lockfile found");
|
|
10164
|
+
return snapshotWithId({
|
|
10165
|
+
schema_version: 1,
|
|
10166
|
+
captured_at: input.now ? new Date(input.now).toISOString() : new Date().toISOString(),
|
|
10167
|
+
root,
|
|
10168
|
+
machine: { hostname: hostname(), platform: platform(), arch: arch() },
|
|
10169
|
+
target: {
|
|
10170
|
+
task_id: input.task_id ?? null,
|
|
10171
|
+
run_id: input.run_id ?? null,
|
|
10172
|
+
agent_id: input.agent_id ?? null
|
|
10173
|
+
},
|
|
10174
|
+
runtime: {
|
|
10175
|
+
bun: Bun.version || null,
|
|
10176
|
+
node: process.version,
|
|
10177
|
+
executable: process.execPath
|
|
10178
|
+
},
|
|
10179
|
+
package_manager: {
|
|
10180
|
+
manager: packageManager(env, lockfiles),
|
|
10181
|
+
user_agent: env["npm_config_user_agent"] ? redactEvidenceText(env["npm_config_user_agent"]) : null,
|
|
10182
|
+
manifests,
|
|
10183
|
+
lockfiles
|
|
10184
|
+
},
|
|
10185
|
+
git: captureGit(root, warnings),
|
|
10186
|
+
config_hashes: configHashes,
|
|
10187
|
+
command_env: commandMetadata,
|
|
10188
|
+
warnings
|
|
10189
|
+
});
|
|
10190
|
+
}
|
|
10191
|
+
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
10192
|
+
const path = outputPath ? resolve9(outputPath) : join7(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
10193
|
+
ensureDir2(dirname7(path));
|
|
10194
|
+
writeJsonFile(path, snapshot);
|
|
10195
|
+
return path;
|
|
10196
|
+
}
|
|
10197
|
+
function readEnvironmentSnapshot(path) {
|
|
10198
|
+
const snapshot = readJsonFile(resolve9(path));
|
|
10199
|
+
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
10200
|
+
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
10201
|
+
}
|
|
10202
|
+
return snapshot;
|
|
10203
|
+
}
|
|
10204
|
+
function recordEnvironmentSnapshot(input = {}, db) {
|
|
10205
|
+
let taskId = input.task_id;
|
|
10206
|
+
let runId = input.run_id;
|
|
10207
|
+
const needsDatabase = Boolean(taskId || runId);
|
|
10208
|
+
const d = needsDatabase ? db || getDatabase() : null;
|
|
10209
|
+
if (runId) {
|
|
10210
|
+
runId = resolveTaskRunId(runId, d);
|
|
10211
|
+
const run = getTaskRun(runId, d);
|
|
10212
|
+
if (!run)
|
|
10213
|
+
throw new Error(`Run not found: ${input.run_id}`);
|
|
10214
|
+
taskId = taskId || run.task_id;
|
|
10215
|
+
}
|
|
10216
|
+
if (taskId && d) {
|
|
10217
|
+
taskId = resolvePartialId(d, "tasks", taskId) || taskId;
|
|
10218
|
+
if (!getTask(taskId, d))
|
|
10219
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
10220
|
+
}
|
|
10221
|
+
const snapshot = captureEnvironmentSnapshot({ ...input, task_id: taskId, run_id: runId });
|
|
10222
|
+
const outputPath = writeEnvironmentSnapshot(snapshot, input.output_path);
|
|
10223
|
+
let taskVerificationId = null;
|
|
10224
|
+
let runArtifactId = null;
|
|
10225
|
+
if (runId) {
|
|
10226
|
+
const artifact = addTaskRunArtifact({
|
|
10227
|
+
run_id: runId,
|
|
10228
|
+
path: outputPath,
|
|
10229
|
+
artifact_type: "environment_snapshot",
|
|
10230
|
+
description: "Reproducible local environment snapshot",
|
|
10231
|
+
metadata: { environment_snapshot_id: snapshot.id, schema_version: snapshot.schema_version },
|
|
10232
|
+
store_content: input.store_content ?? true,
|
|
10233
|
+
agent_id: input.agent_id
|
|
10234
|
+
}, d);
|
|
10235
|
+
runArtifactId = artifact.id;
|
|
10236
|
+
} else if (taskId) {
|
|
10237
|
+
const verification = addTaskVerification({
|
|
10238
|
+
task_id: taskId,
|
|
10239
|
+
command: input.command || "capture environment snapshot",
|
|
10240
|
+
status: "unknown",
|
|
10241
|
+
output_summary: `environment snapshot ${snapshot.id}`,
|
|
10242
|
+
artifact_path: outputPath,
|
|
10243
|
+
agent_id: input.agent_id,
|
|
10244
|
+
run_at: snapshot.captured_at
|
|
10245
|
+
}, d);
|
|
10246
|
+
taskVerificationId = verification.id;
|
|
10247
|
+
}
|
|
10248
|
+
return { snapshot, output_path: outputPath, task_verification_id: taskVerificationId, run_artifact_id: runArtifactId };
|
|
10249
|
+
}
|
|
10250
|
+
function keyed(files) {
|
|
10251
|
+
return new Map(files.map((file) => [file.path, file]));
|
|
10252
|
+
}
|
|
10253
|
+
function diffFiles(left, right) {
|
|
10254
|
+
const leftMap = keyed(left);
|
|
10255
|
+
const rightMap = keyed(right);
|
|
10256
|
+
const paths = [...new Set([...leftMap.keys(), ...rightMap.keys()])].sort((a, b) => a.localeCompare(b));
|
|
10257
|
+
return paths.map((path) => ({ path, left_sha256: leftMap.get(path)?.sha256 ?? null, right_sha256: rightMap.get(path)?.sha256 ?? null })).filter((entry) => entry.left_sha256 !== entry.right_sha256);
|
|
10258
|
+
}
|
|
10259
|
+
function compareEnvironmentSnapshots(left, right) {
|
|
10260
|
+
const warnings = [];
|
|
10261
|
+
if (left.schema_version !== right.schema_version)
|
|
10262
|
+
warnings.push("snapshot schema versions differ");
|
|
10263
|
+
return {
|
|
10264
|
+
schema_version: 1,
|
|
10265
|
+
left_id: left.id,
|
|
10266
|
+
right_id: right.id,
|
|
10267
|
+
same_root: left.root === right.root,
|
|
10268
|
+
same_machine: left.machine.hostname === right.machine.hostname && left.machine.platform === right.machine.platform && left.machine.arch === right.machine.arch,
|
|
10269
|
+
same_runtime: left.runtime.bun === right.runtime.bun && left.runtime.node === right.runtime.node,
|
|
10270
|
+
same_git_commit: left.git.commit === right.git.commit,
|
|
10271
|
+
dirty_state_changed: left.git.is_dirty !== right.git.is_dirty,
|
|
10272
|
+
changed_config_hashes: diffFiles(left.config_hashes, right.config_hashes),
|
|
10273
|
+
changed_lockfiles: diffFiles(left.package_manager.lockfiles, right.package_manager.lockfiles),
|
|
10274
|
+
changed_manifests: diffFiles(left.package_manager.manifests, right.package_manager.manifests),
|
|
10275
|
+
warnings
|
|
10276
|
+
};
|
|
10277
|
+
}
|
|
10278
|
+
function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
|
|
10279
|
+
return compareEnvironmentSnapshots(readEnvironmentSnapshot(leftPath), readEnvironmentSnapshot(rightPath));
|
|
10280
|
+
}
|
|
10281
|
+
var MANIFEST_FILES, LOCKFILES, CONFIG_FILES;
|
|
10282
|
+
var init_environment_snapshots = __esm(() => {
|
|
10283
|
+
init_task_runs();
|
|
10284
|
+
init_task_commits();
|
|
10285
|
+
init_database();
|
|
10286
|
+
init_tasks();
|
|
10287
|
+
init_sync_utils();
|
|
10288
|
+
MANIFEST_FILES = ["package.json", "dashboard/package.json", "sdk/package.json"];
|
|
10289
|
+
LOCKFILES = ["bun.lock", "bun.lockb", "package-lock.json", "npm-shrinkwrap.json"];
|
|
10290
|
+
CONFIG_FILES = [
|
|
10291
|
+
"AGENTS.md",
|
|
10292
|
+
"CLAUDE.md",
|
|
10293
|
+
"README.md",
|
|
10294
|
+
"SECURITY.md",
|
|
10295
|
+
"bunfig.toml",
|
|
10296
|
+
"tsconfig.json",
|
|
10297
|
+
"components.json",
|
|
10298
|
+
"next.config.js",
|
|
10299
|
+
"next.config.mjs",
|
|
10300
|
+
"next.config.ts",
|
|
10301
|
+
"vite.config.ts",
|
|
10302
|
+
"dashboard/vite.config.ts"
|
|
10303
|
+
];
|
|
10304
|
+
});
|
|
10305
|
+
|
|
9332
10306
|
// src/mcp/index.ts
|
|
9333
10307
|
init_agents();
|
|
9334
10308
|
init_database();
|
|
@@ -13700,6 +14674,8 @@ var MCP_TOOL_GROUPS = {
|
|
|
13700
14674
|
"check_file_lock",
|
|
13701
14675
|
"create_comment",
|
|
13702
14676
|
"create_handoff",
|
|
14677
|
+
"capture_environment_snapshot",
|
|
14678
|
+
"compare_environment_snapshots",
|
|
13703
14679
|
"create_inbox_item",
|
|
13704
14680
|
"delete_comment",
|
|
13705
14681
|
"detect_file_relationships",
|
|
@@ -13822,10 +14798,12 @@ var MCP_TOOL_GROUPS = {
|
|
|
13822
14798
|
"export_template",
|
|
13823
14799
|
"import_template",
|
|
13824
14800
|
"init_templates",
|
|
14801
|
+
"list_template_library",
|
|
13825
14802
|
"list_templates",
|
|
13826
14803
|
"preview_template",
|
|
13827
14804
|
"template_history",
|
|
13828
|
-
"update_template"
|
|
14805
|
+
"update_template",
|
|
14806
|
+
"write_template_library"
|
|
13829
14807
|
],
|
|
13830
14808
|
webhooks: ["create_webhook", "delete_webhook", "list_webhooks"],
|
|
13831
14809
|
machines: [
|
|
@@ -20916,19 +21894,305 @@ ID: ${updated.id}${taskNote}`
|
|
|
20916
21894
|
}
|
|
20917
21895
|
}
|
|
20918
21896
|
|
|
21897
|
+
// src/mcp/tools/templates.ts
|
|
21898
|
+
init_tasks();
|
|
21899
|
+
function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatError }) {
|
|
21900
|
+
if (shouldRegisterTool("create_template")) {
|
|
21901
|
+
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.", {
|
|
21902
|
+
name: exports_external.string(),
|
|
21903
|
+
title_pattern: exports_external.string(),
|
|
21904
|
+
description: exports_external.string().optional(),
|
|
21905
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21906
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
21907
|
+
project_id: exports_external.string().optional(),
|
|
21908
|
+
plan_id: exports_external.string().optional(),
|
|
21909
|
+
variables: exports_external.array(exports_external.object({
|
|
21910
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
21911
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
21912
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
21913
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
21914
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
21915
|
+
tasks: exports_external.array(exports_external.object({
|
|
21916
|
+
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
21917
|
+
description: exports_external.string().optional(),
|
|
21918
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21919
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
21920
|
+
task_type: exports_external.string().optional(),
|
|
21921
|
+
depends_on: exports_external.array(exports_external.number()).optional().describe("Position indices (0-based) of tasks this task depends on"),
|
|
21922
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
21923
|
+
})).optional().describe("Multi-task template: ordered list of tasks to create together with dependencies")
|
|
21924
|
+
}, async (params) => {
|
|
21925
|
+
try {
|
|
21926
|
+
const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21927
|
+
const t = createTemplate2(params);
|
|
21928
|
+
const withTasks = getTemplateWithTasks2(t.id);
|
|
21929
|
+
const taskCount = withTasks?.tasks.length ?? 0;
|
|
21930
|
+
const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
|
|
21931
|
+
return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
|
|
21932
|
+
} catch (e) {
|
|
21933
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21934
|
+
}
|
|
21935
|
+
});
|
|
21936
|
+
}
|
|
21937
|
+
if (shouldRegisterTool("list_templates")) {
|
|
21938
|
+
server.tool("list_templates", "List all task templates", {}, async () => {
|
|
21939
|
+
try {
|
|
21940
|
+
const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21941
|
+
const templates = listTemplates2();
|
|
21942
|
+
if (templates.length === 0)
|
|
21943
|
+
return { content: [{ type: "text", text: "No templates." }] };
|
|
21944
|
+
const text = templates.map((t) => {
|
|
21945
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
21946
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
21947
|
+
}).join(`
|
|
21948
|
+
`);
|
|
21949
|
+
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
21950
|
+
${text}` }] };
|
|
21951
|
+
} catch (e) {
|
|
21952
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21953
|
+
}
|
|
21954
|
+
});
|
|
21955
|
+
}
|
|
21956
|
+
if (shouldRegisterTool("create_task_from_template")) {
|
|
21957
|
+
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.", {
|
|
21958
|
+
template_id: exports_external.string(),
|
|
21959
|
+
title: exports_external.string().optional().describe("Override title (single-task templates only)"),
|
|
21960
|
+
description: exports_external.string().optional().describe("Override description (single-task templates only)"),
|
|
21961
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
21962
|
+
assigned_to: exports_external.string().optional(),
|
|
21963
|
+
project_id: exports_external.string().optional(),
|
|
21964
|
+
task_list_id: exports_external.string().optional(),
|
|
21965
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders in multi-task templates")
|
|
21966
|
+
}, async (params) => {
|
|
21967
|
+
try {
|
|
21968
|
+
const { taskFromTemplate: taskFromTemplate2, getTemplateWithTasks: getTemplateWithTasks2, tasksFromTemplate: tasksFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
21969
|
+
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
21970
|
+
const resolvedProjectId = params.project_id ? resolveId(params.project_id, "projects") : undefined;
|
|
21971
|
+
const resolvedTaskListId = params.task_list_id ? resolveId(params.task_list_id, "task_lists") : undefined;
|
|
21972
|
+
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
21973
|
+
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
21974
|
+
const effectiveProjectId = resolvedProjectId || templateWithTasks.project_id || undefined;
|
|
21975
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, resolvedTaskListId);
|
|
21976
|
+
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
21977
|
+
`);
|
|
21978
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
21979
|
+
${text}` }] };
|
|
21980
|
+
}
|
|
21981
|
+
const input = taskFromTemplate2(resolvedTemplateId, {
|
|
21982
|
+
title: params.title,
|
|
21983
|
+
description: params.description,
|
|
21984
|
+
priority: params.priority,
|
|
21985
|
+
assigned_to: params.assigned_to,
|
|
21986
|
+
project_id: resolvedProjectId,
|
|
21987
|
+
task_list_id: resolvedTaskListId
|
|
21988
|
+
});
|
|
21989
|
+
const task = createTask(input);
|
|
21990
|
+
return { content: [{ type: "text", text: `Task created from template:
|
|
21991
|
+
${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
21992
|
+
} catch (e) {
|
|
21993
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
21994
|
+
}
|
|
21995
|
+
});
|
|
21996
|
+
}
|
|
21997
|
+
if (shouldRegisterTool("delete_template")) {
|
|
21998
|
+
server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
|
|
21999
|
+
try {
|
|
22000
|
+
const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
22001
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
22002
|
+
const deleted = deleteTemplate2(resolvedId);
|
|
22003
|
+
return { content: [{ type: "text", text: deleted ? "Template deleted." : "Template not found." }] };
|
|
22004
|
+
} catch (e) {
|
|
22005
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22006
|
+
}
|
|
22007
|
+
});
|
|
22008
|
+
}
|
|
22009
|
+
if (shouldRegisterTool("update_template")) {
|
|
22010
|
+
server.tool("update_template", "Update a task template's name, title pattern, description, priority, tags, or other fields.", {
|
|
22011
|
+
id: exports_external.string(),
|
|
22012
|
+
name: exports_external.string().optional(),
|
|
22013
|
+
title_pattern: exports_external.string().optional(),
|
|
22014
|
+
description: exports_external.string().optional(),
|
|
22015
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
22016
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
22017
|
+
project_id: exports_external.string().optional(),
|
|
22018
|
+
plan_id: exports_external.string().optional()
|
|
22019
|
+
}, async ({ id, ...updates }) => {
|
|
22020
|
+
try {
|
|
22021
|
+
const { updateTemplate: updateTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
22022
|
+
const resolvedId = resolveId(id, "task_templates");
|
|
22023
|
+
const t = updateTemplate2(resolvedId, updates);
|
|
22024
|
+
if (!t)
|
|
22025
|
+
return { content: [{ type: "text", text: `Template not found: ${id}` }], isError: true };
|
|
22026
|
+
return { content: [{ type: "text", text: `Template updated: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}` }] };
|
|
22027
|
+
} catch (e) {
|
|
22028
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22029
|
+
}
|
|
22030
|
+
});
|
|
22031
|
+
}
|
|
22032
|
+
if (shouldRegisterTool("init_templates")) {
|
|
22033
|
+
server.tool("init_templates", "Initialize the bundled local template library. Skips templates that already exist by name.", {}, async () => {
|
|
22034
|
+
try {
|
|
22035
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
22036
|
+
const result = initBuiltinTemplates2();
|
|
22037
|
+
if (result.created === 0)
|
|
22038
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
22039
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
22040
|
+
} catch (e) {
|
|
22041
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22042
|
+
}
|
|
22043
|
+
});
|
|
22044
|
+
}
|
|
22045
|
+
if (shouldRegisterTool("list_template_library")) {
|
|
22046
|
+
server.tool("list_template_library", "List the bundled marketplace-free local template library without mutating the database.", {}, async () => {
|
|
22047
|
+
try {
|
|
22048
|
+
const { listBuiltinTemplates: listBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
22049
|
+
const templates = listBuiltinTemplates2().map((template) => ({
|
|
22050
|
+
name: template.name,
|
|
22051
|
+
description: template.description,
|
|
22052
|
+
category: template.category,
|
|
22053
|
+
version: template.version,
|
|
22054
|
+
variables: template.variables,
|
|
22055
|
+
task_count: template.tasks.length
|
|
22056
|
+
}));
|
|
22057
|
+
return { content: [{ type: "text", text: JSON.stringify(templates, null, 2) }] };
|
|
22058
|
+
} catch (e) {
|
|
22059
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22060
|
+
}
|
|
22061
|
+
});
|
|
22062
|
+
}
|
|
22063
|
+
if (shouldRegisterTool("write_template_library")) {
|
|
22064
|
+
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 }) => {
|
|
22065
|
+
try {
|
|
22066
|
+
const { writeBuiltinTemplateFiles: writeBuiltinTemplateFiles2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
22067
|
+
return { content: [{ type: "text", text: JSON.stringify(writeBuiltinTemplateFiles2(directory), null, 2) }] };
|
|
22068
|
+
} catch (e) {
|
|
22069
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22070
|
+
}
|
|
22071
|
+
});
|
|
22072
|
+
}
|
|
22073
|
+
if (shouldRegisterTool("preview_template")) {
|
|
22074
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
22075
|
+
template_id: exports_external.string(),
|
|
22076
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
22077
|
+
}, async (params) => {
|
|
22078
|
+
try {
|
|
22079
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
22080
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
22081
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
22082
|
+
const lines = preview.tasks.map((t) => {
|
|
22083
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
22084
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
22085
|
+
});
|
|
22086
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
22087
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
22088
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
22089
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
22090
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
22091
|
+
${lines.join(`
|
|
22092
|
+
`)}` }] };
|
|
22093
|
+
} catch (e) {
|
|
22094
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22095
|
+
}
|
|
22096
|
+
});
|
|
22097
|
+
}
|
|
22098
|
+
if (shouldRegisterTool("export_template")) {
|
|
22099
|
+
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 }) => {
|
|
22100
|
+
try {
|
|
22101
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
22102
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
22103
|
+
const json = exportTemplate2(resolvedId);
|
|
22104
|
+
return { content: [{ type: "text", text: JSON.stringify(json, null, 2) }] };
|
|
22105
|
+
} catch (e) {
|
|
22106
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22107
|
+
}
|
|
22108
|
+
});
|
|
22109
|
+
}
|
|
22110
|
+
if (shouldRegisterTool("import_template")) {
|
|
22111
|
+
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 }) => {
|
|
22112
|
+
try {
|
|
22113
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
22114
|
+
const parsed = JSON.parse(json);
|
|
22115
|
+
const t = importTemplate2(parsed);
|
|
22116
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
22117
|
+
} catch (e) {
|
|
22118
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22119
|
+
}
|
|
22120
|
+
});
|
|
22121
|
+
}
|
|
22122
|
+
if (shouldRegisterTool("template_history")) {
|
|
22123
|
+
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 }) => {
|
|
22124
|
+
try {
|
|
22125
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
22126
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
22127
|
+
const template = getTemplate2(resolvedId);
|
|
22128
|
+
if (!template)
|
|
22129
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
22130
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
22131
|
+
if (versions.length === 0)
|
|
22132
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
22133
|
+
const lines = versions.map((v) => {
|
|
22134
|
+
const snap = JSON.parse(v.snapshot);
|
|
22135
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
22136
|
+
});
|
|
22137
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
22138
|
+
${lines.join(`
|
|
22139
|
+
`)}` }] };
|
|
22140
|
+
} catch (e) {
|
|
22141
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22142
|
+
}
|
|
22143
|
+
});
|
|
22144
|
+
}
|
|
22145
|
+
}
|
|
22146
|
+
|
|
22147
|
+
// src/mcp/tools/environment-snapshots.ts
|
|
22148
|
+
function registerEnvironmentSnapshotTools(server, { shouldRegisterTool, formatError }) {
|
|
22149
|
+
if (shouldRegisterTool("capture_environment_snapshot")) {
|
|
22150
|
+
server.tool("capture_environment_snapshot", "Capture a local reproducible environment snapshot with Bun/node versions, package-manager state, git status, config hashes, command metadata, and redacted manifests. Optionally attach it to a local task or run.", {
|
|
22151
|
+
root: exports_external.string().optional(),
|
|
22152
|
+
task_id: exports_external.string().optional(),
|
|
22153
|
+
run_id: exports_external.string().optional(),
|
|
22154
|
+
agent_id: exports_external.string().optional(),
|
|
22155
|
+
command: exports_external.string().optional(),
|
|
22156
|
+
output_path: exports_external.string().optional(),
|
|
22157
|
+
include_env_values: exports_external.boolean().optional()
|
|
22158
|
+
}, async (params) => {
|
|
22159
|
+
try {
|
|
22160
|
+
const { recordEnvironmentSnapshot: recordEnvironmentSnapshot2 } = await Promise.resolve().then(() => (init_environment_snapshots(), exports_environment_snapshots));
|
|
22161
|
+
const result = recordEnvironmentSnapshot2(params);
|
|
22162
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
22163
|
+
} catch (e) {
|
|
22164
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22165
|
+
}
|
|
22166
|
+
});
|
|
22167
|
+
}
|
|
22168
|
+
if (shouldRegisterTool("compare_environment_snapshots")) {
|
|
22169
|
+
server.tool("compare_environment_snapshots", "Compare two local environment snapshot JSON files and report runtime, git, manifest, lockfile, and config hash drift.", {
|
|
22170
|
+
left_path: exports_external.string(),
|
|
22171
|
+
right_path: exports_external.string()
|
|
22172
|
+
}, async ({ left_path, right_path }) => {
|
|
22173
|
+
try {
|
|
22174
|
+
const { compareEnvironmentSnapshotFiles: compareEnvironmentSnapshotFiles2 } = await Promise.resolve().then(() => (init_environment_snapshots(), exports_environment_snapshots));
|
|
22175
|
+
return { content: [{ type: "text", text: JSON.stringify(compareEnvironmentSnapshotFiles2(left_path, right_path), null, 2) }] };
|
|
22176
|
+
} catch (e) {
|
|
22177
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
22178
|
+
}
|
|
22179
|
+
});
|
|
22180
|
+
}
|
|
22181
|
+
}
|
|
22182
|
+
|
|
20919
22183
|
// src/lib/package-version.ts
|
|
20920
|
-
import { existsSync as
|
|
20921
|
-
import { dirname as
|
|
22184
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
22185
|
+
import { dirname as dirname8, join as join8 } from "path";
|
|
20922
22186
|
import { fileURLToPath } from "url";
|
|
20923
22187
|
function getPackageVersion(fromUrl = import.meta.url) {
|
|
20924
22188
|
try {
|
|
20925
|
-
let dir =
|
|
22189
|
+
let dir = dirname8(fileURLToPath(fromUrl));
|
|
20926
22190
|
for (let i = 0;i < 5; i++) {
|
|
20927
|
-
const pkgPath =
|
|
20928
|
-
if (
|
|
20929
|
-
return JSON.parse(
|
|
22191
|
+
const pkgPath = join8(dir, "package.json");
|
|
22192
|
+
if (existsSync9(pkgPath)) {
|
|
22193
|
+
return JSON.parse(readFileSync7(pkgPath, "utf-8")).version || "0.0.0";
|
|
20930
22194
|
}
|
|
20931
|
-
const parent =
|
|
22195
|
+
const parent = dirname8(dir);
|
|
20932
22196
|
if (parent === dir)
|
|
20933
22197
|
break;
|
|
20934
22198
|
dir = parent;
|
|
@@ -21108,6 +22372,8 @@ registerTaskResources(server, toolContext);
|
|
|
21108
22372
|
registerTaskRelTools(server, toolContext);
|
|
21109
22373
|
registerCodeTools(server, toolContext);
|
|
21110
22374
|
registerAgentTools(server, { ...toolContext, agentFocusMap });
|
|
22375
|
+
registerTemplateTools(server, toolContext);
|
|
22376
|
+
registerEnvironmentSnapshotTools(server, toolContext);
|
|
21111
22377
|
registerMachineTools(server, { shouldRegisterTool, formatError });
|
|
21112
22378
|
registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError });
|
|
21113
22379
|
async function main() {
|