@hasna/todos 0.13.7 → 0.13.9
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 +3231 -3024
- package/dist/contracts.js +77 -29
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/index.js +110 -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/sync-utils.d.ts +7 -0
- package/dist/lib/sync-utils.d.ts.map +1 -1
- package/dist/mcp/index.js +236 -117
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +77 -29
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +240 -121
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +62 -14
- 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.9",
|
|
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();
|
|
@@ -15130,8 +15180,8 @@ var init_boards = __esm(() => {
|
|
|
15130
15180
|
|
|
15131
15181
|
// src/lib/artifact-store.ts
|
|
15132
15182
|
import { createHash as createHash4 } from "crypto";
|
|
15133
|
-
import { existsSync as
|
|
15134
|
-
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";
|
|
15135
15185
|
import { tmpdir as tmpdir2 } from "os";
|
|
15136
15186
|
function isInMemoryDb2(path) {
|
|
15137
15187
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
@@ -15143,15 +15193,15 @@ function artifactStoreRoot() {
|
|
|
15143
15193
|
return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
|
|
15144
15194
|
const dbPath = getDatabasePath();
|
|
15145
15195
|
if (isInMemoryDb2(dbPath))
|
|
15146
|
-
return
|
|
15147
|
-
return
|
|
15196
|
+
return join6(tmpdir2(), "hasna-todos-artifacts");
|
|
15197
|
+
return join6(dirname4(resolve7(dbPath)), "artifacts");
|
|
15148
15198
|
}
|
|
15149
15199
|
function artifactStorePath(relativePath) {
|
|
15150
15200
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
15151
15201
|
if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
|
|
15152
15202
|
throw new Error("Invalid artifact store path");
|
|
15153
15203
|
}
|
|
15154
|
-
return
|
|
15204
|
+
return join6(artifactStoreRoot(), normalized);
|
|
15155
15205
|
}
|
|
15156
15206
|
function sha2562(buffer) {
|
|
15157
15207
|
return createHash4("sha256").update(buffer).digest("hex");
|
|
@@ -15192,7 +15242,7 @@ function mediaTypeFor(path, textLike) {
|
|
|
15192
15242
|
}
|
|
15193
15243
|
function storeArtifactContent(input) {
|
|
15194
15244
|
const sourcePath = resolve7(input.path);
|
|
15195
|
-
if (!
|
|
15245
|
+
if (!existsSync7(sourcePath))
|
|
15196
15246
|
return null;
|
|
15197
15247
|
const sourceStat = statSync2(sourcePath);
|
|
15198
15248
|
if (!sourceStat.isFile())
|
|
@@ -15209,9 +15259,9 @@ function storeArtifactContent(input) {
|
|
|
15209
15259
|
redactionStatus = "redacted";
|
|
15210
15260
|
}
|
|
15211
15261
|
const storedSha = sha2562(storedBuffer);
|
|
15212
|
-
const relativePath =
|
|
15262
|
+
const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
|
|
15213
15263
|
const destination = artifactStorePath(relativePath);
|
|
15214
|
-
if (!
|
|
15264
|
+
if (!existsSync7(destination)) {
|
|
15215
15265
|
mkdirSync4(dirname4(destination), { recursive: true });
|
|
15216
15266
|
writeFileSync2(destination, storedBuffer);
|
|
15217
15267
|
}
|
|
@@ -15271,7 +15321,7 @@ function verifyStoredArtifact(input) {
|
|
|
15271
15321
|
};
|
|
15272
15322
|
}
|
|
15273
15323
|
const storedPath = artifactStorePath(store.relative_path);
|
|
15274
|
-
if (!
|
|
15324
|
+
if (!existsSync7(storedPath)) {
|
|
15275
15325
|
return {
|
|
15276
15326
|
id: input.id,
|
|
15277
15327
|
path: input.path,
|
|
@@ -17584,8 +17634,8 @@ var exports_doctor = {};
|
|
|
17584
17634
|
__export(exports_doctor, {
|
|
17585
17635
|
runTodosDoctor: () => runTodosDoctor
|
|
17586
17636
|
});
|
|
17587
|
-
import { chmodSync, copyFileSync, existsSync as
|
|
17588
|
-
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";
|
|
17589
17639
|
function tableExists2(db, table) {
|
|
17590
17640
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
17591
17641
|
}
|
|
@@ -17679,7 +17729,7 @@ function findMissingProjectRoots(db) {
|
|
|
17679
17729
|
continue;
|
|
17680
17730
|
if (!row.path.startsWith("/"))
|
|
17681
17731
|
continue;
|
|
17682
|
-
if (!
|
|
17732
|
+
if (!existsSync8(row.path))
|
|
17683
17733
|
missing++;
|
|
17684
17734
|
}
|
|
17685
17735
|
return missing;
|
|
@@ -17739,16 +17789,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
17739
17789
|
function createBackup(dbPath) {
|
|
17740
17790
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
17741
17791
|
return;
|
|
17742
|
-
if (!
|
|
17792
|
+
if (!existsSync8(dbPath))
|
|
17743
17793
|
return;
|
|
17744
17794
|
const stamp = now().replace(/[:.]/g, "-");
|
|
17745
|
-
const backupDir =
|
|
17795
|
+
const backupDir = join7(dirname5(dbPath), `${basename2(dbPath)}.backup-${stamp}`);
|
|
17746
17796
|
const files = [];
|
|
17747
17797
|
mkdirSync5(backupDir, { recursive: true });
|
|
17748
17798
|
for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
17749
|
-
if (!
|
|
17799
|
+
if (!existsSync8(source))
|
|
17750
17800
|
continue;
|
|
17751
|
-
const target =
|
|
17801
|
+
const target = join7(backupDir, basename2(source));
|
|
17752
17802
|
copyFileSync(source, target);
|
|
17753
17803
|
files.push(target);
|
|
17754
17804
|
}
|
|
@@ -18007,7 +18057,7 @@ var init_doctor = __esm(() => {
|
|
|
18007
18057
|
});
|
|
18008
18058
|
|
|
18009
18059
|
// src/server/routes.ts
|
|
18010
|
-
import { join as
|
|
18060
|
+
import { join as join8, resolve as resolve8, sep as sep2 } from "path";
|
|
18011
18061
|
function parseFieldsParam(url) {
|
|
18012
18062
|
const fieldsParam = url.searchParams.get("fields");
|
|
18013
18063
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -18802,7 +18852,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
18802
18852
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
18803
18853
|
return null;
|
|
18804
18854
|
if (path !== "/") {
|
|
18805
|
-
const filePath =
|
|
18855
|
+
const filePath = join8(ctx.dashboardDir, path);
|
|
18806
18856
|
const resolvedFile = resolve8(filePath);
|
|
18807
18857
|
const resolvedBase = resolve8(ctx.dashboardDir);
|
|
18808
18858
|
if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
|
|
@@ -18812,7 +18862,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
18812
18862
|
if (res2)
|
|
18813
18863
|
return res2;
|
|
18814
18864
|
}
|
|
18815
|
-
const indexPath =
|
|
18865
|
+
const indexPath = join8(ctx.dashboardDir, "index.html");
|
|
18816
18866
|
const res = serveStaticFile2(indexPath);
|
|
18817
18867
|
if (res)
|
|
18818
18868
|
return res;
|
|
@@ -22191,10 +22241,10 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
22191
22241
|
const released2 = await store.tasks.unlock(id);
|
|
22192
22242
|
return json3({ success: released2 });
|
|
22193
22243
|
}
|
|
22194
|
-
if (body2.agent_id &&
|
|
22244
|
+
if (body2.agent_id && body2.agent_id !== principal.agent && !principal.scopes.includes("todos:*")) {
|
|
22195
22245
|
return error(403, "unlock agent_id must match the authenticated agent");
|
|
22196
22246
|
}
|
|
22197
|
-
const agentId2 =
|
|
22247
|
+
const agentId2 = body2.agent_id || principal.agent;
|
|
22198
22248
|
if (!agentId2)
|
|
22199
22249
|
return error(403, "unlock requires an agent-bound key or force=true");
|
|
22200
22250
|
const released = await store.tasks.unlock(id, agentId2);
|
|
@@ -44246,48 +44296,98 @@ var init_token_utils = __esm(() => {
|
|
|
44246
44296
|
};
|
|
44247
44297
|
});
|
|
44248
44298
|
|
|
44249
|
-
// src/lib/
|
|
44250
|
-
import {
|
|
44251
|
-
import {
|
|
44252
|
-
|
|
44253
|
-
|
|
44254
|
-
|
|
44255
|
-
function readPersistedIdentity() {
|
|
44256
|
-
const path = identityFilePath();
|
|
44257
|
-
if (!existsSync8(path))
|
|
44258
|
-
return null;
|
|
44259
|
-
const parsed = readJsonFile(path);
|
|
44260
|
-
if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
|
|
44261
|
-
return null;
|
|
44262
|
-
return parsed;
|
|
44263
|
-
}
|
|
44264
|
-
function canonicalAgentRef(value) {
|
|
44265
|
-
return value.trim().toLowerCase();
|
|
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");
|
|
44266
44305
|
}
|
|
44267
|
-
function
|
|
44268
|
-
|
|
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
|
+
}
|
|
44269
44319
|
}
|
|
44270
|
-
function
|
|
44271
|
-
const
|
|
44272
|
-
|
|
44273
|
-
|
|
44274
|
-
|
|
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 };
|
|
44275
44365
|
}
|
|
44276
|
-
|
|
44277
|
-
|
|
44278
|
-
|
|
44279
|
-
|
|
44280
|
-
|
|
44281
|
-
|
|
44282
|
-
|
|
44283
|
-
|
|
44284
|
-
|
|
44285
|
-
|
|
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
|
+
};
|
|
44286
44385
|
}
|
|
44287
|
-
return {
|
|
44386
|
+
return { agents: cache.agents, seats: cache.seats, allowSeat, degraded: cache.degraded };
|
|
44288
44387
|
}
|
|
44289
|
-
var
|
|
44290
|
-
|
|
44388
|
+
var ROSTER_TTL_MS = 15000, cache;
|
|
44389
|
+
var init_assignee_context = __esm(() => {
|
|
44390
|
+
init_assignee_validation();
|
|
44291
44391
|
});
|
|
44292
44392
|
|
|
44293
44393
|
// src/pr-groups/http-client.ts
|
|
@@ -45605,6 +45705,14 @@ var init_cloud_router = __esm(() => {
|
|
|
45605
45705
|
});
|
|
45606
45706
|
|
|
45607
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
|
+
}
|
|
45608
45716
|
function registerTaskCrudTools(server, ctx) {
|
|
45609
45717
|
const { shouldRegisterTool, resolveId, formatError: formatError2, formatTask } = ctx;
|
|
45610
45718
|
function mutationTaskResponse(task) {
|
|
@@ -45641,6 +45749,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45641
45749
|
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
45642
45750
|
created_by: exports_external.string().optional().describe("Agent who FILED this task. Defaults to the ambient agent identity (todos init / TODOS_AGENT_ID)."),
|
|
45643
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."),
|
|
45644
45753
|
depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
|
|
45645
45754
|
short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
|
|
45646
45755
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
@@ -45651,10 +45760,11 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45651
45760
|
retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
|
|
45652
45761
|
}, async (params) => {
|
|
45653
45762
|
try {
|
|
45654
|
-
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;
|
|
45655
45765
|
const creator = resolveCreatorIdentity(created_by);
|
|
45656
45766
|
const router = resolveWritableIdentity(created_by);
|
|
45657
|
-
const assignee =
|
|
45767
|
+
const assignee = requestedAssignee || (unassigned ? undefined : router.agent_id || undefined);
|
|
45658
45768
|
const cloud = getTodosCloudClient();
|
|
45659
45769
|
if (cloud) {
|
|
45660
45770
|
const payload = { ...rest };
|
|
@@ -45723,6 +45833,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45723
45833
|
project_id: exports_external.string().optional().describe("Project ID"),
|
|
45724
45834
|
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
45725
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."),
|
|
45726
45837
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
45727
45838
|
working_dir: exports_external.string().optional().describe("Working directory associated with the task"),
|
|
45728
45839
|
metadata: exports_external.record(exports_external.unknown()).optional().describe("Metadata object to shallow-merge"),
|
|
@@ -45736,7 +45847,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45736
45847
|
acceptance: exports_external.unknown().optional()
|
|
45737
45848
|
}, async (params) => {
|
|
45738
45849
|
try {
|
|
45739
|
-
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;
|
|
45740
45851
|
const mergedMetadata = { ...metadata ?? {} };
|
|
45741
45852
|
if (expectation_id !== undefined)
|
|
45742
45853
|
mergedMetadata["expectation_id"] = expectation_id;
|
|
@@ -45755,8 +45866,9 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
45755
45866
|
if (acceptance !== undefined)
|
|
45756
45867
|
mergedMetadata["acceptance"] = acceptance;
|
|
45757
45868
|
const resolved = { ...rest, metadata: mergedMetadata };
|
|
45758
|
-
if (assigned_to)
|
|
45759
|
-
resolved.assigned_to = resolveAssignee(assigned_to);
|
|
45869
|
+
if (assigned_to) {
|
|
45870
|
+
resolved.assigned_to = resolveAssignee(await validateMcpAssignee(assigned_to, Boolean(params.allow_seat)));
|
|
45871
|
+
}
|
|
45760
45872
|
if (project_id)
|
|
45761
45873
|
resolved.project_id = resolveId(project_id, "projects");
|
|
45762
45874
|
if (task_list_id)
|
|
@@ -45887,12 +45999,16 @@ ${task.description}` : null
|
|
|
45887
45999
|
completed_at: exports_external.string().optional().describe("ISO timestamp for backdating completion"),
|
|
45888
46000
|
deadline: exports_external.string().nullable().optional(),
|
|
45889
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."),
|
|
45890
46003
|
version: exports_external.number().optional().describe("Expected version for optimistic locking")
|
|
45891
46004
|
}, async (params) => {
|
|
45892
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
|
+
}
|
|
45893
46009
|
const cloud = getTodosCloudClient();
|
|
45894
46010
|
if (cloud) {
|
|
45895
|
-
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;
|
|
45896
46012
|
const patch = { ...updates2 };
|
|
45897
46013
|
if (patch.assigned_to === "")
|
|
45898
46014
|
patch.assigned_to = null;
|
|
@@ -45917,7 +46033,7 @@ ${task.description}` : null
|
|
|
45917
46033
|
return { content: [{ type: "text", text: mutationTaskResponse(updated) }] };
|
|
45918
46034
|
}
|
|
45919
46035
|
const resolvedId = resolveId(params.task_id);
|
|
45920
|
-
const { task_id, version: version2, ...updates } = params;
|
|
46036
|
+
const { task_id, version: version2, allow_seat: _allowSeatLocal, ...updates } = params;
|
|
45921
46037
|
const resolved = { ...updates };
|
|
45922
46038
|
if (resolved.assigned_to === "")
|
|
45923
46039
|
resolved.assigned_to = null;
|
|
@@ -45970,11 +46086,14 @@ var init_task_crud2 = __esm(() => {
|
|
|
45970
46086
|
init_types();
|
|
45971
46087
|
init_token_utils();
|
|
45972
46088
|
init_creator_identity();
|
|
46089
|
+
init_assignee_validation();
|
|
46090
|
+
init_assignee_context();
|
|
46091
|
+
init_agents();
|
|
45973
46092
|
init_cloud_router();
|
|
45974
46093
|
});
|
|
45975
46094
|
|
|
45976
46095
|
// src/lib/project-bootstrap.ts
|
|
45977
|
-
import { existsSync as existsSync9, readFileSync as
|
|
46096
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
45978
46097
|
import { basename as basename3, dirname as dirname6, resolve as resolve9 } from "path";
|
|
45979
46098
|
function safeStat(path) {
|
|
45980
46099
|
try {
|
|
@@ -46008,7 +46127,7 @@ function readPackageJson(path) {
|
|
|
46008
46127
|
if (!existsSync9(file))
|
|
46009
46128
|
return null;
|
|
46010
46129
|
try {
|
|
46011
|
-
const parsed = JSON.parse(
|
|
46130
|
+
const parsed = JSON.parse(readFileSync4(file, "utf-8"));
|
|
46012
46131
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
46013
46132
|
} catch {
|
|
46014
46133
|
return null;
|
|
@@ -46570,8 +46689,8 @@ var init_retention_cleanup = __esm(() => {
|
|
|
46570
46689
|
});
|
|
46571
46690
|
|
|
46572
46691
|
// src/lib/mention-resolver.ts
|
|
46573
|
-
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as
|
|
46574
|
-
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";
|
|
46575
46694
|
function blankResolution(parsed) {
|
|
46576
46695
|
return {
|
|
46577
46696
|
input: parsed.input,
|
|
@@ -46679,7 +46798,7 @@ function resolveFile(parsed, workspace) {
|
|
|
46679
46798
|
return resolution;
|
|
46680
46799
|
}
|
|
46681
46800
|
if (parsed.line !== undefined) {
|
|
46682
|
-
const lineCount =
|
|
46801
|
+
const lineCount = readFileSync5(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
46683
46802
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
46684
46803
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
46685
46804
|
return resolution;
|
|
@@ -46702,7 +46821,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
46702
46821
|
if (SKIP_DIRS.has(entry2.name))
|
|
46703
46822
|
continue;
|
|
46704
46823
|
}
|
|
46705
|
-
const absolutePath =
|
|
46824
|
+
const absolutePath = join10(current, entry2.name);
|
|
46706
46825
|
if (entry2.isDirectory()) {
|
|
46707
46826
|
if (!SKIP_DIRS.has(entry2.name))
|
|
46708
46827
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -46732,7 +46851,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
46732
46851
|
const pattern = symbolPattern(name);
|
|
46733
46852
|
const matches = [];
|
|
46734
46853
|
for (const file of walkSourceFiles(workspace)) {
|
|
46735
|
-
const lines =
|
|
46854
|
+
const lines = readFileSync5(file, "utf-8").split(/\r?\n/);
|
|
46736
46855
|
for (let index = 0;index < lines.length; index += 1) {
|
|
46737
46856
|
const line = lines[index];
|
|
46738
46857
|
const found = pattern.exec(line);
|
|
@@ -50393,8 +50512,8 @@ var init_audit_ledger = __esm(() => {
|
|
|
50393
50512
|
});
|
|
50394
50513
|
|
|
50395
50514
|
// src/lib/release-compatibility.ts
|
|
50396
|
-
import { readFileSync as
|
|
50397
|
-
import { join as
|
|
50515
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
50516
|
+
import { join as join11, resolve as resolve12 } from "path";
|
|
50398
50517
|
import { Database as Database2 } from "bun:sqlite";
|
|
50399
50518
|
function pass(id, message, details) {
|
|
50400
50519
|
return { id, status: "passed", message, details };
|
|
@@ -50406,7 +50525,7 @@ function warn(id, message, details) {
|
|
|
50406
50525
|
return { id, status: "warning", message, details };
|
|
50407
50526
|
}
|
|
50408
50527
|
function readPackageJson2(root) {
|
|
50409
|
-
return JSON.parse(
|
|
50528
|
+
return JSON.parse(readFileSync6(join11(root, "package.json"), "utf8"));
|
|
50410
50529
|
}
|
|
50411
50530
|
function sortedKeys(value) {
|
|
50412
50531
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -80301,7 +80420,7 @@ var init_agent_run_dispatcher = __esm(() => {
|
|
|
80301
80420
|
});
|
|
80302
80421
|
|
|
80303
80422
|
// src/lib/verification-providers.ts
|
|
80304
|
-
import { existsSync as existsSync12, readFileSync as
|
|
80423
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
80305
80424
|
function normalizeName5(name) {
|
|
80306
80425
|
const normalized = name.trim().toLowerCase();
|
|
80307
80426
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -80453,7 +80572,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
80453
80572
|
};
|
|
80454
80573
|
}
|
|
80455
80574
|
function runCiLogProvider(input) {
|
|
80456
|
-
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") : "");
|
|
80457
80576
|
return {
|
|
80458
80577
|
status: classifyLog(text),
|
|
80459
80578
|
attempts: 1,
|
|
@@ -83185,7 +83304,7 @@ var init_local_bridge = __esm(() => {
|
|
|
83185
83304
|
|
|
83186
83305
|
// src/lib/local-backups.ts
|
|
83187
83306
|
import { createHash as createHash7 } from "crypto";
|
|
83188
|
-
import { readFileSync as
|
|
83307
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
83189
83308
|
import { dirname as dirname7, resolve as resolve13 } from "path";
|
|
83190
83309
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
83191
83310
|
function stableJson2(value) {
|
|
@@ -83295,7 +83414,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
83295
83414
|
return path;
|
|
83296
83415
|
}
|
|
83297
83416
|
function readLocalBackupFile(path) {
|
|
83298
|
-
return JSON.parse(
|
|
83417
|
+
return JSON.parse(readFileSync8(resolve13(path), "utf-8"));
|
|
83299
83418
|
}
|
|
83300
83419
|
function verifyLocalBackup(value, options = {}, db) {
|
|
83301
83420
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -84746,8 +84865,8 @@ __export(exports_local_extensions, {
|
|
|
84746
84865
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
84747
84866
|
});
|
|
84748
84867
|
import { createHash as createHash10, createVerify } from "crypto";
|
|
84749
|
-
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as
|
|
84750
|
-
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";
|
|
84751
84870
|
function isObject3(value) {
|
|
84752
84871
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
84753
84872
|
}
|
|
@@ -84828,7 +84947,7 @@ function normalizeManifest(input) {
|
|
|
84828
84947
|
};
|
|
84829
84948
|
}
|
|
84830
84949
|
function parseJson(path) {
|
|
84831
|
-
return JSON.parse(
|
|
84950
|
+
return JSON.parse(readFileSync9(path, "utf8"));
|
|
84832
84951
|
}
|
|
84833
84952
|
function sha2566(bytes) {
|
|
84834
84953
|
return `sha256:${createHash10("sha256").update(bytes).digest("hex")}`;
|
|
@@ -85009,10 +85128,10 @@ function inspectExtensionSource(source3) {
|
|
|
85009
85128
|
if (!existsSync13(resolved))
|
|
85010
85129
|
throw new Error(`extension source not found: ${source3}`);
|
|
85011
85130
|
const stat = statSync6(resolved);
|
|
85012
|
-
const manifestPath = stat.isDirectory() ? [
|
|
85131
|
+
const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync13) : resolved;
|
|
85013
85132
|
if (!manifestPath)
|
|
85014
85133
|
throw new Error(`extension directory ${source3} is missing todos.extension.json`);
|
|
85015
|
-
const raw =
|
|
85134
|
+
const raw = readFileSync9(manifestPath);
|
|
85016
85135
|
const parsed = parseJson(manifestPath);
|
|
85017
85136
|
const bundle = isObject3(parsed) && isObject3(parsed["manifest"]);
|
|
85018
85137
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -85105,15 +85224,15 @@ function projectExtensionSources(projectPath) {
|
|
|
85105
85224
|
return [];
|
|
85106
85225
|
const root = resolve14(projectPath);
|
|
85107
85226
|
const candidates = [
|
|
85108
|
-
|
|
85109
|
-
|
|
85227
|
+
join12(root, "todos.extension.json"),
|
|
85228
|
+
join12(root, ".todos", "todos.extension.json")
|
|
85110
85229
|
];
|
|
85111
|
-
const extensionDir =
|
|
85230
|
+
const extensionDir = join12(root, ".todos", "extensions");
|
|
85112
85231
|
if (existsSync13(extensionDir)) {
|
|
85113
85232
|
for (const entry2 of readdirSync3(extensionDir)) {
|
|
85114
85233
|
if (entry2.startsWith("."))
|
|
85115
85234
|
continue;
|
|
85116
|
-
const full =
|
|
85235
|
+
const full = join12(extensionDir, entry2);
|
|
85117
85236
|
if (statSync6(full).isDirectory() || entry2.endsWith(".json"))
|
|
85118
85237
|
candidates.push(full);
|
|
85119
85238
|
}
|
|
@@ -89525,9 +89644,9 @@ __export(exports_extract, {
|
|
|
89525
89644
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
89526
89645
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
89527
89646
|
});
|
|
89528
|
-
import { existsSync as existsSync14, readFileSync as
|
|
89647
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
89529
89648
|
import { createHash as createHash12 } from "crypto";
|
|
89530
|
-
import { relative as relative5, resolve as resolve15, join as
|
|
89649
|
+
import { relative as relative5, resolve as resolve15, join as join13 } from "path";
|
|
89531
89650
|
function stableHash(value) {
|
|
89532
89651
|
return createHash12("sha256").update(value).digest("hex");
|
|
89533
89652
|
}
|
|
@@ -89536,11 +89655,11 @@ function normalizePathForMatch(value) {
|
|
|
89536
89655
|
}
|
|
89537
89656
|
function readGitignorePatterns(basePath) {
|
|
89538
89657
|
const root = statSync7(basePath).isFile() ? resolve15(basePath, "..") : basePath;
|
|
89539
|
-
const gitignorePath =
|
|
89658
|
+
const gitignorePath = join13(root, ".gitignore");
|
|
89540
89659
|
if (!existsSync14(gitignorePath))
|
|
89541
89660
|
return [];
|
|
89542
89661
|
try {
|
|
89543
|
-
return
|
|
89662
|
+
return readFileSync10(gitignorePath, "utf-8").split(`
|
|
89544
89663
|
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
|
|
89545
89664
|
} catch {
|
|
89546
89665
|
return [];
|
|
@@ -89679,9 +89798,9 @@ function buildCodebaseIndex(options) {
|
|
|
89679
89798
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
89680
89799
|
const indexed = [];
|
|
89681
89800
|
for (const file of files) {
|
|
89682
|
-
const fullPath = statSync7(basePath).isFile() ? basePath :
|
|
89801
|
+
const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
|
|
89683
89802
|
try {
|
|
89684
|
-
const source3 =
|
|
89803
|
+
const source3 = readFileSync10(fullPath, "utf-8");
|
|
89685
89804
|
const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
|
|
89686
89805
|
indexed.push({
|
|
89687
89806
|
file: relPath,
|
|
@@ -89710,9 +89829,9 @@ function extractTodos(options, db) {
|
|
|
89710
89829
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
89711
89830
|
const allComments = [];
|
|
89712
89831
|
for (const file of files) {
|
|
89713
|
-
const fullPath = statSync7(basePath).isFile() ? basePath :
|
|
89832
|
+
const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
|
|
89714
89833
|
try {
|
|
89715
|
-
const source3 =
|
|
89834
|
+
const source3 = readFileSync10(fullPath, "utf-8");
|
|
89716
89835
|
const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
|
|
89717
89836
|
const comments = extractFromSource(source3, relPath, tags);
|
|
89718
89837
|
allComments.push(...comments);
|
|
@@ -90661,7 +90780,7 @@ __export(exports_builtin_templates, {
|
|
|
90661
90780
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
90662
90781
|
});
|
|
90663
90782
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
90664
|
-
import { join as
|
|
90783
|
+
import { join as join14 } from "path";
|
|
90665
90784
|
function templateMetadata(template) {
|
|
90666
90785
|
return {
|
|
90667
90786
|
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
@@ -90720,7 +90839,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
90720
90839
|
mkdirSync7(directory, { recursive: true });
|
|
90721
90840
|
const files = [];
|
|
90722
90841
|
for (const entry2 of exportBuiltinTemplateFiles()) {
|
|
90723
|
-
const path =
|
|
90842
|
+
const path = join14(directory, entry2.filename);
|
|
90724
90843
|
writeFileSync4(path, `${JSON.stringify(entry2.template, null, 2)}
|
|
90725
90844
|
`, "utf-8");
|
|
90726
90845
|
files.push(path);
|
|
@@ -91246,28 +91365,28 @@ __export(exports_environment_snapshots, {
|
|
|
91246
91365
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
91247
91366
|
});
|
|
91248
91367
|
import { createHash as createHash13 } from "crypto";
|
|
91249
|
-
import { existsSync as existsSync15, readFileSync as
|
|
91368
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
|
|
91250
91369
|
import { hostname as hostname3, platform, arch } from "os";
|
|
91251
|
-
import { dirname as dirname8, join as
|
|
91370
|
+
import { dirname as dirname8, join as join15, resolve as resolve16 } from "path";
|
|
91252
91371
|
import { tmpdir as tmpdir3 } from "os";
|
|
91253
91372
|
function sha2567(value) {
|
|
91254
91373
|
return createHash13("sha256").update(value).digest("hex");
|
|
91255
91374
|
}
|
|
91256
91375
|
function fileRecord(root, relativePath) {
|
|
91257
|
-
const path =
|
|
91376
|
+
const path = join15(root, relativePath);
|
|
91258
91377
|
if (!existsSync15(path))
|
|
91259
91378
|
return null;
|
|
91260
91379
|
const stat = statSync8(path);
|
|
91261
91380
|
if (!stat.isFile())
|
|
91262
91381
|
return null;
|
|
91263
|
-
const content =
|
|
91382
|
+
const content = readFileSync11(path);
|
|
91264
91383
|
return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
|
|
91265
91384
|
}
|
|
91266
91385
|
function manifestRecord(root, relativePath) {
|
|
91267
91386
|
const base = fileRecord(root, relativePath);
|
|
91268
91387
|
if (!base)
|
|
91269
91388
|
return null;
|
|
91270
|
-
const parsed = readJsonFile(
|
|
91389
|
+
const parsed = readJsonFile(join15(root, relativePath));
|
|
91271
91390
|
if (!parsed)
|
|
91272
91391
|
return { ...base, redacted: {} };
|
|
91273
91392
|
const redacted = redactValue({
|
|
@@ -91362,8 +91481,8 @@ function commandEnv(env, includeValues) {
|
|
|
91362
91481
|
function defaultSnapshotDir() {
|
|
91363
91482
|
const dbPath = getDatabasePath();
|
|
91364
91483
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
91365
|
-
return
|
|
91366
|
-
return
|
|
91484
|
+
return join15(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
91485
|
+
return join15(dirname8(resolve16(dbPath)), "environment-snapshots");
|
|
91367
91486
|
}
|
|
91368
91487
|
function snapshotWithId(snapshot) {
|
|
91369
91488
|
const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
|
|
@@ -91410,7 +91529,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
91410
91529
|
});
|
|
91411
91530
|
}
|
|
91412
91531
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
91413
|
-
const path = outputPath ? resolve16(outputPath) :
|
|
91532
|
+
const path = outputPath ? resolve16(outputPath) : join15(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
91414
91533
|
ensureDir(dirname8(path));
|
|
91415
91534
|
writeJsonFile(path, snapshot);
|
|
91416
91535
|
return path;
|
|
@@ -92334,26 +92453,26 @@ __export(exports_serve, {
|
|
|
92334
92453
|
MIME_TYPES: () => MIME_TYPES
|
|
92335
92454
|
});
|
|
92336
92455
|
import { existsSync as existsSync16 } from "fs";
|
|
92337
|
-
import { join as
|
|
92456
|
+
import { join as join16, dirname as dirname9, extname } from "path";
|
|
92338
92457
|
import { fileURLToPath } from "url";
|
|
92339
92458
|
function resolveDashboardDir() {
|
|
92340
92459
|
const candidates = [];
|
|
92341
92460
|
try {
|
|
92342
92461
|
const scriptDir = dirname9(fileURLToPath(import.meta.url));
|
|
92343
|
-
candidates.push(
|
|
92344
|
-
candidates.push(
|
|
92462
|
+
candidates.push(join16(scriptDir, "..", "dashboard", "dist"));
|
|
92463
|
+
candidates.push(join16(scriptDir, "..", "..", "dashboard", "dist"));
|
|
92345
92464
|
} catch {}
|
|
92346
92465
|
if (process.argv[1]) {
|
|
92347
92466
|
const mainDir = dirname9(process.argv[1]);
|
|
92348
|
-
candidates.push(
|
|
92349
|
-
candidates.push(
|
|
92467
|
+
candidates.push(join16(mainDir, "..", "dashboard", "dist"));
|
|
92468
|
+
candidates.push(join16(mainDir, "..", "..", "dashboard", "dist"));
|
|
92350
92469
|
}
|
|
92351
|
-
candidates.push(
|
|
92470
|
+
candidates.push(join16(process.cwd(), "dashboard", "dist"));
|
|
92352
92471
|
for (const candidate of candidates) {
|
|
92353
92472
|
if (existsSync16(candidate))
|
|
92354
92473
|
return candidate;
|
|
92355
92474
|
}
|
|
92356
|
-
return
|
|
92475
|
+
return join16(process.cwd(), "dashboard", "dist");
|
|
92357
92476
|
}
|
|
92358
92477
|
function getProvidedApiKey(req) {
|
|
92359
92478
|
const headerKey = req.headers.get("x-api-key");
|