@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/cli/index.js
CHANGED
|
@@ -6154,7 +6154,7 @@ var init_event_hooks = __esm(() => {
|
|
|
6154
6154
|
VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
|
|
6155
6155
|
});
|
|
6156
6156
|
|
|
6157
|
-
// node_modules/.bun/@hasna+events@0.1.
|
|
6157
|
+
// node_modules/.bun/@hasna+events@0.1.9/node_modules/@hasna/events/dist/index.js
|
|
6158
6158
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
6159
6159
|
import { existsSync as existsSync6 } from "fs";
|
|
6160
6160
|
import { homedir } from "os";
|
|
@@ -6171,17 +6171,30 @@ function getPathValue(input, path) {
|
|
|
6171
6171
|
return;
|
|
6172
6172
|
}, input);
|
|
6173
6173
|
}
|
|
6174
|
-
function wildcardToRegExp(pattern) {
|
|
6175
|
-
|
|
6176
|
-
|
|
6174
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
6175
|
+
let body = "";
|
|
6176
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
6177
|
+
const char = pattern[index];
|
|
6178
|
+
if (char === "*") {
|
|
6179
|
+
if (pattern[index + 1] === "*") {
|
|
6180
|
+
body += ".*";
|
|
6181
|
+
index += 1;
|
|
6182
|
+
} else {
|
|
6183
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
6184
|
+
}
|
|
6185
|
+
} else {
|
|
6186
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
6187
|
+
}
|
|
6188
|
+
}
|
|
6189
|
+
return new RegExp(`^${body}$`);
|
|
6177
6190
|
}
|
|
6178
|
-
function matchString(value, matcher) {
|
|
6191
|
+
function matchString(value, matcher, options = {}) {
|
|
6179
6192
|
if (matcher === undefined)
|
|
6180
6193
|
return true;
|
|
6181
6194
|
if (value === undefined)
|
|
6182
6195
|
return false;
|
|
6183
6196
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
6184
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
6197
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
6185
6198
|
}
|
|
6186
6199
|
function matchRecord(input, matcher) {
|
|
6187
6200
|
if (!matcher)
|
|
@@ -6189,7 +6202,9 @@ function matchRecord(input, matcher) {
|
|
|
6189
6202
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
6190
6203
|
const actual = getPathValue(input, path);
|
|
6191
6204
|
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
6192
|
-
return matchString(actual === undefined ? undefined : String(actual), expected
|
|
6205
|
+
return matchString(actual === undefined ? undefined : String(actual), expected, {
|
|
6206
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
6207
|
+
});
|
|
6193
6208
|
}
|
|
6194
6209
|
return actual === expected;
|
|
6195
6210
|
});
|
|
@@ -6543,7 +6558,7 @@ class EventsClient {
|
|
|
6543
6558
|
}
|
|
6544
6559
|
return deliveries;
|
|
6545
6560
|
}
|
|
6546
|
-
async
|
|
6561
|
+
async matchChannel(id, input = {}) {
|
|
6547
6562
|
const channel = await this.store.getChannel(id);
|
|
6548
6563
|
if (!channel)
|
|
6549
6564
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -6560,6 +6575,34 @@ class EventsClient {
|
|
|
6560
6575
|
time: input.time,
|
|
6561
6576
|
id: input.id
|
|
6562
6577
|
});
|
|
6578
|
+
const matched = channelMatchesEvent(channel, event);
|
|
6579
|
+
return {
|
|
6580
|
+
channelId: channel.id,
|
|
6581
|
+
matched,
|
|
6582
|
+
event,
|
|
6583
|
+
filters: channel.filters,
|
|
6584
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
6585
|
+
};
|
|
6586
|
+
}
|
|
6587
|
+
async testChannel(id, input = {}, options = {}) {
|
|
6588
|
+
const channel = await this.store.getChannel(id);
|
|
6589
|
+
if (!channel)
|
|
6590
|
+
throw new Error(`Channel not found: ${id}`);
|
|
6591
|
+
const match = await this.matchChannel(id, input);
|
|
6592
|
+
const event = match.event;
|
|
6593
|
+
if (options.honorFilters && !match.matched) {
|
|
6594
|
+
const timestamp = new Date().toISOString();
|
|
6595
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
6596
|
+
attempt: 1,
|
|
6597
|
+
status: "skipped",
|
|
6598
|
+
startedAt: timestamp,
|
|
6599
|
+
completedAt: timestamp,
|
|
6600
|
+
error: match.reason
|
|
6601
|
+
}]);
|
|
6602
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
6603
|
+
await this.store.appendDelivery(result2);
|
|
6604
|
+
return result2;
|
|
6605
|
+
}
|
|
6563
6606
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
6564
6607
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
6565
6608
|
await this.store.appendDelivery(result);
|
|
@@ -6663,6 +6706,90 @@ var init_dist = __esm(() => {
|
|
|
6663
6706
|
DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
6664
6707
|
});
|
|
6665
6708
|
|
|
6709
|
+
// src/db/task-lists.ts
|
|
6710
|
+
function rowToTaskList(row) {
|
|
6711
|
+
return {
|
|
6712
|
+
...row,
|
|
6713
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
6714
|
+
};
|
|
6715
|
+
}
|
|
6716
|
+
function createTaskList(input, db) {
|
|
6717
|
+
const d = db || getDatabase();
|
|
6718
|
+
const id = uuid();
|
|
6719
|
+
const timestamp = now();
|
|
6720
|
+
const slug = input.slug || slugify(input.name);
|
|
6721
|
+
if (!input.project_id) {
|
|
6722
|
+
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
6723
|
+
if (existing) {
|
|
6724
|
+
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
6728
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
6729
|
+
return getTaskList(id, d);
|
|
6730
|
+
}
|
|
6731
|
+
function getTaskList(id, db) {
|
|
6732
|
+
const d = db || getDatabase();
|
|
6733
|
+
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
6734
|
+
return row ? rowToTaskList(row) : null;
|
|
6735
|
+
}
|
|
6736
|
+
function getTaskListBySlug(slug, projectId, db) {
|
|
6737
|
+
const d = db || getDatabase();
|
|
6738
|
+
let row;
|
|
6739
|
+
if (projectId) {
|
|
6740
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
6741
|
+
} else {
|
|
6742
|
+
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
6743
|
+
}
|
|
6744
|
+
return row ? rowToTaskList(row) : null;
|
|
6745
|
+
}
|
|
6746
|
+
function listTaskLists(projectId, db) {
|
|
6747
|
+
const d = db || getDatabase();
|
|
6748
|
+
if (projectId) {
|
|
6749
|
+
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
6750
|
+
}
|
|
6751
|
+
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
6752
|
+
}
|
|
6753
|
+
function updateTaskList(id, input, db) {
|
|
6754
|
+
const d = db || getDatabase();
|
|
6755
|
+
const existing = getTaskList(id, d);
|
|
6756
|
+
if (!existing)
|
|
6757
|
+
throw new TaskListNotFoundError(id);
|
|
6758
|
+
const sets = ["updated_at = ?"];
|
|
6759
|
+
const params = [now()];
|
|
6760
|
+
if (input.name !== undefined) {
|
|
6761
|
+
sets.push("name = ?");
|
|
6762
|
+
params.push(input.name);
|
|
6763
|
+
}
|
|
6764
|
+
if (input.description !== undefined) {
|
|
6765
|
+
sets.push("description = ?");
|
|
6766
|
+
params.push(input.description);
|
|
6767
|
+
}
|
|
6768
|
+
if (input.metadata !== undefined) {
|
|
6769
|
+
sets.push("metadata = ?");
|
|
6770
|
+
params.push(JSON.stringify(input.metadata));
|
|
6771
|
+
}
|
|
6772
|
+
params.push(id);
|
|
6773
|
+
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
6774
|
+
return getTaskList(id, d);
|
|
6775
|
+
}
|
|
6776
|
+
function deleteTaskList(id, db) {
|
|
6777
|
+
const d = db || getDatabase();
|
|
6778
|
+
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
6779
|
+
}
|
|
6780
|
+
function ensureTaskList(name, slug, projectId, db) {
|
|
6781
|
+
const d = db || getDatabase();
|
|
6782
|
+
const existing = getTaskListBySlug(slug, projectId, d);
|
|
6783
|
+
if (existing)
|
|
6784
|
+
return existing;
|
|
6785
|
+
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
6786
|
+
}
|
|
6787
|
+
var init_task_lists = __esm(() => {
|
|
6788
|
+
init_types();
|
|
6789
|
+
init_database();
|
|
6790
|
+
init_projects();
|
|
6791
|
+
});
|
|
6792
|
+
|
|
6666
6793
|
// src/lib/shared-events.ts
|
|
6667
6794
|
function taskEventData(task, extra = {}) {
|
|
6668
6795
|
return {
|
|
@@ -6692,6 +6819,69 @@ function taskEventData(task, extra = {}) {
|
|
|
6692
6819
|
...extra
|
|
6693
6820
|
};
|
|
6694
6821
|
}
|
|
6822
|
+
function taskEventMetadata(task) {
|
|
6823
|
+
const metadata = {
|
|
6824
|
+
package: "@hasna/todos",
|
|
6825
|
+
todos_event_schema_version: 1,
|
|
6826
|
+
task_id: task.id,
|
|
6827
|
+
task_short_id: task.short_id,
|
|
6828
|
+
project_id: task.project_id,
|
|
6829
|
+
task_list_id: task.task_list_id,
|
|
6830
|
+
working_dir: task.working_dir
|
|
6831
|
+
};
|
|
6832
|
+
try {
|
|
6833
|
+
const project = task.project_id ? getProject(task.project_id) : null;
|
|
6834
|
+
const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
|
|
6835
|
+
if (project) {
|
|
6836
|
+
metadata.project_id = project.id;
|
|
6837
|
+
metadata.project_name = project.name;
|
|
6838
|
+
metadata.project_path = projectPath;
|
|
6839
|
+
metadata.project_canonical_path = project.path;
|
|
6840
|
+
metadata.project_default_task_list_slug = project.task_list_id;
|
|
6841
|
+
metadata.root_project_id = inferRootProjectId(project);
|
|
6842
|
+
} else if (projectPath) {
|
|
6843
|
+
metadata.project_path = projectPath;
|
|
6844
|
+
metadata.project_canonical_path = projectPath;
|
|
6845
|
+
}
|
|
6846
|
+
if (projectPath) {
|
|
6847
|
+
metadata.project_kind = classifyProjectKind(projectPath);
|
|
6848
|
+
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
6849
|
+
if (typeof task.metadata.route_enabled === "boolean") {
|
|
6850
|
+
metadata.route_enabled = task.metadata.route_enabled;
|
|
6851
|
+
}
|
|
6852
|
+
metadata.working_dir = task.working_dir ?? projectPath;
|
|
6853
|
+
}
|
|
6854
|
+
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;
|
|
6855
|
+
if (taskList) {
|
|
6856
|
+
metadata.task_list_id = taskList.id;
|
|
6857
|
+
metadata.task_list_slug = taskList.slug;
|
|
6858
|
+
metadata.task_list_name = taskList.name;
|
|
6859
|
+
metadata.task_list_project_id = taskList.project_id;
|
|
6860
|
+
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
6861
|
+
}
|
|
6862
|
+
} catch {}
|
|
6863
|
+
return metadata;
|
|
6864
|
+
}
|
|
6865
|
+
function classifyProjectKind(path) {
|
|
6866
|
+
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
6867
|
+
}
|
|
6868
|
+
function isWorktreePath(path) {
|
|
6869
|
+
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
6870
|
+
}
|
|
6871
|
+
function inferRootProjectId(project) {
|
|
6872
|
+
return isWorktreePath(project.path) ? null : project.id;
|
|
6873
|
+
}
|
|
6874
|
+
function readMachineLocalPath(project) {
|
|
6875
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
6876
|
+
if (!machineId)
|
|
6877
|
+
return null;
|
|
6878
|
+
try {
|
|
6879
|
+
const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
6880
|
+
return row?.path ?? null;
|
|
6881
|
+
} catch {
|
|
6882
|
+
return null;
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6695
6885
|
async function emitSharedTaskEvent(input) {
|
|
6696
6886
|
const data = taskEventData(input.task, input.data);
|
|
6697
6887
|
await new EventsClient().emit({
|
|
@@ -6702,12 +6892,7 @@ async function emitSharedTaskEvent(input) {
|
|
|
6702
6892
|
message: input.message ?? `${input.type}: ${input.task.title}`,
|
|
6703
6893
|
data,
|
|
6704
6894
|
dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
|
|
6705
|
-
metadata:
|
|
6706
|
-
package: "@hasna/todos",
|
|
6707
|
-
task_id: input.task.id,
|
|
6708
|
-
project_id: input.task.project_id,
|
|
6709
|
-
task_list_id: input.task.task_list_id
|
|
6710
|
-
}
|
|
6895
|
+
metadata: taskEventMetadata(input.task)
|
|
6711
6896
|
}, { deliver: true, dedupe: true });
|
|
6712
6897
|
}
|
|
6713
6898
|
function emitSharedTaskEventQuiet(input) {
|
|
@@ -6718,6 +6903,9 @@ function emitSharedTaskEventQuiet(input) {
|
|
|
6718
6903
|
var SOURCE = "todos";
|
|
6719
6904
|
var init_shared_events = __esm(() => {
|
|
6720
6905
|
init_dist();
|
|
6906
|
+
init_database();
|
|
6907
|
+
init_projects();
|
|
6908
|
+
init_task_lists();
|
|
6721
6909
|
});
|
|
6722
6910
|
|
|
6723
6911
|
// src/lib/secret-redaction.ts
|
|
@@ -14207,90 +14395,6 @@ var init_sync = __esm(() => {
|
|
|
14207
14395
|
init_config();
|
|
14208
14396
|
});
|
|
14209
14397
|
|
|
14210
|
-
// src/db/task-lists.ts
|
|
14211
|
-
function rowToTaskList(row) {
|
|
14212
|
-
return {
|
|
14213
|
-
...row,
|
|
14214
|
-
metadata: JSON.parse(row.metadata || "{}")
|
|
14215
|
-
};
|
|
14216
|
-
}
|
|
14217
|
-
function createTaskList(input, db) {
|
|
14218
|
-
const d = db || getDatabase();
|
|
14219
|
-
const id = uuid();
|
|
14220
|
-
const timestamp = now();
|
|
14221
|
-
const slug = input.slug || slugify(input.name);
|
|
14222
|
-
if (!input.project_id) {
|
|
14223
|
-
const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
|
|
14224
|
-
if (existing) {
|
|
14225
|
-
throw new Error(`Standalone task list with slug "${slug}" already exists`);
|
|
14226
|
-
}
|
|
14227
|
-
}
|
|
14228
|
-
d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
|
|
14229
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
|
|
14230
|
-
return getTaskList(id, d);
|
|
14231
|
-
}
|
|
14232
|
-
function getTaskList(id, db) {
|
|
14233
|
-
const d = db || getDatabase();
|
|
14234
|
-
const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
|
|
14235
|
-
return row ? rowToTaskList(row) : null;
|
|
14236
|
-
}
|
|
14237
|
-
function getTaskListBySlug(slug, projectId, db) {
|
|
14238
|
-
const d = db || getDatabase();
|
|
14239
|
-
let row;
|
|
14240
|
-
if (projectId) {
|
|
14241
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
|
|
14242
|
-
} else {
|
|
14243
|
-
row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
|
|
14244
|
-
}
|
|
14245
|
-
return row ? rowToTaskList(row) : null;
|
|
14246
|
-
}
|
|
14247
|
-
function listTaskLists(projectId, db) {
|
|
14248
|
-
const d = db || getDatabase();
|
|
14249
|
-
if (projectId) {
|
|
14250
|
-
return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
|
|
14251
|
-
}
|
|
14252
|
-
return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
|
|
14253
|
-
}
|
|
14254
|
-
function updateTaskList(id, input, db) {
|
|
14255
|
-
const d = db || getDatabase();
|
|
14256
|
-
const existing = getTaskList(id, d);
|
|
14257
|
-
if (!existing)
|
|
14258
|
-
throw new TaskListNotFoundError(id);
|
|
14259
|
-
const sets = ["updated_at = ?"];
|
|
14260
|
-
const params = [now()];
|
|
14261
|
-
if (input.name !== undefined) {
|
|
14262
|
-
sets.push("name = ?");
|
|
14263
|
-
params.push(input.name);
|
|
14264
|
-
}
|
|
14265
|
-
if (input.description !== undefined) {
|
|
14266
|
-
sets.push("description = ?");
|
|
14267
|
-
params.push(input.description);
|
|
14268
|
-
}
|
|
14269
|
-
if (input.metadata !== undefined) {
|
|
14270
|
-
sets.push("metadata = ?");
|
|
14271
|
-
params.push(JSON.stringify(input.metadata));
|
|
14272
|
-
}
|
|
14273
|
-
params.push(id);
|
|
14274
|
-
d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
14275
|
-
return getTaskList(id, d);
|
|
14276
|
-
}
|
|
14277
|
-
function deleteTaskList(id, db) {
|
|
14278
|
-
const d = db || getDatabase();
|
|
14279
|
-
return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
|
|
14280
|
-
}
|
|
14281
|
-
function ensureTaskList(name, slug, projectId, db) {
|
|
14282
|
-
const d = db || getDatabase();
|
|
14283
|
-
const existing = getTaskListBySlug(slug, projectId, d);
|
|
14284
|
-
if (existing)
|
|
14285
|
-
return existing;
|
|
14286
|
-
return createTaskList({ name, slug, project_id: projectId }, d);
|
|
14287
|
-
}
|
|
14288
|
-
var init_task_lists = __esm(() => {
|
|
14289
|
-
init_types();
|
|
14290
|
-
init_database();
|
|
14291
|
-
init_projects();
|
|
14292
|
-
});
|
|
14293
|
-
|
|
14294
14398
|
// src/lib/project-bootstrap.ts
|
|
14295
14399
|
var exports_project_bootstrap = {};
|
|
14296
14400
|
__export(exports_project_bootstrap, {
|
package/dist/contracts.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),
|