@hasna/todos 0.11.56 → 0.11.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 existsSync6 } from "fs";
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 (!existsSync6(path))
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 join6 } from "path";
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,752 @@ async function testLocalEventHook(name, input) {
6816
6878
  return emitLocalEventHooks({ ...input, hooks: [hook] });
6817
6879
  }
6818
6880
 
6881
+ // node_modules/.bun/@hasna+events@0.1.9/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, options = {}) {
6899
+ let body = "";
6900
+ for (let index = 0;index < pattern.length; index += 1) {
6901
+ const char = pattern[index];
6902
+ if (char === "*") {
6903
+ if (pattern[index + 1] === "*") {
6904
+ body += ".*";
6905
+ index += 1;
6906
+ } else {
6907
+ body += options.segmentSafe ? "[^/]*" : ".*";
6908
+ }
6909
+ } else {
6910
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
6911
+ }
6912
+ }
6913
+ return new RegExp(`^${body}$`);
6914
+ }
6915
+ function matchString(value, matcher, options = {}) {
6916
+ if (matcher === undefined)
6917
+ return true;
6918
+ if (value === undefined)
6919
+ return false;
6920
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
6921
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
6922
+ }
6923
+ function matchRecord(input, matcher) {
6924
+ if (!matcher)
6925
+ return true;
6926
+ return Object.entries(matcher).every(([path, expected]) => {
6927
+ const actual = getPathValue(input, path);
6928
+ if (typeof expected === "string" || Array.isArray(expected)) {
6929
+ return matchString(actual === undefined ? undefined : String(actual), expected, {
6930
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
6931
+ });
6932
+ }
6933
+ return actual === expected;
6934
+ });
6935
+ }
6936
+ function eventMatchesFilter(event, filter) {
6937
+ 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);
6938
+ }
6939
+ function channelMatchesEvent(channel, event) {
6940
+ if (!channel.enabled)
6941
+ return false;
6942
+ if (!channel.filters || channel.filters.length === 0)
6943
+ return true;
6944
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
6945
+ }
6946
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
6947
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
6948
+ function getEventsDataDir(override) {
6949
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
6950
+ }
6951
+ class JsonEventsStore {
6952
+ dataDir;
6953
+ channelsPath;
6954
+ eventsPath;
6955
+ deliveriesPath;
6956
+ constructor(dataDir = getEventsDataDir()) {
6957
+ this.dataDir = dataDir;
6958
+ this.channelsPath = join5(dataDir, "channels.json");
6959
+ this.eventsPath = join5(dataDir, "events.json");
6960
+ this.deliveriesPath = join5(dataDir, "deliveries.json");
6961
+ }
6962
+ async init() {
6963
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
6964
+ await chmod(this.dataDir, 448).catch(() => {
6965
+ return;
6966
+ });
6967
+ await this.ensureArrayFile(this.channelsPath);
6968
+ await this.ensureArrayFile(this.eventsPath);
6969
+ await this.ensureArrayFile(this.deliveriesPath);
6970
+ }
6971
+ async addChannel(channel) {
6972
+ await this.init();
6973
+ const channels = await this.readJson(this.channelsPath, []);
6974
+ const index = channels.findIndex((item) => item.id === channel.id);
6975
+ if (index >= 0) {
6976
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
6977
+ } else {
6978
+ channels.push(channel);
6979
+ }
6980
+ await this.writeJson(this.channelsPath, channels);
6981
+ return index >= 0 ? channels[index] : channel;
6982
+ }
6983
+ async listChannels() {
6984
+ await this.init();
6985
+ return this.readJson(this.channelsPath, []);
6986
+ }
6987
+ async getChannel(id) {
6988
+ const channels = await this.listChannels();
6989
+ return channels.find((channel) => channel.id === id);
6990
+ }
6991
+ async removeChannel(id) {
6992
+ await this.init();
6993
+ const channels = await this.readJson(this.channelsPath, []);
6994
+ const next = channels.filter((channel) => channel.id !== id);
6995
+ await this.writeJson(this.channelsPath, next);
6996
+ return next.length !== channels.length;
6997
+ }
6998
+ async appendEvent(event) {
6999
+ await this.init();
7000
+ const events = await this.readJson(this.eventsPath, []);
7001
+ events.push(event);
7002
+ await this.writeJson(this.eventsPath, events);
7003
+ return event;
7004
+ }
7005
+ async listEvents() {
7006
+ await this.init();
7007
+ return this.readJson(this.eventsPath, []);
7008
+ }
7009
+ async findEventByIdentity(identity) {
7010
+ const events = await this.listEvents();
7011
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
7012
+ }
7013
+ async appendDelivery(result) {
7014
+ await this.init();
7015
+ const deliveries = await this.readJson(this.deliveriesPath, []);
7016
+ deliveries.push(result);
7017
+ await this.writeJson(this.deliveriesPath, deliveries);
7018
+ return result;
7019
+ }
7020
+ async listDeliveries() {
7021
+ await this.init();
7022
+ return this.readJson(this.deliveriesPath, []);
7023
+ }
7024
+ async exportData() {
7025
+ return {
7026
+ channels: await this.listChannels(),
7027
+ events: await this.listEvents(),
7028
+ deliveries: await this.listDeliveries()
7029
+ };
7030
+ }
7031
+ async ensureArrayFile(path) {
7032
+ if (!existsSync6(path)) {
7033
+ await writeFile(path, `[]
7034
+ `, { encoding: "utf-8", mode: 384 });
7035
+ }
7036
+ await chmod(path, 384).catch(() => {
7037
+ return;
7038
+ });
7039
+ }
7040
+ async readJson(path, fallback) {
7041
+ try {
7042
+ const raw = await readFile(path, "utf-8");
7043
+ if (!raw.trim())
7044
+ return fallback;
7045
+ return JSON.parse(raw);
7046
+ } catch (error) {
7047
+ if (error.code === "ENOENT")
7048
+ return fallback;
7049
+ throw error;
7050
+ }
7051
+ }
7052
+ async writeJson(path, value) {
7053
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
7054
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
7055
+ `, { encoding: "utf-8", mode: 384 });
7056
+ await rename(tempPath, path);
7057
+ await chmod(path, 384).catch(() => {
7058
+ return;
7059
+ });
7060
+ }
7061
+ }
7062
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
7063
+ function buildSignatureBase(timestamp, body) {
7064
+ return `${timestamp}.${body}`;
7065
+ }
7066
+ function signPayload(secret, timestamp, body) {
7067
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
7068
+ return `sha256=${digest}`;
7069
+ }
7070
+ function now2() {
7071
+ return new Date().toISOString();
7072
+ }
7073
+ function truncate(value, max = 4096) {
7074
+ return value.length > max ? `${value.slice(0, max)}...` : value;
7075
+ }
7076
+ function buildWebhookRequest(event, channel) {
7077
+ if (!channel.webhook)
7078
+ throw new Error(`Channel ${channel.id} has no webhook config`);
7079
+ const body = JSON.stringify(event);
7080
+ const timestamp = event.time;
7081
+ const headers = {
7082
+ "Content-Type": "application/json",
7083
+ "User-Agent": "@hasna/events",
7084
+ "X-Hasna-Event-Id": event.id,
7085
+ "X-Hasna-Event-Type": event.type,
7086
+ "X-Hasna-Timestamp": timestamp,
7087
+ ...channel.webhook.headers
7088
+ };
7089
+ if (channel.webhook.secret) {
7090
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
7091
+ }
7092
+ return { body, headers };
7093
+ }
7094
+ async function dispatchWebhook(event, channel, options = {}) {
7095
+ if (!channel.webhook)
7096
+ throw new Error(`Channel ${channel.id} has no webhook config`);
7097
+ const startedAt = now2();
7098
+ const { body, headers } = buildWebhookRequest(event, channel);
7099
+ const controller = new AbortController;
7100
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
7101
+ try {
7102
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
7103
+ method: "POST",
7104
+ headers,
7105
+ body,
7106
+ signal: controller.signal
7107
+ });
7108
+ const responseBody = truncate(await response.text());
7109
+ return {
7110
+ attempt: 1,
7111
+ status: response.ok ? "success" : "failed",
7112
+ startedAt,
7113
+ completedAt: now2(),
7114
+ responseStatus: response.status,
7115
+ responseBody,
7116
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
7117
+ };
7118
+ } catch (error) {
7119
+ return {
7120
+ attempt: 1,
7121
+ status: "failed",
7122
+ startedAt,
7123
+ completedAt: now2(),
7124
+ error: error instanceof Error ? error.message : String(error)
7125
+ };
7126
+ } finally {
7127
+ clearTimeout(timeout);
7128
+ }
7129
+ }
7130
+ async function dispatchCommand(event, channel) {
7131
+ if (!channel.command)
7132
+ throw new Error(`Channel ${channel.id} has no command config`);
7133
+ const startedAt = now2();
7134
+ const eventJson = JSON.stringify(event);
7135
+ const env = {
7136
+ ...process.env,
7137
+ ...channel.command.env,
7138
+ HASNA_CHANNEL_ID: channel.id,
7139
+ HASNA_EVENT_ID: event.id,
7140
+ HASNA_EVENT_TYPE: event.type,
7141
+ HASNA_EVENT_SOURCE: event.source,
7142
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
7143
+ HASNA_EVENT_SEVERITY: event.severity,
7144
+ HASNA_EVENT_TIME: event.time,
7145
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
7146
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
7147
+ HASNA_EVENT_JSON: eventJson
7148
+ };
7149
+ return new Promise((resolve6) => {
7150
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
7151
+ cwd: channel.command.cwd,
7152
+ env,
7153
+ stdio: ["pipe", "pipe", "pipe"]
7154
+ });
7155
+ let stdout = "";
7156
+ let stderr = "";
7157
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
7158
+ child.stdin.end(eventJson);
7159
+ child.stdout.on("data", (chunk) => {
7160
+ stdout += chunk.toString();
7161
+ });
7162
+ child.stderr.on("data", (chunk) => {
7163
+ stderr += chunk.toString();
7164
+ });
7165
+ child.on("error", (error) => {
7166
+ clearTimeout(timeout);
7167
+ resolve6({
7168
+ attempt: 1,
7169
+ status: "failed",
7170
+ startedAt,
7171
+ completedAt: now2(),
7172
+ stdout: truncate(stdout),
7173
+ stderr: truncate(stderr),
7174
+ error: error.message
7175
+ });
7176
+ });
7177
+ child.on("close", (code, signal) => {
7178
+ clearTimeout(timeout);
7179
+ const success = code === 0;
7180
+ resolve6({
7181
+ attempt: 1,
7182
+ status: success ? "success" : "failed",
7183
+ startedAt,
7184
+ completedAt: now2(),
7185
+ stdout: truncate(stdout),
7186
+ stderr: truncate(stderr),
7187
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
7188
+ });
7189
+ });
7190
+ });
7191
+ }
7192
+ async function dispatchChannel(event, channel, options = {}) {
7193
+ if (channel.transport === "webhook")
7194
+ return dispatchWebhook(event, channel, options);
7195
+ if (channel.transport === "command")
7196
+ return dispatchCommand(event, channel);
7197
+ return {
7198
+ attempt: 1,
7199
+ status: "skipped",
7200
+ startedAt: now2(),
7201
+ completedAt: now2(),
7202
+ error: `Unsupported transport: ${channel.transport}`
7203
+ };
7204
+ }
7205
+ function createDeliveryResult(event, channel, attempts) {
7206
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
7207
+ return {
7208
+ id: randomUUID2(),
7209
+ eventId: event.id,
7210
+ channelId: channel.id,
7211
+ transport: channel.transport,
7212
+ status,
7213
+ attempts,
7214
+ createdAt: attempts[0]?.startedAt ?? now2(),
7215
+ completedAt: attempts.at(-1)?.completedAt ?? now2()
7216
+ };
7217
+ }
7218
+ function createEvent(input) {
7219
+ return {
7220
+ id: input.id ?? randomUUID22(),
7221
+ source: input.source,
7222
+ type: input.type,
7223
+ time: normalizeTime(input.time),
7224
+ subject: input.subject,
7225
+ severity: input.severity ?? "info",
7226
+ data: input.data ?? {},
7227
+ message: input.message,
7228
+ dedupeKey: input.dedupeKey,
7229
+ schemaVersion: input.schemaVersion ?? "1.0",
7230
+ metadata: input.metadata ?? {}
7231
+ };
7232
+ }
7233
+
7234
+ class EventsClient {
7235
+ store;
7236
+ redactors;
7237
+ transportOptions;
7238
+ constructor(options = {}) {
7239
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
7240
+ this.redactors = options.redactors ?? [];
7241
+ this.transportOptions = { fetchImpl: options.fetchImpl };
7242
+ }
7243
+ async addChannel(input) {
7244
+ const timestamp = new Date().toISOString();
7245
+ return this.store.addChannel({
7246
+ ...input,
7247
+ createdAt: input.createdAt ?? timestamp,
7248
+ updatedAt: input.updatedAt ?? timestamp
7249
+ });
7250
+ }
7251
+ async listChannels() {
7252
+ return this.store.listChannels();
7253
+ }
7254
+ async removeChannel(id) {
7255
+ return this.store.removeChannel(id);
7256
+ }
7257
+ async emit(input, options = {}) {
7258
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
7259
+ if (options.dedupe !== false) {
7260
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
7261
+ if (existing) {
7262
+ return { event: existing, deliveries: [], deduped: true };
7263
+ }
7264
+ }
7265
+ await this.store.appendEvent(event);
7266
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
7267
+ return { event, deliveries, deduped: false };
7268
+ }
7269
+ async listEvents() {
7270
+ return this.store.listEvents();
7271
+ }
7272
+ async listDeliveries() {
7273
+ return this.store.listDeliveries();
7274
+ }
7275
+ async deliver(event) {
7276
+ const channels = await this.store.listChannels();
7277
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
7278
+ const deliveries = [];
7279
+ for (const channel of selected) {
7280
+ const eventForChannel = await this.applyRedaction(event, channel);
7281
+ const result = await this.deliverWithRetry(eventForChannel, channel);
7282
+ await this.store.appendDelivery(result);
7283
+ deliveries.push(result);
7284
+ }
7285
+ return deliveries;
7286
+ }
7287
+ async matchChannel(id, input = {}) {
7288
+ const channel = await this.store.getChannel(id);
7289
+ if (!channel)
7290
+ throw new Error(`Channel not found: ${id}`);
7291
+ const event = createEvent({
7292
+ source: input.source ?? "hasna.events",
7293
+ type: input.type ?? "events.test",
7294
+ subject: input.subject ?? id,
7295
+ severity: input.severity ?? "info",
7296
+ data: input.data ?? { test: true },
7297
+ message: input.message ?? "Hasna events test delivery",
7298
+ dedupeKey: input.dedupeKey,
7299
+ schemaVersion: input.schemaVersion,
7300
+ metadata: input.metadata,
7301
+ time: input.time,
7302
+ id: input.id
7303
+ });
7304
+ const matched = channelMatchesEvent(channel, event);
7305
+ return {
7306
+ channelId: channel.id,
7307
+ matched,
7308
+ event,
7309
+ filters: channel.filters,
7310
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
7311
+ };
7312
+ }
7313
+ async testChannel(id, input = {}, options = {}) {
7314
+ const channel = await this.store.getChannel(id);
7315
+ if (!channel)
7316
+ throw new Error(`Channel not found: ${id}`);
7317
+ const match = await this.matchChannel(id, input);
7318
+ const event = match.event;
7319
+ if (options.honorFilters && !match.matched) {
7320
+ const timestamp = new Date().toISOString();
7321
+ const result2 = createDeliveryResult(event, channel, [{
7322
+ attempt: 1,
7323
+ status: "skipped",
7324
+ startedAt: timestamp,
7325
+ completedAt: timestamp,
7326
+ error: match.reason
7327
+ }]);
7328
+ result2.metadata = { reason: "filter_mismatch" };
7329
+ await this.store.appendDelivery(result2);
7330
+ return result2;
7331
+ }
7332
+ const eventForChannel = await this.applyRedaction(event, channel);
7333
+ const result = await this.deliverWithRetry(eventForChannel, channel);
7334
+ await this.store.appendDelivery(result);
7335
+ return result;
7336
+ }
7337
+ async replay(options = {}) {
7338
+ const events = (await this.store.listEvents()).filter((event) => {
7339
+ if (options.eventId && event.id !== options.eventId)
7340
+ return false;
7341
+ if (options.source && event.source !== options.source)
7342
+ return false;
7343
+ if (options.type && event.type !== options.type)
7344
+ return false;
7345
+ return true;
7346
+ });
7347
+ if (options.dryRun)
7348
+ return { events, deliveries: [] };
7349
+ const deliveries = [];
7350
+ for (const event of events) {
7351
+ deliveries.push(...await this.deliver(event));
7352
+ }
7353
+ return { events, deliveries };
7354
+ }
7355
+ async applyRedaction(event, channel) {
7356
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
7357
+ for (const redactor of this.redactors) {
7358
+ next = await redactor(next, channel);
7359
+ }
7360
+ return next;
7361
+ }
7362
+ async deliverWithRetry(event, channel) {
7363
+ const policy = normalizeRetryPolicy(channel.retry);
7364
+ const attempts = [];
7365
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
7366
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
7367
+ attempt.attempt = index + 1;
7368
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
7369
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
7370
+ }
7371
+ attempts.push(attempt);
7372
+ if (attempt.status !== "failed")
7373
+ break;
7374
+ if (attempt.nextBackoffMs)
7375
+ await Bun.sleep(attempt.nextBackoffMs);
7376
+ }
7377
+ return createDeliveryResult(event, channel, attempts);
7378
+ }
7379
+ }
7380
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
7381
+ if (paths.length === 0)
7382
+ return event;
7383
+ const copy = structuredClone(event);
7384
+ for (const path of paths) {
7385
+ setPath(copy, path, replacement);
7386
+ }
7387
+ return copy;
7388
+ }
7389
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
7390
+ return redactValue2(event, replacement);
7391
+ }
7392
+ function shouldRedactKey(key) {
7393
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
7394
+ }
7395
+ function redactValue2(value, replacement) {
7396
+ if (Array.isArray(value))
7397
+ return value.map((item) => redactValue2(item, replacement));
7398
+ if (!value || typeof value !== "object")
7399
+ return value;
7400
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
7401
+ key,
7402
+ shouldRedactKey(key) ? replacement : redactValue2(item, replacement)
7403
+ ]));
7404
+ }
7405
+ function setPath(input, path, replacement) {
7406
+ const parts = path.split(".");
7407
+ let cursor = input;
7408
+ for (const part of parts.slice(0, -1)) {
7409
+ const next = cursor[part];
7410
+ if (!next || typeof next !== "object")
7411
+ return;
7412
+ cursor = next;
7413
+ }
7414
+ const last = parts.at(-1);
7415
+ if (last && last in cursor)
7416
+ cursor[last] = replacement;
7417
+ }
7418
+ function normalizeTime(value) {
7419
+ if (!value)
7420
+ return new Date().toISOString();
7421
+ return value instanceof Date ? value.toISOString() : value;
7422
+ }
7423
+ function normalizeRetryPolicy(policy) {
7424
+ return {
7425
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
7426
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
7427
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
7428
+ };
7429
+ }
7430
+
7431
+ // src/lib/shared-events.ts
7432
+ init_database();
7433
+
7434
+ // src/db/task-lists.ts
7435
+ init_types();
7436
+ init_database();
7437
+ function rowToTaskList(row) {
7438
+ return {
7439
+ ...row,
7440
+ metadata: JSON.parse(row.metadata || "{}")
7441
+ };
7442
+ }
7443
+ function createTaskList(input, db) {
7444
+ const d = db || getDatabase();
7445
+ const id = uuid();
7446
+ const timestamp = now();
7447
+ const slug = input.slug || slugify(input.name);
7448
+ if (!input.project_id) {
7449
+ const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
7450
+ if (existing) {
7451
+ throw new Error(`Standalone task list with slug "${slug}" already exists`);
7452
+ }
7453
+ }
7454
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
7455
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
7456
+ return getTaskList(id, d);
7457
+ }
7458
+ function getTaskList(id, db) {
7459
+ const d = db || getDatabase();
7460
+ const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
7461
+ return row ? rowToTaskList(row) : null;
7462
+ }
7463
+ function getTaskListBySlug(slug, projectId, db) {
7464
+ const d = db || getDatabase();
7465
+ let row;
7466
+ if (projectId) {
7467
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
7468
+ } else {
7469
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
7470
+ }
7471
+ return row ? rowToTaskList(row) : null;
7472
+ }
7473
+ function listTaskLists(projectId, db) {
7474
+ const d = db || getDatabase();
7475
+ if (projectId) {
7476
+ return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
7477
+ }
7478
+ return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
7479
+ }
7480
+ function updateTaskList(id, input, db) {
7481
+ const d = db || getDatabase();
7482
+ const existing = getTaskList(id, d);
7483
+ if (!existing)
7484
+ throw new TaskListNotFoundError(id);
7485
+ const sets = ["updated_at = ?"];
7486
+ const params = [now()];
7487
+ if (input.name !== undefined) {
7488
+ sets.push("name = ?");
7489
+ params.push(input.name);
7490
+ }
7491
+ if (input.description !== undefined) {
7492
+ sets.push("description = ?");
7493
+ params.push(input.description);
7494
+ }
7495
+ if (input.metadata !== undefined) {
7496
+ sets.push("metadata = ?");
7497
+ params.push(JSON.stringify(input.metadata));
7498
+ }
7499
+ params.push(id);
7500
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
7501
+ return getTaskList(id, d);
7502
+ }
7503
+ function deleteTaskList(id, db) {
7504
+ const d = db || getDatabase();
7505
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
7506
+ }
7507
+ function ensureTaskList(name, slug, projectId, db) {
7508
+ const d = db || getDatabase();
7509
+ const existing = getTaskListBySlug(slug, projectId, d);
7510
+ if (existing)
7511
+ return existing;
7512
+ return createTaskList({ name, slug, project_id: projectId }, d);
7513
+ }
7514
+
7515
+ // src/lib/shared-events.ts
7516
+ var SOURCE = "todos";
7517
+ function taskEventData(task, extra = {}) {
7518
+ return {
7519
+ id: task.id,
7520
+ task_id: task.id,
7521
+ short_id: task.short_id,
7522
+ title: task.title,
7523
+ description: task.description,
7524
+ status: task.status,
7525
+ priority: task.priority,
7526
+ project_id: task.project_id,
7527
+ parent_id: task.parent_id,
7528
+ plan_id: task.plan_id,
7529
+ task_list_id: task.task_list_id,
7530
+ agent_id: task.agent_id,
7531
+ assigned_to: task.assigned_to,
7532
+ session_id: task.session_id,
7533
+ working_dir: task.working_dir,
7534
+ tags: task.tags,
7535
+ metadata: task.metadata,
7536
+ version: task.version,
7537
+ created_at: task.created_at,
7538
+ updated_at: task.updated_at,
7539
+ started_at: task.started_at,
7540
+ completed_at: task.completed_at,
7541
+ due_at: task.due_at,
7542
+ ...extra
7543
+ };
7544
+ }
7545
+ function taskEventMetadata(task) {
7546
+ const metadata = {
7547
+ package: "@hasna/todos",
7548
+ todos_event_schema_version: 1,
7549
+ task_id: task.id,
7550
+ task_short_id: task.short_id,
7551
+ project_id: task.project_id,
7552
+ task_list_id: task.task_list_id,
7553
+ working_dir: task.working_dir
7554
+ };
7555
+ try {
7556
+ const project = task.project_id ? getProject(task.project_id) : null;
7557
+ const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
7558
+ if (project) {
7559
+ metadata.project_id = project.id;
7560
+ metadata.project_name = project.name;
7561
+ metadata.project_path = projectPath;
7562
+ metadata.project_canonical_path = project.path;
7563
+ metadata.project_default_task_list_slug = project.task_list_id;
7564
+ metadata.root_project_id = inferRootProjectId(project);
7565
+ } else if (projectPath) {
7566
+ metadata.project_path = projectPath;
7567
+ metadata.project_canonical_path = projectPath;
7568
+ }
7569
+ if (projectPath) {
7570
+ metadata.project_kind = classifyProjectKind(projectPath);
7571
+ metadata.project_is_worktree = isWorktreePath(projectPath);
7572
+ if (typeof task.metadata.route_enabled === "boolean") {
7573
+ metadata.route_enabled = task.metadata.route_enabled;
7574
+ }
7575
+ metadata.working_dir = task.working_dir ?? projectPath;
7576
+ }
7577
+ const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
7578
+ if (taskList) {
7579
+ metadata.task_list_id = taskList.id;
7580
+ metadata.task_list_slug = taskList.slug;
7581
+ metadata.task_list_name = taskList.name;
7582
+ metadata.task_list_project_id = taskList.project_id;
7583
+ metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
7584
+ }
7585
+ } catch {}
7586
+ return metadata;
7587
+ }
7588
+ function classifyProjectKind(path) {
7589
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
7590
+ }
7591
+ function isWorktreePath(path) {
7592
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
7593
+ }
7594
+ function inferRootProjectId(project) {
7595
+ return isWorktreePath(project.path) ? null : project.id;
7596
+ }
7597
+ function readMachineLocalPath(project) {
7598
+ const machineId = process.env["TODOS_MACHINE_ID"];
7599
+ if (!machineId)
7600
+ return null;
7601
+ try {
7602
+ const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
7603
+ return row?.path ?? null;
7604
+ } catch {
7605
+ return null;
7606
+ }
7607
+ }
7608
+ async function emitSharedTaskEvent(input) {
7609
+ const data = taskEventData(input.task, input.data);
7610
+ await new EventsClient().emit({
7611
+ source: SOURCE,
7612
+ type: input.type,
7613
+ subject: input.task.id,
7614
+ severity: input.severity ?? "info",
7615
+ message: input.message ?? `${input.type}: ${input.task.title}`,
7616
+ data,
7617
+ dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
7618
+ metadata: taskEventMetadata(input.task)
7619
+ }, { deliver: true, dedupe: true });
7620
+ }
7621
+ function emitSharedTaskEventQuiet(input) {
7622
+ emitSharedTaskEvent(input).catch(() => {
7623
+ return;
7624
+ });
7625
+ }
7626
+
6819
7627
  // src/db/audit.ts
6820
7628
  init_database();
6821
7629
  function logTaskChange(taskId, action, field2, oldValue, newValue, agentId, db) {
@@ -7078,7 +7886,7 @@ async function deliverWebhook(wh, event, body, attempt, db) {
7078
7886
  activeDeliveries--;
7079
7887
  }
7080
7888
  }
7081
- async function dispatchWebhook(event, payload, db) {
7889
+ async function dispatchWebhook2(event, payload, db) {
7082
7890
  const d = db || getDatabase();
7083
7891
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
7084
7892
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
@@ -7232,7 +8040,10 @@ function createTask(input, db) {
7232
8040
  insertTaskTags(id, tags, d);
7233
8041
  }
7234
8042
  const task = getTask(id, d);
7235
- dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
8043
+ const payload = taskEventData(task);
8044
+ dispatchWebhook2("task.created", payload, d).catch(() => {});
8045
+ emitLocalEventHooksQuiet({ type: "task.created", payload });
8046
+ emitSharedTaskEventQuiet({ type: "task.created", task });
7236
8047
  return task;
7237
8048
  }
7238
8049
  function getTask(id, db) {
@@ -7576,18 +8387,7 @@ function updateTask(id, input, db) {
7576
8387
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
7577
8388
  if (input.approved_by !== undefined)
7578
8389
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
7579
- if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
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 {
8390
+ const updatedTask = {
7591
8391
  ...task,
7592
8392
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
7593
8393
  tags: input.tags ?? task.tags,
@@ -7605,6 +8405,22 @@ function updateTask(id, input, db) {
7605
8405
  approved_by: input.approved_by ?? task.approved_by,
7606
8406
  approved_at: input.approved_by ? timestamp : task.approved_at
7607
8407
  };
8408
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
8409
+ const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
8410
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
8411
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
8412
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
8413
+ }
8414
+ if (input.status !== undefined && input.status !== task.status) {
8415
+ const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
8416
+ dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
8417
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
8418
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
8419
+ }
8420
+ if (input.approved_by !== undefined) {
8421
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
8422
+ }
8423
+ return updatedTask;
7608
8424
  }
7609
8425
  function deleteTask(id, db) {
7610
8426
  const d = db || getDatabase();
@@ -8337,9 +9153,12 @@ function startTask(id, agentId, db) {
8337
9153
  throw new Error(`Task ${id} could not be started because it changed during claim`);
8338
9154
  }
8339
9155
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
8340
- dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
8341
- emitLocalEventHooksQuiet({ type: "task.started", payload: { id, agent_id: agentId, title: task.title } });
8342
- return { ...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 };
9156
+ 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 };
9157
+ const payload = taskEventData(startedTask, { agent_id: agentId });
9158
+ dispatchWebhook2("task.started", payload, d).catch(() => {});
9159
+ emitLocalEventHooksQuiet({ type: "task.started", payload });
9160
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
9161
+ return startedTask;
8343
9162
  }
8344
9163
  function completeTask(id, agentId, db, options) {
8345
9164
  const d = db || getDatabase();
@@ -8375,8 +9194,21 @@ function completeTask(id, agentId, db, options) {
8375
9194
  });
8376
9195
  tx();
8377
9196
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
8378
- dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
8379
- emitLocalEventHooksQuiet({ type: "task.completed", payload: { id, agent_id: agentId, title: task.title, completed_at: timestamp } });
9197
+ const completedTaskForEvent = {
9198
+ ...task,
9199
+ status: "completed",
9200
+ locked_by: null,
9201
+ locked_at: null,
9202
+ completed_at: timestamp,
9203
+ confidence,
9204
+ version: task.version + 1,
9205
+ updated_at: timestamp,
9206
+ metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
9207
+ };
9208
+ const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
9209
+ dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
9210
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
9211
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
8380
9212
  let spawnedTask = null;
8381
9213
  if (task.recurrence_rule && !options?.skip_recurrence) {
8382
9214
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -8417,8 +9249,12 @@ function completeTask(id, agentId, db, options) {
8417
9249
  if (unblockedDeps.length > 0) {
8418
9250
  meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
8419
9251
  for (const dep of unblockedDeps) {
8420
- dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
8421
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload: { id: dep.id, unblocked_by: id, title: dep.title } });
9252
+ const depTask = getTask(dep.id, d);
9253
+ const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
9254
+ dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
9255
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
9256
+ if (depTask)
9257
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
8422
9258
  }
8423
9259
  }
8424
9260
  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 +9440,6 @@ function failTask(id, agentId, reason, options, db) {
8604
9440
  const timestamp = now();
8605
9441
  d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
8606
9442
  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
9443
  const failedTask = {
8611
9444
  ...task,
8612
9445
  status: "failed",
@@ -8616,6 +9449,11 @@ function failTask(id, agentId, reason, options, db) {
8616
9449
  version: task.version + 1,
8617
9450
  updated_at: timestamp
8618
9451
  };
9452
+ logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
9453
+ const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
9454
+ dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
9455
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
9456
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
8619
9457
  let retryTask;
8620
9458
  if (options?.retry) {
8621
9459
  const retryCount = (task.retry_count || 0) + 1;
@@ -8690,9 +9528,12 @@ function stealTask(agentId, opts, db) {
8690
9528
  return null;
8691
9529
  logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
8692
9530
  logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
8693
- dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
8694
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to } });
8695
- return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
9531
+ const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
9532
+ const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
9533
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
9534
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
9535
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
9536
+ return stolenTask;
8696
9537
  }
8697
9538
  function claimOrSteal(agentId, filters, db) {
8698
9539
  const d = db || getDatabase();
@@ -9751,8 +10592,8 @@ init_database();
9751
10592
  init_database();
9752
10593
  init_redaction();
9753
10594
  import { createHash as createHash2 } from "crypto";
9754
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
9755
- import { basename, dirname as dirname5, join as join5, resolve as resolve6 } from "path";
10595
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
10596
+ import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
9756
10597
  import { tmpdir } from "os";
9757
10598
  function isInMemoryDb2(path) {
9758
10599
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -9764,15 +10605,15 @@ function artifactStoreRoot() {
9764
10605
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
9765
10606
  const dbPath = getDatabasePath();
9766
10607
  if (isInMemoryDb2(dbPath))
9767
- return join5(tmpdir(), "hasna-todos-artifacts");
9768
- return join5(dirname5(resolve6(dbPath)), "artifacts");
10608
+ return join6(tmpdir(), "hasna-todos-artifacts");
10609
+ return join6(dirname5(resolve6(dbPath)), "artifacts");
9769
10610
  }
9770
10611
  function artifactStorePath(relativePath) {
9771
10612
  const normalized = relativePath.replace(/\\/g, "/");
9772
10613
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
9773
10614
  throw new Error("Invalid artifact store path");
9774
10615
  }
9775
- return join5(artifactStoreRoot(), normalized);
10616
+ return join6(artifactStoreRoot(), normalized);
9776
10617
  }
9777
10618
  function sha256(buffer) {
9778
10619
  return createHash2("sha256").update(buffer).digest("hex");
@@ -9813,7 +10654,7 @@ function mediaTypeFor(path, textLike) {
9813
10654
  }
9814
10655
  function storeArtifactContent(input) {
9815
10656
  const sourcePath = resolve6(input.path);
9816
- if (!existsSync7(sourcePath))
10657
+ if (!existsSync8(sourcePath))
9817
10658
  return null;
9818
10659
  const sourceStat = statSync2(sourcePath);
9819
10660
  if (!sourceStat.isFile())
@@ -9830,9 +10671,9 @@ function storeArtifactContent(input) {
9830
10671
  redactionStatus = "redacted";
9831
10672
  }
9832
10673
  const storedSha = sha256(storedBuffer);
9833
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
10674
+ const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
9834
10675
  const destination = artifactStorePath(relativePath);
9835
- if (!existsSync7(destination)) {
10676
+ if (!existsSync8(destination)) {
9836
10677
  mkdirSync4(dirname5(destination), { recursive: true });
9837
10678
  writeFileSync2(destination, storedBuffer);
9838
10679
  }
@@ -9892,7 +10733,7 @@ function verifyStoredArtifact(input) {
9892
10733
  };
9893
10734
  }
9894
10735
  const storedPath = artifactStorePath(store.relative_path);
9895
- if (!existsSync7(storedPath)) {
10736
+ if (!existsSync8(storedPath)) {
9896
10737
  return {
9897
10738
  id: input.id,
9898
10739
  path: input.path,
@@ -9973,15 +10814,15 @@ function getArtifactStoreRoot(dbPath) {
9973
10814
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
9974
10815
  const path = dbPath ?? getDatabasePath();
9975
10816
  if (isInMemoryDb2(path))
9976
- return join5(tmpdir(), "hasna-todos-artifacts");
9977
- return join5(dirname5(resolve6(path)), "artifacts");
10817
+ return join6(tmpdir(), "hasna-todos-artifacts");
10818
+ return join6(dirname5(resolve6(path)), "artifacts");
9978
10819
  }
9979
10820
  function computeContentHash(path) {
9980
10821
  return sha256(readFileSync4(resolve6(path)));
9981
10822
  }
9982
10823
  function storeArtifactFile(input) {
9983
10824
  const sourcePath = resolve6(input.sourcePath);
9984
- if (!existsSync7(sourcePath)) {
10825
+ if (!existsSync8(sourcePath)) {
9985
10826
  throw new Error(`Source file not found: ${input.sourcePath}`);
9986
10827
  }
9987
10828
  if (!statSync2(sourcePath).isFile()) {
@@ -9994,7 +10835,7 @@ function storeArtifactFile(input) {
9994
10835
  let localPath = sourcePath;
9995
10836
  if (storageMode === "copy") {
9996
10837
  const fileName = input.name && input.name.trim().length > 0 ? basename(input.name) : basename(sourcePath);
9997
- const destination = join5(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
10838
+ const destination = join6(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
9998
10839
  mkdirSync4(dirname5(destination), { recursive: true });
9999
10840
  writeFileSync2(destination, buffer);
10000
10841
  localPath = destination;
@@ -10004,7 +10845,7 @@ function storeArtifactFile(input) {
10004
10845
  function deleteStoredArtifactFile(localPath, storageMode, _dbPath2) {
10005
10846
  if (storageMode === "reference")
10006
10847
  return false;
10007
- if (!localPath || !existsSync7(localPath))
10848
+ if (!localPath || !existsSync8(localPath))
10008
10849
  return false;
10009
10850
  rmSync(localPath, { force: true });
10010
10851
  try {
@@ -10016,8 +10857,8 @@ function isArtifactExpired(deletedAt, policy = {}) {
10016
10857
  if (!deletedAt)
10017
10858
  return false;
10018
10859
  const retentionDays = policy.deleted_retention_days ?? DEFAULT_DELETED_RETENTION_DAYS;
10019
- const now2 = policy.now ?? new Date;
10020
- const ageMs = now2.getTime() - new Date(deletedAt).getTime();
10860
+ const now3 = policy.now ?? new Date;
10861
+ const ageMs = now3.getTime() - new Date(deletedAt).getTime();
10021
10862
  return ageMs > retentionDays * 24 * 60 * 60 * 1000;
10022
10863
  }
10023
10864
  function buildArtifactExportManifest(artifacts, dbPath) {
@@ -11092,7 +11933,7 @@ function rowToTask2(row) {
11092
11933
  requires_approval: Boolean(row.requires_approval)
11093
11934
  };
11094
11935
  }
11095
- function rowToTaskList(row) {
11936
+ function rowToTaskList2(row) {
11096
11937
  return { ...row, metadata: parseJsonObject3(row.metadata) };
11097
11938
  }
11098
11939
  function rowWithMetadata(row) {
@@ -11128,7 +11969,7 @@ function createLocalBridgeBundle(options = {}, db) {
11128
11969
  const project = options.project_id ? d.query("SELECT * FROM projects WHERE id = ?").get(options.project_id) : null;
11129
11970
  const data = redactValue({
11130
11971
  projects: options.project_id ? project ? [project] : [] : d.query("SELECT * FROM projects ORDER BY name").all(),
11131
- task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList),
11972
+ task_lists: (options.project_id ? d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(options.project_id) : d.query("SELECT * FROM task_lists ORDER BY name").all()).map(rowToTaskList2),
11132
11973
  plans: options.project_id ? d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at").all(options.project_id) : d.query("SELECT * FROM plans ORDER BY created_at").all(),
11133
11974
  tasks: queryByTaskIds(d, "SELECT * FROM tasks WHERE id IN (__TASK_IDS__) ORDER BY created_at", taskIds).map(rowToTask2),
11134
11975
  task_dependencies: queryByTaskIds(d, "SELECT task_id, depends_on, external_project_id, external_task_id FROM task_dependencies WHERE task_id IN (__TASK_IDS__) ORDER BY task_id, depends_on", taskIds),
@@ -11783,7 +12624,7 @@ function writeOnboardingFixtureFiles(directory) {
11783
12624
  mkdirSync5(directory, { recursive: true });
11784
12625
  const files = [];
11785
12626
  for (const fixture of allFixtures()) {
11786
- const path = join6(directory, `${fixture.summary.name}.bridge.json`);
12627
+ const path = join7(directory, `${fixture.summary.name}.bridge.json`);
11787
12628
  writeFileSync3(path, `${JSON.stringify(fixture.bundle, null, 2)}
11788
12629
  `, "utf-8");
11789
12630
  files.push(path);
@@ -12609,7 +13450,7 @@ function renderLocalSnapshotMarkdown(snapshot) {
12609
13450
  }
12610
13451
  // src/lib/sdk-integration-fixtures.ts
12611
13452
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
12612
- import { join as join7 } from "path";
13453
+ import { join as join8 } from "path";
12613
13454
 
12614
13455
  // src/cli-mcp-parity.ts
12615
13456
  function source4(version) {
@@ -13919,7 +14760,7 @@ function limits(input) {
13919
14760
  stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
13920
14761
  };
13921
14762
  }
13922
- function truncate(value, max) {
14763
+ function truncate2(value, max) {
13923
14764
  if (!value)
13924
14765
  return value ?? null;
13925
14766
  const redacted = redactEvidenceText(value);
@@ -13940,9 +14781,9 @@ function acceptanceCriteria(task2, maxText) {
13940
14781
  const metadata = task2.metadata || {};
13941
14782
  const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
13942
14783
  if (Array.isArray(raw))
13943
- return raw.map((item) => truncate(String(item), maxText)).filter((item) => Boolean(item));
14784
+ return raw.map((item) => truncate2(String(item), maxText)).filter((item) => Boolean(item));
13944
14785
  if (typeof raw === "string") {
13945
- return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate(line, maxText)).filter((item) => Boolean(item));
14786
+ return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate2(line, maxText)).filter((item) => Boolean(item));
13946
14787
  }
13947
14788
  return [];
13948
14789
  }
@@ -13965,7 +14806,7 @@ function addFile(files, path, source5, base) {
13965
14806
  path,
13966
14807
  status: base?.status || "active",
13967
14808
  agent_id: base?.agent_id ?? null,
13968
- note: truncate(base?.note, 240),
14809
+ note: truncate2(base?.note, 240),
13969
14810
  updated_at: base?.updated_at || "",
13970
14811
  sources: [source5]
13971
14812
  });
@@ -14023,7 +14864,7 @@ function estimateTokens(value) {
14023
14864
  return Math.max(1, Math.ceil((text || "").length / 4));
14024
14865
  }
14025
14866
  function summarizeStrings(values, maxChars) {
14026
- return truncate(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
14867
+ return truncate2(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
14027
14868
  }
14028
14869
  function summarizeSection(pack, section, maxChars) {
14029
14870
  if (section === "project")
@@ -14214,8 +15055,8 @@ function createAgentContextPack(input, db) {
14214
15055
  ...taskFiles.map((file) => file.updated_at)
14215
15056
  ], task2.updated_at);
14216
15057
  const warnings = [];
14217
- const now2 = input.now ? new Date(input.now) : new Date;
14218
- if (Date.parse(task2.updated_at) < now2.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
15058
+ const now3 = input.now ? new Date(input.now) : new Date;
15059
+ if (Date.parse(task2.updated_at) < now3.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
14219
15060
  warnings.push(`task state is older than ${limit.stale_after_hours} hours`);
14220
15061
  }
14221
15062
  if (comments.length > recentComments.length)
@@ -14230,7 +15071,7 @@ function createAgentContextPack(input, db) {
14230
15071
  id: task2.id,
14231
15072
  short_id: task2.short_id,
14232
15073
  title: redactEvidenceText(task2.title),
14233
- description: truncate(task2.description, limit.max_text_chars),
15074
+ description: truncate2(task2.description, limit.max_text_chars),
14234
15075
  status: task2.status,
14235
15076
  priority: task2.priority,
14236
15077
  assigned_to: task2.assigned_to,
@@ -14254,7 +15095,7 @@ function createAgentContextPack(input, db) {
14254
15095
  plan: plan ? {
14255
15096
  id: plan.id,
14256
15097
  name: plan.name,
14257
- description: truncate(plan.description, limit.max_text_chars),
15098
+ description: truncate2(plan.description, limit.max_text_chars),
14258
15099
  status: plan.status,
14259
15100
  agent_id: plan.agent_id,
14260
15101
  tasks: planTasks.slice(0, limit.plan_task_limit).map(taskSummary).filter((item) => Boolean(item)),
@@ -14273,7 +15114,7 @@ function createAgentContextPack(input, db) {
14273
15114
  type: comment.type,
14274
15115
  progress_pct: comment.progress_pct,
14275
15116
  created_at: comment.created_at,
14276
- content: truncate(comment.content, limit.max_text_chars) || ""
15117
+ content: truncate2(comment.content, limit.max_text_chars) || ""
14277
15118
  })),
14278
15119
  omitted: Math.max(0, comments.length - recentComments.length)
14279
15120
  },
@@ -14281,7 +15122,7 @@ function createAgentContextPack(input, db) {
14281
15122
  traceability: {
14282
15123
  commits: traceability.commits.map((commit) => ({
14283
15124
  sha: commit.sha,
14284
- message: truncate(commit.message, 240),
15125
+ message: truncate2(commit.message, 240),
14285
15126
  files_changed: commit.files_changed,
14286
15127
  committed_at: commit.committed_at
14287
15128
  })),
@@ -14289,7 +15130,7 @@ function createAgentContextPack(input, db) {
14289
15130
  verifications: verifications.map((verification) => ({
14290
15131
  command: verification.command,
14291
15132
  status: verification.status,
14292
- output_summary: truncate(verification.output_summary, limit.max_text_chars),
15133
+ output_summary: truncate2(verification.output_summary, limit.max_text_chars),
14293
15134
  artifact_path: verification.artifact_path,
14294
15135
  run_at: verification.run_at
14295
15136
  })),
@@ -14300,14 +15141,14 @@ function createAgentContextPack(input, db) {
14300
15141
  id: ledger.run.id,
14301
15142
  title: ledger.run.title,
14302
15143
  status: ledger.run.status,
14303
- summary: truncate(ledger.run.summary, limit.max_text_chars),
15144
+ summary: truncate2(ledger.run.summary, limit.max_text_chars),
14304
15145
  agent_id: ledger.run.agent_id,
14305
15146
  started_at: ledger.run.started_at,
14306
15147
  completed_at: ledger.run.completed_at,
14307
- events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate(event.message, 500), created_at: event.created_at })),
14308
- commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary: truncate(command.output_summary, limit.max_text_chars), artifact_path: command.artifact_path })),
14309
- files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate(file.note, 240) })),
14310
- artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate(artifact.description, 240), sha256: artifact.sha256 }))
15148
+ events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate2(event.message, 500), created_at: event.created_at })),
15149
+ 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 })),
15150
+ files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate2(file.note, 240) })),
15151
+ artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate2(artifact.description, 240), sha256: artifact.sha256 }))
14311
15152
  })),
14312
15153
  omitted: Math.max(0, runs.length - selectedRuns.length)
14313
15154
  },
@@ -14412,7 +15253,7 @@ function renderAgentContextPackCompactMarkdown(pack) {
14412
15253
  const lines = [
14413
15254
  `# Context: ${pack.task.title}`,
14414
15255
  `${pack.task.status} | ${pack.task.priority} | ${pack.task.short_id || pack.task.id.slice(0, 8)}`,
14415
- pack.task.description ? truncate(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
15256
+ pack.task.description ? truncate2(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
14416
15257
  "",
14417
15258
  "## Must Know",
14418
15259
  bullet([
@@ -14558,7 +15399,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
14558
15399
  ];
14559
15400
  const written = [];
14560
15401
  for (const [name, payload] of files) {
14561
- const file = join7(directory, name);
15402
+ const file = join8(directory, name);
14562
15403
  writeFileSync5(file, `${JSON.stringify(payload, null, 2)}
14563
15404
  `, "utf-8");
14564
15405
  written.push(file);
@@ -15275,7 +16116,7 @@ function createRoadmap(input) {
15275
16116
  if (!name)
15276
16117
  throw new Error("Roadmap name is required");
15277
16118
  const store = readStore();
15278
- const now2 = timestamp();
16119
+ const now3 = timestamp();
15279
16120
  const roadmap = {
15280
16121
  id: newId("roadmap"),
15281
16122
  name,
@@ -15286,8 +16127,8 @@ function createRoadmap(input) {
15286
16127
  agent_id: cleanString(input.agent_id),
15287
16128
  release: cleanString(input.release),
15288
16129
  milestone_ids: [],
15289
- created_at: now2,
15290
- updated_at: now2
16130
+ created_at: now3,
16131
+ updated_at: now3
15291
16132
  };
15292
16133
  store.roadmaps[roadmap.id] = roadmap;
15293
16134
  writeStore(store);
@@ -15347,7 +16188,7 @@ function createMilestone(input) {
15347
16188
  const title = input.title.trim();
15348
16189
  if (!title)
15349
16190
  throw new Error("Milestone title is required");
15350
- const now2 = timestamp();
16191
+ const now3 = timestamp();
15351
16192
  const milestone = {
15352
16193
  id: newId("milestone"),
15353
16194
  roadmap_id: roadmapId,
@@ -15362,11 +16203,11 @@ function createMilestone(input) {
15362
16203
  run_ids: cleanList3(input.run_ids),
15363
16204
  release: cleanString(input.release ?? roadmap.release ?? undefined),
15364
16205
  tags: cleanList3(input.tags),
15365
- created_at: now2,
15366
- updated_at: now2
16206
+ created_at: now3,
16207
+ updated_at: now3
15367
16208
  };
15368
16209
  store.milestones[milestone.id] = milestone;
15369
- store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList3([...roadmap.milestone_ids, milestone.id]), updated_at: now2 };
16210
+ store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList3([...roadmap.milestone_ids, milestone.id]), updated_at: now3 };
15370
16211
  writeStore(store);
15371
16212
  return milestone;
15372
16213
  }
@@ -15426,7 +16267,7 @@ function upsertReleaseGroup(input) {
15426
16267
  throw new Error("Release group name is required");
15427
16268
  const key = releaseKey(roadmapId, name);
15428
16269
  const existing = store.releases[key];
15429
- const now2 = timestamp();
16270
+ const now3 = timestamp();
15430
16271
  const release = {
15431
16272
  name,
15432
16273
  version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
@@ -15437,8 +16278,8 @@ function upsertReleaseGroup(input) {
15437
16278
  plan_ids: input.plan_ids === undefined ? existing?.plan_ids ?? [] : cleanList3(input.plan_ids),
15438
16279
  run_ids: input.run_ids === undefined ? existing?.run_ids ?? [] : cleanList3(input.run_ids),
15439
16280
  notes: input.notes === undefined ? existing?.notes ?? null : cleanString(input.notes),
15440
- created_at: existing?.created_at ?? now2,
15441
- updated_at: now2
16281
+ created_at: existing?.created_at ?? now3,
16282
+ updated_at: now3
15442
16283
  };
15443
16284
  store.releases[key] = release;
15444
16285
  writeStore(store);
@@ -15829,7 +16670,7 @@ function renderLocalAuditLedgerMarkdown(ledger) {
15829
16670
  init_migrations();
15830
16671
  init_schema();
15831
16672
  import { readFileSync as readFileSync6 } from "fs";
15832
- import { join as join8, resolve as resolve8 } from "path";
16673
+ import { join as join9, resolve as resolve8 } from "path";
15833
16674
  import { Database as Database2 } from "bun:sqlite";
15834
16675
  var LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION = 1;
15835
16676
  var EXPECTED_PACKAGE_NAME = "@hasna/todos";
@@ -15875,7 +16716,7 @@ function warn(id, message, details) {
15875
16716
  return { id, status: "warning", message, details };
15876
16717
  }
15877
16718
  function readPackageJson(root) {
15878
- return JSON.parse(readFileSync6(join8(root, "package.json"), "utf8"));
16719
+ return JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
15879
16720
  }
15880
16721
  function sortedKeys(value) {
15881
16722
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -17287,6 +18128,470 @@ function importExternalIssues(input, db) {
17287
18128
  ]
17288
18129
  };
17289
18130
  }
18131
+ // src/lib/tester-issue-reports.ts
18132
+ init_database();
18133
+ import { createHash as createHash7 } from "crypto";
18134
+ init_redaction();
18135
+ var TESTERS_ISSUE_REPORT_SCHEMA_VERSION = "testers.issue_report.v1";
18136
+ var TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_result.v1";
18137
+ var TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION = "todos.tester_issue_report_batch_result.v1";
18138
+ var PRIORITIES3 = ["low", "medium", "high", "critical"];
18139
+ var SEVERITIES = new Set(PRIORITIES3);
18140
+ var KINDS = new Set([
18141
+ "assertion_failure",
18142
+ "runtime_error",
18143
+ "console_error",
18144
+ "network_error",
18145
+ "visual_regression",
18146
+ "accessibility",
18147
+ "performance",
18148
+ "broken_link",
18149
+ "security",
18150
+ "unknown"
18151
+ ]);
18152
+ function asObject3(value) {
18153
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
18154
+ }
18155
+ function asString3(value) {
18156
+ if (typeof value === "string" && value.trim())
18157
+ return value.trim();
18158
+ if (typeof value === "number" && Number.isFinite(value))
18159
+ return String(value);
18160
+ return null;
18161
+ }
18162
+ function stringArray(value, limit = 20) {
18163
+ if (!Array.isArray(value))
18164
+ return [];
18165
+ return [...new Set(value.map((item) => asString3(item)).filter((item) => Boolean(item)))].slice(0, limit);
18166
+ }
18167
+ function objectArray(value, limit = 20) {
18168
+ if (!Array.isArray(value))
18169
+ return [];
18170
+ return value.map(asObject3).filter((item) => Object.keys(item).length > 0).slice(0, limit);
18171
+ }
18172
+ function cleanKey(value) {
18173
+ return value.toLowerCase().trim().replace(/[^a-z0-9._:-]+/g, "-").replace(/^-+|-+$/g, "");
18174
+ }
18175
+ function truncate3(value, max) {
18176
+ if (!value)
18177
+ return;
18178
+ const redacted = redactEvidenceText(value).trim();
18179
+ if (!redacted)
18180
+ return;
18181
+ return redacted.length > max ? `${redacted.slice(0, max - 3)}...` : redacted;
18182
+ }
18183
+ function normalizeText2(value) {
18184
+ return (value || "").toLowerCase().replace(/https?:\/\/\S+/g, " ").replace(/[`"'()[\]{}.,:;!?/#\\_-]+/g, " ").replace(/\s+/g, " ").trim();
18185
+ }
18186
+ function normalizeUrlPattern(value) {
18187
+ if (!value)
18188
+ return "";
18189
+ try {
18190
+ const url = new URL(value);
18191
+ return `${url.origin.toLowerCase()}${url.pathname.replace(/\/+$/, "") || "/"}`;
18192
+ } catch {
18193
+ return value.split(/[?#]/, 1)[0].replace(/\/+$/, "").toLowerCase();
18194
+ }
18195
+ }
18196
+ function normalizeKind(value) {
18197
+ const raw = cleanKey(asString3(value) || "unknown").replace(/-/g, "_");
18198
+ return KINDS.has(raw) ? raw : raw || "unknown";
18199
+ }
18200
+ function normalizeSeverity(value, fallback) {
18201
+ const raw = cleanKey(asString3(value) || fallback);
18202
+ if (SEVERITIES.has(raw))
18203
+ return raw;
18204
+ if (/^(p0|blocker|urgent|highest)$/.test(raw))
18205
+ return "critical";
18206
+ if (/^(p1|major)$/.test(raw))
18207
+ return "high";
18208
+ if (/^(p3|minor|info)$/.test(raw))
18209
+ return "low";
18210
+ return fallback;
18211
+ }
18212
+ function reportSource(input) {
18213
+ const source6 = asObject3(input["source"]);
18214
+ const normalized = {
18215
+ tool: asString3(source6["tool"]) || asString3(input["tool"]) || "testers",
18216
+ run_id: asString3(source6["run_id"]) || asString3(input["run_id"]) || undefined,
18217
+ result_id: asString3(source6["result_id"]) || asString3(input["result_id"]) || undefined,
18218
+ scenario_id: asString3(source6["scenario_id"]) || asString3(input["scenario_id"]) || undefined,
18219
+ scenario_name: asString3(source6["scenario_name"]) || asString3(input["scenario_name"]) || undefined,
18220
+ project_id: asString3(source6["project_id"]) || asString3(input["project_id"]) || undefined,
18221
+ url: asString3(source6["url"]) || asString3(input["url"]) || undefined,
18222
+ page_url: asString3(source6["page_url"]) || asString3(input["page_url"]) || undefined,
18223
+ artifact_url: asString3(source6["artifact_url"]) || undefined,
18224
+ screenshot_url: asString3(source6["screenshot_url"]) || undefined,
18225
+ commit: asString3(source6["commit"]) || undefined,
18226
+ branch: asString3(source6["branch"]) || undefined
18227
+ };
18228
+ return Object.values(normalized).some(Boolean) ? normalized : undefined;
18229
+ }
18230
+ function reportTarget(input) {
18231
+ const target = asObject3(input["target"]);
18232
+ const normalized = {
18233
+ url: asString3(target["url"]) || asString3(input["target_url"]) || undefined,
18234
+ route: asString3(target["route"]) || undefined,
18235
+ selector: asString3(target["selector"]) || undefined,
18236
+ component: asString3(target["component"]) || undefined,
18237
+ browser: asString3(target["browser"]) || undefined,
18238
+ viewport: asString3(target["viewport"]) || undefined
18239
+ };
18240
+ return Object.values(normalized).some(Boolean) ? normalized : undefined;
18241
+ }
18242
+ function reportFailure(input) {
18243
+ const failure = asObject3(input["failure"]);
18244
+ const steps = stringArray(failure["steps"] ?? input["steps"], 50);
18245
+ const normalized = {
18246
+ message: truncate3(asString3(failure["message"]) || asString3(input["error"]) || asString3(input["message"]), 1000),
18247
+ expected: truncate3(asString3(failure["expected"]), 1000),
18248
+ actual: truncate3(asString3(failure["actual"]), 1000),
18249
+ stack: truncate3(asString3(failure["stack"]) || asString3(input["stack"]), 3000),
18250
+ reasoning: truncate3(asString3(failure["reasoning"]) || asString3(input["reasoning"]), 1500),
18251
+ steps: steps.length > 0 ? steps.map((step) => truncate3(step, 400)).filter(Boolean) : undefined
18252
+ };
18253
+ return Object.values(normalized).some(Boolean) ? normalized : undefined;
18254
+ }
18255
+ function artifactArray(value) {
18256
+ return objectArray(value, 12).map((item) => ({
18257
+ kind: asString3(item["kind"]) || undefined,
18258
+ label: asString3(item["label"]) || asString3(item["name"]) || undefined,
18259
+ path: asString3(item["path"]) || asString3(item["file_path"]) || undefined,
18260
+ url: asString3(item["url"]) || undefined
18261
+ })).filter((item) => item.kind || item.label || item.path || item.url);
18262
+ }
18263
+ function reportEvidence(input) {
18264
+ const evidence = asObject3(input["evidence"]);
18265
+ const logs = stringArray(evidence["logs"], 8).map((log) => truncate3(log, 1000)).filter(Boolean);
18266
+ const screenshots = artifactArray(evidence["screenshots"] ?? input["screenshots"]);
18267
+ const artifacts = artifactArray(evidence["artifacts"] ?? input["artifacts"]);
18268
+ const normalized = {
18269
+ logs: logs.length > 0 ? logs : undefined,
18270
+ screenshots: screenshots.length > 0 ? screenshots : undefined,
18271
+ artifacts: artifacts.length > 0 ? artifacts : undefined
18272
+ };
18273
+ return Object.values(normalized).some(Boolean) ? normalized : undefined;
18274
+ }
18275
+ function normalizeTesterIssueReport(value, fallbackPriority = "medium") {
18276
+ const input = asObject3(value);
18277
+ if (input["schema_version"] !== TESTERS_ISSUE_REPORT_SCHEMA_VERSION) {
18278
+ throw new Error(`Expected schema_version ${TESTERS_ISSUE_REPORT_SCHEMA_VERSION}`);
18279
+ }
18280
+ const failure = reportFailure(input);
18281
+ const source6 = reportSource(input);
18282
+ const target = reportTarget(input);
18283
+ const title = truncate3(asString3(input["title"]) || asString3(input["summary"]) || failure?.message || source6?.scenario_name || "Tester issue report", 220);
18284
+ if (!title)
18285
+ throw new Error("Tester issue report requires a title");
18286
+ const labels = [
18287
+ ...stringArray(input["labels"], 20),
18288
+ ...stringArray(input["tags"], 20)
18289
+ ].map(cleanKey).filter(Boolean);
18290
+ const report = {
18291
+ schema_version: TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
18292
+ id: asString3(input["id"]) || undefined,
18293
+ fingerprint: asString3(input["fingerprint"]) || undefined,
18294
+ title,
18295
+ summary: truncate3(asString3(input["summary"]), 1000) ?? null,
18296
+ kind: normalizeKind(input["kind"] ?? input["type"]),
18297
+ severity: normalizeSeverity(input["severity"] ?? input["priority"], fallbackPriority),
18298
+ source: source6,
18299
+ target,
18300
+ failure,
18301
+ evidence: reportEvidence(input),
18302
+ labels: labels.length > 0 ? [...new Set(labels)].slice(0, 20) : undefined,
18303
+ metadata: redactValue(asObject3(input["metadata"])),
18304
+ occurred_at: asString3(input["occurred_at"]) || asString3(input["timestamp"]) || undefined
18305
+ };
18306
+ return redactValue(report);
18307
+ }
18308
+ function fingerprintTesterIssueReport(report) {
18309
+ if (report.fingerprint)
18310
+ return `testers:${cleanKey(report.fingerprint)}`;
18311
+ const stackTop = report.failure?.stack?.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "";
18312
+ const url = normalizeUrlPattern(report.target?.url || report.source?.page_url || report.source?.url || "");
18313
+ const raw = [
18314
+ report.kind || "unknown",
18315
+ report.source?.project_id || "",
18316
+ report.source?.scenario_id || report.source?.scenario_name || "",
18317
+ url,
18318
+ report.target?.route || "",
18319
+ report.target?.selector || report.target?.component || "",
18320
+ normalizeText2(report.failure?.message || report.summary || report.title).slice(0, 240),
18321
+ normalizeText2(stackTop).slice(0, 160)
18322
+ ].join("::");
18323
+ return `testers:${createHash7("sha256").update(raw).digest("hex").slice(0, 16)}`;
18324
+ }
18325
+ function priorityForSeverity(severity, fallback) {
18326
+ return PRIORITIES3.includes(severity) ? severity : fallback;
18327
+ }
18328
+ function maxPriority(left, right) {
18329
+ return PRIORITIES3.indexOf(right) > PRIORITIES3.indexOf(left) ? right : left;
18330
+ }
18331
+ function taskTitle2(report) {
18332
+ const title = report.title.replace(/^BUG:\s*/i, "").replace(/^\[testers\]\s*/i, "");
18333
+ return `BUG: [testers] ${title}`.slice(0, 240);
18334
+ }
18335
+ function evidenceLines(report) {
18336
+ const lines = [];
18337
+ for (const item of report.evidence?.screenshots || []) {
18338
+ lines.push(`Screenshot: ${item.label || item.kind || item.path || item.url}${item.path ? ` (${item.path})` : item.url ? ` (${item.url})` : ""}`);
18339
+ }
18340
+ for (const item of report.evidence?.artifacts || []) {
18341
+ lines.push(`Artifact: ${item.label || item.kind || item.path || item.url}${item.path ? ` (${item.path})` : item.url ? ` (${item.url})` : ""}`);
18342
+ }
18343
+ for (const log of report.evidence?.logs || [])
18344
+ lines.push(`Log: ${log}`);
18345
+ return lines.slice(0, 12);
18346
+ }
18347
+ function taskDescription2(report, fingerprint2) {
18348
+ const failure = report.failure;
18349
+ const lines = [
18350
+ "Tester issue report.",
18351
+ "",
18352
+ `Schema: ${report.schema_version}`,
18353
+ `Fingerprint: ${fingerprint2}`,
18354
+ `Kind: ${report.kind || "unknown"}`,
18355
+ `Severity: ${report.severity || "medium"}`,
18356
+ report.source?.run_id ? `Run: ${report.source.run_id}` : null,
18357
+ report.source?.result_id ? `Result: ${report.source.result_id}` : null,
18358
+ report.source?.scenario_name || report.source?.scenario_id ? `Scenario: ${report.source.scenario_name || report.source.scenario_id}` : null,
18359
+ report.target?.url || report.source?.page_url || report.source?.url ? `URL: ${report.target?.url || report.source?.page_url || report.source?.url}` : null,
18360
+ report.target?.route ? `Route: ${report.target.route}` : null,
18361
+ report.target?.selector ? `Selector: ${report.target.selector}` : null,
18362
+ report.occurred_at ? `Occurred at: ${report.occurred_at}` : null,
18363
+ "",
18364
+ report.summary ? `Summary:
18365
+ ${report.summary}` : null,
18366
+ failure?.message ? `Failure:
18367
+ ${failure.message}` : null,
18368
+ failure?.expected ? `Expected:
18369
+ ${failure.expected}` : null,
18370
+ failure?.actual ? `Actual:
18371
+ ${failure.actual}` : null,
18372
+ failure?.reasoning ? `Reasoning:
18373
+ ${failure.reasoning}` : null,
18374
+ failure?.steps?.length ? `Steps:
18375
+ ${failure.steps.map((step, index) => `${index + 1}. ${step}`).join(`
18376
+ `)}` : null,
18377
+ evidenceLines(report).length ? `Evidence:
18378
+ ${evidenceLines(report).map((line) => `- ${line}`).join(`
18379
+ `)}` : null,
18380
+ failure?.stack ? `Stack:
18381
+ ${failure.stack}` : null
18382
+ ].filter((line) => line !== null);
18383
+ return lines.join(`
18384
+ `).replace(/\n{3,}/g, `
18385
+
18386
+ `).slice(0, 6000);
18387
+ }
18388
+ function taskTags(report) {
18389
+ return [...new Set([
18390
+ "bug",
18391
+ "testers",
18392
+ "tester-report",
18393
+ report.kind ? cleanKey(String(report.kind)).replace(/_/g, "-") : "unknown",
18394
+ ...report.labels || []
18395
+ ].filter(Boolean))].slice(0, 16);
18396
+ }
18397
+ function storedReportSummary(report) {
18398
+ return {
18399
+ id: report.id ?? null,
18400
+ title: report.title,
18401
+ kind: report.kind ?? "unknown",
18402
+ severity: report.severity ?? "medium",
18403
+ run_id: report.source?.run_id ?? null,
18404
+ result_id: report.source?.result_id ?? null,
18405
+ scenario_id: report.source?.scenario_id ?? null,
18406
+ scenario_name: report.source?.scenario_name ?? null,
18407
+ url: report.target?.url ?? report.source?.page_url ?? report.source?.url ?? null,
18408
+ occurred_at: report.occurred_at ?? null
18409
+ };
18410
+ }
18411
+ function testerMetadata(report, fingerprint2, previous, timestamp2) {
18412
+ const occurrenceCount = typeof previous?.["occurrence_count"] === "number" ? previous["occurrence_count"] + 1 : 1;
18413
+ const previousRecent = previous?.["recent_reports"];
18414
+ const recent = Array.isArray(previousRecent) ? previousRecent : [];
18415
+ return {
18416
+ schema_version: TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
18417
+ fingerprint: fingerprint2,
18418
+ first_seen_at: asString3(previous?.["first_seen_at"]) || timestamp2,
18419
+ last_seen_at: timestamp2,
18420
+ occurrence_count: occurrenceCount,
18421
+ latest_report: storedReportSummary(report),
18422
+ recent_reports: [...recent.slice(-4), storedReportSummary(report)]
18423
+ };
18424
+ }
18425
+ function sourceMetadata(report) {
18426
+ const url = report.target?.url || report.source?.page_url || report.source?.url || null;
18427
+ return {
18428
+ ...url ? { source_url: url, external_url: url, issue_url: url } : {},
18429
+ ...report.source?.run_id ? { tester_run_id: report.source.run_id } : {},
18430
+ ...report.source?.result_id ? { tester_result_id: report.source.result_id } : {},
18431
+ ...report.source?.scenario_id ? { tester_scenario_id: report.source.scenario_id } : {},
18432
+ ...report.source?.project_id ? { tester_project_id: report.source.project_id } : {}
18433
+ };
18434
+ }
18435
+ function findExistingTask2(fingerprint2, input, db) {
18436
+ for (const task2 of listTasks({
18437
+ include_archived: true,
18438
+ project_id: input.project_id,
18439
+ task_list_id: input.task_list_id
18440
+ }, db)) {
18441
+ const metadata = task2.metadata || {};
18442
+ const tester = asObject3(metadata["tester_issue_report"]);
18443
+ if (tester["fingerprint"] === fingerprint2)
18444
+ return task2;
18445
+ if (metadata["tester_issue_fingerprint"] === fingerprint2)
18446
+ return task2;
18447
+ if (metadata["external_ref"] === fingerprint2)
18448
+ return task2;
18449
+ }
18450
+ return null;
18451
+ }
18452
+ function commandsFor(task2) {
18453
+ return [
18454
+ "todos issues report --file tester-report.json --apply --json",
18455
+ task2 ? `todos show ${task2.id.slice(0, 8)}` : `todos list --tags tester-report --json`,
18456
+ `todos dedupe scan --threshold 0.8 --json`
18457
+ ];
18458
+ }
18459
+ function updateExistingTask(task2, report, fingerprint2, input, timestamp2, db) {
18460
+ if (input.update_existing === false)
18461
+ return { action: "matched", task: task2 };
18462
+ const previous = asObject3(task2.metadata["tester_issue_report"]);
18463
+ const severityPriority = priorityForSeverity(report.severity, input.default_priority || "medium");
18464
+ const nextStatus = task2.status === "completed" || task2.status === "cancelled" ? "pending" : task2.status;
18465
+ const action = nextStatus !== task2.status ? "regressed" : "updated";
18466
+ const updated = updateTask(task2.id, {
18467
+ version: task2.version,
18468
+ title: taskTitle2(report),
18469
+ description: taskDescription2(report, fingerprint2),
18470
+ priority: maxPriority(task2.priority, severityPriority),
18471
+ status: nextStatus,
18472
+ completed_at: nextStatus !== task2.status ? null : undefined,
18473
+ tags: [...new Set([...task2.tags, ...taskTags(report)])],
18474
+ metadata: {
18475
+ ...task2.metadata,
18476
+ ...sourceMetadata(report),
18477
+ external_ref: fingerprint2,
18478
+ tester_issue_fingerprint: fingerprint2,
18479
+ tester_issue_report: testerMetadata(report, fingerprint2, previous, timestamp2)
18480
+ },
18481
+ ...input.assigned_to !== undefined ? { assigned_to: input.assigned_to } : {},
18482
+ task_type: task2.task_type || "bug"
18483
+ }, db);
18484
+ return { action, task: updated };
18485
+ }
18486
+ function upsertTesterIssueReport(input, db) {
18487
+ const d = db || getDatabase();
18488
+ const timestamp2 = now();
18489
+ const warnings = [];
18490
+ const report = normalizeTesterIssueReport(input.report, input.default_priority || "medium");
18491
+ const fingerprint2 = fingerprintTesterIssueReport(report);
18492
+ const existing = findExistingTask2(fingerprint2, input, d);
18493
+ if (!input.apply) {
18494
+ const action = existing ? "matched" : "preview";
18495
+ return {
18496
+ schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
18497
+ local_only: true,
18498
+ dry_run: true,
18499
+ processed_at: timestamp2,
18500
+ action,
18501
+ fingerprint: fingerprint2,
18502
+ report,
18503
+ task: existing,
18504
+ warnings,
18505
+ commands: commandsFor(existing)
18506
+ };
18507
+ }
18508
+ if (existing) {
18509
+ const updated = updateExistingTask(existing, report, fingerprint2, input, timestamp2, d);
18510
+ return {
18511
+ schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
18512
+ local_only: true,
18513
+ dry_run: false,
18514
+ processed_at: timestamp2,
18515
+ action: updated.action,
18516
+ fingerprint: fingerprint2,
18517
+ report,
18518
+ task: updated.task,
18519
+ warnings,
18520
+ commands: commandsFor(updated.task)
18521
+ };
18522
+ }
18523
+ const priority = priorityForSeverity(report.severity, input.default_priority || "medium");
18524
+ const task2 = createTask({
18525
+ title: taskTitle2(report),
18526
+ description: taskDescription2(report, fingerprint2),
18527
+ priority,
18528
+ status: "pending",
18529
+ tags: taskTags(report),
18530
+ metadata: {
18531
+ ...sourceMetadata(report),
18532
+ external_ref: fingerprint2,
18533
+ tester_issue_fingerprint: fingerprint2,
18534
+ tester_issue_report: testerMetadata(report, fingerprint2, null, timestamp2),
18535
+ tester_issue_report_raw: redactValue({
18536
+ ...report,
18537
+ evidence: report.evidence ? {
18538
+ screenshots: report.evidence.screenshots,
18539
+ artifacts: report.evidence.artifacts
18540
+ } : undefined
18541
+ })
18542
+ },
18543
+ project_id: input.project_id,
18544
+ task_list_id: input.task_list_id,
18545
+ agent_id: input.agent_id,
18546
+ assigned_to: input.assigned_to,
18547
+ task_type: "bug"
18548
+ }, d);
18549
+ return {
18550
+ schema_version: TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
18551
+ local_only: true,
18552
+ dry_run: false,
18553
+ processed_at: timestamp2,
18554
+ action: "created",
18555
+ fingerprint: fingerprint2,
18556
+ report,
18557
+ task: task2,
18558
+ warnings,
18559
+ commands: commandsFor(task2)
18560
+ };
18561
+ }
18562
+ function upsertTesterIssueReports(input, db) {
18563
+ const d = db || getDatabase();
18564
+ const run = () => input.reports.map((report) => upsertTesterIssueReport({ ...input, report }, d));
18565
+ const results = input.apply ? d.transaction(run)() : run();
18566
+ const summary = {
18567
+ total: results.length,
18568
+ preview: 0,
18569
+ matched: 0,
18570
+ created: 0,
18571
+ updated: 0,
18572
+ regressed: 0
18573
+ };
18574
+ for (const result of results)
18575
+ summary[result.action]++;
18576
+ return {
18577
+ schema_version: TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION,
18578
+ local_only: true,
18579
+ dry_run: !input.apply,
18580
+ processed_at: now(),
18581
+ results,
18582
+ summary
18583
+ };
18584
+ }
18585
+ function readTesterIssueReportsPayload(value) {
18586
+ if (Array.isArray(value))
18587
+ return value;
18588
+ const record = asObject3(value);
18589
+ if (Array.isArray(record["reports"]))
18590
+ return record["reports"];
18591
+ if (Array.isArray(record["issues"]))
18592
+ return record["issues"];
18593
+ return [value];
18594
+ }
17290
18595
  // src/lib/local-notifications.ts
17291
18596
  init_database();
17292
18597
 
@@ -17389,13 +18694,13 @@ function eventSeverity(eventType) {
17389
18694
  function payloadText(payload) {
17390
18695
  return JSON.stringify(payload).toLowerCase();
17391
18696
  }
17392
- function asString3(value) {
18697
+ function asString4(value) {
17393
18698
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
17394
18699
  }
17395
18700
  function fieldMatches(allowed, value) {
17396
18701
  if (!allowed || allowed.length === 0)
17397
18702
  return true;
17398
- const stringValue = asString3(value);
18703
+ const stringValue = asString4(value);
17399
18704
  return Boolean(stringValue && allowed.includes(stringValue));
17400
18705
  }
17401
18706
  function containsMatches(needles, payload) {
@@ -17461,8 +18766,8 @@ function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules
17461
18766
  if (isQuietTime(rule.quiet_hours, timestamp2))
17462
18767
  skipped.push("quiet hours active");
17463
18768
  const matched = skipped.length === 0;
17464
- const title = asString3(payload["title"]) || asString3(payload["name"]) || input.type;
17465
- const taskId = asString3(payload["id"]) || asString3(payload["task_id"]);
18769
+ const title = asString4(payload["title"]) || asString4(payload["name"]) || input.type;
18770
+ const taskId = asString4(payload["id"]) || asString4(payload["task_id"]);
17466
18771
  const notification = {
17467
18772
  rule: rule.name,
17468
18773
  event_type: input.type,
@@ -17471,8 +18776,8 @@ function evaluateTerminalWatchRules(input, rules = listTerminalNotificationRules
17471
18776
  message: `${input.type}: ${title}`,
17472
18777
  timestamp: timestamp2,
17473
18778
  task_id: taskId,
17474
- project_id: asString3(payload["project_id"]),
17475
- agent_id: asString3(payload["agent_id"]) || asString3(payload["assigned_to"]),
18779
+ project_id: asString4(payload["project_id"]),
18780
+ agent_id: asString4(payload["agent_id"]) || asString4(payload["assigned_to"]),
17476
18781
  bell: rule.bell && severity === "critical",
17477
18782
  payload
17478
18783
  };
@@ -18122,12 +19427,12 @@ function summarizeTask(task2) {
18122
19427
  };
18123
19428
  }
18124
19429
  function overdueTasks(tasks, nowIso) {
18125
- const now2 = Date.parse(nowIso);
19430
+ const now3 = Date.parse(nowIso);
18126
19431
  return tasks.filter((task2) => {
18127
19432
  if (isTerminal(task2) || !task2.due_at)
18128
19433
  return false;
18129
19434
  const due = Date.parse(task2.due_at);
18130
- return Number.isFinite(due) && due < now2;
19435
+ return Number.isFinite(due) && due < now3;
18131
19436
  });
18132
19437
  }
18133
19438
  function isReady(task2, db) {
@@ -18460,7 +19765,7 @@ function renderLocalReportMarkdown(report) {
18460
19765
  // src/lib/local-encryption.ts
18461
19766
  init_config();
18462
19767
  init_redaction();
18463
- import { createCipheriv, createDecipheriv, createHash as createHash7, randomBytes, scryptSync, timingSafeEqual } from "crypto";
19768
+ import { createCipheriv, createDecipheriv, createHash as createHash8, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
18464
19769
  var TODOS_ENCRYPTED_VALUE_KIND = "hasna.todos.encrypted-value";
18465
19770
  var TODOS_ENCRYPTED_BRIDGE_KIND = "hasna.todos.encrypted-bridge";
18466
19771
  var TODOS_ENCRYPTION_SCHEMA_VERSION = 1;
@@ -18482,11 +19787,11 @@ class EncryptedPayloadError extends Error {
18482
19787
  super(message);
18483
19788
  }
18484
19789
  }
18485
- function now2() {
19790
+ function now3() {
18486
19791
  return new Date().toISOString();
18487
19792
  }
18488
19793
  function sha2564(value) {
18489
- return createHash7("sha256").update(value).digest("hex");
19794
+ return createHash8("sha256").update(value).digest("hex");
18490
19795
  }
18491
19796
  function normalizeProfileName(value) {
18492
19797
  const name = (value || DEFAULT_ENCRYPTION_PROFILE).trim();
@@ -18519,7 +19824,7 @@ function upsertEncryptionProfile(input) {
18519
19824
  const name = normalizeProfileName(input.name);
18520
19825
  const config = loadConfig();
18521
19826
  const existing = config.encryption_profiles?.[name];
18522
- const timestamp2 = now2();
19827
+ const timestamp2 = now3();
18523
19828
  const profile = {
18524
19829
  name,
18525
19830
  algorithm: "aes-256-gcm",
@@ -18576,7 +19881,7 @@ function encryptString(plaintext, options = {}) {
18576
19881
  return {
18577
19882
  schemaVersion: TODOS_ENCRYPTION_SCHEMA_VERSION,
18578
19883
  kind: TODOS_ENCRYPTED_VALUE_KIND,
18579
- encryptedAt: options.encryptedAt ?? now2(),
19884
+ encryptedAt: options.encryptedAt ?? now3(),
18580
19885
  profile: profile.name,
18581
19886
  key_env: profile.key_env,
18582
19887
  algorithm: "aes-256-gcm",
@@ -18611,7 +19916,7 @@ function decryptString(envelope, env = process.env) {
18611
19916
  ]).toString("utf8");
18612
19917
  const expected = Buffer.from(envelope.plaintext_sha256, "hex");
18613
19918
  const actual = Buffer.from(sha2564(plaintext), "hex");
18614
- if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
19919
+ if (expected.length !== actual.length || !timingSafeEqual2(expected, actual)) {
18615
19920
  throw new EncryptedPayloadError("decrypted payload checksum mismatch");
18616
19921
  }
18617
19922
  return plaintext;
@@ -20053,87 +21358,6 @@ function getCapableAgents(capabilities, opts, db) {
20053
21358
  return opts?.limit ? scored.slice(0, opts.limit) : scored;
20054
21359
  }
20055
21360
 
20056
- // src/db/task-lists.ts
20057
- init_types();
20058
- init_database();
20059
- function rowToTaskList2(row) {
20060
- return {
20061
- ...row,
20062
- metadata: JSON.parse(row.metadata || "{}")
20063
- };
20064
- }
20065
- function createTaskList(input, db) {
20066
- const d = db || getDatabase();
20067
- const id = uuid();
20068
- const timestamp2 = now();
20069
- const slug = input.slug || slugify(input.name);
20070
- if (!input.project_id) {
20071
- const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
20072
- if (existing) {
20073
- throw new Error(`Standalone task list with slug "${slug}" already exists`);
20074
- }
20075
- }
20076
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
20077
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp2, timestamp2]);
20078
- return getTaskList(id, d);
20079
- }
20080
- function getTaskList(id, db) {
20081
- const d = db || getDatabase();
20082
- const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
20083
- return row ? rowToTaskList2(row) : null;
20084
- }
20085
- function getTaskListBySlug(slug, projectId, db) {
20086
- const d = db || getDatabase();
20087
- let row;
20088
- if (projectId) {
20089
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
20090
- } else {
20091
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
20092
- }
20093
- return row ? rowToTaskList2(row) : null;
20094
- }
20095
- function listTaskLists(projectId, db) {
20096
- const d = db || getDatabase();
20097
- if (projectId) {
20098
- return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList2);
20099
- }
20100
- return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList2);
20101
- }
20102
- function updateTaskList(id, input, db) {
20103
- const d = db || getDatabase();
20104
- const existing = getTaskList(id, d);
20105
- if (!existing)
20106
- throw new TaskListNotFoundError(id);
20107
- const sets = ["updated_at = ?"];
20108
- const params = [now()];
20109
- if (input.name !== undefined) {
20110
- sets.push("name = ?");
20111
- params.push(input.name);
20112
- }
20113
- if (input.description !== undefined) {
20114
- sets.push("description = ?");
20115
- params.push(input.description);
20116
- }
20117
- if (input.metadata !== undefined) {
20118
- sets.push("metadata = ?");
20119
- params.push(JSON.stringify(input.metadata));
20120
- }
20121
- params.push(id);
20122
- d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
20123
- return getTaskList(id, d);
20124
- }
20125
- function deleteTaskList(id, db) {
20126
- const d = db || getDatabase();
20127
- return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
20128
- }
20129
- function ensureTaskList(name, slug, projectId, db) {
20130
- const d = db || getDatabase();
20131
- const existing = getTaskListBySlug(slug, projectId, d);
20132
- if (existing)
20133
- return existing;
20134
- return createTaskList({ name, slug, project_id: projectId }, d);
20135
- }
20136
-
20137
21361
  // src/storage/local-sqlite.ts
20138
21362
  init_database();
20139
21363
 
@@ -20702,7 +21926,7 @@ function createHybridTodosStorageAdapter(options) {
20702
21926
  }
20703
21927
 
20704
21928
  // src/storage/postgres-adapter.ts
20705
- import { randomUUID as randomUUID2 } from "crypto";
21929
+ import { randomUUID as randomUUID3 } from "crypto";
20706
21930
  function createPostgresTodosStorageAdapter(options) {
20707
21931
  const store = new PostgresJsonRecordStore(options);
20708
21932
  const adapter = {
@@ -20885,7 +22109,7 @@ async function createTask2(input, store, context) {
20885
22109
  const timestamp2 = new Date().toISOString();
20886
22110
  const shortId = input.project_id ? await nextTaskShortId2(input.project_id, store, context) : null;
20887
22111
  const task2 = {
20888
- id: randomUUID2(),
22112
+ id: randomUUID3(),
20889
22113
  short_id: shortId,
20890
22114
  project_id: input.project_id ?? context?.projectId ?? null,
20891
22115
  parent_id: input.parent_id ?? null,
@@ -21052,7 +22276,7 @@ async function getChangedSince(since, filters, store) {
21052
22276
  async function createProject2(input, store, context) {
21053
22277
  const timestamp2 = new Date().toISOString();
21054
22278
  const project = {
21055
- id: randomUUID2(),
22279
+ id: randomUUID3(),
21056
22280
  name: input.name,
21057
22281
  path: input.path,
21058
22282
  description: input.description ?? null,
@@ -21072,7 +22296,7 @@ async function updateProject2(id, input, store) {
21072
22296
  async function createPlan2(input, store, context) {
21073
22297
  const timestamp2 = new Date().toISOString();
21074
22298
  return store.upsert("plans", {
21075
- id: randomUUID2(),
22299
+ id: randomUUID3(),
21076
22300
  project_id: input.project_id ?? context?.projectId ?? null,
21077
22301
  task_list_id: input.task_list_id ?? context?.taskListId ?? null,
21078
22302
  agent_id: input.agent_id ?? context?.agentId ?? null,
@@ -21094,7 +22318,7 @@ async function registerAgent2(input, store, context) {
21094
22318
  }
21095
22319
  const timestamp2 = new Date().toISOString();
21096
22320
  const agent = {
21097
- id: existing?.id ?? randomUUID2().slice(0, 8),
22321
+ id: existing?.id ?? randomUUID3().slice(0, 8),
21098
22322
  name: input.name,
21099
22323
  description: input.description ?? existing?.description ?? null,
21100
22324
  role: input.role ?? existing?.role ?? null,
@@ -21130,7 +22354,7 @@ async function updateAgent2(id, input, store) {
21130
22354
  async function createTaskList2(input, store, context) {
21131
22355
  const timestamp2 = new Date().toISOString();
21132
22356
  return store.upsert("task_lists", {
21133
- id: randomUUID2(),
22357
+ id: randomUUID3(),
21134
22358
  project_id: input.project_id ?? context?.projectId ?? null,
21135
22359
  slug: input.slug ?? slugify2(input.name),
21136
22360
  name: input.name,
@@ -21152,7 +22376,7 @@ async function updateTaskList2(id, input, store) {
21152
22376
  async function createTemplate2(input, store, context) {
21153
22377
  const timestamp2 = new Date().toISOString();
21154
22378
  return store.upsert("templates", {
21155
- id: randomUUID2(),
22379
+ id: randomUUID3(),
21156
22380
  name: input.name,
21157
22381
  title_pattern: input.title_pattern,
21158
22382
  description: input.description ?? null,
@@ -21181,7 +22405,7 @@ async function updateTemplate2(id, input, store) {
21181
22405
  }
21182
22406
  async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentId, store, context) {
21183
22407
  const entry2 = {
21184
- id: randomUUID2(),
22408
+ id: randomUUID3(),
21185
22409
  task_id: taskId,
21186
22410
  action,
21187
22411
  field: field2 ?? null,
@@ -21194,7 +22418,7 @@ async function logTaskChange2(taskId, action, field2, oldValue, newValue, agentI
21194
22418
  }
21195
22419
  async function addComment2(input, store, context) {
21196
22420
  const comment = {
21197
- id: randomUUID2(),
22421
+ id: randomUUID3(),
21198
22422
  task_id: input.task_id,
21199
22423
  agent_id: input.agent_id ?? context?.agentId ?? null,
21200
22424
  session_id: input.session_id ?? context?.sessionId ?? null,
@@ -21369,10 +22593,10 @@ function assertRemoteAdapterCapabilities(adapter, mode) {
21369
22593
  }
21370
22594
  }
21371
22595
  // src/storage/s3-artifacts.ts
21372
- import { createHash as createHash8, createHmac } from "crypto";
22596
+ import { createHash as createHash9, createHmac as createHmac2 } from "crypto";
21373
22597
  function createTodosS3ArtifactStore(options) {
21374
22598
  const requestFetch = options.fetch ?? fetch;
21375
- const now3 = options.now ?? (() => new Date);
22599
+ const now4 = options.now ?? (() => new Date);
21376
22600
  return {
21377
22601
  objectKey: (relativePath) => buildS3ObjectKey(options.config, relativePath),
21378
22602
  objectUrl: (relativePath) => buildS3ObjectUrl(options.config, buildS3ObjectKey(options.config, relativePath)),
@@ -21394,7 +22618,7 @@ function createTodosS3ArtifactStore(options) {
21394
22618
  headers,
21395
22619
  body,
21396
22620
  credentials: options.credentials,
21397
- now: now3()
22621
+ now: now4()
21398
22622
  });
21399
22623
  const response = await requestFetch(url, { method: "PUT", headers: signed.headers, body });
21400
22624
  if (!response.ok)
@@ -21415,7 +22639,7 @@ function createTodosS3ArtifactStore(options) {
21415
22639
  service: "s3",
21416
22640
  headers: {},
21417
22641
  credentials: options.credentials,
21418
- now: now3()
22642
+ now: now4()
21419
22643
  });
21420
22644
  const response = await requestFetch(url, { method: "GET", headers: signed.headers });
21421
22645
  if (!response.ok)
@@ -21431,7 +22655,7 @@ function createTodosS3ArtifactStore(options) {
21431
22655
  service: "s3",
21432
22656
  headers: {},
21433
22657
  credentials: options.credentials,
21434
- now: now3()
22658
+ now: now4()
21435
22659
  });
21436
22660
  const response = await requestFetch(url, { method: "DELETE", headers: signed.headers });
21437
22661
  if (!response.ok && response.status !== 404)
@@ -21541,13 +22765,13 @@ function toAmzDate(date) {
21541
22765
  return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
21542
22766
  }
21543
22767
  function sha256Hex(value) {
21544
- return createHash8("sha256").update(value).digest("hex");
22768
+ return createHash9("sha256").update(value).digest("hex");
21545
22769
  }
21546
22770
  function hmac(key, value) {
21547
- return createHmac("sha256", key).update(value).digest();
22771
+ return createHmac2("sha256", key).update(value).digest();
21548
22772
  }
21549
22773
  function hmacHex(key, value) {
21550
- return createHmac("sha256", key).update(value).digest("hex");
22774
+ return createHmac2("sha256", key).update(value).digest("hex");
21551
22775
  }
21552
22776
  function getSigningKey(secretAccessKey, dateStamp, region, service) {
21553
22777
  const dateKey = hmac(`AWS4${secretAccessKey}`, dateStamp);
@@ -21559,7 +22783,7 @@ function getSigningKey(secretAccessKey, dateStamp, region, service) {
21559
22783
  init_database();
21560
22784
  async function uploadRunArtifactsToS3(options) {
21561
22785
  const db = options.db ?? getDatabase();
21562
- const now3 = options.now ?? (() => new Date);
22786
+ const now4 = options.now ?? (() => new Date);
21563
22787
  const result = emptyResult();
21564
22788
  for (const artifact of listRunArtifacts(db, options.filter)) {
21565
22789
  try {
@@ -21598,7 +22822,7 @@ async function uploadRunArtifactsToS3(options) {
21598
22822
  url: ref.url,
21599
22823
  sha256: content.sha256,
21600
22824
  size_bytes: content.size_bytes,
21601
- uploaded_at: now3().toISOString()
22825
+ uploaded_at: now4().toISOString()
21602
22826
  };
21603
22827
  updateArtifactMetadata(db, artifact.id, {
21604
22828
  ...metadata,
@@ -21673,7 +22897,7 @@ function planRunArtifactsS3Sync(options) {
21673
22897
  }
21674
22898
  async function downloadRunArtifactsFromS3(options) {
21675
22899
  const db = options.db ?? getDatabase();
21676
- const now3 = options.now ?? (() => new Date);
22900
+ const now4 = options.now ?? (() => new Date);
21677
22901
  const result = emptyResult();
21678
22902
  for (const artifact of listRunArtifacts(db, options.filter)) {
21679
22903
  try {
@@ -21709,7 +22933,7 @@ async function downloadRunArtifactsFromS3(options) {
21709
22933
  ...metadata,
21710
22934
  remote_artifact_store: {
21711
22935
  ...remote,
21712
- downloaded_at: now3().toISOString()
22936
+ downloaded_at: now4().toISOString()
21713
22937
  }
21714
22938
  });
21715
22939
  result.downloaded += 1;
@@ -22728,7 +23952,7 @@ function parseObject2(value) {
22728
23952
  function normalizeTags(tags) {
22729
23953
  return [...new Set((tags || []).map((tag) => tag.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
22730
23954
  }
22731
- function normalizeText2(value) {
23955
+ function normalizeText3(value) {
22732
23956
  if (value === undefined)
22733
23957
  return null;
22734
23958
  const trimmed = value.trim();
@@ -22783,9 +24007,9 @@ function createKnowledgeRecord(input, db) {
22783
24007
  id,
22784
24008
  input.record_type,
22785
24009
  title,
22786
- normalizeText2(input.content),
22787
- normalizeText2(input.decision),
22788
- normalizeText2(input.rationale),
24010
+ normalizeText3(input.content),
24011
+ normalizeText3(input.decision),
24012
+ normalizeText3(input.rationale),
22789
24013
  JSON.stringify(input.alternatives || []),
22790
24014
  resolveKnownId("tasks", input.task_id, d),
22791
24015
  resolveKnownId("projects", input.project_id, d),
@@ -22980,7 +24204,7 @@ function parseObject3(value) {
22980
24204
  function normalizeTags2(tags) {
22981
24205
  return [...new Set((tags || []).flatMap((tag) => tag.split(",")).map((tag) => tag.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
22982
24206
  }
22983
- function normalizeText3(value) {
24207
+ function normalizeText4(value) {
22984
24208
  if (value === null || value === undefined)
22985
24209
  return null;
22986
24210
  const trimmed = value.trim();
@@ -23049,13 +24273,13 @@ function createRisk(input, db) {
23049
24273
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
23050
24274
  id,
23051
24275
  title,
23052
- normalizeText3(input.description),
24276
+ normalizeText4(input.description),
23053
24277
  status,
23054
24278
  severity,
23055
24279
  probability,
23056
- normalizeText3(input.owner),
23057
- normalizeText3(input.mitigation),
23058
- normalizeText3(input.due_at),
24280
+ normalizeText4(input.owner),
24281
+ normalizeText4(input.mitigation),
24282
+ normalizeText4(input.due_at),
23059
24283
  resolveKnownId2("projects", input.project_id, d),
23060
24284
  resolveKnownId2("plans", input.plan_id, d),
23061
24285
  resolveKnownId2("tasks", input.task_id, d),
@@ -23090,7 +24314,7 @@ function updateRisk(id, input, db) {
23090
24314
  }
23091
24315
  if (input.description !== undefined) {
23092
24316
  sets.push("description = ?");
23093
- params.push(normalizeText3(input.description));
24317
+ params.push(normalizeText4(input.description));
23094
24318
  }
23095
24319
  if (input.status !== undefined) {
23096
24320
  assertStatus(input.status);
@@ -23111,15 +24335,15 @@ function updateRisk(id, input, db) {
23111
24335
  }
23112
24336
  if (input.owner !== undefined) {
23113
24337
  sets.push("owner = ?");
23114
- params.push(normalizeText3(input.owner));
24338
+ params.push(normalizeText4(input.owner));
23115
24339
  }
23116
24340
  if (input.mitigation !== undefined) {
23117
24341
  sets.push("mitigation = ?");
23118
- params.push(normalizeText3(input.mitigation));
24342
+ params.push(normalizeText4(input.mitigation));
23119
24343
  }
23120
24344
  if (input.due_at !== undefined) {
23121
24345
  sets.push("due_at = ?");
23122
- params.push(normalizeText3(input.due_at));
24346
+ params.push(normalizeText4(input.due_at));
23123
24347
  }
23124
24348
  if (input.project_id !== undefined) {
23125
24349
  sets.push("project_id = ?");
@@ -23640,7 +24864,7 @@ function renderRetrospectiveMarkdown(record) {
23640
24864
  }
23641
24865
  // src/lib/project-bootstrap.ts
23642
24866
  init_database();
23643
- import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
24867
+ import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
23644
24868
  import { basename as basename2, dirname as dirname7, resolve as resolve9 } from "path";
23645
24869
  function safeStat(path) {
23646
24870
  try {
@@ -23659,7 +24883,7 @@ function canonicalPath(input) {
23659
24883
  function findUp(start, marker) {
23660
24884
  let current = canonicalPath(start);
23661
24885
  while (true) {
23662
- if (existsSync8(resolve9(current, marker)))
24886
+ if (existsSync9(resolve9(current, marker)))
23663
24887
  return current;
23664
24888
  const parent = dirname7(current);
23665
24889
  if (parent === current)
@@ -23671,7 +24895,7 @@ function readPackageJson2(path) {
23671
24895
  if (!path)
23672
24896
  return null;
23673
24897
  const file = resolve9(path, "package.json");
23674
- if (!existsSync8(file))
24898
+ if (!existsSync9(file))
23675
24899
  return null;
23676
24900
  try {
23677
24901
  const parsed = JSON.parse(readFileSync7(file, "utf-8"));
@@ -23693,7 +24917,7 @@ function workspaceMarker(root, rootPackage) {
23693
24917
  if (rootPackage?.workspaces)
23694
24918
  markers.push("package.json#workspaces");
23695
24919
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
23696
- if (existsSync8(resolve9(root, marker)))
24920
+ if (existsSync9(resolve9(root, marker)))
23697
24921
  markers.push(marker);
23698
24922
  }
23699
24923
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -23801,7 +25025,7 @@ function getProjectByPathForBootstrap(path, db) {
23801
25025
  }
23802
25026
  // src/db/api-keys.ts
23803
25027
  init_database();
23804
- import { createHash as createHash9, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
25028
+ import { createHash as createHash10, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
23805
25029
  function rowToRecord(row) {
23806
25030
  return {
23807
25031
  id: row.id,
@@ -23815,12 +25039,12 @@ function rowToRecord(row) {
23815
25039
  };
23816
25040
  }
23817
25041
  function hashApiKey(key) {
23818
- return createHash9("sha256").update(key).digest("hex");
25042
+ return createHash10("sha256").update(key).digest("hex");
23819
25043
  }
23820
25044
  function safeEqualHex(a, b) {
23821
25045
  if (a.length !== b.length)
23822
25046
  return false;
23823
- return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
25047
+ return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
23824
25048
  }
23825
25049
  function generatePlaintextKey() {
23826
25050
  return `tdos_${randomBytes2(32).toString("base64url")}`;
@@ -24006,18 +25230,18 @@ var gatherTrainingData = async (options = {}) => {
24006
25230
  };
24007
25231
  // src/lib/model-config.ts
24008
25232
  init_sync_utils();
24009
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
24010
- import { join as join9 } from "path";
25233
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
25234
+ import { join as join10 } from "path";
24011
25235
  var DEFAULT_MODEL = "gpt-4o-mini";
24012
25236
  function getConfigDir() {
24013
25237
  return getTodosGlobalDir();
24014
25238
  }
24015
25239
  function getConfigPath2() {
24016
- return join9(getConfigDir(), "config.json");
25240
+ return join10(getConfigDir(), "config.json");
24017
25241
  }
24018
25242
  function readConfig() {
24019
25243
  const configPath = getConfigPath2();
24020
- if (!existsSync9(configPath))
25244
+ if (!existsSync10(configPath))
24021
25245
  return {};
24022
25246
  try {
24023
25247
  const raw = readFileSync8(configPath, "utf-8");
@@ -24028,7 +25252,7 @@ function readConfig() {
24028
25252
  }
24029
25253
  function writeConfig(config) {
24030
25254
  const configDir = getConfigDir();
24031
- if (!existsSync9(configDir)) {
25255
+ if (!existsSync10(configDir)) {
24032
25256
  mkdirSync8(configDir, { recursive: true });
24033
25257
  }
24034
25258
  writeFileSync6(getConfigPath2(), JSON.stringify(config, null, 2) + `
@@ -24753,7 +25977,7 @@ CLI equivalent: \`${r.equivalent_cli}\`
24753
25977
  `);
24754
25978
  }
24755
25979
  // src/lib/verification-providers.ts
24756
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
25980
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
24757
25981
  init_database();
24758
25982
  init_config();
24759
25983
  init_redaction();
@@ -24919,7 +26143,7 @@ Timed out after ${provider.timeout_ms}ms`);
24919
26143
  };
24920
26144
  }
24921
26145
  function runCiLogProvider(input) {
24922
- const text = input.log_text ?? (input.log_path && existsSync10(input.log_path) ? readFileSync9(input.log_path, "utf-8") : "");
26146
+ const text = input.log_text ?? (input.log_path && existsSync11(input.log_path) ? readFileSync9(input.log_path, "utf-8") : "");
24923
26147
  return {
24924
26148
  status: classifyLog(text),
24925
26149
  attempts: 1,
@@ -24931,7 +26155,7 @@ function runBrowserProvider(input) {
24931
26155
  if (!input.artifact_path) {
24932
26156
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
24933
26157
  }
24934
- if (!existsSync10(input.artifact_path)) {
26158
+ if (!existsSync11(input.artifact_path)) {
24935
26159
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
24936
26160
  }
24937
26161
  return {
@@ -25529,11 +26753,11 @@ function buildResourceSnapshot(uri, staleMs = DEFAULT_STALE_MS) {
25529
26753
  content_hash: hashContent(content)
25530
26754
  };
25531
26755
  }
25532
- function isSnapshotStale(snapshot, now3 = new Date) {
26756
+ function isSnapshotStale(snapshot, now4 = new Date) {
25533
26757
  const staleAt = Date.parse(snapshot.stale_after);
25534
26758
  if (!Number.isFinite(staleAt))
25535
26759
  return true;
25536
- return now3.getTime() >= staleAt;
26760
+ return now4.getTime() >= staleAt;
25537
26761
  }
25538
26762
  function subscribeResource(uri, agentId) {
25539
26763
  const key = `${uri}:${agentId || "*"}`;
@@ -25570,21 +26794,21 @@ function resourceDiagnostics() {
25570
26794
  };
25571
26795
  }
25572
26796
  // src/lib/sandbox-profiles.ts
25573
- import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync10 } from "fs";
25574
- import { join as join10, dirname as dirname9 } from "path";
26797
+ import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync10 } from "fs";
26798
+ import { join as join11, dirname as dirname9 } from "path";
25575
26799
  var SANDBOX_PROFILE_VERSION = "todos.sandbox-profile.v1";
25576
26800
  function getProfilesPath() {
25577
26801
  if (process.env["TODOS_SANDBOX_PROFILES_PATH"]) {
25578
26802
  return process.env["TODOS_SANDBOX_PROFILES_PATH"];
25579
26803
  }
25580
- const localDir = join10(process.cwd(), ".todos");
25581
- const local = join10(localDir, "sandbox-profiles.json");
25582
- if (existsSync11(localDir))
26804
+ const localDir = join11(process.cwd(), ".todos");
26805
+ const local = join11(localDir, "sandbox-profiles.json");
26806
+ if (existsSync12(localDir))
25583
26807
  return local;
25584
- if (existsSync11(local))
26808
+ if (existsSync12(local))
25585
26809
  return local;
25586
26810
  const home = process.env["HOME"] || "~";
25587
- return join10(home, ".hasna", "todos", "sandbox-profiles.json");
26811
+ return join11(home, ".hasna", "todos", "sandbox-profiles.json");
25588
26812
  }
25589
26813
  var cached2 = null;
25590
26814
  function resetSandboxProfileCache() {
@@ -25616,7 +26840,7 @@ function loadSandboxProfiles() {
25616
26840
  if (cached2)
25617
26841
  return cached2;
25618
26842
  const path = getProfilesPath();
25619
- if (!existsSync11(path)) {
26843
+ if (!existsSync12(path)) {
25620
26844
  cached2 = getDefaultSandboxProfiles();
25621
26845
  return cached2;
25622
26846
  }
@@ -25989,7 +27213,7 @@ function getDefaultAgentAdapters() {
25989
27213
  }
25990
27214
  function resetAgentAdapterCache() {}
25991
27215
  // src/lib/git-traceability.ts
25992
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
27216
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
25993
27217
  import { spawnSync as spawnSync2 } from "child_process";
25994
27218
  import { resolve as resolve11 } from "path";
25995
27219
  var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
@@ -26035,7 +27259,7 @@ function inspectGitCommit(sha, cwd) {
26035
27259
  }
26036
27260
  function loadCiSnapshot(path) {
26037
27261
  const target = path ? resolve11(path) : resolve11(process.cwd(), ".todos", "ci-snapshot.json");
26038
- if (!existsSync12(target))
27262
+ if (!existsSync13(target))
26039
27263
  return null;
26040
27264
  try {
26041
27265
  const parsed = JSON.parse(readFileSync11(target, "utf8"));
@@ -26131,8 +27355,8 @@ function formatTraceabilityReport(report) {
26131
27355
  `);
26132
27356
  }
26133
27357
  // src/lib/mention-resolver.ts
26134
- import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
26135
- import { basename as basename3, isAbsolute, join as join11, relative as relative4, resolve as resolve12, sep } from "path";
27358
+ import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
27359
+ import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as resolve12, sep } from "path";
26136
27360
  init_database();
26137
27361
  var PREFIXES = {
26138
27362
  file: "file",
@@ -26283,7 +27507,7 @@ function resolveFile(parsed, workspace) {
26283
27507
  return resolution;
26284
27508
  }
26285
27509
  resolution.path = relPath;
26286
- if (!existsSync13(absolutePath)) {
27510
+ if (!existsSync14(absolutePath)) {
26287
27511
  resolution.warnings.push("file does not exist in the local workspace");
26288
27512
  return resolution;
26289
27513
  }
@@ -26316,7 +27540,7 @@ function walkSourceFiles(root, current = root, files = []) {
26316
27540
  if (SKIP_DIRS.has(entry2.name))
26317
27541
  continue;
26318
27542
  }
26319
- const absolutePath = join11(current, entry2.name);
27543
+ const absolutePath = join12(current, entry2.name);
26320
27544
  if (entry2.isDirectory()) {
26321
27545
  if (!SKIP_DIRS.has(entry2.name))
26322
27546
  walkSourceFiles(root, absolutePath, files);
@@ -28189,9 +29413,9 @@ function getAdapterDocsFingerprint() {
28189
29413
  }
28190
29414
  // src/lib/inbox-intake.ts
28191
29415
  init_database();
28192
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
29416
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
28193
29417
  import { basename as basename4 } from "path";
28194
- import { createHash as createHash10 } from "crypto";
29418
+ import { createHash as createHash11 } from "crypto";
28195
29419
  init_secret_redaction();
28196
29420
  var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
28197
29421
  var INTAKE_SOURCE_TYPES = [
@@ -28204,7 +29428,7 @@ var INTAKE_SOURCE_TYPES = [
28204
29428
  ];
28205
29429
  var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
28206
29430
  function fingerprint2(text) {
28207
- return createHash10("sha256").update(text).digest("hex").slice(0, 16);
29431
+ return createHash11("sha256").update(text).digest("hex").slice(0, 16);
28208
29432
  }
28209
29433
  function loadRawContent(input) {
28210
29434
  if (input.github_url) {
@@ -28235,7 +29459,7 @@ function loadRawContent(input) {
28235
29459
  }
28236
29460
  }
28237
29461
  if (input.file_path) {
28238
- if (!existsSync14(input.file_path))
29462
+ if (!existsSync15(input.file_path))
28239
29463
  throw new Error(`File not found: ${input.file_path}`);
28240
29464
  const raw = readFileSync13(input.file_path, "utf8");
28241
29465
  const name = basename4(input.file_path).toLowerCase();
@@ -28853,7 +30077,7 @@ function formatNlIntakePreviewText(preview) {
28853
30077
  }
28854
30078
  // src/lib/issue-importers.ts
28855
30079
  init_database();
28856
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
30080
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
28857
30081
  var ISSUE_IMPORT_SCHEMA = "todos.issue_import.v1";
28858
30082
  var ISSUE_SOURCES = ["github", "linear", "jira", "auto"];
28859
30083
  var GITHUB_LABEL_PRIORITY = {
@@ -29072,7 +30296,7 @@ function parseIssueExport(data, source9 = "auto") {
29072
30296
  return normalized;
29073
30297
  }
29074
30298
  function loadIssueExportFromFile(path) {
29075
- if (!existsSync15(path))
30299
+ if (!existsSync16(path))
29076
30300
  throw new Error(`File not found: ${path}`);
29077
30301
  return JSON.parse(readFileSync14(path, "utf8"));
29078
30302
  }
@@ -29232,8 +30456,8 @@ todos import issues ./linear.json --source linear --dry-run
29232
30456
  // src/lib/run-records.ts
29233
30457
  init_database();
29234
30458
  init_secret_redaction();
29235
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
29236
- import { join as join12, dirname as dirname10 } from "path";
30459
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
30460
+ import { join as join13, dirname as dirname10 } from "path";
29237
30461
  var RUN_RECORD_SCHEMA = "todos.run_record.v1";
29238
30462
  var RUN_RECORD_STATUSES = ["active", "completed", "failed", "archived"];
29239
30463
  function parseJsonArray3(raw, fallback = []) {
@@ -29426,7 +30650,7 @@ function buildRunReplayBundle(id, db) {
29426
30650
  }
29427
30651
  function exportRunReplay(id, outputPath, db) {
29428
30652
  const bundle = buildRunReplayBundle(id, db);
29429
- const path = outputPath ?? join12(process.cwd(), ".todos", "replays", `${id.slice(0, 8)}.json`);
30653
+ const path = outputPath ?? join13(process.cwd(), ".todos", "replays", `${id.slice(0, 8)}.json`);
29430
30654
  mkdirSync11(dirname10(path), { recursive: true });
29431
30655
  writeFileSync9(path, JSON.stringify(bundle, null, 2));
29432
30656
  const d = db || getDatabase();
@@ -29466,16 +30690,16 @@ function formatRunRecordMarkdown(record) {
29466
30690
  `;
29467
30691
  }
29468
30692
  function getDefaultReplayDir() {
29469
- const local = join12(process.cwd(), ".todos", "replays");
29470
- if (existsSync16(join12(process.cwd(), ".todos")))
30693
+ const local = join13(process.cwd(), ".todos", "replays");
30694
+ if (existsSync17(join13(process.cwd(), ".todos")))
29471
30695
  return local;
29472
30696
  const home = process.env["HOME"] || "~";
29473
- return join12(home, ".hasna", "todos", "replays");
30697
+ return join13(home, ".hasna", "todos", "replays");
29474
30698
  }
29475
30699
  // src/lib/release-checks.ts
29476
30700
  init_secret_redaction();
29477
- import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
29478
- import { join as join13, relative as relative5 } from "path";
30701
+ import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
30702
+ import { join as join14, relative as relative5 } from "path";
29479
30703
  var RELEASE_CHECK_SCHEMA = "todos.release_check.v1";
29480
30704
  var FORBIDDEN_DIST_PATTERNS = [
29481
30705
  {
@@ -29488,16 +30712,16 @@ var FORBIDDEN_DIST_PATTERNS = [
29488
30712
  ];
29489
30713
  var REQUIRED_BINS = ["todos", "todos-mcp", "todos-serve"];
29490
30714
  function readPackageJson3(root) {
29491
- const path = join13(root, "package.json");
29492
- if (!existsSync17(path))
30715
+ const path = join14(root, "package.json");
30716
+ if (!existsSync18(path))
29493
30717
  throw new Error(`package.json not found in ${root}`);
29494
30718
  return JSON.parse(readFileSync15(path, "utf8"));
29495
30719
  }
29496
30720
  function walkFiles(dir, acc = []) {
29497
- if (!existsSync17(dir))
30721
+ if (!existsSync18(dir))
29498
30722
  return acc;
29499
30723
  for (const entry2 of readdirSync3(dir)) {
29500
- const full = join13(dir, entry2);
30724
+ const full = join14(dir, entry2);
29501
30725
  const st = statSync5(full);
29502
30726
  if (st.isDirectory())
29503
30727
  walkFiles(full, acc);
@@ -29515,8 +30739,8 @@ function auditPackageContents(root) {
29515
30739
  checks.push({ id: "files_dist", severity: "error", message: "package.json files must include dist" });
29516
30740
  }
29517
30741
  for (const pattern of files) {
29518
- const target = join13(root, pattern);
29519
- if (!existsSync17(target)) {
30742
+ const target = join14(root, pattern);
30743
+ if (!existsSync18(target)) {
29520
30744
  checks.push({ id: `files_missing_${pattern}`, severity: "error", message: `Published file path missing: ${pattern}` });
29521
30745
  }
29522
30746
  }
@@ -29530,8 +30754,8 @@ function auditPackageContents(root) {
29530
30754
  checks.push({ id: `bin_${name}`, severity: "error", message: `Missing bin entry: ${name}` });
29531
30755
  continue;
29532
30756
  }
29533
- const binPath = join13(root, rel);
29534
- if (!existsSync17(binPath)) {
30757
+ const binPath = join14(root, rel);
30758
+ if (!existsSync18(binPath)) {
29535
30759
  checks.push({ id: `bin_path_${name}`, severity: "error", message: `Bin file missing: ${rel}` });
29536
30760
  } else {
29537
30761
  checks.push({ id: `bin_ok_${name}`, severity: "info", message: `Bin present: ${name} \u2192 ${rel}` });
@@ -29548,8 +30772,8 @@ function auditPackageContents(root) {
29548
30772
  }
29549
30773
  function scanDistArtifacts(root) {
29550
30774
  const checks = [];
29551
- const distDir = join13(root, "dist");
29552
- if (!existsSync17(distDir)) {
30775
+ const distDir = join14(root, "dist");
30776
+ if (!existsSync18(distDir)) {
29553
30777
  checks.push({ id: "dist_missing", severity: "error", message: "dist/ directory not found \u2014 run bun run build" });
29554
30778
  return checks;
29555
30779
  }
@@ -29855,8 +31079,8 @@ function renderReleaseNotesMarkdown(document) {
29855
31079
  // src/lib/db-backup.ts
29856
31080
  init_database();
29857
31081
  init_migrations();
29858
- import { existsSync as existsSync18, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
29859
- import { dirname as dirname11, join as join14, resolve as resolve13 } from "path";
31082
+ import { existsSync as existsSync19, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
31083
+ import { dirname as dirname11, join as join15, resolve as resolve13 } from "path";
29860
31084
  import { Database as Database3 } from "bun:sqlite";
29861
31085
  var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
29862
31086
  function resolveDbPath(dbPath) {
@@ -29873,7 +31097,7 @@ function resolveDbPath(dbPath) {
29873
31097
  }
29874
31098
  function backupDatabase(outputPath, sourcePath) {
29875
31099
  const source9 = resolveDbPath(sourcePath);
29876
- if (!existsSync18(source9))
31100
+ if (!existsSync19(source9))
29877
31101
  throw new Error(`Database not found: ${source9}`);
29878
31102
  mkdirSync12(dirname11(outputPath), { recursive: true });
29879
31103
  closeDatabase();
@@ -29896,7 +31120,7 @@ function backupDatabase(outputPath, sourcePath) {
29896
31120
  };
29897
31121
  }
29898
31122
  function restoreDatabase(backupPath, targetPath) {
29899
- if (!existsSync18(backupPath))
31123
+ if (!existsSync19(backupPath))
29900
31124
  throw new Error(`Backup not found: ${backupPath}`);
29901
31125
  const integrity = checkDatabaseIntegrity(backupPath);
29902
31126
  if (!integrity.ok) {
@@ -29923,7 +31147,7 @@ function restoreDatabase(backupPath, targetPath) {
29923
31147
  function checkDatabaseIntegrity(dbPath) {
29924
31148
  const path = dbPath ? resolve13(dbPath) : resolveDbPath();
29925
31149
  const errors = [];
29926
- if (!existsSync18(path)) {
31150
+ if (!existsSync19(path)) {
29927
31151
  return {
29928
31152
  schema_version: DB_BACKUP_SCHEMA,
29929
31153
  path,
@@ -30022,11 +31246,11 @@ function migrationDryRun(dbPath) {
30022
31246
  function defaultBackupPath(dbPath) {
30023
31247
  const base = dbPath ? dirname11(resolve13(dbPath)) : dirname11(resolveDbPath());
30024
31248
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
30025
- return join14(base, "backups", `todos-${stamp}.db`);
31249
+ return join15(base, "backups", `todos-${stamp}.db`);
30026
31250
  }
30027
31251
  function readBackupManifest(backupPath) {
30028
31252
  const manifestPath = `${backupPath}.json`;
30029
- if (!existsSync18(manifestPath))
31253
+ if (!existsSync19(manifestPath))
30030
31254
  return null;
30031
31255
  try {
30032
31256
  return JSON.parse(readFileSync16(manifestPath, "utf8"));
@@ -30054,7 +31278,10 @@ var SCHEMA_ENTITIES = [
30054
31278
  "verification_evidence",
30055
31279
  "handoff",
30056
31280
  "import_export_bundle",
30057
- "mcp_response"
31281
+ "mcp_response",
31282
+ "tester_issue_report",
31283
+ "tester_issue_report_result",
31284
+ "tester_issue_report_batch_result"
30058
31285
  ];
30059
31286
  var ISO_DATE = { type: "string", description: "ISO-8601 timestamp" };
30060
31287
  function def(entity, schemaVersion, title, required, properties) {
@@ -30184,6 +31411,81 @@ var JSON_SCHEMAS = {
30184
31411
  }
30185
31412
  },
30186
31413
  is_error: { type: "boolean" }
31414
+ }),
31415
+ tester_issue_report: def("tester_issue_report", "testers.issue_report.v1", "TesterIssueReport", ["schema_version", "title"], {
31416
+ schema_version: { type: "string", enum: ["testers.issue_report.v1"] },
31417
+ id: { type: "string" },
31418
+ fingerprint: { type: "string" },
31419
+ title: { type: "string" },
31420
+ summary: { type: ["string", "null"] },
31421
+ kind: { type: "string" },
31422
+ severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
31423
+ source: {
31424
+ type: "object",
31425
+ additionalProperties: true,
31426
+ properties: {
31427
+ tool: { type: "string" },
31428
+ run_id: { type: "string" },
31429
+ result_id: { type: "string" },
31430
+ scenario_id: { type: "string" },
31431
+ scenario_name: { type: "string" },
31432
+ project_id: { type: "string" },
31433
+ url: { type: "string" },
31434
+ page_url: { type: "string" },
31435
+ artifact_url: { type: "string" },
31436
+ screenshot_url: { type: "string" },
31437
+ commit: { type: "string" },
31438
+ branch: { type: "string" }
31439
+ }
31440
+ },
31441
+ target: {
31442
+ type: "object",
31443
+ additionalProperties: true,
31444
+ properties: {
31445
+ url: { type: "string" },
31446
+ route: { type: "string" },
31447
+ selector: { type: "string" },
31448
+ component: { type: "string" },
31449
+ browser: { type: "string" },
31450
+ viewport: { type: "string" }
31451
+ }
31452
+ },
31453
+ failure: {
31454
+ type: "object",
31455
+ additionalProperties: true,
31456
+ properties: {
31457
+ message: { type: "string" },
31458
+ expected: { type: "string" },
31459
+ actual: { type: "string" },
31460
+ stack: { type: "string" },
31461
+ reasoning: { type: "string" },
31462
+ steps: { type: "array", items: { type: "string" } }
31463
+ }
31464
+ },
31465
+ evidence: { type: "object", additionalProperties: true },
31466
+ labels: { type: "array", items: { type: "string" } },
31467
+ metadata: { type: "object", additionalProperties: true },
31468
+ occurred_at: ISO_DATE
31469
+ }),
31470
+ 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"], {
31471
+ schema_version: { type: "string", enum: ["todos.tester_issue_report_result.v1"] },
31472
+ local_only: { type: "boolean" },
31473
+ dry_run: { type: "boolean" },
31474
+ processed_at: ISO_DATE,
31475
+ action: { type: "string", enum: ["preview", "matched", "created", "updated", "regressed"] },
31476
+ fingerprint: { type: "string" },
31477
+ report: { type: "object", additionalProperties: true },
31478
+ task: { type: ["object", "null"], additionalProperties: true },
31479
+ warnings: { type: "array", items: { type: "string" } },
31480
+ commands: { type: "array", items: { type: "string" } }
31481
+ }),
31482
+ 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"], {
31483
+ schema_version: { type: "string", enum: ["todos.tester_issue_report_batch_result.v1"] },
31484
+ local_only: { type: "boolean" },
31485
+ dry_run: { type: "boolean" },
31486
+ processed_at: ISO_DATE,
31487
+ results: { type: "array", items: { type: "object", additionalProperties: true } },
31488
+ summary: { type: "object", additionalProperties: true }
30187
31489
  })
30188
31490
  };
30189
31491
  var SCHEMA_CONTRACT_FIXTURES = {
@@ -30264,6 +31566,47 @@ var SCHEMA_CONTRACT_FIXTURES = {
30264
31566
  mcp_response: {
30265
31567
  schema_version: "todos.mcp_response.v1",
30266
31568
  content: [{ type: "text", text: "ok" }]
31569
+ },
31570
+ tester_issue_report: {
31571
+ schema_version: "testers.issue_report.v1",
31572
+ title: "Checkout button fails",
31573
+ kind: "assertion_failure",
31574
+ severity: "high",
31575
+ fingerprint: "checkout-button-1234",
31576
+ source: {
31577
+ tool: "testers",
31578
+ run_id: "run-001",
31579
+ scenario_id: "scenario-001",
31580
+ url: "https://preview.example.com/checkout"
31581
+ },
31582
+ failure: {
31583
+ message: "Expected checkout button to become enabled",
31584
+ steps: ["Open checkout", "Fill required fields"]
31585
+ },
31586
+ labels: ["checkout", "regression"]
31587
+ },
31588
+ tester_issue_report_result: {
31589
+ schema_version: "todos.tester_issue_report_result.v1",
31590
+ local_only: true,
31591
+ dry_run: false,
31592
+ processed_at: "2026-01-01T00:00:00.000Z",
31593
+ action: "created",
31594
+ fingerprint: "testers:checkout-button-1234",
31595
+ report: {
31596
+ schema_version: "testers.issue_report.v1",
31597
+ title: "Checkout button fails"
31598
+ },
31599
+ task: null,
31600
+ warnings: [],
31601
+ commands: []
31602
+ },
31603
+ tester_issue_report_batch_result: {
31604
+ schema_version: "todos.tester_issue_report_batch_result.v1",
31605
+ local_only: true,
31606
+ dry_run: false,
31607
+ processed_at: "2026-01-01T00:00:00.000Z",
31608
+ results: [],
31609
+ summary: { total: 0, preview: 0, matched: 0, created: 0, updated: 0, regressed: 0 }
30267
31610
  }
30268
31611
  };
30269
31612
  function typeMatches(value, expected) {
@@ -30427,15 +31770,15 @@ ${SCHEMA_ENTITIES.map((e) => `- **${e}**: \`${JSON_SCHEMAS[e].schema_version}\``
30427
31770
  }
30428
31771
  function exportSchemasToDirectory(dir) {
30429
31772
  const { mkdirSync: mkdirSync13, writeFileSync: writeFileSync11 } = __require("fs");
30430
- const { join: join15 } = __require("path");
31773
+ const { join: join16 } = __require("path");
30431
31774
  mkdirSync13(dir, { recursive: true });
30432
31775
  const written = [];
30433
31776
  for (const entity of SCHEMA_ENTITIES) {
30434
- const path = join15(dir, `${entity}.${JSON_SCHEMAS[entity].schema_version.replace(/\./g, "-")}.json`);
31777
+ const path = join16(dir, `${entity}.${JSON_SCHEMAS[entity].schema_version.replace(/\./g, "-")}.json`);
30435
31778
  writeFileSync11(path, JSON.stringify(JSON_SCHEMAS[entity], null, 2));
30436
31779
  written.push(path);
30437
31780
  }
30438
- const catalogPath = join15(dir, "catalog.json");
31781
+ const catalogPath = join16(dir, "catalog.json");
30439
31782
  writeFileSync11(catalogPath, JSON.stringify({
30440
31783
  catalog_version: JSON_SCHEMA_CATALOG_VERSION,
30441
31784
  semver: SCHEMA_SEMVER,
@@ -30641,6 +31984,9 @@ function hasFts(db) {
30641
31984
  function escapeFtsQuery(q) {
30642
31985
  return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
30643
31986
  }
31987
+ function shouldUseFts(q) {
31988
+ return /^[\p{L}\p{N}_\-\s]+$/u.test(q);
31989
+ }
30644
31990
  function searchTasks(options, projectId, taskListId, db) {
30645
31991
  const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
30646
31992
  const d = db || getDatabase();
@@ -30649,7 +31995,8 @@ function searchTasks(options, projectId, taskListId, db) {
30649
31995
  let sql;
30650
31996
  const raw = opts.query?.trim() ?? "";
30651
31997
  const q = raw === "*" ? "" : raw;
30652
- if (hasFts(d) && q) {
31998
+ const useFts = hasFts(d) && q && shouldUseFts(q);
31999
+ if (useFts) {
30653
32000
  const ftsQuery = escapeFtsQuery(q);
30654
32001
  sql = `SELECT t.* FROM tasks t
30655
32002
  INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
@@ -30657,8 +32004,16 @@ function searchTasks(options, projectId, taskListId, db) {
30657
32004
  params.push(ftsQuery);
30658
32005
  } else if (q) {
30659
32006
  const pattern = `%${q}%`;
30660
- sql = `SELECT * FROM tasks t WHERE (t.title LIKE ? OR t.description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?))`;
30661
- params.push(pattern, pattern, pattern);
32007
+ sql = `SELECT * FROM tasks t WHERE (
32008
+ t.id LIKE ?
32009
+ OR t.short_id LIKE ?
32010
+ OR t.title LIKE ?
32011
+ OR t.description LIKE ?
32012
+ OR t.working_dir LIKE ?
32013
+ OR t.metadata LIKE ?
32014
+ OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?)
32015
+ )`;
32016
+ params.push(pattern, pattern, pattern, pattern, pattern, pattern, pattern);
30662
32017
  } else {
30663
32018
  sql = `SELECT * FROM tasks t WHERE 1=1`;
30664
32019
  }
@@ -30714,7 +32069,7 @@ function searchTasks(options, projectId, taskListId, db) {
30714
32069
  } else if (opts.is_blocked === false) {
30715
32070
  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
32071
  }
30717
- if (hasFts(d) && q) {
32072
+ if (useFts) {
30718
32073
  sql += ` ORDER BY bm25(tasks_fts),
30719
32074
  CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
30720
32075
  t.created_at DESC`;
@@ -32992,7 +34347,7 @@ function generateCliReferenceMarkdown() {
32992
34347
  // src/db/builtin-templates.ts
32993
34348
  init_database();
32994
34349
  import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
32995
- import { join as join15 } from "path";
34350
+ import { join as join16 } from "path";
32996
34351
  var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21";
32997
34352
  var BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library";
32998
34353
  var TEMPLATE_LIBRARY_SCHEMA = "todos.template_library.v1";
@@ -33268,7 +34623,7 @@ function writeBuiltinTemplateFiles(directory) {
33268
34623
  mkdirSync15(directory, { recursive: true });
33269
34624
  const files = [];
33270
34625
  for (const entry2 of exportBuiltinTemplateFiles()) {
33271
- const path = join15(directory, entry2.filename);
34626
+ const path = join16(directory, entry2.filename);
33272
34627
  writeFileSync13(path, `${JSON.stringify(entry2.template, null, 2)}
33273
34628
  `, "utf-8");
33274
34629
  files.push(path);
@@ -33550,10 +34905,10 @@ todos machines topology # full diagnostic report
33550
34905
  `;
33551
34906
  }
33552
34907
  // src/lib/environment-snapshots.ts
33553
- import { createHash as createHash11 } from "crypto";
33554
- import { existsSync as existsSync19, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
34908
+ import { createHash as createHash12 } from "crypto";
34909
+ import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
33555
34910
  import { hostname as hostname3, platform, arch } from "os";
33556
- import { dirname as dirname15, join as join16, resolve as resolve14 } from "path";
34911
+ import { dirname as dirname15, join as join17, resolve as resolve14 } from "path";
33557
34912
  import { tmpdir as tmpdir2 } from "os";
33558
34913
  init_database();
33559
34914
  init_redaction();
@@ -33575,11 +34930,11 @@ var CONFIG_FILES = [
33575
34930
  "dashboard/vite.config.ts"
33576
34931
  ];
33577
34932
  function sha2565(value) {
33578
- return createHash11("sha256").update(value).digest("hex");
34933
+ return createHash12("sha256").update(value).digest("hex");
33579
34934
  }
33580
34935
  function fileRecord(root, relativePath) {
33581
- const path = join16(root, relativePath);
33582
- if (!existsSync19(path))
34936
+ const path = join17(root, relativePath);
34937
+ if (!existsSync20(path))
33583
34938
  return null;
33584
34939
  const stat = statSync7(path);
33585
34940
  if (!stat.isFile())
@@ -33591,7 +34946,7 @@ function manifestRecord(root, relativePath) {
33591
34946
  const base = fileRecord(root, relativePath);
33592
34947
  if (!base)
33593
34948
  return null;
33594
- const parsed = readJsonFile(join16(root, relativePath));
34949
+ const parsed = readJsonFile(join17(root, relativePath));
33595
34950
  if (!parsed)
33596
34951
  return { ...base, redacted: {} };
33597
34952
  const redacted = redactValue({
@@ -33686,8 +35041,8 @@ function commandEnv(env, includeValues) {
33686
35041
  function defaultSnapshotDir() {
33687
35042
  const dbPath = getDatabasePath();
33688
35043
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
33689
- return join16(tmpdir2(), "hasna-todos", "environment-snapshots");
33690
- return join16(dirname15(resolve14(dbPath)), "environment-snapshots");
35044
+ return join17(tmpdir2(), "hasna-todos", "environment-snapshots");
35045
+ return join17(dirname15(resolve14(dbPath)), "environment-snapshots");
33691
35046
  }
33692
35047
  function snapshotWithId(snapshot) {
33693
35048
  const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
@@ -33734,7 +35089,7 @@ function captureEnvironmentSnapshot(input = {}) {
33734
35089
  });
33735
35090
  }
33736
35091
  function writeEnvironmentSnapshot(snapshot, outputPath) {
33737
- const path = outputPath ? resolve14(outputPath) : join16(defaultSnapshotDir(), `${snapshot.id}.json`);
35092
+ const path = outputPath ? resolve14(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
33738
35093
  ensureDir2(dirname15(path));
33739
35094
  writeJsonFile(path, snapshot);
33740
35095
  return path;
@@ -33825,9 +35180,9 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
33825
35180
  }
33826
35181
  // src/lib/decision-records.ts
33827
35182
  init_database();
33828
- import { createHash as createHash12 } from "crypto";
35183
+ import { createHash as createHash13 } from "crypto";
33829
35184
  import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync15 } from "fs";
33830
- import { dirname as dirname16, join as join17 } from "path";
35185
+ import { dirname as dirname16, join as join18 } from "path";
33831
35186
  var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
33832
35187
  var KNOWLEDGE_SNAPSHOT_SCHEMA = "todos.knowledge_snapshot.v1";
33833
35188
  var DECISION_STATUSES = ["proposed", "accepted", "deprecated", "superseded", "rejected"];
@@ -33881,7 +35236,7 @@ function rowToDecisionRecord(row) {
33881
35236
  }
33882
35237
  function stableSnapshotHash(payload) {
33883
35238
  const { captured_at: _capturedAt, ...rest } = payload;
33884
- return createHash12("sha256").update(JSON.stringify(rest)).digest("hex");
35239
+ return createHash13("sha256").update(JSON.stringify(rest)).digest("hex");
33885
35240
  }
33886
35241
  function createDecisionRecord(input, db) {
33887
35242
  const d = db || getDatabase();
@@ -34057,7 +35412,7 @@ function exportDecisionRecord(id, outputPath, format = "markdown", db) {
34057
35412
  if (!record)
34058
35413
  throw new Error(`Decision record not found: ${id}`);
34059
35414
  const content = format === "markdown" ? formatDecisionRecordMarkdown(record) : JSON.stringify(record, null, 2);
34060
- const path = outputPath ?? join17(process.cwd(), ".todos", "decisions", `${record.short_ref}.${format === "markdown" ? "md" : "json"}`);
35415
+ const path = outputPath ?? join18(process.cwd(), ".todos", "decisions", `${record.short_ref}.${format === "markdown" ? "md" : "json"}`);
34061
35416
  mkdirSync17(dirname16(path), { recursive: true });
34062
35417
  writeFileSync15(path, content, "utf8");
34063
35418
  return { path, content };
@@ -34205,7 +35560,7 @@ function exportKnowledgeSnapshot(id, outputPath, format = "markdown", db) {
34205
35560
  throw new Error(`Knowledge snapshot not found: ${id}`);
34206
35561
  const content = format === "markdown" ? formatKnowledgeSnapshotMarkdown(record) : JSON.stringify(record, null, 2);
34207
35562
  const slug = record.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
34208
- const path = outputPath ?? join17(process.cwd(), ".todos", "knowledge", `${slug || record.id.slice(0, 8)}.${format === "markdown" ? "md" : "json"}`);
35563
+ const path = outputPath ?? join18(process.cwd(), ".todos", "knowledge", `${slug || record.id.slice(0, 8)}.${format === "markdown" ? "md" : "json"}`);
34209
35564
  mkdirSync17(dirname16(path), { recursive: true });
34210
35565
  writeFileSync15(path, content, "utf8");
34211
35566
  return { path, content };
@@ -34500,8 +35855,8 @@ todos report export --kind retrospective --days 14 --format markdown --out retro
34500
35855
  `;
34501
35856
  }
34502
35857
  // src/lib/command-aliases.ts
34503
- import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
34504
- import { join as join18 } from "path";
35858
+ import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
35859
+ import { join as join19 } from "path";
34505
35860
  var COMMAND_ALIASES_SCHEMA = "todos.command_aliases.v1";
34506
35861
  var RESERVED = new Set([...listTopLevelCommands(), "help", "version", "alias", "shortcuts"]);
34507
35862
  var BUILTIN_SHORTCUTS = [
@@ -34520,7 +35875,7 @@ var BUILTIN_SHORTCUTS = [
34520
35875
  { pattern: /^reports?$/, argv: ["report", "docs"], explain: "Report export documentation" }
34521
35876
  ];
34522
35877
  function aliasesPath(cwd = process.cwd()) {
34523
- return join18(cwd, ".todos", "aliases.json");
35878
+ return join19(cwd, ".todos", "aliases.json");
34524
35879
  }
34525
35880
  function emptyStore() {
34526
35881
  return { schema_version: COMMAND_ALIASES_SCHEMA, aliases: {}, updated_at: new Date(0).toISOString() };
@@ -34537,7 +35892,7 @@ function validateAliasName(name) {
34537
35892
  }
34538
35893
  function loadAliasStore(cwd) {
34539
35894
  const path = aliasesPath(cwd);
34540
- if (!existsSync20(path))
35895
+ if (!existsSync21(path))
34541
35896
  return emptyStore();
34542
35897
  const parsed = JSON.parse(readFileSync20(path, "utf8"));
34543
35898
  if (parsed.schema_version !== COMMAND_ALIASES_SCHEMA) {
@@ -34547,7 +35902,7 @@ function loadAliasStore(cwd) {
34547
35902
  }
34548
35903
  function saveAliasStore(store, cwd) {
34549
35904
  const path = aliasesPath(cwd);
34550
- mkdirSync19(join18(path, ".."), { recursive: true });
35905
+ mkdirSync19(join19(path, ".."), { recursive: true });
34551
35906
  store.updated_at = new Date().toISOString();
34552
35907
  writeFileSync17(path, JSON.stringify(store, null, 2), "utf8");
34553
35908
  }
@@ -35195,18 +36550,18 @@ function createBranchWorkPlan(input, db) {
35195
36550
  }
35196
36551
  // src/lib/user-scaffolds.ts
35197
36552
  init_database();
35198
- import { existsSync as existsSync21, readFileSync as readFileSync21, writeFileSync as writeFileSync18, mkdirSync as mkdirSync20 } from "fs";
35199
- import { join as join19 } from "path";
36553
+ import { existsSync as existsSync22, readFileSync as readFileSync21, writeFileSync as writeFileSync18, mkdirSync as mkdirSync20 } from "fs";
36554
+ import { join as join20 } from "path";
35200
36555
  var USER_SCAFFOLD_SCHEMA = "todos.user_scaffold.v1";
35201
36556
  var SCAFFOLD_KINDS = ["task", "project", "plan", "checklist", "contract", "verification_policy"];
35202
36557
  function storeDir(cwd = process.cwd()) {
35203
- return join19(cwd, ".todos", "scaffolds");
36558
+ return join20(cwd, ".todos", "scaffolds");
35204
36559
  }
35205
36560
  function storePath(cwd) {
35206
- return join19(storeDir(cwd), "store.json");
36561
+ return join20(storeDir(cwd), "store.json");
35207
36562
  }
35208
36563
  function versionsDir(cwd) {
35209
- return join19(storeDir(cwd), "versions");
36564
+ return join20(storeDir(cwd), "versions");
35210
36565
  }
35211
36566
  function slugify5(name) {
35212
36567
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
@@ -35216,7 +36571,7 @@ function emptyStore2() {
35216
36571
  }
35217
36572
  function loadUserScaffoldStore(cwd) {
35218
36573
  const path = storePath(cwd);
35219
- if (!existsSync21(path))
36574
+ if (!existsSync22(path))
35220
36575
  return emptyStore2();
35221
36576
  const parsed = JSON.parse(readFileSync21(path, "utf8"));
35222
36577
  if (parsed.schema_version !== USER_SCAFFOLD_SCHEMA) {
@@ -35231,7 +36586,7 @@ function saveUserScaffoldStore(store, cwd) {
35231
36586
  }
35232
36587
  function snapshotVersion(scaffold, cwd) {
35233
36588
  mkdirSync20(versionsDir(cwd), { recursive: true });
35234
- const path = join19(versionsDir(cwd), `${scaffold.id}-v${scaffold.version}.json`);
36589
+ const path = join20(versionsDir(cwd), `${scaffold.id}-v${scaffold.version}.json`);
35235
36590
  writeFileSync18(path, JSON.stringify(scaffold, null, 2), "utf8");
35236
36591
  }
35237
36592
  function listUserScaffolds(kind, cwd) {
@@ -35478,7 +36833,7 @@ function listLinkedTemplates(db, cwd) {
35478
36833
  // src/lib/agent-workflow-demo.ts
35479
36834
  init_database();
35480
36835
  import { mkdtempSync } from "fs";
35481
- import { join as join20 } from "path";
36836
+ import { join as join21 } from "path";
35482
36837
  import { tmpdir as tmpdir3 } from "os";
35483
36838
  var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
35484
36839
  var DEMO_DEFAULT_AGENT = "demoagent";
@@ -35494,7 +36849,7 @@ function setupEphemeralDemoDb(options = {}) {
35494
36849
  if (options.db_path) {
35495
36850
  db_path = options.db_path;
35496
36851
  } else if (options.persist) {
35497
- db_path = join20(mkdtempSync(join20(tmpdir3(), "todos-demo-")), "todos.db");
36852
+ db_path = join21(mkdtempSync(join21(tmpdir3(), "todos-demo-")), "todos.db");
35498
36853
  } else {
35499
36854
  db_path = ":memory:";
35500
36855
  }
@@ -37830,18 +39185,18 @@ function runSearchView(idOrName, db) {
37830
39185
  return { ...runSavedSearch(view.filters, view.scope, d), view };
37831
39186
  }
37832
39187
  // src/lib/claude-tasks.ts
37833
- import { existsSync as existsSync22, readFileSync as readFileSync22, readdirSync as readdirSync4, writeFileSync as writeFileSync19 } from "fs";
37834
- import { join as join21 } from "path";
39188
+ import { existsSync as existsSync23, readFileSync as readFileSync22, readdirSync as readdirSync4, writeFileSync as writeFileSync19 } from "fs";
39189
+ import { join as join22 } from "path";
37835
39190
  init_config();
37836
39191
  init_sync_utils();
37837
39192
  function getTaskListDir(taskListId) {
37838
- return join21(HOME, ".claude", "tasks", taskListId);
39193
+ return join22(HOME, ".claude", "tasks", taskListId);
37839
39194
  }
37840
39195
  function readClaudeTask(dir, filename) {
37841
- return readJsonFile(join21(dir, filename));
39196
+ return readJsonFile(join22(dir, filename));
37842
39197
  }
37843
39198
  function writeClaudeTask(dir, task2) {
37844
- writeJsonFile(join21(dir, `${task2.id}.json`), task2);
39199
+ writeJsonFile(join22(dir, `${task2.id}.json`), task2);
37845
39200
  }
37846
39201
  function toClaudeStatus(status) {
37847
39202
  if (status === "pending" || status === "in_progress" || status === "completed") {
@@ -37853,14 +39208,14 @@ function toSqliteStatus(status) {
37853
39208
  return status;
37854
39209
  }
37855
39210
  function readPrefixCounter(dir) {
37856
- const path = join21(dir, ".prefix-counter");
37857
- if (!existsSync22(path))
39211
+ const path = join22(dir, ".prefix-counter");
39212
+ if (!existsSync23(path))
37858
39213
  return 0;
37859
39214
  const val = parseInt(readFileSync22(path, "utf-8").trim(), 10);
37860
39215
  return isNaN(val) ? 0 : val;
37861
39216
  }
37862
39217
  function writePrefixCounter(dir, value) {
37863
- writeFileSync19(join21(dir, ".prefix-counter"), String(value));
39218
+ writeFileSync19(join22(dir, ".prefix-counter"), String(value));
37864
39219
  }
37865
39220
  function formatPrefixedSubject(title, prefix, counter) {
37866
39221
  const padded = String(counter).padStart(5, "0");
@@ -37887,7 +39242,7 @@ function taskToClaudeTask(task2, claudeTaskId, existingMeta) {
37887
39242
  }
37888
39243
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
37889
39244
  const dir = getTaskListDir(taskListId);
37890
- if (!existsSync22(dir))
39245
+ if (!existsSync23(dir))
37891
39246
  ensureDir2(dir);
37892
39247
  const filter = {};
37893
39248
  if (projectId)
@@ -37896,7 +39251,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
37896
39251
  const existingByTodosId = new Map;
37897
39252
  const files = listJsonFiles(dir);
37898
39253
  for (const f of files) {
37899
- const path = join21(dir, f);
39254
+ const path = join22(dir, f);
37900
39255
  const ct = readClaudeTask(dir, f);
37901
39256
  if (ct?.metadata?.["todos_id"]) {
37902
39257
  existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
@@ -37983,7 +39338,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
37983
39338
  }
37984
39339
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
37985
39340
  const dir = getTaskListDir(taskListId);
37986
- if (!existsSync22(dir)) {
39341
+ if (!existsSync23(dir)) {
37987
39342
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
37988
39343
  }
37989
39344
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
@@ -38003,7 +39358,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
38003
39358
  }
38004
39359
  for (const f of files) {
38005
39360
  try {
38006
- const filePath = join21(dir, f);
39361
+ const filePath = join22(dir, f);
38007
39362
  const ct = readClaudeTask(dir, f);
38008
39363
  if (!ct)
38009
39364
  continue;
@@ -38071,22 +39426,22 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
38071
39426
  }
38072
39427
 
38073
39428
  // src/lib/agent-tasks.ts
38074
- import { existsSync as existsSync23 } from "fs";
38075
- import { join as join22 } from "path";
39429
+ import { existsSync as existsSync24 } from "fs";
39430
+ import { join as join23 } from "path";
38076
39431
  init_sync_utils();
38077
39432
  init_config();
38078
39433
  function agentBaseDir(agent) {
38079
39434
  const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
38080
- return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join22(getTodosGlobalDir(), "agents");
39435
+ return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join23(getTodosGlobalDir(), "agents");
38081
39436
  }
38082
39437
  function getTaskListDir2(agent, taskListId) {
38083
- return join22(agentBaseDir(agent), agent, taskListId);
39438
+ return join23(agentBaseDir(agent), agent, taskListId);
38084
39439
  }
38085
39440
  function readAgentTask(dir, filename) {
38086
- return readJsonFile(join22(dir, filename));
39441
+ return readJsonFile(join23(dir, filename));
38087
39442
  }
38088
39443
  function writeAgentTask(dir, task2) {
38089
- writeJsonFile(join22(dir, `${task2.id}.json`), task2);
39444
+ writeJsonFile(join23(dir, `${task2.id}.json`), task2);
38090
39445
  }
38091
39446
  function taskToAgentTask(task2, externalId, existingMeta) {
38092
39447
  return {
@@ -38111,7 +39466,7 @@ function metadataKey(agent) {
38111
39466
  }
38112
39467
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
38113
39468
  const dir = getTaskListDir2(agent, taskListId);
38114
- if (!existsSync23(dir))
39469
+ if (!existsSync24(dir))
38115
39470
  ensureDir2(dir);
38116
39471
  const filter = {};
38117
39472
  if (projectId)
@@ -38120,7 +39475,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
38120
39475
  const existingByTodosId = new Map;
38121
39476
  const files = listJsonFiles(dir);
38122
39477
  for (const f of files) {
38123
- const path = join22(dir, f);
39478
+ const path = join23(dir, f);
38124
39479
  const at = readAgentTask(dir, f);
38125
39480
  if (at?.metadata?.["todos_id"]) {
38126
39481
  existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
@@ -38194,7 +39549,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
38194
39549
  }
38195
39550
  function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
38196
39551
  const dir = getTaskListDir2(agent, taskListId);
38197
- if (!existsSync23(dir)) {
39552
+ if (!existsSync24(dir)) {
38198
39553
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
38199
39554
  }
38200
39555
  const files = listJsonFiles(dir);
@@ -38213,7 +39568,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
38213
39568
  }
38214
39569
  for (const f of files) {
38215
39570
  try {
38216
- const filePath = join22(dir, f);
39571
+ const filePath = join23(dir, f);
38217
39572
  const at = readAgentTask(dir, f);
38218
39573
  if (!at)
38219
39574
  continue;
@@ -38351,9 +39706,9 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
38351
39706
  return { pushed, pulled, errors };
38352
39707
  }
38353
39708
  // src/lib/extract.ts
38354
- import { existsSync as existsSync24, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
38355
- import { createHash as createHash13 } from "crypto";
38356
- import { relative as relative6, resolve as resolve15, join as join23 } from "path";
39709
+ import { existsSync as existsSync25, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
39710
+ import { createHash as createHash14 } from "crypto";
39711
+ import { relative as relative6, resolve as resolve15, join as join24 } from "path";
38357
39712
  var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
38358
39713
  var DEFAULT_EXTENSIONS = new Set([
38359
39714
  ".ts",
@@ -38417,15 +39772,15 @@ var SKIP_DIRS2 = new Set([
38417
39772
  ".parcel-cache"
38418
39773
  ]);
38419
39774
  function stableHash(value) {
38420
- return createHash13("sha256").update(value).digest("hex");
39775
+ return createHash14("sha256").update(value).digest("hex");
38421
39776
  }
38422
39777
  function normalizePathForMatch(value) {
38423
39778
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
38424
39779
  }
38425
39780
  function readGitignorePatterns(basePath) {
38426
39781
  const root = statSync8(basePath).isFile() ? resolve15(basePath, "..") : basePath;
38427
- const gitignorePath = join23(root, ".gitignore");
38428
- if (!existsSync24(gitignorePath))
39782
+ const gitignorePath = join24(root, ".gitignore");
39783
+ if (!existsSync25(gitignorePath))
38429
39784
  return [];
38430
39785
  try {
38431
39786
  return readFileSync23(gitignorePath, "utf-8").split(`
@@ -38567,7 +39922,7 @@ function buildCodebaseIndex(options) {
38567
39922
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
38568
39923
  const indexed = [];
38569
39924
  for (const file of files) {
38570
- const fullPath = statSync8(basePath).isFile() ? basePath : join23(basePath, file);
39925
+ const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
38571
39926
  try {
38572
39927
  const source9 = readFileSync23(fullPath, "utf-8");
38573
39928
  const relPath = statSync8(basePath).isFile() ? relative6(resolve15(basePath, ".."), fullPath) : file;
@@ -38598,7 +39953,7 @@ function extractTodos(options, db) {
38598
39953
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
38599
39954
  const allComments = [];
38600
39955
  for (const file of files) {
38601
- const fullPath = statSync8(basePath).isFile() ? basePath : join23(basePath, file);
39956
+ const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
38602
39957
  try {
38603
39958
  const source9 = readFileSync23(fullPath, "utf-8");
38604
39959
  const relPath = statSync8(basePath).isFile() ? relative6(resolve15(basePath, ".."), fullPath) : file;
@@ -38633,7 +39988,7 @@ function extractTodos(options, db) {
38633
39988
  skipped++;
38634
39989
  continue;
38635
39990
  }
38636
- const taskTags = ["extracted", comment.tag.toLowerCase(), ...options.tags || []];
39991
+ const taskTags2 = ["extracted", comment.tag.toLowerCase(), ...options.tags || []];
38637
39992
  const task2 = createTask({
38638
39993
  title: `[${comment.tag}] ${comment.message}`,
38639
39994
  description: `Extracted from code comment in \`${comment.file}\` at line ${comment.line}:
@@ -38645,7 +40000,7 @@ ${comment.raw.trim()}
38645
40000
  task_list_id: options.task_list_id,
38646
40001
  assigned_to: options.assigned_to,
38647
40002
  agent_id: options.agent_id,
38648
- tags: taskTags,
40003
+ tags: taskTags2,
38649
40004
  metadata: {
38650
40005
  source: "code_comment",
38651
40006
  comment_type: comment.tag,
@@ -38906,7 +40261,7 @@ init_config();
38906
40261
  // src/lib/natural-language-intake.ts
38907
40262
  init_database();
38908
40263
  init_redaction();
38909
- var PRIORITIES3 = new Set(["low", "medium", "high", "critical"]);
40264
+ var PRIORITIES4 = new Set(["low", "medium", "high", "critical"]);
38910
40265
  function compact2(value) {
38911
40266
  return value.replace(/\s+/g, " ").trim();
38912
40267
  }
@@ -38918,7 +40273,7 @@ function splitItems(text) {
38918
40273
  }
38919
40274
  function parsePriority(line, fallback) {
38920
40275
  const explicit = line.match(/\b(?:priority[:= ]+|p:)(critical|high|medium|low)\b/i)?.[1]?.toLowerCase();
38921
- if (explicit && PRIORITIES3.has(explicit))
40276
+ if (explicit && PRIORITIES4.has(explicit))
38922
40277
  return explicit;
38923
40278
  if (/\b(p0|urgent|blocker|critical)\b/i.test(line))
38924
40279
  return "critical";
@@ -39305,7 +40660,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
39305
40660
  }
39306
40661
  // src/lib/agent-replay-simulator.ts
39307
40662
  init_redaction();
39308
- import { createHash as createHash14 } from "crypto";
40663
+ import { createHash as createHash15 } from "crypto";
39309
40664
  import { readFileSync as readFileSync24 } from "fs";
39310
40665
  function isObject(value) {
39311
40666
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -39327,7 +40682,7 @@ function stable2(value) {
39327
40682
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
39328
40683
  }
39329
40684
  function fingerprint3(value) {
39330
- return createHash14("sha256").update(JSON.stringify(stable2(value))).digest("hex");
40685
+ return createHash15("sha256").update(JSON.stringify(stable2(value))).digest("hex");
39331
40686
  }
39332
40687
  function unpackFixture(input) {
39333
40688
  if (!isObject(input))
@@ -39566,9 +40921,9 @@ function renderAgentReplaySimulationMarkdown(simulation) {
39566
40921
  }
39567
40922
  // src/lib/local-extensions.ts
39568
40923
  init_config();
39569
- import { createHash as createHash15, createVerify } from "crypto";
39570
- import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
39571
- import { basename as basename5, join as join24, resolve as resolve16 } from "path";
40924
+ import { createHash as createHash16, createVerify } from "crypto";
40925
+ import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
40926
+ import { basename as basename5, join as join25, resolve as resolve16 } from "path";
39572
40927
  init_redaction();
39573
40928
  function isObject2(value) {
39574
40929
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -39653,7 +41008,7 @@ function parseJson(path) {
39653
41008
  return JSON.parse(readFileSync25(path, "utf8"));
39654
41009
  }
39655
41010
  function sha2566(bytes) {
39656
- return `sha256:${createHash15("sha256").update(bytes).digest("hex")}`;
41011
+ return `sha256:${createHash16("sha256").update(bytes).digest("hex")}`;
39657
41012
  }
39658
41013
  function compareVersions(a, b) {
39659
41014
  const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -39849,10 +41204,10 @@ function verifyExtensionSignature(input) {
39849
41204
  }
39850
41205
  function inspectExtensionSource(source9) {
39851
41206
  const resolved = resolve16(source9);
39852
- if (!existsSync25(resolved))
41207
+ if (!existsSync26(resolved))
39853
41208
  throw new Error(`extension source not found: ${source9}`);
39854
41209
  const stat = statSync9(resolved);
39855
- const manifestPath = stat.isDirectory() ? [join24(resolved, "todos.extension.json"), join24(resolved, "extension.json")].find(existsSync25) : resolved;
41210
+ const manifestPath = stat.isDirectory() ? [join25(resolved, "todos.extension.json"), join25(resolved, "extension.json")].find(existsSync26) : resolved;
39856
41211
  if (!manifestPath)
39857
41212
  throw new Error(`extension directory ${source9} is missing todos.extension.json`);
39858
41213
  const raw = readFileSync25(manifestPath);
@@ -39948,20 +41303,20 @@ function projectExtensionSources(projectPath) {
39948
41303
  return [];
39949
41304
  const root = resolve16(projectPath);
39950
41305
  const candidates = [
39951
- join24(root, "todos.extension.json"),
39952
- join24(root, ".todos", "todos.extension.json")
41306
+ join25(root, "todos.extension.json"),
41307
+ join25(root, ".todos", "todos.extension.json")
39953
41308
  ];
39954
- const extensionDir = join24(root, ".todos", "extensions");
39955
- if (existsSync25(extensionDir)) {
41309
+ const extensionDir = join25(root, ".todos", "extensions");
41310
+ if (existsSync26(extensionDir)) {
39956
41311
  for (const entry2 of readdirSync5(extensionDir)) {
39957
41312
  if (entry2.startsWith("."))
39958
41313
  continue;
39959
- const full = join24(extensionDir, entry2);
41314
+ const full = join25(extensionDir, entry2);
39960
41315
  if (statSync9(full).isDirectory() || entry2.endsWith(".json"))
39961
41316
  candidates.push(full);
39962
41317
  }
39963
41318
  }
39964
- return candidates.filter(existsSync25);
41319
+ return candidates.filter(existsSync26);
39965
41320
  }
39966
41321
  function discoverLocalExtensions(options = {}) {
39967
41322
  const config = loadConfig();
@@ -40248,6 +41603,18 @@ function formatDispatchMessage(tasks, opts = {}) {
40248
41603
  // src/lib/tmux.ts
40249
41604
  var DELAY_MIN = 3000;
40250
41605
  var DELAY_MAX = 5000;
41606
+ var IDLE_TMUX_COMMANDS = new Set([
41607
+ "bash",
41608
+ "dash",
41609
+ "elvish",
41610
+ "fish",
41611
+ "ksh",
41612
+ "nu",
41613
+ "pwsh",
41614
+ "sh",
41615
+ "tmux",
41616
+ "zsh"
41617
+ ]);
40251
41618
  function parseTmuxTarget(spec) {
40252
41619
  if (!spec || spec.trim() === "") {
40253
41620
  throw new Error("tmux target spec cannot be empty");
@@ -40304,17 +41671,73 @@ async function validateTmuxTarget(spec) {
40304
41671
  throw new Error(`tmux target "${targetStr}" not found: ${stderr.trim() || "unknown error"}`);
40305
41672
  }
40306
41673
  }
41674
+ async function inspectTmuxPane(target) {
41675
+ const proc = Bun.spawn([
41676
+ "tmux",
41677
+ "display-message",
41678
+ "-p",
41679
+ "-t",
41680
+ target,
41681
+ "#{pane_id}\t#{pane_current_command}\t#{pane_dead}\t#{pane_input_off}\t#{pane_in_mode}"
41682
+ ], {
41683
+ stdout: "pipe",
41684
+ stderr: "pipe"
41685
+ });
41686
+ const exitCode = await proc.exited;
41687
+ if (exitCode !== 0) {
41688
+ const stderr = await new Response(proc.stderr).text();
41689
+ throw new Error(`tmux target "${target}" not found: ${stderr.trim() || "unknown error"}`);
41690
+ }
41691
+ const stdout = (await new Response(proc.stdout).text()).trim();
41692
+ const [paneId, currentCommand = "", paneDead = "0", inputOff = "0", inMode = "0"] = stdout.split("\t");
41693
+ if (!paneId) {
41694
+ throw new Error(`tmux target "${target}" did not resolve to a pane`);
41695
+ }
41696
+ return {
41697
+ target,
41698
+ paneId,
41699
+ currentCommand,
41700
+ paneDead: paneDead === "1",
41701
+ inputOff: inputOff === "1",
41702
+ inMode: inMode === "1"
41703
+ };
41704
+ }
41705
+ function tmuxPaneBusyStatus(pane) {
41706
+ if (pane.paneDead) {
41707
+ return { busy: true, reason: "pane is dead" };
41708
+ }
41709
+ if (pane.inputOff) {
41710
+ return { busy: true, reason: "pane input is disabled" };
41711
+ }
41712
+ if (pane.inMode) {
41713
+ return { busy: true, reason: "pane is in copy or alternate mode" };
41714
+ }
41715
+ const currentCommand = pane.currentCommand.trim();
41716
+ if (currentCommand && !IDLE_TMUX_COMMANDS.has(currentCommand)) {
41717
+ return { busy: true, reason: `pane is running ${currentCommand}` };
41718
+ }
41719
+ return { busy: false, reason: null };
41720
+ }
40307
41721
  function calculateDelay(message) {
40308
41722
  const len = message.length;
40309
41723
  const extra = Math.floor(len / 100 * 40);
40310
41724
  return Math.min(DELAY_MIN + extra, DELAY_MAX);
40311
41725
  }
40312
- async function sendToTmux(target, message, delayMs, dryRun = false) {
41726
+ async function sendToTmux(target, message, delayMs, options = false) {
41727
+ const opts = typeof options === "boolean" ? { dryRun: options } : options;
41728
+ const dryRun = opts.dryRun ?? false;
40313
41729
  if (dryRun) {
40314
41730
  console.log(`[dry-run] sendToTmux target=${target} delay=${delayMs}ms`);
40315
41731
  console.log(`[dry-run] message: ${message.slice(0, 200)}`);
40316
41732
  return;
40317
41733
  }
41734
+ if (!opts.confirmBusy) {
41735
+ const pane = await inspectTmuxPane(target);
41736
+ const status = tmuxPaneBusyStatus(pane);
41737
+ if (status.busy) {
41738
+ throw new Error(`tmux target "${target}" appears busy (${status.reason}). Re-run with --confirm-busy to send anyway.`);
41739
+ }
41740
+ }
40318
41741
  const sendProc = Bun.spawn(["tmux", "send-keys", "-t", target, message, ""], {
40319
41742
  stdout: "pipe",
40320
41743
  stderr: "pipe"
@@ -40346,7 +41769,10 @@ async function executeDispatch(dispatch, opts = {}, db) {
40346
41769
  }
40347
41770
  const delayMs = dispatch.delay_ms ?? calculateDelay(message);
40348
41771
  try {
40349
- await sendToTmux(dispatch.target_window, message, delayMs, opts.dryRun ?? false);
41772
+ await sendToTmux(dispatch.target_window, message, delayMs, {
41773
+ dryRun: opts.dryRun ?? false,
41774
+ confirmBusy: opts.confirmBusy ?? false
41775
+ });
40350
41776
  createDispatchLog({
40351
41777
  dispatch_id: dispatch.id,
40352
41778
  target_window: dispatch.target_window,
@@ -40404,7 +41830,7 @@ async function dispatchToMultiple(input, opts = {}, db) {
40404
41830
  init_redaction();
40405
41831
 
40406
41832
  // src/lib/retention-cleanup.ts
40407
- import { existsSync as existsSync26, unlinkSync as unlinkSync2 } from "fs";
41833
+ import { existsSync as existsSync27, unlinkSync as unlinkSync2 } from "fs";
40408
41834
  init_database();
40409
41835
  var RETENTION_CLEANUP_CONFIRMATION = "delete-local-retention-data";
40410
41836
  var ALL_SCOPES = ["comments", "runs", "verifications", "expired_artifacts"];
@@ -40616,7 +42042,7 @@ function applyRetentionCleanup(input, db) {
40616
42042
  for (const artifact of report.candidates.artifact_files) {
40617
42043
  try {
40618
42044
  const path = artifactStorePath(artifact.relative_path);
40619
- if (!existsSync26(path)) {
42045
+ if (!existsSync27(path)) {
40620
42046
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
40621
42047
  continue;
40622
42048
  }
@@ -40854,8 +42280,8 @@ function renderScalePerformanceReportMarkdown(report) {
40854
42280
  init_database();
40855
42281
  init_migrations();
40856
42282
  init_schema();
40857
- import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync21, statSync as statSync10 } from "fs";
40858
- import { basename as basename6, dirname as dirname18, join as join25 } from "path";
42283
+ import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync21, statSync as statSync10 } from "fs";
42284
+ import { basename as basename6, dirname as dirname18, join as join26 } from "path";
40859
42285
  var REQUIRED_TABLES2 = [
40860
42286
  "_migrations",
40861
42287
  "projects",
@@ -40962,7 +42388,7 @@ function findMissingProjectRoots(db) {
40962
42388
  continue;
40963
42389
  if (!row.path.startsWith("/"))
40964
42390
  continue;
40965
- if (!existsSync27(row.path))
42391
+ if (!existsSync28(row.path))
40966
42392
  missing++;
40967
42393
  }
40968
42394
  return missing;
@@ -41022,16 +42448,16 @@ function databasePermissionsAreUnsafe(dbPath) {
41022
42448
  function createBackup(dbPath) {
41023
42449
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
41024
42450
  return;
41025
- if (!existsSync27(dbPath))
42451
+ if (!existsSync28(dbPath))
41026
42452
  return;
41027
42453
  const stamp = now().replace(/[:.]/g, "-");
41028
- const backupDir = join25(dirname18(dbPath), `${basename6(dbPath)}.backup-${stamp}`);
42454
+ const backupDir = join26(dirname18(dbPath), `${basename6(dbPath)}.backup-${stamp}`);
41029
42455
  const files = [];
41030
42456
  mkdirSync21(backupDir, { recursive: true });
41031
42457
  for (const source9 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
41032
- if (!existsSync27(source9))
42458
+ if (!existsSync28(source9))
41033
42459
  continue;
41034
- const target = join25(backupDir, basename6(source9));
42460
+ const target = join26(backupDir, basename6(source9));
41035
42461
  copyFileSync2(source9, target);
41036
42462
  files.push(target);
41037
42463
  }
@@ -41320,7 +42746,7 @@ function upsertCapacityProfile(input) {
41320
42746
  const store = readStore2();
41321
42747
  const key = profileKey(agentId, projectId);
41322
42748
  const existing = store.profiles[key];
41323
- const now3 = timestamp2();
42749
+ const now4 = timestamp2();
41324
42750
  const profile = {
41325
42751
  id: existing?.id ?? key,
41326
42752
  agent_id: agentId,
@@ -41328,8 +42754,8 @@ function upsertCapacityProfile(input) {
41328
42754
  minutes_per_day: assertMinutes(input.minutes_per_day),
41329
42755
  working_days: normalizeWorkingDays(input.working_days),
41330
42756
  effective_from: cleanString2(input.effective_from),
41331
- created_at: existing?.created_at ?? now3,
41332
- updated_at: now3
42757
+ created_at: existing?.created_at ?? now4,
42758
+ updated_at: now4
41333
42759
  };
41334
42760
  store.profiles[key] = profile;
41335
42761
  writeStore2(store);
@@ -41726,6 +43152,8 @@ export {
41726
43152
  uuid,
41727
43153
  upsertWorkspaceTrustProfile,
41728
43154
  upsertVerificationProvider,
43155
+ upsertTesterIssueReports,
43156
+ upsertTesterIssueReport,
41729
43157
  upsertTerminalNotificationRule,
41730
43158
  upsertSecretSafetyConfig,
41731
43159
  upsertRunnerSandboxProfile,
@@ -41937,6 +43365,7 @@ export {
41937
43365
  recordTaskReview,
41938
43366
  recordFilesTouched,
41939
43367
  recordEnvironmentSnapshot,
43368
+ readTesterIssueReportsPayload,
41940
43369
  readLocalBackupFile,
41941
43370
  readEnvironmentSnapshot,
41942
43371
  readBundleFile,
@@ -41975,6 +43404,7 @@ export {
41975
43404
  parseCiLog,
41976
43405
  now,
41977
43406
  notifyUpcomingDeadlines2 as notifyUpcomingDeadlines,
43407
+ normalizeTesterIssueReport,
41978
43408
  normalizeScope,
41979
43409
  normalizeGeneratedAgentNames,
41980
43410
  normalizeFeatureManifest,
@@ -42363,6 +43793,7 @@ export {
42363
43793
  formatAgentWorkflowDemoReport,
42364
43794
  formatActivityRecordText,
42365
43795
  finishTaskRun,
43796
+ fingerprintTesterIssueReport,
42366
43797
  fingerprintInboxInput,
42367
43798
  findTasksByFile,
42368
43799
  findTaskByCommit,
@@ -42422,7 +43853,7 @@ export {
42422
43853
  emitLocalEventHooksQuiet,
42423
43854
  emitLocalEventHooks,
42424
43855
  downloadRunArtifactsFromS3,
42425
- dispatchWebhook,
43856
+ dispatchWebhook2 as dispatchWebhook,
42426
43857
  dispatchToMultiple,
42427
43858
  dismissReminder,
42428
43859
  discoverVerificationProviderCapabilities,
@@ -42665,6 +44096,9 @@ export {
42665
44096
  TODOS_CLI_MCP_PARITY,
42666
44097
  TODOS_CAPABILITIES,
42667
44098
  TODOS_API_ROUTES,
44099
+ TESTERS_ISSUE_REPORT_SCHEMA_VERSION,
44100
+ TESTERS_ISSUE_REPORT_RESULT_SCHEMA_VERSION,
44101
+ TESTERS_ISSUE_REPORT_BATCH_RESULT_SCHEMA_VERSION,
42668
44102
  TASK_STATUSES,
42669
44103
  TASK_SCHEDULING_SCHEMA,
42670
44104
  TASK_PRIORITIES,