@hasna/todos 0.11.56 → 0.11.57
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 +34 -0
- package/dist/cli/commands/dispatch.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts +0 -2
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +2689 -2030
- package/dist/contracts.d.ts +2 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +1222 -93
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1622 -291
- package/dist/json-contracts.d.ts.map +1 -1
- package/dist/lib/dispatch.d.ts +3 -0
- package/dist/lib/dispatch.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/json-schemas.d.ts +1 -1
- package/dist/lib/json-schemas.d.ts.map +1 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts +14 -0
- package/dist/lib/shared-events.d.ts.map +1 -0
- package/dist/lib/tester-issue-reports.d.ts +105 -0
- package/dist/lib/tester-issue-reports.d.ts.map +1 -0
- package/dist/lib/tmux.d.ts +19 -1
- package/dist/lib/tmux.d.ts.map +1 -1
- package/dist/mcp/index.js +865 -172
- package/dist/mcp/tools/dispatch.d.ts.map +1 -1
- package/dist/registry.js +1214 -93
- package/dist/release-provenance.json +7 -0
- package/dist/server/index.js +861 -168
- package/dist/storage.js +661 -65
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3050,7 +3050,7 @@ var init_redaction = __esm(() => {
|
|
|
3050
3050
|
});
|
|
3051
3051
|
|
|
3052
3052
|
// src/lib/secret-redaction.ts
|
|
3053
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
3053
|
+
import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
|
|
3054
3054
|
function registerCustomRedactor(fn) {
|
|
3055
3055
|
customRedactors.push(fn);
|
|
3056
3056
|
}
|
|
@@ -3115,7 +3115,7 @@ function scanAndRedactText(text, options = {}) {
|
|
|
3115
3115
|
};
|
|
3116
3116
|
}
|
|
3117
3117
|
function scanFileForSecrets(path, options = {}) {
|
|
3118
|
-
if (!
|
|
3118
|
+
if (!existsSync7(path))
|
|
3119
3119
|
throw new Error(`File not found: ${path}`);
|
|
3120
3120
|
const content = readFileSync3(path, "utf8");
|
|
3121
3121
|
return scanAndRedactText(content, options);
|
|
@@ -4874,6 +4874,67 @@ var TODOS_JSON_CONTRACTS = [
|
|
|
4874
4874
|
},
|
|
4875
4875
|
optional: {}
|
|
4876
4876
|
}),
|
|
4877
|
+
contract({
|
|
4878
|
+
id: "tester_issue_report",
|
|
4879
|
+
name: "Tester Issue Report",
|
|
4880
|
+
description: "Generic issue report payload emitted by @hasna/testers or compatible test runners for conversion into local tasks.",
|
|
4881
|
+
surfaces: ["cli", "sdk"],
|
|
4882
|
+
stability: "stable",
|
|
4883
|
+
required: {
|
|
4884
|
+
schema_version: field("string", "Must be testers.issue_report.v1."),
|
|
4885
|
+
title: field("string", "Short human-readable issue title.")
|
|
4886
|
+
},
|
|
4887
|
+
optional: {
|
|
4888
|
+
kind: field("string", "Issue kind such as assertion_failure, console_error, network_error, accessibility, or unknown."),
|
|
4889
|
+
severity: field("string", "Issue severity mapped to task priority: low, medium, high, or critical."),
|
|
4890
|
+
id: idField,
|
|
4891
|
+
fingerprint: field("string", "Stable tester-side fingerprint. If omitted, @hasna/todos derives one from issue fields."),
|
|
4892
|
+
summary: field(["string", "null"], "Short issue summary.", true),
|
|
4893
|
+
source: field("object", "Run, result, scenario, URL, commit, and branch source metadata."),
|
|
4894
|
+
target: field("object", "URL, route, selector, component, browser, and viewport metadata."),
|
|
4895
|
+
failure: field("object", "Failure message, expected/actual values, stack, reasoning, and reproduction steps."),
|
|
4896
|
+
evidence: field("object", "Logs, screenshots, and artifact references."),
|
|
4897
|
+
labels: tagsField,
|
|
4898
|
+
metadata: metadataField,
|
|
4899
|
+
occurred_at: isoDateField
|
|
4900
|
+
}
|
|
4901
|
+
}),
|
|
4902
|
+
contract({
|
|
4903
|
+
id: "tester_issue_report_result",
|
|
4904
|
+
name: "Tester Issue Report Result",
|
|
4905
|
+
description: "Local-only dry-run or applied result from mapping a tester issue report to a task.",
|
|
4906
|
+
surfaces: ["cli", "sdk"],
|
|
4907
|
+
stability: "stable",
|
|
4908
|
+
required: {
|
|
4909
|
+
schema_version: field("string", "Result schema version."),
|
|
4910
|
+
local_only: field("boolean", "Always true; issue reports are applied to local todos state."),
|
|
4911
|
+
dry_run: field("boolean", "True when no task was created or updated."),
|
|
4912
|
+
processed_at: isoDateField,
|
|
4913
|
+
action: field("string", "preview, matched, created, updated, or regressed."),
|
|
4914
|
+
fingerprint: field("string", "Computed or supplied tester issue fingerprint."),
|
|
4915
|
+
report: field("object", "Normalized tester issue report."),
|
|
4916
|
+
task: field(["object", "null"], "Created, updated, matched, or null task.", true),
|
|
4917
|
+
warnings: field("array", "Non-fatal warnings."),
|
|
4918
|
+
commands: field("array", "Follow-up CLI commands for operators and agents.")
|
|
4919
|
+
},
|
|
4920
|
+
optional: {}
|
|
4921
|
+
}),
|
|
4922
|
+
contract({
|
|
4923
|
+
id: "tester_issue_report_batch_result",
|
|
4924
|
+
name: "Tester Issue Report Batch Result",
|
|
4925
|
+
description: "Local-only dry-run or applied batch result from mapping tester issue reports to tasks.",
|
|
4926
|
+
surfaces: ["cli", "sdk"],
|
|
4927
|
+
stability: "stable",
|
|
4928
|
+
required: {
|
|
4929
|
+
schema_version: field("string", "Batch result schema version."),
|
|
4930
|
+
local_only: field("boolean", "Always true; issue reports are applied to local todos state."),
|
|
4931
|
+
dry_run: field("boolean", "True when no tasks were created or updated."),
|
|
4932
|
+
processed_at: isoDateField,
|
|
4933
|
+
results: field("array", "Single report result entries."),
|
|
4934
|
+
summary: field("object", "Counts grouped by action plus total.")
|
|
4935
|
+
},
|
|
4936
|
+
optional: {}
|
|
4937
|
+
}),
|
|
4877
4938
|
contract({
|
|
4878
4939
|
id: "verification_provider",
|
|
4879
4940
|
name: "Verification Provider",
|
|
@@ -5914,7 +5975,7 @@ var TODOS_JSON_CONTRACTS_MANIFEST = createJsonContractsManifest({
|
|
|
5914
5975
|
});
|
|
5915
5976
|
// src/lib/onboarding-fixtures.ts
|
|
5916
5977
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
5917
|
-
import { join as
|
|
5978
|
+
import { join as join7 } from "path";
|
|
5918
5979
|
|
|
5919
5980
|
// src/lib/local-bridge.ts
|
|
5920
5981
|
init_database();
|
|
@@ -6574,6 +6635,7 @@ function explainRunnerSandbox(input = {}) {
|
|
|
6574
6635
|
// src/lib/event-hooks.ts
|
|
6575
6636
|
init_config();
|
|
6576
6637
|
var LOCAL_EVENT_TYPES = [
|
|
6638
|
+
"task.created",
|
|
6577
6639
|
"task.assigned",
|
|
6578
6640
|
"task.blocked",
|
|
6579
6641
|
"task.started",
|
|
@@ -6816,6 +6878,568 @@ async function testLocalEventHook(name, input) {
|
|
|
6816
6878
|
return emitLocalEventHooks({ ...input, hooks: [hook] });
|
|
6817
6879
|
}
|
|
6818
6880
|
|
|
6881
|
+
// node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
|
|
6882
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6883
|
+
import { existsSync as existsSync6 } from "fs";
|
|
6884
|
+
import { homedir } from "os";
|
|
6885
|
+
import { join as join5 } from "path";
|
|
6886
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
6887
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
6888
|
+
import { spawn } from "child_process";
|
|
6889
|
+
import { randomUUID as randomUUID22 } from "crypto";
|
|
6890
|
+
function getPathValue(input, path) {
|
|
6891
|
+
return path.split(".").reduce((value, part) => {
|
|
6892
|
+
if (value && typeof value === "object" && part in value) {
|
|
6893
|
+
return value[part];
|
|
6894
|
+
}
|
|
6895
|
+
return;
|
|
6896
|
+
}, input);
|
|
6897
|
+
}
|
|
6898
|
+
function wildcardToRegExp(pattern) {
|
|
6899
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
6900
|
+
return new RegExp(`^${escaped}$`);
|
|
6901
|
+
}
|
|
6902
|
+
function matchString(value, matcher) {
|
|
6903
|
+
if (matcher === undefined)
|
|
6904
|
+
return true;
|
|
6905
|
+
if (value === undefined)
|
|
6906
|
+
return false;
|
|
6907
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
6908
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
6909
|
+
}
|
|
6910
|
+
function matchRecord(input, matcher) {
|
|
6911
|
+
if (!matcher)
|
|
6912
|
+
return true;
|
|
6913
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
6914
|
+
const actual = getPathValue(input, path);
|
|
6915
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
6916
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
6917
|
+
}
|
|
6918
|
+
return actual === expected;
|
|
6919
|
+
});
|
|
6920
|
+
}
|
|
6921
|
+
function eventMatchesFilter(event, filter) {
|
|
6922
|
+
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);
|
|
6923
|
+
}
|
|
6924
|
+
function channelMatchesEvent(channel, event) {
|
|
6925
|
+
if (!channel.enabled)
|
|
6926
|
+
return false;
|
|
6927
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
6928
|
+
return true;
|
|
6929
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
6930
|
+
}
|
|
6931
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
6932
|
+
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
6933
|
+
function getEventsDataDir(override) {
|
|
6934
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
|
|
6935
|
+
}
|
|
6936
|
+
|
|
6937
|
+
class JsonEventsStore {
|
|
6938
|
+
dataDir;
|
|
6939
|
+
channelsPath;
|
|
6940
|
+
eventsPath;
|
|
6941
|
+
deliveriesPath;
|
|
6942
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
6943
|
+
this.dataDir = dataDir;
|
|
6944
|
+
this.channelsPath = join5(dataDir, "channels.json");
|
|
6945
|
+
this.eventsPath = join5(dataDir, "events.json");
|
|
6946
|
+
this.deliveriesPath = join5(dataDir, "deliveries.json");
|
|
6947
|
+
}
|
|
6948
|
+
async init() {
|
|
6949
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
6950
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
6951
|
+
return;
|
|
6952
|
+
});
|
|
6953
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
6954
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
6955
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
6956
|
+
}
|
|
6957
|
+
async addChannel(channel) {
|
|
6958
|
+
await this.init();
|
|
6959
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
6960
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
6961
|
+
if (index >= 0) {
|
|
6962
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
6963
|
+
} else {
|
|
6964
|
+
channels.push(channel);
|
|
6965
|
+
}
|
|
6966
|
+
await this.writeJson(this.channelsPath, channels);
|
|
6967
|
+
return index >= 0 ? channels[index] : channel;
|
|
6968
|
+
}
|
|
6969
|
+
async listChannels() {
|
|
6970
|
+
await this.init();
|
|
6971
|
+
return this.readJson(this.channelsPath, []);
|
|
6972
|
+
}
|
|
6973
|
+
async getChannel(id) {
|
|
6974
|
+
const channels = await this.listChannels();
|
|
6975
|
+
return channels.find((channel) => channel.id === id);
|
|
6976
|
+
}
|
|
6977
|
+
async removeChannel(id) {
|
|
6978
|
+
await this.init();
|
|
6979
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
6980
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
6981
|
+
await this.writeJson(this.channelsPath, next);
|
|
6982
|
+
return next.length !== channels.length;
|
|
6983
|
+
}
|
|
6984
|
+
async appendEvent(event) {
|
|
6985
|
+
await this.init();
|
|
6986
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
6987
|
+
events.push(event);
|
|
6988
|
+
await this.writeJson(this.eventsPath, events);
|
|
6989
|
+
return event;
|
|
6990
|
+
}
|
|
6991
|
+
async listEvents() {
|
|
6992
|
+
await this.init();
|
|
6993
|
+
return this.readJson(this.eventsPath, []);
|
|
6994
|
+
}
|
|
6995
|
+
async findEventByIdentity(identity) {
|
|
6996
|
+
const events = await this.listEvents();
|
|
6997
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
6998
|
+
}
|
|
6999
|
+
async appendDelivery(result) {
|
|
7000
|
+
await this.init();
|
|
7001
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
7002
|
+
deliveries.push(result);
|
|
7003
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
7004
|
+
return result;
|
|
7005
|
+
}
|
|
7006
|
+
async listDeliveries() {
|
|
7007
|
+
await this.init();
|
|
7008
|
+
return this.readJson(this.deliveriesPath, []);
|
|
7009
|
+
}
|
|
7010
|
+
async exportData() {
|
|
7011
|
+
return {
|
|
7012
|
+
channels: await this.listChannels(),
|
|
7013
|
+
events: await this.listEvents(),
|
|
7014
|
+
deliveries: await this.listDeliveries()
|
|
7015
|
+
};
|
|
7016
|
+
}
|
|
7017
|
+
async ensureArrayFile(path) {
|
|
7018
|
+
if (!existsSync6(path)) {
|
|
7019
|
+
await writeFile(path, `[]
|
|
7020
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
7021
|
+
}
|
|
7022
|
+
await chmod(path, 384).catch(() => {
|
|
7023
|
+
return;
|
|
7024
|
+
});
|
|
7025
|
+
}
|
|
7026
|
+
async readJson(path, fallback) {
|
|
7027
|
+
try {
|
|
7028
|
+
const raw = await readFile(path, "utf-8");
|
|
7029
|
+
if (!raw.trim())
|
|
7030
|
+
return fallback;
|
|
7031
|
+
return JSON.parse(raw);
|
|
7032
|
+
} catch (error) {
|
|
7033
|
+
if (error.code === "ENOENT")
|
|
7034
|
+
return fallback;
|
|
7035
|
+
throw error;
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
async writeJson(path, value) {
|
|
7039
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
7040
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
|
|
7041
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
7042
|
+
await rename(tempPath, path);
|
|
7043
|
+
await chmod(path, 384).catch(() => {
|
|
7044
|
+
return;
|
|
7045
|
+
});
|
|
7046
|
+
}
|
|
7047
|
+
}
|
|
7048
|
+
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
7049
|
+
function buildSignatureBase(timestamp, body) {
|
|
7050
|
+
return `${timestamp}.${body}`;
|
|
7051
|
+
}
|
|
7052
|
+
function signPayload(secret, timestamp, body) {
|
|
7053
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
7054
|
+
return `sha256=${digest}`;
|
|
7055
|
+
}
|
|
7056
|
+
function now2() {
|
|
7057
|
+
return new Date().toISOString();
|
|
7058
|
+
}
|
|
7059
|
+
function truncate(value, max = 4096) {
|
|
7060
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
7061
|
+
}
|
|
7062
|
+
function buildWebhookRequest(event, channel) {
|
|
7063
|
+
if (!channel.webhook)
|
|
7064
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
7065
|
+
const body = JSON.stringify(event);
|
|
7066
|
+
const timestamp = event.time;
|
|
7067
|
+
const headers = {
|
|
7068
|
+
"Content-Type": "application/json",
|
|
7069
|
+
"User-Agent": "@hasna/events",
|
|
7070
|
+
"X-Hasna-Event-Id": event.id,
|
|
7071
|
+
"X-Hasna-Event-Type": event.type,
|
|
7072
|
+
"X-Hasna-Timestamp": timestamp,
|
|
7073
|
+
...channel.webhook.headers
|
|
7074
|
+
};
|
|
7075
|
+
if (channel.webhook.secret) {
|
|
7076
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
7077
|
+
}
|
|
7078
|
+
return { body, headers };
|
|
7079
|
+
}
|
|
7080
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
7081
|
+
if (!channel.webhook)
|
|
7082
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
7083
|
+
const startedAt = now2();
|
|
7084
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
7085
|
+
const controller = new AbortController;
|
|
7086
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
7087
|
+
try {
|
|
7088
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
7089
|
+
method: "POST",
|
|
7090
|
+
headers,
|
|
7091
|
+
body,
|
|
7092
|
+
signal: controller.signal
|
|
7093
|
+
});
|
|
7094
|
+
const responseBody = truncate(await response.text());
|
|
7095
|
+
return {
|
|
7096
|
+
attempt: 1,
|
|
7097
|
+
status: response.ok ? "success" : "failed",
|
|
7098
|
+
startedAt,
|
|
7099
|
+
completedAt: now2(),
|
|
7100
|
+
responseStatus: response.status,
|
|
7101
|
+
responseBody,
|
|
7102
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
7103
|
+
};
|
|
7104
|
+
} catch (error) {
|
|
7105
|
+
return {
|
|
7106
|
+
attempt: 1,
|
|
7107
|
+
status: "failed",
|
|
7108
|
+
startedAt,
|
|
7109
|
+
completedAt: now2(),
|
|
7110
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7111
|
+
};
|
|
7112
|
+
} finally {
|
|
7113
|
+
clearTimeout(timeout);
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
async function dispatchCommand(event, channel) {
|
|
7117
|
+
if (!channel.command)
|
|
7118
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
7119
|
+
const startedAt = now2();
|
|
7120
|
+
const eventJson = JSON.stringify(event);
|
|
7121
|
+
const env = {
|
|
7122
|
+
...process.env,
|
|
7123
|
+
...channel.command.env,
|
|
7124
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
7125
|
+
HASNA_EVENT_ID: event.id,
|
|
7126
|
+
HASNA_EVENT_TYPE: event.type,
|
|
7127
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
7128
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
7129
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
7130
|
+
HASNA_EVENT_TIME: event.time,
|
|
7131
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
7132
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
7133
|
+
HASNA_EVENT_JSON: eventJson
|
|
7134
|
+
};
|
|
7135
|
+
return new Promise((resolve6) => {
|
|
7136
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
7137
|
+
cwd: channel.command.cwd,
|
|
7138
|
+
env,
|
|
7139
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7140
|
+
});
|
|
7141
|
+
let stdout = "";
|
|
7142
|
+
let stderr = "";
|
|
7143
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
7144
|
+
child.stdin.end(eventJson);
|
|
7145
|
+
child.stdout.on("data", (chunk) => {
|
|
7146
|
+
stdout += chunk.toString();
|
|
7147
|
+
});
|
|
7148
|
+
child.stderr.on("data", (chunk) => {
|
|
7149
|
+
stderr += chunk.toString();
|
|
7150
|
+
});
|
|
7151
|
+
child.on("error", (error) => {
|
|
7152
|
+
clearTimeout(timeout);
|
|
7153
|
+
resolve6({
|
|
7154
|
+
attempt: 1,
|
|
7155
|
+
status: "failed",
|
|
7156
|
+
startedAt,
|
|
7157
|
+
completedAt: now2(),
|
|
7158
|
+
stdout: truncate(stdout),
|
|
7159
|
+
stderr: truncate(stderr),
|
|
7160
|
+
error: error.message
|
|
7161
|
+
});
|
|
7162
|
+
});
|
|
7163
|
+
child.on("close", (code, signal) => {
|
|
7164
|
+
clearTimeout(timeout);
|
|
7165
|
+
const success = code === 0;
|
|
7166
|
+
resolve6({
|
|
7167
|
+
attempt: 1,
|
|
7168
|
+
status: success ? "success" : "failed",
|
|
7169
|
+
startedAt,
|
|
7170
|
+
completedAt: now2(),
|
|
7171
|
+
stdout: truncate(stdout),
|
|
7172
|
+
stderr: truncate(stderr),
|
|
7173
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
7174
|
+
});
|
|
7175
|
+
});
|
|
7176
|
+
});
|
|
7177
|
+
}
|
|
7178
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
7179
|
+
if (channel.transport === "webhook")
|
|
7180
|
+
return dispatchWebhook(event, channel, options);
|
|
7181
|
+
if (channel.transport === "command")
|
|
7182
|
+
return dispatchCommand(event, channel);
|
|
7183
|
+
return {
|
|
7184
|
+
attempt: 1,
|
|
7185
|
+
status: "skipped",
|
|
7186
|
+
startedAt: now2(),
|
|
7187
|
+
completedAt: now2(),
|
|
7188
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
7189
|
+
};
|
|
7190
|
+
}
|
|
7191
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
7192
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
7193
|
+
return {
|
|
7194
|
+
id: randomUUID2(),
|
|
7195
|
+
eventId: event.id,
|
|
7196
|
+
channelId: channel.id,
|
|
7197
|
+
transport: channel.transport,
|
|
7198
|
+
status,
|
|
7199
|
+
attempts,
|
|
7200
|
+
createdAt: attempts[0]?.startedAt ?? now2(),
|
|
7201
|
+
completedAt: attempts.at(-1)?.completedAt ?? now2()
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
function createEvent(input) {
|
|
7205
|
+
return {
|
|
7206
|
+
id: input.id ?? randomUUID22(),
|
|
7207
|
+
source: input.source,
|
|
7208
|
+
type: input.type,
|
|
7209
|
+
time: normalizeTime(input.time),
|
|
7210
|
+
subject: input.subject,
|
|
7211
|
+
severity: input.severity ?? "info",
|
|
7212
|
+
data: input.data ?? {},
|
|
7213
|
+
message: input.message,
|
|
7214
|
+
dedupeKey: input.dedupeKey,
|
|
7215
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
7216
|
+
metadata: input.metadata ?? {}
|
|
7217
|
+
};
|
|
7218
|
+
}
|
|
7219
|
+
|
|
7220
|
+
class EventsClient {
|
|
7221
|
+
store;
|
|
7222
|
+
redactors;
|
|
7223
|
+
transportOptions;
|
|
7224
|
+
constructor(options = {}) {
|
|
7225
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
7226
|
+
this.redactors = options.redactors ?? [];
|
|
7227
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
7228
|
+
}
|
|
7229
|
+
async addChannel(input) {
|
|
7230
|
+
const timestamp = new Date().toISOString();
|
|
7231
|
+
return this.store.addChannel({
|
|
7232
|
+
...input,
|
|
7233
|
+
createdAt: input.createdAt ?? timestamp,
|
|
7234
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
7235
|
+
});
|
|
7236
|
+
}
|
|
7237
|
+
async listChannels() {
|
|
7238
|
+
return this.store.listChannels();
|
|
7239
|
+
}
|
|
7240
|
+
async removeChannel(id) {
|
|
7241
|
+
return this.store.removeChannel(id);
|
|
7242
|
+
}
|
|
7243
|
+
async emit(input, options = {}) {
|
|
7244
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
7245
|
+
if (options.dedupe !== false) {
|
|
7246
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
7247
|
+
if (existing) {
|
|
7248
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
await this.store.appendEvent(event);
|
|
7252
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
7253
|
+
return { event, deliveries, deduped: false };
|
|
7254
|
+
}
|
|
7255
|
+
async listEvents() {
|
|
7256
|
+
return this.store.listEvents();
|
|
7257
|
+
}
|
|
7258
|
+
async listDeliveries() {
|
|
7259
|
+
return this.store.listDeliveries();
|
|
7260
|
+
}
|
|
7261
|
+
async deliver(event) {
|
|
7262
|
+
const channels = await this.store.listChannels();
|
|
7263
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
7264
|
+
const deliveries = [];
|
|
7265
|
+
for (const channel of selected) {
|
|
7266
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
7267
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
7268
|
+
await this.store.appendDelivery(result);
|
|
7269
|
+
deliveries.push(result);
|
|
7270
|
+
}
|
|
7271
|
+
return deliveries;
|
|
7272
|
+
}
|
|
7273
|
+
async testChannel(id, input = {}) {
|
|
7274
|
+
const channel = await this.store.getChannel(id);
|
|
7275
|
+
if (!channel)
|
|
7276
|
+
throw new Error(`Channel not found: ${id}`);
|
|
7277
|
+
const event = createEvent({
|
|
7278
|
+
source: input.source ?? "hasna.events",
|
|
7279
|
+
type: input.type ?? "events.test",
|
|
7280
|
+
subject: input.subject ?? id,
|
|
7281
|
+
severity: input.severity ?? "info",
|
|
7282
|
+
data: input.data ?? { test: true },
|
|
7283
|
+
message: input.message ?? "Hasna events test delivery",
|
|
7284
|
+
dedupeKey: input.dedupeKey,
|
|
7285
|
+
schemaVersion: input.schemaVersion,
|
|
7286
|
+
metadata: input.metadata,
|
|
7287
|
+
time: input.time,
|
|
7288
|
+
id: input.id
|
|
7289
|
+
});
|
|
7290
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
7291
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
7292
|
+
await this.store.appendDelivery(result);
|
|
7293
|
+
return result;
|
|
7294
|
+
}
|
|
7295
|
+
async replay(options = {}) {
|
|
7296
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
7297
|
+
if (options.eventId && event.id !== options.eventId)
|
|
7298
|
+
return false;
|
|
7299
|
+
if (options.source && event.source !== options.source)
|
|
7300
|
+
return false;
|
|
7301
|
+
if (options.type && event.type !== options.type)
|
|
7302
|
+
return false;
|
|
7303
|
+
return true;
|
|
7304
|
+
});
|
|
7305
|
+
if (options.dryRun)
|
|
7306
|
+
return { events, deliveries: [] };
|
|
7307
|
+
const deliveries = [];
|
|
7308
|
+
for (const event of events) {
|
|
7309
|
+
deliveries.push(...await this.deliver(event));
|
|
7310
|
+
}
|
|
7311
|
+
return { events, deliveries };
|
|
7312
|
+
}
|
|
7313
|
+
async applyRedaction(event, channel) {
|
|
7314
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
7315
|
+
for (const redactor of this.redactors) {
|
|
7316
|
+
next = await redactor(next, channel);
|
|
7317
|
+
}
|
|
7318
|
+
return next;
|
|
7319
|
+
}
|
|
7320
|
+
async deliverWithRetry(event, channel) {
|
|
7321
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
7322
|
+
const attempts = [];
|
|
7323
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
7324
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
7325
|
+
attempt.attempt = index + 1;
|
|
7326
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
7327
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
7328
|
+
}
|
|
7329
|
+
attempts.push(attempt);
|
|
7330
|
+
if (attempt.status !== "failed")
|
|
7331
|
+
break;
|
|
7332
|
+
if (attempt.nextBackoffMs)
|
|
7333
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
7334
|
+
}
|
|
7335
|
+
return createDeliveryResult(event, channel, attempts);
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
7339
|
+
if (paths.length === 0)
|
|
7340
|
+
return event;
|
|
7341
|
+
const copy = structuredClone(event);
|
|
7342
|
+
for (const path of paths) {
|
|
7343
|
+
setPath(copy, path, replacement);
|
|
7344
|
+
}
|
|
7345
|
+
return copy;
|
|
7346
|
+
}
|
|
7347
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
7348
|
+
return redactValue2(event, replacement);
|
|
7349
|
+
}
|
|
7350
|
+
function shouldRedactKey(key) {
|
|
7351
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
7352
|
+
}
|
|
7353
|
+
function redactValue2(value, replacement) {
|
|
7354
|
+
if (Array.isArray(value))
|
|
7355
|
+
return value.map((item) => redactValue2(item, replacement));
|
|
7356
|
+
if (!value || typeof value !== "object")
|
|
7357
|
+
return value;
|
|
7358
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
7359
|
+
key,
|
|
7360
|
+
shouldRedactKey(key) ? replacement : redactValue2(item, replacement)
|
|
7361
|
+
]));
|
|
7362
|
+
}
|
|
7363
|
+
function setPath(input, path, replacement) {
|
|
7364
|
+
const parts = path.split(".");
|
|
7365
|
+
let cursor = input;
|
|
7366
|
+
for (const part of parts.slice(0, -1)) {
|
|
7367
|
+
const next = cursor[part];
|
|
7368
|
+
if (!next || typeof next !== "object")
|
|
7369
|
+
return;
|
|
7370
|
+
cursor = next;
|
|
7371
|
+
}
|
|
7372
|
+
const last = parts.at(-1);
|
|
7373
|
+
if (last && last in cursor)
|
|
7374
|
+
cursor[last] = replacement;
|
|
7375
|
+
}
|
|
7376
|
+
function normalizeTime(value) {
|
|
7377
|
+
if (!value)
|
|
7378
|
+
return new Date().toISOString();
|
|
7379
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
7380
|
+
}
|
|
7381
|
+
function normalizeRetryPolicy(policy) {
|
|
7382
|
+
return {
|
|
7383
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
7384
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
7385
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
7386
|
+
};
|
|
7387
|
+
}
|
|
7388
|
+
|
|
7389
|
+
// src/lib/shared-events.ts
|
|
7390
|
+
var SOURCE = "todos";
|
|
7391
|
+
function taskEventData(task, extra = {}) {
|
|
7392
|
+
return {
|
|
7393
|
+
id: task.id,
|
|
7394
|
+
task_id: task.id,
|
|
7395
|
+
short_id: task.short_id,
|
|
7396
|
+
title: task.title,
|
|
7397
|
+
description: task.description,
|
|
7398
|
+
status: task.status,
|
|
7399
|
+
priority: task.priority,
|
|
7400
|
+
project_id: task.project_id,
|
|
7401
|
+
parent_id: task.parent_id,
|
|
7402
|
+
plan_id: task.plan_id,
|
|
7403
|
+
task_list_id: task.task_list_id,
|
|
7404
|
+
agent_id: task.agent_id,
|
|
7405
|
+
assigned_to: task.assigned_to,
|
|
7406
|
+
session_id: task.session_id,
|
|
7407
|
+
working_dir: task.working_dir,
|
|
7408
|
+
tags: task.tags,
|
|
7409
|
+
metadata: task.metadata,
|
|
7410
|
+
version: task.version,
|
|
7411
|
+
created_at: task.created_at,
|
|
7412
|
+
updated_at: task.updated_at,
|
|
7413
|
+
started_at: task.started_at,
|
|
7414
|
+
completed_at: task.completed_at,
|
|
7415
|
+
due_at: task.due_at,
|
|
7416
|
+
...extra
|
|
7417
|
+
};
|
|
7418
|
+
}
|
|
7419
|
+
async function emitSharedTaskEvent(input) {
|
|
7420
|
+
const data = taskEventData(input.task, input.data);
|
|
7421
|
+
await new EventsClient().emit({
|
|
7422
|
+
source: SOURCE,
|
|
7423
|
+
type: input.type,
|
|
7424
|
+
subject: input.task.id,
|
|
7425
|
+
severity: input.severity ?? "info",
|
|
7426
|
+
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
7427
|
+
data,
|
|
7428
|
+
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
7429
|
+
metadata: {
|
|
7430
|
+
package: "@hasna/todos",
|
|
7431
|
+
task_id: input.task.id,
|
|
7432
|
+
project_id: input.task.project_id,
|
|
7433
|
+
task_list_id: input.task.task_list_id
|
|
7434
|
+
}
|
|
7435
|
+
}, { deliver: true, dedupe: true });
|
|
7436
|
+
}
|
|
7437
|
+
function emitSharedTaskEventQuiet(input) {
|
|
7438
|
+
emitSharedTaskEvent(input).catch(() => {
|
|
7439
|
+
return;
|
|
7440
|
+
});
|
|
7441
|
+
}
|
|
7442
|
+
|
|
6819
7443
|
// src/db/audit.ts
|
|
6820
7444
|
init_database();
|
|
6821
7445
|
function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db) {
|
|
@@ -7078,7 +7702,7 @@ async function deliverWebhook(wh, event, body, attempt, db) {
|
|
|
7078
7702
|
activeDeliveries--;
|
|
7079
7703
|
}
|
|
7080
7704
|
}
|
|
7081
|
-
async function
|
|
7705
|
+
async function dispatchWebhook2(event, payload, db) {
|
|
7082
7706
|
const d = db || getDatabase();
|
|
7083
7707
|
const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
|
|
7084
7708
|
const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
|
|
@@ -7232,7 +7856,10 @@ function createTask(input, db) {
|
|
|
7232
7856
|
insertTaskTags(id, tags, d);
|
|
7233
7857
|
}
|
|
7234
7858
|
const task = getTask(id, d);
|
|
7235
|
-
|
|
7859
|
+
const payload = taskEventData(task);
|
|
7860
|
+
dispatchWebhook2("task.created", payload, d).catch(() => {});
|
|
7861
|
+
emitLocalEventHooksQuiet({ type: "task.created", payload });
|
|
7862
|
+
emitSharedTaskEventQuiet({ type: "task.created", task });
|
|
7236
7863
|
return task;
|
|
7237
7864
|
}
|
|
7238
7865
|
function getTask(id, db) {
|
|
@@ -7576,18 +8203,7 @@ function updateTask(id, input, db) {
|
|
|
7576
8203
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
7577
8204
|
if (input.approved_by !== undefined)
|
|
7578
8205
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
7579
|
-
|
|
7580
|
-
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
7581
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id, assigned_to: input.assigned_to, title: task.title } });
|
|
7582
|
-
}
|
|
7583
|
-
if (input.status !== undefined && input.status !== task.status) {
|
|
7584
|
-
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
7585
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload: { id, old_status: task.status, new_status: input.status, title: task.title } });
|
|
7586
|
-
}
|
|
7587
|
-
if (input.approved_by !== undefined) {
|
|
7588
|
-
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
7589
|
-
}
|
|
7590
|
-
return {
|
|
8206
|
+
const updatedTask = {
|
|
7591
8207
|
...task,
|
|
7592
8208
|
...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
|
|
7593
8209
|
tags: input.tags ?? task.tags,
|
|
@@ -7605,6 +8221,22 @@ function updateTask(id, input, db) {
|
|
|
7605
8221
|
approved_by: input.approved_by ?? task.approved_by,
|
|
7606
8222
|
approved_at: input.approved_by ? timestamp : task.approved_at
|
|
7607
8223
|
};
|
|
8224
|
+
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
8225
|
+
const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
|
|
8226
|
+
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
8227
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
8228
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
|
|
8229
|
+
}
|
|
8230
|
+
if (input.status !== undefined && input.status !== task.status) {
|
|
8231
|
+
const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
|
|
8232
|
+
dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
|
|
8233
|
+
emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
|
|
8234
|
+
emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
|
|
8235
|
+
}
|
|
8236
|
+
if (input.approved_by !== undefined) {
|
|
8237
|
+
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
8238
|
+
}
|
|
8239
|
+
return updatedTask;
|
|
7608
8240
|
}
|
|
7609
8241
|
function deleteTask(id, db) {
|
|
7610
8242
|
const d = db || getDatabase();
|
|
@@ -8337,9 +8969,12 @@ function startTask(id, agentId, db) {
|
|
|
8337
8969
|
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
8338
8970
|
}
|
|
8339
8971
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8972
|
+
const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, started_at: task.started_at || timestamp, version: task.version + 1, updated_at: timestamp };
|
|
8973
|
+
const payload = taskEventData(startedTask, { agent_id: agentId });
|
|
8974
|
+
dispatchWebhook2("task.started", payload, d).catch(() => {});
|
|
8975
|
+
emitLocalEventHooksQuiet({ type: "task.started", payload });
|
|
8976
|
+
emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
|
|
8977
|
+
return startedTask;
|
|
8343
8978
|
}
|
|
8344
8979
|
function completeTask(id, agentId, db, options) {
|
|
8345
8980
|
const d = db || getDatabase();
|
|
@@ -8375,8 +9010,21 @@ function completeTask(id, agentId, db, options) {
|
|
|
8375
9010
|
});
|
|
8376
9011
|
tx();
|
|
8377
9012
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
8378
|
-
|
|
8379
|
-
|
|
9013
|
+
const completedTaskForEvent = {
|
|
9014
|
+
...task,
|
|
9015
|
+
status: "completed",
|
|
9016
|
+
locked_by: null,
|
|
9017
|
+
locked_at: null,
|
|
9018
|
+
completed_at: timestamp,
|
|
9019
|
+
confidence,
|
|
9020
|
+
version: task.version + 1,
|
|
9021
|
+
updated_at: timestamp,
|
|
9022
|
+
metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
|
|
9023
|
+
};
|
|
9024
|
+
const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
|
|
9025
|
+
dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
|
|
9026
|
+
emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
|
|
9027
|
+
emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
|
|
8380
9028
|
let spawnedTask = null;
|
|
8381
9029
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
8382
9030
|
spawnedTask = spawnNextRecurrence(task, d, timestamp);
|
|
@@ -8417,8 +9065,12 @@ function completeTask(id, agentId, db, options) {
|
|
|
8417
9065
|
if (unblockedDeps.length > 0) {
|
|
8418
9066
|
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
8419
9067
|
for (const dep of unblockedDeps) {
|
|
8420
|
-
|
|
8421
|
-
|
|
9068
|
+
const depTask = getTask(dep.id, d);
|
|
9069
|
+
const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
|
|
9070
|
+
dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
|
|
9071
|
+
emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
|
|
9072
|
+
if (depTask)
|
|
9073
|
+
emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
|
|
8422
9074
|
}
|
|
8423
9075
|
}
|
|
8424
9076
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -8604,9 +9256,6 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
8604
9256
|
const timestamp = now();
|
|
8605
9257
|
d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
|
|
8606
9258
|
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
8607
|
-
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
8608
|
-
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
8609
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title } });
|
|
8610
9259
|
const failedTask = {
|
|
8611
9260
|
...task,
|
|
8612
9261
|
status: "failed",
|
|
@@ -8616,6 +9265,11 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
8616
9265
|
version: task.version + 1,
|
|
8617
9266
|
updated_at: timestamp
|
|
8618
9267
|
};
|
|
9268
|
+
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
9269
|
+
const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
|
|
9270
|
+
dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
|
|
9271
|
+
emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
|
|
9272
|
+
emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
|
|
8619
9273
|
let retryTask;
|
|
8620
9274
|
if (options?.retry) {
|
|
8621
9275
|
const retryCount = (task.retry_count || 0) + 1;
|
|
@@ -8690,9 +9344,12 @@ function stealTask(agentId, opts, db) {
|
|
|
8690
9344
|
return null;
|
|
8691
9345
|
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
8692
9346
|
logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
9347
|
+
const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
9348
|
+
const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
|
|
9349
|
+
dispatchWebhook2("task.assigned", payload, d).catch(() => {});
|
|
9350
|
+
emitLocalEventHooksQuiet({ type: "task.assigned", payload });
|
|
9351
|
+
emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
|
|
9352
|
+
return stolenTask;
|
|
8696
9353
|
}
|
|
8697
9354
|
function claimOrSteal(agentId, filters, db) {
|
|
8698
9355
|
const d = db || getDatabase();
|
|
@@ -9751,8 +10408,8 @@ init_database();
|
|
|
9751
10408
|
init_database();
|
|
9752
10409
|
init_redaction();
|
|
9753
10410
|
import { createHash as createHash2 } from "crypto";
|
|
9754
|
-
import { existsSync as
|
|
9755
|
-
import { basename, dirname as dirname5, join as
|
|
10411
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10412
|
+
import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
|
|
9756
10413
|
import { tmpdir } from "os";
|
|
9757
10414
|
function isInMemoryDb2(path) {
|
|
9758
10415
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
@@ -9764,15 +10421,15 @@ function artifactStoreRoot() {
|
|
|
9764
10421
|
return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
9765
10422
|
const dbPath = getDatabasePath();
|
|
9766
10423
|
if (isInMemoryDb2(dbPath))
|
|
9767
|
-
return
|
|
9768
|
-
return
|
|
10424
|
+
return join6(tmpdir(), "hasna-todos-artifacts");
|
|
10425
|
+
return join6(dirname5(resolve6(dbPath)), "artifacts");
|
|
9769
10426
|
}
|
|
9770
10427
|
function artifactStorePath(relativePath) {
|
|
9771
10428
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
9772
10429
|
if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
|
|
9773
10430
|
throw new Error("Invalid artifact store path");
|
|
9774
10431
|
}
|
|
9775
|
-
return
|
|
10432
|
+
return join6(artifactStoreRoot(), normalized);
|
|
9776
10433
|
}
|
|
9777
10434
|
function sha256(buffer) {
|
|
9778
10435
|
return createHash2("sha256").update(buffer).digest("hex");
|
|
@@ -9813,7 +10470,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
9813
10470
|
}
|
|
9814
10471
|
function storeArtifactContent(input) {
|
|
9815
10472
|
const sourcePath = resolve6(input.path);
|
|
9816
|
-
if (!
|
|
10473
|
+
if (!existsSync8(sourcePath))
|
|
9817
10474
|
return null;
|
|
9818
10475
|
const sourceStat = statSync2(sourcePath);
|
|
9819
10476
|
if (!sourceStat.isFile())
|
|
@@ -9830,9 +10487,9 @@ function storeArtifactContent(input) {
|
|
|
9830
10487
|
redactionStatus = "redacted";
|
|
9831
10488
|
}
|
|
9832
10489
|
const storedSha = sha256(storedBuffer);
|
|
9833
|
-
const relativePath =
|
|
10490
|
+
const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
9834
10491
|
const destination = artifactStorePath(relativePath);
|
|
9835
|
-
if (!
|
|
10492
|
+
if (!existsSync8(destination)) {
|
|
9836
10493
|
mkdirSync4(dirname5(destination), { recursive: true });
|
|
9837
10494
|
writeFileSync2(destination, storedBuffer);
|
|
9838
10495
|
}
|
|
@@ -9892,7 +10549,7 @@ function verifyStoredArtifact(input) {
|
|
|
9892
10549
|
};
|
|
9893
10550
|
}
|
|
9894
10551
|
const storedPath = artifactStorePath(store.relative_path);
|
|
9895
|
-
if (!
|
|
10552
|
+
if (!existsSync8(storedPath)) {
|
|
9896
10553
|
return {
|
|
9897
10554
|
id: input.id,
|
|
9898
10555
|
path: input.path,
|
|
@@ -9973,15 +10630,15 @@ function getArtifactStoreRoot(dbPath) {
|
|
|
9973
10630
|
return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
9974
10631
|
const path = dbPath ?? getDatabasePath();
|
|
9975
10632
|
if (isInMemoryDb2(path))
|
|
9976
|
-
return
|
|
9977
|
-
return
|
|
10633
|
+
return join6(tmpdir(), "hasna-todos-artifacts");
|
|
10634
|
+
return join6(dirname5(resolve6(path)), "artifacts");
|
|
9978
10635
|
}
|
|
9979
10636
|
function computeContentHash(path) {
|
|
9980
10637
|
return sha256(readFileSync4(resolve6(path)));
|
|
9981
10638
|
}
|
|
9982
10639
|
function storeArtifactFile(input) {
|
|
9983
10640
|
const sourcePath = resolve6(input.sourcePath);
|
|
9984
|
-
if (!
|
|
10641
|
+
if (!existsSync8(sourcePath)) {
|
|
9985
10642
|
throw new Error(`Source file not found: ${input.sourcePath}`);
|
|
9986
10643
|
}
|
|
9987
10644
|
if (!statSync2(sourcePath).isFile()) {
|
|
@@ -9994,7 +10651,7 @@ function storeArtifactFile(input) {
|
|
|
9994
10651
|
let localPath = sourcePath;
|
|
9995
10652
|
if (storageMode === "copy") {
|
|
9996
10653
|
const fileName = input.name && input.name.trim().length > 0 ? basename(input.name) : basename(sourcePath);
|
|
9997
|
-
const destination =
|
|
10654
|
+
const destination = join6(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
|
|
9998
10655
|
mkdirSync4(dirname5(destination), { recursive: true });
|
|
9999
10656
|
writeFileSync2(destination, buffer);
|
|
10000
10657
|
localPath = destination;
|
|
@@ -10004,7 +10661,7 @@ function storeArtifactFile(input) {
|
|
|
10004
10661
|
function deleteStoredArtifactFile(localPath, storageMode, _dbPath2) {
|
|
10005
10662
|
if (storageMode === "reference")
|
|
10006
10663
|
return false;
|
|
10007
|
-
if (!localPath || !
|
|
10664
|
+
if (!localPath || !existsSync8(localPath))
|
|
10008
10665
|
return false;
|
|
10009
10666
|
rmSync(localPath, { force: true });
|
|
10010
10667
|
try {
|
|
@@ -10016,8 +10673,8 @@ function isArtifactExpired(deletedAt, policy = {}) {
|
|
|
10016
10673
|
if (!deletedAt)
|
|
10017
10674
|
return false;
|
|
10018
10675
|
const retentionDays = policy.deleted_retention_days ?? DEFAULT_DELETED_RETENTION_DAYS;
|
|
10019
|
-
const
|
|
10020
|
-
const ageMs =
|
|
10676
|
+
const now3 = policy.now ?? new Date;
|
|
10677
|
+
const ageMs = now3.getTime() - new Date(deletedAt).getTime();
|
|
10021
10678
|
return ageMs > retentionDays * 24 * 60 * 60 * 1000;
|
|
10022
10679
|
}
|
|
10023
10680
|
function buildArtifactExportManifest(artifacts, dbPath) {
|
|
@@ -11783,7 +12440,7 @@ function writeOnboardingFixtureFiles(directory) {
|
|
|
11783
12440
|
mkdirSync5(directory, { recursive: true });
|
|
11784
12441
|
const files = [];
|
|
11785
12442
|
for (const fixture of allFixtures()) {
|
|
11786
|
-
const path =
|
|
12443
|
+
const path = join7(directory, `${fixture.summary.name}.bridge.json`);
|
|
11787
12444
|
writeFileSync3(path, `${JSON.stringify(fixture.bundle, null, 2)}
|
|
11788
12445
|
`, "utf-8");
|
|
11789
12446
|
files.push(path);
|
|
@@ -12609,7 +13266,7 @@ function renderLocalSnapshotMarkdown(snapshot) {
|
|
|
12609
13266
|
}
|
|
12610
13267
|
// src/lib/sdk-integration-fixtures.ts
|
|
12611
13268
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
12612
|
-
import { join as
|
|
13269
|
+
import { join as join8 } from "path";
|
|
12613
13270
|
|
|
12614
13271
|
// src/cli-mcp-parity.ts
|
|
12615
13272
|
function source4(version) {
|
|
@@ -13919,7 +14576,7 @@ function limits(input) {
|
|
|
13919
14576
|
stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
|
|
13920
14577
|
};
|
|
13921
14578
|
}
|
|
13922
|
-
function
|
|
14579
|
+
function truncate2(value, max) {
|
|
13923
14580
|
if (!value)
|
|
13924
14581
|
return value ?? null;
|
|
13925
14582
|
const redacted = redactEvidenceText(value);
|
|
@@ -13940,9 +14597,9 @@ function acceptanceCriteria(task2, maxText) {
|
|
|
13940
14597
|
const metadata = task2.metadata || {};
|
|
13941
14598
|
const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
|
|
13942
14599
|
if (Array.isArray(raw))
|
|
13943
|
-
return raw.map((item) =>
|
|
14600
|
+
return raw.map((item) => truncate2(String(item), maxText)).filter((item) => Boolean(item));
|
|
13944
14601
|
if (typeof raw === "string") {
|
|
13945
|
-
return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) =>
|
|
14602
|
+
return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate2(line, maxText)).filter((item) => Boolean(item));
|
|
13946
14603
|
}
|
|
13947
14604
|
return [];
|
|
13948
14605
|
}
|
|
@@ -13965,7 +14622,7 @@ function addFile(files, path, source5, base) {
|
|
|
13965
14622
|
path,
|
|
13966
14623
|
status: base?.status || "active",
|
|
13967
14624
|
agent_id: base?.agent_id ?? null,
|
|
13968
|
-
note:
|
|
14625
|
+
note: truncate2(base?.note, 240),
|
|
13969
14626
|
updated_at: base?.updated_at || "",
|
|
13970
14627
|
sources: [source5]
|
|
13971
14628
|
});
|
|
@@ -14023,7 +14680,7 @@ function estimateTokens(value) {
|
|
|
14023
14680
|
return Math.max(1, Math.ceil((text || "").length / 4));
|
|
14024
14681
|
}
|
|
14025
14682
|
function summarizeStrings(values, maxChars) {
|
|
14026
|
-
return
|
|
14683
|
+
return truncate2(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
|
|
14027
14684
|
}
|
|
14028
14685
|
function summarizeSection(pack, section, maxChars) {
|
|
14029
14686
|
if (section === "project")
|
|
@@ -14214,8 +14871,8 @@ function createAgentContextPack(input, db) {
|
|
|
14214
14871
|
...taskFiles.map((file) => file.updated_at)
|
|
14215
14872
|
], task2.updated_at);
|
|
14216
14873
|
const warnings = [];
|
|
14217
|
-
const
|
|
14218
|
-
if (Date.parse(task2.updated_at) <
|
|
14874
|
+
const now3 = input.now ? new Date(input.now) : new Date;
|
|
14875
|
+
if (Date.parse(task2.updated_at) < now3.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
|
|
14219
14876
|
warnings.push(`task state is older than ${limit.stale_after_hours} hours`);
|
|
14220
14877
|
}
|
|
14221
14878
|
if (comments.length > recentComments.length)
|
|
@@ -14230,7 +14887,7 @@ function createAgentContextPack(input, db) {
|
|
|
14230
14887
|
id: task2.id,
|
|
14231
14888
|
short_id: task2.short_id,
|
|
14232
14889
|
title: redactEvidenceText(task2.title),
|
|
14233
|
-
description:
|
|
14890
|
+
description: truncate2(task2.description, limit.max_text_chars),
|
|
14234
14891
|
status: task2.status,
|
|
14235
14892
|
priority: task2.priority,
|
|
14236
14893
|
assigned_to: task2.assigned_to,
|
|
@@ -14254,7 +14911,7 @@ function createAgentContextPack(input, db) {
|
|
|
14254
14911
|
plan: plan ? {
|
|
14255
14912
|
id: plan.id,
|
|
14256
14913
|
name: plan.name,
|
|
14257
|
-
description:
|
|
14914
|
+
description: truncate2(plan.description, limit.max_text_chars),
|
|
14258
14915
|
status: plan.status,
|
|
14259
14916
|
agent_id: plan.agent_id,
|
|
14260
14917
|
tasks: planTasks.slice(0, limit.plan_task_limit).map(taskSummary).filter((item) => Boolean(item)),
|
|
@@ -14273,7 +14930,7 @@ function createAgentContextPack(input, db) {
|
|
|
14273
14930
|
type: comment.type,
|
|
14274
14931
|
progress_pct: comment.progress_pct,
|
|
14275
14932
|
created_at: comment.created_at,
|
|
14276
|
-
content:
|
|
14933
|
+
content: truncate2(comment.content, limit.max_text_chars) || ""
|
|
14277
14934
|
})),
|
|
14278
14935
|
omitted: Math.max(0, comments.length - recentComments.length)
|
|
14279
14936
|
},
|
|
@@ -14281,7 +14938,7 @@ function createAgentContextPack(input, db) {
|
|
|
14281
14938
|
traceability: {
|
|
14282
14939
|
commits: traceability.commits.map((commit) => ({
|
|
14283
14940
|
sha: commit.sha,
|
|
14284
|
-
message:
|
|
14941
|
+
message: truncate2(commit.message, 240),
|
|
14285
14942
|
files_changed: commit.files_changed,
|
|
14286
14943
|
committed_at: commit.committed_at
|
|
14287
14944
|
})),
|
|
@@ -14289,7 +14946,7 @@ function createAgentContextPack(input, db) {
|
|
|
14289
14946
|
verifications: verifications.map((verification) => ({
|
|
14290
14947
|
command: verification.command,
|
|
14291
14948
|
status: verification.status,
|
|
14292
|
-
output_summary:
|
|
14949
|
+
output_summary: truncate2(verification.output_summary, limit.max_text_chars),
|
|
14293
14950
|
artifact_path: verification.artifact_path,
|
|
14294
14951
|
run_at: verification.run_at
|
|
14295
14952
|
})),
|
|
@@ -14300,14 +14957,14 @@ function createAgentContextPack(input, db) {
|
|
|
14300
14957
|
id: ledger.run.id,
|
|
14301
14958
|
title: ledger.run.title,
|
|
14302
14959
|
status: ledger.run.status,
|
|
14303
|
-
summary:
|
|
14960
|
+
summary: truncate2(ledger.run.summary, limit.max_text_chars),
|
|
14304
14961
|
agent_id: ledger.run.agent_id,
|
|
14305
14962
|
started_at: ledger.run.started_at,
|
|
14306
14963
|
completed_at: ledger.run.completed_at,
|
|
14307
|
-
events: ledger.events.map((event) => ({ event_type: event.event_type, message:
|
|
14308
|
-
commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary:
|
|
14309
|
-
files: ledger.files.map((file) => ({ path: file.path, status: file.status, note:
|
|
14310
|
-
artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description:
|
|
14964
|
+
events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate2(event.message, 500), created_at: event.created_at })),
|
|
14965
|
+
commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary: truncate2(command.output_summary, limit.max_text_chars), artifact_path: command.artifact_path })),
|
|
14966
|
+
files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate2(file.note, 240) })),
|
|
14967
|
+
artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate2(artifact.description, 240), sha256: artifact.sha256 }))
|
|
14311
14968
|
})),
|
|
14312
14969
|
omitted: Math.max(0, runs.length - selectedRuns.length)
|
|
14313
14970
|
},
|
|
@@ -14412,7 +15069,7 @@ function renderAgentContextPackCompactMarkdown(pack) {
|
|
|
14412
15069
|
const lines = [
|
|
14413
15070
|
`# Context: ${pack.task.title}`,
|
|
14414
15071
|
`${pack.task.status} | ${pack.task.priority} | ${pack.task.short_id || pack.task.id.slice(0, 8)}`,
|
|
14415
|
-
pack.task.description ?
|
|
15072
|
+
pack.task.description ? truncate2(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
|
|
14416
15073
|
"",
|
|
14417
15074
|
"## Must Know",
|
|
14418
15075
|
bullet([
|
|
@@ -14558,7 +15215,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
|
14558
15215
|
];
|
|
14559
15216
|
const written = [];
|
|
14560
15217
|
for (const [name, payload] of files) {
|
|
14561
|
-
const file =
|
|
15218
|
+
const file = join8(directory, name);
|
|
14562
15219
|
writeFileSync5(file, `${JSON.stringify(payload, null, 2)}
|
|
14563
15220
|
`, "utf-8");
|
|
14564
15221
|
written.push(file);
|
|
@@ -15275,7 +15932,7 @@ function createRoadmap(input) {
|
|
|
15275
15932
|
if (!name)
|
|
15276
15933
|
throw new Error("Roadmap name is required");
|
|
15277
15934
|
const store = readStore();
|
|
15278
|
-
const
|
|
15935
|
+
const now3 = timestamp();
|
|
15279
15936
|
const roadmap = {
|
|
15280
15937
|
id: newId("roadmap"),
|
|
15281
15938
|
name,
|
|
@@ -15286,8 +15943,8 @@ function createRoadmap(input) {
|
|
|
15286
15943
|
agent_id: cleanString(input.agent_id),
|
|
15287
15944
|
release: cleanString(input.release),
|
|
15288
15945
|
milestone_ids: [],
|
|
15289
|
-
created_at:
|
|
15290
|
-
updated_at:
|
|
15946
|
+
created_at: now3,
|
|
15947
|
+
updated_at: now3
|
|
15291
15948
|
};
|
|
15292
15949
|
store.roadmaps[roadmap.id] = roadmap;
|
|
15293
15950
|
writeStore(store);
|
|
@@ -15347,7 +16004,7 @@ function createMilestone(input) {
|
|
|
15347
16004
|
const title = input.title.trim();
|
|
15348
16005
|
if (!title)
|
|
15349
16006
|
throw new Error("Milestone title is required");
|
|
15350
|
-
const
|
|
16007
|
+
const now3 = timestamp();
|
|
15351
16008
|
const milestone = {
|
|
15352
16009
|
id: newId("milestone"),
|
|
15353
16010
|
roadmap_id: roadmapId,
|
|
@@ -15362,11 +16019,11 @@ function createMilestone(input) {
|
|
|
15362
16019
|
run_ids: cleanList3(input.run_ids),
|
|
15363
16020
|
release: cleanString(input.release ?? roadmap.release ?? undefined),
|
|
15364
16021
|
tags: cleanList3(input.tags),
|
|
15365
|
-
created_at:
|
|
15366
|
-
updated_at:
|
|
16022
|
+
created_at: now3,
|
|
16023
|
+
updated_at: now3
|
|
15367
16024
|
};
|
|
15368
16025
|
store.milestones[milestone.id] = milestone;
|
|
15369
|
-
store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList3([...roadmap.milestone_ids, milestone.id]), updated_at:
|
|
16026
|
+
store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList3([...roadmap.milestone_ids, milestone.id]), updated_at: now3 };
|
|
15370
16027
|
writeStore(store);
|
|
15371
16028
|
return milestone;
|
|
15372
16029
|
}
|
|
@@ -15426,7 +16083,7 @@ function upsertReleaseGroup(input) {
|
|
|
15426
16083
|
throw new Error("Release group name is required");
|
|
15427
16084
|
const key = releaseKey(roadmapId, name);
|
|
15428
16085
|
const existing = store.releases[key];
|
|
15429
|
-
const
|
|
16086
|
+
const now3 = timestamp();
|
|
15430
16087
|
const release = {
|
|
15431
16088
|
name,
|
|
15432
16089
|
version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
|
|
@@ -15437,8 +16094,8 @@ function upsertReleaseGroup(input) {
|
|
|
15437
16094
|
plan_ids: input.plan_ids === undefined ? existing?.plan_ids ?? [] : cleanList3(input.plan_ids),
|
|
15438
16095
|
run_ids: input.run_ids === undefined ? existing?.run_ids ?? [] : cleanList3(input.run_ids),
|
|
15439
16096
|
notes: input.notes === undefined ? existing?.notes ?? null : cleanString(input.notes),
|
|
15440
|
-
created_at: existing?.created_at ??
|
|
15441
|
-
updated_at:
|
|
16097
|
+
created_at: existing?.created_at ?? now3,
|
|
16098
|
+
updated_at: now3
|
|
15442
16099
|
};
|
|
15443
16100
|
store.releases[key] = release;
|
|
15444
16101
|
writeStore(store);
|
|
@@ -15829,7 +16486,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
|
|
|
15829
16486
|
init_migrations();
|
|
15830
16487
|
init_schema();
|
|
15831
16488
|
import { readFileSync as readFileSync6 } from "fs";
|
|
15832
|
-
import { join as
|
|
16489
|
+
import { join as join9, resolve as resolve8 } from "path";
|
|
15833
16490
|
import { Database as Database2 } from "bun:sqlite";
|
|
15834
16491
|
var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
15835
16492
|
var EXPECTED_PACKAGE_NAME = "@hasna/todos";
|
|
@@ -15875,7 +16532,7 @@ function warn(id, message, details) {
|
|
|
15875
16532
|
return { id, status: "warning", message, details };
|
|
15876
16533
|
}
|
|
15877
16534
|
function readPackageJson(root) {
|
|
15878
|
-
return JSON.parse(readFileSync6(
|
|
16535
|
+
return JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
|
|
15879
16536
|
}
|
|
15880
16537
|
function sortedKeys(value) {
|
|
15881
16538
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -17287,6 +17944,470 @@ function importExternalIssues(input, db) {
|
|
|
17287
17944
|
]
|
|
17288
17945
|
};
|
|
17289
17946
|
}
|
|
17947
|
+
// src/lib/tester-issue-reports.ts
|
|
17948
|
+
init_database();
|
|
17949
|
+
import { createHash as createHash7 } from "crypto";
|
|
17950
|
+
init_redaction();
|
|
17951
|
+
var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1";
|
|
17952
|
+
var TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1";
|
|
17953
|
+
var TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1";
|
|
17954
|
+
var PRIORITIES3 = ["low", "medium", "high", "critical"];
|
|
17955
|
+
var SEVERITIES = new Set(PRIORITIES3);
|
|
17956
|
+
var KINDS = new Set([
|
|
17957
|
+
"assertion_failure",
|
|
17958
|
+
"runtime_error",
|
|
17959
|
+
"console_error",
|
|
17960
|
+
"network_error",
|
|
17961
|
+
"visual_regression",
|
|
17962
|
+
"accessibility",
|
|
17963
|
+
"performance",
|
|
17964
|
+
"broken_link",
|
|
17965
|
+
"security",
|
|
17966
|
+
"unknown"
|
|
17967
|
+
]);
|
|
17968
|
+
function asObject3(value) {
|
|
17969
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
17970
|
+
}
|
|
17971
|
+
function asString3(value) {
|
|
17972
|
+
if (typeof value === "string" && value.trim())
|
|
17973
|
+
return value.trim();
|
|
17974
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
17975
|
+
return String(value);
|
|
17976
|
+
return null;
|
|
17977
|
+
}
|
|
17978
|
+
function stringArray(value, limit = 20) {
|
|
17979
|
+
if (!Array.isArray(value))
|
|
17980
|
+
return [];
|
|
17981
|
+
return [...new Set(value.map((item) => asString3(item)).filter((item) => Boolean(item)))].slice(0, limit);
|
|
17982
|
+
}
|
|
17983
|
+
function objectArray(value, limit = 20) {
|
|
17984
|
+
if (!Array.isArray(value))
|
|
17985
|
+
return [];
|
|
17986
|
+
return value.map(asObject3).filter((item) => Object.keys(item).length > 0).slice(0, limit);
|
|
17987
|
+
}
|
|
17988
|
+
function cleanKey(value) {
|
|
17989
|
+
return value.toLowerCase().trim().replace(/[^a-z0-9._:-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
17990
|
+
}
|
|
17991
|
+
function truncate3(value, max) {
|
|
17992
|
+
if (!value)
|
|
17993
|
+
return;
|
|
17994
|
+
const redacted = redactEvidenceText(value).trim();
|
|
17995
|
+
if (!redacted)
|
|
17996
|
+
return;
|
|
17997
|
+
return redacted.length > max ? `${redacted.slice(0, max - 3)}...` : redacted;
|
|
17998
|
+
}
|
|
17999
|
+
function normalizeText2(value) {
|
|
18000
|
+
return (value || "").toLowerCase().replace(/https?:\/\/\S+/g, " ").replace(/[`"'()[\]{}.,:;!?/#\\_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
18001
|
+
}
|
|
18002
|
+
function normalizeUrlPattern(value) {
|
|
18003
|
+
if (!value)
|
|
18004
|
+
return "";
|
|
18005
|
+
try {
|
|
18006
|
+
const url = new URL(value);
|
|
18007
|
+
return `${url.origin.toLowerCase()}${url.pathname.replace(/\/+$/, "") || "/"}`;
|
|
18008
|
+
} catch {
|
|
18009
|
+
return value.split(/[?#]/, 1)[0].replace(/\/+$/, "").toLowerCase();
|
|
18010
|
+
}
|
|
18011
|
+
}
|
|
18012
|
+
function normalizeKind(value) {
|
|
18013
|
+
const raw = cleanKey(asString3(value) || "unknown").replace(/-/g, "_");
|
|
18014
|
+
return KINDS.has(raw) ? raw : raw || "unknown";
|
|
18015
|
+
}
|
|
18016
|
+
function normalizeSeverity(value, fallback) {
|
|
18017
|
+
const raw = cleanKey(asString3(value) || fallback);
|
|
18018
|
+
if (SEVERITIES.has(raw))
|
|
18019
|
+
return raw;
|
|
18020
|
+
if (/^(p0|blocker|urgent|highest)$/.test(raw))
|
|
18021
|
+
return "critical";
|
|
18022
|
+
if (/^(p1|major)$/.test(raw))
|
|
18023
|
+
return "high";
|
|
18024
|
+
if (/^(p3|minor|info)$/.test(raw))
|
|
18025
|
+
return "low";
|
|
18026
|
+
return fallback;
|
|
18027
|
+
}
|
|
18028
|
+
function reportSource(input) {
|
|
18029
|
+
const source6 = asObject3(input["source"]);
|
|
18030
|
+
const normalized = {
|
|
18031
|
+
tool: asString3(source6["tool"]) || asString3(input["tool"]) || "testers",
|
|
18032
|
+
run_id: asString3(source6["run_id"]) || asString3(input["run_id"]) || undefined,
|
|
18033
|
+
result_id: asString3(source6["result_id"]) || asString3(input["result_id"]) || undefined,
|
|
18034
|
+
scenario_id: asString3(source6["scenario_id"]) || asString3(input["scenario_id"]) || undefined,
|
|
18035
|
+
scenario_name: asString3(source6["scenario_name"]) || asString3(input["scenario_name"]) || undefined,
|
|
18036
|
+
project_id: asString3(source6["project_id"]) || asString3(input["project_id"]) || undefined,
|
|
18037
|
+
url: asString3(source6["url"]) || asString3(input["url"]) || undefined,
|
|
18038
|
+
page_url: asString3(source6["page_url"]) || asString3(input["page_url"]) || undefined,
|
|
18039
|
+
artifact_url: asString3(source6["artifact_url"]) || undefined,
|
|
18040
|
+
screenshot_url: asString3(source6["screenshot_url"]) || undefined,
|
|
18041
|
+
commit: asString3(source6["commit"]) || undefined,
|
|
18042
|
+
branch: asString3(source6["branch"]) || undefined
|
|
18043
|
+
};
|
|
18044
|
+
return Object.values(normalized).some(Boolean) ? normalized : undefined;
|
|
18045
|
+
}
|
|
18046
|
+
function reportTarget(input) {
|
|
18047
|
+
const target = asObject3(input["target"]);
|
|
18048
|
+
const normalized = {
|
|
18049
|
+
url: asString3(target["url"]) || asString3(input["target_url"]) || undefined,
|
|
18050
|
+
route: asString3(target["route"]) || undefined,
|
|
18051
|
+
selector: asString3(target["selector"]) || undefined,
|
|
18052
|
+
component: asString3(target["component"]) || undefined,
|
|
18053
|
+
browser: asString3(target["browser"]) || undefined,
|
|
18054
|
+
viewport: asString3(target["viewport"]) || undefined
|
|
18055
|
+
};
|
|
18056
|
+
return Object.values(normalized).some(Boolean) ? normalized : undefined;
|
|
18057
|
+
}
|
|
18058
|
+
function reportFailure(input) {
|
|
18059
|
+
const failure = asObject3(input["failure"]);
|
|
18060
|
+
const steps = stringArray(failure["steps"] ?? input["steps"], 50);
|
|
18061
|
+
const normalized = {
|
|
18062
|
+
message: truncate3(asString3(failure["message"]) || asString3(input["error"]) || asString3(input["message"]), 1000),
|
|
18063
|
+
expected: truncate3(asString3(failure["expected"]), 1000),
|
|
18064
|
+
actual: truncate3(asString3(failure["actual"]), 1000),
|
|
18065
|
+
stack: truncate3(asString3(failure["stack"]) || asString3(input["stack"]), 3000),
|
|
18066
|
+
reasoning: truncate3(asString3(failure["reasoning"]) || asString3(input["reasoning"]), 1500),
|
|
18067
|
+
steps: steps.length > 0 ? steps.map((step) => truncate3(step, 400)).filter(Boolean) : undefined
|
|
18068
|
+
};
|
|
18069
|
+
return Object.values(normalized).some(Boolean) ? normalized : undefined;
|
|
18070
|
+
}
|
|
18071
|
+
function artifactArray(value) {
|
|
18072
|
+
return objectArray(value, 12).map((item) => ({
|
|
18073
|
+
kind: asString3(item["kind"]) || undefined,
|
|
18074
|
+
label: asString3(item["label"]) || asString3(item["name"]) || undefined,
|
|
18075
|
+
path: asString3(item["path"]) || asString3(item["file_path"]) || undefined,
|
|
18076
|
+
url: asString3(item["url"]) || undefined
|
|
18077
|
+
})).filter((item) => item.kind || item.label || item.path || item.url);
|
|
18078
|
+
}
|
|
18079
|
+
function reportEvidence(input) {
|
|
18080
|
+
const evidence = asObject3(input["evidence"]);
|
|
18081
|
+
const logs = stringArray(evidence["logs"], 8).map((log) => truncate3(log, 1000)).filter(Boolean);
|
|
18082
|
+
const screenshots = artifactArray(evidence["screenshots"] ?? input["screenshots"]);
|
|
18083
|
+
const artifacts = artifactArray(evidence["artifacts"] ?? input["artifacts"]);
|
|
18084
|
+
const normalized = {
|
|
18085
|
+
logs: logs.length > 0 ? logs : undefined,
|
|
18086
|
+
screenshots: screenshots.length > 0 ? screenshots : undefined,
|
|
18087
|
+
artifacts: artifacts.length > 0 ? artifacts : undefined
|
|
18088
|
+
};
|
|
18089
|
+
return Object.values(normalized).some(Boolean) ? normalized : undefined;
|
|
18090
|
+
}
|
|
18091
|
+
function normalizeTesterIssueReport(value, fallbackPriority = "medium") {
|
|
18092
|
+
const input = asObject3(value);
|
|
18093
|
+
if (input["schema_version"] !== TESTERS_ISSUE_REPORT_SCHEMA_VERSION) {
|
|
18094
|
+
throw new Error(`Expected schema_version ${TESTERS_ISSUE_REPORT_SCHEMA_VERSION}`);
|
|
18095
|
+
}
|
|
18096
|
+
const failure = reportFailure(input);
|
|
18097
|
+
const source6 = reportSource(input);
|
|
18098
|
+
const target = reportTarget(input);
|
|
18099
|
+
const title = truncate3(asString3(input["title"]) || asString3(input["summary"]) || failure?.message || source6?.scenario_name || "Tester issue report", 220);
|
|
18100
|
+
if (!title)
|
|
18101
|
+
throw new Error("Tester issue report requires a title");
|
|
18102
|
+
const labels = [
|
|
18103
|
+
...stringArray(input["labels"], 20),
|
|
18104
|
+
...stringArray(input["tags"], 20)
|
|
18105
|
+
].map(cleanKey).filter(Boolean);
|
|
18106
|
+
const report = {
|
|
18107
|
+
schema_version: TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
|
|
18108
|
+
id: asString3(input["id"]) || undefined,
|
|
18109
|
+
fingerprint: asString3(input["fingerprint"]) || undefined,
|
|
18110
|
+
title,
|
|
18111
|
+
summary: truncate3(asString3(input["summary"]), 1000) ?? null,
|
|
18112
|
+
kind: normalizeKind(input["kind"] ?? input["type"]),
|
|
18113
|
+
severity: normalizeSeverity(input["severity"] ?? input["priority"], fallbackPriority),
|
|
18114
|
+
source: source6,
|
|
18115
|
+
target,
|
|
18116
|
+
failure,
|
|
18117
|
+
evidence: reportEvidence(input),
|
|
18118
|
+
labels: labels.length > 0 ? [...new Set(labels)].slice(0, 20) : undefined,
|
|
18119
|
+
metadata: redactValue(asObject3(input["metadata"])),
|
|
18120
|
+
occurred_at: asString3(input["occurred_at"]) || asString3(input["timestamp"]) || undefined
|
|
18121
|
+
};
|
|
18122
|
+
return redactValue(report);
|
|
18123
|
+
}
|
|
18124
|
+
function fingerprintTesterIssueReport(report) {
|
|
18125
|
+
if (report.fingerprint)
|
|
18126
|
+
return `testers:${cleanKey(report.fingerprint)}`;
|
|
18127
|
+
const stackTop = report.failure?.stack?.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "";
|
|
18128
|
+
const url = normalizeUrlPattern(report.target?.url || report.source?.page_url || report.source?.url || "");
|
|
18129
|
+
const raw = [
|
|
18130
|
+
report.kind || "unknown",
|
|
18131
|
+
report.source?.project_id || "",
|
|
18132
|
+
report.source?.scenario_id || report.source?.scenario_name || "",
|
|
18133
|
+
url,
|
|
18134
|
+
report.target?.route || "",
|
|
18135
|
+
report.target?.selector || report.target?.component || "",
|
|
18136
|
+
normalizeText2(report.failure?.message || report.summary || report.title).slice(0, 240),
|
|
18137
|
+
normalizeText2(stackTop).slice(0, 160)
|
|
18138
|
+
].join("::");
|
|
18139
|
+
return `testers:${createHash7("sha256").update(raw).digest("hex").slice(0, 16)}`;
|
|
18140
|
+
}
|
|
18141
|
+
function priorityForSeverity(severity, fallback) {
|
|
18142
|
+
return PRIORITIES3.includes(severity) ? severity : fallback;
|
|
18143
|
+
}
|
|
18144
|
+
function maxPriority(left, right) {
|
|
18145
|
+
return PRIORITIES3.indexOf(right) > PRIORITIES3.indexOf(left) ? right : left;
|
|
18146
|
+
}
|
|
18147
|
+
function taskTitle2(report) {
|
|
18148
|
+
const title = report.title.replace(/^BUG:\s*/i, "").replace(/^\[testers\]\s*/i, "");
|
|
18149
|
+
return `BUG: [testers] ${title}`.slice(0, 240);
|
|
18150
|
+
}
|
|
18151
|
+
function evidenceLines(report) {
|
|
18152
|
+
const lines = [];
|
|
18153
|
+
for (const item of report.evidence?.screenshots || []) {
|
|
18154
|
+
lines.push(`Screenshot: ${item.label || item.kind || item.path || item.url}${item.path ? ` (${item.path})` : item.url ? ` (${item.url})` : ""}`);
|
|
18155
|
+
}
|
|
18156
|
+
for (const item of report.evidence?.artifacts || []) {
|
|
18157
|
+
lines.push(`Artifact: ${item.label || item.kind || item.path || item.url}${item.path ? ` (${item.path})` : item.url ? ` (${item.url})` : ""}`);
|
|
18158
|
+
}
|
|
18159
|
+
for (const log of report.evidence?.logs || [])
|
|
18160
|
+
lines.push(`Log: ${log}`);
|
|
18161
|
+
return lines.slice(0, 12);
|
|
18162
|
+
}
|
|
18163
|
+
function taskDescription2(report, fingerprint2) {
|
|
18164
|
+
const failure = report.failure;
|
|
18165
|
+
const lines = [
|
|
18166
|
+
"Tester issue report.",
|
|
18167
|
+
"",
|
|
18168
|
+
`Schema: ${report.schema_version}`,
|
|
18169
|
+
`Fingerprint: ${fingerprint2}`,
|
|
18170
|
+
`Kind: ${report.kind || "unknown"}`,
|
|
18171
|
+
`Severity: ${report.severity || "medium"}`,
|
|
18172
|
+
report.source?.run_id ? `Run: ${report.source.run_id}` : null,
|
|
18173
|
+
report.source?.result_id ? `Result: ${report.source.result_id}` : null,
|
|
18174
|
+
report.source?.scenario_name || report.source?.scenario_id ? `Scenario: ${report.source.scenario_name || report.source.scenario_id}` : null,
|
|
18175
|
+
report.target?.url || report.source?.page_url || report.source?.url ? `URL: ${report.target?.url || report.source?.page_url || report.source?.url}` : null,
|
|
18176
|
+
report.target?.route ? `Route: ${report.target.route}` : null,
|
|
18177
|
+
report.target?.selector ? `Selector: ${report.target.selector}` : null,
|
|
18178
|
+
report.occurred_at ? `Occurred at: ${report.occurred_at}` : null,
|
|
18179
|
+
"",
|
|
18180
|
+
report.summary ? `Summary:
|
|
18181
|
+
${report.summary}` : null,
|
|
18182
|
+
failure?.message ? `Failure:
|
|
18183
|
+
${failure.message}` : null,
|
|
18184
|
+
failure?.expected ? `Expected:
|
|
18185
|
+
${failure.expected}` : null,
|
|
18186
|
+
failure?.actual ? `Actual:
|
|
18187
|
+
${failure.actual}` : null,
|
|
18188
|
+
failure?.reasoning ? `Reasoning:
|
|
18189
|
+
${failure.reasoning}` : null,
|
|
18190
|
+
failure?.steps?.length ? `Steps:
|
|
18191
|
+
${failure.steps.map((step, index) => `${index + 1}. ${step}`).join(`
|
|
18192
|
+
`)}` : null,
|
|
18193
|
+
evidenceLines(report).length ? `Evidence:
|
|
18194
|
+
${evidenceLines(report).map((line) => `- ${line}`).join(`
|
|
18195
|
+
`)}` : null,
|
|
18196
|
+
failure?.stack ? `Stack:
|
|
18197
|
+
${failure.stack}` : null
|
|
18198
|
+
].filter((line) => line !== null);
|
|
18199
|
+
return lines.join(`
|
|
18200
|
+
`).replace(/\n{3,}/g, `
|
|
18201
|
+
|
|
18202
|
+
`).slice(0, 6000);
|
|
18203
|
+
}
|
|
18204
|
+
function taskTags(report) {
|
|
18205
|
+
return [...new Set([
|
|
18206
|
+
"bug",
|
|
18207
|
+
"testers",
|
|
18208
|
+
"tester-report",
|
|
18209
|
+
report.kind ? cleanKey(String(report.kind)).replace(/_/g, "-") : "unknown",
|
|
18210
|
+
...report.labels || []
|
|
18211
|
+
].filter(Boolean))].slice(0, 16);
|
|
18212
|
+
}
|
|
18213
|
+
function storedReportSummary(report) {
|
|
18214
|
+
return {
|
|
18215
|
+
id: report.id ?? null,
|
|
18216
|
+
title: report.title,
|
|
18217
|
+
kind: report.kind ?? "unknown",
|
|
18218
|
+
severity: report.severity ?? "medium",
|
|
18219
|
+
run_id: report.source?.run_id ?? null,
|
|
18220
|
+
result_id: report.source?.result_id ?? null,
|
|
18221
|
+
scenario_id: report.source?.scenario_id ?? null,
|
|
18222
|
+
scenario_name: report.source?.scenario_name ?? null,
|
|
18223
|
+
url: report.target?.url ?? report.source?.page_url ?? report.source?.url ?? null,
|
|
18224
|
+
occurred_at: report.occurred_at ?? null
|
|
18225
|
+
};
|
|
18226
|
+
}
|
|
18227
|
+
function testerMetadata(report, fingerprint2, previous, timestamp2) {
|
|
18228
|
+
const occurrenceCount = typeof previous?.["occurrence_count"] === "number" ? previous["occurrence_count"] + 1 : 1;
|
|
18229
|
+
const previousRecent = previous?.["recent_reports"];
|
|
18230
|
+
const recent = Array.isArray(previousRecent) ? previousRecent : [];
|
|
18231
|
+
return {
|
|
18232
|
+
schema_version: TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
|
|
18233
|
+
fingerprint: fingerprint2,
|
|
18234
|
+
first_seen_at: asString3(previous?.["first_seen_at"]) || timestamp2,
|
|
18235
|
+
last_seen_at: timestamp2,
|
|
18236
|
+
occurrence_count: occurrenceCount,
|
|
18237
|
+
latest_report: storedReportSummary(report),
|
|
18238
|
+
recent_reports: [...recent.slice(-4), storedReportSummary(report)]
|
|
18239
|
+
};
|
|
18240
|
+
}
|
|
18241
|
+
function sourceMetadata(report) {
|
|
18242
|
+
const url = report.target?.url || report.source?.page_url || report.source?.url || null;
|
|
18243
|
+
return {
|
|
18244
|
+
...url ? { source_url: url, external_url: url, issue_url: url } : {},
|
|
18245
|
+
...report.source?.run_id ? { tester_run_id: report.source.run_id } : {},
|
|
18246
|
+
...report.source?.result_id ? { tester_result_id: report.source.result_id } : {},
|
|
18247
|
+
...report.source?.scenario_id ? { tester_scenario_id: report.source.scenario_id } : {},
|
|
18248
|
+
...report.source?.project_id ? { tester_project_id: report.source.project_id } : {}
|
|
18249
|
+
};
|
|
18250
|
+
}
|
|
18251
|
+
function findExistingTask2(fingerprint2, input, db) {
|
|
18252
|
+
for (const task2 of listTasks({
|
|
18253
|
+
include_archived: true,
|
|
18254
|
+
project_id: input.project_id,
|
|
18255
|
+
task_list_id: input.task_list_id
|
|
18256
|
+
}, db)) {
|
|
18257
|
+
const metadata = task2.metadata || {};
|
|
18258
|
+
const tester = asObject3(metadata["tester_issue_report"]);
|
|
18259
|
+
if (tester["fingerprint"] === fingerprint2)
|
|
18260
|
+
return task2;
|
|
18261
|
+
if (metadata["tester_issue_fingerprint"] === fingerprint2)
|
|
18262
|
+
return task2;
|
|
18263
|
+
if (metadata["external_ref"] === fingerprint2)
|
|
18264
|
+
return task2;
|
|
18265
|
+
}
|
|
18266
|
+
return null;
|
|
18267
|
+
}
|
|
18268
|
+
function commandsFor(task2) {
|
|
18269
|
+
return [
|
|
18270
|
+
"todos issues report --file tester-report.json --apply --json",
|
|
18271
|
+
task2 ? `todos show ${task2.id.slice(0, 8)}` : `todos list --tags tester-report --json`,
|
|
18272
|
+
`todos dedupe scan --threshold 0.8 --json`
|
|
18273
|
+
];
|
|
18274
|
+
}
|
|
18275
|
+
function updateExistingTask(task2, report, fingerprint2, input, timestamp2, db) {
|
|
18276
|
+
if (input.update_existing === false)
|
|
18277
|
+
return { action: "matched", task: task2 };
|
|
18278
|
+
const previous = asObject3(task2.metadata["tester_issue_report"]);
|
|
18279
|
+
const severityPriority = priorityForSeverity(report.severity, input.default_priority || "medium");
|
|
18280
|
+
const nextStatus = task2.status === "completed" || task2.status === "cancelled" ? "pending" : task2.status;
|
|
18281
|
+
const action = nextStatus !== task2.status ? "regressed" : "updated";
|
|
18282
|
+
const updated = updateTask(task2.id, {
|
|
18283
|
+
version: task2.version,
|
|
18284
|
+
title: taskTitle2(report),
|
|
18285
|
+
description: taskDescription2(report, fingerprint2),
|
|
18286
|
+
priority: maxPriority(task2.priority, severityPriority),
|
|
18287
|
+
status: nextStatus,
|
|
18288
|
+
completed_at: nextStatus !== task2.status ? null : undefined,
|
|
18289
|
+
tags: [...new Set([...task2.tags, ...taskTags(report)])],
|
|
18290
|
+
metadata: {
|
|
18291
|
+
...task2.metadata,
|
|
18292
|
+
...sourceMetadata(report),
|
|
18293
|
+
external_ref: fingerprint2,
|
|
18294
|
+
tester_issue_fingerprint: fingerprint2,
|
|
18295
|
+
tester_issue_report: testerMetadata(report, fingerprint2, previous, timestamp2)
|
|
18296
|
+
},
|
|
18297
|
+
...input.assigned_to !== undefined ? { assigned_to: input.assigned_to } : {},
|
|
18298
|
+
task_type: task2.task_type || "bug"
|
|
18299
|
+
}, db);
|
|
18300
|
+
return { action, task: updated };
|
|
18301
|
+
}
|
|
18302
|
+
function upsertTesterIssueReport(input, db) {
|
|
18303
|
+
const d = db || getDatabase();
|
|
18304
|
+
const timestamp2 = now();
|
|
18305
|
+
const warnings = [];
|
|
18306
|
+
const report = normalizeTesterIssueReport(input.report, input.default_priority || "medium");
|
|
18307
|
+
const fingerprint2 = fingerprintTesterIssueReport(report);
|
|
18308
|
+
const existing = findExistingTask2(fingerprint2, input, d);
|
|
18309
|
+
if (!input.apply) {
|
|
18310
|
+
const action = existing ? "matched" : "preview";
|
|
18311
|
+
return {
|
|
18312
|
+
schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
18313
|
+
local_only: true,
|
|
18314
|
+
dry_run: true,
|
|
18315
|
+
processed_at: timestamp2,
|
|
18316
|
+
action,
|
|
18317
|
+
fingerprint: fingerprint2,
|
|
18318
|
+
report,
|
|
18319
|
+
task: existing,
|
|
18320
|
+
warnings,
|
|
18321
|
+
commands: commandsFor(existing)
|
|
18322
|
+
};
|
|
18323
|
+
}
|
|
18324
|
+
if (existing) {
|
|
18325
|
+
const updated = updateExistingTask(existing, report, fingerprint2, input, timestamp2, d);
|
|
18326
|
+
return {
|
|
18327
|
+
schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
18328
|
+
local_only: true,
|
|
18329
|
+
dry_run: false,
|
|
18330
|
+
processed_at: timestamp2,
|
|
18331
|
+
action: updated.action,
|
|
18332
|
+
fingerprint: fingerprint2,
|
|
18333
|
+
report,
|
|
18334
|
+
task: updated.task,
|
|
18335
|
+
warnings,
|
|
18336
|
+
commands: commandsFor(updated.task)
|
|
18337
|
+
};
|
|
18338
|
+
}
|
|
18339
|
+
const priority = priorityForSeverity(report.severity, input.default_priority || "medium");
|
|
18340
|
+
const task2 = createTask({
|
|
18341
|
+
title: taskTitle2(report),
|
|
18342
|
+
description: taskDescription2(report, fingerprint2),
|
|
18343
|
+
priority,
|
|
18344
|
+
status: "pending",
|
|
18345
|
+
tags: taskTags(report),
|
|
18346
|
+
metadata: {
|
|
18347
|
+
...sourceMetadata(report),
|
|
18348
|
+
external_ref: fingerprint2,
|
|
18349
|
+
tester_issue_fingerprint: fingerprint2,
|
|
18350
|
+
tester_issue_report: testerMetadata(report, fingerprint2, null, timestamp2),
|
|
18351
|
+
tester_issue_report_raw: redactValue({
|
|
18352
|
+
...report,
|
|
18353
|
+
evidence: report.evidence ? {
|
|
18354
|
+
screenshots: report.evidence.screenshots,
|
|
18355
|
+
artifacts: report.evidence.artifacts
|
|
18356
|
+
} : undefined
|
|
18357
|
+
})
|
|
18358
|
+
},
|
|
18359
|
+
project_id: input.project_id,
|
|
18360
|
+
task_list_id: input.task_list_id,
|
|
18361
|
+
agent_id: input.agent_id,
|
|
18362
|
+
assigned_to: input.assigned_to,
|
|
18363
|
+
task_type: "bug"
|
|
18364
|
+
}, d);
|
|
18365
|
+
return {
|
|
18366
|
+
schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
18367
|
+
local_only: true,
|
|
18368
|
+
dry_run: false,
|
|
18369
|
+
processed_at: timestamp2,
|
|
18370
|
+
action: "created",
|
|
18371
|
+
fingerprint: fingerprint2,
|
|
18372
|
+
report,
|
|
18373
|
+
task: task2,
|
|
18374
|
+
warnings,
|
|
18375
|
+
commands: commandsFor(task2)
|
|
18376
|
+
};
|
|
18377
|
+
}
|
|
18378
|
+
function upsertTesterIssueReports(input, db) {
|
|
18379
|
+
const d = db || getDatabase();
|
|
18380
|
+
const run = () => input.reports.map((report) => upsertTesterIssueReport({ ...input, report }, d));
|
|
18381
|
+
const results = input.apply ? d.transaction(run)() : run();
|
|
18382
|
+
const summary = {
|
|
18383
|
+
total: results.length,
|
|
18384
|
+
preview: 0,
|
|
18385
|
+
matched: 0,
|
|
18386
|
+
created: 0,
|
|
18387
|
+
updated: 0,
|
|
18388
|
+
regressed: 0
|
|
18389
|
+
};
|
|
18390
|
+
for (const result of results)
|
|
18391
|
+
summary[result.action]++;
|
|
18392
|
+
return {
|
|
18393
|
+
schema_version: TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION,
|
|
18394
|
+
local_only: true,
|
|
18395
|
+
dry_run: !input.apply,
|
|
18396
|
+
processed_at: now(),
|
|
18397
|
+
results,
|
|
18398
|
+
summary
|
|
18399
|
+
};
|
|
18400
|
+
}
|
|
18401
|
+
function readTesterIssueReportsPayload(value) {
|
|
18402
|
+
if (Array.isArray(value))
|
|
18403
|
+
return value;
|
|
18404
|
+
const record = asObject3(value);
|
|
18405
|
+
if (Array.isArray(record["reports"]))
|
|
18406
|
+
return record["reports"];
|
|
18407
|
+
if (Array.isArray(record["issues"]))
|
|
18408
|
+
return record["issues"];
|
|
18409
|
+
return [value];
|
|
18410
|
+
}
|
|
17290
18411
|
// src/lib/local-notifications.ts
|
|
17291
18412
|
init_database();
|
|
17292
18413
|
|
|
@@ -17389,13 +18510,13 @@ function eventSeverity(eventType) {
|
|
|
17389
18510
|
function payloadText(payload) {
|
|
17390
18511
|
return JSON.stringify(payload).toLowerCase();
|
|
17391
18512
|
}
|
|
17392
|
-
function
|
|
18513
|
+
function asString4(value) {
|
|
17393
18514
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
17394
18515
|
}
|
|
17395
18516
|
function fieldMatches(allowed, value) {
|
|
17396
18517
|
if (!allowed || allowed.length === 0)
|
|
17397
18518
|
return true;
|
|
17398
|
-
const stringValue =
|
|
18519
|
+
const stringValue = asString4(value);
|
|
17399
18520
|
return Boolean(stringValue && allowed.includes(stringValue));
|
|
17400
18521
|
}
|
|
17401
18522
|
function containsMatches(needles, payload) {
|
|
@@ -17461,8 +18582,8 @@ function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules
|
|
|
17461
18582
|
if (isQuietTime(rule.quiet_hours, timestamp2))
|
|
17462
18583
|
skipped.push("quiet hours active");
|
|
17463
18584
|
const matched = skipped.length === 0;
|
|
17464
|
-
const title =
|
|
17465
|
-
const taskId =
|
|
18585
|
+
const title = asString4(payload["title"]) || asString4(payload["name"]) || input.type;
|
|
18586
|
+
const taskId = asString4(payload["id"]) || asString4(payload["task_id"]);
|
|
17466
18587
|
const notification = {
|
|
17467
18588
|
rule: rule.name,
|
|
17468
18589
|
event_type: input.type,
|
|
@@ -17471,8 +18592,8 @@ function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules
|
|
|
17471
18592
|
message: `${input.type}: ${title}`,
|
|
17472
18593
|
timestamp: timestamp2,
|
|
17473
18594
|
task_id: taskId,
|
|
17474
|
-
project_id:
|
|
17475
|
-
agent_id:
|
|
18595
|
+
project_id: asString4(payload["project_id"]),
|
|
18596
|
+
agent_id: asString4(payload["agent_id"]) || asString4(payload["assigned_to"]),
|
|
17476
18597
|
bell: rule.bell && severity === "critical",
|
|
17477
18598
|
payload
|
|
17478
18599
|
};
|
|
@@ -18122,12 +19243,12 @@ function summarizeTask(task2) {
|
|
|
18122
19243
|
};
|
|
18123
19244
|
}
|
|
18124
19245
|
function overdueTasks(tasks, nowIso) {
|
|
18125
|
-
const
|
|
19246
|
+
const now3 = Date.parse(nowIso);
|
|
18126
19247
|
return tasks.filter((task2) => {
|
|
18127
19248
|
if (isTerminal(task2) || !task2.due_at)
|
|
18128
19249
|
return false;
|
|
18129
19250
|
const due = Date.parse(task2.due_at);
|
|
18130
|
-
return Number.isFinite(due) && due <
|
|
19251
|
+
return Number.isFinite(due) && due < now3;
|
|
18131
19252
|
});
|
|
18132
19253
|
}
|
|
18133
19254
|
function isReady(task2, db) {
|
|
@@ -18460,7 +19581,7 @@ function renderLocalReportMarkdown(report) {
|
|
|
18460
19581
|
// src/lib/local-encryption.ts
|
|
18461
19582
|
init_config();
|
|
18462
19583
|
init_redaction();
|
|
18463
|
-
import { createCipheriv, createDecipheriv, createHash as
|
|
19584
|
+
import { createCipheriv, createDecipheriv, createHash as createHash8, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
18464
19585
|
var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value";
|
|
18465
19586
|
var TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge";
|
|
18466
19587
|
var TODOS_ENCRYPTION_SCHEMA_VERSION = 1;
|
|
@@ -18482,11 +19603,11 @@ class EncryptedPayloadError extends Error {
|
|
|
18482
19603
|
super(message);
|
|
18483
19604
|
}
|
|
18484
19605
|
}
|
|
18485
|
-
function
|
|
19606
|
+
function now3() {
|
|
18486
19607
|
return new Date().toISOString();
|
|
18487
19608
|
}
|
|
18488
19609
|
function sha2564(value) {
|
|
18489
|
-
return
|
|
19610
|
+
return createHash8("sha256").update(value).digest("hex");
|
|
18490
19611
|
}
|
|
18491
19612
|
function normalizeProfileName(value) {
|
|
18492
19613
|
const name = (value || DEFAULT_ENCRYPTION_PROFILE).trim();
|
|
@@ -18519,7 +19640,7 @@ function upsertEncryptionProfile(input) {
|
|
|
18519
19640
|
const name = normalizeProfileName(input.name);
|
|
18520
19641
|
const config = loadConfig();
|
|
18521
19642
|
const existing = config.encryption_profiles?.[name];
|
|
18522
|
-
const timestamp2 =
|
|
19643
|
+
const timestamp2 = now3();
|
|
18523
19644
|
const profile = {
|
|
18524
19645
|
name,
|
|
18525
19646
|
algorithm: "aes-256-gcm",
|
|
@@ -18576,7 +19697,7 @@ function encryptString(plaintext, options = {}) {
|
|
|
18576
19697
|
return {
|
|
18577
19698
|
schemaVersion: TODOS_ENCRYPTION_SCHEMA_VERSION,
|
|
18578
19699
|
kind: TODOS_ENCRYPTED_VALUE_KIND,
|
|
18579
|
-
encryptedAt: options.encryptedAt ??
|
|
19700
|
+
encryptedAt: options.encryptedAt ?? now3(),
|
|
18580
19701
|
profile: profile.name,
|
|
18581
19702
|
key_env: profile.key_env,
|
|
18582
19703
|
algorithm: "aes-256-gcm",
|
|
@@ -18611,7 +19732,7 @@ function decryptString(envelope, env = process.env) {
|
|
|
18611
19732
|
]).toString("utf8");
|
|
18612
19733
|
const expected = Buffer.from(envelope.plaintext_sha256, "hex");
|
|
18613
19734
|
const actual = Buffer.from(sha2564(plaintext), "hex");
|
|
18614
|
-
if (expected.length !== actual.length || !
|
|
19735
|
+
if (expected.length !== actual.length || !timingSafeEqual2(expected, actual)) {
|
|
18615
19736
|
throw new EncryptedPayloadError("decrypted payload checksum mismatch");
|
|
18616
19737
|
}
|
|
18617
19738
|
return plaintext;
|
|
@@ -20702,7 +21823,7 @@ function createHybridTodosStorageAdapter(options) {
|
|
|
20702
21823
|
}
|
|
20703
21824
|
|
|
20704
21825
|
// src/storage/postgres-adapter.ts
|
|
20705
|
-
import { randomUUID as
|
|
21826
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
20706
21827
|
function createPostgresTodosStorageAdapter(options) {
|
|
20707
21828
|
const store = new PostgresJsonRecordStore(options);
|
|
20708
21829
|
const adapter = {
|
|
@@ -20885,7 +22006,7 @@ async function createTask2(input, store, context) {
|
|
|
20885
22006
|
const timestamp2 = new Date().toISOString();
|
|
20886
22007
|
const shortId = input.project_id ? await nextTaskShortId2(input.project_id, store, context) : null;
|
|
20887
22008
|
const task2 = {
|
|
20888
|
-
id:
|
|
22009
|
+
id: randomUUID3(),
|
|
20889
22010
|
short_id: shortId,
|
|
20890
22011
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
20891
22012
|
parent_id: input.parent_id ?? null,
|
|
@@ -21052,7 +22173,7 @@ async function getChangedSince(since, filters, store) {
|
|
|
21052
22173
|
async function createProject2(input, store, context) {
|
|
21053
22174
|
const timestamp2 = new Date().toISOString();
|
|
21054
22175
|
const project = {
|
|
21055
|
-
id:
|
|
22176
|
+
id: randomUUID3(),
|
|
21056
22177
|
name: input.name,
|
|
21057
22178
|
path: input.path,
|
|
21058
22179
|
description: input.description ?? null,
|
|
@@ -21072,7 +22193,7 @@ async function updateProject2(id, input, store) {
|
|
|
21072
22193
|
async function createPlan2(input, store, context) {
|
|
21073
22194
|
const timestamp2 = new Date().toISOString();
|
|
21074
22195
|
return store.upsert("plans", {
|
|
21075
|
-
id:
|
|
22196
|
+
id: randomUUID3(),
|
|
21076
22197
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
21077
22198
|
task_list_id: input.task_list_id ?? context?.taskListId ?? null,
|
|
21078
22199
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
@@ -21094,7 +22215,7 @@ async function registerAgent2(input, store, context) {
|
|
|
21094
22215
|
}
|
|
21095
22216
|
const timestamp2 = new Date().toISOString();
|
|
21096
22217
|
const agent = {
|
|
21097
|
-
id: existing?.id ??
|
|
22218
|
+
id: existing?.id ?? randomUUID3().slice(0, 8),
|
|
21098
22219
|
name: input.name,
|
|
21099
22220
|
description: input.description ?? existing?.description ?? null,
|
|
21100
22221
|
role: input.role ?? existing?.role ?? null,
|
|
@@ -21130,7 +22251,7 @@ async function updateAgent2(id, input, store) {
|
|
|
21130
22251
|
async function createTaskList2(input, store, context) {
|
|
21131
22252
|
const timestamp2 = new Date().toISOString();
|
|
21132
22253
|
return store.upsert("task_lists", {
|
|
21133
|
-
id:
|
|
22254
|
+
id: randomUUID3(),
|
|
21134
22255
|
project_id: input.project_id ?? context?.projectId ?? null,
|
|
21135
22256
|
slug: input.slug ?? slugify2(input.name),
|
|
21136
22257
|
name: input.name,
|
|
@@ -21152,7 +22273,7 @@ async function updateTaskList2(id, input, store) {
|
|
|
21152
22273
|
async function createTemplate2(input, store, context) {
|
|
21153
22274
|
const timestamp2 = new Date().toISOString();
|
|
21154
22275
|
return store.upsert("templates", {
|
|
21155
|
-
id:
|
|
22276
|
+
id: randomUUID3(),
|
|
21156
22277
|
name: input.name,
|
|
21157
22278
|
title_pattern: input.title_pattern,
|
|
21158
22279
|
description: input.description ?? null,
|
|
@@ -21181,7 +22302,7 @@ async function updateTemplate2(id, input, store) {
|
|
|
21181
22302
|
}
|
|
21182
22303
|
async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentId, store, context) {
|
|
21183
22304
|
const entry2 = {
|
|
21184
|
-
id:
|
|
22305
|
+
id: randomUUID3(),
|
|
21185
22306
|
task_id: taskId,
|
|
21186
22307
|
action,
|
|
21187
22308
|
field: field2 ?? null,
|
|
@@ -21194,7 +22315,7 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
|
|
|
21194
22315
|
}
|
|
21195
22316
|
async function addComment2(input, store, context) {
|
|
21196
22317
|
const comment = {
|
|
21197
|
-
id:
|
|
22318
|
+
id: randomUUID3(),
|
|
21198
22319
|
task_id: input.task_id,
|
|
21199
22320
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
21200
22321
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
@@ -21369,10 +22490,10 @@ function assertRemoteAdapterCapabilities(adapter, mode) {
|
|
|
21369
22490
|
}
|
|
21370
22491
|
}
|
|
21371
22492
|
// src/storage/s3-artifacts.ts
|
|
21372
|
-
import { createHash as
|
|
22493
|
+
import { createHash as createHash9, createHmac as createHmac2 } from "crypto";
|
|
21373
22494
|
function createTodosS3ArtifactStore(options) {
|
|
21374
22495
|
const requestFetch = options.fetch ?? fetch;
|
|
21375
|
-
const
|
|
22496
|
+
const now4 = options.now ?? (() => new Date);
|
|
21376
22497
|
return {
|
|
21377
22498
|
objectKey: (relativePath) => buildS3ObjectKey(options.config, relativePath),
|
|
21378
22499
|
objectUrl: (relativePath) => buildS3ObjectUrl(options.config, buildS3ObjectKey(options.config, relativePath)),
|
|
@@ -21394,7 +22515,7 @@ function createTodosS3ArtifactStore(options) {
|
|
|
21394
22515
|
headers,
|
|
21395
22516
|
body,
|
|
21396
22517
|
credentials: options.credentials,
|
|
21397
|
-
now:
|
|
22518
|
+
now: now4()
|
|
21398
22519
|
});
|
|
21399
22520
|
const response = await requestFetch(url, { method: "PUT", headers: signed.headers, body });
|
|
21400
22521
|
if (!response.ok)
|
|
@@ -21415,7 +22536,7 @@ function createTodosS3ArtifactStore(options) {
|
|
|
21415
22536
|
service: "s3",
|
|
21416
22537
|
headers: {},
|
|
21417
22538
|
credentials: options.credentials,
|
|
21418
|
-
now:
|
|
22539
|
+
now: now4()
|
|
21419
22540
|
});
|
|
21420
22541
|
const response = await requestFetch(url, { method: "GET", headers: signed.headers });
|
|
21421
22542
|
if (!response.ok)
|
|
@@ -21431,7 +22552,7 @@ function createTodosS3ArtifactStore(options) {
|
|
|
21431
22552
|
service: "s3",
|
|
21432
22553
|
headers: {},
|
|
21433
22554
|
credentials: options.credentials,
|
|
21434
|
-
now:
|
|
22555
|
+
now: now4()
|
|
21435
22556
|
});
|
|
21436
22557
|
const response = await requestFetch(url, { method: "DELETE", headers: signed.headers });
|
|
21437
22558
|
if (!response.ok && response.status !== 404)
|
|
@@ -21541,13 +22662,13 @@ function toAmzDate(date) {
|
|
|
21541
22662
|
return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
21542
22663
|
}
|
|
21543
22664
|
function sha256Hex(value) {
|
|
21544
|
-
return
|
|
22665
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
21545
22666
|
}
|
|
21546
22667
|
function hmac(key, value) {
|
|
21547
|
-
return
|
|
22668
|
+
return createHmac2("sha256", key).update(value).digest();
|
|
21548
22669
|
}
|
|
21549
22670
|
function hmacHex(key, value) {
|
|
21550
|
-
return
|
|
22671
|
+
return createHmac2("sha256", key).update(value).digest("hex");
|
|
21551
22672
|
}
|
|
21552
22673
|
function getSigningKey(secretAccessKey, dateStamp, region, service) {
|
|
21553
22674
|
const dateKey = hmac(`AWS4${secretAccessKey}`, dateStamp);
|
|
@@ -21559,7 +22680,7 @@ function getSigningKey(secretAccessKey, dateStamp, region, service) {
|
|
|
21559
22680
|
init_database();
|
|
21560
22681
|
async function uploadRunArtifactsToS3(options) {
|
|
21561
22682
|
const db = options.db ?? getDatabase();
|
|
21562
|
-
const
|
|
22683
|
+
const now4 = options.now ?? (() => new Date);
|
|
21563
22684
|
const result = emptyResult();
|
|
21564
22685
|
for (const artifact of listRunArtifacts(db, options.filter)) {
|
|
21565
22686
|
try {
|
|
@@ -21598,7 +22719,7 @@ async function uploadRunArtifactsToS3(options) {
|
|
|
21598
22719
|
url: ref.url,
|
|
21599
22720
|
sha256: content.sha256,
|
|
21600
22721
|
size_bytes: content.size_bytes,
|
|
21601
|
-
uploaded_at:
|
|
22722
|
+
uploaded_at: now4().toISOString()
|
|
21602
22723
|
};
|
|
21603
22724
|
updateArtifactMetadata(db, artifact.id, {
|
|
21604
22725
|
...metadata,
|
|
@@ -21673,7 +22794,7 @@ function planRunArtifactsS3Sync(options) {
|
|
|
21673
22794
|
}
|
|
21674
22795
|
async function downloadRunArtifactsFromS3(options) {
|
|
21675
22796
|
const db = options.db ?? getDatabase();
|
|
21676
|
-
const
|
|
22797
|
+
const now4 = options.now ?? (() => new Date);
|
|
21677
22798
|
const result = emptyResult();
|
|
21678
22799
|
for (const artifact of listRunArtifacts(db, options.filter)) {
|
|
21679
22800
|
try {
|
|
@@ -21709,7 +22830,7 @@ async function downloadRunArtifactsFromS3(options) {
|
|
|
21709
22830
|
...metadata,
|
|
21710
22831
|
remote_artifact_store: {
|
|
21711
22832
|
...remote,
|
|
21712
|
-
downloaded_at:
|
|
22833
|
+
downloaded_at: now4().toISOString()
|
|
21713
22834
|
}
|
|
21714
22835
|
});
|
|
21715
22836
|
result.downloaded += 1;
|
|
@@ -22728,7 +23849,7 @@ function parseObject2(value) {
|
|
|
22728
23849
|
function normalizeTags(tags) {
|
|
22729
23850
|
return [...new Set((tags || []).map((tag) => tag.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
22730
23851
|
}
|
|
22731
|
-
function
|
|
23852
|
+
function normalizeText3(value) {
|
|
22732
23853
|
if (value === undefined)
|
|
22733
23854
|
return null;
|
|
22734
23855
|
const trimmed = value.trim();
|
|
@@ -22783,9 +23904,9 @@ function createKnowledgeRecord(input, db) {
|
|
|
22783
23904
|
id,
|
|
22784
23905
|
input.record_type,
|
|
22785
23906
|
title,
|
|
22786
|
-
|
|
22787
|
-
|
|
22788
|
-
|
|
23907
|
+
normalizeText3(input.content),
|
|
23908
|
+
normalizeText3(input.decision),
|
|
23909
|
+
normalizeText3(input.rationale),
|
|
22789
23910
|
JSON.stringify(input.alternatives || []),
|
|
22790
23911
|
resolveKnownId("tasks", input.task_id, d),
|
|
22791
23912
|
resolveKnownId("projects", input.project_id, d),
|
|
@@ -22980,7 +24101,7 @@ function parseObject3(value) {
|
|
|
22980
24101
|
function normalizeTags2(tags) {
|
|
22981
24102
|
return [...new Set((tags || []).flatMap((tag) => tag.split(",")).map((tag) => tag.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
22982
24103
|
}
|
|
22983
|
-
function
|
|
24104
|
+
function normalizeText4(value) {
|
|
22984
24105
|
if (value === null || value === undefined)
|
|
22985
24106
|
return null;
|
|
22986
24107
|
const trimmed = value.trim();
|
|
@@ -23049,13 +24170,13 @@ function createRisk(input, db) {
|
|
|
23049
24170
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
23050
24171
|
id,
|
|
23051
24172
|
title,
|
|
23052
|
-
|
|
24173
|
+
normalizeText4(input.description),
|
|
23053
24174
|
status,
|
|
23054
24175
|
severity,
|
|
23055
24176
|
probability,
|
|
23056
|
-
|
|
23057
|
-
|
|
23058
|
-
|
|
24177
|
+
normalizeText4(input.owner),
|
|
24178
|
+
normalizeText4(input.mitigation),
|
|
24179
|
+
normalizeText4(input.due_at),
|
|
23059
24180
|
resolveKnownId2("projects", input.project_id, d),
|
|
23060
24181
|
resolveKnownId2("plans", input.plan_id, d),
|
|
23061
24182
|
resolveKnownId2("tasks", input.task_id, d),
|
|
@@ -23090,7 +24211,7 @@ function updateRisk(id, input, db) {
|
|
|
23090
24211
|
}
|
|
23091
24212
|
if (input.description !== undefined) {
|
|
23092
24213
|
sets.push("description = ?");
|
|
23093
|
-
params.push(
|
|
24214
|
+
params.push(normalizeText4(input.description));
|
|
23094
24215
|
}
|
|
23095
24216
|
if (input.status !== undefined) {
|
|
23096
24217
|
assertStatus(input.status);
|
|
@@ -23111,15 +24232,15 @@ function updateRisk(id, input, db) {
|
|
|
23111
24232
|
}
|
|
23112
24233
|
if (input.owner !== undefined) {
|
|
23113
24234
|
sets.push("owner = ?");
|
|
23114
|
-
params.push(
|
|
24235
|
+
params.push(normalizeText4(input.owner));
|
|
23115
24236
|
}
|
|
23116
24237
|
if (input.mitigation !== undefined) {
|
|
23117
24238
|
sets.push("mitigation = ?");
|
|
23118
|
-
params.push(
|
|
24239
|
+
params.push(normalizeText4(input.mitigation));
|
|
23119
24240
|
}
|
|
23120
24241
|
if (input.due_at !== undefined) {
|
|
23121
24242
|
sets.push("due_at = ?");
|
|
23122
|
-
params.push(
|
|
24243
|
+
params.push(normalizeText4(input.due_at));
|
|
23123
24244
|
}
|
|
23124
24245
|
if (input.project_id !== undefined) {
|
|
23125
24246
|
sets.push("project_id = ?");
|
|
@@ -23640,7 +24761,7 @@ function renderRetrospectiveMarkdown(record) {
|
|
|
23640
24761
|
}
|
|
23641
24762
|
// src/lib/project-bootstrap.ts
|
|
23642
24763
|
init_database();
|
|
23643
|
-
import { existsSync as
|
|
24764
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
23644
24765
|
import { basename as basename2, dirname as dirname7, resolve as resolve9 } from "path";
|
|
23645
24766
|
function safeStat(path) {
|
|
23646
24767
|
try {
|
|
@@ -23659,7 +24780,7 @@ function canonicalPath(input) {
|
|
|
23659
24780
|
function findUp(start, marker) {
|
|
23660
24781
|
let current = canonicalPath(start);
|
|
23661
24782
|
while (true) {
|
|
23662
|
-
if (
|
|
24783
|
+
if (existsSync9(resolve9(current, marker)))
|
|
23663
24784
|
return current;
|
|
23664
24785
|
const parent = dirname7(current);
|
|
23665
24786
|
if (parent === current)
|
|
@@ -23671,7 +24792,7 @@ function readPackageJson2(path) {
|
|
|
23671
24792
|
if (!path)
|
|
23672
24793
|
return null;
|
|
23673
24794
|
const file = resolve9(path, "package.json");
|
|
23674
|
-
if (!
|
|
24795
|
+
if (!existsSync9(file))
|
|
23675
24796
|
return null;
|
|
23676
24797
|
try {
|
|
23677
24798
|
const parsed = JSON.parse(readFileSync7(file, "utf-8"));
|
|
@@ -23693,7 +24814,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
23693
24814
|
if (rootPackage?.workspaces)
|
|
23694
24815
|
markers.push("package.json#workspaces");
|
|
23695
24816
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
23696
|
-
if (
|
|
24817
|
+
if (existsSync9(resolve9(root, marker)))
|
|
23697
24818
|
markers.push(marker);
|
|
23698
24819
|
}
|
|
23699
24820
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -23801,7 +24922,7 @@ function getProjectByPathForBootstrap(path, db) {
|
|
|
23801
24922
|
}
|
|
23802
24923
|
// src/db/api-keys.ts
|
|
23803
24924
|
init_database();
|
|
23804
|
-
import { createHash as
|
|
24925
|
+
import { createHash as createHash10, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
23805
24926
|
function rowToRecord(row) {
|
|
23806
24927
|
return {
|
|
23807
24928
|
id: row.id,
|
|
@@ -23815,12 +24936,12 @@ function rowToRecord(row) {
|
|
|
23815
24936
|
};
|
|
23816
24937
|
}
|
|
23817
24938
|
function hashApiKey(key) {
|
|
23818
|
-
return
|
|
24939
|
+
return createHash10("sha256").update(key).digest("hex");
|
|
23819
24940
|
}
|
|
23820
24941
|
function safeEqualHex(a, b) {
|
|
23821
24942
|
if (a.length !== b.length)
|
|
23822
24943
|
return false;
|
|
23823
|
-
return
|
|
24944
|
+
return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
23824
24945
|
}
|
|
23825
24946
|
function generatePlaintextKey() {
|
|
23826
24947
|
return `tdos_${randomBytes2(32).toString("base64url")}`;
|
|
@@ -24006,18 +25127,18 @@ var gatherTrainingData = async (options = {}) => {
|
|
|
24006
25127
|
};
|
|
24007
25128
|
// src/lib/model-config.ts
|
|
24008
25129
|
init_sync_utils();
|
|
24009
|
-
import { existsSync as
|
|
24010
|
-
import { join as
|
|
25130
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
25131
|
+
import { join as join10 } from "path";
|
|
24011
25132
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
24012
25133
|
function getConfigDir() {
|
|
24013
25134
|
return getTodosGlobalDir();
|
|
24014
25135
|
}
|
|
24015
25136
|
function getConfigPath2() {
|
|
24016
|
-
return
|
|
25137
|
+
return join10(getConfigDir(), "config.json");
|
|
24017
25138
|
}
|
|
24018
25139
|
function readConfig() {
|
|
24019
25140
|
const configPath = getConfigPath2();
|
|
24020
|
-
if (!
|
|
25141
|
+
if (!existsSync10(configPath))
|
|
24021
25142
|
return {};
|
|
24022
25143
|
try {
|
|
24023
25144
|
const raw = readFileSync8(configPath, "utf-8");
|
|
@@ -24028,7 +25149,7 @@ function readConfig() {
|
|
|
24028
25149
|
}
|
|
24029
25150
|
function writeConfig(config) {
|
|
24030
25151
|
const configDir = getConfigDir();
|
|
24031
|
-
if (!
|
|
25152
|
+
if (!existsSync10(configDir)) {
|
|
24032
25153
|
mkdirSync8(configDir, { recursive: true });
|
|
24033
25154
|
}
|
|
24034
25155
|
writeFileSync6(getConfigPath2(), JSON.stringify(config, null, 2) + `
|
|
@@ -24753,7 +25874,7 @@ CLI equivalent: \`${r.equivalent_cli}\`
|
|
|
24753
25874
|
`);
|
|
24754
25875
|
}
|
|
24755
25876
|
// src/lib/verification-providers.ts
|
|
24756
|
-
import { existsSync as
|
|
25877
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
24757
25878
|
init_database();
|
|
24758
25879
|
init_config();
|
|
24759
25880
|
init_redaction();
|
|
@@ -24919,7 +26040,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
24919
26040
|
};
|
|
24920
26041
|
}
|
|
24921
26042
|
function runCiLogProvider(input) {
|
|
24922
|
-
const text = input.log_text ?? (input.log_path &&
|
|
26043
|
+
const text = input.log_text ?? (input.log_path && existsSync11(input.log_path) ? readFileSync9(input.log_path, "utf-8") : "");
|
|
24923
26044
|
return {
|
|
24924
26045
|
status: classifyLog(text),
|
|
24925
26046
|
attempts: 1,
|
|
@@ -24931,7 +26052,7 @@ function runBrowserProvider(input) {
|
|
|
24931
26052
|
if (!input.artifact_path) {
|
|
24932
26053
|
return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
|
|
24933
26054
|
}
|
|
24934
|
-
if (!
|
|
26055
|
+
if (!existsSync11(input.artifact_path)) {
|
|
24935
26056
|
return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
|
|
24936
26057
|
}
|
|
24937
26058
|
return {
|
|
@@ -25529,11 +26650,11 @@ function buildResourceSnapshot(uri, staleMs = DEFAULT_STALE_MS) {
|
|
|
25529
26650
|
content_hash: hashContent(content)
|
|
25530
26651
|
};
|
|
25531
26652
|
}
|
|
25532
|
-
function isSnapshotStale(snapshot,
|
|
26653
|
+
function isSnapshotStale(snapshot, now4 = new Date) {
|
|
25533
26654
|
const staleAt = Date.parse(snapshot.stale_after);
|
|
25534
26655
|
if (!Number.isFinite(staleAt))
|
|
25535
26656
|
return true;
|
|
25536
|
-
return
|
|
26657
|
+
return now4.getTime() >= staleAt;
|
|
25537
26658
|
}
|
|
25538
26659
|
function subscribeResource(uri, agentId) {
|
|
25539
26660
|
const key = `${uri}:${agentId || "*"}`;
|
|
@@ -25570,21 +26691,21 @@ function resourceDiagnostics() {
|
|
|
25570
26691
|
};
|
|
25571
26692
|
}
|
|
25572
26693
|
// src/lib/sandbox-profiles.ts
|
|
25573
|
-
import { existsSync as
|
|
25574
|
-
import { join as
|
|
26694
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync10 } from "fs";
|
|
26695
|
+
import { join as join11, dirname as dirname9 } from "path";
|
|
25575
26696
|
var SANDBOX_PROFILE_VERSION = "todos.sandbox-profile.v1";
|
|
25576
26697
|
function getProfilesPath() {
|
|
25577
26698
|
if (process.env["TODOS_SANDBOX_PROFILES_PATH"]) {
|
|
25578
26699
|
return process.env["TODOS_SANDBOX_PROFILES_PATH"];
|
|
25579
26700
|
}
|
|
25580
|
-
const localDir =
|
|
25581
|
-
const local =
|
|
25582
|
-
if (
|
|
26701
|
+
const localDir = join11(process.cwd(), ".todos");
|
|
26702
|
+
const local = join11(localDir, "sandbox-profiles.json");
|
|
26703
|
+
if (existsSync12(localDir))
|
|
25583
26704
|
return local;
|
|
25584
|
-
if (
|
|
26705
|
+
if (existsSync12(local))
|
|
25585
26706
|
return local;
|
|
25586
26707
|
const home = process.env["HOME"] || "~";
|
|
25587
|
-
return
|
|
26708
|
+
return join11(home, ".hasna", "todos", "sandbox-profiles.json");
|
|
25588
26709
|
}
|
|
25589
26710
|
var cached2 = null;
|
|
25590
26711
|
function resetSandboxProfileCache() {
|
|
@@ -25616,7 +26737,7 @@ function loadSandboxProfiles() {
|
|
|
25616
26737
|
if (cached2)
|
|
25617
26738
|
return cached2;
|
|
25618
26739
|
const path = getProfilesPath();
|
|
25619
|
-
if (!
|
|
26740
|
+
if (!existsSync12(path)) {
|
|
25620
26741
|
cached2 = getDefaultSandboxProfiles();
|
|
25621
26742
|
return cached2;
|
|
25622
26743
|
}
|
|
@@ -25989,7 +27110,7 @@ function getDefaultAgentAdapters() {
|
|
|
25989
27110
|
}
|
|
25990
27111
|
function resetAgentAdapterCache() {}
|
|
25991
27112
|
// src/lib/git-traceability.ts
|
|
25992
|
-
import { existsSync as
|
|
27113
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
25993
27114
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
25994
27115
|
import { resolve as resolve11 } from "path";
|
|
25995
27116
|
var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
|
|
@@ -26035,7 +27156,7 @@ function inspectGitCommit(sha, cwd) {
|
|
|
26035
27156
|
}
|
|
26036
27157
|
function loadCiSnapshot(path) {
|
|
26037
27158
|
const target = path ? resolve11(path) : resolve11(process.cwd(), ".todos", "ci-snapshot.json");
|
|
26038
|
-
if (!
|
|
27159
|
+
if (!existsSync13(target))
|
|
26039
27160
|
return null;
|
|
26040
27161
|
try {
|
|
26041
27162
|
const parsed = JSON.parse(readFileSync11(target, "utf8"));
|
|
@@ -26131,8 +27252,8 @@ function formatTraceabilityReport(report) {
|
|
|
26131
27252
|
`);
|
|
26132
27253
|
}
|
|
26133
27254
|
// src/lib/mention-resolver.ts
|
|
26134
|
-
import { existsSync as
|
|
26135
|
-
import { basename as basename3, isAbsolute, join as
|
|
27255
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
27256
|
+
import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as resolve12, sep } from "path";
|
|
26136
27257
|
init_database();
|
|
26137
27258
|
var PREFIXES = {
|
|
26138
27259
|
file: "file",
|
|
@@ -26283,7 +27404,7 @@ function resolveFile(parsed, workspace) {
|
|
|
26283
27404
|
return resolution;
|
|
26284
27405
|
}
|
|
26285
27406
|
resolution.path = relPath;
|
|
26286
|
-
if (!
|
|
27407
|
+
if (!existsSync14(absolutePath)) {
|
|
26287
27408
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
26288
27409
|
return resolution;
|
|
26289
27410
|
}
|
|
@@ -26316,7 +27437,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
26316
27437
|
if (SKIP_DIRS.has(entry2.name))
|
|
26317
27438
|
continue;
|
|
26318
27439
|
}
|
|
26319
|
-
const absolutePath =
|
|
27440
|
+
const absolutePath = join12(current, entry2.name);
|
|
26320
27441
|
if (entry2.isDirectory()) {
|
|
26321
27442
|
if (!SKIP_DIRS.has(entry2.name))
|
|
26322
27443
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -28189,9 +29310,9 @@ function getAdapterDocsFingerprint() {
|
|
|
28189
29310
|
}
|
|
28190
29311
|
// src/lib/inbox-intake.ts
|
|
28191
29312
|
init_database();
|
|
28192
|
-
import { existsSync as
|
|
29313
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
28193
29314
|
import { basename as basename4 } from "path";
|
|
28194
|
-
import { createHash as
|
|
29315
|
+
import { createHash as createHash11 } from "crypto";
|
|
28195
29316
|
init_secret_redaction();
|
|
28196
29317
|
var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
|
|
28197
29318
|
var INTAKE_SOURCE_TYPES = [
|
|
@@ -28204,7 +29325,7 @@ var INTAKE_SOURCE_TYPES = [
|
|
|
28204
29325
|
];
|
|
28205
29326
|
var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
|
|
28206
29327
|
function fingerprint2(text) {
|
|
28207
|
-
return
|
|
29328
|
+
return createHash11("sha256").update(text).digest("hex").slice(0, 16);
|
|
28208
29329
|
}
|
|
28209
29330
|
function loadRawContent(input) {
|
|
28210
29331
|
if (input.github_url) {
|
|
@@ -28235,7 +29356,7 @@ function loadRawContent(input) {
|
|
|
28235
29356
|
}
|
|
28236
29357
|
}
|
|
28237
29358
|
if (input.file_path) {
|
|
28238
|
-
if (!
|
|
29359
|
+
if (!existsSync15(input.file_path))
|
|
28239
29360
|
throw new Error(`File not found: ${input.file_path}`);
|
|
28240
29361
|
const raw = readFileSync13(input.file_path, "utf8");
|
|
28241
29362
|
const name = basename4(input.file_path).toLowerCase();
|
|
@@ -28853,7 +29974,7 @@ function formatNlIntakePreviewText(preview) {
|
|
|
28853
29974
|
}
|
|
28854
29975
|
// src/lib/issue-importers.ts
|
|
28855
29976
|
init_database();
|
|
28856
|
-
import { existsSync as
|
|
29977
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
28857
29978
|
var ISSUE_IMPORT_SCHEMA = "todos.issue_import.v1";
|
|
28858
29979
|
var ISSUE_SOURCES = ["github", "linear", "jira", "auto"];
|
|
28859
29980
|
var GITHUB_LABEL_PRIORITY = {
|
|
@@ -29072,7 +30193,7 @@ function parseIssueExport(data, source9 = "auto") {
|
|
|
29072
30193
|
return normalized;
|
|
29073
30194
|
}
|
|
29074
30195
|
function loadIssueExportFromFile(path) {
|
|
29075
|
-
if (!
|
|
30196
|
+
if (!existsSync16(path))
|
|
29076
30197
|
throw new Error(`File not found: ${path}`);
|
|
29077
30198
|
return JSON.parse(readFileSync14(path, "utf8"));
|
|
29078
30199
|
}
|
|
@@ -29232,8 +30353,8 @@ todos import issues ./linear.json --source linear --dry-run
|
|
|
29232
30353
|
// src/lib/run-records.ts
|
|
29233
30354
|
init_database();
|
|
29234
30355
|
init_secret_redaction();
|
|
29235
|
-
import { existsSync as
|
|
29236
|
-
import { join as
|
|
30356
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
30357
|
+
import { join as join13, dirname as dirname10 } from "path";
|
|
29237
30358
|
var RUN_RECORD_SCHEMA = "todos.run_record.v1";
|
|
29238
30359
|
var RUN_RECORD_STATUSES = ["active", "completed", "failed", "archived"];
|
|
29239
30360
|
function parseJsonArray3(raw, fallback = []) {
|
|
@@ -29426,7 +30547,7 @@ function buildRunReplayBundle(id, db) {
|
|
|
29426
30547
|
}
|
|
29427
30548
|
function exportRunReplay(id, outputPath, db) {
|
|
29428
30549
|
const bundle = buildRunReplayBundle(id, db);
|
|
29429
|
-
const path = outputPath ??
|
|
30550
|
+
const path = outputPath ?? join13(process.cwd(), ".todos", "replays", `${id.slice(0, 8)}.json`);
|
|
29430
30551
|
mkdirSync11(dirname10(path), { recursive: true });
|
|
29431
30552
|
writeFileSync9(path, JSON.stringify(bundle, null, 2));
|
|
29432
30553
|
const d = db || getDatabase();
|
|
@@ -29466,16 +30587,16 @@ function formatRunRecordMarkdown(record) {
|
|
|
29466
30587
|
`;
|
|
29467
30588
|
}
|
|
29468
30589
|
function getDefaultReplayDir() {
|
|
29469
|
-
const local =
|
|
29470
|
-
if (
|
|
30590
|
+
const local = join13(process.cwd(), ".todos", "replays");
|
|
30591
|
+
if (existsSync17(join13(process.cwd(), ".todos")))
|
|
29471
30592
|
return local;
|
|
29472
30593
|
const home = process.env["HOME"] || "~";
|
|
29473
|
-
return
|
|
30594
|
+
return join13(home, ".hasna", "todos", "replays");
|
|
29474
30595
|
}
|
|
29475
30596
|
// src/lib/release-checks.ts
|
|
29476
30597
|
init_secret_redaction();
|
|
29477
|
-
import { existsSync as
|
|
29478
|
-
import { join as
|
|
30598
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
30599
|
+
import { join as join14, relative as relative5 } from "path";
|
|
29479
30600
|
var RELEASE_CHECK_SCHEMA = "todos.release_check.v1";
|
|
29480
30601
|
var FORBIDDEN_DIST_PATTERNS = [
|
|
29481
30602
|
{
|
|
@@ -29488,16 +30609,16 @@ var FORBIDDEN_DIST_PATTERNS = [
|
|
|
29488
30609
|
];
|
|
29489
30610
|
var REQUIRED_BINS = ["todos", "todos-mcp", "todos-serve"];
|
|
29490
30611
|
function readPackageJson3(root) {
|
|
29491
|
-
const path =
|
|
29492
|
-
if (!
|
|
30612
|
+
const path = join14(root, "package.json");
|
|
30613
|
+
if (!existsSync18(path))
|
|
29493
30614
|
throw new Error(`package.json not found in ${root}`);
|
|
29494
30615
|
return JSON.parse(readFileSync15(path, "utf8"));
|
|
29495
30616
|
}
|
|
29496
30617
|
function walkFiles(dir, acc = []) {
|
|
29497
|
-
if (!
|
|
30618
|
+
if (!existsSync18(dir))
|
|
29498
30619
|
return acc;
|
|
29499
30620
|
for (const entry2 of readdirSync3(dir)) {
|
|
29500
|
-
const full =
|
|
30621
|
+
const full = join14(dir, entry2);
|
|
29501
30622
|
const st = statSync5(full);
|
|
29502
30623
|
if (st.isDirectory())
|
|
29503
30624
|
walkFiles(full, acc);
|
|
@@ -29515,8 +30636,8 @@ function auditPackageContents(root) {
|
|
|
29515
30636
|
checks.push({ id: "files_dist", severity: "error", message: "package.json files must include dist" });
|
|
29516
30637
|
}
|
|
29517
30638
|
for (const pattern of files) {
|
|
29518
|
-
const target =
|
|
29519
|
-
if (!
|
|
30639
|
+
const target = join14(root, pattern);
|
|
30640
|
+
if (!existsSync18(target)) {
|
|
29520
30641
|
checks.push({ id: `files_missing_${pattern}`, severity: "error", message: `Published file path missing: ${pattern}` });
|
|
29521
30642
|
}
|
|
29522
30643
|
}
|
|
@@ -29530,8 +30651,8 @@ function auditPackageContents(root) {
|
|
|
29530
30651
|
checks.push({ id: `bin_${name}`, severity: "error", message: `Missing bin entry: ${name}` });
|
|
29531
30652
|
continue;
|
|
29532
30653
|
}
|
|
29533
|
-
const binPath =
|
|
29534
|
-
if (!
|
|
30654
|
+
const binPath = join14(root, rel);
|
|
30655
|
+
if (!existsSync18(binPath)) {
|
|
29535
30656
|
checks.push({ id: `bin_path_${name}`, severity: "error", message: `Bin file missing: ${rel}` });
|
|
29536
30657
|
} else {
|
|
29537
30658
|
checks.push({ id: `bin_ok_${name}`, severity: "info", message: `Bin present: ${name} \u2192 ${rel}` });
|
|
@@ -29548,8 +30669,8 @@ function auditPackageContents(root) {
|
|
|
29548
30669
|
}
|
|
29549
30670
|
function scanDistArtifacts(root) {
|
|
29550
30671
|
const checks = [];
|
|
29551
|
-
const distDir =
|
|
29552
|
-
if (!
|
|
30672
|
+
const distDir = join14(root, "dist");
|
|
30673
|
+
if (!existsSync18(distDir)) {
|
|
29553
30674
|
checks.push({ id: "dist_missing", severity: "error", message: "dist/ directory not found \u2014 run bun run build" });
|
|
29554
30675
|
return checks;
|
|
29555
30676
|
}
|
|
@@ -29855,8 +30976,8 @@ function renderReleaseNotesMarkdown(document) {
|
|
|
29855
30976
|
// src/lib/db-backup.ts
|
|
29856
30977
|
init_database();
|
|
29857
30978
|
init_migrations();
|
|
29858
|
-
import { existsSync as
|
|
29859
|
-
import { dirname as dirname11, join as
|
|
30979
|
+
import { existsSync as existsSync19, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
|
|
30980
|
+
import { dirname as dirname11, join as join15, resolve as resolve13 } from "path";
|
|
29860
30981
|
import { Database as Database3 } from "bun:sqlite";
|
|
29861
30982
|
var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
|
|
29862
30983
|
function resolveDbPath(dbPath) {
|
|
@@ -29873,7 +30994,7 @@ function resolveDbPath(dbPath) {
|
|
|
29873
30994
|
}
|
|
29874
30995
|
function backupDatabase(outputPath, sourcePath) {
|
|
29875
30996
|
const source9 = resolveDbPath(sourcePath);
|
|
29876
|
-
if (!
|
|
30997
|
+
if (!existsSync19(source9))
|
|
29877
30998
|
throw new Error(`Database not found: ${source9}`);
|
|
29878
30999
|
mkdirSync12(dirname11(outputPath), { recursive: true });
|
|
29879
31000
|
closeDatabase();
|
|
@@ -29896,7 +31017,7 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
29896
31017
|
};
|
|
29897
31018
|
}
|
|
29898
31019
|
function restoreDatabase(backupPath, targetPath) {
|
|
29899
|
-
if (!
|
|
31020
|
+
if (!existsSync19(backupPath))
|
|
29900
31021
|
throw new Error(`Backup not found: ${backupPath}`);
|
|
29901
31022
|
const integrity = checkDatabaseIntegrity(backupPath);
|
|
29902
31023
|
if (!integrity.ok) {
|
|
@@ -29923,7 +31044,7 @@ function restoreDatabase(backupPath, targetPath) {
|
|
|
29923
31044
|
function checkDatabaseIntegrity(dbPath) {
|
|
29924
31045
|
const path = dbPath ? resolve13(dbPath) : resolveDbPath();
|
|
29925
31046
|
const errors = [];
|
|
29926
|
-
if (!
|
|
31047
|
+
if (!existsSync19(path)) {
|
|
29927
31048
|
return {
|
|
29928
31049
|
schema_version: DB_BACKUP_SCHEMA,
|
|
29929
31050
|
path,
|
|
@@ -30022,11 +31143,11 @@ function migrationDryRun(dbPath) {
|
|
|
30022
31143
|
function defaultBackupPath(dbPath) {
|
|
30023
31144
|
const base = dbPath ? dirname11(resolve13(dbPath)) : dirname11(resolveDbPath());
|
|
30024
31145
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
30025
|
-
return
|
|
31146
|
+
return join15(base, "backups", `todos-${stamp}.db`);
|
|
30026
31147
|
}
|
|
30027
31148
|
function readBackupManifest(backupPath) {
|
|
30028
31149
|
const manifestPath = `${backupPath}.json`;
|
|
30029
|
-
if (!
|
|
31150
|
+
if (!existsSync19(manifestPath))
|
|
30030
31151
|
return null;
|
|
30031
31152
|
try {
|
|
30032
31153
|
return JSON.parse(readFileSync16(manifestPath, "utf8"));
|
|
@@ -30054,7 +31175,10 @@ var SCHEMA_ENTITIES = [
|
|
|
30054
31175
|
"verification_evidence",
|
|
30055
31176
|
"handoff",
|
|
30056
31177
|
"import_export_bundle",
|
|
30057
|
-
"mcp_response"
|
|
31178
|
+
"mcp_response",
|
|
31179
|
+
"tester_issue_report",
|
|
31180
|
+
"tester_issue_report_result",
|
|
31181
|
+
"tester_issue_report_batch_result"
|
|
30058
31182
|
];
|
|
30059
31183
|
var ISO_DATE = { type: "string", description: "ISO-8601 timestamp" };
|
|
30060
31184
|
function def(entity, schemaVersion, title, required, properties) {
|
|
@@ -30184,6 +31308,81 @@ var JSON_SCHEMAS = {
|
|
|
30184
31308
|
}
|
|
30185
31309
|
},
|
|
30186
31310
|
is_error: { type: "boolean" }
|
|
31311
|
+
}),
|
|
31312
|
+
tester_issue_report: def("tester_issue_report", "testers.issue_report.v1", "TesterIssueReport", ["schema_version", "title"], {
|
|
31313
|
+
schema_version: { type: "string", enum: ["testers.issue_report.v1"] },
|
|
31314
|
+
id: { type: "string" },
|
|
31315
|
+
fingerprint: { type: "string" },
|
|
31316
|
+
title: { type: "string" },
|
|
31317
|
+
summary: { type: ["string", "null"] },
|
|
31318
|
+
kind: { type: "string" },
|
|
31319
|
+
severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
31320
|
+
source: {
|
|
31321
|
+
type: "object",
|
|
31322
|
+
additionalProperties: true,
|
|
31323
|
+
properties: {
|
|
31324
|
+
tool: { type: "string" },
|
|
31325
|
+
run_id: { type: "string" },
|
|
31326
|
+
result_id: { type: "string" },
|
|
31327
|
+
scenario_id: { type: "string" },
|
|
31328
|
+
scenario_name: { type: "string" },
|
|
31329
|
+
project_id: { type: "string" },
|
|
31330
|
+
url: { type: "string" },
|
|
31331
|
+
page_url: { type: "string" },
|
|
31332
|
+
artifact_url: { type: "string" },
|
|
31333
|
+
screenshot_url: { type: "string" },
|
|
31334
|
+
commit: { type: "string" },
|
|
31335
|
+
branch: { type: "string" }
|
|
31336
|
+
}
|
|
31337
|
+
},
|
|
31338
|
+
target: {
|
|
31339
|
+
type: "object",
|
|
31340
|
+
additionalProperties: true,
|
|
31341
|
+
properties: {
|
|
31342
|
+
url: { type: "string" },
|
|
31343
|
+
route: { type: "string" },
|
|
31344
|
+
selector: { type: "string" },
|
|
31345
|
+
component: { type: "string" },
|
|
31346
|
+
browser: { type: "string" },
|
|
31347
|
+
viewport: { type: "string" }
|
|
31348
|
+
}
|
|
31349
|
+
},
|
|
31350
|
+
failure: {
|
|
31351
|
+
type: "object",
|
|
31352
|
+
additionalProperties: true,
|
|
31353
|
+
properties: {
|
|
31354
|
+
message: { type: "string" },
|
|
31355
|
+
expected: { type: "string" },
|
|
31356
|
+
actual: { type: "string" },
|
|
31357
|
+
stack: { type: "string" },
|
|
31358
|
+
reasoning: { type: "string" },
|
|
31359
|
+
steps: { type: "array", items: { type: "string" } }
|
|
31360
|
+
}
|
|
31361
|
+
},
|
|
31362
|
+
evidence: { type: "object", additionalProperties: true },
|
|
31363
|
+
labels: { type: "array", items: { type: "string" } },
|
|
31364
|
+
metadata: { type: "object", additionalProperties: true },
|
|
31365
|
+
occurred_at: ISO_DATE
|
|
31366
|
+
}),
|
|
31367
|
+
tester_issue_report_result: def("tester_issue_report_result", "todos.tester_issue_report_result.v1", "TesterIssueReportResult", ["schema_version", "local_only", "dry_run", "processed_at", "action", "fingerprint", "report", "task", "warnings", "commands"], {
|
|
31368
|
+
schema_version: { type: "string", enum: ["todos.tester_issue_report_result.v1"] },
|
|
31369
|
+
local_only: { type: "boolean" },
|
|
31370
|
+
dry_run: { type: "boolean" },
|
|
31371
|
+
processed_at: ISO_DATE,
|
|
31372
|
+
action: { type: "string", enum: ["preview", "matched", "created", "updated", "regressed"] },
|
|
31373
|
+
fingerprint: { type: "string" },
|
|
31374
|
+
report: { type: "object", additionalProperties: true },
|
|
31375
|
+
task: { type: ["object", "null"], additionalProperties: true },
|
|
31376
|
+
warnings: { type: "array", items: { type: "string" } },
|
|
31377
|
+
commands: { type: "array", items: { type: "string" } }
|
|
31378
|
+
}),
|
|
31379
|
+
tester_issue_report_batch_result: def("tester_issue_report_batch_result", "todos.tester_issue_report_batch_result.v1", "TesterIssueReportBatchResult", ["schema_version", "local_only", "dry_run", "processed_at", "results", "summary"], {
|
|
31380
|
+
schema_version: { type: "string", enum: ["todos.tester_issue_report_batch_result.v1"] },
|
|
31381
|
+
local_only: { type: "boolean" },
|
|
31382
|
+
dry_run: { type: "boolean" },
|
|
31383
|
+
processed_at: ISO_DATE,
|
|
31384
|
+
results: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
31385
|
+
summary: { type: "object", additionalProperties: true }
|
|
30187
31386
|
})
|
|
30188
31387
|
};
|
|
30189
31388
|
var SCHEMA_CONTRACT_FIXTURES = {
|
|
@@ -30264,6 +31463,47 @@ var SCHEMA_CONTRACT_FIXTURES = {
|
|
|
30264
31463
|
mcp_response: {
|
|
30265
31464
|
schema_version: "todos.mcp_response.v1",
|
|
30266
31465
|
content: [{ type: "text", text: "ok" }]
|
|
31466
|
+
},
|
|
31467
|
+
tester_issue_report: {
|
|
31468
|
+
schema_version: "testers.issue_report.v1",
|
|
31469
|
+
title: "Checkout button fails",
|
|
31470
|
+
kind: "assertion_failure",
|
|
31471
|
+
severity: "high",
|
|
31472
|
+
fingerprint: "checkout-button-1234",
|
|
31473
|
+
source: {
|
|
31474
|
+
tool: "testers",
|
|
31475
|
+
run_id: "run-001",
|
|
31476
|
+
scenario_id: "scenario-001",
|
|
31477
|
+
url: "https://preview.example.com/checkout"
|
|
31478
|
+
},
|
|
31479
|
+
failure: {
|
|
31480
|
+
message: "Expected checkout button to become enabled",
|
|
31481
|
+
steps: ["Open checkout", "Fill required fields"]
|
|
31482
|
+
},
|
|
31483
|
+
labels: ["checkout", "regression"]
|
|
31484
|
+
},
|
|
31485
|
+
tester_issue_report_result: {
|
|
31486
|
+
schema_version: "todos.tester_issue_report_result.v1",
|
|
31487
|
+
local_only: true,
|
|
31488
|
+
dry_run: false,
|
|
31489
|
+
processed_at: "2026-01-01T00:00:00.000Z",
|
|
31490
|
+
action: "created",
|
|
31491
|
+
fingerprint: "testers:checkout-button-1234",
|
|
31492
|
+
report: {
|
|
31493
|
+
schema_version: "testers.issue_report.v1",
|
|
31494
|
+
title: "Checkout button fails"
|
|
31495
|
+
},
|
|
31496
|
+
task: null,
|
|
31497
|
+
warnings: [],
|
|
31498
|
+
commands: []
|
|
31499
|
+
},
|
|
31500
|
+
tester_issue_report_batch_result: {
|
|
31501
|
+
schema_version: "todos.tester_issue_report_batch_result.v1",
|
|
31502
|
+
local_only: true,
|
|
31503
|
+
dry_run: false,
|
|
31504
|
+
processed_at: "2026-01-01T00:00:00.000Z",
|
|
31505
|
+
results: [],
|
|
31506
|
+
summary: { total: 0, preview: 0, matched: 0, created: 0, updated: 0, regressed: 0 }
|
|
30267
31507
|
}
|
|
30268
31508
|
};
|
|
30269
31509
|
function typeMatches(value, expected) {
|
|
@@ -30427,15 +31667,15 @@ ${SCHEMA_ENTITIES.map((e) => `- **${e}**: \`${JSON_SCHEMAS[e].schema_version}\``
|
|
|
30427
31667
|
}
|
|
30428
31668
|
function exportSchemasToDirectory(dir) {
|
|
30429
31669
|
const { mkdirSync: mkdirSync13, writeFileSync: writeFileSync11 } = __require("fs");
|
|
30430
|
-
const { join:
|
|
31670
|
+
const { join: join16 } = __require("path");
|
|
30431
31671
|
mkdirSync13(dir, { recursive: true });
|
|
30432
31672
|
const written = [];
|
|
30433
31673
|
for (const entity of SCHEMA_ENTITIES) {
|
|
30434
|
-
const path =
|
|
31674
|
+
const path = join16(dir, `${entity}.${JSON_SCHEMAS[entity].schema_version.replace(/\./g, "-")}.json`);
|
|
30435
31675
|
writeFileSync11(path, JSON.stringify(JSON_SCHEMAS[entity], null, 2));
|
|
30436
31676
|
written.push(path);
|
|
30437
31677
|
}
|
|
30438
|
-
const catalogPath =
|
|
31678
|
+
const catalogPath = join16(dir, "catalog.json");
|
|
30439
31679
|
writeFileSync11(catalogPath, JSON.stringify({
|
|
30440
31680
|
catalog_version: JSON_SCHEMA_CATALOG_VERSION,
|
|
30441
31681
|
semver: SCHEMA_SEMVER,
|
|
@@ -30641,6 +31881,9 @@ function hasFts(db) {
|
|
|
30641
31881
|
function escapeFtsQuery(q) {
|
|
30642
31882
|
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
30643
31883
|
}
|
|
31884
|
+
function shouldUseFts(q) {
|
|
31885
|
+
return /^[\p{L}\p{N}_\-\s]+$/u.test(q);
|
|
31886
|
+
}
|
|
30644
31887
|
function searchTasks(options, projectId, taskListId, db) {
|
|
30645
31888
|
const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
|
|
30646
31889
|
const d = db || getDatabase();
|
|
@@ -30649,7 +31892,8 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
30649
31892
|
let sql;
|
|
30650
31893
|
const raw = opts.query?.trim() ?? "";
|
|
30651
31894
|
const q = raw === "*" ? "" : raw;
|
|
30652
|
-
|
|
31895
|
+
const useFts = hasFts(d) && q && shouldUseFts(q);
|
|
31896
|
+
if (useFts) {
|
|
30653
31897
|
const ftsQuery = escapeFtsQuery(q);
|
|
30654
31898
|
sql = `SELECT t.* FROM tasks t
|
|
30655
31899
|
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
@@ -30657,8 +31901,16 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
30657
31901
|
params.push(ftsQuery);
|
|
30658
31902
|
} else if (q) {
|
|
30659
31903
|
const pattern = `%${q}%`;
|
|
30660
|
-
sql = `SELECT * FROM tasks t WHERE (
|
|
30661
|
-
|
|
31904
|
+
sql = `SELECT * FROM tasks t WHERE (
|
|
31905
|
+
t.id LIKE ?
|
|
31906
|
+
OR t.short_id LIKE ?
|
|
31907
|
+
OR t.title LIKE ?
|
|
31908
|
+
OR t.description LIKE ?
|
|
31909
|
+
OR t.working_dir LIKE ?
|
|
31910
|
+
OR t.metadata LIKE ?
|
|
31911
|
+
OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?)
|
|
31912
|
+
)`;
|
|
31913
|
+
params.push(pattern, pattern, pattern, pattern, pattern, pattern, pattern);
|
|
30662
31914
|
} else {
|
|
30663
31915
|
sql = `SELECT * FROM tasks t WHERE 1=1`;
|
|
30664
31916
|
}
|
|
@@ -30714,7 +31966,7 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
30714
31966
|
} else if (opts.is_blocked === false) {
|
|
30715
31967
|
sql += " AND t.id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
30716
31968
|
}
|
|
30717
|
-
if (
|
|
31969
|
+
if (useFts) {
|
|
30718
31970
|
sql += ` ORDER BY bm25(tasks_fts),
|
|
30719
31971
|
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
30720
31972
|
t.created_at DESC`;
|
|
@@ -32992,7 +34244,7 @@ function generateCliReferenceMarkdown() {
|
|
|
32992
34244
|
// src/db/builtin-templates.ts
|
|
32993
34245
|
init_database();
|
|
32994
34246
|
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
32995
|
-
import { join as
|
|
34247
|
+
import { join as join16 } from "path";
|
|
32996
34248
|
var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21";
|
|
32997
34249
|
var BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library";
|
|
32998
34250
|
var TEMPLATE_LIBRARY_SCHEMA = "todos.template_library.v1";
|
|
@@ -33268,7 +34520,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
33268
34520
|
mkdirSync15(directory, { recursive: true });
|
|
33269
34521
|
const files = [];
|
|
33270
34522
|
for (const entry2 of exportBuiltinTemplateFiles()) {
|
|
33271
|
-
const path =
|
|
34523
|
+
const path = join16(directory, entry2.filename);
|
|
33272
34524
|
writeFileSync13(path, `${JSON.stringify(entry2.template, null, 2)}
|
|
33273
34525
|
`, "utf-8");
|
|
33274
34526
|
files.push(path);
|
|
@@ -33550,10 +34802,10 @@ todos machines topology # full diagnostic report
|
|
|
33550
34802
|
`;
|
|
33551
34803
|
}
|
|
33552
34804
|
// src/lib/environment-snapshots.ts
|
|
33553
|
-
import { createHash as
|
|
33554
|
-
import { existsSync as
|
|
34805
|
+
import { createHash as createHash12 } from "crypto";
|
|
34806
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
|
|
33555
34807
|
import { hostname as hostname3, platform, arch } from "os";
|
|
33556
|
-
import { dirname as dirname15, join as
|
|
34808
|
+
import { dirname as dirname15, join as join17, resolve as resolve14 } from "path";
|
|
33557
34809
|
import { tmpdir as tmpdir2 } from "os";
|
|
33558
34810
|
init_database();
|
|
33559
34811
|
init_redaction();
|
|
@@ -33575,11 +34827,11 @@ var CONFIG_FILES = [
|
|
|
33575
34827
|
"dashboard/vite.config.ts"
|
|
33576
34828
|
];
|
|
33577
34829
|
function sha2565(value) {
|
|
33578
|
-
return
|
|
34830
|
+
return createHash12("sha256").update(value).digest("hex");
|
|
33579
34831
|
}
|
|
33580
34832
|
function fileRecord(root, relativePath) {
|
|
33581
|
-
const path =
|
|
33582
|
-
if (!
|
|
34833
|
+
const path = join17(root, relativePath);
|
|
34834
|
+
if (!existsSync20(path))
|
|
33583
34835
|
return null;
|
|
33584
34836
|
const stat = statSync7(path);
|
|
33585
34837
|
if (!stat.isFile())
|
|
@@ -33591,7 +34843,7 @@ function manifestRecord(root, relativePath) {
|
|
|
33591
34843
|
const base = fileRecord(root, relativePath);
|
|
33592
34844
|
if (!base)
|
|
33593
34845
|
return null;
|
|
33594
|
-
const parsed = readJsonFile(
|
|
34846
|
+
const parsed = readJsonFile(join17(root, relativePath));
|
|
33595
34847
|
if (!parsed)
|
|
33596
34848
|
return { ...base, redacted: {} };
|
|
33597
34849
|
const redacted = redactValue({
|
|
@@ -33686,8 +34938,8 @@ function commandEnv(env, includeValues) {
|
|
|
33686
34938
|
function defaultSnapshotDir() {
|
|
33687
34939
|
const dbPath = getDatabasePath();
|
|
33688
34940
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
33689
|
-
return
|
|
33690
|
-
return
|
|
34941
|
+
return join17(tmpdir2(), "hasna-todos", "environment-snapshots");
|
|
34942
|
+
return join17(dirname15(resolve14(dbPath)), "environment-snapshots");
|
|
33691
34943
|
}
|
|
33692
34944
|
function snapshotWithId(snapshot) {
|
|
33693
34945
|
const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
|
|
@@ -33734,7 +34986,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
33734
34986
|
});
|
|
33735
34987
|
}
|
|
33736
34988
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
33737
|
-
const path = outputPath ? resolve14(outputPath) :
|
|
34989
|
+
const path = outputPath ? resolve14(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
33738
34990
|
ensureDir2(dirname15(path));
|
|
33739
34991
|
writeJsonFile(path, snapshot);
|
|
33740
34992
|
return path;
|
|
@@ -33825,9 +35077,9 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
|
|
|
33825
35077
|
}
|
|
33826
35078
|
// src/lib/decision-records.ts
|
|
33827
35079
|
init_database();
|
|
33828
|
-
import { createHash as
|
|
35080
|
+
import { createHash as createHash13 } from "crypto";
|
|
33829
35081
|
import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync15 } from "fs";
|
|
33830
|
-
import { dirname as dirname16, join as
|
|
35082
|
+
import { dirname as dirname16, join as join18 } from "path";
|
|
33831
35083
|
var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
|
|
33832
35084
|
var KNOWLEDGE_SNAPSHOT_SCHEMA = "todos.knowledge_snapshot.v1";
|
|
33833
35085
|
var DECISION_STATUSES = ["proposed", "accepted", "deprecated", "superseded", "rejected"];
|
|
@@ -33881,7 +35133,7 @@ function rowToDecisionRecord(row) {
|
|
|
33881
35133
|
}
|
|
33882
35134
|
function stableSnapshotHash(payload) {
|
|
33883
35135
|
const { captured_at: _capturedAt, ...rest } = payload;
|
|
33884
|
-
return
|
|
35136
|
+
return createHash13("sha256").update(JSON.stringify(rest)).digest("hex");
|
|
33885
35137
|
}
|
|
33886
35138
|
function createDecisionRecord(input, db) {
|
|
33887
35139
|
const d = db || getDatabase();
|
|
@@ -34057,7 +35309,7 @@ function exportDecisionRecord(id, outputPath, format = "markdown", db) {
|
|
|
34057
35309
|
if (!record)
|
|
34058
35310
|
throw new Error(`Decision record not found: ${id}`);
|
|
34059
35311
|
const content = format === "markdown" ? formatDecisionRecordMarkdown(record) : JSON.stringify(record, null, 2);
|
|
34060
|
-
const path = outputPath ??
|
|
35312
|
+
const path = outputPath ?? join18(process.cwd(), ".todos", "decisions", `${record.short_ref}.${format === "markdown" ? "md" : "json"}`);
|
|
34061
35313
|
mkdirSync17(dirname16(path), { recursive: true });
|
|
34062
35314
|
writeFileSync15(path, content, "utf8");
|
|
34063
35315
|
return { path, content };
|
|
@@ -34205,7 +35457,7 @@ function exportKnowledgeSnapshot(id, outputPath, format = "markdown", db) {
|
|
|
34205
35457
|
throw new Error(`Knowledge snapshot not found: ${id}`);
|
|
34206
35458
|
const content = format === "markdown" ? formatKnowledgeSnapshotMarkdown(record) : JSON.stringify(record, null, 2);
|
|
34207
35459
|
const slug = record.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
34208
|
-
const path = outputPath ??
|
|
35460
|
+
const path = outputPath ?? join18(process.cwd(), ".todos", "knowledge", `${slug || record.id.slice(0, 8)}.${format === "markdown" ? "md" : "json"}`);
|
|
34209
35461
|
mkdirSync17(dirname16(path), { recursive: true });
|
|
34210
35462
|
writeFileSync15(path, content, "utf8");
|
|
34211
35463
|
return { path, content };
|
|
@@ -34500,8 +35752,8 @@ todos report export --kind retrospective --days 14 --format markdown --out retro
|
|
|
34500
35752
|
`;
|
|
34501
35753
|
}
|
|
34502
35754
|
// src/lib/command-aliases.ts
|
|
34503
|
-
import { existsSync as
|
|
34504
|
-
import { join as
|
|
35755
|
+
import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
|
|
35756
|
+
import { join as join19 } from "path";
|
|
34505
35757
|
var COMMAND_ALIASES_SCHEMA = "todos.command_aliases.v1";
|
|
34506
35758
|
var RESERVED = new Set([...listTopLevelCommands(), "help", "version", "alias", "shortcuts"]);
|
|
34507
35759
|
var BUILTIN_SHORTCUTS = [
|
|
@@ -34520,7 +35772,7 @@ var BUILTIN_SHORTCUTS = [
|
|
|
34520
35772
|
{ pattern: /^reports?$/, argv: ["report", "docs"], explain: "Report export documentation" }
|
|
34521
35773
|
];
|
|
34522
35774
|
function aliasesPath(cwd = process.cwd()) {
|
|
34523
|
-
return
|
|
35775
|
+
return join19(cwd, ".todos", "aliases.json");
|
|
34524
35776
|
}
|
|
34525
35777
|
function emptyStore() {
|
|
34526
35778
|
return { schema_version: COMMAND_ALIASES_SCHEMA, aliases: {}, updated_at: new Date(0).toISOString() };
|
|
@@ -34537,7 +35789,7 @@ function validateAliasName(name) {
|
|
|
34537
35789
|
}
|
|
34538
35790
|
function loadAliasStore(cwd) {
|
|
34539
35791
|
const path = aliasesPath(cwd);
|
|
34540
|
-
if (!
|
|
35792
|
+
if (!existsSync21(path))
|
|
34541
35793
|
return emptyStore();
|
|
34542
35794
|
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
34543
35795
|
if (parsed.schema_version !== COMMAND_ALIASES_SCHEMA) {
|
|
@@ -34547,7 +35799,7 @@ function loadAliasStore(cwd) {
|
|
|
34547
35799
|
}
|
|
34548
35800
|
function saveAliasStore(store, cwd) {
|
|
34549
35801
|
const path = aliasesPath(cwd);
|
|
34550
|
-
mkdirSync19(
|
|
35802
|
+
mkdirSync19(join19(path, ".."), { recursive: true });
|
|
34551
35803
|
store.updated_at = new Date().toISOString();
|
|
34552
35804
|
writeFileSync17(path, JSON.stringify(store, null, 2), "utf8");
|
|
34553
35805
|
}
|
|
@@ -35195,18 +36447,18 @@ function createBranchWorkPlan(input, db) {
|
|
|
35195
36447
|
}
|
|
35196
36448
|
// src/lib/user-scaffolds.ts
|
|
35197
36449
|
init_database();
|
|
35198
|
-
import { existsSync as
|
|
35199
|
-
import { join as
|
|
36450
|
+
import { existsSync as existsSync22, readFileSync as readFileSync21, writeFileSync as writeFileSync18, mkdirSync as mkdirSync20 } from "fs";
|
|
36451
|
+
import { join as join20 } from "path";
|
|
35200
36452
|
var USER_SCAFFOLD_SCHEMA = "todos.user_scaffold.v1";
|
|
35201
36453
|
var SCAFFOLD_KINDS = ["task", "project", "plan", "checklist", "contract", "verification_policy"];
|
|
35202
36454
|
function storeDir(cwd = process.cwd()) {
|
|
35203
|
-
return
|
|
36455
|
+
return join20(cwd, ".todos", "scaffolds");
|
|
35204
36456
|
}
|
|
35205
36457
|
function storePath(cwd) {
|
|
35206
|
-
return
|
|
36458
|
+
return join20(storeDir(cwd), "store.json");
|
|
35207
36459
|
}
|
|
35208
36460
|
function versionsDir(cwd) {
|
|
35209
|
-
return
|
|
36461
|
+
return join20(storeDir(cwd), "versions");
|
|
35210
36462
|
}
|
|
35211
36463
|
function slugify5(name) {
|
|
35212
36464
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -35216,7 +36468,7 @@ function emptyStore2() {
|
|
|
35216
36468
|
}
|
|
35217
36469
|
function loadUserScaffoldStore(cwd) {
|
|
35218
36470
|
const path = storePath(cwd);
|
|
35219
|
-
if (!
|
|
36471
|
+
if (!existsSync22(path))
|
|
35220
36472
|
return emptyStore2();
|
|
35221
36473
|
const parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
35222
36474
|
if (parsed.schema_version !== USER_SCAFFOLD_SCHEMA) {
|
|
@@ -35231,7 +36483,7 @@ function saveUserScaffoldStore(store, cwd) {
|
|
|
35231
36483
|
}
|
|
35232
36484
|
function snapshotVersion(scaffold, cwd) {
|
|
35233
36485
|
mkdirSync20(versionsDir(cwd), { recursive: true });
|
|
35234
|
-
const path =
|
|
36486
|
+
const path = join20(versionsDir(cwd), `${scaffold.id}-v${scaffold.version}.json`);
|
|
35235
36487
|
writeFileSync18(path, JSON.stringify(scaffold, null, 2), "utf8");
|
|
35236
36488
|
}
|
|
35237
36489
|
function listUserScaffolds(kind, cwd) {
|
|
@@ -35478,7 +36730,7 @@ function listLinkedTemplates(db, cwd) {
|
|
|
35478
36730
|
// src/lib/agent-workflow-demo.ts
|
|
35479
36731
|
init_database();
|
|
35480
36732
|
import { mkdtempSync } from "fs";
|
|
35481
|
-
import { join as
|
|
36733
|
+
import { join as join21 } from "path";
|
|
35482
36734
|
import { tmpdir as tmpdir3 } from "os";
|
|
35483
36735
|
var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
|
|
35484
36736
|
var DEMO_DEFAULT_AGENT = "demoagent";
|
|
@@ -35494,7 +36746,7 @@ function setupEphemeralDemoDb(options = {}) {
|
|
|
35494
36746
|
if (options.db_path) {
|
|
35495
36747
|
db_path = options.db_path;
|
|
35496
36748
|
} else if (options.persist) {
|
|
35497
|
-
db_path =
|
|
36749
|
+
db_path = join21(mkdtempSync(join21(tmpdir3(), "todos-demo-")), "todos.db");
|
|
35498
36750
|
} else {
|
|
35499
36751
|
db_path = ":memory:";
|
|
35500
36752
|
}
|
|
@@ -37830,18 +39082,18 @@ function runSearchView(idOrName, db) {
|
|
|
37830
39082
|
return { ...runSavedSearch(view.filters, view.scope, d), view };
|
|
37831
39083
|
}
|
|
37832
39084
|
// src/lib/claude-tasks.ts
|
|
37833
|
-
import { existsSync as
|
|
37834
|
-
import { join as
|
|
39085
|
+
import { existsSync as existsSync23, readFileSync as readFileSync22, readdirSync as readdirSync4, writeFileSync as writeFileSync19 } from "fs";
|
|
39086
|
+
import { join as join22 } from "path";
|
|
37835
39087
|
init_config();
|
|
37836
39088
|
init_sync_utils();
|
|
37837
39089
|
function getTaskListDir(taskListId) {
|
|
37838
|
-
return
|
|
39090
|
+
return join22(HOME, ".claude", "tasks", taskListId);
|
|
37839
39091
|
}
|
|
37840
39092
|
function readClaudeTask(dir, filename) {
|
|
37841
|
-
return readJsonFile(
|
|
39093
|
+
return readJsonFile(join22(dir, filename));
|
|
37842
39094
|
}
|
|
37843
39095
|
function writeClaudeTask(dir, task2) {
|
|
37844
|
-
writeJsonFile(
|
|
39096
|
+
writeJsonFile(join22(dir, `${task2.id}.json`), task2);
|
|
37845
39097
|
}
|
|
37846
39098
|
function toClaudeStatus(status) {
|
|
37847
39099
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -37853,14 +39105,14 @@ function toSqliteStatus(status) {
|
|
|
37853
39105
|
return status;
|
|
37854
39106
|
}
|
|
37855
39107
|
function readPrefixCounter(dir) {
|
|
37856
|
-
const path =
|
|
37857
|
-
if (!
|
|
39108
|
+
const path = join22(dir, ".prefix-counter");
|
|
39109
|
+
if (!existsSync23(path))
|
|
37858
39110
|
return 0;
|
|
37859
39111
|
const val = parseInt(readFileSync22(path, "utf-8").trim(), 10);
|
|
37860
39112
|
return isNaN(val) ? 0 : val;
|
|
37861
39113
|
}
|
|
37862
39114
|
function writePrefixCounter(dir, value) {
|
|
37863
|
-
writeFileSync19(
|
|
39115
|
+
writeFileSync19(join22(dir, ".prefix-counter"), String(value));
|
|
37864
39116
|
}
|
|
37865
39117
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
37866
39118
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -37887,7 +39139,7 @@ function taskToClaudeTask(task2, claudeTaskId, existingMeta) {
|
|
|
37887
39139
|
}
|
|
37888
39140
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
37889
39141
|
const dir = getTaskListDir(taskListId);
|
|
37890
|
-
if (!
|
|
39142
|
+
if (!existsSync23(dir))
|
|
37891
39143
|
ensureDir2(dir);
|
|
37892
39144
|
const filter = {};
|
|
37893
39145
|
if (projectId)
|
|
@@ -37896,7 +39148,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
37896
39148
|
const existingByTodosId = new Map;
|
|
37897
39149
|
const files = listJsonFiles(dir);
|
|
37898
39150
|
for (const f of files) {
|
|
37899
|
-
const path =
|
|
39151
|
+
const path = join22(dir, f);
|
|
37900
39152
|
const ct = readClaudeTask(dir, f);
|
|
37901
39153
|
if (ct?.metadata?.["todos_id"]) {
|
|
37902
39154
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -37983,7 +39235,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
37983
39235
|
}
|
|
37984
39236
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
37985
39237
|
const dir = getTaskListDir(taskListId);
|
|
37986
|
-
if (!
|
|
39238
|
+
if (!existsSync23(dir)) {
|
|
37987
39239
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
37988
39240
|
}
|
|
37989
39241
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -38003,7 +39255,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
38003
39255
|
}
|
|
38004
39256
|
for (const f of files) {
|
|
38005
39257
|
try {
|
|
38006
|
-
const filePath =
|
|
39258
|
+
const filePath = join22(dir, f);
|
|
38007
39259
|
const ct = readClaudeTask(dir, f);
|
|
38008
39260
|
if (!ct)
|
|
38009
39261
|
continue;
|
|
@@ -38071,22 +39323,22 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
38071
39323
|
}
|
|
38072
39324
|
|
|
38073
39325
|
// src/lib/agent-tasks.ts
|
|
38074
|
-
import { existsSync as
|
|
38075
|
-
import { join as
|
|
39326
|
+
import { existsSync as existsSync24 } from "fs";
|
|
39327
|
+
import { join as join23 } from "path";
|
|
38076
39328
|
init_sync_utils();
|
|
38077
39329
|
init_config();
|
|
38078
39330
|
function agentBaseDir(agent) {
|
|
38079
39331
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
38080
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
39332
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join23(getTodosGlobalDir(), "agents");
|
|
38081
39333
|
}
|
|
38082
39334
|
function getTaskListDir2(agent, taskListId) {
|
|
38083
|
-
return
|
|
39335
|
+
return join23(agentBaseDir(agent), agent, taskListId);
|
|
38084
39336
|
}
|
|
38085
39337
|
function readAgentTask(dir, filename) {
|
|
38086
|
-
return readJsonFile(
|
|
39338
|
+
return readJsonFile(join23(dir, filename));
|
|
38087
39339
|
}
|
|
38088
39340
|
function writeAgentTask(dir, task2) {
|
|
38089
|
-
writeJsonFile(
|
|
39341
|
+
writeJsonFile(join23(dir, `${task2.id}.json`), task2);
|
|
38090
39342
|
}
|
|
38091
39343
|
function taskToAgentTask(task2, externalId, existingMeta) {
|
|
38092
39344
|
return {
|
|
@@ -38111,7 +39363,7 @@ function metadataKey(agent) {
|
|
|
38111
39363
|
}
|
|
38112
39364
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
38113
39365
|
const dir = getTaskListDir2(agent, taskListId);
|
|
38114
|
-
if (!
|
|
39366
|
+
if (!existsSync24(dir))
|
|
38115
39367
|
ensureDir2(dir);
|
|
38116
39368
|
const filter = {};
|
|
38117
39369
|
if (projectId)
|
|
@@ -38120,7 +39372,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
38120
39372
|
const existingByTodosId = new Map;
|
|
38121
39373
|
const files = listJsonFiles(dir);
|
|
38122
39374
|
for (const f of files) {
|
|
38123
|
-
const path =
|
|
39375
|
+
const path = join23(dir, f);
|
|
38124
39376
|
const at = readAgentTask(dir, f);
|
|
38125
39377
|
if (at?.metadata?.["todos_id"]) {
|
|
38126
39378
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -38194,7 +39446,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
38194
39446
|
}
|
|
38195
39447
|
function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
38196
39448
|
const dir = getTaskListDir2(agent, taskListId);
|
|
38197
|
-
if (!
|
|
39449
|
+
if (!existsSync24(dir)) {
|
|
38198
39450
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
38199
39451
|
}
|
|
38200
39452
|
const files = listJsonFiles(dir);
|
|
@@ -38213,7 +39465,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
38213
39465
|
}
|
|
38214
39466
|
for (const f of files) {
|
|
38215
39467
|
try {
|
|
38216
|
-
const filePath =
|
|
39468
|
+
const filePath = join23(dir, f);
|
|
38217
39469
|
const at = readAgentTask(dir, f);
|
|
38218
39470
|
if (!at)
|
|
38219
39471
|
continue;
|
|
@@ -38351,9 +39603,9 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
38351
39603
|
return { pushed, pulled, errors };
|
|
38352
39604
|
}
|
|
38353
39605
|
// src/lib/extract.ts
|
|
38354
|
-
import { existsSync as
|
|
38355
|
-
import { createHash as
|
|
38356
|
-
import { relative as relative6, resolve as resolve15, join as
|
|
39606
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
|
|
39607
|
+
import { createHash as createHash14 } from "crypto";
|
|
39608
|
+
import { relative as relative6, resolve as resolve15, join as join24 } from "path";
|
|
38357
39609
|
var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
|
|
38358
39610
|
var DEFAULT_EXTENSIONS = new Set([
|
|
38359
39611
|
".ts",
|
|
@@ -38417,15 +39669,15 @@ var SKIP_DIRS2 = new Set([
|
|
|
38417
39669
|
".parcel-cache"
|
|
38418
39670
|
]);
|
|
38419
39671
|
function stableHash(value) {
|
|
38420
|
-
return
|
|
39672
|
+
return createHash14("sha256").update(value).digest("hex");
|
|
38421
39673
|
}
|
|
38422
39674
|
function normalizePathForMatch(value) {
|
|
38423
39675
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
38424
39676
|
}
|
|
38425
39677
|
function readGitignorePatterns(basePath) {
|
|
38426
39678
|
const root = statSync8(basePath).isFile() ? resolve15(basePath, "..") : basePath;
|
|
38427
|
-
const gitignorePath =
|
|
38428
|
-
if (!
|
|
39679
|
+
const gitignorePath = join24(root, ".gitignore");
|
|
39680
|
+
if (!existsSync25(gitignorePath))
|
|
38429
39681
|
return [];
|
|
38430
39682
|
try {
|
|
38431
39683
|
return readFileSync23(gitignorePath, "utf-8").split(`
|
|
@@ -38567,7 +39819,7 @@ function buildCodebaseIndex(options) {
|
|
|
38567
39819
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
38568
39820
|
const indexed = [];
|
|
38569
39821
|
for (const file of files) {
|
|
38570
|
-
const fullPath = statSync8(basePath).isFile() ? basePath :
|
|
39822
|
+
const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
|
|
38571
39823
|
try {
|
|
38572
39824
|
const source9 = readFileSync23(fullPath, "utf-8");
|
|
38573
39825
|
const relPath = statSync8(basePath).isFile() ? relative6(resolve15(basePath, ".."), fullPath) : file;
|
|
@@ -38598,7 +39850,7 @@ function extractTodos(options, db) {
|
|
|
38598
39850
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
38599
39851
|
const allComments = [];
|
|
38600
39852
|
for (const file of files) {
|
|
38601
|
-
const fullPath = statSync8(basePath).isFile() ? basePath :
|
|
39853
|
+
const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
|
|
38602
39854
|
try {
|
|
38603
39855
|
const source9 = readFileSync23(fullPath, "utf-8");
|
|
38604
39856
|
const relPath = statSync8(basePath).isFile() ? relative6(resolve15(basePath, ".."), fullPath) : file;
|
|
@@ -38633,7 +39885,7 @@ function extractTodos(options, db) {
|
|
|
38633
39885
|
skipped++;
|
|
38634
39886
|
continue;
|
|
38635
39887
|
}
|
|
38636
|
-
const
|
|
39888
|
+
const taskTags2 = ["extracted", comment.tag.toLowerCase(), ...options.tags || []];
|
|
38637
39889
|
const task2 = createTask({
|
|
38638
39890
|
title: `[${comment.tag}] ${comment.message}`,
|
|
38639
39891
|
description: `Extracted from code comment in \`${comment.file}\` at line ${comment.line}:
|
|
@@ -38645,7 +39897,7 @@ ${comment.raw.trim()}
|
|
|
38645
39897
|
task_list_id: options.task_list_id,
|
|
38646
39898
|
assigned_to: options.assigned_to,
|
|
38647
39899
|
agent_id: options.agent_id,
|
|
38648
|
-
tags:
|
|
39900
|
+
tags: taskTags2,
|
|
38649
39901
|
metadata: {
|
|
38650
39902
|
source: "code_comment",
|
|
38651
39903
|
comment_type: comment.tag,
|
|
@@ -38906,7 +40158,7 @@ init_config();
|
|
|
38906
40158
|
// src/lib/natural-language-intake.ts
|
|
38907
40159
|
init_database();
|
|
38908
40160
|
init_redaction();
|
|
38909
|
-
var
|
|
40161
|
+
var PRIORITIES4 = new Set(["low", "medium", "high", "critical"]);
|
|
38910
40162
|
function compact2(value) {
|
|
38911
40163
|
return value.replace(/\s+/g, " ").trim();
|
|
38912
40164
|
}
|
|
@@ -38918,7 +40170,7 @@ function splitItems(text) {
|
|
|
38918
40170
|
}
|
|
38919
40171
|
function parsePriority(line, fallback) {
|
|
38920
40172
|
const explicit = line.match(/\b(?:priority[:= ]+|p:)(critical|high|medium|low)\b/i)?.[1]?.toLowerCase();
|
|
38921
|
-
if (explicit &&
|
|
40173
|
+
if (explicit && PRIORITIES4.has(explicit))
|
|
38922
40174
|
return explicit;
|
|
38923
40175
|
if (/\b(p0|urgent|blocker|critical)\b/i.test(line))
|
|
38924
40176
|
return "critical";
|
|
@@ -39305,7 +40557,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
|
|
|
39305
40557
|
}
|
|
39306
40558
|
// src/lib/agent-replay-simulator.ts
|
|
39307
40559
|
init_redaction();
|
|
39308
|
-
import { createHash as
|
|
40560
|
+
import { createHash as createHash15 } from "crypto";
|
|
39309
40561
|
import { readFileSync as readFileSync24 } from "fs";
|
|
39310
40562
|
function isObject(value) {
|
|
39311
40563
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -39327,7 +40579,7 @@ function stable2(value) {
|
|
|
39327
40579
|
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
|
|
39328
40580
|
}
|
|
39329
40581
|
function fingerprint3(value) {
|
|
39330
|
-
return
|
|
40582
|
+
return createHash15("sha256").update(JSON.stringify(stable2(value))).digest("hex");
|
|
39331
40583
|
}
|
|
39332
40584
|
function unpackFixture(input) {
|
|
39333
40585
|
if (!isObject(input))
|
|
@@ -39566,9 +40818,9 @@ function renderAgentReplaySimulationMarkdown(simulation) {
|
|
|
39566
40818
|
}
|
|
39567
40819
|
// src/lib/local-extensions.ts
|
|
39568
40820
|
init_config();
|
|
39569
|
-
import { createHash as
|
|
39570
|
-
import { existsSync as
|
|
39571
|
-
import { basename as basename5, join as
|
|
40821
|
+
import { createHash as createHash16, createVerify } from "crypto";
|
|
40822
|
+
import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
|
|
40823
|
+
import { basename as basename5, join as join25, resolve as resolve16 } from "path";
|
|
39572
40824
|
init_redaction();
|
|
39573
40825
|
function isObject2(value) {
|
|
39574
40826
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -39653,7 +40905,7 @@ function parseJson(path) {
|
|
|
39653
40905
|
return JSON.parse(readFileSync25(path, "utf8"));
|
|
39654
40906
|
}
|
|
39655
40907
|
function sha2566(bytes) {
|
|
39656
|
-
return `sha256:${
|
|
40908
|
+
return `sha256:${createHash16("sha256").update(bytes).digest("hex")}`;
|
|
39657
40909
|
}
|
|
39658
40910
|
function compareVersions(a, b) {
|
|
39659
40911
|
const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -39849,10 +41101,10 @@ function verifyExtensionSignature(input) {
|
|
|
39849
41101
|
}
|
|
39850
41102
|
function inspectExtensionSource(source9) {
|
|
39851
41103
|
const resolved = resolve16(source9);
|
|
39852
|
-
if (!
|
|
41104
|
+
if (!existsSync26(resolved))
|
|
39853
41105
|
throw new Error(`extension source not found: ${source9}`);
|
|
39854
41106
|
const stat = statSync9(resolved);
|
|
39855
|
-
const manifestPath = stat.isDirectory() ? [
|
|
41107
|
+
const manifestPath = stat.isDirectory() ? [join25(resolved, "todos.extension.json"), join25(resolved, "extension.json")].find(existsSync26) : resolved;
|
|
39856
41108
|
if (!manifestPath)
|
|
39857
41109
|
throw new Error(`extension directory ${source9} is missing todos.extension.json`);
|
|
39858
41110
|
const raw = readFileSync25(manifestPath);
|
|
@@ -39948,20 +41200,20 @@ function projectExtensionSources(projectPath) {
|
|
|
39948
41200
|
return [];
|
|
39949
41201
|
const root = resolve16(projectPath);
|
|
39950
41202
|
const candidates = [
|
|
39951
|
-
|
|
39952
|
-
|
|
41203
|
+
join25(root, "todos.extension.json"),
|
|
41204
|
+
join25(root, ".todos", "todos.extension.json")
|
|
39953
41205
|
];
|
|
39954
|
-
const extensionDir =
|
|
39955
|
-
if (
|
|
41206
|
+
const extensionDir = join25(root, ".todos", "extensions");
|
|
41207
|
+
if (existsSync26(extensionDir)) {
|
|
39956
41208
|
for (const entry2 of readdirSync5(extensionDir)) {
|
|
39957
41209
|
if (entry2.startsWith("."))
|
|
39958
41210
|
continue;
|
|
39959
|
-
const full =
|
|
41211
|
+
const full = join25(extensionDir, entry2);
|
|
39960
41212
|
if (statSync9(full).isDirectory() || entry2.endsWith(".json"))
|
|
39961
41213
|
candidates.push(full);
|
|
39962
41214
|
}
|
|
39963
41215
|
}
|
|
39964
|
-
return candidates.filter(
|
|
41216
|
+
return candidates.filter(existsSync26);
|
|
39965
41217
|
}
|
|
39966
41218
|
function discoverLocalExtensions(options = {}) {
|
|
39967
41219
|
const config = loadConfig();
|
|
@@ -40248,6 +41500,18 @@ function formatDispatchMessage(tasks, opts = {}) {
|
|
|
40248
41500
|
// src/lib/tmux.ts
|
|
40249
41501
|
var DELAY_MIN = 3000;
|
|
40250
41502
|
var DELAY_MAX = 5000;
|
|
41503
|
+
var IDLE_TMUX_COMMANDS = new Set([
|
|
41504
|
+
"bash",
|
|
41505
|
+
"dash",
|
|
41506
|
+
"elvish",
|
|
41507
|
+
"fish",
|
|
41508
|
+
"ksh",
|
|
41509
|
+
"nu",
|
|
41510
|
+
"pwsh",
|
|
41511
|
+
"sh",
|
|
41512
|
+
"tmux",
|
|
41513
|
+
"zsh"
|
|
41514
|
+
]);
|
|
40251
41515
|
function parseTmuxTarget(spec) {
|
|
40252
41516
|
if (!spec || spec.trim() === "") {
|
|
40253
41517
|
throw new Error("tmux target spec cannot be empty");
|
|
@@ -40304,17 +41568,73 @@ async function validateTmuxTarget(spec) {
|
|
|
40304
41568
|
throw new Error(`tmux target "${targetStr}" not found: ${stderr.trim() || "unknown error"}`);
|
|
40305
41569
|
}
|
|
40306
41570
|
}
|
|
41571
|
+
async function inspectTmuxPane(target) {
|
|
41572
|
+
const proc = Bun.spawn([
|
|
41573
|
+
"tmux",
|
|
41574
|
+
"display-message",
|
|
41575
|
+
"-p",
|
|
41576
|
+
"-t",
|
|
41577
|
+
target,
|
|
41578
|
+
"#{pane_id}\t#{pane_current_command}\t#{pane_dead}\t#{pane_input_off}\t#{pane_in_mode}"
|
|
41579
|
+
], {
|
|
41580
|
+
stdout: "pipe",
|
|
41581
|
+
stderr: "pipe"
|
|
41582
|
+
});
|
|
41583
|
+
const exitCode = await proc.exited;
|
|
41584
|
+
if (exitCode !== 0) {
|
|
41585
|
+
const stderr = await new Response(proc.stderr).text();
|
|
41586
|
+
throw new Error(`tmux target "${target}" not found: ${stderr.trim() || "unknown error"}`);
|
|
41587
|
+
}
|
|
41588
|
+
const stdout = (await new Response(proc.stdout).text()).trim();
|
|
41589
|
+
const [paneId, currentCommand = "", paneDead = "0", inputOff = "0", inMode = "0"] = stdout.split("\t");
|
|
41590
|
+
if (!paneId) {
|
|
41591
|
+
throw new Error(`tmux target "${target}" did not resolve to a pane`);
|
|
41592
|
+
}
|
|
41593
|
+
return {
|
|
41594
|
+
target,
|
|
41595
|
+
paneId,
|
|
41596
|
+
currentCommand,
|
|
41597
|
+
paneDead: paneDead === "1",
|
|
41598
|
+
inputOff: inputOff === "1",
|
|
41599
|
+
inMode: inMode === "1"
|
|
41600
|
+
};
|
|
41601
|
+
}
|
|
41602
|
+
function tmuxPaneBusyStatus(pane) {
|
|
41603
|
+
if (pane.paneDead) {
|
|
41604
|
+
return { busy: true, reason: "pane is dead" };
|
|
41605
|
+
}
|
|
41606
|
+
if (pane.inputOff) {
|
|
41607
|
+
return { busy: true, reason: "pane input is disabled" };
|
|
41608
|
+
}
|
|
41609
|
+
if (pane.inMode) {
|
|
41610
|
+
return { busy: true, reason: "pane is in copy or alternate mode" };
|
|
41611
|
+
}
|
|
41612
|
+
const currentCommand = pane.currentCommand.trim();
|
|
41613
|
+
if (currentCommand && !IDLE_TMUX_COMMANDS.has(currentCommand)) {
|
|
41614
|
+
return { busy: true, reason: `pane is running ${currentCommand}` };
|
|
41615
|
+
}
|
|
41616
|
+
return { busy: false, reason: null };
|
|
41617
|
+
}
|
|
40307
41618
|
function calculateDelay(message) {
|
|
40308
41619
|
const len = message.length;
|
|
40309
41620
|
const extra = Math.floor(len / 100 * 40);
|
|
40310
41621
|
return Math.min(DELAY_MIN + extra, DELAY_MAX);
|
|
40311
41622
|
}
|
|
40312
|
-
async function sendToTmux(target, message, delayMs,
|
|
41623
|
+
async function sendToTmux(target, message, delayMs, options = false) {
|
|
41624
|
+
const opts = typeof options === "boolean" ? { dryRun: options } : options;
|
|
41625
|
+
const dryRun = opts.dryRun ?? false;
|
|
40313
41626
|
if (dryRun) {
|
|
40314
41627
|
console.log(`[dry-run] sendToTmux target=${target} delay=${delayMs}ms`);
|
|
40315
41628
|
console.log(`[dry-run] message: ${message.slice(0, 200)}`);
|
|
40316
41629
|
return;
|
|
40317
41630
|
}
|
|
41631
|
+
if (!opts.confirmBusy) {
|
|
41632
|
+
const pane = await inspectTmuxPane(target);
|
|
41633
|
+
const status = tmuxPaneBusyStatus(pane);
|
|
41634
|
+
if (status.busy) {
|
|
41635
|
+
throw new Error(`tmux target "${target}" appears busy (${status.reason}). Re-run with --confirm-busy to send anyway.`);
|
|
41636
|
+
}
|
|
41637
|
+
}
|
|
40318
41638
|
const sendProc = Bun.spawn(["tmux", "send-keys", "-t", target, message, ""], {
|
|
40319
41639
|
stdout: "pipe",
|
|
40320
41640
|
stderr: "pipe"
|
|
@@ -40346,7 +41666,10 @@ async function executeDispatch(dispatch, opts = {}, db) {
|
|
|
40346
41666
|
}
|
|
40347
41667
|
const delayMs = dispatch.delay_ms ?? calculateDelay(message);
|
|
40348
41668
|
try {
|
|
40349
|
-
await sendToTmux(dispatch.target_window, message, delayMs,
|
|
41669
|
+
await sendToTmux(dispatch.target_window, message, delayMs, {
|
|
41670
|
+
dryRun: opts.dryRun ?? false,
|
|
41671
|
+
confirmBusy: opts.confirmBusy ?? false
|
|
41672
|
+
});
|
|
40350
41673
|
createDispatchLog({
|
|
40351
41674
|
dispatch_id: dispatch.id,
|
|
40352
41675
|
target_window: dispatch.target_window,
|
|
@@ -40404,7 +41727,7 @@ async function dispatchToMultiple(input, opts = {}, db) {
|
|
|
40404
41727
|
init_redaction();
|
|
40405
41728
|
|
|
40406
41729
|
// src/lib/retention-cleanup.ts
|
|
40407
|
-
import { existsSync as
|
|
41730
|
+
import { existsSync as existsSync27, unlinkSync as unlinkSync2 } from "fs";
|
|
40408
41731
|
init_database();
|
|
40409
41732
|
var RETENTION_CLEANUP_CONFIRMATION = "delete-local-retention-data";
|
|
40410
41733
|
var ALL_SCOPES = ["comments", "runs", "verifications", "expired_artifacts"];
|
|
@@ -40616,7 +41939,7 @@ function applyRetentionCleanup(input, db) {
|
|
|
40616
41939
|
for (const artifact of report.candidates.artifact_files) {
|
|
40617
41940
|
try {
|
|
40618
41941
|
const path = artifactStorePath(artifact.relative_path);
|
|
40619
|
-
if (!
|
|
41942
|
+
if (!existsSync27(path)) {
|
|
40620
41943
|
report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
|
|
40621
41944
|
continue;
|
|
40622
41945
|
}
|
|
@@ -40854,8 +42177,8 @@ function renderScalePerformanceReportMarkdown(report) {
|
|
|
40854
42177
|
init_database();
|
|
40855
42178
|
init_migrations();
|
|
40856
42179
|
init_schema();
|
|
40857
|
-
import { chmodSync, copyFileSync as copyFileSync2, existsSync as
|
|
40858
|
-
import { basename as basename6, dirname as dirname18, join as
|
|
42180
|
+
import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync21, statSync as statSync10 } from "fs";
|
|
42181
|
+
import { basename as basename6, dirname as dirname18, join as join26 } from "path";
|
|
40859
42182
|
var REQUIRED_TABLES2 = [
|
|
40860
42183
|
"_migrations",
|
|
40861
42184
|
"projects",
|
|
@@ -40962,7 +42285,7 @@ function findMissingProjectRoots(db) {
|
|
|
40962
42285
|
continue;
|
|
40963
42286
|
if (!row.path.startsWith("/"))
|
|
40964
42287
|
continue;
|
|
40965
|
-
if (!
|
|
42288
|
+
if (!existsSync28(row.path))
|
|
40966
42289
|
missing++;
|
|
40967
42290
|
}
|
|
40968
42291
|
return missing;
|
|
@@ -41022,16 +42345,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
41022
42345
|
function createBackup(dbPath) {
|
|
41023
42346
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
41024
42347
|
return;
|
|
41025
|
-
if (!
|
|
42348
|
+
if (!existsSync28(dbPath))
|
|
41026
42349
|
return;
|
|
41027
42350
|
const stamp = now().replace(/[:.]/g, "-");
|
|
41028
|
-
const backupDir =
|
|
42351
|
+
const backupDir = join26(dirname18(dbPath), `${basename6(dbPath)}.backup-${stamp}`);
|
|
41029
42352
|
const files = [];
|
|
41030
42353
|
mkdirSync21(backupDir, { recursive: true });
|
|
41031
42354
|
for (const source9 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
41032
|
-
if (!
|
|
42355
|
+
if (!existsSync28(source9))
|
|
41033
42356
|
continue;
|
|
41034
|
-
const target =
|
|
42357
|
+
const target = join26(backupDir, basename6(source9));
|
|
41035
42358
|
copyFileSync2(source9, target);
|
|
41036
42359
|
files.push(target);
|
|
41037
42360
|
}
|
|
@@ -41320,7 +42643,7 @@ function upsertCapacityProfile(input) {
|
|
|
41320
42643
|
const store = readStore2();
|
|
41321
42644
|
const key = profileKey(agentId, projectId);
|
|
41322
42645
|
const existing = store.profiles[key];
|
|
41323
|
-
const
|
|
42646
|
+
const now4 = timestamp2();
|
|
41324
42647
|
const profile = {
|
|
41325
42648
|
id: existing?.id ?? key,
|
|
41326
42649
|
agent_id: agentId,
|
|
@@ -41328,8 +42651,8 @@ function upsertCapacityProfile(input) {
|
|
|
41328
42651
|
minutes_per_day: assertMinutes(input.minutes_per_day),
|
|
41329
42652
|
working_days: normalizeWorkingDays(input.working_days),
|
|
41330
42653
|
effective_from: cleanString2(input.effective_from),
|
|
41331
|
-
created_at: existing?.created_at ??
|
|
41332
|
-
updated_at:
|
|
42654
|
+
created_at: existing?.created_at ?? now4,
|
|
42655
|
+
updated_at: now4
|
|
41333
42656
|
};
|
|
41334
42657
|
store.profiles[key] = profile;
|
|
41335
42658
|
writeStore2(store);
|
|
@@ -41726,6 +43049,8 @@ export {
|
|
|
41726
43049
|
uuid,
|
|
41727
43050
|
upsertWorkspaceTrustProfile,
|
|
41728
43051
|
upsertVerificationProvider,
|
|
43052
|
+
upsertTesterIssueReports,
|
|
43053
|
+
upsertTesterIssueReport,
|
|
41729
43054
|
upsertTerminalNotificationRule,
|
|
41730
43055
|
upsertSecretSafetyConfig,
|
|
41731
43056
|
upsertRunnerSandboxProfile,
|
|
@@ -41937,6 +43262,7 @@ export {
|
|
|
41937
43262
|
recordTaskReview,
|
|
41938
43263
|
recordFilesTouched,
|
|
41939
43264
|
recordEnvironmentSnapshot,
|
|
43265
|
+
readTesterIssueReportsPayload,
|
|
41940
43266
|
readLocalBackupFile,
|
|
41941
43267
|
readEnvironmentSnapshot,
|
|
41942
43268
|
readBundleFile,
|
|
@@ -41975,6 +43301,7 @@ export {
|
|
|
41975
43301
|
parseCiLog,
|
|
41976
43302
|
now,
|
|
41977
43303
|
notifyUpcomingDeadlines2 as notifyUpcomingDeadlines,
|
|
43304
|
+
normalizeTesterIssueReport,
|
|
41978
43305
|
normalizeScope,
|
|
41979
43306
|
normalizeGeneratedAgentNames,
|
|
41980
43307
|
normalizeFeatureManifest,
|
|
@@ -42363,6 +43690,7 @@ export {
|
|
|
42363
43690
|
formatAgentWorkflowDemoReport,
|
|
42364
43691
|
formatActivityRecordText,
|
|
42365
43692
|
finishTaskRun,
|
|
43693
|
+
fingerprintTesterIssueReport,
|
|
42366
43694
|
fingerprintInboxInput,
|
|
42367
43695
|
findTasksByFile,
|
|
42368
43696
|
findTaskByCommit,
|
|
@@ -42422,7 +43750,7 @@ export {
|
|
|
42422
43750
|
emitLocalEventHooksQuiet,
|
|
42423
43751
|
emitLocalEventHooks,
|
|
42424
43752
|
downloadRunArtifactsFromS3,
|
|
42425
|
-
dispatchWebhook,
|
|
43753
|
+
dispatchWebhook2 as dispatchWebhook,
|
|
42426
43754
|
dispatchToMultiple,
|
|
42427
43755
|
dismissReminder,
|
|
42428
43756
|
discoverVerificationProviderCapabilities,
|
|
@@ -42665,6 +43993,9 @@ export {
|
|
|
42665
43993
|
TODOS_CLI_MCP_PARITY,
|
|
42666
43994
|
TODOS_CAPABILITIES,
|
|
42667
43995
|
TODOS_API_ROUTES,
|
|
43996
|
+
TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
|
|
43997
|
+
TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
|
|
43998
|
+
TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION,
|
|
42668
43999
|
TASK_STATUSES,
|
|
42669
44000
|
TASK_SCHEDULING_SCHEMA,
|
|
42670
44001
|
TASK_PRIORITIES,
|