@hasna/todos 0.11.58 → 0.11.60
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 +66 -2
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +1518 -197
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +600 -14
- package/dist/db/findings.d.ts +108 -0
- package/dist/db/findings.d.ts.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts +3 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-runs.d.ts +56 -0
- package/dist/db/task-runs.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +2 -2
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +968 -15
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/access-profiles.d.ts.map +1 -1
- package/dist/lib/event-hooks.d.ts +1 -1
- package/dist/lib/event-hooks.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +1 -1
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/mcp/index.js +1082 -27
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp/tools/task-resources.d.ts.map +1 -1
- package/dist/mcp.js +12 -1
- package/dist/registry.js +600 -14
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +1082 -27
- package/dist/server/routes.d.ts +1 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/serve.d.ts +2 -0
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.js +473 -11
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -3223,6 +3223,49 @@ var init_migrations = __esm(() => {
|
|
|
3223
3223
|
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_plan ON local_retrospectives(plan_id);
|
|
3224
3224
|
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_agent ON local_retrospectives(agent_id);
|
|
3225
3225
|
INSERT OR IGNORE INTO _migrations (id) VALUES (61);
|
|
3226
|
+
`,
|
|
3227
|
+
`
|
|
3228
|
+
CREATE TABLE IF NOT EXISTS task_run_transactions (
|
|
3229
|
+
id TEXT PRIMARY KEY,
|
|
3230
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3231
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3232
|
+
key TEXT NOT NULL,
|
|
3233
|
+
loop_id TEXT,
|
|
3234
|
+
loop_run_id TEXT,
|
|
3235
|
+
metadata TEXT DEFAULT '{}',
|
|
3236
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3237
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3238
|
+
UNIQUE(task_id, key)
|
|
3239
|
+
);
|
|
3240
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_task_key ON task_run_transactions(task_id, key);
|
|
3241
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_key ON task_run_transactions(key);
|
|
3242
|
+
CREATE INDEX IF NOT EXISTS idx_task_run_transactions_run ON task_run_transactions(run_id);
|
|
3243
|
+
|
|
3244
|
+
CREATE TABLE IF NOT EXISTS task_findings (
|
|
3245
|
+
id TEXT PRIMARY KEY,
|
|
3246
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3247
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3248
|
+
fingerprint TEXT NOT NULL,
|
|
3249
|
+
title TEXT NOT NULL,
|
|
3250
|
+
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low', 'medium', 'high', 'critical')),
|
|
3251
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'resolved', 'ignored')),
|
|
3252
|
+
source TEXT,
|
|
3253
|
+
summary TEXT,
|
|
3254
|
+
artifact_path TEXT,
|
|
3255
|
+
metadata TEXT DEFAULT '{}',
|
|
3256
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3257
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3258
|
+
resolved_at TEXT,
|
|
3259
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3260
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3261
|
+
UNIQUE(task_id, fingerprint)
|
|
3262
|
+
);
|
|
3263
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_task ON task_findings(task_id);
|
|
3264
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_run ON task_findings(run_id);
|
|
3265
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_status ON task_findings(status);
|
|
3266
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source);
|
|
3267
|
+
CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint);
|
|
3268
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (62);
|
|
3226
3269
|
`
|
|
3227
3270
|
];
|
|
3228
3271
|
});
|
|
@@ -3660,6 +3703,47 @@ function ensureSchema(db) {
|
|
|
3660
3703
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_run ON task_run_artifacts(run_id)");
|
|
3661
3704
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_task ON task_run_artifacts(task_id)");
|
|
3662
3705
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_path ON task_run_artifacts(path)");
|
|
3706
|
+
ensureTable("task_run_transactions", `
|
|
3707
|
+
CREATE TABLE task_run_transactions (
|
|
3708
|
+
id TEXT PRIMARY KEY,
|
|
3709
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3710
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3711
|
+
key TEXT NOT NULL,
|
|
3712
|
+
loop_id TEXT,
|
|
3713
|
+
loop_run_id TEXT,
|
|
3714
|
+
metadata TEXT DEFAULT '{}',
|
|
3715
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3716
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3717
|
+
UNIQUE(task_id, key)
|
|
3718
|
+
)`);
|
|
3719
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_task_key ON task_run_transactions(task_id, key)");
|
|
3720
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_key ON task_run_transactions(key)");
|
|
3721
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_transactions_run ON task_run_transactions(run_id)");
|
|
3722
|
+
ensureTable("task_findings", `
|
|
3723
|
+
CREATE TABLE task_findings (
|
|
3724
|
+
id TEXT PRIMARY KEY,
|
|
3725
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3726
|
+
run_id TEXT REFERENCES task_runs(id) ON DELETE SET NULL,
|
|
3727
|
+
fingerprint TEXT NOT NULL,
|
|
3728
|
+
title TEXT NOT NULL,
|
|
3729
|
+
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low', 'medium', 'high', 'critical')),
|
|
3730
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'resolved', 'ignored')),
|
|
3731
|
+
source TEXT,
|
|
3732
|
+
summary TEXT,
|
|
3733
|
+
artifact_path TEXT,
|
|
3734
|
+
metadata TEXT DEFAULT '{}',
|
|
3735
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3736
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3737
|
+
resolved_at TEXT,
|
|
3738
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3739
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3740
|
+
UNIQUE(task_id, fingerprint)
|
|
3741
|
+
)`);
|
|
3742
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_task ON task_findings(task_id)");
|
|
3743
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_run ON task_findings(run_id)");
|
|
3744
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_status ON task_findings(status)");
|
|
3745
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_source ON task_findings(source)");
|
|
3746
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_findings_fingerprint ON task_findings(fingerprint)");
|
|
3663
3747
|
ensureTable("inbox_items", `
|
|
3664
3748
|
CREATE TABLE inbox_items (
|
|
3665
3749
|
id TEXT PRIMARY KEY,
|
|
@@ -6134,6 +6218,7 @@ var init_event_hooks = __esm(() => {
|
|
|
6134
6218
|
"task.blocked",
|
|
6135
6219
|
"task.started",
|
|
6136
6220
|
"task.completed",
|
|
6221
|
+
"task.updated",
|
|
6137
6222
|
"task.due",
|
|
6138
6223
|
"task.due_soon",
|
|
6139
6224
|
"task.failed",
|
|
@@ -6154,7 +6239,7 @@ var init_event_hooks = __esm(() => {
|
|
|
6154
6239
|
VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
|
|
6155
6240
|
});
|
|
6156
6241
|
|
|
6157
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
6242
|
+
// node_modules/.bun/@hasna+events@0.1.10/node_modules/@hasna/events/dist/index.js
|
|
6158
6243
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6159
6244
|
import { existsSync as existsSync6 } from "fs";
|
|
6160
6245
|
import { homedir } from "os";
|
|
@@ -6201,14 +6286,40 @@ function matchRecord(input, matcher) {
|
|
|
6201
6286
|
return true;
|
|
6202
6287
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
6203
6288
|
const actual = getPathValue(input, path);
|
|
6204
|
-
|
|
6205
|
-
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
6206
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
6207
|
-
});
|
|
6208
|
-
}
|
|
6209
|
-
return actual === expected;
|
|
6289
|
+
return matchField(actual, expected, path);
|
|
6210
6290
|
});
|
|
6211
6291
|
}
|
|
6292
|
+
function matchField(actual, expected, path) {
|
|
6293
|
+
if (isNegativeMatcher(expected)) {
|
|
6294
|
+
return !matchPositiveField(actual, expected.not, path);
|
|
6295
|
+
}
|
|
6296
|
+
return matchPositiveField(actual, expected, path);
|
|
6297
|
+
}
|
|
6298
|
+
function matchPositiveField(actual, expected, path) {
|
|
6299
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
6300
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
6301
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
6302
|
+
}));
|
|
6303
|
+
}
|
|
6304
|
+
if (Array.isArray(actual)) {
|
|
6305
|
+
return actual.some((item) => item === expected);
|
|
6306
|
+
}
|
|
6307
|
+
return actual === expected;
|
|
6308
|
+
}
|
|
6309
|
+
function stringCandidates(actual) {
|
|
6310
|
+
if (actual === undefined)
|
|
6311
|
+
return [];
|
|
6312
|
+
if (Array.isArray(actual)) {
|
|
6313
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
6314
|
+
}
|
|
6315
|
+
return [String(actual)];
|
|
6316
|
+
}
|
|
6317
|
+
function isPrimitiveFieldValue(value) {
|
|
6318
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
6319
|
+
}
|
|
6320
|
+
function isNegativeMatcher(value) {
|
|
6321
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
6322
|
+
}
|
|
6212
6323
|
function eventMatchesFilter(event, filter) {
|
|
6213
6324
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
6214
6325
|
}
|
|
@@ -6816,9 +6927,66 @@ function taskEventData(task, extra = {}) {
|
|
|
6816
6927
|
started_at: task.started_at,
|
|
6817
6928
|
completed_at: task.completed_at,
|
|
6818
6929
|
due_at: task.due_at,
|
|
6930
|
+
requires_approval: task.requires_approval,
|
|
6931
|
+
approved_by: task.approved_by,
|
|
6932
|
+
approved_at: task.approved_at,
|
|
6819
6933
|
...extra
|
|
6820
6934
|
};
|
|
6821
6935
|
}
|
|
6936
|
+
function booleanField(value) {
|
|
6937
|
+
if (typeof value === "boolean")
|
|
6938
|
+
return value;
|
|
6939
|
+
if (typeof value === "number") {
|
|
6940
|
+
if (value === 1)
|
|
6941
|
+
return true;
|
|
6942
|
+
if (value === 0)
|
|
6943
|
+
return false;
|
|
6944
|
+
}
|
|
6945
|
+
if (typeof value === "string") {
|
|
6946
|
+
const normalized = value.trim().toLowerCase();
|
|
6947
|
+
if (["true", "1", "yes", "on"].includes(normalized))
|
|
6948
|
+
return true;
|
|
6949
|
+
if (["false", "0", "no", "off"].includes(normalized))
|
|
6950
|
+
return false;
|
|
6951
|
+
}
|
|
6952
|
+
return;
|
|
6953
|
+
}
|
|
6954
|
+
function objectField(value) {
|
|
6955
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
6956
|
+
}
|
|
6957
|
+
function firstBoolean(records, keys) {
|
|
6958
|
+
for (const record of records) {
|
|
6959
|
+
for (const key of keys) {
|
|
6960
|
+
const value = booleanField(record[key]);
|
|
6961
|
+
if (value !== undefined)
|
|
6962
|
+
return value;
|
|
6963
|
+
}
|
|
6964
|
+
}
|
|
6965
|
+
return;
|
|
6966
|
+
}
|
|
6967
|
+
function routingAutomationMetadata(task) {
|
|
6968
|
+
const automation = objectField(task.metadata.automation);
|
|
6969
|
+
const records = [task.metadata];
|
|
6970
|
+
if (automation)
|
|
6971
|
+
records.push(automation);
|
|
6972
|
+
const result = {};
|
|
6973
|
+
const aliases = [
|
|
6974
|
+
["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
|
|
6975
|
+
["no_auto", ["no_auto", "noAuto"]],
|
|
6976
|
+
["manual", ["manual"]],
|
|
6977
|
+
["manual_required", ["manual_required", "manualRequired"]],
|
|
6978
|
+
["requires_approval", ["requires_approval", "requiresApproval"]],
|
|
6979
|
+
["approval_required", ["approval_required", "approvalRequired"]]
|
|
6980
|
+
];
|
|
6981
|
+
for (const [canonical, keys] of aliases) {
|
|
6982
|
+
const value = firstBoolean(records, keys);
|
|
6983
|
+
if (value !== undefined)
|
|
6984
|
+
result[canonical] = value;
|
|
6985
|
+
}
|
|
6986
|
+
if (task.requires_approval)
|
|
6987
|
+
result.requires_approval = true;
|
|
6988
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
6989
|
+
}
|
|
6822
6990
|
function taskEventMetadata(task) {
|
|
6823
6991
|
const metadata = {
|
|
6824
6992
|
package: "@hasna/todos",
|
|
@@ -6829,6 +6997,14 @@ function taskEventMetadata(task) {
|
|
|
6829
6997
|
task_list_id: task.task_list_id,
|
|
6830
6998
|
working_dir: task.working_dir
|
|
6831
6999
|
};
|
|
7000
|
+
const routeEnabled = booleanField(task.metadata.route_enabled);
|
|
7001
|
+
if (routeEnabled !== undefined) {
|
|
7002
|
+
metadata.route_enabled = routeEnabled;
|
|
7003
|
+
}
|
|
7004
|
+
const automation = routingAutomationMetadata(task);
|
|
7005
|
+
if (automation) {
|
|
7006
|
+
metadata.automation = automation;
|
|
7007
|
+
}
|
|
6832
7008
|
try {
|
|
6833
7009
|
const project = task.project_id ? getProject(task.project_id) : null;
|
|
6834
7010
|
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
@@ -6846,9 +7022,6 @@ function taskEventMetadata(task) {
|
|
|
6846
7022
|
if (projectPath) {
|
|
6847
7023
|
metadata.project_kind = classifyProjectKind(projectPath);
|
|
6848
7024
|
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
6849
|
-
if (typeof task.metadata.route_enabled === "boolean") {
|
|
6850
|
-
metadata.route_enabled = task.metadata.route_enabled;
|
|
6851
|
-
}
|
|
6852
7025
|
metadata.working_dir = task.working_dir ?? projectPath;
|
|
6853
7026
|
}
|
|
6854
7027
|
const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
|
|
@@ -7463,6 +7636,17 @@ function replaceTaskTags(taskId, tags, db) {
|
|
|
7463
7636
|
db.run("DELETE FROM task_tags WHERE task_id = ?", [taskId]);
|
|
7464
7637
|
insertTaskTags(taskId, tags, db);
|
|
7465
7638
|
}
|
|
7639
|
+
function addMetadataConditions(metadata, conditions, params) {
|
|
7640
|
+
if (!metadata)
|
|
7641
|
+
return;
|
|
7642
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
7643
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key)) {
|
|
7644
|
+
throw new Error(`Invalid metadata filter key: ${key}`);
|
|
7645
|
+
}
|
|
7646
|
+
conditions.push(`json_extract(metadata, '$."${key}"') = ?`);
|
|
7647
|
+
params.push(value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value));
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7466
7650
|
function createTask(input, db) {
|
|
7467
7651
|
const d = db || getDatabase();
|
|
7468
7652
|
const timestamp = now();
|
|
@@ -7645,6 +7829,7 @@ function listTasks(filter = {}, db) {
|
|
|
7645
7829
|
params.push(filter.task_type);
|
|
7646
7830
|
}
|
|
7647
7831
|
}
|
|
7832
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7648
7833
|
const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
|
|
7649
7834
|
if (filter.cursor) {
|
|
7650
7835
|
try {
|
|
@@ -7669,6 +7854,54 @@ function listTasks(filter = {}, db) {
|
|
|
7669
7854
|
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC${limitClause}`).all(...params);
|
|
7670
7855
|
return rows.map(rowToTask);
|
|
7671
7856
|
}
|
|
7857
|
+
function getTaskByFingerprint(fingerprint, db) {
|
|
7858
|
+
const tasks = listTasks({ metadata: { fingerprint }, limit: 1 }, db);
|
|
7859
|
+
return tasks[0] ?? null;
|
|
7860
|
+
}
|
|
7861
|
+
function mergeTaskMetadata(current, next, fingerprint) {
|
|
7862
|
+
return {
|
|
7863
|
+
...current,
|
|
7864
|
+
...next ?? {},
|
|
7865
|
+
fingerprint
|
|
7866
|
+
};
|
|
7867
|
+
}
|
|
7868
|
+
function upsertTaskByFingerprint(input, db) {
|
|
7869
|
+
const d = db || getDatabase();
|
|
7870
|
+
const fingerprint = input.fingerprint.trim();
|
|
7871
|
+
if (!fingerprint)
|
|
7872
|
+
throw new Error("fingerprint is required");
|
|
7873
|
+
const existing = getTaskByFingerprint(fingerprint, d);
|
|
7874
|
+
const metadata = mergeTaskMetadata(existing?.metadata ?? {}, input.metadata, fingerprint);
|
|
7875
|
+
if (!existing) {
|
|
7876
|
+
const task2 = createTask({ ...input, metadata }, d);
|
|
7877
|
+
return { task: task2, created: true };
|
|
7878
|
+
}
|
|
7879
|
+
const task = updateTask(existing.id, {
|
|
7880
|
+
version: existing.version,
|
|
7881
|
+
title: input.title,
|
|
7882
|
+
description: input.description,
|
|
7883
|
+
status: input.status,
|
|
7884
|
+
priority: input.priority,
|
|
7885
|
+
project_id: input.project_id,
|
|
7886
|
+
assigned_to: input.assigned_to,
|
|
7887
|
+
working_dir: input.working_dir,
|
|
7888
|
+
plan_id: input.plan_id,
|
|
7889
|
+
task_list_id: input.task_list_id,
|
|
7890
|
+
tags: input.tags,
|
|
7891
|
+
metadata,
|
|
7892
|
+
due_at: input.due_at,
|
|
7893
|
+
estimated_minutes: input.estimated_minutes,
|
|
7894
|
+
sla_minutes: input.sla_minutes,
|
|
7895
|
+
confidence: input.confidence,
|
|
7896
|
+
retry_count: input.retry_count,
|
|
7897
|
+
max_retries: input.max_retries,
|
|
7898
|
+
retry_after: input.retry_after,
|
|
7899
|
+
requires_approval: input.requires_approval,
|
|
7900
|
+
recurrence_rule: input.recurrence_rule,
|
|
7901
|
+
task_type: input.task_type
|
|
7902
|
+
}, d);
|
|
7903
|
+
return { task, created: false };
|
|
7904
|
+
}
|
|
7672
7905
|
function countTasks(filter = {}, db) {
|
|
7673
7906
|
const d = db || getDatabase();
|
|
7674
7907
|
const conditions = [];
|
|
@@ -7732,6 +7965,7 @@ function countTasks(filter = {}, db) {
|
|
|
7732
7965
|
conditions.push("task_list_id = ?");
|
|
7733
7966
|
params.push(filter.task_list_id);
|
|
7734
7967
|
}
|
|
7968
|
+
addMetadataConditions(filter.metadata, conditions, params);
|
|
7735
7969
|
if (!filter.include_archived) {
|
|
7736
7970
|
conditions.push("archived_at IS NULL");
|
|
7737
7971
|
}
|
|
@@ -7782,6 +8016,10 @@ function updateTask(id, input, db) {
|
|
|
7782
8016
|
sets.push("assigned_to = ?");
|
|
7783
8017
|
params.push(input.assigned_to);
|
|
7784
8018
|
}
|
|
8019
|
+
if (input.working_dir !== undefined) {
|
|
8020
|
+
sets.push("working_dir = ?");
|
|
8021
|
+
params.push(input.working_dir);
|
|
8022
|
+
}
|
|
7785
8023
|
if (input.tags !== undefined) {
|
|
7786
8024
|
sets.push("tags = ?");
|
|
7787
8025
|
params.push(JSON.stringify(input.tags));
|
|
@@ -7870,6 +8108,8 @@ function updateTask(id, input, db) {
|
|
|
7870
8108
|
logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
|
|
7871
8109
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
|
|
7872
8110
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
8111
|
+
if (input.working_dir !== undefined && input.working_dir !== task.working_dir)
|
|
8112
|
+
logTaskChange(id, "update", "working_dir", task.working_dir, input.working_dir, agentId, d);
|
|
7873
8113
|
if (input.approved_by !== undefined)
|
|
7874
8114
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
7875
8115
|
const updatedTask = {
|
|
@@ -7905,6 +8145,10 @@ function updateTask(id, input, db) {
|
|
|
7905
8145
|
if (input.approved_by !== undefined) {
|
|
7906
8146
|
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
7907
8147
|
}
|
|
8148
|
+
const updatePayload = taskEventData(updatedTask);
|
|
8149
|
+
dispatchWebhook2("task.updated", updatePayload, d).catch(() => {});
|
|
8150
|
+
emitLocalEventHooksQuiet({ type: "task.updated", payload: updatePayload });
|
|
8151
|
+
emitSharedTaskEventQuiet({ type: "task.updated", task: updatedTask });
|
|
7908
8152
|
return updatedTask;
|
|
7909
8153
|
}
|
|
7910
8154
|
function deleteTask(id, db) {
|
|
@@ -10903,17 +11147,22 @@ var init_task_commits = __esm(() => {
|
|
|
10903
11147
|
var exports_task_runs = {};
|
|
10904
11148
|
__export(exports_task_runs, {
|
|
10905
11149
|
verifyTaskRunArtifacts: () => verifyTaskRunArtifacts,
|
|
11150
|
+
summarizeTaskRun: () => summarizeTaskRun,
|
|
10906
11151
|
startTaskRun: () => startTaskRun,
|
|
10907
11152
|
resolveTaskRunId: () => resolveTaskRunId,
|
|
10908
11153
|
redactEvidenceText: () => redactEvidenceText,
|
|
10909
11154
|
listTaskRuns: () => listTaskRuns,
|
|
10910
11155
|
getTaskRunLedger: () => getTaskRunLedger,
|
|
10911
11156
|
getTaskRun: () => getTaskRun,
|
|
11157
|
+
finishTaskRunTransaction: () => finishTaskRunTransaction,
|
|
10912
11158
|
finishTaskRun: () => finishTaskRun,
|
|
11159
|
+
findTaskRunByTransactionKey: () => findTaskRunByTransactionKey,
|
|
11160
|
+
beginTaskRunTransaction: () => beginTaskRunTransaction,
|
|
10913
11161
|
addTaskRunFile: () => addTaskRunFile,
|
|
10914
11162
|
addTaskRunEvent: () => addTaskRunEvent,
|
|
10915
11163
|
addTaskRunCommand: () => addTaskRunCommand,
|
|
10916
|
-
addTaskRunArtifact: () => addTaskRunArtifact
|
|
11164
|
+
addTaskRunArtifact: () => addTaskRunArtifact,
|
|
11165
|
+
LOOP_RUN_TRANSACTION_SCHEMA_VERSION: () => LOOP_RUN_TRANSACTION_SCHEMA_VERSION
|
|
10917
11166
|
});
|
|
10918
11167
|
function parseObject(value) {
|
|
10919
11168
|
if (!value)
|
|
@@ -10937,6 +11186,72 @@ function rowToArtifact(row) {
|
|
|
10937
11186
|
function getRunRow(runId, db) {
|
|
10938
11187
|
return db.query("SELECT * FROM task_runs WHERE id = ?").get(runId);
|
|
10939
11188
|
}
|
|
11189
|
+
function normalizeTransactionKey(input) {
|
|
11190
|
+
const key = (input.key || input.loop_run_id || input.loop_id || "").trim();
|
|
11191
|
+
if (!key)
|
|
11192
|
+
throw new Error("idempotent run transactions require --key, --loop-run-id, or --loop-id");
|
|
11193
|
+
return key.toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 240);
|
|
11194
|
+
}
|
|
11195
|
+
function loopTransactionMetadata(record) {
|
|
11196
|
+
const value = record.metadata["loop_transaction"];
|
|
11197
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
11198
|
+
}
|
|
11199
|
+
function runKey(record) {
|
|
11200
|
+
const tx = loopTransactionMetadata(record);
|
|
11201
|
+
const key = tx["idempotency_key"] ?? record.metadata["idempotency_key"];
|
|
11202
|
+
return typeof key === "string" ? key : null;
|
|
11203
|
+
}
|
|
11204
|
+
function loopId(record) {
|
|
11205
|
+
const tx = loopTransactionMetadata(record);
|
|
11206
|
+
const value = tx["loop_id"] ?? record.metadata["loop_id"];
|
|
11207
|
+
return typeof value === "string" ? value : null;
|
|
11208
|
+
}
|
|
11209
|
+
function loopRunId(record) {
|
|
11210
|
+
const tx = loopTransactionMetadata(record);
|
|
11211
|
+
const value = tx["loop_run_id"] ?? record.metadata["loop_run_id"];
|
|
11212
|
+
return typeof value === "string" ? value : null;
|
|
11213
|
+
}
|
|
11214
|
+
function getTaskRunTransactionByKey(key, taskId, db) {
|
|
11215
|
+
if (taskId) {
|
|
11216
|
+
return db.query("SELECT * FROM task_run_transactions WHERE task_id = ? AND key = ?").get(taskId, key);
|
|
11217
|
+
}
|
|
11218
|
+
const rows = db.query("SELECT * FROM task_run_transactions WHERE key = ? ORDER BY created_at DESC LIMIT 2").all(key);
|
|
11219
|
+
if (rows.length > 1)
|
|
11220
|
+
throw new Error(`Run transaction key is ambiguous across tasks: ${key}. Pass task_id.`);
|
|
11221
|
+
return rows[0] ?? null;
|
|
11222
|
+
}
|
|
11223
|
+
function summarizeTaskRun(run) {
|
|
11224
|
+
return {
|
|
11225
|
+
id: run.id,
|
|
11226
|
+
task_id: run.task_id,
|
|
11227
|
+
agent_id: run.agent_id,
|
|
11228
|
+
title: run.title,
|
|
11229
|
+
status: run.status,
|
|
11230
|
+
summary: run.summary,
|
|
11231
|
+
idempotency_key: runKey(run),
|
|
11232
|
+
loop_id: loopId(run),
|
|
11233
|
+
loop_run_id: loopRunId(run),
|
|
11234
|
+
metadata_keys: Object.keys(run.metadata).sort(),
|
|
11235
|
+
started_at: run.started_at,
|
|
11236
|
+
completed_at: run.completed_at,
|
|
11237
|
+
updated_at: run.updated_at
|
|
11238
|
+
};
|
|
11239
|
+
}
|
|
11240
|
+
function findTaskRunByTransactionKey(key, taskId, db) {
|
|
11241
|
+
const d = db || getDatabase();
|
|
11242
|
+
const normalized = normalizeTransactionKey({ key });
|
|
11243
|
+
const transaction = getTaskRunTransactionByKey(normalized, taskId, d);
|
|
11244
|
+
if (transaction?.run_id)
|
|
11245
|
+
return getTaskRun(transaction.run_id, d);
|
|
11246
|
+
return null;
|
|
11247
|
+
}
|
|
11248
|
+
function loopRunCommands(run, key) {
|
|
11249
|
+
return [
|
|
11250
|
+
run ? `todos runs show ${run.id.slice(0, 8)}` : "todos runs list",
|
|
11251
|
+
run ? `todos findings list --task ${run.task_id.slice(0, 8)} --json` : "todos findings list --json",
|
|
11252
|
+
`todos runs begin <task-id> --key ${key} --apply --json`
|
|
11253
|
+
];
|
|
11254
|
+
}
|
|
10940
11255
|
function resolveTaskRunId(idOrPrefix, db) {
|
|
10941
11256
|
const d = db || getDatabase();
|
|
10942
11257
|
const rows = d.query("SELECT id FROM task_runs WHERE id = ? OR id LIKE ? ORDER BY created_at DESC LIMIT 2").all(idOrPrefix, `${idOrPrefix}%`);
|
|
@@ -10955,7 +11270,7 @@ function startTaskRun(input, db) {
|
|
|
10955
11270
|
const d = db || getDatabase();
|
|
10956
11271
|
if (!getTask(input.task_id, d))
|
|
10957
11272
|
throw new TaskNotFoundError(input.task_id);
|
|
10958
|
-
const id = uuid();
|
|
11273
|
+
const id = input.id ?? uuid();
|
|
10959
11274
|
const timestamp = input.started_at || now();
|
|
10960
11275
|
if (input.claim && input.agent_id) {
|
|
10961
11276
|
startTask(input.task_id, input.agent_id, d);
|
|
@@ -10993,6 +11308,97 @@ function startTaskRun(input, db) {
|
|
|
10993
11308
|
emitLocalEventHooksQuiet({ type: "run.started", payload: { id: run.id, task_id: run.task_id, agent_id: run.agent_id, title: run.title } });
|
|
10994
11309
|
return run;
|
|
10995
11310
|
}
|
|
11311
|
+
function beginTaskRunTransaction(input, db) {
|
|
11312
|
+
const d = db || getDatabase();
|
|
11313
|
+
if (!getTask(input.task_id, d))
|
|
11314
|
+
throw new TaskNotFoundError(input.task_id);
|
|
11315
|
+
const timestamp = input.started_at || now();
|
|
11316
|
+
const key = normalizeTransactionKey(input);
|
|
11317
|
+
const existing = findTaskRunByTransactionKey(key, input.task_id, d);
|
|
11318
|
+
const dryRun = !input.apply;
|
|
11319
|
+
if (existing) {
|
|
11320
|
+
return {
|
|
11321
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11322
|
+
local_only: true,
|
|
11323
|
+
dry_run: dryRun,
|
|
11324
|
+
processed_at: timestamp,
|
|
11325
|
+
action: "matched",
|
|
11326
|
+
key,
|
|
11327
|
+
run: summarizeTaskRun(existing),
|
|
11328
|
+
warnings: existing.status === "running" ? [] : [`matched ${existing.status} run`],
|
|
11329
|
+
commands: loopRunCommands(existing, key)
|
|
11330
|
+
};
|
|
11331
|
+
}
|
|
11332
|
+
if (dryRun) {
|
|
11333
|
+
return {
|
|
11334
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11335
|
+
local_only: true,
|
|
11336
|
+
dry_run: true,
|
|
11337
|
+
processed_at: timestamp,
|
|
11338
|
+
action: "preview",
|
|
11339
|
+
key,
|
|
11340
|
+
run: null,
|
|
11341
|
+
warnings: [],
|
|
11342
|
+
commands: loopRunCommands(null, key)
|
|
11343
|
+
};
|
|
11344
|
+
}
|
|
11345
|
+
const metadata = redactValue({
|
|
11346
|
+
...input.metadata || {},
|
|
11347
|
+
loop_transaction: {
|
|
11348
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11349
|
+
idempotency_key: key,
|
|
11350
|
+
loop_id: input.loop_id ?? null,
|
|
11351
|
+
loop_run_id: input.loop_run_id ?? null,
|
|
11352
|
+
first_seen_at: timestamp
|
|
11353
|
+
},
|
|
11354
|
+
idempotency_key: key
|
|
11355
|
+
});
|
|
11356
|
+
const created = d.transaction(() => {
|
|
11357
|
+
d.run(`INSERT OR IGNORE INTO task_run_transactions (
|
|
11358
|
+
id, task_id, run_id, key, loop_id, loop_run_id, metadata, created_at, updated_at
|
|
11359
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?)`, [
|
|
11360
|
+
uuid(),
|
|
11361
|
+
input.task_id,
|
|
11362
|
+
key,
|
|
11363
|
+
input.loop_id ?? null,
|
|
11364
|
+
input.loop_run_id ?? null,
|
|
11365
|
+
JSON.stringify(metadata),
|
|
11366
|
+
timestamp,
|
|
11367
|
+
timestamp
|
|
11368
|
+
]);
|
|
11369
|
+
const transaction = getTaskRunTransactionByKey(key, input.task_id, d);
|
|
11370
|
+
if (!transaction)
|
|
11371
|
+
throw new Error(`Could not create run transaction for key: ${key}`);
|
|
11372
|
+
if (transaction.run_id) {
|
|
11373
|
+
const existingRun = getTaskRun(transaction.run_id, d);
|
|
11374
|
+
if (existingRun)
|
|
11375
|
+
return { run: existingRun, action: "matched" };
|
|
11376
|
+
}
|
|
11377
|
+
const run = startTaskRun({
|
|
11378
|
+
id: uuid(),
|
|
11379
|
+
task_id: input.task_id,
|
|
11380
|
+
agent_id: input.agent_id,
|
|
11381
|
+
title: input.title,
|
|
11382
|
+
summary: input.summary,
|
|
11383
|
+
metadata,
|
|
11384
|
+
claim: input.claim,
|
|
11385
|
+
started_at: timestamp
|
|
11386
|
+
}, d);
|
|
11387
|
+
d.run("UPDATE task_run_transactions SET run_id = ?, loop_id = COALESCE(?, loop_id), loop_run_id = COALESCE(?, loop_run_id), metadata = ?, updated_at = ? WHERE id = ?", [run.id, input.loop_id ?? null, input.loop_run_id ?? null, JSON.stringify(metadata), timestamp, transaction.id]);
|
|
11388
|
+
return { run, action: "created" };
|
|
11389
|
+
})();
|
|
11390
|
+
return {
|
|
11391
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11392
|
+
local_only: true,
|
|
11393
|
+
dry_run: false,
|
|
11394
|
+
processed_at: timestamp,
|
|
11395
|
+
action: created.action,
|
|
11396
|
+
key,
|
|
11397
|
+
run: summarizeTaskRun(created.run),
|
|
11398
|
+
warnings: [],
|
|
11399
|
+
commands: loopRunCommands(created.run, key)
|
|
11400
|
+
};
|
|
11401
|
+
}
|
|
10996
11402
|
function addTaskRunEvent(input, db) {
|
|
10997
11403
|
const d = db || getDatabase();
|
|
10998
11404
|
const runId = resolveTaskRunId(input.run_id, d);
|
|
@@ -11172,6 +11578,66 @@ function finishTaskRun(input, db) {
|
|
|
11172
11578
|
});
|
|
11173
11579
|
return updated;
|
|
11174
11580
|
}
|
|
11581
|
+
function finishTaskRunTransaction(input, db) {
|
|
11582
|
+
const d = db || getDatabase();
|
|
11583
|
+
const timestamp = input.completed_at || now();
|
|
11584
|
+
const status = input.status || "completed";
|
|
11585
|
+
const key = input.key ? normalizeTransactionKey({ key: input.key }) : "";
|
|
11586
|
+
const run = input.run_id ? getTaskRun(resolveTaskRunId(input.run_id, d), d) : key ? findTaskRunByTransactionKey(key, input.task_id, d) : null;
|
|
11587
|
+
if (!run) {
|
|
11588
|
+
throw new Error(input.run_id ? `Run not found: ${input.run_id}` : "runs finish requires a run id or --key");
|
|
11589
|
+
}
|
|
11590
|
+
if (input.task_id && run.task_id !== input.task_id) {
|
|
11591
|
+
throw new Error(`Run ${run.id} belongs to task ${run.task_id}, not ${input.task_id}`);
|
|
11592
|
+
}
|
|
11593
|
+
const resolvedKey = key || runKey(run) || run.id;
|
|
11594
|
+
const dryRun = input.apply === false;
|
|
11595
|
+
if (run.status !== "running") {
|
|
11596
|
+
const conflict = run.status !== status;
|
|
11597
|
+
return {
|
|
11598
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11599
|
+
local_only: true,
|
|
11600
|
+
dry_run: dryRun,
|
|
11601
|
+
processed_at: timestamp,
|
|
11602
|
+
action: conflict ? "conflict" : "matched",
|
|
11603
|
+
key: resolvedKey,
|
|
11604
|
+
run: summarizeTaskRun(run),
|
|
11605
|
+
warnings: conflict ? [`run is already ${run.status}; requested ${status}`] : [],
|
|
11606
|
+
commands: loopRunCommands(run, resolvedKey)
|
|
11607
|
+
};
|
|
11608
|
+
}
|
|
11609
|
+
if (dryRun) {
|
|
11610
|
+
return {
|
|
11611
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11612
|
+
local_only: true,
|
|
11613
|
+
dry_run: true,
|
|
11614
|
+
processed_at: timestamp,
|
|
11615
|
+
action: "preview",
|
|
11616
|
+
key: resolvedKey,
|
|
11617
|
+
run: summarizeTaskRun({ ...run, status, summary: input.summary ?? run.summary, completed_at: timestamp, updated_at: timestamp }),
|
|
11618
|
+
warnings: [],
|
|
11619
|
+
commands: loopRunCommands(run, resolvedKey)
|
|
11620
|
+
};
|
|
11621
|
+
}
|
|
11622
|
+
const finished = finishTaskRun({
|
|
11623
|
+
run_id: run.id,
|
|
11624
|
+
status,
|
|
11625
|
+
summary: input.summary,
|
|
11626
|
+
agent_id: input.agent_id,
|
|
11627
|
+
completed_at: timestamp
|
|
11628
|
+
}, d);
|
|
11629
|
+
return {
|
|
11630
|
+
schema_version: LOOP_RUN_TRANSACTION_SCHEMA_VERSION,
|
|
11631
|
+
local_only: true,
|
|
11632
|
+
dry_run: false,
|
|
11633
|
+
processed_at: timestamp,
|
|
11634
|
+
action: "finished",
|
|
11635
|
+
key: resolvedKey,
|
|
11636
|
+
run: summarizeTaskRun(finished),
|
|
11637
|
+
warnings: [],
|
|
11638
|
+
commands: loopRunCommands(finished, resolvedKey)
|
|
11639
|
+
};
|
|
11640
|
+
}
|
|
11175
11641
|
function listTaskRuns(taskId, db) {
|
|
11176
11642
|
const d = db || getDatabase();
|
|
11177
11643
|
const rows = taskId ? d.query("SELECT * FROM task_runs WHERE task_id = ? ORDER BY started_at DESC, created_at DESC").all(taskId) : d.query("SELECT * FROM task_runs ORDER BY started_at DESC, created_at DESC LIMIT 100").all();
|
|
@@ -11189,6 +11655,7 @@ function getTaskRunLedger(runId, db) {
|
|
|
11189
11655
|
const files = d.query("SELECT * FROM task_files WHERE task_id = ? ORDER BY updated_at DESC, path").all(run.task_id);
|
|
11190
11656
|
return { run, events, commands, artifacts, files };
|
|
11191
11657
|
}
|
|
11658
|
+
var LOOP_RUN_TRANSACTION_SCHEMA_VERSION = "todos.loop_run_transaction.v1";
|
|
11192
11659
|
var init_task_runs = __esm(() => {
|
|
11193
11660
|
init_artifact_store();
|
|
11194
11661
|
init_event_hooks();
|
|
@@ -11577,6 +12044,7 @@ var init_calendar = __esm(() => {
|
|
|
11577
12044
|
var exports_tasks = {};
|
|
11578
12045
|
__export(exports_tasks, {
|
|
11579
12046
|
watchTask: () => watchTask,
|
|
12047
|
+
upsertTaskByFingerprint: () => upsertTaskByFingerprint,
|
|
11580
12048
|
updateTaskBoard: () => updateTaskBoard,
|
|
11581
12049
|
updateTask: () => updateTask,
|
|
11582
12050
|
unwatchTask: () => unwatchTask,
|
|
@@ -11620,6 +12088,7 @@ __export(exports_tasks, {
|
|
|
11620
12088
|
getTaskGraph: () => getTaskGraph,
|
|
11621
12089
|
getTaskDependents: () => getTaskDependents,
|
|
11622
12090
|
getTaskDependencies: () => getTaskDependencies,
|
|
12091
|
+
getTaskByFingerprint: () => getTaskByFingerprint,
|
|
11623
12092
|
getTaskBoard: () => getTaskBoard,
|
|
11624
12093
|
getTask: () => getTask,
|
|
11625
12094
|
getStatus: () => getStatus,
|
|
@@ -11860,6 +12329,62 @@ function parsePriority(value) {
|
|
|
11860
12329
|
}
|
|
11861
12330
|
return value;
|
|
11862
12331
|
}
|
|
12332
|
+
function parseJsonObject3(value, flag) {
|
|
12333
|
+
if (!value)
|
|
12334
|
+
return;
|
|
12335
|
+
let parsed;
|
|
12336
|
+
try {
|
|
12337
|
+
parsed = JSON.parse(value);
|
|
12338
|
+
} catch (error) {
|
|
12339
|
+
console.error(chalk2.red(`${flag} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`));
|
|
12340
|
+
process.exit(1);
|
|
12341
|
+
}
|
|
12342
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
12343
|
+
console.error(chalk2.red(`${flag} must be a JSON object`));
|
|
12344
|
+
process.exit(1);
|
|
12345
|
+
}
|
|
12346
|
+
return parsed;
|
|
12347
|
+
}
|
|
12348
|
+
function parseJsonValue(value) {
|
|
12349
|
+
if (value === undefined)
|
|
12350
|
+
return;
|
|
12351
|
+
try {
|
|
12352
|
+
return JSON.parse(value);
|
|
12353
|
+
} catch {
|
|
12354
|
+
return value;
|
|
12355
|
+
}
|
|
12356
|
+
}
|
|
12357
|
+
function parseTags(value) {
|
|
12358
|
+
return value ? value.split(",").map((tag) => tag.trim()).filter(Boolean) : undefined;
|
|
12359
|
+
}
|
|
12360
|
+
function buildExpectationMetadata(opts) {
|
|
12361
|
+
const metadata = parseJsonObject3(opts["metadataJson"], "--metadata-json") ?? {};
|
|
12362
|
+
const expectationId = opts["expectationId"];
|
|
12363
|
+
const expectationFingerprint = opts["expectationFingerprint"];
|
|
12364
|
+
const evidencePaths = opts["evidencePaths"];
|
|
12365
|
+
const originLoopId = opts["originLoopId"];
|
|
12366
|
+
const originRunId = opts["originRunId"];
|
|
12367
|
+
const expected = opts["expected"];
|
|
12368
|
+
const observed = opts["observed"];
|
|
12369
|
+
const acceptance = opts["acceptance"];
|
|
12370
|
+
if (expectationId !== undefined)
|
|
12371
|
+
metadata["expectation_id"] = expectationId;
|
|
12372
|
+
if (expectationFingerprint !== undefined)
|
|
12373
|
+
metadata["expectation_fingerprint"] = expectationFingerprint;
|
|
12374
|
+
if (evidencePaths !== undefined)
|
|
12375
|
+
metadata["evidence_paths"] = String(evidencePaths).split(",").map((path) => path.trim()).filter(Boolean);
|
|
12376
|
+
if (originLoopId !== undefined)
|
|
12377
|
+
metadata["origin_loop_id"] = originLoopId;
|
|
12378
|
+
if (originRunId !== undefined)
|
|
12379
|
+
metadata["origin_run_id"] = originRunId;
|
|
12380
|
+
if (expected !== undefined)
|
|
12381
|
+
metadata["expected"] = parseJsonValue(String(expected));
|
|
12382
|
+
if (observed !== undefined)
|
|
12383
|
+
metadata["observed"] = parseJsonValue(String(observed));
|
|
12384
|
+
if (acceptance !== undefined)
|
|
12385
|
+
metadata["acceptance"] = parseJsonValue(String(acceptance));
|
|
12386
|
+
return metadata;
|
|
12387
|
+
}
|
|
11863
12388
|
function registerTaskCommands(program2) {
|
|
11864
12389
|
program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--approval", "Require approval before completion").option("--recurrence <rule>", "Recurrence rule, e.g. 'every day', 'every weekday', 'every 2 weeks'").option("--due <date>", "Due date (ISO string or YYYY-MM-DD)").option("--reason <text>", "Why this task exists").option("--project <id>", "Assign to project by ID or slug (overrides auto-detect)").action((title, opts) => {
|
|
11865
12390
|
const globalOpts = program2.opts();
|
|
@@ -11876,7 +12401,7 @@ function registerTaskCommands(program2) {
|
|
|
11876
12401
|
}
|
|
11877
12402
|
return id;
|
|
11878
12403
|
})() : undefined;
|
|
11879
|
-
const
|
|
12404
|
+
const task2 = createTask({
|
|
11880
12405
|
title,
|
|
11881
12406
|
description: opts.description,
|
|
11882
12407
|
priority: parsePriority(opts.priority),
|
|
@@ -11898,10 +12423,53 @@ function registerTaskCommands(program2) {
|
|
|
11898
12423
|
reason: opts.reason
|
|
11899
12424
|
});
|
|
11900
12425
|
if (globalOpts.json) {
|
|
11901
|
-
output(
|
|
12426
|
+
output(task2, true);
|
|
11902
12427
|
} else {
|
|
11903
12428
|
console.log(chalk2.green("Task created:"));
|
|
11904
|
-
console.log(formatTaskLine(
|
|
12429
|
+
console.log(formatTaskLine(task2));
|
|
12430
|
+
}
|
|
12431
|
+
});
|
|
12432
|
+
const task = program2.command("task").description("Task subcommands for deterministic automation");
|
|
12433
|
+
task.command("upsert").description("Create or update a task by stable metadata fingerprint").requiredOption("--fingerprint <key>", "Stable dedupe fingerprint").requiredOption("--title <text>", "Task title").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("-s, --status <status>", "Task status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--metadata-json <json>", "JSON object merged into task metadata").option("--working-dir <path>", "Working directory to store on create/update").option("--project <id>", "Assign to project by ID, slug, or path").option("--assign <agent>", "Assign to agent").option("--expectation-id <id>", "Expectation metadata ID").option("--expectation-fingerprint <key>", "Expectation metadata fingerprint").option("--evidence-paths <paths>", "Comma-separated evidence paths").option("--origin-loop-id <id>", "Origin loop ID").option("--origin-run-id <id>", "Origin run ID").option("--expected <json-or-text>", "Expected value metadata").option("--observed <json-or-text>", "Observed value metadata").option("--acceptance <json-or-text>", "Acceptance metadata").action((opts) => {
|
|
12434
|
+
const globalOpts = program2.opts();
|
|
12435
|
+
opts.tags = opts.tags || opts.tag;
|
|
12436
|
+
opts.list = opts.list || opts.taskList;
|
|
12437
|
+
const explicitProject = opts.project || globalOpts.project;
|
|
12438
|
+
const projectId = explicitProject ? resolveProjectIdOrSlug(explicitProject) : autoProject(globalOpts);
|
|
12439
|
+
const taskListId = opts.list ? (() => {
|
|
12440
|
+
const db = getDatabase();
|
|
12441
|
+
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
12442
|
+
if (!id) {
|
|
12443
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
12444
|
+
process.exit(1);
|
|
12445
|
+
}
|
|
12446
|
+
return id;
|
|
12447
|
+
})() : undefined;
|
|
12448
|
+
let result;
|
|
12449
|
+
try {
|
|
12450
|
+
result = upsertTaskByFingerprint({
|
|
12451
|
+
fingerprint: opts.fingerprint,
|
|
12452
|
+
title: opts.title,
|
|
12453
|
+
description: opts.description,
|
|
12454
|
+
priority: parsePriority(opts.priority),
|
|
12455
|
+
status: opts.status ? normalizeStatus(opts.status) : undefined,
|
|
12456
|
+
task_list_id: taskListId,
|
|
12457
|
+
tags: parseTags(opts.tags),
|
|
12458
|
+
metadata: buildExpectationMetadata(opts),
|
|
12459
|
+
working_dir: opts.workingDir ? resolve8(opts.workingDir) : process.cwd(),
|
|
12460
|
+
project_id: projectId,
|
|
12461
|
+
assigned_to: opts.assign,
|
|
12462
|
+
agent_id: globalOpts.agent,
|
|
12463
|
+
session_id: globalOpts.session
|
|
12464
|
+
});
|
|
12465
|
+
} catch (e) {
|
|
12466
|
+
handleError(e);
|
|
12467
|
+
}
|
|
12468
|
+
if (globalOpts.json) {
|
|
12469
|
+
output(result, true);
|
|
12470
|
+
} else {
|
|
12471
|
+
console.log(chalk2.green(result.created ? "Task created:" : "Task updated:"));
|
|
12472
|
+
console.log(formatTaskLine(result.task));
|
|
11905
12473
|
}
|
|
11906
12474
|
});
|
|
11907
12475
|
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action((opts) => {
|
|
@@ -12059,89 +12627,89 @@ function registerTaskCommands(program2) {
|
|
|
12059
12627
|
program2.command("show <id>").description("Show full task details").action((id) => {
|
|
12060
12628
|
const globalOpts = program2.opts();
|
|
12061
12629
|
const resolvedId = resolveTaskId(id);
|
|
12062
|
-
const
|
|
12063
|
-
if (!
|
|
12630
|
+
const task2 = getTaskWithRelations(resolvedId);
|
|
12631
|
+
if (!task2) {
|
|
12064
12632
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
12065
12633
|
process.exit(1);
|
|
12066
12634
|
}
|
|
12067
12635
|
if (globalOpts.json) {
|
|
12068
|
-
output(
|
|
12636
|
+
output(task2, true);
|
|
12069
12637
|
return;
|
|
12070
12638
|
}
|
|
12071
12639
|
console.log(chalk2.bold(`Task Details:
|
|
12072
12640
|
`));
|
|
12073
|
-
console.log(` ${chalk2.dim("ID:")} ${
|
|
12074
|
-
console.log(` ${chalk2.dim("Title:")} ${
|
|
12075
|
-
console.log(` ${chalk2.dim("Status:")} ${(statusColors[
|
|
12076
|
-
console.log(` ${chalk2.dim("Priority:")} ${(priorityColors[
|
|
12077
|
-
if (
|
|
12078
|
-
console.log(` ${chalk2.dim("Desc:")} ${
|
|
12079
|
-
if (
|
|
12080
|
-
console.log(` ${chalk2.dim("Assigned:")} ${
|
|
12081
|
-
if (
|
|
12082
|
-
console.log(` ${chalk2.dim("Agent:")} ${
|
|
12083
|
-
if (
|
|
12084
|
-
console.log(` ${chalk2.dim("Session:")} ${
|
|
12085
|
-
if (
|
|
12086
|
-
console.log(` ${chalk2.dim("Locked:")} ${
|
|
12087
|
-
if (
|
|
12088
|
-
const approvalStatus =
|
|
12641
|
+
console.log(` ${chalk2.dim("ID:")} ${task2.id}`);
|
|
12642
|
+
console.log(` ${chalk2.dim("Title:")} ${task2.title}`);
|
|
12643
|
+
console.log(` ${chalk2.dim("Status:")} ${(statusColors[task2.status] || chalk2.white)(task2.status)}`);
|
|
12644
|
+
console.log(` ${chalk2.dim("Priority:")} ${(priorityColors[task2.priority] || chalk2.white)(task2.priority)}`);
|
|
12645
|
+
if (task2.description)
|
|
12646
|
+
console.log(` ${chalk2.dim("Desc:")} ${task2.description}`);
|
|
12647
|
+
if (task2.assigned_to)
|
|
12648
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task2.assigned_to}`);
|
|
12649
|
+
if (task2.agent_id)
|
|
12650
|
+
console.log(` ${chalk2.dim("Agent:")} ${task2.agent_id}`);
|
|
12651
|
+
if (task2.session_id)
|
|
12652
|
+
console.log(` ${chalk2.dim("Session:")} ${task2.session_id}`);
|
|
12653
|
+
if (task2.locked_by)
|
|
12654
|
+
console.log(` ${chalk2.dim("Locked:")} ${task2.locked_by} (at ${task2.locked_at})`);
|
|
12655
|
+
if (task2.requires_approval) {
|
|
12656
|
+
const approvalStatus = task2.approved_by ? chalk2.green(`approved by ${task2.approved_by}`) : chalk2.yellow("pending approval");
|
|
12089
12657
|
console.log(` ${chalk2.dim("Approval:")} ${approvalStatus}`);
|
|
12090
12658
|
}
|
|
12091
|
-
if (
|
|
12092
|
-
console.log(` ${chalk2.dim("Estimate:")} ${
|
|
12093
|
-
if (
|
|
12094
|
-
console.log(` ${chalk2.dim("SLA:")} ${
|
|
12095
|
-
if (
|
|
12096
|
-
console.log(` ${chalk2.dim("Due:")} ${
|
|
12097
|
-
if (
|
|
12098
|
-
console.log(` ${chalk2.dim("Repeats:")} ${
|
|
12099
|
-
if (
|
|
12100
|
-
console.log(` ${chalk2.dim("Project:")} ${
|
|
12101
|
-
if (
|
|
12102
|
-
console.log(` ${chalk2.dim("Plan:")} ${
|
|
12103
|
-
if (
|
|
12104
|
-
console.log(` ${chalk2.dim("WorkDir:")} ${
|
|
12105
|
-
if (
|
|
12106
|
-
console.log(` ${chalk2.dim("Parent:")} ${
|
|
12107
|
-
if (
|
|
12108
|
-
console.log(` ${chalk2.dim("Tags:")} ${
|
|
12109
|
-
console.log(` ${chalk2.dim("Version:")} ${
|
|
12110
|
-
console.log(` ${chalk2.dim("Created:")} ${
|
|
12111
|
-
if (
|
|
12112
|
-
console.log(` ${chalk2.dim("Started:")} ${
|
|
12113
|
-
if (
|
|
12114
|
-
console.log(` ${chalk2.dim("Done:")} ${
|
|
12115
|
-
if (
|
|
12116
|
-
const dur = Math.round((new Date(
|
|
12659
|
+
if (task2.estimated_minutes)
|
|
12660
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task2.estimated_minutes} minutes`);
|
|
12661
|
+
if (task2.sla_minutes)
|
|
12662
|
+
console.log(` ${chalk2.dim("SLA:")} ${task2.sla_minutes} minutes`);
|
|
12663
|
+
if (task2.due_at)
|
|
12664
|
+
console.log(` ${chalk2.dim("Due:")} ${task2.due_at}`);
|
|
12665
|
+
if (task2.recurrence_rule)
|
|
12666
|
+
console.log(` ${chalk2.dim("Repeats:")} ${task2.recurrence_rule}`);
|
|
12667
|
+
if (task2.project_id)
|
|
12668
|
+
console.log(` ${chalk2.dim("Project:")} ${task2.project_id}`);
|
|
12669
|
+
if (task2.plan_id)
|
|
12670
|
+
console.log(` ${chalk2.dim("Plan:")} ${task2.plan_id}`);
|
|
12671
|
+
if (task2.working_dir)
|
|
12672
|
+
console.log(` ${chalk2.dim("WorkDir:")} ${task2.working_dir}`);
|
|
12673
|
+
if (task2.parent)
|
|
12674
|
+
console.log(` ${chalk2.dim("Parent:")} ${task2.parent.id.slice(0, 8)} | ${task2.parent.title}`);
|
|
12675
|
+
if (task2.tags.length > 0)
|
|
12676
|
+
console.log(` ${chalk2.dim("Tags:")} ${task2.tags.join(", ")}`);
|
|
12677
|
+
console.log(` ${chalk2.dim("Version:")} ${task2.version}`);
|
|
12678
|
+
console.log(` ${chalk2.dim("Created:")} ${task2.created_at}`);
|
|
12679
|
+
if (task2.started_at)
|
|
12680
|
+
console.log(` ${chalk2.dim("Started:")} ${task2.started_at}`);
|
|
12681
|
+
if (task2.completed_at) {
|
|
12682
|
+
console.log(` ${chalk2.dim("Done:")} ${task2.completed_at}`);
|
|
12683
|
+
if (task2.started_at) {
|
|
12684
|
+
const dur = Math.round((new Date(task2.completed_at).getTime() - new Date(task2.started_at).getTime()) / 60000);
|
|
12117
12685
|
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
12118
12686
|
}
|
|
12119
12687
|
}
|
|
12120
|
-
if (
|
|
12688
|
+
if (task2.subtasks.length > 0) {
|
|
12121
12689
|
console.log(chalk2.bold(`
|
|
12122
|
-
Subtasks (${
|
|
12123
|
-
for (const st of
|
|
12690
|
+
Subtasks (${task2.subtasks.length}):`));
|
|
12691
|
+
for (const st of task2.subtasks) {
|
|
12124
12692
|
console.log(` ${formatTaskLine(st)}`);
|
|
12125
12693
|
}
|
|
12126
12694
|
}
|
|
12127
|
-
if (
|
|
12695
|
+
if (task2.dependencies.length > 0) {
|
|
12128
12696
|
console.log(chalk2.bold(`
|
|
12129
|
-
Depends on (${
|
|
12130
|
-
for (const dep of
|
|
12697
|
+
Depends on (${task2.dependencies.length}):`));
|
|
12698
|
+
for (const dep of task2.dependencies) {
|
|
12131
12699
|
console.log(` ${formatTaskLine(dep)}`);
|
|
12132
12700
|
}
|
|
12133
12701
|
}
|
|
12134
|
-
if (
|
|
12702
|
+
if (task2.blocked_by.length > 0) {
|
|
12135
12703
|
console.log(chalk2.bold(`
|
|
12136
|
-
Blocks (${
|
|
12137
|
-
for (const b of
|
|
12704
|
+
Blocks (${task2.blocked_by.length}):`));
|
|
12705
|
+
for (const b of task2.blocked_by) {
|
|
12138
12706
|
console.log(` ${formatTaskLine(b)}`);
|
|
12139
12707
|
}
|
|
12140
12708
|
}
|
|
12141
|
-
if (
|
|
12709
|
+
if (task2.comments.length > 0) {
|
|
12142
12710
|
console.log(chalk2.bold(`
|
|
12143
|
-
Comments (${
|
|
12144
|
-
for (const c of
|
|
12711
|
+
Comments (${task2.comments.length}):`));
|
|
12712
|
+
for (const c of task2.comments) {
|
|
12145
12713
|
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
12146
12714
|
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
12147
12715
|
}
|
|
@@ -12160,8 +12728,8 @@ function registerTaskCommands(program2) {
|
|
|
12160
12728
|
console.error(chalk2.red("No task ID given and no active task found. Pass an ID or use --agent."));
|
|
12161
12729
|
process.exit(1);
|
|
12162
12730
|
}
|
|
12163
|
-
const
|
|
12164
|
-
if (!
|
|
12731
|
+
const task2 = getTaskWithRelations(resolvedId);
|
|
12732
|
+
if (!task2) {
|
|
12165
12733
|
console.error(chalk2.red(`Task not found: ${id || resolvedId}`));
|
|
12166
12734
|
process.exit(1);
|
|
12167
12735
|
}
|
|
@@ -12169,55 +12737,55 @@ function registerTaskCommands(program2) {
|
|
|
12169
12737
|
const { listTaskFiles: listTaskFiles2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
12170
12738
|
const { getTaskCommits: getTaskCommits2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
12171
12739
|
try {
|
|
12172
|
-
|
|
12740
|
+
task2.files = listTaskFiles2(task2.id);
|
|
12173
12741
|
} catch (e) {
|
|
12174
12742
|
console.error(chalk2.dim(`Warning: could not load task files: ${e instanceof Error ? e.message : String(e)}`));
|
|
12175
12743
|
}
|
|
12176
12744
|
try {
|
|
12177
|
-
|
|
12745
|
+
task2.commits = getTaskCommits2(task2.id);
|
|
12178
12746
|
} catch (e) {
|
|
12179
12747
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
12180
12748
|
}
|
|
12181
|
-
output(
|
|
12749
|
+
output(task2, true);
|
|
12182
12750
|
return;
|
|
12183
12751
|
}
|
|
12184
|
-
const sid =
|
|
12185
|
-
const statusColor = statusColors[
|
|
12186
|
-
const prioColor = priorityColors[
|
|
12752
|
+
const sid = task2.short_id || task2.id.slice(0, 8);
|
|
12753
|
+
const statusColor = statusColors[task2.status] || chalk2.white;
|
|
12754
|
+
const prioColor = priorityColors[task2.priority] || chalk2.white;
|
|
12187
12755
|
console.log(chalk2.bold(`
|
|
12188
|
-
${chalk2.cyan(sid)} ${statusColor(
|
|
12756
|
+
${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${task2.title}
|
|
12189
12757
|
`));
|
|
12190
|
-
if (
|
|
12758
|
+
if (task2.description) {
|
|
12191
12759
|
console.log(chalk2.dim("Description:"));
|
|
12192
|
-
console.log(` ${
|
|
12760
|
+
console.log(` ${task2.description}
|
|
12193
12761
|
`);
|
|
12194
12762
|
}
|
|
12195
|
-
if (
|
|
12196
|
-
console.log(` ${chalk2.dim("Assigned:")} ${
|
|
12197
|
-
if (
|
|
12198
|
-
console.log(` ${chalk2.dim("Locked by:")} ${
|
|
12199
|
-
if (
|
|
12200
|
-
console.log(` ${chalk2.dim("Project:")} ${
|
|
12201
|
-
if (
|
|
12202
|
-
console.log(` ${chalk2.dim("Plan:")} ${
|
|
12203
|
-
if (
|
|
12204
|
-
console.log(` ${chalk2.dim("Started:")} ${
|
|
12205
|
-
if (
|
|
12206
|
-
console.log(` ${chalk2.dim("Completed:")} ${
|
|
12207
|
-
if (
|
|
12208
|
-
const dur = Math.round((new Date(
|
|
12763
|
+
if (task2.assigned_to)
|
|
12764
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task2.assigned_to}`);
|
|
12765
|
+
if (task2.locked_by)
|
|
12766
|
+
console.log(` ${chalk2.dim("Locked by:")} ${task2.locked_by}`);
|
|
12767
|
+
if (task2.project_id)
|
|
12768
|
+
console.log(` ${chalk2.dim("Project:")} ${task2.project_id}`);
|
|
12769
|
+
if (task2.plan_id)
|
|
12770
|
+
console.log(` ${chalk2.dim("Plan:")} ${task2.plan_id}`);
|
|
12771
|
+
if (task2.started_at)
|
|
12772
|
+
console.log(` ${chalk2.dim("Started:")} ${task2.started_at}`);
|
|
12773
|
+
if (task2.completed_at) {
|
|
12774
|
+
console.log(` ${chalk2.dim("Completed:")} ${task2.completed_at}`);
|
|
12775
|
+
if (task2.started_at) {
|
|
12776
|
+
const dur = Math.round((new Date(task2.completed_at).getTime() - new Date(task2.started_at).getTime()) / 60000);
|
|
12209
12777
|
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
12210
12778
|
}
|
|
12211
12779
|
}
|
|
12212
|
-
if (
|
|
12213
|
-
console.log(` ${chalk2.dim("Estimate:")} ${
|
|
12214
|
-
if (
|
|
12215
|
-
console.log(` ${chalk2.dim("Tags:")} ${
|
|
12216
|
-
const unfinishedDeps =
|
|
12217
|
-
if (
|
|
12780
|
+
if (task2.estimated_minutes)
|
|
12781
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task2.estimated_minutes}m`);
|
|
12782
|
+
if (task2.tags.length > 0)
|
|
12783
|
+
console.log(` ${chalk2.dim("Tags:")} ${task2.tags.join(", ")}`);
|
|
12784
|
+
const unfinishedDeps = task2.dependencies.filter((d) => d.status !== "completed" && d.status !== "cancelled");
|
|
12785
|
+
if (task2.dependencies.length > 0) {
|
|
12218
12786
|
console.log(chalk2.bold(`
|
|
12219
|
-
Depends on (${
|
|
12220
|
-
for (const dep of
|
|
12787
|
+
Depends on (${task2.dependencies.length}):`));
|
|
12788
|
+
for (const dep of task2.dependencies) {
|
|
12221
12789
|
const blocked = dep.status !== "completed" && dep.status !== "cancelled";
|
|
12222
12790
|
const icon = blocked ? chalk2.red("\u2717") : chalk2.green("\u2713");
|
|
12223
12791
|
console.log(` ${icon} ${formatTaskLine(dep)}`);
|
|
@@ -12227,21 +12795,21 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12227
12795
|
console.log(chalk2.red(`
|
|
12228
12796
|
BLOCKED by ${unfinishedDeps.length} unfinished dep(s)`));
|
|
12229
12797
|
}
|
|
12230
|
-
if (
|
|
12798
|
+
if (task2.blocked_by.length > 0) {
|
|
12231
12799
|
console.log(chalk2.bold(`
|
|
12232
|
-
Blocks (${
|
|
12233
|
-
for (const b of
|
|
12800
|
+
Blocks (${task2.blocked_by.length}):`));
|
|
12801
|
+
for (const b of task2.blocked_by)
|
|
12234
12802
|
console.log(` ${formatTaskLine(b)}`);
|
|
12235
12803
|
}
|
|
12236
|
-
if (
|
|
12804
|
+
if (task2.subtasks.length > 0) {
|
|
12237
12805
|
console.log(chalk2.bold(`
|
|
12238
|
-
Subtasks (${
|
|
12239
|
-
for (const st of
|
|
12806
|
+
Subtasks (${task2.subtasks.length}):`));
|
|
12807
|
+
for (const st of task2.subtasks)
|
|
12240
12808
|
console.log(` ${formatTaskLine(st)}`);
|
|
12241
12809
|
}
|
|
12242
12810
|
try {
|
|
12243
12811
|
const { listTaskFiles: listTaskFiles2 } = await Promise.resolve().then(() => (init_task_files(), exports_task_files));
|
|
12244
|
-
const files = listTaskFiles2(
|
|
12812
|
+
const files = listTaskFiles2(task2.id);
|
|
12245
12813
|
if (files.length > 0) {
|
|
12246
12814
|
console.log(chalk2.bold(`
|
|
12247
12815
|
Files (${files.length}):`));
|
|
@@ -12253,7 +12821,7 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12253
12821
|
}
|
|
12254
12822
|
try {
|
|
12255
12823
|
const { getTaskCommits: getTaskCommits2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
|
|
12256
|
-
const commits = getTaskCommits2(
|
|
12824
|
+
const commits = getTaskCommits2(task2.id);
|
|
12257
12825
|
if (commits.length > 0) {
|
|
12258
12826
|
console.log(chalk2.bold(`
|
|
12259
12827
|
Commits (${commits.length}):`));
|
|
@@ -12263,19 +12831,19 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12263
12831
|
} catch (e) {
|
|
12264
12832
|
console.error(chalk2.dim(`Warning: could not load task commits: ${e instanceof Error ? e.message : String(e)}`));
|
|
12265
12833
|
}
|
|
12266
|
-
if (
|
|
12834
|
+
if (task2.comments.length > 0) {
|
|
12267
12835
|
console.log(chalk2.bold(`
|
|
12268
|
-
Comments (${
|
|
12269
|
-
for (const c of
|
|
12836
|
+
Comments (${task2.comments.length}):`));
|
|
12837
|
+
for (const c of task2.comments) {
|
|
12270
12838
|
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
12271
12839
|
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
12272
12840
|
}
|
|
12273
12841
|
}
|
|
12274
|
-
if (
|
|
12275
|
-
const done =
|
|
12842
|
+
if (task2.checklist && task2.checklist.length > 0) {
|
|
12843
|
+
const done = task2.checklist.filter((c) => c.checked).length;
|
|
12276
12844
|
console.log(chalk2.bold(`
|
|
12277
|
-
Checklist (${done}/${
|
|
12278
|
-
for (const item of
|
|
12845
|
+
Checklist (${done}/${task2.checklist.length}):`));
|
|
12846
|
+
for (const item of task2.checklist) {
|
|
12279
12847
|
const icon = item.checked ? chalk2.green("\u2611") : chalk2.dim("\u2610");
|
|
12280
12848
|
console.log(` ${icon} ${item.text || item.title}`);
|
|
12281
12849
|
}
|
|
@@ -12328,9 +12896,9 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12328
12896
|
return resolved;
|
|
12329
12897
|
})() : undefined;
|
|
12330
12898
|
const planId = opts.plan ? resolvePlanId(opts.plan) : opts.clearPlan ? null : undefined;
|
|
12331
|
-
let
|
|
12899
|
+
let task2;
|
|
12332
12900
|
try {
|
|
12333
|
-
|
|
12901
|
+
task2 = updateTask(resolvedId, {
|
|
12334
12902
|
version: current.version,
|
|
12335
12903
|
title: opts.title,
|
|
12336
12904
|
description: opts.description,
|
|
@@ -12350,10 +12918,10 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12350
12918
|
handleError(e);
|
|
12351
12919
|
}
|
|
12352
12920
|
if (globalOpts.json) {
|
|
12353
|
-
output(
|
|
12921
|
+
output(task2, true);
|
|
12354
12922
|
} else {
|
|
12355
12923
|
console.log(chalk2.green("Task updated:"));
|
|
12356
|
-
console.log(formatTaskLine(
|
|
12924
|
+
console.log(formatTaskLine(task2));
|
|
12357
12925
|
}
|
|
12358
12926
|
});
|
|
12359
12927
|
program2.command("done <id>").description("Mark a task as completed").option("--attach-ids <ids>", "Comma-separated @hasna/attachments IDs to link as evidence").option("--files-changed <files>", "Comma-separated list of files changed").option("--test-results <results>", "Test results summary").option("--commit-hash <hash>", "Git commit hash").option("--notes <notes>", "Completion notes").option("--confidence <0-1>", "Agent's confidence 0.0-1.0 that the task is fully complete (default: 1.0, <0.7 flagged for review)").action((id, opts) => {
|
|
@@ -12363,37 +12931,37 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12363
12931
|
const filesChanged = opts.filesChanged ? opts.filesChanged.split(",").map((s) => s.trim()) : undefined;
|
|
12364
12932
|
const confidence = opts.confidence !== undefined ? parseFloat(opts.confidence) : undefined;
|
|
12365
12933
|
const evidence = attachmentIds || filesChanged || opts.testResults || opts.commitHash || opts.notes ? { attachment_ids: attachmentIds, files_changed: filesChanged, test_results: opts.testResults, commit_hash: opts.commitHash, notes: opts.notes } : undefined;
|
|
12366
|
-
let
|
|
12934
|
+
let task2;
|
|
12367
12935
|
try {
|
|
12368
|
-
|
|
12936
|
+
task2 = completeTask(resolvedId, globalOpts.agent, undefined, { ...evidence, confidence });
|
|
12369
12937
|
} catch (e) {
|
|
12370
12938
|
handleError(e);
|
|
12371
12939
|
}
|
|
12372
12940
|
if (globalOpts.json) {
|
|
12373
|
-
output(
|
|
12941
|
+
output(task2, true);
|
|
12374
12942
|
} else {
|
|
12375
12943
|
console.log(chalk2.green("Task completed:"));
|
|
12376
|
-
console.log(formatTaskLine(
|
|
12944
|
+
console.log(formatTaskLine(task2));
|
|
12377
12945
|
}
|
|
12378
12946
|
});
|
|
12379
12947
|
program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
|
|
12380
12948
|
const globalOpts = program2.opts();
|
|
12381
12949
|
const resolvedId = resolveTaskId(id);
|
|
12382
|
-
const
|
|
12383
|
-
if (!
|
|
12950
|
+
const task2 = getTask(resolvedId);
|
|
12951
|
+
if (!task2) {
|
|
12384
12952
|
console.error(chalk2.red(`Task not found: ${id}`));
|
|
12385
12953
|
process.exit(1);
|
|
12386
12954
|
}
|
|
12387
|
-
if (!
|
|
12955
|
+
if (!task2.requires_approval) {
|
|
12388
12956
|
console.log(chalk2.yellow("This task does not require approval."));
|
|
12389
12957
|
return;
|
|
12390
12958
|
}
|
|
12391
|
-
if (
|
|
12392
|
-
console.log(chalk2.yellow(`Already approved by ${
|
|
12959
|
+
if (task2.approved_by) {
|
|
12960
|
+
console.log(chalk2.yellow(`Already approved by ${task2.approved_by}.`));
|
|
12393
12961
|
return;
|
|
12394
12962
|
}
|
|
12395
12963
|
try {
|
|
12396
|
-
const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version:
|
|
12964
|
+
const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version: task2.version });
|
|
12397
12965
|
if (globalOpts.json) {
|
|
12398
12966
|
output(updated, true);
|
|
12399
12967
|
} else {
|
|
@@ -12408,17 +12976,17 @@ ${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${ta
|
|
|
12408
12976
|
const globalOpts = program2.opts();
|
|
12409
12977
|
const agentId = globalOpts.agent || "cli";
|
|
12410
12978
|
const resolvedId = resolveTaskId(id);
|
|
12411
|
-
let
|
|
12979
|
+
let task2;
|
|
12412
12980
|
try {
|
|
12413
|
-
|
|
12981
|
+
task2 = startTask(resolvedId, agentId);
|
|
12414
12982
|
} catch (e) {
|
|
12415
12983
|
handleError(e);
|
|
12416
12984
|
}
|
|
12417
12985
|
if (globalOpts.json) {
|
|
12418
|
-
output(
|
|
12986
|
+
output(task2, true);
|
|
12419
12987
|
} else {
|
|
12420
12988
|
console.log(chalk2.green(`Task started by ${agentId}:`));
|
|
12421
|
-
console.log(formatTaskLine(
|
|
12989
|
+
console.log(formatTaskLine(task2));
|
|
12422
12990
|
}
|
|
12423
12991
|
});
|
|
12424
12992
|
program2.command("lock <id>").description("Acquire exclusive lock on a task").action((id) => {
|
|
@@ -13591,7 +14159,7 @@ function likePattern(query) {
|
|
|
13591
14159
|
return null;
|
|
13592
14160
|
return `%${trimmed}%`;
|
|
13593
14161
|
}
|
|
13594
|
-
function
|
|
14162
|
+
function parseJsonObject4(value) {
|
|
13595
14163
|
if (!value)
|
|
13596
14164
|
return {};
|
|
13597
14165
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -13606,7 +14174,7 @@ function parseJsonObject3(value) {
|
|
|
13606
14174
|
}
|
|
13607
14175
|
}
|
|
13608
14176
|
function rowToTaskRun(row) {
|
|
13609
|
-
return { ...row, metadata:
|
|
14177
|
+
return { ...row, metadata: parseJsonObject4(row.metadata) };
|
|
13610
14178
|
}
|
|
13611
14179
|
function taskMatchesSavedFilters(task, filters, db) {
|
|
13612
14180
|
if (filters.plan_id && task.plan_id !== filters.plan_id)
|
|
@@ -14969,7 +15537,7 @@ function packageSource(version) {
|
|
|
14969
15537
|
function emptyCounts() {
|
|
14970
15538
|
return Object.fromEntries(dataKeys.map((key) => [key, 0]));
|
|
14971
15539
|
}
|
|
14972
|
-
function
|
|
15540
|
+
function parseJsonObject5(value) {
|
|
14973
15541
|
if (!value)
|
|
14974
15542
|
return {};
|
|
14975
15543
|
if (typeof value === "object" && !Array.isArray(value))
|
|
@@ -15015,34 +15583,34 @@ function rowToTask3(row) {
|
|
|
15015
15583
|
return {
|
|
15016
15584
|
...row,
|
|
15017
15585
|
tags: parseJsonArray2(row.tags),
|
|
15018
|
-
metadata:
|
|
15586
|
+
metadata: parseJsonObject5(row.metadata),
|
|
15019
15587
|
requires_approval: Boolean(row.requires_approval)
|
|
15020
15588
|
};
|
|
15021
15589
|
}
|
|
15022
15590
|
function rowToTaskList2(row) {
|
|
15023
|
-
return { ...row, metadata:
|
|
15591
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
15024
15592
|
}
|
|
15025
15593
|
function rowWithMetadata(row) {
|
|
15026
|
-
return { ...row, metadata:
|
|
15594
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
15027
15595
|
}
|
|
15028
15596
|
function rowToRunEvent(row) {
|
|
15029
|
-
return { ...row, data:
|
|
15597
|
+
return { ...row, data: parseJsonObject5(row.data) };
|
|
15030
15598
|
}
|
|
15031
15599
|
function rowToCommit2(row) {
|
|
15032
15600
|
return { ...row, files_changed: row.files_changed ? parseJsonArray2(row.files_changed) : null };
|
|
15033
15601
|
}
|
|
15034
15602
|
function rowToSavedView(row) {
|
|
15035
|
-
return { ...row, filters:
|
|
15603
|
+
return { ...row, filters: parseJsonObject5(row.filters) };
|
|
15036
15604
|
}
|
|
15037
15605
|
function rowToTaskBoard2(row) {
|
|
15038
15606
|
return {
|
|
15039
15607
|
...row,
|
|
15040
15608
|
lanes: parseJsonArray2(row.lanes),
|
|
15041
|
-
filters:
|
|
15609
|
+
filters: parseJsonObject5(row.filters)
|
|
15042
15610
|
};
|
|
15043
15611
|
}
|
|
15044
15612
|
function rowToCalendarItem2(row) {
|
|
15045
|
-
return { ...row, metadata:
|
|
15613
|
+
return { ...row, metadata: parseJsonObject5(row.metadata) };
|
|
15046
15614
|
}
|
|
15047
15615
|
function bridgeStats(data) {
|
|
15048
15616
|
return Object.fromEntries(dataKeys.map((key) => [key, data[key].length]));
|
|
@@ -18389,6 +18957,7 @@ var init_token_utils = __esm(() => {
|
|
|
18389
18957
|
"add_task_run_event",
|
|
18390
18958
|
"add_task_run_file",
|
|
18391
18959
|
"acknowledge_handoff",
|
|
18960
|
+
"begin_task_run_transaction",
|
|
18392
18961
|
"build_local_report",
|
|
18393
18962
|
"cancel_agent_run_dispatch",
|
|
18394
18963
|
"finish_task_run",
|
|
@@ -18436,6 +19005,7 @@ var init_token_utils = __esm(() => {
|
|
|
18436
19005
|
"list_local_snapshots",
|
|
18437
19006
|
"list_retrospectives",
|
|
18438
19007
|
"list_risks",
|
|
19008
|
+
"list_task_findings",
|
|
18439
19009
|
"list_task_runs",
|
|
18440
19010
|
"list_verification_providers",
|
|
18441
19011
|
"merge_duplicate_task",
|
|
@@ -18444,6 +19014,7 @@ var init_token_utils = __esm(() => {
|
|
|
18444
19014
|
"remove_review_routing_rule",
|
|
18445
19015
|
"restore_local_backup",
|
|
18446
19016
|
"retry_agent_run_dispatch",
|
|
19017
|
+
"resolve_missing_task_findings",
|
|
18447
19018
|
"resolve_mentions",
|
|
18448
19019
|
"run_next_agent_dispatch",
|
|
18449
19020
|
"search_knowledge_records",
|
|
@@ -18486,9 +19057,17 @@ var init_token_utils = __esm(() => {
|
|
|
18486
19057
|
"unlock_file",
|
|
18487
19058
|
"unwatch_task",
|
|
18488
19059
|
"update_comment",
|
|
19060
|
+
"upsert_task_finding",
|
|
18489
19061
|
"update_risk",
|
|
18490
19062
|
"watch_task"
|
|
18491
19063
|
],
|
|
19064
|
+
loops: [
|
|
19065
|
+
"begin_task_run_transaction",
|
|
19066
|
+
"finish_task_run",
|
|
19067
|
+
"list_task_findings",
|
|
19068
|
+
"resolve_missing_task_findings",
|
|
19069
|
+
"upsert_task_finding"
|
|
19070
|
+
],
|
|
18492
19071
|
agents: [
|
|
18493
19072
|
"auto_assign_task",
|
|
18494
19073
|
"delete_agent",
|
|
@@ -18570,7 +19149,7 @@ var init_token_utils = __esm(() => {
|
|
|
18570
19149
|
maintenance: ["extract_todos", "get_sla_breaches", "notify_upcoming_deadlines", "run_doctor", "score_task", "watch_source_todos"]
|
|
18571
19150
|
};
|
|
18572
19151
|
MCP_PROFILE_GROUPS = {
|
|
18573
|
-
minimal: ["core"],
|
|
19152
|
+
minimal: ["core", "loops"],
|
|
18574
19153
|
core: ["core"],
|
|
18575
19154
|
standard: ["core", "tasks", "projects", "resources", "agents", "metadata"],
|
|
18576
19155
|
agent: ["core", "tasks", "projects", "resources"],
|
|
@@ -20863,6 +21442,39 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
20863
21442
|
return json2({ error: e instanceof Error ? e.message : "Failed to create task" }, 500);
|
|
20864
21443
|
}
|
|
20865
21444
|
}
|
|
21445
|
+
async function handleUpsertTask(req, ctx, json2, taskToSummary2) {
|
|
21446
|
+
try {
|
|
21447
|
+
const body = await req.json();
|
|
21448
|
+
if (typeof body["fingerprint"] !== "string" || body["fingerprint"].trim() === "") {
|
|
21449
|
+
return json2({ error: "Missing 'fingerprint'" }, 400);
|
|
21450
|
+
}
|
|
21451
|
+
if (typeof body["title"] !== "string" || body["title"].trim() === "") {
|
|
21452
|
+
return json2({ error: "Missing 'title'" }, 400);
|
|
21453
|
+
}
|
|
21454
|
+
const metadata = body["metadata"] && typeof body["metadata"] === "object" && !Array.isArray(body["metadata"]) ? { ...body["metadata"] } : {};
|
|
21455
|
+
for (const key of ["expectation_id", "expectation_fingerprint", "evidence_paths", "origin_loop_id", "origin_run_id", "expected", "observed", "acceptance"]) {
|
|
21456
|
+
if (body[key] !== undefined)
|
|
21457
|
+
metadata[key] = body[key];
|
|
21458
|
+
}
|
|
21459
|
+
const result = upsertTaskByFingerprint({
|
|
21460
|
+
fingerprint: body["fingerprint"],
|
|
21461
|
+
title: body["title"],
|
|
21462
|
+
description: typeof body["description"] === "string" ? body["description"] : undefined,
|
|
21463
|
+
status: body["status"],
|
|
21464
|
+
priority: body["priority"],
|
|
21465
|
+
project_id: typeof body["project_id"] === "string" ? body["project_id"] : undefined,
|
|
21466
|
+
task_list_id: typeof body["task_list_id"] === "string" ? body["task_list_id"] : undefined,
|
|
21467
|
+
assigned_to: typeof body["assigned_to"] === "string" ? body["assigned_to"] : undefined,
|
|
21468
|
+
working_dir: typeof body["working_dir"] === "string" ? body["working_dir"] : undefined,
|
|
21469
|
+
tags: Array.isArray(body["tags"]) ? body["tags"].filter((tag) => typeof tag === "string") : undefined,
|
|
21470
|
+
metadata
|
|
21471
|
+
});
|
|
21472
|
+
ctx.broadcastEvent({ type: "task", task_id: result.task.id, action: result.created ? "created" : "updated", agent_id: result.task.agent_id, project_id: result.task.project_id });
|
|
21473
|
+
return json2({ created: result.created, task: taskToSummary2(result.task) }, result.created ? 201 : 200);
|
|
21474
|
+
} catch (e) {
|
|
21475
|
+
return json2({ error: e instanceof Error ? e.message : "Failed to upsert task" }, 500);
|
|
21476
|
+
}
|
|
21477
|
+
}
|
|
20866
21478
|
function handleTasksExport(_req, url, _ctx, _json, taskToSummary2) {
|
|
20867
21479
|
const format = url.searchParams.get("format") || "json";
|
|
20868
21480
|
const status = url.searchParams.get("status") || undefined;
|
|
@@ -26005,6 +26617,61 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26005
26617
|
}
|
|
26006
26618
|
});
|
|
26007
26619
|
}
|
|
26620
|
+
if (shouldRegisterTool("upsert_task")) {
|
|
26621
|
+
server.tool("upsert_task", "Create or update a task by stable metadata fingerprint. Metadata is shallow-merged on updates.", {
|
|
26622
|
+
fingerprint: exports_external.string().describe("Stable dedupe fingerprint stored as metadata.fingerprint"),
|
|
26623
|
+
title: exports_external.string().describe("Task title"),
|
|
26624
|
+
description: exports_external.string().optional().describe("Task description"),
|
|
26625
|
+
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
26626
|
+
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
26627
|
+
project_id: exports_external.string().optional().describe("Project ID"),
|
|
26628
|
+
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
26629
|
+
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
26630
|
+
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
26631
|
+
working_dir: exports_external.string().optional().describe("Working directory associated with the task"),
|
|
26632
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Metadata object to shallow-merge"),
|
|
26633
|
+
expectation_id: exports_external.string().optional(),
|
|
26634
|
+
expectation_fingerprint: exports_external.string().optional(),
|
|
26635
|
+
evidence_paths: exports_external.array(exports_external.string()).optional(),
|
|
26636
|
+
origin_loop_id: exports_external.string().optional(),
|
|
26637
|
+
origin_run_id: exports_external.string().optional(),
|
|
26638
|
+
expected: exports_external.unknown().optional(),
|
|
26639
|
+
observed: exports_external.unknown().optional(),
|
|
26640
|
+
acceptance: exports_external.unknown().optional()
|
|
26641
|
+
}, async (params) => {
|
|
26642
|
+
try {
|
|
26643
|
+
const { assigned_to, project_id, task_list_id, metadata, expectation_id, expectation_fingerprint, evidence_paths, origin_loop_id, origin_run_id, expected, observed, acceptance, ...rest } = params;
|
|
26644
|
+
const mergedMetadata = { ...metadata ?? {} };
|
|
26645
|
+
if (expectation_id !== undefined)
|
|
26646
|
+
mergedMetadata["expectation_id"] = expectation_id;
|
|
26647
|
+
if (expectation_fingerprint !== undefined)
|
|
26648
|
+
mergedMetadata["expectation_fingerprint"] = expectation_fingerprint;
|
|
26649
|
+
if (evidence_paths !== undefined)
|
|
26650
|
+
mergedMetadata["evidence_paths"] = evidence_paths;
|
|
26651
|
+
if (origin_loop_id !== undefined)
|
|
26652
|
+
mergedMetadata["origin_loop_id"] = origin_loop_id;
|
|
26653
|
+
if (origin_run_id !== undefined)
|
|
26654
|
+
mergedMetadata["origin_run_id"] = origin_run_id;
|
|
26655
|
+
if (expected !== undefined)
|
|
26656
|
+
mergedMetadata["expected"] = expected;
|
|
26657
|
+
if (observed !== undefined)
|
|
26658
|
+
mergedMetadata["observed"] = observed;
|
|
26659
|
+
if (acceptance !== undefined)
|
|
26660
|
+
mergedMetadata["acceptance"] = acceptance;
|
|
26661
|
+
const resolved = { ...rest, metadata: mergedMetadata };
|
|
26662
|
+
if (assigned_to)
|
|
26663
|
+
resolved.assigned_to = resolveAssignee(assigned_to);
|
|
26664
|
+
if (project_id)
|
|
26665
|
+
resolved.project_id = resolveId(project_id, "projects");
|
|
26666
|
+
if (task_list_id)
|
|
26667
|
+
resolved.task_list_id = resolveId(task_list_id, "task_lists");
|
|
26668
|
+
const result = upsertTaskByFingerprint(resolved);
|
|
26669
|
+
return { content: [{ type: "text", text: compactJson({ created: result.created, task: JSON.parse(mutationTaskResponse(result.task)) }) }] };
|
|
26670
|
+
} catch (e) {
|
|
26671
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
26672
|
+
}
|
|
26673
|
+
});
|
|
26674
|
+
}
|
|
26008
26675
|
if (shouldRegisterTool("list_tasks")) {
|
|
26009
26676
|
server.tool("list_tasks", "List tasks with optional filters. Pass empty arrays for multi-value filters (e.g. status=[] shows all).", {
|
|
26010
26677
|
status: exports_external.union([exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]), exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))]).optional().describe("Filter by status"),
|
|
@@ -26016,7 +26683,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
26016
26683
|
created_after: exports_external.string().optional().describe("ISO date \u2014 tasks created after this date"),
|
|
26017
26684
|
created_before: exports_external.string().optional().describe("ISO date \u2014 tasks created before this date"),
|
|
26018
26685
|
limit: exports_external.number().optional().describe("Max results (default: 50, max 500)"),
|
|
26019
|
-
offset: exports_external.number().optional().describe("Pagination offset")
|
|
26686
|
+
offset: exports_external.number().optional().describe("Pagination offset"),
|
|
26687
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Exact top-level metadata filters")
|
|
26020
26688
|
}, async (params) => {
|
|
26021
26689
|
try {
|
|
26022
26690
|
const resolved = { ...params };
|
|
@@ -27604,7 +28272,7 @@ function parseAssignee(line) {
|
|
|
27604
28272
|
return mention;
|
|
27605
28273
|
return line.match(/\bassign(?:ed)?\s+(?:to\s+)?([a-zA-Z0-9._-]+)/i)?.[1] || null;
|
|
27606
28274
|
}
|
|
27607
|
-
function
|
|
28275
|
+
function parseTags2(line) {
|
|
27608
28276
|
return Array.from(new Set(Array.from(line.matchAll(/#([a-zA-Z0-9._-]+)/g)).map((match) => match[1].toLowerCase()))).slice(0, 10);
|
|
27609
28277
|
}
|
|
27610
28278
|
function parseDependencies(line) {
|
|
@@ -27676,7 +28344,7 @@ function previewNaturalLanguageIntake(input, db) {
|
|
|
27676
28344
|
description: `Parsed from local natural-language intake:
|
|
27677
28345
|
${line}`,
|
|
27678
28346
|
priority: parsePriority2(line, fallbackPriority),
|
|
27679
|
-
tags:
|
|
28347
|
+
tags: parseTags2(line),
|
|
27680
28348
|
assigned_to: parseAssignee(line),
|
|
27681
28349
|
due_at: parseDue(line, referenceDate),
|
|
27682
28350
|
depends_on: parseDependencies(line),
|
|
@@ -28184,7 +28852,7 @@ function countMovedStandaloneVerifications(db, duplicateId) {
|
|
|
28184
28852
|
const rows = db.query("SELECT command FROM task_verifications WHERE task_id = ?").all(duplicateId);
|
|
28185
28853
|
return rows.filter((row) => !generatedCommands.has(row.command)).length;
|
|
28186
28854
|
}
|
|
28187
|
-
function
|
|
28855
|
+
function mergeTaskMetadata2(primary, duplicate, input, mergedAt) {
|
|
28188
28856
|
const mergedDuplicates = Array.isArray(primary.metadata["merged_duplicates"]) ? [...primary.metadata["merged_duplicates"]] : [];
|
|
28189
28857
|
mergedDuplicates.push({
|
|
28190
28858
|
id: duplicate.id,
|
|
@@ -28245,7 +28913,7 @@ function mergeDuplicateTask(input, db) {
|
|
|
28245
28913
|
updateTask(primary.id, {
|
|
28246
28914
|
version: primary.version,
|
|
28247
28915
|
tags: mergedTags,
|
|
28248
|
-
metadata:
|
|
28916
|
+
metadata: mergeTaskMetadata2(primary, duplicate, input, mergedAt),
|
|
28249
28917
|
description: mergeTaskDescription(primary, duplicate) ?? undefined
|
|
28250
28918
|
}, d);
|
|
28251
28919
|
moved.comments = updateRows(d, "task_comments", "task_id", duplicate.id, primary.id);
|
|
@@ -34239,6 +34907,366 @@ var init_task_meta_tools = __esm(() => {
|
|
|
34239
34907
|
init_zod();
|
|
34240
34908
|
});
|
|
34241
34909
|
|
|
34910
|
+
// src/db/findings.ts
|
|
34911
|
+
var exports_findings = {};
|
|
34912
|
+
__export(exports_findings, {
|
|
34913
|
+
upsertTaskFinding: () => upsertTaskFinding,
|
|
34914
|
+
resolveMissingTaskFindings: () => resolveMissingTaskFindings,
|
|
34915
|
+
listTaskFindings: () => listTaskFindings,
|
|
34916
|
+
listCompactTaskFindings: () => listCompactTaskFindings,
|
|
34917
|
+
TASK_FINDING_UPSERT_SCHEMA_VERSION: () => TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
34918
|
+
TASK_FINDING_SCHEMA_VERSION: () => TASK_FINDING_SCHEMA_VERSION,
|
|
34919
|
+
TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION: () => TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION
|
|
34920
|
+
});
|
|
34921
|
+
function parseObject2(value) {
|
|
34922
|
+
if (!value)
|
|
34923
|
+
return {};
|
|
34924
|
+
try {
|
|
34925
|
+
const parsed = JSON.parse(value);
|
|
34926
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
34927
|
+
} catch {
|
|
34928
|
+
return {};
|
|
34929
|
+
}
|
|
34930
|
+
}
|
|
34931
|
+
function normalizeKey(value) {
|
|
34932
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
34933
|
+
}
|
|
34934
|
+
function normalizeFingerprint(value) {
|
|
34935
|
+
const normalized = normalizeKey(value);
|
|
34936
|
+
if (!normalized)
|
|
34937
|
+
throw new Error("finding fingerprint is required");
|
|
34938
|
+
return normalized.slice(0, 240);
|
|
34939
|
+
}
|
|
34940
|
+
function normalizeSeverity(value) {
|
|
34941
|
+
const normalized = normalizeKey(value || "medium");
|
|
34942
|
+
if (SEVERITIES.has(normalized))
|
|
34943
|
+
return normalized;
|
|
34944
|
+
if (/^(p0|blocker|urgent|highest)$/.test(normalized))
|
|
34945
|
+
return "critical";
|
|
34946
|
+
if (/^(p1|major)$/.test(normalized))
|
|
34947
|
+
return "high";
|
|
34948
|
+
if (/^(p3|minor|info)$/.test(normalized))
|
|
34949
|
+
return "low";
|
|
34950
|
+
return "medium";
|
|
34951
|
+
}
|
|
34952
|
+
function normalizeStatus2(value) {
|
|
34953
|
+
const normalized = normalizeKey(value || "open");
|
|
34954
|
+
if (STATUSES.has(normalized))
|
|
34955
|
+
return normalized;
|
|
34956
|
+
if (normalized === "closed" || normalized === "fixed")
|
|
34957
|
+
return "resolved";
|
|
34958
|
+
return "open";
|
|
34959
|
+
}
|
|
34960
|
+
function normalizeResolutionStatus(value) {
|
|
34961
|
+
const status = normalizeStatus2(value || "resolved");
|
|
34962
|
+
if (status === "open")
|
|
34963
|
+
throw new Error("resolve-missing status must be resolved or ignored");
|
|
34964
|
+
return status;
|
|
34965
|
+
}
|
|
34966
|
+
function redactOptional(value, max = 2000) {
|
|
34967
|
+
if (!value)
|
|
34968
|
+
return null;
|
|
34969
|
+
const redacted = redactEvidenceText(value).trim();
|
|
34970
|
+
if (!redacted)
|
|
34971
|
+
return null;
|
|
34972
|
+
return redacted.length > max ? `${redacted.slice(0, max - 3)}...` : redacted;
|
|
34973
|
+
}
|
|
34974
|
+
function rowToFinding(row) {
|
|
34975
|
+
return {
|
|
34976
|
+
schema_version: TASK_FINDING_SCHEMA_VERSION,
|
|
34977
|
+
...row,
|
|
34978
|
+
severity: normalizeSeverity(row.severity),
|
|
34979
|
+
status: normalizeStatus2(row.status),
|
|
34980
|
+
metadata: parseObject2(row.metadata)
|
|
34981
|
+
};
|
|
34982
|
+
}
|
|
34983
|
+
function compactFinding(finding2) {
|
|
34984
|
+
return {
|
|
34985
|
+
schema_version: TASK_FINDING_SCHEMA_VERSION,
|
|
34986
|
+
id: finding2.id,
|
|
34987
|
+
task_id: finding2.task_id,
|
|
34988
|
+
run_id: finding2.run_id,
|
|
34989
|
+
fingerprint: finding2.fingerprint,
|
|
34990
|
+
title: finding2.title,
|
|
34991
|
+
severity: finding2.severity,
|
|
34992
|
+
status: finding2.status,
|
|
34993
|
+
source: finding2.source,
|
|
34994
|
+
summary: finding2.summary,
|
|
34995
|
+
artifact_path: finding2.artifact_path,
|
|
34996
|
+
first_seen_at: finding2.first_seen_at,
|
|
34997
|
+
last_seen_at: finding2.last_seen_at,
|
|
34998
|
+
resolved_at: finding2.resolved_at,
|
|
34999
|
+
metadata_keys: Object.keys(finding2.metadata).sort()
|
|
35000
|
+
};
|
|
35001
|
+
}
|
|
35002
|
+
function previewFinding(existing, next, timestamp3) {
|
|
35003
|
+
return {
|
|
35004
|
+
...existing,
|
|
35005
|
+
run_id: next.run_id,
|
|
35006
|
+
title: next.title,
|
|
35007
|
+
severity: next.severity,
|
|
35008
|
+
status: next.status,
|
|
35009
|
+
source: next.source,
|
|
35010
|
+
summary: next.summary,
|
|
35011
|
+
artifact_path: next.artifact_path,
|
|
35012
|
+
metadata: next.metadata,
|
|
35013
|
+
last_seen_at: timestamp3,
|
|
35014
|
+
resolved_at: next.status === "open" ? null : existing.resolved_at || timestamp3,
|
|
35015
|
+
updated_at: timestamp3
|
|
35016
|
+
};
|
|
35017
|
+
}
|
|
35018
|
+
function upsertAction(existing, next) {
|
|
35019
|
+
if (sameFinding(existing, next))
|
|
35020
|
+
return "matched";
|
|
35021
|
+
return existing.status !== "open" && next.status === "open" ? "reopened" : "updated";
|
|
35022
|
+
}
|
|
35023
|
+
function resolveRunForTask(runId, taskId, db) {
|
|
35024
|
+
if (!runId)
|
|
35025
|
+
return null;
|
|
35026
|
+
const resolved = resolveTaskRunId(runId, db);
|
|
35027
|
+
const run = getTaskRun(resolved, db);
|
|
35028
|
+
if (!run)
|
|
35029
|
+
throw new Error(`Run not found: ${runId}`);
|
|
35030
|
+
if (run.task_id !== taskId)
|
|
35031
|
+
throw new Error(`Run ${resolved} belongs to task ${run.task_id}, not ${taskId}`);
|
|
35032
|
+
return resolved;
|
|
35033
|
+
}
|
|
35034
|
+
function getFindingByFingerprint(taskId, fingerprint2, db) {
|
|
35035
|
+
const row = db.query("SELECT * FROM task_findings WHERE task_id = ? AND fingerprint = ?").get(taskId, fingerprint2);
|
|
35036
|
+
return row ? rowToFinding(row) : null;
|
|
35037
|
+
}
|
|
35038
|
+
function assertTask(taskId, db) {
|
|
35039
|
+
if (!getTask(taskId, db))
|
|
35040
|
+
throw new TaskNotFoundError(taskId);
|
|
35041
|
+
}
|
|
35042
|
+
function nextFinding(input, db) {
|
|
35043
|
+
const fingerprint2 = normalizeFingerprint(input.fingerprint);
|
|
35044
|
+
const title = redactOptional(input.title, 300);
|
|
35045
|
+
if (!title)
|
|
35046
|
+
throw new Error("finding title is required");
|
|
35047
|
+
return {
|
|
35048
|
+
fingerprint: fingerprint2,
|
|
35049
|
+
run_id: resolveRunForTask(input.run_id, input.task_id, db),
|
|
35050
|
+
title,
|
|
35051
|
+
severity: normalizeSeverity(input.severity),
|
|
35052
|
+
status: normalizeStatus2(input.status),
|
|
35053
|
+
source: redactOptional(input.source, 120),
|
|
35054
|
+
summary: redactOptional(input.summary, 2000),
|
|
35055
|
+
artifact_path: redactOptional(input.artifact_path, 1000),
|
|
35056
|
+
metadata: redactValue(input.metadata || {})
|
|
35057
|
+
};
|
|
35058
|
+
}
|
|
35059
|
+
function sameFinding(left, right) {
|
|
35060
|
+
return left.run_id === right.run_id && left.title === right.title && left.severity === right.severity && left.status === right.status && left.source === right.source && left.summary === right.summary && left.artifact_path === right.artifact_path && JSON.stringify(left.metadata) === JSON.stringify(right.metadata);
|
|
35061
|
+
}
|
|
35062
|
+
function upsertTaskFinding(input, db) {
|
|
35063
|
+
const d = db || getDatabase();
|
|
35064
|
+
assertTask(input.task_id, d);
|
|
35065
|
+
const timestamp3 = input.observed_at || now();
|
|
35066
|
+
const warnings = [];
|
|
35067
|
+
const next = nextFinding(input, d);
|
|
35068
|
+
const existing = getFindingByFingerprint(input.task_id, next.fingerprint, d);
|
|
35069
|
+
const dryRun = !input.apply;
|
|
35070
|
+
if (dryRun) {
|
|
35071
|
+
const action2 = existing ? upsertAction(existing, next) : "preview";
|
|
35072
|
+
return {
|
|
35073
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35074
|
+
local_only: true,
|
|
35075
|
+
dry_run: true,
|
|
35076
|
+
processed_at: timestamp3,
|
|
35077
|
+
action: action2,
|
|
35078
|
+
fingerprint: next.fingerprint,
|
|
35079
|
+
finding: existing ? compactFinding(previewFinding(existing, next, timestamp3)) : null,
|
|
35080
|
+
warnings
|
|
35081
|
+
};
|
|
35082
|
+
}
|
|
35083
|
+
if (!existing) {
|
|
35084
|
+
const id = uuid();
|
|
35085
|
+
d.run(`INSERT INTO task_findings (
|
|
35086
|
+
id, task_id, run_id, fingerprint, title, severity, status, source, summary, artifact_path,
|
|
35087
|
+
metadata, first_seen_at, last_seen_at, resolved_at, created_at, updated_at
|
|
35088
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
35089
|
+
id,
|
|
35090
|
+
input.task_id,
|
|
35091
|
+
next.run_id,
|
|
35092
|
+
next.fingerprint,
|
|
35093
|
+
next.title,
|
|
35094
|
+
next.severity,
|
|
35095
|
+
next.status,
|
|
35096
|
+
next.source,
|
|
35097
|
+
next.summary,
|
|
35098
|
+
next.artifact_path,
|
|
35099
|
+
JSON.stringify(next.metadata),
|
|
35100
|
+
timestamp3,
|
|
35101
|
+
timestamp3,
|
|
35102
|
+
next.status === "open" ? null : timestamp3,
|
|
35103
|
+
timestamp3,
|
|
35104
|
+
timestamp3
|
|
35105
|
+
]);
|
|
35106
|
+
return {
|
|
35107
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35108
|
+
local_only: true,
|
|
35109
|
+
dry_run: false,
|
|
35110
|
+
processed_at: timestamp3,
|
|
35111
|
+
action: "created",
|
|
35112
|
+
fingerprint: next.fingerprint,
|
|
35113
|
+
finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
|
|
35114
|
+
warnings
|
|
35115
|
+
};
|
|
35116
|
+
}
|
|
35117
|
+
if (sameFinding(existing, next)) {
|
|
35118
|
+
return {
|
|
35119
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35120
|
+
local_only: true,
|
|
35121
|
+
dry_run: false,
|
|
35122
|
+
processed_at: timestamp3,
|
|
35123
|
+
action: "matched",
|
|
35124
|
+
fingerprint: next.fingerprint,
|
|
35125
|
+
finding: compactFinding(existing),
|
|
35126
|
+
warnings
|
|
35127
|
+
};
|
|
35128
|
+
}
|
|
35129
|
+
const action = upsertAction(existing, next);
|
|
35130
|
+
d.run(`UPDATE task_findings SET
|
|
35131
|
+
run_id = ?, title = ?, severity = ?, status = ?, source = ?, summary = ?, artifact_path = ?,
|
|
35132
|
+
metadata = ?, last_seen_at = ?, resolved_at = ?, updated_at = ?
|
|
35133
|
+
WHERE id = ?`, [
|
|
35134
|
+
next.run_id,
|
|
35135
|
+
next.title,
|
|
35136
|
+
next.severity,
|
|
35137
|
+
next.status,
|
|
35138
|
+
next.source,
|
|
35139
|
+
next.summary,
|
|
35140
|
+
next.artifact_path,
|
|
35141
|
+
JSON.stringify(next.metadata),
|
|
35142
|
+
timestamp3,
|
|
35143
|
+
next.status === "open" ? null : existing.resolved_at || timestamp3,
|
|
35144
|
+
timestamp3,
|
|
35145
|
+
existing.id
|
|
35146
|
+
]);
|
|
35147
|
+
return {
|
|
35148
|
+
schema_version: TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
35149
|
+
local_only: true,
|
|
35150
|
+
dry_run: false,
|
|
35151
|
+
processed_at: timestamp3,
|
|
35152
|
+
action,
|
|
35153
|
+
fingerprint: next.fingerprint,
|
|
35154
|
+
finding: compactFinding(getFindingByFingerprint(input.task_id, next.fingerprint, d)),
|
|
35155
|
+
warnings
|
|
35156
|
+
};
|
|
35157
|
+
}
|
|
35158
|
+
function listTaskFindings(filter = {}, db) {
|
|
35159
|
+
const d = db || getDatabase();
|
|
35160
|
+
const conditions = ["1=1"];
|
|
35161
|
+
const params = [];
|
|
35162
|
+
if (filter.task_id) {
|
|
35163
|
+
conditions.push("task_id = ?");
|
|
35164
|
+
params.push(filter.task_id);
|
|
35165
|
+
}
|
|
35166
|
+
if (filter.run_id) {
|
|
35167
|
+
conditions.push("run_id = ?");
|
|
35168
|
+
params.push(resolveTaskRunId(filter.run_id, d));
|
|
35169
|
+
}
|
|
35170
|
+
if (filter.status) {
|
|
35171
|
+
conditions.push("status = ?");
|
|
35172
|
+
params.push(normalizeStatus2(filter.status));
|
|
35173
|
+
}
|
|
35174
|
+
if (filter.source) {
|
|
35175
|
+
conditions.push("source = ?");
|
|
35176
|
+
params.push(redactOptional(filter.source, 120));
|
|
35177
|
+
}
|
|
35178
|
+
const limit = Math.min(Math.max(Math.floor(filter.limit ?? 50), 1), 500);
|
|
35179
|
+
const rows = d.query(`SELECT * FROM task_findings WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC, created_at DESC LIMIT ?`).all(...[...params, limit]);
|
|
35180
|
+
return rows.map(rowToFinding);
|
|
35181
|
+
}
|
|
35182
|
+
function listCompactTaskFindings(filter = {}, db) {
|
|
35183
|
+
return listTaskFindings(filter, db).map(compactFinding);
|
|
35184
|
+
}
|
|
35185
|
+
function resolveMissingTaskFindings(input, db) {
|
|
35186
|
+
const d = db || getDatabase();
|
|
35187
|
+
assertTask(input.task_id, d);
|
|
35188
|
+
const timestamp3 = input.resolved_at || now();
|
|
35189
|
+
const status = normalizeResolutionStatus(input.status);
|
|
35190
|
+
const runId = resolveRunForTask(input.run_id, input.task_id, d);
|
|
35191
|
+
const present = new Set(input.fingerprints.map(normalizeFingerprint));
|
|
35192
|
+
const warnings = [];
|
|
35193
|
+
const conditions = ["task_id = ?", "status = 'open'"];
|
|
35194
|
+
const params = [input.task_id];
|
|
35195
|
+
if (input.source) {
|
|
35196
|
+
conditions.push("source = ?");
|
|
35197
|
+
params.push(redactOptional(input.source, 120));
|
|
35198
|
+
}
|
|
35199
|
+
const candidates = d.query(`SELECT * FROM task_findings WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC, created_at DESC`).all(...params).map(rowToFinding).filter((finding2) => !present.has(finding2.fingerprint));
|
|
35200
|
+
const limit = Math.min(Math.max(Math.floor(input.limit ?? 50), 1), 200);
|
|
35201
|
+
const display = candidates.slice(0, limit);
|
|
35202
|
+
const omittedCount = Math.max(0, candidates.length - display.length);
|
|
35203
|
+
if (!input.apply) {
|
|
35204
|
+
return {
|
|
35205
|
+
schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
35206
|
+
local_only: true,
|
|
35207
|
+
dry_run: true,
|
|
35208
|
+
processed_at: timestamp3,
|
|
35209
|
+
action: candidates.length > 0 ? "preview" : "noop",
|
|
35210
|
+
task_id: input.task_id,
|
|
35211
|
+
source: input.source ? redactOptional(input.source, 120) : null,
|
|
35212
|
+
run_id: runId,
|
|
35213
|
+
present_fingerprint_count: present.size,
|
|
35214
|
+
candidate_count: candidates.length,
|
|
35215
|
+
changed_count: 0,
|
|
35216
|
+
omitted_count: omittedCount,
|
|
35217
|
+
findings: display.map(compactFinding),
|
|
35218
|
+
warnings
|
|
35219
|
+
};
|
|
35220
|
+
}
|
|
35221
|
+
const metadataPatch = redactValue({
|
|
35222
|
+
resolved_by: {
|
|
35223
|
+
agent_id: input.agent_id ?? null,
|
|
35224
|
+
run_id: runId,
|
|
35225
|
+
reason: input.reason ? redactEvidenceText(input.reason) : "missing from latest loop finding set"
|
|
35226
|
+
}
|
|
35227
|
+
});
|
|
35228
|
+
const tx = d.transaction(() => {
|
|
35229
|
+
for (const finding2 of candidates) {
|
|
35230
|
+
d.run("UPDATE task_findings SET status = ?, resolved_at = ?, metadata = ?, updated_at = ? WHERE id = ? AND status = 'open'", [
|
|
35231
|
+
status,
|
|
35232
|
+
timestamp3,
|
|
35233
|
+
JSON.stringify({ ...finding2.metadata, ...metadataPatch }),
|
|
35234
|
+
timestamp3,
|
|
35235
|
+
finding2.id
|
|
35236
|
+
]);
|
|
35237
|
+
}
|
|
35238
|
+
});
|
|
35239
|
+
tx();
|
|
35240
|
+
const updated = candidates.map((finding2) => d.query("SELECT * FROM task_findings WHERE id = ?").get(finding2.id)).filter((row) => Boolean(row)).map(rowToFinding);
|
|
35241
|
+
const visibleUpdated = updated.slice(0, limit);
|
|
35242
|
+
return {
|
|
35243
|
+
schema_version: TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION,
|
|
35244
|
+
local_only: true,
|
|
35245
|
+
dry_run: false,
|
|
35246
|
+
processed_at: timestamp3,
|
|
35247
|
+
action: updated.length > 0 ? status : "noop",
|
|
35248
|
+
task_id: input.task_id,
|
|
35249
|
+
source: input.source ? redactOptional(input.source, 120) : null,
|
|
35250
|
+
run_id: runId,
|
|
35251
|
+
present_fingerprint_count: present.size,
|
|
35252
|
+
candidate_count: candidates.length,
|
|
35253
|
+
changed_count: updated.length,
|
|
35254
|
+
omitted_count: omittedCount,
|
|
35255
|
+
findings: visibleUpdated.map(compactFinding),
|
|
35256
|
+
warnings
|
|
35257
|
+
};
|
|
35258
|
+
}
|
|
35259
|
+
var TASK_FINDING_SCHEMA_VERSION = "todos.task_finding.v1", TASK_FINDING_UPSERT_SCHEMA_VERSION = "todos.task_finding_upsert.v1", TASK_FINDING_RESOLVE_MISSING_SCHEMA_VERSION = "todos.task_finding_resolve_missing.v1", SEVERITIES, STATUSES;
|
|
35260
|
+
var init_findings = __esm(() => {
|
|
35261
|
+
init_redaction();
|
|
35262
|
+
init_types();
|
|
35263
|
+
init_database();
|
|
35264
|
+
init_tasks();
|
|
35265
|
+
init_task_runs();
|
|
35266
|
+
SEVERITIES = new Set(["low", "medium", "high", "critical"]);
|
|
35267
|
+
STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
35268
|
+
});
|
|
35269
|
+
|
|
34242
35270
|
// src/lib/agent-run-dispatcher.ts
|
|
34243
35271
|
var exports_agent_run_dispatcher = {};
|
|
34244
35272
|
__export(exports_agent_run_dispatcher, {
|
|
@@ -35167,7 +36195,7 @@ function parseArray2(value) {
|
|
|
35167
36195
|
return [];
|
|
35168
36196
|
}
|
|
35169
36197
|
}
|
|
35170
|
-
function
|
|
36198
|
+
function parseObject3(value) {
|
|
35171
36199
|
try {
|
|
35172
36200
|
const parsed = JSON.parse(value || "{}");
|
|
35173
36201
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -35208,7 +36236,7 @@ function rowToKnowledgeRecord(row) {
|
|
|
35208
36236
|
agent_id: row.agent_id,
|
|
35209
36237
|
snapshot_id: row.snapshot_id,
|
|
35210
36238
|
tags: parseArray2(row.tags),
|
|
35211
|
-
metadata: redactValue(
|
|
36239
|
+
metadata: redactValue(parseObject3(row.metadata)),
|
|
35212
36240
|
created_at: row.created_at,
|
|
35213
36241
|
updated_at: row.updated_at
|
|
35214
36242
|
};
|
|
@@ -35427,7 +36455,7 @@ function parseArray3(value) {
|
|
|
35427
36455
|
return [];
|
|
35428
36456
|
}
|
|
35429
36457
|
}
|
|
35430
|
-
function
|
|
36458
|
+
function parseObject4(value) {
|
|
35431
36459
|
try {
|
|
35432
36460
|
const parsed = JSON.parse(value || "{}");
|
|
35433
36461
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -35476,7 +36504,7 @@ function rowToRisk(row) {
|
|
|
35476
36504
|
plan_id: row.plan_id,
|
|
35477
36505
|
task_id: row.task_id,
|
|
35478
36506
|
tags: parseArray3(row.tags),
|
|
35479
|
-
metadata: redactValue(
|
|
36507
|
+
metadata: redactValue(parseObject4(row.metadata)),
|
|
35480
36508
|
created_at: row.created_at,
|
|
35481
36509
|
updated_at: row.updated_at,
|
|
35482
36510
|
closed_at: row.closed_at
|
|
@@ -36327,7 +37355,7 @@ function extractUsage(value) {
|
|
|
36327
37355
|
}
|
|
36328
37356
|
return own;
|
|
36329
37357
|
}
|
|
36330
|
-
function
|
|
37358
|
+
function parseJsonObject6(value) {
|
|
36331
37359
|
if (!value)
|
|
36332
37360
|
return {};
|
|
36333
37361
|
try {
|
|
@@ -36499,7 +37527,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
36499
37527
|
completedRunMs += millisBetween(run.started_at, run.completed_at);
|
|
36500
37528
|
else
|
|
36501
37529
|
openRunMs += millisBetween(run.started_at, generatedAt);
|
|
36502
|
-
const usage = extractUsage(
|
|
37530
|
+
const usage = extractUsage(parseJsonObject6(run.metadata));
|
|
36503
37531
|
metadataUsage.tokens += usage.tokens;
|
|
36504
37532
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
36505
37533
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -36511,7 +37539,7 @@ function createLocalUsageLedger(options = {}, db) {
|
|
|
36511
37539
|
JOIN tasks t ON t.id = e.task_id
|
|
36512
37540
|
${runClause}`, runParams);
|
|
36513
37541
|
for (const event of eventRows) {
|
|
36514
|
-
const usage = extractUsage(
|
|
37542
|
+
const usage = extractUsage(parseJsonObject6(event.data));
|
|
36515
37543
|
metadataUsage.tokens += usage.tokens;
|
|
36516
37544
|
metadataUsage.cost_usd += usage.cost_usd;
|
|
36517
37545
|
metadataUsage.duration_ms += usage.duration_ms;
|
|
@@ -40312,6 +41340,38 @@ ${lines.join(`
|
|
|
40312
41340
|
}
|
|
40313
41341
|
});
|
|
40314
41342
|
}
|
|
41343
|
+
if (shouldRegisterTool("begin_task_run_transaction")) {
|
|
41344
|
+
server.tool("begin_task_run_transaction", "Preview or apply an idempotent local loop run transaction keyed by a stable loop/run id.", {
|
|
41345
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
41346
|
+
key: exports_external.string().optional().describe("Stable idempotency key"),
|
|
41347
|
+
loop_id: exports_external.string().optional().describe("Loop identifier; used as key fallback"),
|
|
41348
|
+
loop_run_id: exports_external.string().optional().describe("Loop run identifier; used as key fallback"),
|
|
41349
|
+
agent_id: exports_external.string().optional().describe("Agent starting the run"),
|
|
41350
|
+
title: exports_external.string().optional().describe("Run title"),
|
|
41351
|
+
summary: exports_external.string().optional().describe("Run summary"),
|
|
41352
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Additional local metadata"),
|
|
41353
|
+
claim: exports_external.boolean().optional().describe("Claim/start the task before recording the run"),
|
|
41354
|
+
apply: exports_external.boolean().optional().describe("Apply the transaction; false or omitted is dry-run")
|
|
41355
|
+
}, async ({ task_id, key, loop_id, loop_run_id, agent_id, title, summary, metadata, claim, apply }) => {
|
|
41356
|
+
try {
|
|
41357
|
+
const result = beginTaskRunTransaction({
|
|
41358
|
+
task_id: resolveId(task_id),
|
|
41359
|
+
key,
|
|
41360
|
+
loop_id,
|
|
41361
|
+
loop_run_id,
|
|
41362
|
+
agent_id,
|
|
41363
|
+
title,
|
|
41364
|
+
summary,
|
|
41365
|
+
metadata,
|
|
41366
|
+
claim,
|
|
41367
|
+
apply
|
|
41368
|
+
});
|
|
41369
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
41370
|
+
} catch (e) {
|
|
41371
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41372
|
+
}
|
|
41373
|
+
});
|
|
41374
|
+
}
|
|
40315
41375
|
if (shouldRegisterTool("list_task_runs")) {
|
|
40316
41376
|
server.tool("list_task_runs", "List local run ledger entries, optionally scoped to a task.", { task_id: exports_external.string().optional().describe("Optional task ID") }, async ({ task_id }) => {
|
|
40317
41377
|
try {
|
|
@@ -40409,15 +41469,117 @@ ${lines.join(`
|
|
|
40409
41469
|
});
|
|
40410
41470
|
}
|
|
40411
41471
|
if (shouldRegisterTool("finish_task_run")) {
|
|
40412
|
-
server.tool("finish_task_run", "Finish a local run ledger entry as completed, failed, or cancelled.", {
|
|
40413
|
-
run_id: exports_external.string().describe("Run ID or prefix"),
|
|
40414
|
-
|
|
41472
|
+
server.tool("finish_task_run", "Finish a local run ledger entry as completed, failed, or cancelled. Supports idempotent lookup by key.", {
|
|
41473
|
+
run_id: exports_external.string().optional().describe("Run ID or prefix"),
|
|
41474
|
+
key: exports_external.string().optional().describe("Idempotency key when run_id is omitted"),
|
|
41475
|
+
task_id: exports_external.string().optional().describe("Task scope for key lookup"),
|
|
41476
|
+
status: exports_external.enum(["completed", "failed", "cancelled"]).optional().describe("Final run status"),
|
|
40415
41477
|
summary: exports_external.string().optional().describe("Final summary"),
|
|
40416
|
-
agent_id: exports_external.string().optional().describe("Agent finishing the run")
|
|
40417
|
-
|
|
41478
|
+
agent_id: exports_external.string().optional().describe("Agent finishing the run"),
|
|
41479
|
+
apply: exports_external.boolean().optional().describe("Apply the transaction; false previews without mutating")
|
|
41480
|
+
}, async ({ run_id, key, task_id, status, summary, agent_id, apply }) => {
|
|
40418
41481
|
try {
|
|
40419
|
-
|
|
40420
|
-
|
|
41482
|
+
if (run_id && !key && apply === undefined) {
|
|
41483
|
+
const result2 = finishTaskRunTransaction({ run_id, status: status || "completed", summary, agent_id, apply: true });
|
|
41484
|
+
const run = result2.run ? getTaskRunLedger(result2.run.id).run : null;
|
|
41485
|
+
return { content: [{ type: "text", text: JSON.stringify(run, null, 2) }] };
|
|
41486
|
+
}
|
|
41487
|
+
const result = finishTaskRunTransaction({
|
|
41488
|
+
run_id,
|
|
41489
|
+
key,
|
|
41490
|
+
task_id: task_id ? resolveId(task_id) : undefined,
|
|
41491
|
+
status: status || "completed",
|
|
41492
|
+
summary,
|
|
41493
|
+
agent_id,
|
|
41494
|
+
apply: apply !== false
|
|
41495
|
+
});
|
|
41496
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
41497
|
+
} catch (e) {
|
|
41498
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41499
|
+
}
|
|
41500
|
+
});
|
|
41501
|
+
}
|
|
41502
|
+
if (shouldRegisterTool("upsert_task_finding")) {
|
|
41503
|
+
server.tool("upsert_task_finding", "Preview or apply an idempotent local finding upsert scoped by task and fingerprint.", {
|
|
41504
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
41505
|
+
fingerprint: exports_external.string().describe("Stable finding fingerprint"),
|
|
41506
|
+
title: exports_external.string().describe("Finding title"),
|
|
41507
|
+
severity: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Finding severity"),
|
|
41508
|
+
status: exports_external.enum(["open", "resolved", "ignored"]).optional().describe("Finding status"),
|
|
41509
|
+
source: exports_external.string().optional().describe("Loop/tool source name"),
|
|
41510
|
+
summary: exports_external.string().optional().describe("Bounded finding summary"),
|
|
41511
|
+
artifact_path: exports_external.string().optional().describe("Local artifact path/reference; content is not read"),
|
|
41512
|
+
run_id: exports_external.string().optional().describe("Run ledger ID or prefix"),
|
|
41513
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Additional local metadata"),
|
|
41514
|
+
apply: exports_external.boolean().optional().describe("Apply the upsert; false or omitted is dry-run")
|
|
41515
|
+
}, async ({ task_id, fingerprint: fingerprint3, title, severity, status, source: source3, summary, artifact_path, run_id, metadata, apply }) => {
|
|
41516
|
+
try {
|
|
41517
|
+
const result = upsertTaskFinding({
|
|
41518
|
+
task_id: resolveId(task_id),
|
|
41519
|
+
fingerprint: fingerprint3,
|
|
41520
|
+
title,
|
|
41521
|
+
severity,
|
|
41522
|
+
status,
|
|
41523
|
+
source: source3,
|
|
41524
|
+
summary,
|
|
41525
|
+
artifact_path,
|
|
41526
|
+
run_id,
|
|
41527
|
+
metadata,
|
|
41528
|
+
apply
|
|
41529
|
+
});
|
|
41530
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
41531
|
+
} catch (e) {
|
|
41532
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41533
|
+
}
|
|
41534
|
+
});
|
|
41535
|
+
}
|
|
41536
|
+
if (shouldRegisterTool("list_task_findings")) {
|
|
41537
|
+
server.tool("list_task_findings", "List compact local findings with bounded output.", {
|
|
41538
|
+
task_id: exports_external.string().optional().describe("Filter by task"),
|
|
41539
|
+
run_id: exports_external.string().optional().describe("Filter by run"),
|
|
41540
|
+
status: exports_external.enum(["open", "resolved", "ignored"]).optional().describe("Filter by status"),
|
|
41541
|
+
source: exports_external.string().optional().describe("Filter by source"),
|
|
41542
|
+
limit: exports_external.number().optional().describe("Maximum findings to return")
|
|
41543
|
+
}, async ({ task_id, run_id, status, source: source3, limit }) => {
|
|
41544
|
+
try {
|
|
41545
|
+
const findings = listCompactTaskFindings({
|
|
41546
|
+
task_id: task_id ? resolveId(task_id) : undefined,
|
|
41547
|
+
run_id,
|
|
41548
|
+
status,
|
|
41549
|
+
source: source3,
|
|
41550
|
+
limit
|
|
41551
|
+
});
|
|
41552
|
+
return { content: [{ type: "text", text: JSON.stringify(findings) }] };
|
|
41553
|
+
} catch (e) {
|
|
41554
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
41555
|
+
}
|
|
41556
|
+
});
|
|
41557
|
+
}
|
|
41558
|
+
if (shouldRegisterTool("resolve_missing_task_findings")) {
|
|
41559
|
+
server.tool("resolve_missing_task_findings", "Resolve open findings absent from the latest loop finding set.", {
|
|
41560
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
41561
|
+
fingerprints: exports_external.array(exports_external.string()).optional().describe("Fingerprints still present in the latest run"),
|
|
41562
|
+
source: exports_external.string().optional().describe("Only resolve findings from this source"),
|
|
41563
|
+
run_id: exports_external.string().optional().describe("Run ledger ID or prefix for audit metadata"),
|
|
41564
|
+
status: exports_external.enum(["resolved", "ignored"]).optional().describe("Resolution status"),
|
|
41565
|
+
agent_id: exports_external.string().optional().describe("Agent resolving findings"),
|
|
41566
|
+
reason: exports_external.string().optional().describe("Resolution reason"),
|
|
41567
|
+
limit: exports_external.number().optional().describe("Maximum findings returned"),
|
|
41568
|
+
apply: exports_external.boolean().optional().describe("Apply resolution; false or omitted is dry-run")
|
|
41569
|
+
}, async ({ task_id, fingerprints, source: source3, run_id, status, agent_id, reason, limit, apply }) => {
|
|
41570
|
+
try {
|
|
41571
|
+
const result = resolveMissingTaskFindings({
|
|
41572
|
+
task_id: resolveId(task_id),
|
|
41573
|
+
fingerprints: fingerprints || [],
|
|
41574
|
+
source: source3,
|
|
41575
|
+
run_id,
|
|
41576
|
+
status,
|
|
41577
|
+
agent_id,
|
|
41578
|
+
reason,
|
|
41579
|
+
limit,
|
|
41580
|
+
apply
|
|
41581
|
+
});
|
|
41582
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
40421
41583
|
} catch (e) {
|
|
40422
41584
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
40423
41585
|
}
|
|
@@ -40753,6 +41915,7 @@ var init_task_resources = __esm(() => {
|
|
|
40753
41915
|
init_agents();
|
|
40754
41916
|
init_task_commits();
|
|
40755
41917
|
init_task_runs();
|
|
41918
|
+
init_findings();
|
|
40756
41919
|
init_agent_run_dispatcher();
|
|
40757
41920
|
init_verification_providers();
|
|
40758
41921
|
init_release_notes();
|
|
@@ -44493,8 +45656,10 @@ function taskToSummary(task2, fields) {
|
|
|
44493
45656
|
task_list_id: task2.task_list_id,
|
|
44494
45657
|
agent_id: task2.agent_id,
|
|
44495
45658
|
assigned_to: task2.assigned_to,
|
|
45659
|
+
working_dir: task2.working_dir,
|
|
44496
45660
|
locked_by: task2.locked_by,
|
|
44497
45661
|
tags: task2.tags,
|
|
45662
|
+
metadata: task2.metadata,
|
|
44498
45663
|
version: task2.version,
|
|
44499
45664
|
created_at: task2.created_at,
|
|
44500
45665
|
updated_at: task2.updated_at,
|
|
@@ -44636,6 +45801,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
44636
45801
|
if (path === "/api/tasks" && method === "POST") {
|
|
44637
45802
|
return handleCreateTask(req, ctx, jsonWithCors, taskToSummary);
|
|
44638
45803
|
}
|
|
45804
|
+
if (path === "/api/tasks/upsert" && method === "POST") {
|
|
45805
|
+
return handleUpsertTask(req, ctx, jsonWithCors, taskToSummary);
|
|
45806
|
+
}
|
|
44639
45807
|
if (path === "/api/tasks/export" && method === "GET") {
|
|
44640
45808
|
return handleTasksExport(req, url, ctx, jsonWithCors, taskToSummary);
|
|
44641
45809
|
}
|
|
@@ -44831,7 +45999,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
44831
45999
|
} catch {}
|
|
44832
46000
|
}
|
|
44833
46001
|
}
|
|
44834
|
-
var MIME_TYPES, SECURITY_HEADERS, rateLimitMap, RATE_LIMIT_WINDOW_MS = 60000, RATE_LIMIT_MAX
|
|
46002
|
+
var MIME_TYPES, SECURITY_HEADERS, rateLimitMap, RATE_LIMIT_WINDOW_MS = 60000, RATE_LIMIT_MAX;
|
|
44835
46003
|
var init_serve = __esm(() => {
|
|
44836
46004
|
init_database();
|
|
44837
46005
|
init_api_keys();
|
|
@@ -44856,6 +46024,7 @@ var init_serve = __esm(() => {
|
|
|
44856
46024
|
"Permissions-Policy": "camera=, microphone=, geolocation="
|
|
44857
46025
|
};
|
|
44858
46026
|
rateLimitMap = new Map;
|
|
46027
|
+
RATE_LIMIT_MAX = Number.parseInt(process.env["TODOS_RATE_LIMIT_MAX"] || "120", 10);
|
|
44859
46028
|
});
|
|
44860
46029
|
|
|
44861
46030
|
// src/cli/components/Header.tsx
|
|
@@ -47509,9 +48678,9 @@ function normalizeKind(value) {
|
|
|
47509
48678
|
const raw = cleanKey(asString4(value) || "unknown").replace(/-/g, "_");
|
|
47510
48679
|
return KINDS.has(raw) ? raw : raw || "unknown";
|
|
47511
48680
|
}
|
|
47512
|
-
function
|
|
48681
|
+
function normalizeSeverity2(value, fallback) {
|
|
47513
48682
|
const raw = cleanKey(asString4(value) || fallback);
|
|
47514
|
-
if (
|
|
48683
|
+
if (SEVERITIES2.has(raw))
|
|
47515
48684
|
return raw;
|
|
47516
48685
|
if (/^(p0|blocker|urgent|highest)$/.test(raw))
|
|
47517
48686
|
return "critical";
|
|
@@ -47606,7 +48775,7 @@ function normalizeTesterIssueReport(value, fallbackPriority = "medium") {
|
|
|
47606
48775
|
title,
|
|
47607
48776
|
summary: truncate3(asString4(input["summary"]), 1000) ?? null,
|
|
47608
48777
|
kind: normalizeKind(input["kind"] ?? input["type"]),
|
|
47609
|
-
severity:
|
|
48778
|
+
severity: normalizeSeverity2(input["severity"] ?? input["priority"], fallbackPriority),
|
|
47610
48779
|
source: source3,
|
|
47611
48780
|
target,
|
|
47612
48781
|
failure,
|
|
@@ -47904,13 +49073,13 @@ function readTesterIssueReportsPayload(value) {
|
|
|
47904
49073
|
return record["issues"];
|
|
47905
49074
|
return [value];
|
|
47906
49075
|
}
|
|
47907
|
-
var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1", TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1", TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1", PRIORITIES5,
|
|
49076
|
+
var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1", TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1", TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1", PRIORITIES5, SEVERITIES2, KINDS;
|
|
47908
49077
|
var init_tester_issue_reports = __esm(() => {
|
|
47909
49078
|
init_database();
|
|
47910
49079
|
init_tasks();
|
|
47911
49080
|
init_redaction();
|
|
47912
49081
|
PRIORITIES5 = ["low", "medium", "high", "critical"];
|
|
47913
|
-
|
|
49082
|
+
SEVERITIES2 = new Set(PRIORITIES5);
|
|
47914
49083
|
KINDS = new Set([
|
|
47915
49084
|
"assertion_failure",
|
|
47916
49085
|
"runtime_error",
|
|
@@ -51092,6 +52261,23 @@ Verifications:`));
|
|
|
51092
52261
|
console.log(`${result.status} ${result.provider}: ${result.output_summary || ""}`);
|
|
51093
52262
|
});
|
|
51094
52263
|
const runs = program2.command("runs").description("Manage the local run ledger and evidence capture");
|
|
52264
|
+
runs.command("begin <task-id>").description("Preview or apply an idempotent loop run transaction").option("--key <key>", "Stable idempotency key for this loop transaction").option("--loop-id <id>", "Loop identifier; used as the key when --key/--loop-run-id are omitted").option("--loop-run-id <id>", "Loop run identifier; used as the key when --key is omitted").option("--agent <name>", "Agent starting the run").option("--title <text>", "Run title").option("--summary <text>", "Run summary").option("--metadata <json>", "Additional JSON metadata").option("--claim", "Claim/start the task for the agent before recording the run").option("--apply", "Apply the transaction; omitted means dry-run").action(async (taskId, opts) => {
|
|
52265
|
+
const globalOpts = program2.opts();
|
|
52266
|
+
const { beginTaskRunTransaction: beginTaskRunTransaction2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
52267
|
+
const result = beginTaskRunTransaction2({
|
|
52268
|
+
task_id: resolveTaskId(taskId),
|
|
52269
|
+
key: opts.key,
|
|
52270
|
+
loop_id: opts.loopId,
|
|
52271
|
+
loop_run_id: opts.loopRunId,
|
|
52272
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
52273
|
+
title: opts.title,
|
|
52274
|
+
summary: opts.summary,
|
|
52275
|
+
metadata: parseJsonOption(opts.metadata, "--metadata"),
|
|
52276
|
+
claim: opts.claim,
|
|
52277
|
+
apply: opts.apply
|
|
52278
|
+
});
|
|
52279
|
+
output(result, true);
|
|
52280
|
+
});
|
|
51095
52281
|
runs.command("start <task-id>").description("Start a local run ledger entry for a task").option("--agent <name>", "Agent starting the run").option("--title <text>", "Run title").option("--summary <text>", "Run summary").option("--metadata <json>", "Additional JSON metadata").option("--claim", "Claim/start the task for the agent before recording the run").action(async (taskId, opts) => {
|
|
51096
52282
|
const globalOpts = program2.opts();
|
|
51097
52283
|
const { startTaskRun: startTaskRun2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
@@ -51303,19 +52489,68 @@ Artifacts:`));
|
|
|
51303
52489
|
process.exit(1);
|
|
51304
52490
|
}
|
|
51305
52491
|
});
|
|
51306
|
-
runs.command("finish
|
|
52492
|
+
runs.command("finish [run-id]").description("Finish a run ledger entry idempotently").option("--key <key>", "Resolve run by idempotency key when run-id is omitted").option("--task <task-id>", "Task scope for --key lookup").option("--status <status>", "completed, failed, or cancelled", "completed").option("--summary <text>", "Final summary").option("--agent <name>", "Agent finishing the run").option("--dry-run", "Preview without mutating").action(async (runId, opts) => {
|
|
51307
52493
|
const globalOpts = program2.opts();
|
|
51308
52494
|
if (opts.status !== "completed" && opts.status !== "failed" && opts.status !== "cancelled") {
|
|
51309
52495
|
console.error(chalk8.red("--status must be completed, failed, or cancelled"));
|
|
51310
52496
|
process.exit(1);
|
|
51311
52497
|
}
|
|
51312
|
-
const {
|
|
51313
|
-
const
|
|
51314
|
-
|
|
51315
|
-
|
|
51316
|
-
|
|
51317
|
-
|
|
51318
|
-
|
|
52498
|
+
const { finishTaskRunTransaction: finishTaskRunTransaction2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
|
|
52499
|
+
const result = finishTaskRunTransaction2({
|
|
52500
|
+
run_id: runId,
|
|
52501
|
+
key: opts.key,
|
|
52502
|
+
task_id: opts.task ? resolveTaskId(opts.task) : undefined,
|
|
52503
|
+
status: opts.status,
|
|
52504
|
+
summary: opts.summary,
|
|
52505
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
52506
|
+
apply: !opts.dryRun
|
|
52507
|
+
});
|
|
52508
|
+
output(result, true);
|
|
52509
|
+
});
|
|
52510
|
+
const findings = program2.command("findings").description("Manage local task findings for loop dedupe and resolution");
|
|
52511
|
+
findings.command("upsert").description("Preview or apply an idempotent finding upsert").requiredOption("--task <task-id>", "Task ID").requiredOption("--fingerprint <value>", "Stable finding fingerprint").requiredOption("--title <text>", "Finding title").option("--severity <severity>", "low, medium, high, or critical", "medium").option("--status <status>", "open, resolved, or ignored", "open").option("--source <source>", "Loop/tool source name").option("--summary <text>", "Bounded finding summary").option("--artifact <path>", "Local artifact path/reference; content is not read").option("--run <run-id>", "Run ledger ID or prefix").option("--metadata <json>", "Additional JSON metadata").option("--apply", "Apply the upsert; omitted means dry-run").action(async (opts) => {
|
|
52512
|
+
const { upsertTaskFinding: upsertTaskFinding2 } = await Promise.resolve().then(() => (init_findings(), exports_findings));
|
|
52513
|
+
const result = upsertTaskFinding2({
|
|
52514
|
+
task_id: resolveTaskId(opts.task),
|
|
52515
|
+
fingerprint: opts.fingerprint,
|
|
52516
|
+
title: opts.title,
|
|
52517
|
+
severity: opts.severity,
|
|
52518
|
+
status: opts.status,
|
|
52519
|
+
source: opts.source,
|
|
52520
|
+
summary: opts.summary,
|
|
52521
|
+
artifact_path: opts.artifact,
|
|
52522
|
+
run_id: opts.run,
|
|
52523
|
+
metadata: parseJsonOption(opts.metadata, "--metadata"),
|
|
52524
|
+
apply: opts.apply
|
|
52525
|
+
});
|
|
52526
|
+
output(result, true);
|
|
52527
|
+
});
|
|
52528
|
+
findings.command("resolve-missing").description("Resolve open findings absent from the latest loop finding set").requiredOption("--task <task-id>", "Task ID").option("--fingerprints <list>", "Comma-separated fingerprints still present").option("--source <source>", "Only resolve findings from this source").option("--run <run-id>", "Run ledger ID or prefix for audit metadata").option("--status <status>", "resolved or ignored", "resolved").option("--agent <name>", "Agent resolving findings").option("--reason <text>", "Resolution reason").option("--limit <n>", "Maximum findings returned", "50").option("--apply", "Apply resolution; omitted means dry-run").action(async (opts) => {
|
|
52529
|
+
const globalOpts = program2.opts();
|
|
52530
|
+
const { resolveMissingTaskFindings: resolveMissingTaskFindings2 } = await Promise.resolve().then(() => (init_findings(), exports_findings));
|
|
52531
|
+
const result = resolveMissingTaskFindings2({
|
|
52532
|
+
task_id: resolveTaskId(opts.task),
|
|
52533
|
+
fingerprints: listOption(opts.fingerprints) || [],
|
|
52534
|
+
source: opts.source,
|
|
52535
|
+
run_id: opts.run,
|
|
52536
|
+
status: opts.status,
|
|
52537
|
+
agent_id: opts.agent || globalOpts.agent,
|
|
52538
|
+
reason: opts.reason,
|
|
52539
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined,
|
|
52540
|
+
apply: opts.apply
|
|
52541
|
+
});
|
|
52542
|
+
output(result, true);
|
|
52543
|
+
});
|
|
52544
|
+
findings.command("list").description("List compact local findings").option("--task <task-id>", "Filter by task").option("--run <run-id>", "Filter by run").option("--status <status>", "Filter by open, resolved, or ignored").option("--source <source>", "Filter by source").option("--limit <n>", "Maximum findings returned", "50").action(async (opts) => {
|
|
52545
|
+
const { listCompactTaskFindings: listCompactTaskFindings2 } = await Promise.resolve().then(() => (init_findings(), exports_findings));
|
|
52546
|
+
const findings2 = listCompactTaskFindings2({
|
|
52547
|
+
task_id: opts.task ? resolveTaskId(opts.task) : undefined,
|
|
52548
|
+
run_id: opts.run,
|
|
52549
|
+
status: opts.status,
|
|
52550
|
+
source: opts.source,
|
|
52551
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined
|
|
52552
|
+
});
|
|
52553
|
+
output(findings2, true);
|
|
51319
52554
|
});
|
|
51320
52555
|
const agentRuns = program2.command("agent-runs").description("Queue and dispatch local agent runs");
|
|
51321
52556
|
agentRuns.command("adapter-set <name>").description("Create or update a local agent run adapter").option("--command <command>", "Local command template. Supports {task_id}, {run_id}, and {agent_id}").option("--sandbox <name>", "Runner sandbox profile to check before launch").option("--cwd <path>", "Command working directory").option("--env <json>", "Static adapter environment as a JSON object").action(async (name, opts) => {
|
|
@@ -52229,7 +53464,7 @@ function parseRecordType(value) {
|
|
|
52229
53464
|
console.error(chalk13.red(`type must be one of: ${RECORD_TYPES.join(", ")}`));
|
|
52230
53465
|
process.exit(1);
|
|
52231
53466
|
}
|
|
52232
|
-
function
|
|
53467
|
+
function parseJsonObject7(value, label) {
|
|
52233
53468
|
if (!value)
|
|
52234
53469
|
return;
|
|
52235
53470
|
try {
|
|
@@ -52289,7 +53524,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
52289
53524
|
plan_id: opts.plan,
|
|
52290
53525
|
agent_id: opts.agent || globalOpts.agent,
|
|
52291
53526
|
tags: tagsFromOption(opts.tag),
|
|
52292
|
-
metadata:
|
|
53527
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
52293
53528
|
}, getDatabase());
|
|
52294
53529
|
if (opts.json || globalOpts.json)
|
|
52295
53530
|
output(record, true);
|
|
@@ -52314,7 +53549,7 @@ function registerKnowledgeCommands(program2) {
|
|
|
52314
53549
|
blockers: opts.blocker,
|
|
52315
53550
|
next_steps: opts.next,
|
|
52316
53551
|
tags: tagsFromOption(opts.tag),
|
|
52317
|
-
metadata:
|
|
53552
|
+
metadata: parseJsonObject7(opts.metadataJson, "--metadata-json")
|
|
52318
53553
|
}, getDatabase());
|
|
52319
53554
|
if (opts.json || globalOpts.json)
|
|
52320
53555
|
output(result, true);
|
|
@@ -52400,7 +53635,7 @@ function parseChoice(value, choices, label) {
|
|
|
52400
53635
|
console.error(chalk14.red(`${label} must be one of: ${choices.join(", ")}`));
|
|
52401
53636
|
process.exit(1);
|
|
52402
53637
|
}
|
|
52403
|
-
function
|
|
53638
|
+
function parseJsonObject8(value, label) {
|
|
52404
53639
|
if (!value)
|
|
52405
53640
|
return;
|
|
52406
53641
|
try {
|
|
@@ -52421,8 +53656,8 @@ function tagsFromOption2(value) {
|
|
|
52421
53656
|
}
|
|
52422
53657
|
function commonFilters2(opts) {
|
|
52423
53658
|
return {
|
|
52424
|
-
status: opts.status ? parseChoice(opts.status,
|
|
52425
|
-
severity: opts.severity ? parseChoice(opts.severity,
|
|
53659
|
+
status: opts.status ? parseChoice(opts.status, STATUSES2, "--status") : undefined,
|
|
53660
|
+
severity: opts.severity ? parseChoice(opts.severity, SEVERITIES3, "--severity") : undefined,
|
|
52426
53661
|
probability: opts.probability ? parseChoice(opts.probability, PROBABILITIES, "--probability") : undefined,
|
|
52427
53662
|
owner: opts.owner,
|
|
52428
53663
|
project_id: opts.project,
|
|
@@ -52455,8 +53690,8 @@ function registerRiskCommands(program2) {
|
|
|
52455
53690
|
const risk = createRisk({
|
|
52456
53691
|
title,
|
|
52457
53692
|
description: opts.description,
|
|
52458
|
-
status: parseChoice(opts.status,
|
|
52459
|
-
severity: parseChoice(opts.severity,
|
|
53693
|
+
status: parseChoice(opts.status, STATUSES2, "--status"),
|
|
53694
|
+
severity: parseChoice(opts.severity, SEVERITIES3, "--severity"),
|
|
52460
53695
|
probability: parseChoice(opts.probability, PROBABILITIES, "--probability"),
|
|
52461
53696
|
owner: opts.owner,
|
|
52462
53697
|
mitigation: opts.mitigation,
|
|
@@ -52465,7 +53700,7 @@ function registerRiskCommands(program2) {
|
|
|
52465
53700
|
plan_id: opts.plan,
|
|
52466
53701
|
task_id: opts.task,
|
|
52467
53702
|
tags: tagsFromOption2(opts.tag),
|
|
52468
|
-
metadata:
|
|
53703
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
52469
53704
|
}, getDatabase());
|
|
52470
53705
|
if (opts.json || globalOpts.json)
|
|
52471
53706
|
output(risk, true);
|
|
@@ -52507,8 +53742,8 @@ function registerRiskCommands(program2) {
|
|
|
52507
53742
|
const risk = updateRisk(id, {
|
|
52508
53743
|
title: opts.title,
|
|
52509
53744
|
description: opts.description,
|
|
52510
|
-
status: opts.status ? parseChoice(opts.status,
|
|
52511
|
-
severity: opts.severity ? parseChoice(opts.severity,
|
|
53745
|
+
status: opts.status ? parseChoice(opts.status, STATUSES2, "--status") : undefined,
|
|
53746
|
+
severity: opts.severity ? parseChoice(opts.severity, SEVERITIES3, "--severity") : undefined,
|
|
52512
53747
|
probability: opts.probability ? parseChoice(opts.probability, PROBABILITIES, "--probability") : undefined,
|
|
52513
53748
|
owner: opts.owner,
|
|
52514
53749
|
mitigation: opts.mitigation,
|
|
@@ -52517,7 +53752,7 @@ function registerRiskCommands(program2) {
|
|
|
52517
53752
|
plan_id: opts.plan,
|
|
52518
53753
|
task_id: opts.task,
|
|
52519
53754
|
tags: opts.tag.length > 0 ? tagsFromOption2(opts.tag) : undefined,
|
|
52520
|
-
metadata:
|
|
53755
|
+
metadata: parseJsonObject8(opts.metadataJson, "--metadata-json")
|
|
52521
53756
|
}, getDatabase());
|
|
52522
53757
|
if (opts.json || globalOpts.json)
|
|
52523
53758
|
output(risk, true);
|
|
@@ -52575,13 +53810,13 @@ function registerRiskCommands(program2) {
|
|
|
52575
53810
|
}
|
|
52576
53811
|
});
|
|
52577
53812
|
}
|
|
52578
|
-
var
|
|
53813
|
+
var STATUSES2, SEVERITIES3, PROBABILITIES;
|
|
52579
53814
|
var init_risk_commands = __esm(() => {
|
|
52580
53815
|
init_database();
|
|
52581
53816
|
init_project_risks();
|
|
52582
53817
|
init_helpers();
|
|
52583
|
-
|
|
52584
|
-
|
|
53818
|
+
STATUSES2 = ["open", "mitigating", "resolved", "accepted"];
|
|
53819
|
+
SEVERITIES3 = ["low", "medium", "high", "critical"];
|
|
52585
53820
|
PROBABILITIES = ["low", "medium", "high"];
|
|
52586
53821
|
});
|
|
52587
53822
|
|
|
@@ -55166,6 +56401,92 @@ var init_json_contracts = __esm(() => {
|
|
|
55166
56401
|
},
|
|
55167
56402
|
optional: {}
|
|
55168
56403
|
}),
|
|
56404
|
+
contract({
|
|
56405
|
+
id: "loop_run_transaction",
|
|
56406
|
+
name: "Loop Run Transaction",
|
|
56407
|
+
description: "Compact local result for idempotent loop run begin/finish transactions.",
|
|
56408
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56409
|
+
stability: "stable",
|
|
56410
|
+
required: {
|
|
56411
|
+
schema_version: field("string", "Result schema version."),
|
|
56412
|
+
local_only: field("boolean", "Always true; loop run transactions use local state."),
|
|
56413
|
+
dry_run: field("boolean", "True when no run ledger mutation was applied."),
|
|
56414
|
+
processed_at: isoDateField,
|
|
56415
|
+
action: field("string", "preview, created, matched, finished, or conflict."),
|
|
56416
|
+
key: field("string", "Stable idempotency key used to dedupe the transaction."),
|
|
56417
|
+
run: field(["object", "null"], "Compact run summary or null for create previews.", true),
|
|
56418
|
+
warnings: field("array", "Non-fatal warnings such as terminal-status conflicts."),
|
|
56419
|
+
commands: field("array", "Follow-up CLI commands for agents and operators.")
|
|
56420
|
+
},
|
|
56421
|
+
optional: {}
|
|
56422
|
+
}),
|
|
56423
|
+
contract({
|
|
56424
|
+
id: "task_finding",
|
|
56425
|
+
name: "Task Finding",
|
|
56426
|
+
description: "Compact local finding record deduped by task and fingerprint.",
|
|
56427
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56428
|
+
stability: "stable",
|
|
56429
|
+
required: {
|
|
56430
|
+
schema_version: field("string", "Finding schema version."),
|
|
56431
|
+
id: idField,
|
|
56432
|
+
task_id: idField,
|
|
56433
|
+
run_id: field(["string", "null"], "Optional run ledger ID.", true),
|
|
56434
|
+
fingerprint: field("string", "Stable finding fingerprint scoped to the task."),
|
|
56435
|
+
title: field("string", "Short redacted finding title."),
|
|
56436
|
+
severity: field("string", "low, medium, high, or critical."),
|
|
56437
|
+
status: field("string", "open, resolved, or ignored."),
|
|
56438
|
+
source: field(["string", "null"], "Optional loop/tool source.", true),
|
|
56439
|
+
summary: field(["string", "null"], "Bounded redacted finding summary.", true),
|
|
56440
|
+
artifact_path: field(["string", "null"], "Local artifact path/reference; raw content is not included.", true),
|
|
56441
|
+
first_seen_at: isoDateField,
|
|
56442
|
+
last_seen_at: isoDateField,
|
|
56443
|
+
resolved_at: field(["string", "null"], "Resolution timestamp when closed.", true),
|
|
56444
|
+
metadata_keys: field("array", "Sorted metadata keys; metadata values are intentionally omitted in compact output.")
|
|
56445
|
+
},
|
|
56446
|
+
optional: {}
|
|
56447
|
+
}),
|
|
56448
|
+
contract({
|
|
56449
|
+
id: "task_finding_upsert",
|
|
56450
|
+
name: "Task Finding Upsert Result",
|
|
56451
|
+
description: "Local-only dry-run or applied result from idempotently upserting a task finding.",
|
|
56452
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56453
|
+
stability: "stable",
|
|
56454
|
+
required: {
|
|
56455
|
+
schema_version: field("string", "Result schema version."),
|
|
56456
|
+
local_only: field("boolean", "Always true; finding upserts use local state."),
|
|
56457
|
+
dry_run: field("boolean", "True when no finding row was created or updated."),
|
|
56458
|
+
processed_at: isoDateField,
|
|
56459
|
+
action: field("string", "preview, created, matched, updated, or reopened."),
|
|
56460
|
+
fingerprint: field("string", "Normalized finding fingerprint."),
|
|
56461
|
+
finding: field(["object", "null"], "Compact finding summary or null for create previews.", true),
|
|
56462
|
+
warnings: field("array", "Non-fatal warnings.")
|
|
56463
|
+
},
|
|
56464
|
+
optional: {}
|
|
56465
|
+
}),
|
|
56466
|
+
contract({
|
|
56467
|
+
id: "task_finding_resolve_missing",
|
|
56468
|
+
name: "Task Finding Resolve Missing Result",
|
|
56469
|
+
description: "Local-only dry-run or applied result from resolving open findings absent from the latest loop finding set.",
|
|
56470
|
+
surfaces: ["cli", "mcp", "sdk"],
|
|
56471
|
+
stability: "stable",
|
|
56472
|
+
required: {
|
|
56473
|
+
schema_version: field("string", "Result schema version."),
|
|
56474
|
+
local_only: field("boolean", "Always true; finding resolution uses local state."),
|
|
56475
|
+
dry_run: field("boolean", "True when no finding rows were changed."),
|
|
56476
|
+
processed_at: isoDateField,
|
|
56477
|
+
action: field("string", "preview, resolved, ignored, or noop."),
|
|
56478
|
+
task_id: idField,
|
|
56479
|
+
source: field(["string", "null"], "Optional source scope.", true),
|
|
56480
|
+
run_id: field(["string", "null"], "Optional run ledger ID used for audit metadata.", true),
|
|
56481
|
+
present_fingerprint_count: field("integer", "Number of fingerprints supplied as still present."),
|
|
56482
|
+
candidate_count: field("integer", "Open findings missing from the supplied set."),
|
|
56483
|
+
changed_count: field("integer", "Rows resolved or ignored by the applied transaction."),
|
|
56484
|
+
omitted_count: field("integer", "Matching findings omitted from bounded output."),
|
|
56485
|
+
findings: field("array", "Bounded compact finding summaries."),
|
|
56486
|
+
warnings: field("array", "Non-fatal warnings.")
|
|
56487
|
+
},
|
|
56488
|
+
optional: {}
|
|
56489
|
+
}),
|
|
55169
56490
|
contract({
|
|
55170
56491
|
id: "verification_provider",
|
|
55171
56492
|
name: "Verification Provider",
|