@hasna/todos 0.13.6 → 0.13.8
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/assignee-guard.d.ts +13 -0
- package/dist/cli/assignee-guard.d.ts.map +1 -0
- package/dist/cli/claim-guard.d.ts +3 -0
- package/dist/cli/claim-guard.d.ts.map +1 -0
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +3299 -3076
- package/dist/contracts.js +81 -29
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/index.js +114 -58
- package/dist/lib/agent-tasks.d.ts.map +1 -1
- package/dist/lib/assignee-context.d.ts +40 -0
- package/dist/lib/assignee-context.d.ts.map +1 -0
- package/dist/lib/assignee-validation.d.ts +72 -0
- package/dist/lib/assignee-validation.d.ts.map +1 -0
- package/dist/lib/claude-tasks.d.ts.map +1 -1
- package/dist/lib/creator-identity.d.ts +30 -0
- package/dist/lib/creator-identity.d.ts.map +1 -1
- package/dist/lib/sync-utils.d.ts +7 -0
- package/dist/lib/sync-utils.d.ts.map +1 -1
- package/dist/mcp/index.js +245 -112
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +81 -29
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +249 -116
- package/dist/storage.js +66 -14
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -70,7 +70,7 @@ var package_default;
|
|
|
70
70
|
var init_package = __esm(() => {
|
|
71
71
|
package_default = {
|
|
72
72
|
name: "@hasna/todos",
|
|
73
|
-
version: "0.13.
|
|
73
|
+
version: "0.13.8",
|
|
74
74
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
75
75
|
type: "module",
|
|
76
76
|
main: "dist/index.js",
|
|
@@ -11956,6 +11956,50 @@ var init_checklists = __esm(() => {
|
|
|
11956
11956
|
init_database();
|
|
11957
11957
|
});
|
|
11958
11958
|
|
|
11959
|
+
// src/lib/creator-identity.ts
|
|
11960
|
+
import { existsSync as existsSync6, rmSync } from "fs";
|
|
11961
|
+
import { join as join5 } from "path";
|
|
11962
|
+
function identityFilePath() {
|
|
11963
|
+
return join5(getTodosGlobalDir(), "identity.json");
|
|
11964
|
+
}
|
|
11965
|
+
function readPersistedIdentity() {
|
|
11966
|
+
const path = identityFilePath();
|
|
11967
|
+
if (!existsSync6(path))
|
|
11968
|
+
return null;
|
|
11969
|
+
const parsed = readJsonFile(path);
|
|
11970
|
+
if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
|
|
11971
|
+
return null;
|
|
11972
|
+
return parsed;
|
|
11973
|
+
}
|
|
11974
|
+
function canonicalAgentRef(value) {
|
|
11975
|
+
return value.trim().toLowerCase();
|
|
11976
|
+
}
|
|
11977
|
+
function isProcessBoundSource(source) {
|
|
11978
|
+
return source === "explicit" || source === "env";
|
|
11979
|
+
}
|
|
11980
|
+
function resolveWritableIdentity(explicit) {
|
|
11981
|
+
const resolved = resolveCreatorIdentity(explicit);
|
|
11982
|
+
if (!isProcessBoundSource(resolved.source))
|
|
11983
|
+
return { agent_id: null, source: "none" };
|
|
11984
|
+
return resolved;
|
|
11985
|
+
}
|
|
11986
|
+
function resolveCreatorIdentity(explicit) {
|
|
11987
|
+
const fromExplicit = explicit?.trim();
|
|
11988
|
+
if (fromExplicit)
|
|
11989
|
+
return { agent_id: canonicalAgentRef(fromExplicit), source: "explicit" };
|
|
11990
|
+
const fromEnv = (process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"] || "").trim();
|
|
11991
|
+
if (fromEnv)
|
|
11992
|
+
return { agent_id: canonicalAgentRef(fromEnv), source: "env" };
|
|
11993
|
+
const persisted = readPersistedIdentity();
|
|
11994
|
+
if (persisted) {
|
|
11995
|
+
return { agent_id: canonicalAgentRef(persisted.agent_name || persisted.agent_id), source: "persisted" };
|
|
11996
|
+
}
|
|
11997
|
+
return { agent_id: null, source: "none" };
|
|
11998
|
+
}
|
|
11999
|
+
var init_creator_identity = __esm(() => {
|
|
12000
|
+
init_sync_utils();
|
|
12001
|
+
});
|
|
12002
|
+
|
|
11959
12003
|
// src/lib/recurrence.ts
|
|
11960
12004
|
function parseRecurrenceRule(rule) {
|
|
11961
12005
|
const normalized = rule.trim().toLowerCase();
|
|
@@ -12682,6 +12726,11 @@ __export(exports_task_lifecycle, {
|
|
|
12682
12726
|
claimOrSteal: () => claimOrSteal,
|
|
12683
12727
|
claimNextTask: () => claimNextTask2
|
|
12684
12728
|
});
|
|
12729
|
+
function sameHolder(stored, incoming) {
|
|
12730
|
+
if (!stored || !incoming)
|
|
12731
|
+
return false;
|
|
12732
|
+
return canonicalAgentRef(stored) === canonicalAgentRef(incoming);
|
|
12733
|
+
}
|
|
12685
12734
|
function lockExpiresAt(lockedAt) {
|
|
12686
12735
|
if (!lockedAt)
|
|
12687
12736
|
return null;
|
|
@@ -12732,13 +12781,13 @@ function startTask2(id, agentId, db) {
|
|
|
12732
12781
|
const cutoff = lockExpiryCutoff();
|
|
12733
12782
|
const timestamp2 = now();
|
|
12734
12783
|
const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
|
|
12735
|
-
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
|
|
12784
|
+
WHERE id = ? AND status IN ('pending', 'in_progress') AND (locked_by IS NULL OR LOWER(TRIM(locked_by)) = LOWER(TRIM(?)) OR locked_at < ?)`, [agentId, agentId, timestamp2, timestamp2, timestamp2, id, agentId, cutoff]);
|
|
12736
12785
|
if (result.changes === 0) {
|
|
12737
12786
|
const current = getTask(id, d);
|
|
12738
12787
|
if (!current)
|
|
12739
12788
|
throw new TaskNotFoundError(id);
|
|
12740
12789
|
assertStartable(current, agentId);
|
|
12741
|
-
if (current.locked_by && current.locked_by
|
|
12790
|
+
if (current.locked_by && !sameHolder(current.locked_by, agentId) && !isLockExpired(current.locked_at)) {
|
|
12742
12791
|
throw new LockError(id, current.locked_by);
|
|
12743
12792
|
}
|
|
12744
12793
|
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
@@ -12763,7 +12812,7 @@ function completeTask2(id, agentId, db, options) {
|
|
|
12763
12812
|
if (task.status === "cancelled") {
|
|
12764
12813
|
throw new Error(`Task ${id} is cancelled and cannot be completed`);
|
|
12765
12814
|
}
|
|
12766
|
-
if (agentId && task.locked_by && task.locked_by
|
|
12815
|
+
if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
|
|
12767
12816
|
throw new LockError(id, task.locked_by);
|
|
12768
12817
|
}
|
|
12769
12818
|
checkCompletionGuard(task, agentId || null, d);
|
|
@@ -12885,16 +12934,16 @@ function lockTask2(id, agentId, db) {
|
|
|
12885
12934
|
error: `Task is ${task.status} and cannot be locked`
|
|
12886
12935
|
};
|
|
12887
12936
|
}
|
|
12888
|
-
if (task.locked_by
|
|
12937
|
+
if (sameHolder(task.locked_by, agentId) && !isLockExpired(task.locked_at)) {
|
|
12889
12938
|
const timestamp3 = now();
|
|
12890
|
-
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND locked_by =
|
|
12939
|
+
d.run(`UPDATE tasks SET locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND LOWER(TRIM(locked_by)) = LOWER(TRIM(?))`, [timestamp3, timestamp3, id, agentId]);
|
|
12891
12940
|
logTaskChange2(id, "lock_renew", "locked_by", agentId, agentId, agentId, d);
|
|
12892
12941
|
return { success: true, locked_by: agentId, locked_at: timestamp3, expires_at: lockExpiresAt(timestamp3) };
|
|
12893
12942
|
}
|
|
12894
12943
|
const cutoff = lockExpiryCutoff();
|
|
12895
12944
|
const timestamp2 = now();
|
|
12896
12945
|
const result = d.run(`UPDATE tasks SET locked_by = ?, locked_at = ?, version = version + 1, updated_at = ?
|
|
12897
|
-
WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
|
|
12946
|
+
WHERE id = ? AND status NOT IN ('completed', 'cancelled') AND (locked_by IS NULL OR LOWER(TRIM(locked_by)) = LOWER(TRIM(?)) OR locked_at < ?)`, [agentId, timestamp2, timestamp2, id, agentId, cutoff]);
|
|
12898
12947
|
if (result.changes === 0) {
|
|
12899
12948
|
const current = getTask(id, d);
|
|
12900
12949
|
if (!current)
|
|
@@ -12926,7 +12975,7 @@ function unlockTask2(id, agentId, db) {
|
|
|
12926
12975
|
const task = getTask(id, d);
|
|
12927
12976
|
if (!task)
|
|
12928
12977
|
throw new TaskNotFoundError(id);
|
|
12929
|
-
if (agentId && task.locked_by && task.locked_by
|
|
12978
|
+
if (agentId && task.locked_by && !sameHolder(task.locked_by, agentId)) {
|
|
12930
12979
|
throw new LockError(id, task.locked_by);
|
|
12931
12980
|
}
|
|
12932
12981
|
const timestamp2 = now();
|
|
@@ -13219,6 +13268,7 @@ var MAX_SPAWN_DEPTH = 10;
|
|
|
13219
13268
|
var init_task_lifecycle = __esm(() => {
|
|
13220
13269
|
init_types();
|
|
13221
13270
|
init_database();
|
|
13271
|
+
init_creator_identity();
|
|
13222
13272
|
init_completion_guard();
|
|
13223
13273
|
init_event_emission_safety();
|
|
13224
13274
|
init_event_hooks();
|
|
@@ -13674,6 +13724,10 @@ function updateTask2(id, input, db) {
|
|
|
13674
13724
|
sets.push("description = ?");
|
|
13675
13725
|
params.push(input.description);
|
|
13676
13726
|
}
|
|
13727
|
+
if (input.agent_id !== undefined) {
|
|
13728
|
+
sets.push("agent_id = ?");
|
|
13729
|
+
params.push(input.agent_id);
|
|
13730
|
+
}
|
|
13677
13731
|
if (input.status !== undefined) {
|
|
13678
13732
|
if (input.status === "completed") {
|
|
13679
13733
|
checkCompletionGuard(task, task.assigned_to || task.agent_id || null, d);
|
|
@@ -15126,8 +15180,8 @@ var init_boards = __esm(() => {
|
|
|
15126
15180
|
|
|
15127
15181
|
// src/lib/artifact-store.ts
|
|
15128
15182
|
import { createHash as createHash4 } from "crypto";
|
|
15129
|
-
import { existsSync as
|
|
15130
|
-
import { basename, dirname as dirname4, join as
|
|
15183
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
15184
|
+
import { basename, dirname as dirname4, join as join6, resolve as resolve7 } from "path";
|
|
15131
15185
|
import { tmpdir as tmpdir2 } from "os";
|
|
15132
15186
|
function isInMemoryDb2(path) {
|
|
15133
15187
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
@@ -15139,15 +15193,15 @@ function artifactStoreRoot() {
|
|
|
15139
15193
|
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
15140
15194
|
const dbPath = getDatabasePath();
|
|
15141
15195
|
if (isInMemoryDb2(dbPath))
|
|
15142
|
-
return
|
|
15143
|
-
return
|
|
15196
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
15197
|
+
return join6(dirname4(resolve7(dbPath)), "artifacts");
|
|
15144
15198
|
}
|
|
15145
15199
|
function artifactStorePath(relativePath) {
|
|
15146
15200
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
15147
15201
|
if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
|
|
15148
15202
|
throw new Error("Invalid artifact store path");
|
|
15149
15203
|
}
|
|
15150
|
-
return
|
|
15204
|
+
return join6(artifactStoreRoot(), normalized);
|
|
15151
15205
|
}
|
|
15152
15206
|
function sha2562(buffer) {
|
|
15153
15207
|
return createHash4("sha256").update(buffer).digest("hex");
|
|
@@ -15188,7 +15242,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
15188
15242
|
}
|
|
15189
15243
|
function storeArtifactContent(input) {
|
|
15190
15244
|
const sourcePath = resolve7(input.path);
|
|
15191
|
-
if (!
|
|
15245
|
+
if (!existsSync7(sourcePath))
|
|
15192
15246
|
return null;
|
|
15193
15247
|
const sourceStat = statSync2(sourcePath);
|
|
15194
15248
|
if (!sourceStat.isFile())
|
|
@@ -15205,9 +15259,9 @@ function storeArtifactContent(input) {
|
|
|
15205
15259
|
redactionStatus = "redacted";
|
|
15206
15260
|
}
|
|
15207
15261
|
const storedSha = sha2562(storedBuffer);
|
|
15208
|
-
const relativePath =
|
|
15262
|
+
const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
15209
15263
|
const destination = artifactStorePath(relativePath);
|
|
15210
|
-
if (!
|
|
15264
|
+
if (!existsSync7(destination)) {
|
|
15211
15265
|
mkdirSync4(dirname4(destination), { recursive: true });
|
|
15212
15266
|
writeFileSync2(destination, storedBuffer);
|
|
15213
15267
|
}
|
|
@@ -15267,7 +15321,7 @@ function verifyStoredArtifact(input) {
|
|
|
15267
15321
|
};
|
|
15268
15322
|
}
|
|
15269
15323
|
const storedPath = artifactStorePath(store.relative_path);
|
|
15270
|
-
if (!
|
|
15324
|
+
if (!existsSync7(storedPath)) {
|
|
15271
15325
|
return {
|
|
15272
15326
|
id: input.id,
|
|
15273
15327
|
path: input.path,
|
|
@@ -17580,8 +17634,8 @@ var exports_doctor = {};
|
|
|
17580
17634
|
__export(exports_doctor, {
|
|
17581
17635
|
runTodosDoctor: () => runTodosDoctor
|
|
17582
17636
|
});
|
|
17583
|
-
import { chmodSync, copyFileSync, existsSync as
|
|
17584
|
-
import { basename as basename2, dirname as dirname5, join as
|
|
17637
|
+
import { chmodSync, copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
|
|
17638
|
+
import { basename as basename2, dirname as dirname5, join as join7 } from "path";
|
|
17585
17639
|
function tableExists2(db, table) {
|
|
17586
17640
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
17587
17641
|
}
|
|
@@ -17675,7 +17729,7 @@ function findMissingProjectRoots(db) {
|
|
|
17675
17729
|
continue;
|
|
17676
17730
|
if (!row.path.startsWith("/"))
|
|
17677
17731
|
continue;
|
|
17678
|
-
if (!
|
|
17732
|
+
if (!existsSync8(row.path))
|
|
17679
17733
|
missing++;
|
|
17680
17734
|
}
|
|
17681
17735
|
return missing;
|
|
@@ -17735,16 +17789,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
17735
17789
|
function createBackup(dbPath) {
|
|
17736
17790
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
17737
17791
|
return;
|
|
17738
|
-
if (!
|
|
17792
|
+
if (!existsSync8(dbPath))
|
|
17739
17793
|
return;
|
|
17740
17794
|
const stamp = now().replace(/[:.]/g, "-");
|
|
17741
|
-
const backupDir =
|
|
17795
|
+
const backupDir = join7(dirname5(dbPath), `${basename2(dbPath)}.backup-${stamp}`);
|
|
17742
17796
|
const files = [];
|
|
17743
17797
|
mkdirSync5(backupDir, { recursive: true });
|
|
17744
17798
|
for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
17745
|
-
if (!
|
|
17799
|
+
if (!existsSync8(source))
|
|
17746
17800
|
continue;
|
|
17747
|
-
const target =
|
|
17801
|
+
const target = join7(backupDir, basename2(source));
|
|
17748
17802
|
copyFileSync(source, target);
|
|
17749
17803
|
files.push(target);
|
|
17750
17804
|
}
|
|
@@ -18003,7 +18057,7 @@ var init_doctor = __esm(() => {
|
|
|
18003
18057
|
});
|
|
18004
18058
|
|
|
18005
18059
|
// src/server/routes.ts
|
|
18006
|
-
import { join as
|
|
18060
|
+
import { join as join8, resolve as resolve8, sep as sep2 } from "path";
|
|
18007
18061
|
function parseFieldsParam(url) {
|
|
18008
18062
|
const fieldsParam = url.searchParams.get("fields");
|
|
18009
18063
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -18798,7 +18852,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
18798
18852
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
18799
18853
|
return null;
|
|
18800
18854
|
if (path !== "/") {
|
|
18801
|
-
const filePath =
|
|
18855
|
+
const filePath = join8(ctx.dashboardDir, path);
|
|
18802
18856
|
const resolvedFile = resolve8(filePath);
|
|
18803
18857
|
const resolvedBase = resolve8(ctx.dashboardDir);
|
|
18804
18858
|
if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
|
|
@@ -18808,7 +18862,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
18808
18862
|
if (res2)
|
|
18809
18863
|
return res2;
|
|
18810
18864
|
}
|
|
18811
|
-
const indexPath =
|
|
18865
|
+
const indexPath = join8(ctx.dashboardDir, "index.html");
|
|
18812
18866
|
const res = serveStaticFile2(indexPath);
|
|
18813
18867
|
if (res)
|
|
18814
18868
|
return res;
|
|
@@ -44242,39 +44296,98 @@ var init_token_utils = __esm(() => {
|
|
|
44242
44296
|
};
|
|
44243
44297
|
});
|
|
44244
44298
|
|
|
44245
|
-
// src/lib/
|
|
44246
|
-
import {
|
|
44247
|
-
import {
|
|
44248
|
-
|
|
44249
|
-
|
|
44299
|
+
// src/lib/assignee-validation.ts
|
|
44300
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
44301
|
+
import { homedir as homedir2 } from "os";
|
|
44302
|
+
import { join as join9 } from "path";
|
|
44303
|
+
function defaultSeatRosterPath() {
|
|
44304
|
+
return process.env["TODOS_SEAT_ROSTER_PATH"] || join9(homedir2(), ".hasna", "identities", "hasna-seats.roster.json");
|
|
44250
44305
|
}
|
|
44251
|
-
function
|
|
44252
|
-
|
|
44253
|
-
|
|
44254
|
-
|
|
44255
|
-
|
|
44256
|
-
|
|
44257
|
-
|
|
44258
|
-
|
|
44306
|
+
function loadSeatSlugs(path = defaultSeatRosterPath()) {
|
|
44307
|
+
try {
|
|
44308
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
44309
|
+
const slugs = new Set;
|
|
44310
|
+
for (const agent of parsed.agents ?? []) {
|
|
44311
|
+
if (typeof agent?.slug === "string" && agent.slug.trim()) {
|
|
44312
|
+
slugs.add(normalizeAgentNameInput(agent.slug));
|
|
44313
|
+
}
|
|
44314
|
+
}
|
|
44315
|
+
return slugs;
|
|
44316
|
+
} catch {
|
|
44317
|
+
return new Set;
|
|
44318
|
+
}
|
|
44259
44319
|
}
|
|
44260
|
-
function
|
|
44261
|
-
|
|
44320
|
+
function validateAssignee(input, ctx) {
|
|
44321
|
+
const raw = input.trim();
|
|
44322
|
+
const normalized = normalizeAgentNameInput(raw);
|
|
44323
|
+
if (!normalized) {
|
|
44324
|
+
return {
|
|
44325
|
+
ok: true,
|
|
44326
|
+
assignee: raw,
|
|
44327
|
+
isSeat: false,
|
|
44328
|
+
warning: "Assignee is empty. Use --unassigned to file this deliberately with no owner."
|
|
44329
|
+
};
|
|
44330
|
+
}
|
|
44331
|
+
const byId = ctx.agents.find((a) => a.id.toLowerCase() === normalized);
|
|
44332
|
+
const byName = ctx.agents.filter((a) => normalizeAgentNameInput(a.name) === normalized);
|
|
44333
|
+
const effectiveName = byId ? normalizeAgentNameInput(byId.name) : normalized;
|
|
44334
|
+
if (ctx.seats.has(effectiveName)) {
|
|
44335
|
+
if (ctx.allowSeat) {
|
|
44336
|
+
return { ok: true, assignee: byId ? byId.name : raw, agentId: byId?.id, isSeat: true };
|
|
44337
|
+
}
|
|
44338
|
+
return {
|
|
44339
|
+
ok: false,
|
|
44340
|
+
reason: "seat",
|
|
44341
|
+
message: `'${raw}' is a durable SEAT, and a task assigned to a seat is assigned to nobody \u2014 no session is watching that queue. ` + `Assign a specific agent, use --unassigned to file it with no owner on purpose, or pass --assign-seat if filing at the seat is what you mean.`
|
|
44342
|
+
};
|
|
44343
|
+
}
|
|
44344
|
+
if (byId) {
|
|
44345
|
+
return { ok: true, assignee: byId.name, agentId: byId.id, isSeat: false };
|
|
44346
|
+
}
|
|
44347
|
+
if (byName.length === 0) {
|
|
44348
|
+
return {
|
|
44349
|
+
ok: true,
|
|
44350
|
+
assignee: raw,
|
|
44351
|
+
isSeat: false,
|
|
44352
|
+
warning: `No agent named '${raw}' is registered yet. If that is a typo the task is now routed to nobody \u2014 ` + `check with 'todos agents'.`
|
|
44353
|
+
};
|
|
44354
|
+
}
|
|
44355
|
+
if (byName.length > 1) {
|
|
44356
|
+
const ids = byName.map((a) => a.id).sort();
|
|
44357
|
+
return {
|
|
44358
|
+
ok: false,
|
|
44359
|
+
reason: "ambiguous",
|
|
44360
|
+
message: `'${raw}' names ${byName.length} registered agents (${ids.join(", ")}), so assigning by name would pick one at random \u2014 ` + `which is how a task lands on a stranger. Pass the agent ID instead.`,
|
|
44361
|
+
candidates: byName
|
|
44362
|
+
};
|
|
44363
|
+
}
|
|
44364
|
+
return { ok: true, assignee: byName[0].name, agentId: byName[0].id, isSeat: false };
|
|
44262
44365
|
}
|
|
44263
|
-
|
|
44264
|
-
|
|
44265
|
-
|
|
44266
|
-
|
|
44267
|
-
|
|
44268
|
-
|
|
44269
|
-
|
|
44270
|
-
|
|
44271
|
-
|
|
44272
|
-
|
|
44366
|
+
var init_assignee_validation = () => {};
|
|
44367
|
+
|
|
44368
|
+
// src/lib/assignee-context.ts
|
|
44369
|
+
async function loadAssigneeContext(listAgentsFn, allowSeat, nowMs = Date.now()) {
|
|
44370
|
+
if (!cache || nowMs - cache.at >= ROSTER_TTL_MS) {
|
|
44371
|
+
let agents;
|
|
44372
|
+
let degraded = false;
|
|
44373
|
+
try {
|
|
44374
|
+
agents = await listAgentsFn();
|
|
44375
|
+
} catch {
|
|
44376
|
+
agents = [];
|
|
44377
|
+
degraded = true;
|
|
44378
|
+
}
|
|
44379
|
+
cache = {
|
|
44380
|
+
at: nowMs,
|
|
44381
|
+
agents: agents.map((a) => ({ id: a.id, name: a.name })),
|
|
44382
|
+
seats: loadSeatSlugs(),
|
|
44383
|
+
degraded
|
|
44384
|
+
};
|
|
44273
44385
|
}
|
|
44274
|
-
return {
|
|
44386
|
+
return { agents: cache.agents, seats: cache.seats, allowSeat, degraded: cache.degraded };
|
|
44275
44387
|
}
|
|
44276
|
-
var
|
|
44277
|
-
|
|
44388
|
+
var ROSTER_TTL_MS = 15000, cache;
|
|
44389
|
+
var init_assignee_context = __esm(() => {
|
|
44390
|
+
init_assignee_validation();
|
|
44278
44391
|
});
|
|
44279
44392
|
|
|
44280
44393
|
// src/pr-groups/http-client.ts
|
|
@@ -45592,6 +45705,14 @@ var init_cloud_router = __esm(() => {
|
|
|
45592
45705
|
});
|
|
45593
45706
|
|
|
45594
45707
|
// src/mcp/tools/task-crud.ts
|
|
45708
|
+
async function validateMcpAssignee(value, allowSeat) {
|
|
45709
|
+
const cloud = getTodosCloudClient();
|
|
45710
|
+
const ctx = await loadAssigneeContext(() => cloud ? cloudListAgents(cloud) : listAgents(), allowSeat);
|
|
45711
|
+
const verdict = validateAssignee(value, ctx);
|
|
45712
|
+
if (!verdict.ok)
|
|
45713
|
+
throw new Error(`Cannot assign to '${value}'. ${verdict.message}`);
|
|
45714
|
+
return verdict.assignee;
|
|
45715
|
+
}
|
|
45595
45716
|
function registerTaskCrudTools(server, ctx) {
|
|
45596
45717
|
const { shouldRegisterTool, resolveId, formatError: formatError2, formatTask } = ctx;
|
|
45597
45718
|
function mutationTaskResponse(task) {
|
|
@@ -45628,6 +45749,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45628
45749
|
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
45629
45750
|
created_by: exports_external.string().optional().describe("Agent who FILED this task. Defaults to the ambient agent identity (todos init / TODOS_AGENT_ID)."),
|
|
45630
45751
|
unassigned: exports_external.boolean().optional().describe("Deliberately file with no assignee. Without it, the task defaults to the filer."),
|
|
45752
|
+
allow_seat: exports_external.boolean().optional().describe("Allow assigned_to to name a durable seat. A seat queue has no session watching it, so this must be deliberate."),
|
|
45631
45753
|
depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
|
|
45632
45754
|
short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
|
|
45633
45755
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
@@ -45638,16 +45760,18 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45638
45760
|
retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
|
|
45639
45761
|
}, async (params) => {
|
|
45640
45762
|
try {
|
|
45641
|
-
const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, created_by, unassigned, ...rest } = params;
|
|
45763
|
+
const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, created_by, unassigned, allow_seat, ...rest } = params;
|
|
45764
|
+
const requestedAssignee = assigned_to ? await validateMcpAssignee(assigned_to, Boolean(allow_seat)) : undefined;
|
|
45642
45765
|
const creator = resolveCreatorIdentity(created_by);
|
|
45643
|
-
const
|
|
45766
|
+
const router = resolveWritableIdentity(created_by);
|
|
45767
|
+
const assignee = requestedAssignee || (unassigned ? undefined : router.agent_id || undefined);
|
|
45644
45768
|
const cloud = getTodosCloudClient();
|
|
45645
45769
|
if (cloud) {
|
|
45646
45770
|
const payload = { ...rest };
|
|
45647
|
-
if (creator.agent_id)
|
|
45771
|
+
if (creator.agent_id)
|
|
45648
45772
|
payload.created_by = creator.agent_id;
|
|
45649
|
-
|
|
45650
|
-
|
|
45773
|
+
if (router.agent_id)
|
|
45774
|
+
payload.agent_id = payload.agent_id ?? router.agent_id;
|
|
45651
45775
|
if (assignee)
|
|
45652
45776
|
payload.assigned_to = assignee;
|
|
45653
45777
|
if (project_id)
|
|
@@ -45670,10 +45794,10 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45670
45794
|
return { content: [{ type: "text", text: mutationTaskResponse(created) }] };
|
|
45671
45795
|
}
|
|
45672
45796
|
const resolved = { ...rest };
|
|
45673
|
-
if (creator.agent_id)
|
|
45797
|
+
if (creator.agent_id)
|
|
45674
45798
|
resolved.created_by = creator.agent_id;
|
|
45675
|
-
|
|
45676
|
-
|
|
45799
|
+
if (router.agent_id)
|
|
45800
|
+
resolved.agent_id = resolved.agent_id ?? router.agent_id;
|
|
45677
45801
|
if (assignee)
|
|
45678
45802
|
resolved.assigned_to = resolveAssignee(assignee);
|
|
45679
45803
|
if (project_id)
|
|
@@ -45709,6 +45833,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45709
45833
|
project_id: exports_external.string().optional().describe("Project ID"),
|
|
45710
45834
|
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
45711
45835
|
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
45836
|
+
allow_seat: exports_external.boolean().optional().describe("Allow assigned_to to name a durable seat. A seat queue has no session watching it, so this must be deliberate."),
|
|
45712
45837
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
45713
45838
|
working_dir: exports_external.string().optional().describe("Working directory associated with the task"),
|
|
45714
45839
|
metadata: exports_external.record(exports_external.unknown()).optional().describe("Metadata object to shallow-merge"),
|
|
@@ -45722,7 +45847,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45722
45847
|
acceptance: exports_external.unknown().optional()
|
|
45723
45848
|
}, async (params) => {
|
|
45724
45849
|
try {
|
|
45725
|
-
const { assigned_to, project_id, task_list_id, metadata, expectation_id, expectation_fingerprint, evidence_paths, origin_loop_id, origin_run_id, expected, observed, acceptance, ...rest } = params;
|
|
45850
|
+
const { assigned_to, allow_seat: _allowSeatUpsert, project_id, task_list_id, metadata, expectation_id, expectation_fingerprint, evidence_paths, origin_loop_id, origin_run_id, expected, observed, acceptance, ...rest } = params;
|
|
45726
45851
|
const mergedMetadata = { ...metadata ?? {} };
|
|
45727
45852
|
if (expectation_id !== undefined)
|
|
45728
45853
|
mergedMetadata["expectation_id"] = expectation_id;
|
|
@@ -45741,8 +45866,9 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45741
45866
|
if (acceptance !== undefined)
|
|
45742
45867
|
mergedMetadata["acceptance"] = acceptance;
|
|
45743
45868
|
const resolved = { ...rest, metadata: mergedMetadata };
|
|
45744
|
-
if (assigned_to)
|
|
45745
|
-
resolved.assigned_to = resolveAssignee(assigned_to);
|
|
45869
|
+
if (assigned_to) {
|
|
45870
|
+
resolved.assigned_to = resolveAssignee(await validateMcpAssignee(assigned_to, Boolean(params.allow_seat)));
|
|
45871
|
+
}
|
|
45746
45872
|
if (project_id)
|
|
45747
45873
|
resolved.project_id = resolveId(project_id, "projects");
|
|
45748
45874
|
if (task_list_id)
|
|
@@ -45873,12 +45999,16 @@ ${task.description}` : null
|
|
|
45873
45999
|
completed_at: exports_external.string().optional().describe("ISO timestamp for backdating completion"),
|
|
45874
46000
|
deadline: exports_external.string().nullable().optional(),
|
|
45875
46001
|
retry_count: exports_external.number().optional(),
|
|
46002
|
+
allow_seat: exports_external.boolean().optional().describe("Allow assigned_to to name a durable seat. A seat queue has no session watching it, so this must be deliberate."),
|
|
45876
46003
|
version: exports_external.number().optional().describe("Expected version for optimistic locking")
|
|
45877
46004
|
}, async (params) => {
|
|
45878
46005
|
try {
|
|
46006
|
+
if (typeof params.assigned_to === "string" && params.assigned_to !== "") {
|
|
46007
|
+
params.assigned_to = await validateMcpAssignee(params.assigned_to, Boolean(params.allow_seat));
|
|
46008
|
+
}
|
|
45879
46009
|
const cloud = getTodosCloudClient();
|
|
45880
46010
|
if (cloud) {
|
|
45881
|
-
const { task_id: task_id2, version: version3, estimate, deadline, ...updates2 } = params;
|
|
46011
|
+
const { task_id: task_id2, version: version3, estimate, deadline, allow_seat: _allowSeatCloud, ...updates2 } = params;
|
|
45882
46012
|
const patch = { ...updates2 };
|
|
45883
46013
|
if (patch.assigned_to === "")
|
|
45884
46014
|
patch.assigned_to = null;
|
|
@@ -45903,7 +46033,7 @@ ${task.description}` : null
|
|
|
45903
46033
|
return { content: [{ type: "text", text: mutationTaskResponse(updated) }] };
|
|
45904
46034
|
}
|
|
45905
46035
|
const resolvedId = resolveId(params.task_id);
|
|
45906
|
-
const { task_id, version: version2, ...updates } = params;
|
|
46036
|
+
const { task_id, version: version2, allow_seat: _allowSeatLocal, ...updates } = params;
|
|
45907
46037
|
const resolved = { ...updates };
|
|
45908
46038
|
if (resolved.assigned_to === "")
|
|
45909
46039
|
resolved.assigned_to = null;
|
|
@@ -45956,11 +46086,14 @@ var init_task_crud2 = __esm(() => {
|
|
|
45956
46086
|
init_types();
|
|
45957
46087
|
init_token_utils();
|
|
45958
46088
|
init_creator_identity();
|
|
46089
|
+
init_assignee_validation();
|
|
46090
|
+
init_assignee_context();
|
|
46091
|
+
init_agents();
|
|
45959
46092
|
init_cloud_router();
|
|
45960
46093
|
});
|
|
45961
46094
|
|
|
45962
46095
|
// src/lib/project-bootstrap.ts
|
|
45963
|
-
import { existsSync as existsSync9, readFileSync as
|
|
46096
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
45964
46097
|
import { basename as basename3, dirname as dirname6, resolve as resolve9 } from "path";
|
|
45965
46098
|
function safeStat(path) {
|
|
45966
46099
|
try {
|
|
@@ -45994,7 +46127,7 @@ function readPackageJson(path) {
|
|
|
45994
46127
|
if (!existsSync9(file))
|
|
45995
46128
|
return null;
|
|
45996
46129
|
try {
|
|
45997
|
-
const parsed = JSON.parse(
|
|
46130
|
+
const parsed = JSON.parse(readFileSync4(file, "utf-8"));
|
|
45998
46131
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
45999
46132
|
} catch {
|
|
46000
46133
|
return null;
|
|
@@ -46556,8 +46689,8 @@ var init_retention_cleanup = __esm(() => {
|
|
|
46556
46689
|
});
|
|
46557
46690
|
|
|
46558
46691
|
// src/lib/mention-resolver.ts
|
|
46559
|
-
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as
|
|
46560
|
-
import { basename as basename4, isAbsolute, join as
|
|
46692
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
|
|
46693
|
+
import { basename as basename4, isAbsolute, join as join10, relative as relative3, resolve as resolve10, sep as sep3 } from "path";
|
|
46561
46694
|
function blankResolution(parsed) {
|
|
46562
46695
|
return {
|
|
46563
46696
|
input: parsed.input,
|
|
@@ -46665,7 +46798,7 @@ function resolveFile(parsed, workspace) {
|
|
|
46665
46798
|
return resolution;
|
|
46666
46799
|
}
|
|
46667
46800
|
if (parsed.line !== undefined) {
|
|
46668
|
-
const lineCount =
|
|
46801
|
+
const lineCount = readFileSync5(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
46669
46802
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
46670
46803
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
46671
46804
|
return resolution;
|
|
@@ -46688,7 +46821,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
46688
46821
|
if (SKIP_DIRS.has(entry2.name))
|
|
46689
46822
|
continue;
|
|
46690
46823
|
}
|
|
46691
|
-
const absolutePath =
|
|
46824
|
+
const absolutePath = join10(current, entry2.name);
|
|
46692
46825
|
if (entry2.isDirectory()) {
|
|
46693
46826
|
if (!SKIP_DIRS.has(entry2.name))
|
|
46694
46827
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -46718,7 +46851,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
46718
46851
|
const pattern = symbolPattern(name);
|
|
46719
46852
|
const matches = [];
|
|
46720
46853
|
for (const file of walkSourceFiles(workspace)) {
|
|
46721
|
-
const lines =
|
|
46854
|
+
const lines = readFileSync5(file, "utf-8").split(/\r?\n/);
|
|
46722
46855
|
for (let index = 0;index < lines.length; index += 1) {
|
|
46723
46856
|
const line = lines[index];
|
|
46724
46857
|
const found = pattern.exec(line);
|
|
@@ -50379,8 +50512,8 @@ var init_audit_ledger = __esm(() => {
|
|
|
50379
50512
|
});
|
|
50380
50513
|
|
|
50381
50514
|
// src/lib/release-compatibility.ts
|
|
50382
|
-
import { readFileSync as
|
|
50383
|
-
import { join as
|
|
50515
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
50516
|
+
import { join as join11, resolve as resolve12 } from "path";
|
|
50384
50517
|
import { Database as Database2 } from "bun:sqlite";
|
|
50385
50518
|
function pass(id, message, details) {
|
|
50386
50519
|
return { id, status: "passed", message, details };
|
|
@@ -50392,7 +50525,7 @@ function warn(id, message, details) {
|
|
|
50392
50525
|
return { id, status: "warning", message, details };
|
|
50393
50526
|
}
|
|
50394
50527
|
function readPackageJson2(root) {
|
|
50395
|
-
return JSON.parse(
|
|
50528
|
+
return JSON.parse(readFileSync6(join11(root, "package.json"), "utf8"));
|
|
50396
50529
|
}
|
|
50397
50530
|
function sortedKeys(value) {
|
|
50398
50531
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -80287,7 +80420,7 @@ var init_agent_run_dispatcher = __esm(() => {
|
|
|
80287
80420
|
});
|
|
80288
80421
|
|
|
80289
80422
|
// src/lib/verification-providers.ts
|
|
80290
|
-
import { existsSync as existsSync12, readFileSync as
|
|
80423
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
80291
80424
|
function normalizeName5(name) {
|
|
80292
80425
|
const normalized = name.trim().toLowerCase();
|
|
80293
80426
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -80439,7 +80572,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
80439
80572
|
};
|
|
80440
80573
|
}
|
|
80441
80574
|
function runCiLogProvider(input) {
|
|
80442
|
-
const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ?
|
|
80575
|
+
const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync7(input.log_path, "utf-8") : "");
|
|
80443
80576
|
return {
|
|
80444
80577
|
status: classifyLog(text),
|
|
80445
80578
|
attempts: 1,
|
|
@@ -83171,7 +83304,7 @@ var init_local_bridge = __esm(() => {
|
|
|
83171
83304
|
|
|
83172
83305
|
// src/lib/local-backups.ts
|
|
83173
83306
|
import { createHash as createHash7 } from "crypto";
|
|
83174
|
-
import { readFileSync as
|
|
83307
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
83175
83308
|
import { dirname as dirname7, resolve as resolve13 } from "path";
|
|
83176
83309
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
83177
83310
|
function stableJson2(value) {
|
|
@@ -83281,7 +83414,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
83281
83414
|
return path;
|
|
83282
83415
|
}
|
|
83283
83416
|
function readLocalBackupFile(path) {
|
|
83284
|
-
return JSON.parse(
|
|
83417
|
+
return JSON.parse(readFileSync8(resolve13(path), "utf-8"));
|
|
83285
83418
|
}
|
|
83286
83419
|
function verifyLocalBackup(value, options = {}, db) {
|
|
83287
83420
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -84732,8 +84865,8 @@ __export(exports_local_extensions, {
|
|
|
84732
84865
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
84733
84866
|
});
|
|
84734
84867
|
import { createHash as createHash10, createVerify } from "crypto";
|
|
84735
|
-
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as
|
|
84736
|
-
import { basename as basename5, join as
|
|
84868
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
84869
|
+
import { basename as basename5, join as join12, resolve as resolve14 } from "path";
|
|
84737
84870
|
function isObject3(value) {
|
|
84738
84871
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
84739
84872
|
}
|
|
@@ -84814,7 +84947,7 @@ function normalizeManifest(input) {
|
|
|
84814
84947
|
};
|
|
84815
84948
|
}
|
|
84816
84949
|
function parseJson(path) {
|
|
84817
|
-
return JSON.parse(
|
|
84950
|
+
return JSON.parse(readFileSync9(path, "utf8"));
|
|
84818
84951
|
}
|
|
84819
84952
|
function sha2566(bytes) {
|
|
84820
84953
|
return `sha256:${createHash10("sha256").update(bytes).digest("hex")}`;
|
|
@@ -84995,10 +85128,10 @@ function inspectExtensionSource(source3) {
|
|
|
84995
85128
|
if (!existsSync13(resolved))
|
|
84996
85129
|
throw new Error(`extension source not found: ${source3}`);
|
|
84997
85130
|
const stat = statSync6(resolved);
|
|
84998
|
-
const manifestPath = stat.isDirectory() ? [
|
|
85131
|
+
const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync13) : resolved;
|
|
84999
85132
|
if (!manifestPath)
|
|
85000
85133
|
throw new Error(`extension directory ${source3} is missing todos.extension.json`);
|
|
85001
|
-
const raw =
|
|
85134
|
+
const raw = readFileSync9(manifestPath);
|
|
85002
85135
|
const parsed = parseJson(manifestPath);
|
|
85003
85136
|
const bundle = isObject3(parsed) && isObject3(parsed["manifest"]);
|
|
85004
85137
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -85091,15 +85224,15 @@ function projectExtensionSources(projectPath) {
|
|
|
85091
85224
|
return [];
|
|
85092
85225
|
const root = resolve14(projectPath);
|
|
85093
85226
|
const candidates = [
|
|
85094
|
-
|
|
85095
|
-
|
|
85227
|
+
join12(root, "todos.extension.json"),
|
|
85228
|
+
join12(root, ".todos", "todos.extension.json")
|
|
85096
85229
|
];
|
|
85097
|
-
const extensionDir =
|
|
85230
|
+
const extensionDir = join12(root, ".todos", "extensions");
|
|
85098
85231
|
if (existsSync13(extensionDir)) {
|
|
85099
85232
|
for (const entry2 of readdirSync3(extensionDir)) {
|
|
85100
85233
|
if (entry2.startsWith("."))
|
|
85101
85234
|
continue;
|
|
85102
|
-
const full =
|
|
85235
|
+
const full = join12(extensionDir, entry2);
|
|
85103
85236
|
if (statSync6(full).isDirectory() || entry2.endsWith(".json"))
|
|
85104
85237
|
candidates.push(full);
|
|
85105
85238
|
}
|
|
@@ -89511,9 +89644,9 @@ __export(exports_extract, {
|
|
|
89511
89644
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
89512
89645
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
89513
89646
|
});
|
|
89514
|
-
import { existsSync as existsSync14, readFileSync as
|
|
89647
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
89515
89648
|
import { createHash as createHash12 } from "crypto";
|
|
89516
|
-
import { relative as relative5, resolve as resolve15, join as
|
|
89649
|
+
import { relative as relative5, resolve as resolve15, join as join13 } from "path";
|
|
89517
89650
|
function stableHash(value) {
|
|
89518
89651
|
return createHash12("sha256").update(value).digest("hex");
|
|
89519
89652
|
}
|
|
@@ -89522,11 +89655,11 @@ function normalizePathForMatch(value) {
|
|
|
89522
89655
|
}
|
|
89523
89656
|
function readGitignorePatterns(basePath) {
|
|
89524
89657
|
const root = statSync7(basePath).isFile() ? resolve15(basePath, "..") : basePath;
|
|
89525
|
-
const gitignorePath =
|
|
89658
|
+
const gitignorePath = join13(root, ".gitignore");
|
|
89526
89659
|
if (!existsSync14(gitignorePath))
|
|
89527
89660
|
return [];
|
|
89528
89661
|
try {
|
|
89529
|
-
return
|
|
89662
|
+
return readFileSync10(gitignorePath, "utf-8").split(`
|
|
89530
89663
|
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
|
|
89531
89664
|
} catch {
|
|
89532
89665
|
return [];
|
|
@@ -89665,9 +89798,9 @@ function buildCodebaseIndex(options) {
|
|
|
89665
89798
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
89666
89799
|
const indexed = [];
|
|
89667
89800
|
for (const file of files) {
|
|
89668
|
-
const fullPath = statSync7(basePath).isFile() ? basePath :
|
|
89801
|
+
const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
|
|
89669
89802
|
try {
|
|
89670
|
-
const source3 =
|
|
89803
|
+
const source3 = readFileSync10(fullPath, "utf-8");
|
|
89671
89804
|
const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
|
|
89672
89805
|
indexed.push({
|
|
89673
89806
|
file: relPath,
|
|
@@ -89696,9 +89829,9 @@ function extractTodos(options, db) {
|
|
|
89696
89829
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
89697
89830
|
const allComments = [];
|
|
89698
89831
|
for (const file of files) {
|
|
89699
|
-
const fullPath = statSync7(basePath).isFile() ? basePath :
|
|
89832
|
+
const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
|
|
89700
89833
|
try {
|
|
89701
|
-
const source3 =
|
|
89834
|
+
const source3 = readFileSync10(fullPath, "utf-8");
|
|
89702
89835
|
const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
|
|
89703
89836
|
const comments = extractFromSource(source3, relPath, tags);
|
|
89704
89837
|
allComments.push(...comments);
|
|
@@ -90647,7 +90780,7 @@ __export(exports_builtin_templates, {
|
|
|
90647
90780
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
90648
90781
|
});
|
|
90649
90782
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
90650
|
-
import { join as
|
|
90783
|
+
import { join as join14 } from "path";
|
|
90651
90784
|
function templateMetadata(template) {
|
|
90652
90785
|
return {
|
|
90653
90786
|
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
@@ -90706,7 +90839,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
90706
90839
|
mkdirSync7(directory, { recursive: true });
|
|
90707
90840
|
const files = [];
|
|
90708
90841
|
for (const entry2 of exportBuiltinTemplateFiles()) {
|
|
90709
|
-
const path =
|
|
90842
|
+
const path = join14(directory, entry2.filename);
|
|
90710
90843
|
writeFileSync4(path, `${JSON.stringify(entry2.template, null, 2)}
|
|
90711
90844
|
`, "utf-8");
|
|
90712
90845
|
files.push(path);
|
|
@@ -91232,28 +91365,28 @@ __export(exports_environment_snapshots, {
|
|
|
91232
91365
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
91233
91366
|
});
|
|
91234
91367
|
import { createHash as createHash13 } from "crypto";
|
|
91235
|
-
import { existsSync as existsSync15, readFileSync as
|
|
91368
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
|
|
91236
91369
|
import { hostname as hostname3, platform, arch } from "os";
|
|
91237
|
-
import { dirname as dirname8, join as
|
|
91370
|
+
import { dirname as dirname8, join as join15, resolve as resolve16 } from "path";
|
|
91238
91371
|
import { tmpdir as tmpdir3 } from "os";
|
|
91239
91372
|
function sha2567(value) {
|
|
91240
91373
|
return createHash13("sha256").update(value).digest("hex");
|
|
91241
91374
|
}
|
|
91242
91375
|
function fileRecord(root, relativePath) {
|
|
91243
|
-
const path =
|
|
91376
|
+
const path = join15(root, relativePath);
|
|
91244
91377
|
if (!existsSync15(path))
|
|
91245
91378
|
return null;
|
|
91246
91379
|
const stat = statSync8(path);
|
|
91247
91380
|
if (!stat.isFile())
|
|
91248
91381
|
return null;
|
|
91249
|
-
const content =
|
|
91382
|
+
const content = readFileSync11(path);
|
|
91250
91383
|
return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
|
|
91251
91384
|
}
|
|
91252
91385
|
function manifestRecord(root, relativePath) {
|
|
91253
91386
|
const base = fileRecord(root, relativePath);
|
|
91254
91387
|
if (!base)
|
|
91255
91388
|
return null;
|
|
91256
|
-
const parsed = readJsonFile(
|
|
91389
|
+
const parsed = readJsonFile(join15(root, relativePath));
|
|
91257
91390
|
if (!parsed)
|
|
91258
91391
|
return { ...base, redacted: {} };
|
|
91259
91392
|
const redacted = redactValue({
|
|
@@ -91348,8 +91481,8 @@ function commandEnv(env, includeValues) {
|
|
|
91348
91481
|
function defaultSnapshotDir() {
|
|
91349
91482
|
const dbPath = getDatabasePath();
|
|
91350
91483
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
91351
|
-
return
|
|
91352
|
-
return
|
|
91484
|
+
return join15(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
91485
|
+
return join15(dirname8(resolve16(dbPath)), "environment-snapshots");
|
|
91353
91486
|
}
|
|
91354
91487
|
function snapshotWithId(snapshot) {
|
|
91355
91488
|
const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
|
|
@@ -91396,7 +91529,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
91396
91529
|
});
|
|
91397
91530
|
}
|
|
91398
91531
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
91399
|
-
const path = outputPath ? resolve16(outputPath) :
|
|
91532
|
+
const path = outputPath ? resolve16(outputPath) : join15(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
91400
91533
|
ensureDir(dirname8(path));
|
|
91401
91534
|
writeJsonFile(path, snapshot);
|
|
91402
91535
|
return path;
|
|
@@ -92320,26 +92453,26 @@ __export(exports_serve, {
|
|
|
92320
92453
|
MIME_TYPES: () => MIME_TYPES
|
|
92321
92454
|
});
|
|
92322
92455
|
import { existsSync as existsSync16 } from "fs";
|
|
92323
|
-
import { join as
|
|
92456
|
+
import { join as join16, dirname as dirname9, extname } from "path";
|
|
92324
92457
|
import { fileURLToPath } from "url";
|
|
92325
92458
|
function resolveDashboardDir() {
|
|
92326
92459
|
const candidates = [];
|
|
92327
92460
|
try {
|
|
92328
92461
|
const scriptDir = dirname9(fileURLToPath(import.meta.url));
|
|
92329
|
-
candidates.push(
|
|
92330
|
-
candidates.push(
|
|
92462
|
+
candidates.push(join16(scriptDir, "..", "dashboard", "dist"));
|
|
92463
|
+
candidates.push(join16(scriptDir, "..", "..", "dashboard", "dist"));
|
|
92331
92464
|
} catch {}
|
|
92332
92465
|
if (process.argv[1]) {
|
|
92333
92466
|
const mainDir = dirname9(process.argv[1]);
|
|
92334
|
-
candidates.push(
|
|
92335
|
-
candidates.push(
|
|
92467
|
+
candidates.push(join16(mainDir, "..", "dashboard", "dist"));
|
|
92468
|
+
candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
|
|
92336
92469
|
}
|
|
92337
|
-
candidates.push(
|
|
92470
|
+
candidates.push(join16(process.cwd(), "dashboard", "dist"));
|
|
92338
92471
|
for (const candidate of candidates) {
|
|
92339
92472
|
if (existsSync16(candidate))
|
|
92340
92473
|
return candidate;
|
|
92341
92474
|
}
|
|
92342
|
-
return
|
|
92475
|
+
return join16(process.cwd(), "dashboard", "dist");
|
|
92343
92476
|
}
|
|
92344
92477
|
function getProvidedApiKey(req) {
|
|
92345
92478
|
const headerKey = req.headers.get("x-api-key");
|