@hasna/todos 0.11.9 → 0.11.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +966 -48
- package/dist/db/builtin-templates.d.ts +22 -0
- package/dist/db/builtin-templates.d.ts.map +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/templates.d.ts +80 -1
- package/dist/db/templates.d.ts.map +1 -1
- package/dist/db/webhooks.d.ts +11 -0
- package/dist/db/webhooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +642 -20
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +824 -43
- package/dist/server/index.js +533 -25
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +54 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -2290,6 +2290,20 @@ function ensureSchema(db) {
|
|
|
2290
2290
|
metadata TEXT DEFAULT '{}',
|
|
2291
2291
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2292
2292
|
)`);
|
|
2293
|
+
ensureTable("template_tasks", `
|
|
2294
|
+
CREATE TABLE template_tasks (
|
|
2295
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
2296
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
2297
|
+
position INTEGER NOT NULL,
|
|
2298
|
+
title_pattern TEXT NOT NULL,
|
|
2299
|
+
description TEXT,
|
|
2300
|
+
priority TEXT DEFAULT 'medium',
|
|
2301
|
+
tags TEXT DEFAULT '[]',
|
|
2302
|
+
task_type TEXT,
|
|
2303
|
+
depends_on_positions TEXT DEFAULT '[]',
|
|
2304
|
+
metadata TEXT DEFAULT '{}',
|
|
2305
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2306
|
+
)`);
|
|
2293
2307
|
ensureTable("task_checklists", `
|
|
2294
2308
|
CREATE TABLE task_checklists (
|
|
2295
2309
|
id TEXT PRIMARY KEY,
|
|
@@ -2374,6 +2388,36 @@ function ensureSchema(db) {
|
|
|
2374
2388
|
ensureColumn("projects", "org_id", "TEXT");
|
|
2375
2389
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
2376
2390
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
2391
|
+
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
2392
|
+
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
2393
|
+
ensureColumn("template_tasks", "condition", "TEXT");
|
|
2394
|
+
ensureColumn("template_tasks", "include_template_id", "TEXT");
|
|
2395
|
+
ensureTable("template_versions", `
|
|
2396
|
+
CREATE TABLE template_versions (
|
|
2397
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
2398
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
2399
|
+
version INTEGER NOT NULL,
|
|
2400
|
+
snapshot TEXT NOT NULL,
|
|
2401
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2402
|
+
)`);
|
|
2403
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id)");
|
|
2404
|
+
ensureColumn("webhooks", "project_id", "TEXT");
|
|
2405
|
+
ensureColumn("webhooks", "task_list_id", "TEXT");
|
|
2406
|
+
ensureColumn("webhooks", "agent_id", "TEXT");
|
|
2407
|
+
ensureColumn("webhooks", "task_id", "TEXT");
|
|
2408
|
+
ensureTable("webhook_deliveries", `
|
|
2409
|
+
CREATE TABLE webhook_deliveries (
|
|
2410
|
+
id TEXT PRIMARY KEY,
|
|
2411
|
+
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
2412
|
+
event TEXT NOT NULL,
|
|
2413
|
+
payload TEXT NOT NULL,
|
|
2414
|
+
status_code INTEGER,
|
|
2415
|
+
response TEXT,
|
|
2416
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
2417
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2418
|
+
)`);
|
|
2419
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook ON webhook_deliveries(webhook_id)");
|
|
2420
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event ON webhook_deliveries(event)");
|
|
2377
2421
|
ensureColumn("task_comments", "type", "TEXT DEFAULT 'comment'");
|
|
2378
2422
|
ensureColumn("task_comments", "progress_pct", "INTEGER");
|
|
2379
2423
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
|
|
@@ -2400,6 +2444,7 @@ function ensureSchema(db) {
|
|
|
2400
2444
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_source ON task_relationships(source_task_id)");
|
|
2401
2445
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_target ON task_relationships(target_task_id)");
|
|
2402
2446
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_type ON task_relationships(relationship_type)");
|
|
2447
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id)");
|
|
2403
2448
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_source ON kg_edges(source_id, source_type)");
|
|
2404
2449
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_target ON kg_edges(target_id, target_type)");
|
|
2405
2450
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_kg_relation ON kg_edges(relation_type)");
|
|
@@ -3043,6 +3088,44 @@ var init_database = __esm(() => {
|
|
|
3043
3088
|
);
|
|
3044
3089
|
|
|
3045
3090
|
INSERT OR IGNORE INTO _migrations (id) VALUES (36);
|
|
3091
|
+
`,
|
|
3092
|
+
`
|
|
3093
|
+
CREATE TABLE IF NOT EXISTS template_tasks (
|
|
3094
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
3095
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
3096
|
+
position INTEGER NOT NULL,
|
|
3097
|
+
title_pattern TEXT NOT NULL,
|
|
3098
|
+
description TEXT,
|
|
3099
|
+
priority TEXT DEFAULT 'medium',
|
|
3100
|
+
tags TEXT DEFAULT '[]',
|
|
3101
|
+
task_type TEXT,
|
|
3102
|
+
depends_on_positions TEXT DEFAULT '[]',
|
|
3103
|
+
metadata TEXT DEFAULT '{}',
|
|
3104
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3105
|
+
);
|
|
3106
|
+
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
3107
|
+
|
|
3108
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (37);
|
|
3109
|
+
`,
|
|
3110
|
+
`
|
|
3111
|
+
ALTER TABLE task_templates ADD COLUMN variables TEXT DEFAULT '[]';
|
|
3112
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
3113
|
+
`,
|
|
3114
|
+
`
|
|
3115
|
+
ALTER TABLE template_tasks ADD COLUMN condition TEXT;
|
|
3116
|
+
ALTER TABLE template_tasks ADD COLUMN include_template_id TEXT;
|
|
3117
|
+
ALTER TABLE task_templates ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
|
|
3118
|
+
|
|
3119
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
3120
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
3121
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
3122
|
+
version INTEGER NOT NULL,
|
|
3123
|
+
snapshot TEXT NOT NULL,
|
|
3124
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3125
|
+
);
|
|
3126
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
3127
|
+
|
|
3128
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (39);
|
|
3046
3129
|
`
|
|
3047
3130
|
];
|
|
3048
3131
|
});
|
|
@@ -3680,18 +3763,37 @@ var init_recurrence = __esm(() => {
|
|
|
3680
3763
|
var exports_webhooks = {};
|
|
3681
3764
|
__export(exports_webhooks, {
|
|
3682
3765
|
listWebhooks: () => listWebhooks,
|
|
3766
|
+
listDeliveries: () => listDeliveries,
|
|
3683
3767
|
getWebhook: () => getWebhook,
|
|
3684
3768
|
dispatchWebhook: () => dispatchWebhook,
|
|
3685
3769
|
deleteWebhook: () => deleteWebhook,
|
|
3686
3770
|
createWebhook: () => createWebhook
|
|
3687
3771
|
});
|
|
3688
3772
|
function rowToWebhook(row) {
|
|
3689
|
-
return {
|
|
3773
|
+
return {
|
|
3774
|
+
...row,
|
|
3775
|
+
events: JSON.parse(row.events || "[]"),
|
|
3776
|
+
active: !!row.active,
|
|
3777
|
+
project_id: row.project_id || null,
|
|
3778
|
+
task_list_id: row.task_list_id || null,
|
|
3779
|
+
agent_id: row.agent_id || null,
|
|
3780
|
+
task_id: row.task_id || null
|
|
3781
|
+
};
|
|
3690
3782
|
}
|
|
3691
3783
|
function createWebhook(input, db) {
|
|
3692
3784
|
const d = db || getDatabase();
|
|
3693
3785
|
const id = uuid();
|
|
3694
|
-
d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?,
|
|
3786
|
+
d.run(`INSERT INTO webhooks (id, url, events, secret, project_id, task_list_id, agent_id, task_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3787
|
+
id,
|
|
3788
|
+
input.url,
|
|
3789
|
+
JSON.stringify(input.events || []),
|
|
3790
|
+
input.secret || null,
|
|
3791
|
+
input.project_id || null,
|
|
3792
|
+
input.task_list_id || null,
|
|
3793
|
+
input.agent_id || null,
|
|
3794
|
+
input.task_id || null,
|
|
3795
|
+
now()
|
|
3796
|
+
]);
|
|
3695
3797
|
return getWebhook(id, d);
|
|
3696
3798
|
}
|
|
3697
3799
|
function getWebhook(id, db) {
|
|
@@ -3707,22 +3809,69 @@ function deleteWebhook(id, db) {
|
|
|
3707
3809
|
const d = db || getDatabase();
|
|
3708
3810
|
return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
|
|
3709
3811
|
}
|
|
3812
|
+
function listDeliveries(webhookId, limit = 50, db) {
|
|
3813
|
+
const d = db || getDatabase();
|
|
3814
|
+
if (webhookId) {
|
|
3815
|
+
return d.query("SELECT * FROM webhook_deliveries WHERE webhook_id = ? ORDER BY created_at DESC LIMIT ?").all(webhookId, limit);
|
|
3816
|
+
}
|
|
3817
|
+
return d.query("SELECT * FROM webhook_deliveries ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
3818
|
+
}
|
|
3819
|
+
function logDelivery(d, webhookId, event, payload, statusCode, response, attempt) {
|
|
3820
|
+
const id = uuid();
|
|
3821
|
+
d.run(`INSERT INTO webhook_deliveries (id, webhook_id, event, payload, status_code, response, attempt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, webhookId, event, payload, statusCode, response, attempt, now()]);
|
|
3822
|
+
}
|
|
3823
|
+
function matchesScope(wh, payload) {
|
|
3824
|
+
if (wh.project_id && payload.project_id !== wh.project_id)
|
|
3825
|
+
return false;
|
|
3826
|
+
if (wh.task_list_id && payload.task_list_id !== wh.task_list_id)
|
|
3827
|
+
return false;
|
|
3828
|
+
if (wh.agent_id && payload.agent_id !== wh.agent_id && payload.assigned_to !== wh.agent_id)
|
|
3829
|
+
return false;
|
|
3830
|
+
if (wh.task_id && payload.id !== wh.task_id)
|
|
3831
|
+
return false;
|
|
3832
|
+
return true;
|
|
3833
|
+
}
|
|
3834
|
+
async function deliverWebhook(wh, event, body, attempt, db) {
|
|
3835
|
+
try {
|
|
3836
|
+
const headers = { "Content-Type": "application/json" };
|
|
3837
|
+
if (wh.secret) {
|
|
3838
|
+
const encoder = new TextEncoder;
|
|
3839
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
3840
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
3841
|
+
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3842
|
+
}
|
|
3843
|
+
const resp = await fetch(wh.url, { method: "POST", headers, body });
|
|
3844
|
+
const respText = await resp.text().catch(() => "");
|
|
3845
|
+
logDelivery(db, wh.id, event, body, resp.status, respText.slice(0, 1000), attempt);
|
|
3846
|
+
if (resp.status >= 400 && attempt < MAX_RETRY_ATTEMPTS) {
|
|
3847
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
3848
|
+
setTimeout(() => {
|
|
3849
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
3850
|
+
}, delay);
|
|
3851
|
+
}
|
|
3852
|
+
} catch (err) {
|
|
3853
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3854
|
+
logDelivery(db, wh.id, event, body, null, errorMsg.slice(0, 1000), attempt);
|
|
3855
|
+
if (attempt < MAX_RETRY_ATTEMPTS) {
|
|
3856
|
+
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
3857
|
+
setTimeout(() => {
|
|
3858
|
+
deliverWebhook(wh, event, body, attempt + 1, db).catch(() => {});
|
|
3859
|
+
}, delay);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3710
3863
|
async function dispatchWebhook(event, payload, db) {
|
|
3711
|
-
const
|
|
3864
|
+
const d = db || getDatabase();
|
|
3865
|
+
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
3866
|
+
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
3712
3867
|
for (const wh of webhooks) {
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
const encoder = new TextEncoder;
|
|
3718
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
3719
|
-
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
|
|
3720
|
-
headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3721
|
-
}
|
|
3722
|
-
fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
|
|
3723
|
-
} catch {}
|
|
3868
|
+
if (!matchesScope(wh, payloadObj))
|
|
3869
|
+
continue;
|
|
3870
|
+
const body = JSON.stringify({ event, payload, timestamp: now() });
|
|
3871
|
+
deliverWebhook(wh, event, body, 1, d).catch(() => {});
|
|
3724
3872
|
}
|
|
3725
3873
|
}
|
|
3874
|
+
var MAX_RETRY_ATTEMPTS = 3, RETRY_BASE_DELAY_MS = 1000;
|
|
3726
3875
|
var init_webhooks = __esm(() => {
|
|
3727
3876
|
init_database();
|
|
3728
3877
|
});
|
|
@@ -3731,18 +3880,42 @@ var init_webhooks = __esm(() => {
|
|
|
3731
3880
|
var exports_templates = {};
|
|
3732
3881
|
__export(exports_templates, {
|
|
3733
3882
|
updateTemplate: () => updateTemplate,
|
|
3883
|
+
tasksFromTemplate: () => tasksFromTemplate,
|
|
3734
3884
|
taskFromTemplate: () => taskFromTemplate,
|
|
3885
|
+
resolveVariables: () => resolveVariables,
|
|
3886
|
+
previewTemplate: () => previewTemplate,
|
|
3735
3887
|
listTemplates: () => listTemplates,
|
|
3888
|
+
listTemplateVersions: () => listTemplateVersions,
|
|
3889
|
+
importTemplate: () => importTemplate,
|
|
3890
|
+
getTemplateWithTasks: () => getTemplateWithTasks,
|
|
3891
|
+
getTemplateVersion: () => getTemplateVersion,
|
|
3892
|
+
getTemplateTasks: () => getTemplateTasks,
|
|
3736
3893
|
getTemplate: () => getTemplate,
|
|
3894
|
+
exportTemplate: () => exportTemplate,
|
|
3895
|
+
evaluateCondition: () => evaluateCondition,
|
|
3737
3896
|
deleteTemplate: () => deleteTemplate,
|
|
3738
|
-
createTemplate: () => createTemplate
|
|
3897
|
+
createTemplate: () => createTemplate,
|
|
3898
|
+
addTemplateTasks: () => addTemplateTasks
|
|
3739
3899
|
});
|
|
3740
3900
|
function rowToTemplate(row) {
|
|
3741
3901
|
return {
|
|
3742
3902
|
...row,
|
|
3743
3903
|
tags: JSON.parse(row.tags || "[]"),
|
|
3904
|
+
variables: JSON.parse(row.variables || "[]"),
|
|
3905
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
3906
|
+
priority: row.priority || "medium",
|
|
3907
|
+
version: row.version ?? 1
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
function rowToTemplateTask(row) {
|
|
3911
|
+
return {
|
|
3912
|
+
...row,
|
|
3913
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
3914
|
+
depends_on_positions: JSON.parse(row.depends_on_positions || "[]"),
|
|
3744
3915
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
3745
|
-
priority: row.priority || "medium"
|
|
3916
|
+
priority: row.priority || "medium",
|
|
3917
|
+
condition: row.condition ?? null,
|
|
3918
|
+
include_template_id: row.include_template_id ?? null
|
|
3746
3919
|
};
|
|
3747
3920
|
}
|
|
3748
3921
|
function resolveTemplateId(id, d) {
|
|
@@ -3751,19 +3924,23 @@ function resolveTemplateId(id, d) {
|
|
|
3751
3924
|
function createTemplate(input, db) {
|
|
3752
3925
|
const d = db || getDatabase();
|
|
3753
3926
|
const id = uuid();
|
|
3754
|
-
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
|
|
3755
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3927
|
+
d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, variables, project_id, plan_id, metadata, created_at)
|
|
3928
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3756
3929
|
id,
|
|
3757
3930
|
input.name,
|
|
3758
3931
|
input.title_pattern,
|
|
3759
3932
|
input.description || null,
|
|
3760
3933
|
input.priority || "medium",
|
|
3761
3934
|
JSON.stringify(input.tags || []),
|
|
3935
|
+
JSON.stringify(input.variables || []),
|
|
3762
3936
|
input.project_id || null,
|
|
3763
3937
|
input.plan_id || null,
|
|
3764
3938
|
JSON.stringify(input.metadata || {}),
|
|
3765
3939
|
now()
|
|
3766
3940
|
]);
|
|
3941
|
+
if (input.tasks && input.tasks.length > 0) {
|
|
3942
|
+
addTemplateTasks(id, input.tasks, d);
|
|
3943
|
+
}
|
|
3767
3944
|
return getTemplate(id, d);
|
|
3768
3945
|
}
|
|
3769
3946
|
function getTemplate(id, db) {
|
|
@@ -3790,7 +3967,23 @@ function updateTemplate(id, updates, db) {
|
|
|
3790
3967
|
const resolved = resolveTemplateId(id, d);
|
|
3791
3968
|
if (!resolved)
|
|
3792
3969
|
return null;
|
|
3793
|
-
const
|
|
3970
|
+
const current = getTemplateWithTasks(resolved, d);
|
|
3971
|
+
if (current) {
|
|
3972
|
+
const snapshot = JSON.stringify({
|
|
3973
|
+
name: current.name,
|
|
3974
|
+
title_pattern: current.title_pattern,
|
|
3975
|
+
description: current.description,
|
|
3976
|
+
priority: current.priority,
|
|
3977
|
+
tags: current.tags,
|
|
3978
|
+
variables: current.variables,
|
|
3979
|
+
project_id: current.project_id,
|
|
3980
|
+
plan_id: current.plan_id,
|
|
3981
|
+
metadata: current.metadata,
|
|
3982
|
+
tasks: current.tasks
|
|
3983
|
+
});
|
|
3984
|
+
d.run(`INSERT INTO template_versions (id, template_id, version, snapshot, created_at) VALUES (?, ?, ?, ?, ?)`, [uuid(), resolved, current.version, snapshot, now()]);
|
|
3985
|
+
}
|
|
3986
|
+
const sets = ["version = version + 1"];
|
|
3794
3987
|
const values = [];
|
|
3795
3988
|
if (updates.name !== undefined) {
|
|
3796
3989
|
sets.push("name = ?");
|
|
@@ -3812,6 +4005,10 @@ function updateTemplate(id, updates, db) {
|
|
|
3812
4005
|
sets.push("tags = ?");
|
|
3813
4006
|
values.push(JSON.stringify(updates.tags));
|
|
3814
4007
|
}
|
|
4008
|
+
if (updates.variables !== undefined) {
|
|
4009
|
+
sets.push("variables = ?");
|
|
4010
|
+
values.push(JSON.stringify(updates.variables));
|
|
4011
|
+
}
|
|
3815
4012
|
if (updates.project_id !== undefined) {
|
|
3816
4013
|
sets.push("project_id = ?");
|
|
3817
4014
|
values.push(updates.project_id);
|
|
@@ -3824,8 +4021,6 @@ function updateTemplate(id, updates, db) {
|
|
|
3824
4021
|
sets.push("metadata = ?");
|
|
3825
4022
|
values.push(JSON.stringify(updates.metadata));
|
|
3826
4023
|
}
|
|
3827
|
-
if (sets.length === 0)
|
|
3828
|
-
return getTemplate(resolved, d);
|
|
3829
4024
|
values.push(resolved);
|
|
3830
4025
|
d.run(`UPDATE task_templates SET ${sets.join(", ")} WHERE id = ?`, values);
|
|
3831
4026
|
return getTemplate(resolved, d);
|
|
@@ -3845,8 +4040,294 @@ function taskFromTemplate(templateId, overrides = {}, db) {
|
|
|
3845
4040
|
...overrides
|
|
3846
4041
|
};
|
|
3847
4042
|
}
|
|
4043
|
+
function addTemplateTasks(templateId, tasks, db) {
|
|
4044
|
+
const d = db || getDatabase();
|
|
4045
|
+
const template = getTemplate(templateId, d);
|
|
4046
|
+
if (!template)
|
|
4047
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4048
|
+
d.run("DELETE FROM template_tasks WHERE template_id = ?", [templateId]);
|
|
4049
|
+
const results = [];
|
|
4050
|
+
for (let i = 0;i < tasks.length; i++) {
|
|
4051
|
+
const task = tasks[i];
|
|
4052
|
+
const id = uuid();
|
|
4053
|
+
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)
|
|
4054
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4055
|
+
id,
|
|
4056
|
+
templateId,
|
|
4057
|
+
i,
|
|
4058
|
+
task.title_pattern,
|
|
4059
|
+
task.description || null,
|
|
4060
|
+
task.priority || "medium",
|
|
4061
|
+
JSON.stringify(task.tags || []),
|
|
4062
|
+
task.task_type || null,
|
|
4063
|
+
task.condition || null,
|
|
4064
|
+
task.include_template_id || null,
|
|
4065
|
+
JSON.stringify(task.depends_on || []),
|
|
4066
|
+
JSON.stringify(task.metadata || {}),
|
|
4067
|
+
now()
|
|
4068
|
+
]);
|
|
4069
|
+
const row = d.query("SELECT * FROM template_tasks WHERE id = ?").get(id);
|
|
4070
|
+
if (row)
|
|
4071
|
+
results.push(rowToTemplateTask(row));
|
|
4072
|
+
}
|
|
4073
|
+
return results;
|
|
4074
|
+
}
|
|
4075
|
+
function getTemplateWithTasks(id, db) {
|
|
4076
|
+
const d = db || getDatabase();
|
|
4077
|
+
const template = getTemplate(id, d);
|
|
4078
|
+
if (!template)
|
|
4079
|
+
return null;
|
|
4080
|
+
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(template.id);
|
|
4081
|
+
const tasks = rows.map(rowToTemplateTask);
|
|
4082
|
+
return { ...template, tasks };
|
|
4083
|
+
}
|
|
4084
|
+
function getTemplateTasks(templateId, db) {
|
|
4085
|
+
const d = db || getDatabase();
|
|
4086
|
+
const resolved = resolveTemplateId(templateId, d);
|
|
4087
|
+
if (!resolved)
|
|
4088
|
+
return [];
|
|
4089
|
+
const rows = d.query("SELECT * FROM template_tasks WHERE template_id = ? ORDER BY position").all(resolved);
|
|
4090
|
+
return rows.map(rowToTemplateTask);
|
|
4091
|
+
}
|
|
4092
|
+
function evaluateCondition(condition, variables) {
|
|
4093
|
+
if (!condition || condition.trim() === "")
|
|
4094
|
+
return true;
|
|
4095
|
+
const trimmed = condition.trim();
|
|
4096
|
+
const eqMatch = trimmed.match(/^\{([^}]+)\}\s*==\s*(.+)$/);
|
|
4097
|
+
if (eqMatch) {
|
|
4098
|
+
const varName = eqMatch[1];
|
|
4099
|
+
const expected = eqMatch[2].trim();
|
|
4100
|
+
return (variables[varName] ?? "") === expected;
|
|
4101
|
+
}
|
|
4102
|
+
const neqMatch = trimmed.match(/^\{([^}]+)\}\s*!=\s*(.+)$/);
|
|
4103
|
+
if (neqMatch) {
|
|
4104
|
+
const varName = neqMatch[1];
|
|
4105
|
+
const expected = neqMatch[2].trim();
|
|
4106
|
+
return (variables[varName] ?? "") !== expected;
|
|
4107
|
+
}
|
|
4108
|
+
const falsyMatch = trimmed.match(/^!\{([^}]+)\}$/);
|
|
4109
|
+
if (falsyMatch) {
|
|
4110
|
+
const varName = falsyMatch[1];
|
|
4111
|
+
const val = variables[varName];
|
|
4112
|
+
return !val || val === "" || val === "false";
|
|
4113
|
+
}
|
|
4114
|
+
const truthyMatch = trimmed.match(/^\{([^}]+)\}$/);
|
|
4115
|
+
if (truthyMatch) {
|
|
4116
|
+
const varName = truthyMatch[1];
|
|
4117
|
+
const val = variables[varName];
|
|
4118
|
+
return !!val && val !== "" && val !== "false";
|
|
4119
|
+
}
|
|
4120
|
+
return true;
|
|
4121
|
+
}
|
|
4122
|
+
function exportTemplate(id, db) {
|
|
4123
|
+
const d = db || getDatabase();
|
|
4124
|
+
const template = getTemplateWithTasks(id, d);
|
|
4125
|
+
if (!template)
|
|
4126
|
+
throw new Error(`Template not found: ${id}`);
|
|
4127
|
+
return {
|
|
4128
|
+
name: template.name,
|
|
4129
|
+
title_pattern: template.title_pattern,
|
|
4130
|
+
description: template.description,
|
|
4131
|
+
priority: template.priority,
|
|
4132
|
+
tags: template.tags,
|
|
4133
|
+
variables: template.variables,
|
|
4134
|
+
project_id: template.project_id,
|
|
4135
|
+
plan_id: template.plan_id,
|
|
4136
|
+
metadata: template.metadata,
|
|
4137
|
+
tasks: template.tasks.map((t) => ({
|
|
4138
|
+
position: t.position,
|
|
4139
|
+
title_pattern: t.title_pattern,
|
|
4140
|
+
description: t.description,
|
|
4141
|
+
priority: t.priority,
|
|
4142
|
+
tags: t.tags,
|
|
4143
|
+
task_type: t.task_type,
|
|
4144
|
+
condition: t.condition,
|
|
4145
|
+
include_template_id: t.include_template_id,
|
|
4146
|
+
depends_on_positions: t.depends_on_positions,
|
|
4147
|
+
metadata: t.metadata
|
|
4148
|
+
}))
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
function importTemplate(json, db) {
|
|
4152
|
+
const d = db || getDatabase();
|
|
4153
|
+
const taskInputs = (json.tasks || []).map((t) => ({
|
|
4154
|
+
title_pattern: t.title_pattern,
|
|
4155
|
+
description: t.description ?? undefined,
|
|
4156
|
+
priority: t.priority,
|
|
4157
|
+
tags: t.tags,
|
|
4158
|
+
task_type: t.task_type ?? undefined,
|
|
4159
|
+
condition: t.condition ?? undefined,
|
|
4160
|
+
include_template_id: t.include_template_id ?? undefined,
|
|
4161
|
+
depends_on: t.depends_on_positions,
|
|
4162
|
+
metadata: t.metadata
|
|
4163
|
+
}));
|
|
4164
|
+
return createTemplate({
|
|
4165
|
+
name: json.name,
|
|
4166
|
+
title_pattern: json.title_pattern,
|
|
4167
|
+
description: json.description ?? undefined,
|
|
4168
|
+
priority: json.priority,
|
|
4169
|
+
tags: json.tags,
|
|
4170
|
+
variables: json.variables,
|
|
4171
|
+
project_id: json.project_id ?? undefined,
|
|
4172
|
+
plan_id: json.plan_id ?? undefined,
|
|
4173
|
+
metadata: json.metadata,
|
|
4174
|
+
tasks: taskInputs
|
|
4175
|
+
}, d);
|
|
4176
|
+
}
|
|
4177
|
+
function getTemplateVersion(id, version, db) {
|
|
4178
|
+
const d = db || getDatabase();
|
|
4179
|
+
const resolved = resolveTemplateId(id, d);
|
|
4180
|
+
if (!resolved)
|
|
4181
|
+
return null;
|
|
4182
|
+
const row = d.query("SELECT * FROM template_versions WHERE template_id = ? AND version = ?").get(resolved, version);
|
|
4183
|
+
return row || null;
|
|
4184
|
+
}
|
|
4185
|
+
function listTemplateVersions(id, db) {
|
|
4186
|
+
const d = db || getDatabase();
|
|
4187
|
+
const resolved = resolveTemplateId(id, d);
|
|
4188
|
+
if (!resolved)
|
|
4189
|
+
return [];
|
|
4190
|
+
return d.query("SELECT * FROM template_versions WHERE template_id = ? ORDER BY version DESC").all(resolved);
|
|
4191
|
+
}
|
|
4192
|
+
function resolveVariables(templateVars, provided) {
|
|
4193
|
+
const merged = { ...provided };
|
|
4194
|
+
for (const v of templateVars) {
|
|
4195
|
+
if (merged[v.name] === undefined && v.default !== undefined) {
|
|
4196
|
+
merged[v.name] = v.default;
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
const missing = [];
|
|
4200
|
+
for (const v of templateVars) {
|
|
4201
|
+
if (v.required && merged[v.name] === undefined) {
|
|
4202
|
+
missing.push(v.name);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (missing.length > 0) {
|
|
4206
|
+
throw new Error(`Missing required template variable(s): ${missing.join(", ")}`);
|
|
4207
|
+
}
|
|
4208
|
+
return merged;
|
|
4209
|
+
}
|
|
4210
|
+
function substituteVars(text, variables) {
|
|
4211
|
+
let result = text;
|
|
4212
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
4213
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), val);
|
|
4214
|
+
}
|
|
4215
|
+
return result;
|
|
4216
|
+
}
|
|
4217
|
+
function tasksFromTemplate(templateId, projectId, variables, taskListId, db, _visitedTemplateIds) {
|
|
4218
|
+
const d = db || getDatabase();
|
|
4219
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
4220
|
+
if (!template)
|
|
4221
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4222
|
+
const visited = _visitedTemplateIds || new Set;
|
|
4223
|
+
if (visited.has(template.id)) {
|
|
4224
|
+
throw new Error(`Circular template reference detected: ${template.id}`);
|
|
4225
|
+
}
|
|
4226
|
+
visited.add(template.id);
|
|
4227
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
4228
|
+
if (template.tasks.length === 0) {
|
|
4229
|
+
const input = taskFromTemplate(templateId, { project_id: projectId, task_list_id: taskListId }, d);
|
|
4230
|
+
const task = createTask(input, d);
|
|
4231
|
+
return [task];
|
|
4232
|
+
}
|
|
4233
|
+
const createdTasks = [];
|
|
4234
|
+
const positionToId = new Map;
|
|
4235
|
+
const skippedPositions = new Set;
|
|
4236
|
+
for (const tt of template.tasks) {
|
|
4237
|
+
if (tt.include_template_id) {
|
|
4238
|
+
const includedTasks = tasksFromTemplate(tt.include_template_id, projectId, resolved, taskListId, d, visited);
|
|
4239
|
+
createdTasks.push(...includedTasks);
|
|
4240
|
+
if (includedTasks.length > 0) {
|
|
4241
|
+
positionToId.set(tt.position, includedTasks[0].id);
|
|
4242
|
+
} else {
|
|
4243
|
+
skippedPositions.add(tt.position);
|
|
4244
|
+
}
|
|
4245
|
+
continue;
|
|
4246
|
+
}
|
|
4247
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved)) {
|
|
4248
|
+
skippedPositions.add(tt.position);
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
let title = tt.title_pattern;
|
|
4252
|
+
let desc = tt.description;
|
|
4253
|
+
title = substituteVars(title, resolved);
|
|
4254
|
+
if (desc)
|
|
4255
|
+
desc = substituteVars(desc, resolved);
|
|
4256
|
+
const task = createTask({
|
|
4257
|
+
title,
|
|
4258
|
+
description: desc ?? undefined,
|
|
4259
|
+
priority: tt.priority,
|
|
4260
|
+
tags: tt.tags,
|
|
4261
|
+
task_type: tt.task_type ?? undefined,
|
|
4262
|
+
project_id: projectId,
|
|
4263
|
+
task_list_id: taskListId,
|
|
4264
|
+
metadata: tt.metadata
|
|
4265
|
+
}, d);
|
|
4266
|
+
createdTasks.push(task);
|
|
4267
|
+
positionToId.set(tt.position, task.id);
|
|
4268
|
+
}
|
|
4269
|
+
for (const tt of template.tasks) {
|
|
4270
|
+
if (skippedPositions.has(tt.position))
|
|
4271
|
+
continue;
|
|
4272
|
+
if (tt.include_template_id)
|
|
4273
|
+
continue;
|
|
4274
|
+
const deps = tt.depends_on_positions;
|
|
4275
|
+
for (const depPos of deps) {
|
|
4276
|
+
if (skippedPositions.has(depPos))
|
|
4277
|
+
continue;
|
|
4278
|
+
const taskId = positionToId.get(tt.position);
|
|
4279
|
+
const depId = positionToId.get(depPos);
|
|
4280
|
+
if (taskId && depId) {
|
|
4281
|
+
addDependency(taskId, depId, d);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
return createdTasks;
|
|
4286
|
+
}
|
|
4287
|
+
function previewTemplate(templateId, variables, db) {
|
|
4288
|
+
const d = db || getDatabase();
|
|
4289
|
+
const template = getTemplateWithTasks(templateId, d);
|
|
4290
|
+
if (!template)
|
|
4291
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
4292
|
+
const resolved = resolveVariables(template.variables, variables);
|
|
4293
|
+
const tasks = [];
|
|
4294
|
+
if (template.tasks.length === 0) {
|
|
4295
|
+
tasks.push({
|
|
4296
|
+
position: 0,
|
|
4297
|
+
title: substituteVars(template.title_pattern, resolved),
|
|
4298
|
+
description: template.description ? substituteVars(template.description, resolved) : null,
|
|
4299
|
+
priority: template.priority,
|
|
4300
|
+
tags: template.tags,
|
|
4301
|
+
task_type: null,
|
|
4302
|
+
depends_on_positions: []
|
|
4303
|
+
});
|
|
4304
|
+
} else {
|
|
4305
|
+
for (const tt of template.tasks) {
|
|
4306
|
+
if (tt.condition && !evaluateCondition(tt.condition, resolved))
|
|
4307
|
+
continue;
|
|
4308
|
+
tasks.push({
|
|
4309
|
+
position: tt.position,
|
|
4310
|
+
title: substituteVars(tt.title_pattern, resolved),
|
|
4311
|
+
description: tt.description ? substituteVars(tt.description, resolved) : null,
|
|
4312
|
+
priority: tt.priority,
|
|
4313
|
+
tags: tt.tags,
|
|
4314
|
+
task_type: tt.task_type,
|
|
4315
|
+
depends_on_positions: tt.depends_on_positions
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
return {
|
|
4320
|
+
template_id: template.id,
|
|
4321
|
+
template_name: template.name,
|
|
4322
|
+
description: template.description,
|
|
4323
|
+
variables: template.variables,
|
|
4324
|
+
resolved_variables: resolved,
|
|
4325
|
+
tasks
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
3848
4328
|
var init_templates = __esm(() => {
|
|
3849
4329
|
init_database();
|
|
4330
|
+
init_tasks();
|
|
3850
4331
|
});
|
|
3851
4332
|
|
|
3852
4333
|
// src/db/checklists.ts
|
|
@@ -6406,6 +6887,118 @@ var init_task_commits = __esm(() => {
|
|
|
6406
6887
|
init_database();
|
|
6407
6888
|
});
|
|
6408
6889
|
|
|
6890
|
+
// src/db/builtin-templates.ts
|
|
6891
|
+
var exports_builtin_templates = {};
|
|
6892
|
+
__export(exports_builtin_templates, {
|
|
6893
|
+
initBuiltinTemplates: () => initBuiltinTemplates,
|
|
6894
|
+
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
6895
|
+
});
|
|
6896
|
+
function initBuiltinTemplates(db) {
|
|
6897
|
+
const d = db || getDatabase();
|
|
6898
|
+
const existing = listTemplates(d);
|
|
6899
|
+
const existingNames = new Set(existing.map((t) => t.name));
|
|
6900
|
+
let created = 0;
|
|
6901
|
+
let skipped = 0;
|
|
6902
|
+
const names = [];
|
|
6903
|
+
for (const bt of BUILTIN_TEMPLATES) {
|
|
6904
|
+
if (existingNames.has(bt.name)) {
|
|
6905
|
+
skipped++;
|
|
6906
|
+
continue;
|
|
6907
|
+
}
|
|
6908
|
+
const tasks = bt.tasks.map((t) => ({
|
|
6909
|
+
title_pattern: t.title_pattern,
|
|
6910
|
+
description: t.description,
|
|
6911
|
+
priority: t.priority,
|
|
6912
|
+
tags: t.tags,
|
|
6913
|
+
task_type: t.task_type,
|
|
6914
|
+
depends_on: t.depends_on_positions || t.depends_on,
|
|
6915
|
+
metadata: t.metadata
|
|
6916
|
+
}));
|
|
6917
|
+
createTemplate({
|
|
6918
|
+
name: bt.name,
|
|
6919
|
+
title_pattern: `${bt.name}: {${bt.variables[0]?.name || "name"}}`,
|
|
6920
|
+
description: bt.description,
|
|
6921
|
+
variables: bt.variables,
|
|
6922
|
+
tasks
|
|
6923
|
+
}, d);
|
|
6924
|
+
created++;
|
|
6925
|
+
names.push(bt.name);
|
|
6926
|
+
}
|
|
6927
|
+
return { created, skipped, names };
|
|
6928
|
+
}
|
|
6929
|
+
var BUILTIN_TEMPLATES;
|
|
6930
|
+
var init_builtin_templates = __esm(() => {
|
|
6931
|
+
init_database();
|
|
6932
|
+
init_templates();
|
|
6933
|
+
BUILTIN_TEMPLATES = [
|
|
6934
|
+
{
|
|
6935
|
+
name: "open-source-project",
|
|
6936
|
+
description: "Full open-source project bootstrap \u2014 scaffold to publish",
|
|
6937
|
+
variables: [
|
|
6938
|
+
{ name: "name", required: true, description: "Service name" },
|
|
6939
|
+
{ name: "org", required: false, default: "hasna", description: "GitHub org" }
|
|
6940
|
+
],
|
|
6941
|
+
tasks: [
|
|
6942
|
+
{ position: 0, title_pattern: "Scaffold {name} package structure", priority: "critical" },
|
|
6943
|
+
{ position: 1, title_pattern: "Create {name} SQLite database + migrations", priority: "critical", depends_on_positions: [0] },
|
|
6944
|
+
{ position: 2, title_pattern: "Implement {name} CRUD operations", priority: "high", depends_on_positions: [1] },
|
|
6945
|
+
{ position: 3, title_pattern: "Build {name} MCP server with standard tools", priority: "high", depends_on_positions: [2] },
|
|
6946
|
+
{ position: 4, title_pattern: "Build {name} CLI with Commander.js", priority: "high", depends_on_positions: [2] },
|
|
6947
|
+
{ position: 5, title_pattern: "Build {name} REST API", priority: "medium", depends_on_positions: [2] },
|
|
6948
|
+
{ position: 6, title_pattern: "Write unit tests for {name}", priority: "high", depends_on_positions: [2, 3, 4] },
|
|
6949
|
+
{ position: 7, title_pattern: "Add Apache 2.0 license and README", priority: "medium", depends_on_positions: [0] },
|
|
6950
|
+
{ position: 8, title_pattern: "Create GitHub repo {org}/{name}", priority: "medium", depends_on_positions: [7] },
|
|
6951
|
+
{ position: 9, title_pattern: "Add @hasna/cloud adapter", priority: "medium", depends_on_positions: [1] },
|
|
6952
|
+
{ position: 10, title_pattern: "Write PostgreSQL migrations for {name}", priority: "medium", depends_on_positions: [1] },
|
|
6953
|
+
{ position: 11, title_pattern: "Add feedback system + send_feedback MCP tool", priority: "medium", depends_on_positions: [3] },
|
|
6954
|
+
{ position: 12, title_pattern: "Add agent tools (register_agent, heartbeat, set_focus, list_agents)", priority: "medium", depends_on_positions: [3] },
|
|
6955
|
+
{ position: 13, title_pattern: "Create RDS database for {name}", priority: "low", depends_on_positions: [10] },
|
|
6956
|
+
{ position: 14, title_pattern: "Publish @hasna/{name} to npm", priority: "high", depends_on_positions: [6, 7, 8] },
|
|
6957
|
+
{ position: 15, title_pattern: "Install @hasna/{name} globally and verify", priority: "medium", depends_on_positions: [14] }
|
|
6958
|
+
]
|
|
6959
|
+
},
|
|
6960
|
+
{
|
|
6961
|
+
name: "bug-fix",
|
|
6962
|
+
description: "Standard bug fix workflow",
|
|
6963
|
+
variables: [{ name: "bug", required: true, description: "Bug description" }],
|
|
6964
|
+
tasks: [
|
|
6965
|
+
{ position: 0, title_pattern: "Reproduce: {bug}", priority: "critical" },
|
|
6966
|
+
{ position: 1, title_pattern: "Diagnose root cause of {bug}", priority: "critical", depends_on_positions: [0] },
|
|
6967
|
+
{ position: 2, title_pattern: "Implement fix for {bug}", priority: "critical", depends_on_positions: [1] },
|
|
6968
|
+
{ position: 3, title_pattern: "Write regression test for {bug}", priority: "high", depends_on_positions: [2] },
|
|
6969
|
+
{ position: 4, title_pattern: "Publish fix and verify in production", priority: "high", depends_on_positions: [3] }
|
|
6970
|
+
]
|
|
6971
|
+
},
|
|
6972
|
+
{
|
|
6973
|
+
name: "feature",
|
|
6974
|
+
description: "Standard feature development workflow",
|
|
6975
|
+
variables: [{ name: "feature", required: true }, { name: "scope", required: false, default: "medium" }],
|
|
6976
|
+
tasks: [
|
|
6977
|
+
{ position: 0, title_pattern: "Write spec for {feature}", priority: "high" },
|
|
6978
|
+
{ position: 1, title_pattern: "Design implementation approach for {feature}", priority: "high", depends_on_positions: [0] },
|
|
6979
|
+
{ position: 2, title_pattern: "Implement {feature}", priority: "critical", depends_on_positions: [1] },
|
|
6980
|
+
{ position: 3, title_pattern: "Write tests for {feature}", priority: "high", depends_on_positions: [2] },
|
|
6981
|
+
{ position: 4, title_pattern: "Code review for {feature}", priority: "medium", depends_on_positions: [3] },
|
|
6982
|
+
{ position: 5, title_pattern: "Update docs for {feature}", priority: "medium", depends_on_positions: [2] },
|
|
6983
|
+
{ position: 6, title_pattern: "Deploy {feature}", priority: "high", depends_on_positions: [4] }
|
|
6984
|
+
]
|
|
6985
|
+
},
|
|
6986
|
+
{
|
|
6987
|
+
name: "security-audit",
|
|
6988
|
+
description: "Security audit workflow",
|
|
6989
|
+
variables: [{ name: "target", required: true }],
|
|
6990
|
+
tasks: [
|
|
6991
|
+
{ position: 0, title_pattern: "Scan {target} for vulnerabilities", priority: "critical" },
|
|
6992
|
+
{ position: 1, title_pattern: "Review {target} security findings", priority: "critical", depends_on_positions: [0] },
|
|
6993
|
+
{ position: 2, title_pattern: "Fix critical issues in {target}", priority: "critical", depends_on_positions: [1] },
|
|
6994
|
+
{ position: 3, title_pattern: "Retest {target} after fixes", priority: "high", depends_on_positions: [2] },
|
|
6995
|
+
{ position: 4, title_pattern: "Write security report for {target}", priority: "medium", depends_on_positions: [3] },
|
|
6996
|
+
{ position: 5, title_pattern: "Close audit for {target}", priority: "low", depends_on_positions: [4] }
|
|
6997
|
+
]
|
|
6998
|
+
}
|
|
6999
|
+
];
|
|
7000
|
+
});
|
|
7001
|
+
|
|
6409
7002
|
// src/lib/extract.ts
|
|
6410
7003
|
var exports_extract = {};
|
|
6411
7004
|
__export(exports_extract, {
|
|
@@ -22940,6 +23533,44 @@ var init_pg_migrations = __esm(() => {
|
|
|
22940
23533
|
);
|
|
22941
23534
|
|
|
22942
23535
|
INSERT INTO _migrations (id) VALUES (36) ON CONFLICT DO NOTHING;
|
|
23536
|
+
`,
|
|
23537
|
+
`
|
|
23538
|
+
CREATE TABLE IF NOT EXISTS template_tasks (
|
|
23539
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
23540
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
23541
|
+
position INTEGER NOT NULL,
|
|
23542
|
+
title_pattern TEXT NOT NULL,
|
|
23543
|
+
description TEXT,
|
|
23544
|
+
priority TEXT DEFAULT 'medium',
|
|
23545
|
+
tags TEXT DEFAULT '[]',
|
|
23546
|
+
task_type TEXT,
|
|
23547
|
+
depends_on_positions TEXT DEFAULT '[]',
|
|
23548
|
+
metadata TEXT DEFAULT '{}',
|
|
23549
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
23550
|
+
);
|
|
23551
|
+
CREATE INDEX IF NOT EXISTS idx_template_tasks_template ON template_tasks(template_id);
|
|
23552
|
+
|
|
23553
|
+
INSERT INTO _migrations (id) VALUES (37) ON CONFLICT DO NOTHING;
|
|
23554
|
+
`,
|
|
23555
|
+
`
|
|
23556
|
+
ALTER TABLE task_templates ADD COLUMN IF NOT EXISTS variables TEXT DEFAULT '[]';
|
|
23557
|
+
INSERT INTO _migrations (id) VALUES (38) ON CONFLICT DO NOTHING;
|
|
23558
|
+
`,
|
|
23559
|
+
`
|
|
23560
|
+
ALTER TABLE template_tasks ADD COLUMN IF NOT EXISTS condition TEXT;
|
|
23561
|
+
ALTER TABLE template_tasks ADD COLUMN IF NOT EXISTS include_template_id TEXT;
|
|
23562
|
+
ALTER TABLE task_templates ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1;
|
|
23563
|
+
|
|
23564
|
+
CREATE TABLE IF NOT EXISTS template_versions (
|
|
23565
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
23566
|
+
template_id TEXT NOT NULL REFERENCES task_templates(id) ON DELETE CASCADE,
|
|
23567
|
+
version INTEGER NOT NULL,
|
|
23568
|
+
snapshot TEXT NOT NULL,
|
|
23569
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
23570
|
+
);
|
|
23571
|
+
CREATE INDEX IF NOT EXISTS idx_template_versions_template ON template_versions(template_id);
|
|
23572
|
+
|
|
23573
|
+
INSERT INTO _migrations (id) VALUES (39) ON CONFLICT DO NOTHING;
|
|
22943
23574
|
`
|
|
22944
23575
|
];
|
|
22945
23576
|
});
|
|
@@ -23199,6 +23830,11 @@ var init_mcp = __esm(() => {
|
|
|
23199
23830
|
"create_task_from_template",
|
|
23200
23831
|
"delete_template",
|
|
23201
23832
|
"update_template",
|
|
23833
|
+
"init_templates",
|
|
23834
|
+
"preview_template",
|
|
23835
|
+
"export_template",
|
|
23836
|
+
"import_template",
|
|
23837
|
+
"template_history",
|
|
23202
23838
|
"approve_task"
|
|
23203
23839
|
]);
|
|
23204
23840
|
agentFocusMap = new Map;
|
|
@@ -24792,15 +25428,20 @@ ${data.chart}` }] };
|
|
|
24792
25428
|
});
|
|
24793
25429
|
}
|
|
24794
25430
|
if (shouldRegisterTool("create_webhook")) {
|
|
24795
|
-
server.tool("create_webhook", "Register a webhook for task change events.", {
|
|
25431
|
+
server.tool("create_webhook", "Register a webhook for task change events. Optionally scope to a project, task list, agent, or specific task.", {
|
|
24796
25432
|
url: exports_external.string(),
|
|
24797
|
-
events: exports_external.array(exports_external.string()).optional(),
|
|
24798
|
-
secret: exports_external.string().optional()
|
|
25433
|
+
events: exports_external.array(exports_external.string()).optional().describe("Event types to subscribe to (empty = all). E.g. task.created, task.completed, task.failed, task.started, task.assigned, task.status_changed"),
|
|
25434
|
+
secret: exports_external.string().optional().describe("HMAC secret for signing webhook payloads"),
|
|
25435
|
+
project_id: exports_external.string().optional().describe("Only fire for events in this project"),
|
|
25436
|
+
task_list_id: exports_external.string().optional().describe("Only fire for events in this task list"),
|
|
25437
|
+
agent_id: exports_external.string().optional().describe("Only fire for events involving this agent"),
|
|
25438
|
+
task_id: exports_external.string().optional().describe("Only fire for events on this specific task")
|
|
24799
25439
|
}, async (params) => {
|
|
24800
25440
|
try {
|
|
24801
25441
|
const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
24802
25442
|
const wh = createWebhook2(params);
|
|
24803
|
-
|
|
25443
|
+
const scope = [wh.project_id && `project:${wh.project_id}`, wh.task_list_id && `list:${wh.task_list_id}`, wh.agent_id && `agent:${wh.agent_id}`, wh.task_id && `task:${wh.task_id}`].filter(Boolean).join(", ");
|
|
25444
|
+
return { content: [{ type: "text", text: `Webhook created: ${wh.id.slice(0, 8)} | ${wh.url} | events: ${wh.events.length === 0 ? "all" : wh.events.join(",")}${scope ? ` | scope: ${scope}` : ""}` }] };
|
|
24804
25445
|
} catch (e) {
|
|
24805
25446
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
24806
25447
|
}
|
|
@@ -24836,19 +25477,37 @@ ${text}` }] };
|
|
|
24836
25477
|
});
|
|
24837
25478
|
}
|
|
24838
25479
|
if (shouldRegisterTool("create_template")) {
|
|
24839
|
-
server.tool("create_template", "Create a reusable task template.", {
|
|
25480
|
+
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.", {
|
|
24840
25481
|
name: exports_external.string(),
|
|
24841
25482
|
title_pattern: exports_external.string(),
|
|
24842
25483
|
description: exports_external.string().optional(),
|
|
24843
25484
|
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
24844
25485
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
24845
25486
|
project_id: exports_external.string().optional(),
|
|
24846
|
-
plan_id: exports_external.string().optional()
|
|
25487
|
+
plan_id: exports_external.string().optional(),
|
|
25488
|
+
variables: exports_external.array(exports_external.object({
|
|
25489
|
+
name: exports_external.string().describe("Variable name (used as {name} in patterns)"),
|
|
25490
|
+
required: exports_external.boolean().describe("Whether this variable must be provided"),
|
|
25491
|
+
default: exports_external.string().optional().describe("Default value if not provided"),
|
|
25492
|
+
description: exports_external.string().optional().describe("Help text for the variable")
|
|
25493
|
+
})).optional().describe("Typed variable definitions with defaults and required flags"),
|
|
25494
|
+
tasks: exports_external.array(exports_external.object({
|
|
25495
|
+
title_pattern: exports_external.string().describe("Title pattern with optional {variable} placeholders"),
|
|
25496
|
+
description: exports_external.string().optional(),
|
|
25497
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
25498
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
25499
|
+
task_type: exports_external.string().optional(),
|
|
25500
|
+
depends_on: exports_external.array(exports_external.number()).optional().describe("Position indices (0-based) of tasks this task depends on"),
|
|
25501
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
25502
|
+
})).optional().describe("Multi-task template: ordered list of tasks to create together with dependencies")
|
|
24847
25503
|
}, async (params) => {
|
|
24848
25504
|
try {
|
|
24849
|
-
const { createTemplate: createTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25505
|
+
const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
24850
25506
|
const t = createTemplate2(params);
|
|
24851
|
-
|
|
25507
|
+
const withTasks = getTemplateWithTasks2(t.id);
|
|
25508
|
+
const taskCount = withTasks?.tasks.length ?? 0;
|
|
25509
|
+
const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
|
|
25510
|
+
return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
|
|
24852
25511
|
} catch (e) {
|
|
24853
25512
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
24854
25513
|
}
|
|
@@ -24861,7 +25520,10 @@ ${text}` }] };
|
|
|
24861
25520
|
const templates = listTemplates2();
|
|
24862
25521
|
if (templates.length === 0)
|
|
24863
25522
|
return { content: [{ type: "text", text: "No templates." }] };
|
|
24864
|
-
const text = templates.map((t) =>
|
|
25523
|
+
const text = templates.map((t) => {
|
|
25524
|
+
const vars = t.variables.length > 0 ? ` | vars: ${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
25525
|
+
return `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}${vars}`;
|
|
25526
|
+
}).join(`
|
|
24865
25527
|
`);
|
|
24866
25528
|
return { content: [{ type: "text", text: `${templates.length} template(s):
|
|
24867
25529
|
${text}` }] };
|
|
@@ -24871,23 +25533,35 @@ ${text}` }] };
|
|
|
24871
25533
|
});
|
|
24872
25534
|
}
|
|
24873
25535
|
if (shouldRegisterTool("create_task_from_template")) {
|
|
24874
|
-
server.tool("create_task_from_template", "Create
|
|
25536
|
+
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.", {
|
|
24875
25537
|
template_id: exports_external.string(),
|
|
24876
|
-
title: exports_external.string().optional(),
|
|
24877
|
-
description: exports_external.string().optional(),
|
|
25538
|
+
title: exports_external.string().optional().describe("Override title (single-task templates only)"),
|
|
25539
|
+
description: exports_external.string().optional().describe("Override description (single-task templates only)"),
|
|
24878
25540
|
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
24879
25541
|
assigned_to: exports_external.string().optional(),
|
|
24880
|
-
project_id: exports_external.string().optional()
|
|
25542
|
+
project_id: exports_external.string().optional(),
|
|
25543
|
+
task_list_id: exports_external.string().optional(),
|
|
25544
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders in multi-task templates")
|
|
24881
25545
|
}, async (params) => {
|
|
24882
25546
|
try {
|
|
24883
|
-
const { taskFromTemplate: taskFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25547
|
+
const { taskFromTemplate: taskFromTemplate2, getTemplateWithTasks: getTemplateWithTasks2, tasksFromTemplate: tasksFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
24884
25548
|
const resolvedTemplateId = resolveId(params.template_id, "task_templates");
|
|
25549
|
+
const templateWithTasks = getTemplateWithTasks2(resolvedTemplateId);
|
|
25550
|
+
if (templateWithTasks && templateWithTasks.tasks.length > 0) {
|
|
25551
|
+
const effectiveProjectId = params.project_id || templateWithTasks.project_id || undefined;
|
|
25552
|
+
const tasks = tasksFromTemplate2(resolvedTemplateId, effectiveProjectId, params.variables, params.task_list_id);
|
|
25553
|
+
const text = tasks.map((t) => `${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
25554
|
+
`);
|
|
25555
|
+
return { content: [{ type: "text", text: `${tasks.length} task(s) created from template:
|
|
25556
|
+
${text}` }] };
|
|
25557
|
+
}
|
|
24885
25558
|
const input = taskFromTemplate2(resolvedTemplateId, {
|
|
24886
25559
|
title: params.title,
|
|
24887
25560
|
description: params.description,
|
|
24888
25561
|
priority: params.priority,
|
|
24889
25562
|
assigned_to: params.assigned_to,
|
|
24890
|
-
project_id: params.project_id
|
|
25563
|
+
project_id: params.project_id,
|
|
25564
|
+
task_list_id: params.task_list_id
|
|
24891
25565
|
});
|
|
24892
25566
|
const task = createTask(input);
|
|
24893
25567
|
return { content: [{ type: "text", text: `Task created from template:
|
|
@@ -24932,6 +25606,93 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
|
|
|
24932
25606
|
}
|
|
24933
25607
|
});
|
|
24934
25608
|
}
|
|
25609
|
+
if (shouldRegisterTool("init_templates")) {
|
|
25610
|
+
server.tool("init_templates", "Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit). Skips templates that already exist by name.", {}, async () => {
|
|
25611
|
+
try {
|
|
25612
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = await Promise.resolve().then(() => (init_builtin_templates(), exports_builtin_templates));
|
|
25613
|
+
const result = initBuiltinTemplates2();
|
|
25614
|
+
if (result.created === 0) {
|
|
25615
|
+
return { content: [{ type: "text", text: `All ${result.skipped} built-in template(s) already exist.` }] };
|
|
25616
|
+
}
|
|
25617
|
+
return { content: [{ type: "text", text: `Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.` }] };
|
|
25618
|
+
} catch (e) {
|
|
25619
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25620
|
+
}
|
|
25621
|
+
});
|
|
25622
|
+
}
|
|
25623
|
+
if (shouldRegisterTool("preview_template")) {
|
|
25624
|
+
server.tool("preview_template", "Preview a template without creating tasks. Shows resolved titles (variables substituted), dependencies, and priorities.", {
|
|
25625
|
+
template_id: exports_external.string(),
|
|
25626
|
+
variables: exports_external.record(exports_external.string()).optional().describe("Variable substitution map for {name} placeholders")
|
|
25627
|
+
}, async (params) => {
|
|
25628
|
+
try {
|
|
25629
|
+
const { previewTemplate: previewTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25630
|
+
const resolvedId = resolveId(params.template_id, "task_templates");
|
|
25631
|
+
const preview = previewTemplate2(resolvedId, params.variables);
|
|
25632
|
+
const lines = preview.tasks.map((t) => {
|
|
25633
|
+
const deps = t.depends_on_positions.length > 0 ? ` (after: ${t.depends_on_positions.join(", ")})` : "";
|
|
25634
|
+
return ` [${t.position}] ${t.priority} | ${t.title}${deps}`;
|
|
25635
|
+
});
|
|
25636
|
+
const varsInfo = preview.variables.length > 0 ? `
|
|
25637
|
+
Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}` : "";
|
|
25638
|
+
const resolvedVars = Object.keys(preview.resolved_variables).length > 0 ? `
|
|
25639
|
+
Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}` : "";
|
|
25640
|
+
return { content: [{ type: "text", text: `Preview: ${preview.template_name} (${preview.tasks.length} tasks)${varsInfo}${resolvedVars}
|
|
25641
|
+
${lines.join(`
|
|
25642
|
+
`)}` }] };
|
|
25643
|
+
} catch (e) {
|
|
25644
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25645
|
+
}
|
|
25646
|
+
});
|
|
25647
|
+
}
|
|
25648
|
+
if (shouldRegisterTool("export_template")) {
|
|
25649
|
+
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 }) => {
|
|
25650
|
+
try {
|
|
25651
|
+
const { exportTemplate: exportTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25652
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
25653
|
+
const json = exportTemplate2(resolvedId);
|
|
25654
|
+
return { content: [{ type: "text", text: JSON.stringify(json, null, 2) }] };
|
|
25655
|
+
} catch (e) {
|
|
25656
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25657
|
+
}
|
|
25658
|
+
});
|
|
25659
|
+
}
|
|
25660
|
+
if (shouldRegisterTool("import_template")) {
|
|
25661
|
+
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 }) => {
|
|
25662
|
+
try {
|
|
25663
|
+
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25664
|
+
const parsed = JSON.parse(json);
|
|
25665
|
+
const t = importTemplate2(parsed);
|
|
25666
|
+
return { content: [{ type: "text", text: `Template imported: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
|
|
25667
|
+
} catch (e) {
|
|
25668
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25669
|
+
}
|
|
25670
|
+
});
|
|
25671
|
+
}
|
|
25672
|
+
if (shouldRegisterTool("template_history")) {
|
|
25673
|
+
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 }) => {
|
|
25674
|
+
try {
|
|
25675
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
25676
|
+
const resolvedId = resolveId(template_id, "task_templates");
|
|
25677
|
+
const template = getTemplate2(resolvedId);
|
|
25678
|
+
if (!template)
|
|
25679
|
+
return { content: [{ type: "text", text: `Template not found: ${template_id}` }], isError: true };
|
|
25680
|
+
const versions = listTemplateVersions2(resolvedId);
|
|
25681
|
+
if (versions.length === 0) {
|
|
25682
|
+
return { content: [{ type: "text", text: `${template.name} v${template.version} \u2014 no previous versions.` }] };
|
|
25683
|
+
}
|
|
25684
|
+
const lines = versions.map((v) => {
|
|
25685
|
+
const snap = JSON.parse(v.snapshot);
|
|
25686
|
+
return `v${v.version} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`;
|
|
25687
|
+
});
|
|
25688
|
+
return { content: [{ type: "text", text: `${template.name} \u2014 current: v${template.version}
|
|
25689
|
+
${lines.join(`
|
|
25690
|
+
`)}` }] };
|
|
25691
|
+
} catch (e) {
|
|
25692
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
25693
|
+
}
|
|
25694
|
+
});
|
|
25695
|
+
}
|
|
24935
25696
|
if (shouldRegisterTool("approve_task")) {
|
|
24936
25697
|
server.tool("approve_task", "Approve a task with requires_approval=true.", {
|
|
24937
25698
|
id: exports_external.string(),
|
|
@@ -26049,6 +26810,11 @@ ${stack_trace.slice(0, 1500)}
|
|
|
26049
26810
|
"create_task_from_template",
|
|
26050
26811
|
"delete_template",
|
|
26051
26812
|
"update_template",
|
|
26813
|
+
"init_templates",
|
|
26814
|
+
"preview_template",
|
|
26815
|
+
"export_template",
|
|
26816
|
+
"import_template",
|
|
26817
|
+
"template_history",
|
|
26052
26818
|
"bulk_update_tasks",
|
|
26053
26819
|
"bulk_create_tasks",
|
|
26054
26820
|
"get_task_stats",
|
|
@@ -26275,19 +27041,34 @@ ${stack_trace.slice(0, 1500)}
|
|
|
26275
27041
|
delete_webhook: `Delete a webhook by ID.
|
|
26276
27042
|
Params: id(string, req)
|
|
26277
27043
|
Example: {id: 'a1b2c3d4'}`,
|
|
26278
|
-
create_template: `Create a reusable task template.
|
|
26279
|
-
Params: name(string, req), title_pattern(string, req
|
|
26280
|
-
Example: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}
|
|
27044
|
+
create_template: `Create a reusable task template. Supports multi-task templates with dependencies.
|
|
27045
|
+
Params: name(string, req), title_pattern(string, req), description(string), priority(low|medium|high|critical), tags(string[]), project_id(string), plan_id(string), tasks(array of {title_pattern, description, priority, tags, task_type, depends_on(number[]), metadata})
|
|
27046
|
+
Example single: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}
|
|
27047
|
+
Example multi: {name: 'Feature', title_pattern: 'Feature: {name}', tasks: [{title_pattern: 'Design {name}'}, {title_pattern: 'Implement {name}', depends_on: [0]}, {title_pattern: 'Test {name}', depends_on: [1]}]}`,
|
|
26281
27048
|
list_templates: "List all task templates. No params.",
|
|
26282
|
-
create_task_from_template: `Create
|
|
26283
|
-
Params: template_id(string, req), title(string), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string)
|
|
26284
|
-
Example: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}
|
|
27049
|
+
create_task_from_template: `Create task(s) from a template. Multi-task templates create all tasks with dependencies wired. Supports {variable} substitution.
|
|
27050
|
+
Params: template_id(string, req), title(string \u2014 single-task override), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string), task_list_id(string), variables(Record<string,string> \u2014 {name} substitution)
|
|
27051
|
+
Example single: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}
|
|
27052
|
+
Example multi: {template_id: 'a1b2c3d4', project_id: 'proj1', variables: {name: 'OAuth login'}}`,
|
|
26285
27053
|
delete_template: `Delete a task template.
|
|
26286
27054
|
Params: id(string, req)
|
|
26287
27055
|
Example: {id: 'a1b2c3d4'}`,
|
|
26288
27056
|
update_template: `Update a task template's name, title pattern, or other fields.
|
|
26289
|
-
Params: id(string, req), name(string), title_pattern(string), description(string), priority(low|medium|high|critical), tags(string[]), project_id(string), plan_id(string)
|
|
27057
|
+
Params: id(string, req), name(string), title_pattern(string), description(string), priority(low|medium|high|critical), tags(string[]), variables(TemplateVariable[]), project_id(string), plan_id(string)
|
|
26290
27058
|
Example: {id: 'a1b2c3d4', name: 'Renamed Template', priority: 'critical'}`,
|
|
27059
|
+
init_templates: "Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit). Skips already existing. No params.",
|
|
27060
|
+
preview_template: `Preview a template without creating tasks. Shows resolved titles, deps, priorities.
|
|
27061
|
+
Params: template_id(string, req), variables(Record<string,string>)
|
|
27062
|
+
Example: {template_id: 'a1b2c3d4', variables: {name: 'invoices'}}`,
|
|
27063
|
+
export_template: `Export a template as JSON (template + tasks + variables). Use for sharing or backup.
|
|
27064
|
+
Params: template_id(string, req)
|
|
27065
|
+
Example: {template_id: 'a1b2c3d4'}`,
|
|
27066
|
+
import_template: `Import a template from a JSON string (as returned by export_template). Creates new template with new IDs.
|
|
27067
|
+
Params: json(string, req)
|
|
27068
|
+
Example: {json: '{"name":"My Template",...}'}`,
|
|
27069
|
+
template_history: `Show version history of a template. Each update creates a snapshot of the previous state.
|
|
27070
|
+
Params: template_id(string, req)
|
|
27071
|
+
Example: {template_id: 'a1b2c3d4'}`,
|
|
26291
27072
|
get_active_work: `See all in-progress tasks and who is working on them.
|
|
26292
27073
|
Params: project_id(string, optional), task_list_id(string, optional)
|
|
26293
27074
|
Example: {project_id: 'a1b2c3d4'}`,
|
|
@@ -27307,6 +28088,8 @@ async function startServer(port, options) {
|
|
|
27307
28088
|
continue;
|
|
27308
28089
|
if (client.agentId && event.agent_id !== client.agentId)
|
|
27309
28090
|
continue;
|
|
28091
|
+
if (client.projectId && event.project_id !== client.projectId)
|
|
28092
|
+
continue;
|
|
27310
28093
|
try {
|
|
27311
28094
|
client.controller.enqueue(`event: ${eventName}
|
|
27312
28095
|
data: ${data}
|
|
@@ -27345,6 +28128,31 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
27345
28128
|
});
|
|
27346
28129
|
}
|
|
27347
28130
|
if (path === "/api/events" && method === "GET") {
|
|
28131
|
+
const agentId = url.searchParams.get("agent_id") || undefined;
|
|
28132
|
+
const projectId = url.searchParams.get("project_id") || undefined;
|
|
28133
|
+
if (agentId || projectId) {
|
|
28134
|
+
const client = { controller: null, agentId, projectId, events: undefined };
|
|
28135
|
+
const stream2 = new ReadableStream({
|
|
28136
|
+
start(controller) {
|
|
28137
|
+
client.controller = controller;
|
|
28138
|
+
filteredSseClients.add(client);
|
|
28139
|
+
controller.enqueue(`data: ${JSON.stringify({ type: "connected", agent_id: agentId, project_id: projectId, timestamp: new Date().toISOString() })}
|
|
28140
|
+
|
|
28141
|
+
`);
|
|
28142
|
+
},
|
|
28143
|
+
cancel() {
|
|
28144
|
+
filteredSseClients.delete(client);
|
|
28145
|
+
}
|
|
28146
|
+
});
|
|
28147
|
+
return new Response(stream2, {
|
|
28148
|
+
headers: {
|
|
28149
|
+
"Content-Type": "text/event-stream",
|
|
28150
|
+
"Cache-Control": "no-cache",
|
|
28151
|
+
Connection: "keep-alive",
|
|
28152
|
+
"Access-Control-Allow-Origin": "*"
|
|
28153
|
+
}
|
|
28154
|
+
});
|
|
28155
|
+
}
|
|
27348
28156
|
const stream = new ReadableStream({
|
|
27349
28157
|
start(controller) {
|
|
27350
28158
|
sseClients.add(controller);
|
|
@@ -27453,7 +28261,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27453
28261
|
priority: body.priority,
|
|
27454
28262
|
project_id: body.project_id
|
|
27455
28263
|
});
|
|
27456
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id });
|
|
28264
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id, project_id: task.project_id });
|
|
27457
28265
|
return json(taskToSummary(task), 201, port);
|
|
27458
28266
|
} catch (e) {
|
|
27459
28267
|
return json({ error: e instanceof Error ? e.message : "Failed to create task" }, 500, port);
|
|
@@ -27674,7 +28482,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27674
28482
|
const id = startMatch[1];
|
|
27675
28483
|
try {
|
|
27676
28484
|
const task = startTask(id, "dashboard");
|
|
27677
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "started", agent_id: "dashboard" });
|
|
28485
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "started", agent_id: "dashboard", project_id: task.project_id });
|
|
27678
28486
|
return json(taskToSummary(task), 200, port);
|
|
27679
28487
|
} catch (e) {
|
|
27680
28488
|
return json({ error: e instanceof Error ? e.message : "Failed to start task" }, 500, port);
|
|
@@ -27687,7 +28495,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27687
28495
|
const body = await req.json().catch(() => ({}));
|
|
27688
28496
|
const { failTask: failTask2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
27689
28497
|
const result = failTask2(id, body.agent_id, body.reason, { retry: body.retry, error_code: body.error_code });
|
|
27690
|
-
broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null });
|
|
28498
|
+
broadcastEvent({ type: "task", task_id: id, action: "failed", agent_id: body.agent_id || null, project_id: result.task.project_id });
|
|
27691
28499
|
return json({ task: taskToSummary(result.task), retry_task: result.retryTask ? taskToSummary(result.retryTask) : null }, 200, port);
|
|
27692
28500
|
} catch (e) {
|
|
27693
28501
|
return json({ error: e instanceof Error ? e.message : "Failed to fail task" }, 500, port);
|
|
@@ -27698,7 +28506,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
27698
28506
|
const id = completeMatch[1];
|
|
27699
28507
|
try {
|
|
27700
28508
|
const task = completeTask(id, "dashboard");
|
|
27701
|
-
broadcastEvent({ type: "task", task_id: task.id, action: "completed", agent_id: "dashboard" });
|
|
28509
|
+
broadcastEvent({ type: "task", task_id: task.id, action: "completed", agent_id: "dashboard", project_id: task.project_id });
|
|
27702
28510
|
return json(taskToSummary(task), 200, port);
|
|
27703
28511
|
} catch (e) {
|
|
27704
28512
|
return json({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500, port);
|
|
@@ -30699,7 +31507,117 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
30699
31507
|
console.log(chalk2.bold(`${templates.length} template(s):
|
|
30700
31508
|
`));
|
|
30701
31509
|
for (const t of templates) {
|
|
30702
|
-
|
|
31510
|
+
const vars = t.variables && t.variables.length > 0 ? ` ${chalk2.dim(`(${t.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")})`)}` : "";
|
|
31511
|
+
console.log(` ${chalk2.dim(t.id.slice(0, 8))} ${chalk2.bold(t.name)} ${chalk2.cyan(`"${t.title_pattern}"`)} ${chalk2.yellow(t.priority)}${vars}`);
|
|
31512
|
+
}
|
|
31513
|
+
});
|
|
31514
|
+
program2.command("template-init").alias("templates-init").description("Initialize built-in starter templates (open-source-project, bug-fix, feature, security-audit)").action(() => {
|
|
31515
|
+
const globalOpts = program2.opts();
|
|
31516
|
+
const { initBuiltinTemplates: initBuiltinTemplates2 } = (init_builtin_templates(), __toCommonJS(exports_builtin_templates));
|
|
31517
|
+
const result = initBuiltinTemplates2();
|
|
31518
|
+
if (globalOpts.json) {
|
|
31519
|
+
output(result, true);
|
|
31520
|
+
return;
|
|
31521
|
+
}
|
|
31522
|
+
if (result.created === 0) {
|
|
31523
|
+
console.log(chalk2.dim(`All ${result.skipped} built-in template(s) already exist.`));
|
|
31524
|
+
} else {
|
|
31525
|
+
console.log(chalk2.green(`Created ${result.created} template(s): ${result.names.join(", ")}. Skipped ${result.skipped} existing.`));
|
|
31526
|
+
}
|
|
31527
|
+
});
|
|
31528
|
+
program2.command("template-preview <id>").alias("templates-preview").description("Preview a template without creating tasks \u2014 shows resolved titles, deps, and priorities").option("--var <vars...>", "Variable substitution in key=value format (e.g. --var name=invoices)").action((id, opts) => {
|
|
31529
|
+
const globalOpts = program2.opts();
|
|
31530
|
+
const { previewTemplate: previewTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31531
|
+
const variables = {};
|
|
31532
|
+
if (opts.var) {
|
|
31533
|
+
for (const v of opts.var) {
|
|
31534
|
+
const eq = v.indexOf("=");
|
|
31535
|
+
if (eq === -1) {
|
|
31536
|
+
console.error(chalk2.red(`Invalid variable format: ${v} (expected key=value)`));
|
|
31537
|
+
process.exit(1);
|
|
31538
|
+
}
|
|
31539
|
+
variables[v.slice(0, eq)] = v.slice(eq + 1);
|
|
31540
|
+
}
|
|
31541
|
+
}
|
|
31542
|
+
try {
|
|
31543
|
+
const preview = previewTemplate2(id, Object.keys(variables).length > 0 ? variables : undefined);
|
|
31544
|
+
if (globalOpts.json) {
|
|
31545
|
+
output(preview, true);
|
|
31546
|
+
return;
|
|
31547
|
+
}
|
|
31548
|
+
console.log(chalk2.bold(`Preview: ${preview.template_name} (${preview.tasks.length} tasks)`));
|
|
31549
|
+
if (preview.description)
|
|
31550
|
+
console.log(chalk2.dim(` ${preview.description}`));
|
|
31551
|
+
if (preview.variables.length > 0) {
|
|
31552
|
+
console.log(chalk2.dim(` Variables: ${preview.variables.map((v) => `${v.name}${v.required ? "*" : ""}${v.default ? `=${v.default}` : ""}`).join(", ")}`));
|
|
31553
|
+
}
|
|
31554
|
+
if (Object.keys(preview.resolved_variables).length > 0) {
|
|
31555
|
+
console.log(chalk2.dim(` Resolved: ${Object.entries(preview.resolved_variables).map(([k, v]) => `${k}=${v}`).join(", ")}`));
|
|
31556
|
+
}
|
|
31557
|
+
console.log();
|
|
31558
|
+
for (const t of preview.tasks) {
|
|
31559
|
+
const deps = t.depends_on_positions.length > 0 ? chalk2.dim(` (after: ${t.depends_on_positions.join(", ")})`) : "";
|
|
31560
|
+
console.log(` ${chalk2.dim(`[${t.position}]`)} ${chalk2.yellow(t.priority)} | ${t.title}${deps}`);
|
|
31561
|
+
}
|
|
31562
|
+
} catch (e) {
|
|
31563
|
+
handleError(e);
|
|
31564
|
+
}
|
|
31565
|
+
});
|
|
31566
|
+
program2.command("template-export <id>").alias("templates-export").description("Export a template as JSON to stdout").action((id) => {
|
|
31567
|
+
const { exportTemplate: exportTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31568
|
+
try {
|
|
31569
|
+
const json2 = exportTemplate2(id);
|
|
31570
|
+
console.log(JSON.stringify(json2, null, 2));
|
|
31571
|
+
} catch (e) {
|
|
31572
|
+
handleError(e);
|
|
31573
|
+
}
|
|
31574
|
+
});
|
|
31575
|
+
program2.command("template-import").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file").action((opts) => {
|
|
31576
|
+
const globalOpts = program2.opts();
|
|
31577
|
+
const { importTemplate: importTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31578
|
+
const { readFileSync: readFileSync8 } = __require("fs");
|
|
31579
|
+
try {
|
|
31580
|
+
if (!opts.file) {
|
|
31581
|
+
console.error(chalk2.red("--file is required"));
|
|
31582
|
+
process.exit(1);
|
|
31583
|
+
}
|
|
31584
|
+
const content = readFileSync8(opts.file, "utf-8");
|
|
31585
|
+
const json2 = JSON.parse(content);
|
|
31586
|
+
const template = importTemplate2(json2);
|
|
31587
|
+
if (globalOpts.json) {
|
|
31588
|
+
output(template, true);
|
|
31589
|
+
} else {
|
|
31590
|
+
console.log(chalk2.green(`Template imported: ${template.id.slice(0, 8)} | ${template.name} | "${template.title_pattern}"`));
|
|
31591
|
+
}
|
|
31592
|
+
} catch (e) {
|
|
31593
|
+
handleError(e);
|
|
31594
|
+
}
|
|
31595
|
+
});
|
|
31596
|
+
program2.command("template-history <id>").alias("templates-history").description("Show version history of a template").action((id) => {
|
|
31597
|
+
const globalOpts = program2.opts();
|
|
31598
|
+
const { listTemplateVersions: listTemplateVersions2, getTemplate: getTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
31599
|
+
try {
|
|
31600
|
+
const template = getTemplate2(id);
|
|
31601
|
+
if (!template) {
|
|
31602
|
+
console.error(chalk2.red("Template not found."));
|
|
31603
|
+
process.exit(1);
|
|
31604
|
+
}
|
|
31605
|
+
const versions = listTemplateVersions2(id);
|
|
31606
|
+
if (globalOpts.json) {
|
|
31607
|
+
output({ current_version: template.version, versions }, true);
|
|
31608
|
+
return;
|
|
31609
|
+
}
|
|
31610
|
+
console.log(chalk2.bold(`${template.name} \u2014 current version: ${template.version}`));
|
|
31611
|
+
if (versions.length === 0) {
|
|
31612
|
+
console.log(chalk2.dim(" No previous versions."));
|
|
31613
|
+
} else {
|
|
31614
|
+
for (const v of versions) {
|
|
31615
|
+
const snap = JSON.parse(v.snapshot);
|
|
31616
|
+
console.log(` ${chalk2.dim(`v${v.version}`)} | ${v.created_at} | ${snap.name} | "${snap.title_pattern}"`);
|
|
31617
|
+
}
|
|
31618
|
+
}
|
|
31619
|
+
} catch (e) {
|
|
31620
|
+
handleError(e);
|
|
30703
31621
|
}
|
|
30704
31622
|
});
|
|
30705
31623
|
program2.command("comment <id> <text>").description("Add a comment to a task").action((id, text) => {
|