@hasna/todos 0.11.57 → 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/cli/index.js +202 -98
- package/dist/contracts.js +201 -17
- package/dist/index.js +201 -98
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/mcp/index.js +202 -98
- package/dist/registry.js +201 -17
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +202 -98
- package/dist/storage.js +199 -96
- package/package.json +2 -2
package/dist/mcp/index.js
CHANGED
|
@@ -8653,7 +8653,7 @@ var init_event_hooks = __esm(() => {
|
|
|
8653
8653
|
VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
|
|
8654
8654
|
});
|
|
8655
8655
|
|
|
8656
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
8656
|
+
// node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
|
|
8657
8657
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
8658
8658
|
import { existsSync as existsSync5 } from "fs";
|
|
8659
8659
|
import { homedir } from "os";
|
|
@@ -8670,17 +8670,30 @@ function getPathValue(input, path) {
|
|
|
8670
8670
|
return;
|
|
8671
8671
|
}, input);
|
|
8672
8672
|
}
|
|
8673
|
-
function wildcardToRegExp(pattern) {
|
|
8674
|
-
|
|
8675
|
-
|
|
8673
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
8674
|
+
let body = "";
|
|
8675
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
8676
|
+
const char = pattern[index];
|
|
8677
|
+
if (char === "*") {
|
|
8678
|
+
if (pattern[index + 1] === "*") {
|
|
8679
|
+
body += ".*";
|
|
8680
|
+
index += 1;
|
|
8681
|
+
} else {
|
|
8682
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
8683
|
+
}
|
|
8684
|
+
} else {
|
|
8685
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
return new RegExp(`^${body}$`);
|
|
8676
8689
|
}
|
|
8677
|
-
function matchString(value, matcher) {
|
|
8690
|
+
function matchString(value, matcher, options = {}) {
|
|
8678
8691
|
if (matcher === undefined)
|
|
8679
8692
|
return true;
|
|
8680
8693
|
if (value === undefined)
|
|
8681
8694
|
return false;
|
|
8682
8695
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
8683
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
8696
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
8684
8697
|
}
|
|
8685
8698
|
function matchRecord(input, matcher) {
|
|
8686
8699
|
if (!matcher)
|
|
@@ -8688,7 +8701,9 @@ function matchRecord(input, matcher) {
|
|
|
8688
8701
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
8689
8702
|
const actual = getPathValue(input, path);
|
|
8690
8703
|
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
8691
|
-
return matchString(actual === undefined ? undefined : String(actual), expected
|
|
8704
|
+
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
8705
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
8706
|
+
});
|
|
8692
8707
|
}
|
|
8693
8708
|
return actual === expected;
|
|
8694
8709
|
});
|
|
@@ -9042,7 +9057,7 @@ class EventsClient {
|
|
|
9042
9057
|
}
|
|
9043
9058
|
return deliveries;
|
|
9044
9059
|
}
|
|
9045
|
-
async
|
|
9060
|
+
async matchChannel(id, input = {}) {
|
|
9046
9061
|
const channel = await this.store.getChannel(id);
|
|
9047
9062
|
if (!channel)
|
|
9048
9063
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -9059,6 +9074,34 @@ class EventsClient {
|
|
|
9059
9074
|
time: input.time,
|
|
9060
9075
|
id: input.id
|
|
9061
9076
|
});
|
|
9077
|
+
const matched = channelMatchesEvent(channel, event);
|
|
9078
|
+
return {
|
|
9079
|
+
channelId: channel.id,
|
|
9080
|
+
matched,
|
|
9081
|
+
event,
|
|
9082
|
+
filters: channel.filters,
|
|
9083
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
9084
|
+
};
|
|
9085
|
+
}
|
|
9086
|
+
async testChannel(id, input = {}, options = {}) {
|
|
9087
|
+
const channel = await this.store.getChannel(id);
|
|
9088
|
+
if (!channel)
|
|
9089
|
+
throw new Error(`Channel not found: ${id}`);
|
|
9090
|
+
const match = await this.matchChannel(id, input);
|
|
9091
|
+
const event = match.event;
|
|
9092
|
+
if (options.honorFilters && !match.matched) {
|
|
9093
|
+
const timestamp = new Date().toISOString();
|
|
9094
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
9095
|
+
attempt: 1,
|
|
9096
|
+
status: "skipped",
|
|
9097
|
+
startedAt: timestamp,
|
|
9098
|
+
completedAt: timestamp,
|
|
9099
|
+
error: match.reason
|
|
9100
|
+
}]);
|
|
9101
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
9102
|
+
await this.store.appendDelivery(result2);
|
|
9103
|
+
return result2;
|
|
9104
|
+
}
|
|
9062
9105
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
9063
9106
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
9064
9107
|
await this.store.appendDelivery(result);
|
|
@@ -9162,6 +9205,90 @@ var init_dist = __esm(() => {
|
|
|
9162
9205
|
DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
9163
9206
|
});
|
|
9164
9207
|
|
|
9208
|
+
// src/db/task-lists.ts
|
|
9209
|
+
function rowToTaskList(row) {
|
|
9210
|
+
return {
|
|
9211
|
+
...row,
|
|
9212
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
9213
|
+
};
|
|
9214
|
+
}
|
|
9215
|
+
function createTaskList(input, db) {
|
|
9216
|
+
const d = db || getDatabase();
|
|
9217
|
+
const id = uuid();
|
|
9218
|
+
const timestamp = now();
|
|
9219
|
+
const slug = input.slug || slugify(input.name);
|
|
9220
|
+
if (!input.project_id) {
|
|
9221
|
+
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
9222
|
+
if (existing) {
|
|
9223
|
+
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
9224
|
+
}
|
|
9225
|
+
}
|
|
9226
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
9227
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
9228
|
+
return getTaskList(id, d);
|
|
9229
|
+
}
|
|
9230
|
+
function getTaskList(id, db) {
|
|
9231
|
+
const d = db || getDatabase();
|
|
9232
|
+
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
9233
|
+
return row ? rowToTaskList(row) : null;
|
|
9234
|
+
}
|
|
9235
|
+
function getTaskListBySlug(slug, projectId, db) {
|
|
9236
|
+
const d = db || getDatabase();
|
|
9237
|
+
let row;
|
|
9238
|
+
if (projectId) {
|
|
9239
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
9240
|
+
} else {
|
|
9241
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
9242
|
+
}
|
|
9243
|
+
return row ? rowToTaskList(row) : null;
|
|
9244
|
+
}
|
|
9245
|
+
function listTaskLists(projectId, db) {
|
|
9246
|
+
const d = db || getDatabase();
|
|
9247
|
+
if (projectId) {
|
|
9248
|
+
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
9249
|
+
}
|
|
9250
|
+
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
9251
|
+
}
|
|
9252
|
+
function updateTaskList(id, input, db) {
|
|
9253
|
+
const d = db || getDatabase();
|
|
9254
|
+
const existing = getTaskList(id, d);
|
|
9255
|
+
if (!existing)
|
|
9256
|
+
throw new TaskListNotFoundError(id);
|
|
9257
|
+
const sets = ["updated_at = ?"];
|
|
9258
|
+
const params = [now()];
|
|
9259
|
+
if (input.name !== undefined) {
|
|
9260
|
+
sets.push("name = ?");
|
|
9261
|
+
params.push(input.name);
|
|
9262
|
+
}
|
|
9263
|
+
if (input.description !== undefined) {
|
|
9264
|
+
sets.push("description = ?");
|
|
9265
|
+
params.push(input.description);
|
|
9266
|
+
}
|
|
9267
|
+
if (input.metadata !== undefined) {
|
|
9268
|
+
sets.push("metadata = ?");
|
|
9269
|
+
params.push(JSON.stringify(input.metadata));
|
|
9270
|
+
}
|
|
9271
|
+
params.push(id);
|
|
9272
|
+
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
9273
|
+
return getTaskList(id, d);
|
|
9274
|
+
}
|
|
9275
|
+
function deleteTaskList(id, db) {
|
|
9276
|
+
const d = db || getDatabase();
|
|
9277
|
+
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
9278
|
+
}
|
|
9279
|
+
function ensureTaskList(name, slug, projectId, db) {
|
|
9280
|
+
const d = db || getDatabase();
|
|
9281
|
+
const existing = getTaskListBySlug(slug, projectId, d);
|
|
9282
|
+
if (existing)
|
|
9283
|
+
return existing;
|
|
9284
|
+
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
9285
|
+
}
|
|
9286
|
+
var init_task_lists = __esm(() => {
|
|
9287
|
+
init_types();
|
|
9288
|
+
init_database();
|
|
9289
|
+
init_projects();
|
|
9290
|
+
});
|
|
9291
|
+
|
|
9165
9292
|
// src/lib/shared-events.ts
|
|
9166
9293
|
function taskEventData(task, extra = {}) {
|
|
9167
9294
|
return {
|
|
@@ -9191,6 +9318,69 @@ function taskEventData(task, extra = {}) {
|
|
|
9191
9318
|
...extra
|
|
9192
9319
|
};
|
|
9193
9320
|
}
|
|
9321
|
+
function taskEventMetadata(task) {
|
|
9322
|
+
const metadata = {
|
|
9323
|
+
package: "@hasna/todos",
|
|
9324
|
+
todos_event_schema_version: 1,
|
|
9325
|
+
task_id: task.id,
|
|
9326
|
+
task_short_id: task.short_id,
|
|
9327
|
+
project_id: task.project_id,
|
|
9328
|
+
task_list_id: task.task_list_id,
|
|
9329
|
+
working_dir: task.working_dir
|
|
9330
|
+
};
|
|
9331
|
+
try {
|
|
9332
|
+
const project = task.project_id ? getProject(task.project_id) : null;
|
|
9333
|
+
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
9334
|
+
if (project) {
|
|
9335
|
+
metadata.project_id = project.id;
|
|
9336
|
+
metadata.project_name = project.name;
|
|
9337
|
+
metadata.project_path = projectPath;
|
|
9338
|
+
metadata.project_canonical_path = project.path;
|
|
9339
|
+
metadata.project_default_task_list_slug = project.task_list_id;
|
|
9340
|
+
metadata.root_project_id = inferRootProjectId(project);
|
|
9341
|
+
} else if (projectPath) {
|
|
9342
|
+
metadata.project_path = projectPath;
|
|
9343
|
+
metadata.project_canonical_path = projectPath;
|
|
9344
|
+
}
|
|
9345
|
+
if (projectPath) {
|
|
9346
|
+
metadata.project_kind = classifyProjectKind(projectPath);
|
|
9347
|
+
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
9348
|
+
if (typeof task.metadata.route_enabled === "boolean") {
|
|
9349
|
+
metadata.route_enabled = task.metadata.route_enabled;
|
|
9350
|
+
}
|
|
9351
|
+
metadata.working_dir = task.working_dir ?? projectPath;
|
|
9352
|
+
}
|
|
9353
|
+
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;
|
|
9354
|
+
if (taskList) {
|
|
9355
|
+
metadata.task_list_id = taskList.id;
|
|
9356
|
+
metadata.task_list_slug = taskList.slug;
|
|
9357
|
+
metadata.task_list_name = taskList.name;
|
|
9358
|
+
metadata.task_list_project_id = taskList.project_id;
|
|
9359
|
+
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
9360
|
+
}
|
|
9361
|
+
} catch {}
|
|
9362
|
+
return metadata;
|
|
9363
|
+
}
|
|
9364
|
+
function classifyProjectKind(path) {
|
|
9365
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
9366
|
+
}
|
|
9367
|
+
function isWorktreePath(path) {
|
|
9368
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
9369
|
+
}
|
|
9370
|
+
function inferRootProjectId(project) {
|
|
9371
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
9372
|
+
}
|
|
9373
|
+
function readMachineLocalPath(project) {
|
|
9374
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
9375
|
+
if (!machineId)
|
|
9376
|
+
return null;
|
|
9377
|
+
try {
|
|
9378
|
+
const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
9379
|
+
return row?.path ?? null;
|
|
9380
|
+
} catch {
|
|
9381
|
+
return null;
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9194
9384
|
async function emitSharedTaskEvent(input) {
|
|
9195
9385
|
const data = taskEventData(input.task, input.data);
|
|
9196
9386
|
await new EventsClient().emit({
|
|
@@ -9201,12 +9391,7 @@ async function emitSharedTaskEvent(input) {
|
|
|
9201
9391
|
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
9202
9392
|
data,
|
|
9203
9393
|
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
9204
|
-
metadata:
|
|
9205
|
-
package: "@hasna/todos",
|
|
9206
|
-
task_id: input.task.id,
|
|
9207
|
-
project_id: input.task.project_id,
|
|
9208
|
-
task_list_id: input.task.task_list_id
|
|
9209
|
-
}
|
|
9394
|
+
metadata: taskEventMetadata(input.task)
|
|
9210
9395
|
}, { deliver: true, dedupe: true });
|
|
9211
9396
|
}
|
|
9212
9397
|
function emitSharedTaskEventQuiet(input) {
|
|
@@ -9217,6 +9402,9 @@ function emitSharedTaskEventQuiet(input) {
|
|
|
9217
9402
|
var SOURCE = "todos";
|
|
9218
9403
|
var init_shared_events = __esm(() => {
|
|
9219
9404
|
init_dist();
|
|
9405
|
+
init_database();
|
|
9406
|
+
init_projects();
|
|
9407
|
+
init_task_lists();
|
|
9220
9408
|
});
|
|
9221
9409
|
|
|
9222
9410
|
// src/lib/secret-redaction.ts
|
|
@@ -14171,90 +14359,6 @@ var init_dispatch = __esm(() => {
|
|
|
14171
14359
|
init_tmux();
|
|
14172
14360
|
});
|
|
14173
14361
|
|
|
14174
|
-
// src/db/task-lists.ts
|
|
14175
|
-
function rowToTaskList(row) {
|
|
14176
|
-
return {
|
|
14177
|
-
...row,
|
|
14178
|
-
metadata: JSON.parse(row.metadata || "{}")
|
|
14179
|
-
};
|
|
14180
|
-
}
|
|
14181
|
-
function createTaskList(input, db) {
|
|
14182
|
-
const d = db || getDatabase();
|
|
14183
|
-
const id = uuid();
|
|
14184
|
-
const timestamp = now();
|
|
14185
|
-
const slug = input.slug || slugify(input.name);
|
|
14186
|
-
if (!input.project_id) {
|
|
14187
|
-
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
14188
|
-
if (existing) {
|
|
14189
|
-
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
14190
|
-
}
|
|
14191
|
-
}
|
|
14192
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
14193
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
14194
|
-
return getTaskList(id, d);
|
|
14195
|
-
}
|
|
14196
|
-
function getTaskList(id, db) {
|
|
14197
|
-
const d = db || getDatabase();
|
|
14198
|
-
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
14199
|
-
return row ? rowToTaskList(row) : null;
|
|
14200
|
-
}
|
|
14201
|
-
function getTaskListBySlug(slug, projectId, db) {
|
|
14202
|
-
const d = db || getDatabase();
|
|
14203
|
-
let row;
|
|
14204
|
-
if (projectId) {
|
|
14205
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
14206
|
-
} else {
|
|
14207
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
14208
|
-
}
|
|
14209
|
-
return row ? rowToTaskList(row) : null;
|
|
14210
|
-
}
|
|
14211
|
-
function listTaskLists(projectId, db) {
|
|
14212
|
-
const d = db || getDatabase();
|
|
14213
|
-
if (projectId) {
|
|
14214
|
-
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
14215
|
-
}
|
|
14216
|
-
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
14217
|
-
}
|
|
14218
|
-
function updateTaskList(id, input, db) {
|
|
14219
|
-
const d = db || getDatabase();
|
|
14220
|
-
const existing = getTaskList(id, d);
|
|
14221
|
-
if (!existing)
|
|
14222
|
-
throw new TaskListNotFoundError(id);
|
|
14223
|
-
const sets = ["updated_at = ?"];
|
|
14224
|
-
const params = [now()];
|
|
14225
|
-
if (input.name !== undefined) {
|
|
14226
|
-
sets.push("name = ?");
|
|
14227
|
-
params.push(input.name);
|
|
14228
|
-
}
|
|
14229
|
-
if (input.description !== undefined) {
|
|
14230
|
-
sets.push("description = ?");
|
|
14231
|
-
params.push(input.description);
|
|
14232
|
-
}
|
|
14233
|
-
if (input.metadata !== undefined) {
|
|
14234
|
-
sets.push("metadata = ?");
|
|
14235
|
-
params.push(JSON.stringify(input.metadata));
|
|
14236
|
-
}
|
|
14237
|
-
params.push(id);
|
|
14238
|
-
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
14239
|
-
return getTaskList(id, d);
|
|
14240
|
-
}
|
|
14241
|
-
function deleteTaskList(id, db) {
|
|
14242
|
-
const d = db || getDatabase();
|
|
14243
|
-
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
14244
|
-
}
|
|
14245
|
-
function ensureTaskList(name, slug, projectId, db) {
|
|
14246
|
-
const d = db || getDatabase();
|
|
14247
|
-
const existing = getTaskListBySlug(slug, projectId, d);
|
|
14248
|
-
if (existing)
|
|
14249
|
-
return existing;
|
|
14250
|
-
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
14251
|
-
}
|
|
14252
|
-
var init_task_lists = __esm(() => {
|
|
14253
|
-
init_types();
|
|
14254
|
-
init_database();
|
|
14255
|
-
init_projects();
|
|
14256
|
-
});
|
|
14257
|
-
|
|
14258
14362
|
// src/mcp/tools/dispatch.ts
|
|
14259
14363
|
function registerDispatchTools(server, { shouldRegisterTool, resolveId, formatError }) {
|
|
14260
14364
|
if (shouldRegisterTool("dispatch_tasks")) {
|
package/dist/registry.js
CHANGED
|
@@ -6773,7 +6773,7 @@ async function testLocalEventHook(name, input) {
|
|
|
6773
6773
|
return emitLocalEventHooks({ ...input, hooks: [hook] });
|
|
6774
6774
|
}
|
|
6775
6775
|
|
|
6776
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
6776
|
+
// node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
|
|
6777
6777
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6778
6778
|
import { existsSync as existsSync6 } from "fs";
|
|
6779
6779
|
import { homedir } from "os";
|
|
@@ -6790,17 +6790,30 @@ function getPathValue(input, path) {
|
|
|
6790
6790
|
return;
|
|
6791
6791
|
}, input);
|
|
6792
6792
|
}
|
|
6793
|
-
function wildcardToRegExp(pattern) {
|
|
6794
|
-
|
|
6795
|
-
|
|
6793
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
6794
|
+
let body = "";
|
|
6795
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
6796
|
+
const char = pattern[index];
|
|
6797
|
+
if (char === "*") {
|
|
6798
|
+
if (pattern[index + 1] === "*") {
|
|
6799
|
+
body += ".*";
|
|
6800
|
+
index += 1;
|
|
6801
|
+
} else {
|
|
6802
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
6803
|
+
}
|
|
6804
|
+
} else {
|
|
6805
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
return new RegExp(`^${body}$`);
|
|
6796
6809
|
}
|
|
6797
|
-
function matchString(value, matcher) {
|
|
6810
|
+
function matchString(value, matcher, options = {}) {
|
|
6798
6811
|
if (matcher === undefined)
|
|
6799
6812
|
return true;
|
|
6800
6813
|
if (value === undefined)
|
|
6801
6814
|
return false;
|
|
6802
6815
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
6803
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
6816
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
6804
6817
|
}
|
|
6805
6818
|
function matchRecord(input, matcher) {
|
|
6806
6819
|
if (!matcher)
|
|
@@ -6808,7 +6821,9 @@ function matchRecord(input, matcher) {
|
|
|
6808
6821
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
6809
6822
|
const actual = getPathValue(input, path);
|
|
6810
6823
|
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
6811
|
-
return matchString(actual === undefined ? undefined : String(actual), expected
|
|
6824
|
+
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
6825
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
6826
|
+
});
|
|
6812
6827
|
}
|
|
6813
6828
|
return actual === expected;
|
|
6814
6829
|
});
|
|
@@ -6828,7 +6843,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
6828
6843
|
function getEventsDataDir(override) {
|
|
6829
6844
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
|
|
6830
6845
|
}
|
|
6831
|
-
|
|
6832
6846
|
class JsonEventsStore {
|
|
6833
6847
|
dataDir;
|
|
6834
6848
|
channelsPath;
|
|
@@ -7165,7 +7179,7 @@ class EventsClient {
|
|
|
7165
7179
|
}
|
|
7166
7180
|
return deliveries;
|
|
7167
7181
|
}
|
|
7168
|
-
async
|
|
7182
|
+
async matchChannel(id, input = {}) {
|
|
7169
7183
|
const channel = await this.store.getChannel(id);
|
|
7170
7184
|
if (!channel)
|
|
7171
7185
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -7182,6 +7196,34 @@ class EventsClient {
|
|
|
7182
7196
|
time: input.time,
|
|
7183
7197
|
id: input.id
|
|
7184
7198
|
});
|
|
7199
|
+
const matched = channelMatchesEvent(channel, event);
|
|
7200
|
+
return {
|
|
7201
|
+
channelId: channel.id,
|
|
7202
|
+
matched,
|
|
7203
|
+
event,
|
|
7204
|
+
filters: channel.filters,
|
|
7205
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
7206
|
+
};
|
|
7207
|
+
}
|
|
7208
|
+
async testChannel(id, input = {}, options = {}) {
|
|
7209
|
+
const channel = await this.store.getChannel(id);
|
|
7210
|
+
if (!channel)
|
|
7211
|
+
throw new Error(`Channel not found: ${id}`);
|
|
7212
|
+
const match = await this.matchChannel(id, input);
|
|
7213
|
+
const event = match.event;
|
|
7214
|
+
if (options.honorFilters && !match.matched) {
|
|
7215
|
+
const timestamp = new Date().toISOString();
|
|
7216
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
7217
|
+
attempt: 1,
|
|
7218
|
+
status: "skipped",
|
|
7219
|
+
startedAt: timestamp,
|
|
7220
|
+
completedAt: timestamp,
|
|
7221
|
+
error: match.reason
|
|
7222
|
+
}]);
|
|
7223
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
7224
|
+
await this.store.appendDelivery(result2);
|
|
7225
|
+
return result2;
|
|
7226
|
+
}
|
|
7185
7227
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
7186
7228
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
7187
7229
|
await this.store.appendDelivery(result);
|
|
@@ -7281,6 +7323,90 @@ function normalizeRetryPolicy(policy) {
|
|
|
7281
7323
|
};
|
|
7282
7324
|
}
|
|
7283
7325
|
|
|
7326
|
+
// src/lib/shared-events.ts
|
|
7327
|
+
init_database();
|
|
7328
|
+
|
|
7329
|
+
// src/db/task-lists.ts
|
|
7330
|
+
init_types();
|
|
7331
|
+
init_database();
|
|
7332
|
+
function rowToTaskList(row) {
|
|
7333
|
+
return {
|
|
7334
|
+
...row,
|
|
7335
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
7336
|
+
};
|
|
7337
|
+
}
|
|
7338
|
+
function createTaskList(input, db) {
|
|
7339
|
+
const d = db || getDatabase();
|
|
7340
|
+
const id = uuid();
|
|
7341
|
+
const timestamp = now();
|
|
7342
|
+
const slug = input.slug || slugify(input.name);
|
|
7343
|
+
if (!input.project_id) {
|
|
7344
|
+
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
7345
|
+
if (existing) {
|
|
7346
|
+
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7349
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
7350
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
7351
|
+
return getTaskList(id, d);
|
|
7352
|
+
}
|
|
7353
|
+
function getTaskList(id, db) {
|
|
7354
|
+
const d = db || getDatabase();
|
|
7355
|
+
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
7356
|
+
return row ? rowToTaskList(row) : null;
|
|
7357
|
+
}
|
|
7358
|
+
function getTaskListBySlug(slug, projectId, db) {
|
|
7359
|
+
const d = db || getDatabase();
|
|
7360
|
+
let row;
|
|
7361
|
+
if (projectId) {
|
|
7362
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
7363
|
+
} else {
|
|
7364
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
7365
|
+
}
|
|
7366
|
+
return row ? rowToTaskList(row) : null;
|
|
7367
|
+
}
|
|
7368
|
+
function listTaskLists(projectId, db) {
|
|
7369
|
+
const d = db || getDatabase();
|
|
7370
|
+
if (projectId) {
|
|
7371
|
+
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
7372
|
+
}
|
|
7373
|
+
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
7374
|
+
}
|
|
7375
|
+
function updateTaskList(id, input, db) {
|
|
7376
|
+
const d = db || getDatabase();
|
|
7377
|
+
const existing = getTaskList(id, d);
|
|
7378
|
+
if (!existing)
|
|
7379
|
+
throw new TaskListNotFoundError(id);
|
|
7380
|
+
const sets = ["updated_at = ?"];
|
|
7381
|
+
const params = [now()];
|
|
7382
|
+
if (input.name !== undefined) {
|
|
7383
|
+
sets.push("name = ?");
|
|
7384
|
+
params.push(input.name);
|
|
7385
|
+
}
|
|
7386
|
+
if (input.description !== undefined) {
|
|
7387
|
+
sets.push("description = ?");
|
|
7388
|
+
params.push(input.description);
|
|
7389
|
+
}
|
|
7390
|
+
if (input.metadata !== undefined) {
|
|
7391
|
+
sets.push("metadata = ?");
|
|
7392
|
+
params.push(JSON.stringify(input.metadata));
|
|
7393
|
+
}
|
|
7394
|
+
params.push(id);
|
|
7395
|
+
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
7396
|
+
return getTaskList(id, d);
|
|
7397
|
+
}
|
|
7398
|
+
function deleteTaskList(id, db) {
|
|
7399
|
+
const d = db || getDatabase();
|
|
7400
|
+
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
7401
|
+
}
|
|
7402
|
+
function ensureTaskList(name, slug, projectId, db) {
|
|
7403
|
+
const d = db || getDatabase();
|
|
7404
|
+
const existing = getTaskListBySlug(slug, projectId, d);
|
|
7405
|
+
if (existing)
|
|
7406
|
+
return existing;
|
|
7407
|
+
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
7408
|
+
}
|
|
7409
|
+
|
|
7284
7410
|
// src/lib/shared-events.ts
|
|
7285
7411
|
var SOURCE = "todos";
|
|
7286
7412
|
function taskEventData(task, extra = {}) {
|
|
@@ -7311,6 +7437,69 @@ function taskEventData(task, extra = {}) {
|
|
|
7311
7437
|
...extra
|
|
7312
7438
|
};
|
|
7313
7439
|
}
|
|
7440
|
+
function taskEventMetadata(task) {
|
|
7441
|
+
const metadata = {
|
|
7442
|
+
package: "@hasna/todos",
|
|
7443
|
+
todos_event_schema_version: 1,
|
|
7444
|
+
task_id: task.id,
|
|
7445
|
+
task_short_id: task.short_id,
|
|
7446
|
+
project_id: task.project_id,
|
|
7447
|
+
task_list_id: task.task_list_id,
|
|
7448
|
+
working_dir: task.working_dir
|
|
7449
|
+
};
|
|
7450
|
+
try {
|
|
7451
|
+
const project = task.project_id ? getProject(task.project_id) : null;
|
|
7452
|
+
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
7453
|
+
if (project) {
|
|
7454
|
+
metadata.project_id = project.id;
|
|
7455
|
+
metadata.project_name = project.name;
|
|
7456
|
+
metadata.project_path = projectPath;
|
|
7457
|
+
metadata.project_canonical_path = project.path;
|
|
7458
|
+
metadata.project_default_task_list_slug = project.task_list_id;
|
|
7459
|
+
metadata.root_project_id = inferRootProjectId(project);
|
|
7460
|
+
} else if (projectPath) {
|
|
7461
|
+
metadata.project_path = projectPath;
|
|
7462
|
+
metadata.project_canonical_path = projectPath;
|
|
7463
|
+
}
|
|
7464
|
+
if (projectPath) {
|
|
7465
|
+
metadata.project_kind = classifyProjectKind(projectPath);
|
|
7466
|
+
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
7467
|
+
if (typeof task.metadata.route_enabled === "boolean") {
|
|
7468
|
+
metadata.route_enabled = task.metadata.route_enabled;
|
|
7469
|
+
}
|
|
7470
|
+
metadata.working_dir = task.working_dir ?? projectPath;
|
|
7471
|
+
}
|
|
7472
|
+
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;
|
|
7473
|
+
if (taskList) {
|
|
7474
|
+
metadata.task_list_id = taskList.id;
|
|
7475
|
+
metadata.task_list_slug = taskList.slug;
|
|
7476
|
+
metadata.task_list_name = taskList.name;
|
|
7477
|
+
metadata.task_list_project_id = taskList.project_id;
|
|
7478
|
+
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
7479
|
+
}
|
|
7480
|
+
} catch {}
|
|
7481
|
+
return metadata;
|
|
7482
|
+
}
|
|
7483
|
+
function classifyProjectKind(path) {
|
|
7484
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
7485
|
+
}
|
|
7486
|
+
function isWorktreePath(path) {
|
|
7487
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
7488
|
+
}
|
|
7489
|
+
function inferRootProjectId(project) {
|
|
7490
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
7491
|
+
}
|
|
7492
|
+
function readMachineLocalPath(project) {
|
|
7493
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
7494
|
+
if (!machineId)
|
|
7495
|
+
return null;
|
|
7496
|
+
try {
|
|
7497
|
+
const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
7498
|
+
return row?.path ?? null;
|
|
7499
|
+
} catch {
|
|
7500
|
+
return null;
|
|
7501
|
+
}
|
|
7502
|
+
}
|
|
7314
7503
|
async function emitSharedTaskEvent(input) {
|
|
7315
7504
|
const data = taskEventData(input.task, input.data);
|
|
7316
7505
|
await new EventsClient().emit({
|
|
@@ -7321,12 +7510,7 @@ async function emitSharedTaskEvent(input) {
|
|
|
7321
7510
|
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
7322
7511
|
data,
|
|
7323
7512
|
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
7324
|
-
metadata:
|
|
7325
|
-
package: "@hasna/todos",
|
|
7326
|
-
task_id: input.task.id,
|
|
7327
|
-
project_id: input.task.project_id,
|
|
7328
|
-
task_list_id: input.task.task_list_id
|
|
7329
|
-
}
|
|
7513
|
+
metadata: taskEventMetadata(input.task)
|
|
7330
7514
|
}, { deliver: true, dedupe: true });
|
|
7331
7515
|
}
|
|
7332
7516
|
function emitSharedTaskEventQuiet(input) {
|
|
@@ -11644,7 +11828,7 @@ function rowToTask2(row) {
|
|
|
11644
11828
|
requires_approval: Boolean(row.requires_approval)
|
|
11645
11829
|
};
|
|
11646
11830
|
}
|
|
11647
|
-
function
|
|
11831
|
+
function rowToTaskList2(row) {
|
|
11648
11832
|
return { ...row, metadata: parseJsonObject3(row.metadata) };
|
|
11649
11833
|
}
|
|
11650
11834
|
function rowWithMetadata(row) {
|
|
@@ -11680,7 +11864,7 @@ function createLocalBridgeBundle(options = {}, db) {
|
|
|
11680
11864
|
const project = options.project_id ? d.query("SELECT * FROM projects WHERE id = ?").get(options.project_id) : null;
|
|
11681
11865
|
const data = redactValue({
|
|
11682
11866
|
projects: options.project_id ? project ? [project] : [] : d.query("SELECT * FROM projects ORDER BY name").all(),
|
|
11683
|
-
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(
|
|
11867
|
+
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),
|
|
11684
11868
|
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(),
|
|
11685
11869
|
tasks: queryByTaskIds(d, "SELECT * FROM tasks WHERE id IN (__TASK_IDS__) ORDER BY created_at", taskIds).map(rowToTask2),
|
|
11686
11870
|
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),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packageName": "@hasna/todos",
|
|
3
|
-
"packageVersion": "0.11.
|
|
3
|
+
"packageVersion": "0.11.58",
|
|
4
4
|
"repository": "https://github.com/hasna/todos.git",
|
|
5
|
-
"gitCommit": "
|
|
6
|
-
"generatedAt": "2026-06-
|
|
5
|
+
"gitCommit": "5f39ea1289e91b8985c1b17e3a21cc58565c0eeb",
|
|
6
|
+
"generatedAt": "2026-06-26T17:33:40.342Z"
|
|
7
7
|
}
|