@hasna/todos 0.15.19 → 0.15.24
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/ai-tools.d.ts +80 -0
- package/dist/ai-tools.d.ts.map +1 -0
- package/dist/ai.d.ts +313 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/cli/cloud-router.d.ts +30 -2
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/ai-commands.d.ts +3 -0
- package/dist/cli/commands/ai-commands.d.ts.map +1 -0
- package/dist/cli/commands/help-commands.d.ts +2 -1
- package/dist/cli/commands/help-commands.d.ts.map +1 -1
- 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 +3685 -572
- package/dist/cli/stage-a.d.ts +34 -12
- package/dist/cli/stage-a.d.ts.map +1 -1
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +861 -29
- package/dist/db/audit.d.ts +8 -0
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts +7 -1
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +1 -1
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3717 -1205
- package/dist/lib/cli-help.d.ts +3 -2
- package/dist/lib/cli-help.d.ts.map +1 -1
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/saved-search-views.d.ts.map +1 -1
- package/dist/lib/stale-lock-handoff.d.ts +25 -0
- package/dist/lib/stale-lock-handoff.d.ts.map +1 -0
- package/dist/mcp/index.js +822 -101
- package/dist/mcp.js +3 -1
- package/dist/project-registration.js +4208 -3709
- package/dist/registry.js +826 -29
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.js +8 -1
- package/dist/sdk/v1.generated.d.ts +29 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +1155 -434
- package/dist/server/openapi.d.ts +189 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/audit-history-import.d.ts +14 -0
- package/dist/storage/audit-history-import.d.ts.map +1 -0
- package/dist/storage/interfaces.d.ts +12 -1
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/local-sqlite.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +531 -34
- package/dist/task-manifest.js +30 -4
- package/dist/types/index.d.ts +43 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -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.15.
|
|
73
|
+
version: "0.15.24",
|
|
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",
|
|
@@ -141,6 +141,8 @@ var init_package = __esm(() => {
|
|
|
141
141
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
142
142
|
"dev:serve": "bun run src/server/index.ts",
|
|
143
143
|
"verify:release": "bun run scripts/verify-public-release.ts --mode=review",
|
|
144
|
+
"verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
|
|
145
|
+
"issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
|
|
144
146
|
prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
|
|
145
147
|
postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
|
|
146
148
|
},
|
|
@@ -861,7 +863,7 @@ function isBlockingDependencyStatus(status) {
|
|
|
861
863
|
function isTerminalStatus(status) {
|
|
862
864
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
863
865
|
}
|
|
864
|
-
var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanRevisionConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
|
|
866
|
+
var TASK_STATUSES, TASK_PRIORITIES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanRevisionConflictError, PlanNotFoundError, LockError, StaleLockHandoffError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DispatchNotFoundError;
|
|
865
867
|
var init_types = __esm(() => {
|
|
866
868
|
TASK_STATUSES = [
|
|
867
869
|
"pending",
|
|
@@ -983,6 +985,16 @@ var init_types = __esm(() => {
|
|
|
983
985
|
this.name = "LockError";
|
|
984
986
|
}
|
|
985
987
|
};
|
|
988
|
+
StaleLockHandoffError = class StaleLockHandoffError extends Error {
|
|
989
|
+
code;
|
|
990
|
+
details;
|
|
991
|
+
constructor(code, message, details = {}) {
|
|
992
|
+
super(message);
|
|
993
|
+
this.code = code;
|
|
994
|
+
this.details = details;
|
|
995
|
+
this.name = "StaleLockHandoffError";
|
|
996
|
+
}
|
|
997
|
+
};
|
|
986
998
|
AgentNotFoundError = class AgentNotFoundError extends Error {
|
|
987
999
|
agentId;
|
|
988
1000
|
static code = "AGENT_NOT_FOUND";
|
|
@@ -1196,6 +1208,498 @@ var init_plan_project_link_contract = __esm(() => {
|
|
|
1196
1208
|
};
|
|
1197
1209
|
});
|
|
1198
1210
|
|
|
1211
|
+
// src/lib/config.ts
|
|
1212
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1213
|
+
import { dirname, join as join3 } from "path";
|
|
1214
|
+
function getConfigPath() {
|
|
1215
|
+
return join3(getTodosGlobalDir(), "config.json");
|
|
1216
|
+
}
|
|
1217
|
+
function loadConfig() {
|
|
1218
|
+
if (cached)
|
|
1219
|
+
return cached;
|
|
1220
|
+
if (!existsSync3(getConfigPath())) {
|
|
1221
|
+
cached = {};
|
|
1222
|
+
return cached;
|
|
1223
|
+
}
|
|
1224
|
+
const config = readJsonFile(getConfigPath()) || {};
|
|
1225
|
+
if (typeof config.sync_agents === "string") {
|
|
1226
|
+
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
1227
|
+
}
|
|
1228
|
+
cached = config;
|
|
1229
|
+
return cached;
|
|
1230
|
+
}
|
|
1231
|
+
function saveConfig(config) {
|
|
1232
|
+
const configPath = getConfigPath();
|
|
1233
|
+
ensureDir(dirname(configPath));
|
|
1234
|
+
writeJsonFile(configPath, config);
|
|
1235
|
+
cached = config;
|
|
1236
|
+
return config;
|
|
1237
|
+
}
|
|
1238
|
+
function getAgentPoolForProject(workingDir) {
|
|
1239
|
+
const config = loadConfig();
|
|
1240
|
+
if (workingDir && config.project_pools) {
|
|
1241
|
+
let bestKey = null;
|
|
1242
|
+
let bestLen = 0;
|
|
1243
|
+
for (const key of Object.keys(config.project_pools)) {
|
|
1244
|
+
if (workingDir.startsWith(key) && key.length > bestLen) {
|
|
1245
|
+
bestKey = key;
|
|
1246
|
+
bestLen = key.length;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (bestKey && config.project_pools[bestKey]) {
|
|
1250
|
+
return config.project_pools[bestKey];
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return config.agent_pool || null;
|
|
1254
|
+
}
|
|
1255
|
+
function getCompletionGuardConfig(projectPath) {
|
|
1256
|
+
const config = loadConfig();
|
|
1257
|
+
const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
1258
|
+
if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
|
|
1259
|
+
return { ...global, ...config.project_overrides[projectPath].completion_guard };
|
|
1260
|
+
}
|
|
1261
|
+
return global;
|
|
1262
|
+
}
|
|
1263
|
+
var cached = null, GUARD_DEFAULTS;
|
|
1264
|
+
var init_config2 = __esm(() => {
|
|
1265
|
+
init_sync_utils();
|
|
1266
|
+
GUARD_DEFAULTS = {
|
|
1267
|
+
enabled: false,
|
|
1268
|
+
min_work_seconds: 30,
|
|
1269
|
+
max_completions_per_window: 5,
|
|
1270
|
+
window_minutes: 10,
|
|
1271
|
+
cooldown_seconds: 60
|
|
1272
|
+
};
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// src/lib/redaction.ts
|
|
1276
|
+
function unique(values) {
|
|
1277
|
+
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
1278
|
+
}
|
|
1279
|
+
function cloneRegex(regex) {
|
|
1280
|
+
return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
1281
|
+
}
|
|
1282
|
+
function customPatterns() {
|
|
1283
|
+
return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
|
|
1284
|
+
try {
|
|
1285
|
+
return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
|
|
1286
|
+
} catch {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
function secretPatterns() {
|
|
1292
|
+
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
1293
|
+
}
|
|
1294
|
+
function isRedactionPlaceholderMatch(match) {
|
|
1295
|
+
const trimmed = match.trim();
|
|
1296
|
+
return new RegExp(`^${REDACTION_PLACEHOLDER}$`).test(trimmed) || new RegExp(`=\\s*['"]?${REDACTION_PLACEHOLDER}['"]?$`).test(trimmed);
|
|
1297
|
+
}
|
|
1298
|
+
function isRedactionPlaceholderKey(key) {
|
|
1299
|
+
return new RegExp(`^${REDACTION_PLACEHOLDER}$`).test(key.trim());
|
|
1300
|
+
}
|
|
1301
|
+
function isSecretKey(key) {
|
|
1302
|
+
if (isRedactionPlaceholderKey(key))
|
|
1303
|
+
return false;
|
|
1304
|
+
if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
|
|
1305
|
+
return false;
|
|
1306
|
+
if (DEFAULT_SECRET_KEY_PATTERN.test(key))
|
|
1307
|
+
return true;
|
|
1308
|
+
return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
|
|
1309
|
+
}
|
|
1310
|
+
function redactEvidenceText(value) {
|
|
1311
|
+
let redacted = value;
|
|
1312
|
+
for (const pattern of secretPatterns()) {
|
|
1313
|
+
const regex = cloneRegex(pattern.regex);
|
|
1314
|
+
const replacement = pattern.replacement ?? "[REDACTED]";
|
|
1315
|
+
redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
|
|
1316
|
+
}
|
|
1317
|
+
return redacted;
|
|
1318
|
+
}
|
|
1319
|
+
function redactValue(value) {
|
|
1320
|
+
if (typeof value === "string")
|
|
1321
|
+
return redactEvidenceText(value);
|
|
1322
|
+
if (Array.isArray(value))
|
|
1323
|
+
return value.map(redactValue);
|
|
1324
|
+
if (value && typeof value === "object") {
|
|
1325
|
+
const redacted = {};
|
|
1326
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1327
|
+
if (isSecretKey(key)) {
|
|
1328
|
+
redacted[key] = "[REDACTED]";
|
|
1329
|
+
} else {
|
|
1330
|
+
redacted[key] = redactValue(child);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return redacted;
|
|
1334
|
+
}
|
|
1335
|
+
return value;
|
|
1336
|
+
}
|
|
1337
|
+
function listSecretFindings(value) {
|
|
1338
|
+
const findings = [];
|
|
1339
|
+
for (const pattern of secretPatterns()) {
|
|
1340
|
+
const matches = value.match(cloneRegex(pattern.regex))?.filter((match) => !isRedactionPlaceholderMatch(match));
|
|
1341
|
+
if (matches?.length)
|
|
1342
|
+
findings.push({ pattern: pattern.name, count: matches.length });
|
|
1343
|
+
}
|
|
1344
|
+
return findings;
|
|
1345
|
+
}
|
|
1346
|
+
function getSecretSafetyConfig() {
|
|
1347
|
+
return {
|
|
1348
|
+
redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
|
|
1349
|
+
redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function upsertSecretSafetyConfig(input) {
|
|
1353
|
+
const config = loadConfig();
|
|
1354
|
+
const next = {
|
|
1355
|
+
redaction_patterns: unique([...config.secret_safety?.redaction_patterns || [], ...input.redaction_patterns || []]),
|
|
1356
|
+
redaction_keys: unique([...config.secret_safety?.redaction_keys || [], ...input.redaction_keys || []])
|
|
1357
|
+
};
|
|
1358
|
+
saveConfig({ ...config, secret_safety: next });
|
|
1359
|
+
return next;
|
|
1360
|
+
}
|
|
1361
|
+
var DEFAULT_SECRET_PATTERNS, DEFAULT_SECRET_KEY_PATTERN, NON_SECRET_USAGE_KEYS, REDACTION_PLACEHOLDER;
|
|
1362
|
+
var init_redaction = __esm(() => {
|
|
1363
|
+
init_config2();
|
|
1364
|
+
DEFAULT_SECRET_PATTERNS = [
|
|
1365
|
+
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
1366
|
+
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
1367
|
+
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
1368
|
+
{ name: "npm-token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_NPM_TOKEN]" },
|
|
1369
|
+
{ name: "github-fine-grained-token", regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
1370
|
+
{ name: "github-token", regex: /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
1371
|
+
{ name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
|
|
1372
|
+
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
1373
|
+
];
|
|
1374
|
+
DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
|
|
1375
|
+
NON_SECRET_USAGE_KEYS = new Set([
|
|
1376
|
+
"tokens",
|
|
1377
|
+
"total_tokens",
|
|
1378
|
+
"token_count",
|
|
1379
|
+
"input_tokens",
|
|
1380
|
+
"output_tokens",
|
|
1381
|
+
"prompt_tokens",
|
|
1382
|
+
"completion_tokens",
|
|
1383
|
+
"cost_tokens"
|
|
1384
|
+
]);
|
|
1385
|
+
REDACTION_PLACEHOLDER = String.raw`\[REDACTED(?:_[A-Z_]+)?\]`;
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
// src/lib/secret-redaction.ts
|
|
1389
|
+
function isAllowlisted(text, match, allowlist) {
|
|
1390
|
+
const context = text.slice(Math.max(0, text.indexOf(match) - 20), text.indexOf(match) + match.length + 20);
|
|
1391
|
+
return allowlist.some((re) => re.test(context) || re.test(match));
|
|
1392
|
+
}
|
|
1393
|
+
function scanTextForSecrets(text, options = {}) {
|
|
1394
|
+
const allowlist = [...DEFAULT_ALLOWLIST, ...options.allowlist ?? []];
|
|
1395
|
+
const matches = [];
|
|
1396
|
+
const patterns = [
|
|
1397
|
+
...DEFAULT_PATTERNS,
|
|
1398
|
+
...options.custom_patterns?.map((p, i) => ({ name: `custom_${i}`, pattern: p })) ?? []
|
|
1399
|
+
];
|
|
1400
|
+
for (const { name, pattern, allowlist_ok } of patterns) {
|
|
1401
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
1402
|
+
let m;
|
|
1403
|
+
while ((m = re.exec(text)) !== null) {
|
|
1404
|
+
const match = m[0];
|
|
1405
|
+
if (allowlist_ok && isAllowlisted(text, match, allowlist))
|
|
1406
|
+
continue;
|
|
1407
|
+
if (isAllowlisted(text, match, allowlist))
|
|
1408
|
+
continue;
|
|
1409
|
+
const line = text.slice(0, m.index).split(`
|
|
1410
|
+
`).length;
|
|
1411
|
+
matches.push({ pattern: name, match: match.slice(0, 12) + (match.length > 12 ? "\u2026" : ""), index: m.index, line });
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
schema_version: SECRET_REDACTION_SCHEMA,
|
|
1416
|
+
clean: matches.length === 0,
|
|
1417
|
+
matches
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function redactText(text, options = {}) {
|
|
1421
|
+
const placeholder = options.placeholder ?? REDACTION_PLACEHOLDER2;
|
|
1422
|
+
let out = text;
|
|
1423
|
+
for (const { pattern, allowlist_ok } of DEFAULT_PATTERNS) {
|
|
1424
|
+
out = out.replace(new RegExp(pattern.source, pattern.flags), (match) => {
|
|
1425
|
+
if (allowlist_ok && isAllowlisted(out, match, [...DEFAULT_ALLOWLIST, ...options.allowlist ?? []])) {
|
|
1426
|
+
return match;
|
|
1427
|
+
}
|
|
1428
|
+
return placeholder;
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
for (const custom of options.custom_patterns ?? []) {
|
|
1432
|
+
out = out.replace(custom, placeholder);
|
|
1433
|
+
}
|
|
1434
|
+
for (const fn of customRedactors) {
|
|
1435
|
+
out = fn(out);
|
|
1436
|
+
}
|
|
1437
|
+
return out;
|
|
1438
|
+
}
|
|
1439
|
+
function redactExportRecord(record) {
|
|
1440
|
+
const base = redactValue(record);
|
|
1441
|
+
for (const [key, value] of Object.entries(base)) {
|
|
1442
|
+
if (typeof value === "string") {
|
|
1443
|
+
base[key] = redactText(value);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return base;
|
|
1447
|
+
}
|
|
1448
|
+
var SECRET_REDACTION_SCHEMA, REDACTION_PLACEHOLDER2 = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
|
|
1449
|
+
var init_secret_redaction = __esm(() => {
|
|
1450
|
+
init_redaction();
|
|
1451
|
+
SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
|
|
1452
|
+
DEFAULT_PATTERNS = [
|
|
1453
|
+
{ name: "openai_sk", pattern: /\bsk-[a-zA-Z0-9]{10,}\b/g },
|
|
1454
|
+
{ name: "github_pat", pattern: /\bghp_[a-zA-Z0-9]{20,}\b/g },
|
|
1455
|
+
{ name: "github_oauth", pattern: /\bgho_[a-zA-Z0-9]{20,}\b/g },
|
|
1456
|
+
{ name: "aws_access_key", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
1457
|
+
{ name: "bearer_token", pattern: /\bBearer\s+[a-zA-Z0-9\-._~+/]+=*\b/gi },
|
|
1458
|
+
{ name: "jwt", pattern: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g },
|
|
1459
|
+
{ name: "private_key_block", pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g },
|
|
1460
|
+
{ name: "generic_api_key", pattern: /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[a-zA-Z0-9\-._]{8,}['"]?/gi, allowlist_ok: true }
|
|
1461
|
+
];
|
|
1462
|
+
DEFAULT_ALLOWLIST = [
|
|
1463
|
+
/\[REDACTED\]/,
|
|
1464
|
+
/example\.com/i,
|
|
1465
|
+
/your-api-key-here/i,
|
|
1466
|
+
/sk-test/i,
|
|
1467
|
+
/ghp_xxx/i
|
|
1468
|
+
];
|
|
1469
|
+
customRedactors = [];
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
// src/lib/prewrite-secrets.ts
|
|
1473
|
+
function mergeFindings(findings) {
|
|
1474
|
+
const byPattern = new Map;
|
|
1475
|
+
for (const finding of findings) {
|
|
1476
|
+
const existing = byPattern.get(finding.pattern);
|
|
1477
|
+
if (!existing) {
|
|
1478
|
+
byPattern.set(finding.pattern, {
|
|
1479
|
+
pattern: finding.pattern,
|
|
1480
|
+
count: finding.count,
|
|
1481
|
+
lines: Array.from(new Set(finding.lines)).sort((a, b) => a - b)
|
|
1482
|
+
});
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
existing.count += finding.count;
|
|
1486
|
+
existing.lines = Array.from(new Set([...existing.lines, ...finding.lines])).sort((a, b) => a - b);
|
|
1487
|
+
}
|
|
1488
|
+
return [...byPattern.values()].sort((left, right) => left.pattern.localeCompare(right.pattern));
|
|
1489
|
+
}
|
|
1490
|
+
function redactPreWriteText(value) {
|
|
1491
|
+
return redactText(redactEvidenceText(value));
|
|
1492
|
+
}
|
|
1493
|
+
function scanPreWriteText(value, context = "text") {
|
|
1494
|
+
const redacted = redactPreWriteText(value);
|
|
1495
|
+
if (redacted === value) {
|
|
1496
|
+
return {
|
|
1497
|
+
schema_version: PREWRITE_SECRET_SCAN_SCHEMA,
|
|
1498
|
+
clean: true,
|
|
1499
|
+
context,
|
|
1500
|
+
findings: []
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
const structural = scanTextForSecrets(value).matches.map((match) => ({
|
|
1504
|
+
pattern: match.pattern,
|
|
1505
|
+
count: 1,
|
|
1506
|
+
lines: match.line ? [match.line] : []
|
|
1507
|
+
}));
|
|
1508
|
+
const configured = listSecretFindings(value).map((finding) => ({
|
|
1509
|
+
pattern: finding.pattern,
|
|
1510
|
+
count: finding.count,
|
|
1511
|
+
lines: []
|
|
1512
|
+
}));
|
|
1513
|
+
return {
|
|
1514
|
+
schema_version: PREWRITE_SECRET_SCAN_SCHEMA,
|
|
1515
|
+
clean: false,
|
|
1516
|
+
context,
|
|
1517
|
+
findings: mergeFindings([...structural, ...configured])
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function assertPreWriteTextClean(value, context = "text") {
|
|
1521
|
+
const scan = scanPreWriteText(value, context);
|
|
1522
|
+
if (!scan.clean)
|
|
1523
|
+
throw new PreWriteSecretError(context, scan.findings);
|
|
1524
|
+
}
|
|
1525
|
+
function sanitizePreWriteText(value, context = "text", options = {}) {
|
|
1526
|
+
const mode = options.mode ?? "redact";
|
|
1527
|
+
if (mode === "block") {
|
|
1528
|
+
assertPreWriteTextClean(value, context);
|
|
1529
|
+
return value;
|
|
1530
|
+
}
|
|
1531
|
+
return redactPreWriteText(value);
|
|
1532
|
+
}
|
|
1533
|
+
function sanitizePreWriteValue(value, context = "value", options = {}) {
|
|
1534
|
+
if (typeof value === "string")
|
|
1535
|
+
return sanitizePreWriteText(value, context, options);
|
|
1536
|
+
if (Array.isArray(value)) {
|
|
1537
|
+
return value.map((item, index) => sanitizePreWriteValue(item, `${context}[${index}]`, options));
|
|
1538
|
+
}
|
|
1539
|
+
if (value && typeof value === "object") {
|
|
1540
|
+
const sanitized = {};
|
|
1541
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1542
|
+
const safeKey = sanitizePreWriteText(key, `${context}.$key`, options);
|
|
1543
|
+
sanitized[safeKey] = sanitizePreWriteValue(child, `${context}.${safeKey}`, options);
|
|
1544
|
+
}
|
|
1545
|
+
return redactValue(sanitized);
|
|
1546
|
+
}
|
|
1547
|
+
return value;
|
|
1548
|
+
}
|
|
1549
|
+
var PREWRITE_SECRET_SCAN_SCHEMA, PreWriteSecretError;
|
|
1550
|
+
var init_prewrite_secrets = __esm(() => {
|
|
1551
|
+
init_redaction();
|
|
1552
|
+
init_secret_redaction();
|
|
1553
|
+
PREWRITE_SECRET_SCAN_SCHEMA = ["todos", "prewrite_secret_scan", "v1"].join(".");
|
|
1554
|
+
PreWriteSecretError = class PreWriteSecretError extends Error {
|
|
1555
|
+
context;
|
|
1556
|
+
findings;
|
|
1557
|
+
constructor(context, findings) {
|
|
1558
|
+
const summary = findings.map((finding) => `${finding.pattern}:${finding.count}`).join(", ");
|
|
1559
|
+
super(`Secret pattern detected before persistence in ${context}: ${summary}`);
|
|
1560
|
+
this.name = "PreWriteSecretError";
|
|
1561
|
+
this.context = context;
|
|
1562
|
+
this.findings = findings;
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
// src/lib/stale-lock-handoff.ts
|
|
1568
|
+
function normalizeExactTaskId(value) {
|
|
1569
|
+
if (typeof value !== "string" || !EXACT_TASK_UUID_RE.test(value.trim())) {
|
|
1570
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_TASK_ID", "stale-lock handoff requires one exact full task UUID", { task_id: typeof value === "string" ? value : null });
|
|
1571
|
+
}
|
|
1572
|
+
return value.trim().toLowerCase();
|
|
1573
|
+
}
|
|
1574
|
+
function requireNonEmptyString(value, field) {
|
|
1575
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
1576
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", `${field} must be a non-empty string`, { field });
|
|
1577
|
+
}
|
|
1578
|
+
const trimmed = value.trim();
|
|
1579
|
+
if (field === "reason" && trimmed.length > MAX_REASON_LENGTH) {
|
|
1580
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", `reason must be at most ${MAX_REASON_LENGTH} characters`, { field, max_length: MAX_REASON_LENGTH });
|
|
1581
|
+
}
|
|
1582
|
+
return trimmed;
|
|
1583
|
+
}
|
|
1584
|
+
function requireCanonicalLockVersion(value) {
|
|
1585
|
+
if (typeof value !== "string" || !CANONICAL_LOCK_VERSION_RE.test(value)) {
|
|
1586
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must be the exact canonical locked_at timestamp (YYYY-MM-DDTHH:mm:ss.sssZ)", { field: "expected_lock_version" });
|
|
1587
|
+
}
|
|
1588
|
+
const parsed = Date.parse(value);
|
|
1589
|
+
if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
1590
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
|
|
1591
|
+
}
|
|
1592
|
+
return value;
|
|
1593
|
+
}
|
|
1594
|
+
function requireStaleThreshold(value) {
|
|
1595
|
+
if (!Number.isSafeInteger(value) || Number(value) <= 0) {
|
|
1596
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "stale_after_seconds must be a positive safe integer", { field: "stale_after_seconds" });
|
|
1597
|
+
}
|
|
1598
|
+
return Number(value);
|
|
1599
|
+
}
|
|
1600
|
+
function prepareStaleLockHandoff(input, options = {}) {
|
|
1601
|
+
const taskId = normalizeExactTaskId(input.task_id);
|
|
1602
|
+
const actor = requireNonEmptyString(input.actor, "actor");
|
|
1603
|
+
const expectedHolder = requireNonEmptyString(input.expected_holder, "expected_holder");
|
|
1604
|
+
const newHolder = requireNonEmptyString(input.new_holder, "new_holder");
|
|
1605
|
+
const expectedLockVersion = requireCanonicalLockVersion(input.expected_lock_version);
|
|
1606
|
+
const staleAfterSeconds = requireStaleThreshold(input.stale_after_seconds);
|
|
1607
|
+
const reason = sanitizePreWriteText(requireNonEmptyString(input.reason, "reason"), "stale_lock_handoff.reason").trim();
|
|
1608
|
+
if (!reason) {
|
|
1609
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "reason must remain non-empty after safety filtering", { field: "reason" });
|
|
1610
|
+
}
|
|
1611
|
+
if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
|
|
1612
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
|
|
1613
|
+
}
|
|
1614
|
+
const operationTimestamp = options.now ?? new Date().toISOString();
|
|
1615
|
+
if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
|
|
1616
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
|
|
1617
|
+
}
|
|
1618
|
+
const staleCutoff = new Date(Date.parse(operationTimestamp) - staleAfterSeconds * 1000).toISOString();
|
|
1619
|
+
return {
|
|
1620
|
+
task_id: taskId,
|
|
1621
|
+
actor,
|
|
1622
|
+
expected_holder: expectedHolder,
|
|
1623
|
+
expected_lock_version: expectedLockVersion,
|
|
1624
|
+
stale_after_seconds: staleAfterSeconds,
|
|
1625
|
+
new_holder: newHolder,
|
|
1626
|
+
reason,
|
|
1627
|
+
operation_timestamp: operationTimestamp,
|
|
1628
|
+
stale_cutoff: staleCutoff,
|
|
1629
|
+
receipt_id: options.receiptId ?? crypto.randomUUID()
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
function buildStaleLockHandoffReceipt(input) {
|
|
1633
|
+
return {
|
|
1634
|
+
schema_version: STALE_LOCK_HANDOFF_SCHEMA_VERSION,
|
|
1635
|
+
receipt_id: input.receipt_id,
|
|
1636
|
+
task_id: input.task_id,
|
|
1637
|
+
actor: input.actor,
|
|
1638
|
+
previous_holder: input.expected_holder,
|
|
1639
|
+
previous_lock_version: input.expected_lock_version,
|
|
1640
|
+
new_holder: input.new_holder,
|
|
1641
|
+
new_lock_version: input.operation_timestamp,
|
|
1642
|
+
stale_after_seconds: input.stale_after_seconds,
|
|
1643
|
+
stale_cutoff: input.stale_cutoff,
|
|
1644
|
+
reason: input.reason,
|
|
1645
|
+
created_at: input.operation_timestamp
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
function staleLockHandoffHistory(receipt, machineId) {
|
|
1649
|
+
return {
|
|
1650
|
+
id: receipt.receipt_id,
|
|
1651
|
+
task_id: receipt.task_id,
|
|
1652
|
+
action: STALE_LOCK_HANDOFF_ACTION,
|
|
1653
|
+
field: STALE_LOCK_HANDOFF_FIELD,
|
|
1654
|
+
old_value: JSON.stringify({
|
|
1655
|
+
holder: receipt.previous_holder,
|
|
1656
|
+
lock_version: receipt.previous_lock_version
|
|
1657
|
+
}),
|
|
1658
|
+
new_value: JSON.stringify(receipt),
|
|
1659
|
+
agent_id: receipt.actor,
|
|
1660
|
+
created_at: receipt.created_at,
|
|
1661
|
+
machine_id: machineId
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function throwStaleLockHandoffConflict(task, input) {
|
|
1665
|
+
if (!task.locked_by || !task.locked_at) {
|
|
1666
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_NOT_LOCKED", `Task ${input.task_id} does not have a complete lock to hand off`, { task_id: input.task_id });
|
|
1667
|
+
}
|
|
1668
|
+
if (task.locked_at !== input.expected_lock_version) {
|
|
1669
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_VERSION_MISMATCH", `Task ${input.task_id} lock version changed`, {
|
|
1670
|
+
task_id: input.task_id,
|
|
1671
|
+
expected_lock_version: input.expected_lock_version,
|
|
1672
|
+
current_lock_version: task.locked_at
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
if (task.locked_by !== input.expected_holder) {
|
|
1676
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_HOLDER_MISMATCH", `Task ${input.task_id} lock holder changed`, {
|
|
1677
|
+
task_id: input.task_id,
|
|
1678
|
+
expected_holder: input.expected_holder,
|
|
1679
|
+
current_holder: task.locked_by
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
if (isTerminalStatus(task.status)) {
|
|
1683
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_TERMINAL", `Task ${input.task_id} is ${task.status} and cannot transfer a lock`, { task_id: input.task_id, status: task.status });
|
|
1684
|
+
}
|
|
1685
|
+
if (task.locked_at >= input.stale_cutoff) {
|
|
1686
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_NOT_STALE", `Task ${input.task_id} lock is not older than the supplied stale threshold`, {
|
|
1687
|
+
task_id: input.task_id,
|
|
1688
|
+
current_lock_version: task.locked_at,
|
|
1689
|
+
stale_cutoff: input.stale_cutoff
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_CONFLICT", `Task ${input.task_id} changed during stale-lock handoff`, { task_id: input.task_id });
|
|
1693
|
+
}
|
|
1694
|
+
var STALE_LOCK_HANDOFF_SCHEMA_VERSION = "todos.stale-lock-handoff.v1", STALE_LOCK_HANDOFF_ACTION = "stale_lock_handoff", STALE_LOCK_HANDOFF_FIELD = "lock", EXACT_TASK_UUID_RE, CANONICAL_LOCK_VERSION_RE, MAX_REASON_LENGTH = 4096;
|
|
1695
|
+
var init_stale_lock_handoff = __esm(() => {
|
|
1696
|
+
init_types();
|
|
1697
|
+
init_creator_identity();
|
|
1698
|
+
init_prewrite_secrets();
|
|
1699
|
+
EXACT_TASK_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1700
|
+
CANONICAL_LOCK_VERSION_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1199
1703
|
// src/lib/slugs.ts
|
|
1200
1704
|
function normalizeSlug(value) {
|
|
1201
1705
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -1862,182 +2366,82 @@ var init_integrity = __esm(() => {
|
|
|
1862
2366
|
};
|
|
1863
2367
|
});
|
|
1864
2368
|
|
|
1865
|
-
// src/
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2369
|
+
// src/storage/audit-history-import.ts
|
|
2370
|
+
function auditHistoryRowsAreFieldIdentical(left, right) {
|
|
2371
|
+
return AUDIT_HISTORY_FIELDS.every((field) => {
|
|
2372
|
+
const leftValue = field === "machine_id" ? left[field] ?? null : left[field];
|
|
2373
|
+
const rightValue = field === "machine_id" ? right[field] ?? null : right[field];
|
|
2374
|
+
return leftValue === rightValue;
|
|
2375
|
+
});
|
|
1870
2376
|
}
|
|
1871
|
-
function
|
|
1872
|
-
|
|
1873
|
-
return cached;
|
|
1874
|
-
if (!existsSync3(getConfigPath())) {
|
|
1875
|
-
cached = {};
|
|
1876
|
-
return cached;
|
|
1877
|
-
}
|
|
1878
|
-
const config = readJsonFile(getConfigPath()) || {};
|
|
1879
|
-
if (typeof config.sync_agents === "string") {
|
|
1880
|
-
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
1881
|
-
}
|
|
1882
|
-
cached = config;
|
|
1883
|
-
return cached;
|
|
2377
|
+
function divergentAuditHistoryReplayError(id) {
|
|
2378
|
+
return `${AUDIT_HISTORY_DIVERGENT_REPLAY}: immutable audit_history row ${id} differs from stored row`;
|
|
1884
2379
|
}
|
|
1885
|
-
function
|
|
1886
|
-
|
|
1887
|
-
ensureDir(dirname(configPath));
|
|
1888
|
-
writeJsonFile(configPath, config);
|
|
1889
|
-
cached = config;
|
|
1890
|
-
return config;
|
|
2380
|
+
function forbiddenAuditHistoryTombstoneError(id) {
|
|
2381
|
+
return `${AUDIT_HISTORY_TOMBSTONE_FORBIDDEN}: audit_history tombstone ${id} is not allowed`;
|
|
1891
2382
|
}
|
|
1892
|
-
function
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
}
|
|
1903
|
-
if (bestKey && config.project_pools[bestKey]) {
|
|
1904
|
-
return config.project_pools[bestKey];
|
|
1905
|
-
}
|
|
2383
|
+
function parseAuditHistoryImportFailure(message) {
|
|
2384
|
+
const divergentPrefix = `${AUDIT_HISTORY_DIVERGENT_REPLAY}: immutable audit_history row `;
|
|
2385
|
+
const divergentSuffix = " differs from stored row";
|
|
2386
|
+
if (message.startsWith(divergentPrefix) && message.endsWith(divergentSuffix)) {
|
|
2387
|
+
return {
|
|
2388
|
+
code: AUDIT_HISTORY_DIVERGENT_REPLAY,
|
|
2389
|
+
auditHistoryId: message.slice(divergentPrefix.length, -divergentSuffix.length),
|
|
2390
|
+
conflict: true,
|
|
2391
|
+
status: 409
|
|
2392
|
+
};
|
|
1906
2393
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2394
|
+
const tombstonePrefix = `${AUDIT_HISTORY_TOMBSTONE_FORBIDDEN}: audit_history tombstone `;
|
|
2395
|
+
const tombstoneSuffix = " is not allowed";
|
|
2396
|
+
if (message.startsWith(tombstonePrefix) && message.endsWith(tombstoneSuffix)) {
|
|
2397
|
+
return {
|
|
2398
|
+
code: AUDIT_HISTORY_TOMBSTONE_FORBIDDEN,
|
|
2399
|
+
auditHistoryId: message.slice(tombstonePrefix.length, -tombstoneSuffix.length),
|
|
2400
|
+
conflict: false,
|
|
2401
|
+
status: 400
|
|
2402
|
+
};
|
|
1914
2403
|
}
|
|
1915
|
-
return
|
|
2404
|
+
return null;
|
|
1916
2405
|
}
|
|
1917
|
-
var
|
|
1918
|
-
var
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2406
|
+
var AUDIT_HISTORY_DIVERGENT_REPLAY = "AUDIT_HISTORY_DIVERGENT_REPLAY", AUDIT_HISTORY_TOMBSTONE_FORBIDDEN = "AUDIT_HISTORY_TOMBSTONE_FORBIDDEN", AUDIT_HISTORY_FIELDS;
|
|
2407
|
+
var init_audit_history_import = __esm(() => {
|
|
2408
|
+
AUDIT_HISTORY_FIELDS = [
|
|
2409
|
+
"id",
|
|
2410
|
+
"task_id",
|
|
2411
|
+
"action",
|
|
2412
|
+
"field",
|
|
2413
|
+
"old_value",
|
|
2414
|
+
"new_value",
|
|
2415
|
+
"agent_id",
|
|
2416
|
+
"created_at",
|
|
2417
|
+
"machine_id"
|
|
2418
|
+
];
|
|
1927
2419
|
});
|
|
1928
2420
|
|
|
1929
|
-
// src/
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
function cloneRegex(regex) {
|
|
1934
|
-
return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
1935
|
-
}
|
|
1936
|
-
function customPatterns() {
|
|
1937
|
-
return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
|
|
1938
|
-
try {
|
|
1939
|
-
return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
|
|
1940
|
-
} catch {
|
|
1941
|
-
return [];
|
|
1942
|
-
}
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
|
-
function secretPatterns() {
|
|
1946
|
-
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
1947
|
-
}
|
|
1948
|
-
function isRedactionPlaceholderMatch(match) {
|
|
1949
|
-
const trimmed = match.trim();
|
|
1950
|
-
return new RegExp(`^${REDACTION_PLACEHOLDER}$`).test(trimmed) || new RegExp(`=\\s*['"]?${REDACTION_PLACEHOLDER}['"]?$`).test(trimmed);
|
|
1951
|
-
}
|
|
1952
|
-
function isRedactionPlaceholderKey(key) {
|
|
1953
|
-
return new RegExp(`^${REDACTION_PLACEHOLDER}$`).test(key.trim());
|
|
1954
|
-
}
|
|
1955
|
-
function isSecretKey(key) {
|
|
1956
|
-
if (isRedactionPlaceholderKey(key))
|
|
1957
|
-
return false;
|
|
1958
|
-
if (NON_SECRET_USAGE_KEYS.has(key.toLowerCase()))
|
|
1959
|
-
return false;
|
|
1960
|
-
if (DEFAULT_SECRET_KEY_PATTERN.test(key))
|
|
1961
|
-
return true;
|
|
1962
|
-
return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
|
|
1963
|
-
}
|
|
1964
|
-
function redactEvidenceText(value) {
|
|
1965
|
-
let redacted = value;
|
|
1966
|
-
for (const pattern of secretPatterns()) {
|
|
1967
|
-
const regex = cloneRegex(pattern.regex);
|
|
1968
|
-
const replacement = pattern.replacement ?? "[REDACTED]";
|
|
1969
|
-
redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
|
|
1970
|
-
}
|
|
1971
|
-
return redacted;
|
|
1972
|
-
}
|
|
1973
|
-
function redactValue(value) {
|
|
1974
|
-
if (typeof value === "string")
|
|
1975
|
-
return redactEvidenceText(value);
|
|
2421
|
+
// src/task-manifest/canonical.ts
|
|
2422
|
+
import { createHash as createHash3 } from "crypto";
|
|
2423
|
+
function canonicalize(value) {
|
|
1976
2424
|
if (Array.isArray(value))
|
|
1977
|
-
return value.map(
|
|
1978
|
-
if (value && typeof value === "object") {
|
|
1979
|
-
|
|
1980
|
-
for (const [key, child] of Object.entries(value)) {
|
|
1981
|
-
if (isSecretKey(key)) {
|
|
1982
|
-
redacted[key] = "[REDACTED]";
|
|
1983
|
-
} else {
|
|
1984
|
-
redacted[key] = redactValue(child);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return redacted;
|
|
2425
|
+
return value.map(canonicalize);
|
|
2426
|
+
if (value !== null && typeof value === "object") {
|
|
2427
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize(entry2)]));
|
|
1988
2428
|
}
|
|
1989
2429
|
return value;
|
|
1990
2430
|
}
|
|
1991
|
-
function
|
|
1992
|
-
|
|
1993
|
-
for (const pattern of secretPatterns()) {
|
|
1994
|
-
const matches = value.match(cloneRegex(pattern.regex))?.filter((match) => !isRedactionPlaceholderMatch(match));
|
|
1995
|
-
if (matches?.length)
|
|
1996
|
-
findings.push({ pattern: pattern.name, count: matches.length });
|
|
1997
|
-
}
|
|
1998
|
-
return findings;
|
|
2431
|
+
function canonicalJson(value) {
|
|
2432
|
+
return JSON.stringify(canonicalize(value));
|
|
1999
2433
|
}
|
|
2000
|
-
function
|
|
2001
|
-
return
|
|
2002
|
-
redaction_patterns: unique(loadConfig().secret_safety?.redaction_patterns),
|
|
2003
|
-
redaction_keys: unique(loadConfig().secret_safety?.redaction_keys)
|
|
2004
|
-
};
|
|
2434
|
+
function canonicalDigest(value) {
|
|
2435
|
+
return createHash3("sha256").update(canonicalJson(value)).digest("hex");
|
|
2005
2436
|
}
|
|
2006
|
-
function
|
|
2007
|
-
const
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
}
|
|
2012
|
-
saveConfig({ ...config, secret_safety: next });
|
|
2013
|
-
return next;
|
|
2437
|
+
function deterministicUuid(namespace, ...parts) {
|
|
2438
|
+
const bytes = createHash3("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
2439
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
2440
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
2441
|
+
const hex = bytes.toString("hex");
|
|
2442
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
2014
2443
|
}
|
|
2015
|
-
var
|
|
2016
|
-
var init_redaction = __esm(() => {
|
|
2017
|
-
init_config2();
|
|
2018
|
-
DEFAULT_SECRET_PATTERNS = [
|
|
2019
|
-
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
2020
|
-
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
2021
|
-
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
2022
|
-
{ name: "npm-token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_NPM_TOKEN]" },
|
|
2023
|
-
{ name: "github-fine-grained-token", regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
2024
|
-
{ name: "github-token", regex: /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g, replacement: "[REDACTED_GITHUB_TOKEN]" },
|
|
2025
|
-
{ name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
|
|
2026
|
-
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
2027
|
-
];
|
|
2028
|
-
DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
|
|
2029
|
-
NON_SECRET_USAGE_KEYS = new Set([
|
|
2030
|
-
"tokens",
|
|
2031
|
-
"total_tokens",
|
|
2032
|
-
"token_count",
|
|
2033
|
-
"input_tokens",
|
|
2034
|
-
"output_tokens",
|
|
2035
|
-
"prompt_tokens",
|
|
2036
|
-
"completion_tokens",
|
|
2037
|
-
"cost_tokens"
|
|
2038
|
-
]);
|
|
2039
|
-
REDACTION_PLACEHOLDER = String.raw`\[REDACTED(?:_[A-Z_]+)?\]`;
|
|
2040
|
-
});
|
|
2444
|
+
var init_canonical = () => {};
|
|
2041
2445
|
|
|
2042
2446
|
// src/storage/postgres-adapter.ts
|
|
2043
2447
|
import { randomUUID } from "crypto";
|
|
@@ -2069,6 +2473,7 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
2069
2473
|
getChangedSince: (since, filters) => getChangedSince(since, filters, store),
|
|
2070
2474
|
lock: (id, agentId) => lockTask(id, agentId, store),
|
|
2071
2475
|
unlock: (id, agentId) => unlockTask(id, agentId, store),
|
|
2476
|
+
handoffStaleLock: (input, context) => store.handoffStaleLock(input, context),
|
|
2072
2477
|
getByFingerprint: (fingerprint) => store.getTaskByFingerprint(fingerprint)
|
|
2073
2478
|
},
|
|
2074
2479
|
dependencies: {
|
|
@@ -2215,6 +2620,91 @@ class PostgresJsonRecordStore {
|
|
|
2215
2620
|
LIMIT 1`, [this.service, type, id]);
|
|
2216
2621
|
return result.rows[0] ? payloadRecord2(result.rows[0].payload) : null;
|
|
2217
2622
|
}
|
|
2623
|
+
async handoffStaleLock(input, context = {}) {
|
|
2624
|
+
const prepared = prepareStaleLockHandoff(input);
|
|
2625
|
+
const receipt = buildStaleLockHandoffReceipt(prepared);
|
|
2626
|
+
const history = staleLockHandoffHistory(receipt, this.machineId(context));
|
|
2627
|
+
await this.ensureSchema();
|
|
2628
|
+
const result = await this.options.client.query(`/* todos:stale-lock-handoff-atomic */ WITH
|
|
2629
|
+
target AS MATERIALIZED (
|
|
2630
|
+
SELECT payload
|
|
2631
|
+
FROM ${this.tableName}
|
|
2632
|
+
WHERE service = $1
|
|
2633
|
+
AND object_type = 'tasks'
|
|
2634
|
+
AND object_id = $2
|
|
2635
|
+
AND deleted_at IS NULL
|
|
2636
|
+
FOR UPDATE
|
|
2637
|
+
),
|
|
2638
|
+
updated AS (
|
|
2639
|
+
UPDATE ${this.tableName} AS task_record
|
|
2640
|
+
SET payload = jsonb_set(
|
|
2641
|
+
jsonb_set(
|
|
2642
|
+
jsonb_set(
|
|
2643
|
+
jsonb_set(
|
|
2644
|
+
task_record.payload,
|
|
2645
|
+
'{locked_by}',
|
|
2646
|
+
to_jsonb($6::text),
|
|
2647
|
+
true
|
|
2648
|
+
),
|
|
2649
|
+
'{locked_at}',
|
|
2650
|
+
to_jsonb($7::text),
|
|
2651
|
+
true
|
|
2652
|
+
),
|
|
2653
|
+
'{updated_at}',
|
|
2654
|
+
to_jsonb($7::text),
|
|
2655
|
+
true
|
|
2656
|
+
),
|
|
2657
|
+
'{version}',
|
|
2658
|
+
to_jsonb(COALESCE((task_record.payload->>'version')::integer, 0) + 1),
|
|
2659
|
+
true
|
|
2660
|
+
),
|
|
2661
|
+
updated_at = $7::timestamptz,
|
|
2662
|
+
source_machine_id = $10,
|
|
2663
|
+
version = COALESCE(task_record.version, 0) + 1
|
|
2664
|
+
FROM target
|
|
2665
|
+
WHERE task_record.service = $1
|
|
2666
|
+
AND task_record.object_type = 'tasks'
|
|
2667
|
+
AND task_record.object_id = $2
|
|
2668
|
+
AND task_record.deleted_at IS NULL
|
|
2669
|
+
AND target.payload->>'locked_by' = $3
|
|
2670
|
+
AND target.payload->>'locked_at' = $4
|
|
2671
|
+
AND $4::timestamptz < $5::timestamptz
|
|
2672
|
+
AND COALESCE(target.payload->>'status', '') NOT IN ('completed', 'failed', 'cancelled')
|
|
2673
|
+
RETURNING task_record.payload
|
|
2674
|
+
),
|
|
2675
|
+
audit AS (
|
|
2676
|
+
INSERT INTO ${this.tableName} (
|
|
2677
|
+
service, object_type, object_id, payload, updated_at,
|
|
2678
|
+
deleted_at, source_machine_id, version
|
|
2679
|
+
)
|
|
2680
|
+
SELECT $1, 'audit_history', $8, $9::jsonb, $7::timestamptz,
|
|
2681
|
+
NULL, $10, NULL
|
|
2682
|
+
FROM updated
|
|
2683
|
+
RETURNING payload
|
|
2684
|
+
)
|
|
2685
|
+
SELECT
|
|
2686
|
+
(SELECT payload FROM target) AS current_payload,
|
|
2687
|
+
(SELECT payload FROM updated) AS updated_payload,
|
|
2688
|
+
(SELECT payload FROM audit) AS audit_payload`, [
|
|
2689
|
+
this.service,
|
|
2690
|
+
prepared.task_id,
|
|
2691
|
+
prepared.expected_holder,
|
|
2692
|
+
prepared.expected_lock_version,
|
|
2693
|
+
prepared.stale_cutoff,
|
|
2694
|
+
prepared.new_holder,
|
|
2695
|
+
prepared.operation_timestamp,
|
|
2696
|
+
receipt.receipt_id,
|
|
2697
|
+
jsonbParam(history),
|
|
2698
|
+
this.machineId(context)
|
|
2699
|
+
]);
|
|
2700
|
+
const row = result.rows[0];
|
|
2701
|
+
if (!row?.current_payload)
|
|
2702
|
+
throw new TaskNotFoundError(prepared.task_id);
|
|
2703
|
+
if (!row.updated_payload || !row.audit_payload) {
|
|
2704
|
+
throwStaleLockHandoffConflict(payloadRecord2(row.current_payload), prepared);
|
|
2705
|
+
}
|
|
2706
|
+
return receipt;
|
|
2707
|
+
}
|
|
2218
2708
|
async list(type) {
|
|
2219
2709
|
return (await this.listRecords(type)).map((record) => record.payload);
|
|
2220
2710
|
}
|
|
@@ -2484,6 +2974,28 @@ class PostgresJsonRecordStore {
|
|
|
2484
2974
|
}
|
|
2485
2975
|
return value;
|
|
2486
2976
|
}
|
|
2977
|
+
async insertImmutableAuditHistory(value, context = {}) {
|
|
2978
|
+
await this.ensureSchema();
|
|
2979
|
+
const inserted = await this.options.client.query(`INSERT INTO ${this.tableName} (
|
|
2980
|
+
service, object_type, object_id, payload, updated_at,
|
|
2981
|
+
deleted_at, source_machine_id, version
|
|
2982
|
+
) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, NULL, $6, NULL)
|
|
2983
|
+
ON CONFLICT (service, object_type, object_id) DO NOTHING
|
|
2984
|
+
RETURNING object_id`, [
|
|
2985
|
+
this.service,
|
|
2986
|
+
"audit_history",
|
|
2987
|
+
value.id,
|
|
2988
|
+
jsonbParam(value),
|
|
2989
|
+
value.created_at,
|
|
2990
|
+
context.requestId ?? this.sourceMachineId ?? null
|
|
2991
|
+
]);
|
|
2992
|
+
if (inserted.rows.length > 0)
|
|
2993
|
+
return "inserted";
|
|
2994
|
+
const existing = await this.get("audit_history", value.id);
|
|
2995
|
+
if (existing && auditHistoryRowsAreFieldIdentical(existing, value))
|
|
2996
|
+
return "identical";
|
|
2997
|
+
throw new Error(divergentAuditHistoryReplayError(value.id));
|
|
2998
|
+
}
|
|
2487
2999
|
async upsertTaskWithPlanMembershipGuard(value, guardedPlanIds, explicitProject, context = {}) {
|
|
2488
3000
|
const planIds = [...new Set(guardedPlanIds.filter(Boolean))].sort();
|
|
2489
3001
|
if (planIds.length === 0)
|
|
@@ -3598,16 +4110,17 @@ async function findCommit(sha, store) {
|
|
|
3598
4110
|
async function addGitRef(input, store, context) {
|
|
3599
4111
|
if (!await store.get("tasks", input.task_id))
|
|
3600
4112
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
4113
|
+
const existing = (await store.list("refs")).filter((ref) => ref.task_id === input.task_id && ref.ref_type === input.ref_type && ref.name === input.name).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id))[0];
|
|
3601
4114
|
const timestamp = new Date().toISOString();
|
|
3602
4115
|
const gitRef = {
|
|
3603
|
-
id:
|
|
4116
|
+
id: existing?.id ?? deterministicUuid("todos:git-ref:v1", input.task_id, input.ref_type, input.name),
|
|
3604
4117
|
task_id: input.task_id,
|
|
3605
4118
|
ref_type: input.ref_type,
|
|
3606
4119
|
name: input.name,
|
|
3607
|
-
url: input.url ?? null,
|
|
3608
|
-
provider: input.provider ?? null,
|
|
4120
|
+
url: input.url ?? existing?.url ?? null,
|
|
4121
|
+
provider: input.provider ?? existing?.provider ?? null,
|
|
3609
4122
|
metadata: input.metadata ?? {},
|
|
3610
|
-
created_at: timestamp,
|
|
4123
|
+
created_at: existing?.created_at ?? timestamp,
|
|
3611
4124
|
updated_at: timestamp
|
|
3612
4125
|
};
|
|
3613
4126
|
await store.upsert("refs", gitRef, context);
|
|
@@ -3972,6 +4485,11 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
3972
4485
|
result.errors.push(...validateSnapshotRoutingDestinationConflicts(snapshot.projects, snapshot.taskLists, existingProjects, existingTaskLists));
|
|
3973
4486
|
if (result.errors.length > 0)
|
|
3974
4487
|
return result;
|
|
4488
|
+
const auditHistory = await preflightAuditHistoryImport(snapshot.auditHistory, snapshot.tombstones ?? [], store);
|
|
4489
|
+
result.errors.push(...auditHistory.errors);
|
|
4490
|
+
if (result.errors.length > 0)
|
|
4491
|
+
return result;
|
|
4492
|
+
result.skipped += auditHistory.identical;
|
|
3975
4493
|
const entries = [
|
|
3976
4494
|
...snapshot.tasks.map((row) => ["tasks", row]),
|
|
3977
4495
|
...snapshot.projects.map((row) => ["projects", row]),
|
|
@@ -3980,9 +4498,20 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
3980
4498
|
...snapshot.agents.map((row) => ["agents", row]),
|
|
3981
4499
|
...snapshot.taskLists.map((row) => ["task_lists", row]),
|
|
3982
4500
|
...snapshot.templates.map((row) => ["templates", row]),
|
|
3983
|
-
...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row])
|
|
3984
|
-
...snapshot.auditHistory.map((row) => ["audit_history", row])
|
|
4501
|
+
...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row])
|
|
3985
4502
|
];
|
|
4503
|
+
for (const row of auditHistory.rowsToInsert) {
|
|
4504
|
+
try {
|
|
4505
|
+
const outcome = await store.insertImmutableAuditHistory(row, context);
|
|
4506
|
+
if (outcome === "inserted")
|
|
4507
|
+
result.inserted += 1;
|
|
4508
|
+
else
|
|
4509
|
+
result.skipped += 1;
|
|
4510
|
+
} catch (error) {
|
|
4511
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
4512
|
+
return result;
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
3986
4515
|
for (const [type, row] of entries) {
|
|
3987
4516
|
try {
|
|
3988
4517
|
const existing = await store.get(type, row.id);
|
|
@@ -4016,6 +4545,32 @@ async function importSnapshot(snapshot, store, context) {
|
|
|
4016
4545
|
}
|
|
4017
4546
|
return result;
|
|
4018
4547
|
}
|
|
4548
|
+
async function preflightAuditHistoryImport(rows, tombstones, store) {
|
|
4549
|
+
const errors = tombstones.filter((tombstone) => tombstone.object_type === "audit_history").map((tombstone) => forbiddenAuditHistoryTombstoneError(tombstone.object_id));
|
|
4550
|
+
const rowsToInsert = [];
|
|
4551
|
+
const seen = new Map;
|
|
4552
|
+
let identical = 0;
|
|
4553
|
+
for (const row of rows) {
|
|
4554
|
+
const prior = seen.get(row.id);
|
|
4555
|
+
if (prior) {
|
|
4556
|
+
if (auditHistoryRowsAreFieldIdentical(prior, row))
|
|
4557
|
+
identical += 1;
|
|
4558
|
+
else
|
|
4559
|
+
errors.push(divergentAuditHistoryReplayError(row.id));
|
|
4560
|
+
continue;
|
|
4561
|
+
}
|
|
4562
|
+
seen.set(row.id, row);
|
|
4563
|
+
const existing = await store.get("audit_history", row.id);
|
|
4564
|
+
if (!existing) {
|
|
4565
|
+
rowsToInsert.push(row);
|
|
4566
|
+
} else if (auditHistoryRowsAreFieldIdentical(existing, row)) {
|
|
4567
|
+
identical += 1;
|
|
4568
|
+
} else {
|
|
4569
|
+
errors.push(divergentAuditHistoryReplayError(row.id));
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
return { rowsToInsert, identical, errors };
|
|
4573
|
+
}
|
|
4019
4574
|
async function requireRecord(type, id, store) {
|
|
4020
4575
|
const record = await store.get(type, id);
|
|
4021
4576
|
if (!record)
|
|
@@ -4122,9 +4677,12 @@ var init_postgres_adapter = __esm(() => {
|
|
|
4122
4677
|
init_types();
|
|
4123
4678
|
init_creator_identity();
|
|
4124
4679
|
init_plan_project_link_contract();
|
|
4680
|
+
init_stale_lock_handoff();
|
|
4125
4681
|
init_postgres_sync();
|
|
4126
4682
|
init_integrity();
|
|
4127
4683
|
init_redaction();
|
|
4684
|
+
init_audit_history_import();
|
|
4685
|
+
init_canonical();
|
|
4128
4686
|
TASK_ORDER_BY = `ORDER BY ${TASK_ORDER_TIEBREAK}`;
|
|
4129
4687
|
});
|
|
4130
4688
|
|
|
@@ -4144,9 +4702,9 @@ var init_types2 = __esm(() => {
|
|
|
4144
4702
|
});
|
|
4145
4703
|
|
|
4146
4704
|
// src/pr-groups/ledger.ts
|
|
4147
|
-
import { createHash as
|
|
4705
|
+
import { createHash as createHash4 } from "crypto";
|
|
4148
4706
|
function sha256(value) {
|
|
4149
|
-
return
|
|
4707
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
4150
4708
|
}
|
|
4151
4709
|
function stableValue(value) {
|
|
4152
4710
|
if (Array.isArray(value))
|
|
@@ -11583,7 +12141,7 @@ var init_runner_sandbox = __esm(() => {
|
|
|
11583
12141
|
});
|
|
11584
12142
|
|
|
11585
12143
|
// src/lib/event-hooks.ts
|
|
11586
|
-
import { createHash as
|
|
12144
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
|
|
11587
12145
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
11588
12146
|
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
11589
12147
|
import { createConnection } from "net";
|
|
@@ -11650,7 +12208,7 @@ function buildEnvelope(type, payload, timestamp2 = new Date().toISOString()) {
|
|
|
11650
12208
|
payload: redactValue(payload ?? {}),
|
|
11651
12209
|
source: { package: "@hasna/todos", local_only: true }
|
|
11652
12210
|
};
|
|
11653
|
-
const digest =
|
|
12211
|
+
const digest = createHash5("sha256").update(canonicalEvent(base)).digest("hex");
|
|
11654
12212
|
return { ...base, integrity: { algorithm: "sha256", digest } };
|
|
11655
12213
|
}
|
|
11656
12214
|
function summarize(value) {
|
|
@@ -12833,185 +13391,6 @@ var init_shared_events = __esm(() => {
|
|
|
12833
13391
|
init_event_emission_safety();
|
|
12834
13392
|
});
|
|
12835
13393
|
|
|
12836
|
-
// src/lib/secret-redaction.ts
|
|
12837
|
-
function isAllowlisted(text, match, allowlist) {
|
|
12838
|
-
const context = text.slice(Math.max(0, text.indexOf(match) - 20), text.indexOf(match) + match.length + 20);
|
|
12839
|
-
return allowlist.some((re) => re.test(context) || re.test(match));
|
|
12840
|
-
}
|
|
12841
|
-
function scanTextForSecrets(text, options = {}) {
|
|
12842
|
-
const allowlist = [...DEFAULT_ALLOWLIST, ...options.allowlist ?? []];
|
|
12843
|
-
const matches = [];
|
|
12844
|
-
const patterns = [
|
|
12845
|
-
...DEFAULT_PATTERNS,
|
|
12846
|
-
...options.custom_patterns?.map((p, i) => ({ name: `custom_${i}`, pattern: p })) ?? []
|
|
12847
|
-
];
|
|
12848
|
-
for (const { name, pattern, allowlist_ok } of patterns) {
|
|
12849
|
-
const re = new RegExp(pattern.source, pattern.flags);
|
|
12850
|
-
let m;
|
|
12851
|
-
while ((m = re.exec(text)) !== null) {
|
|
12852
|
-
const match = m[0];
|
|
12853
|
-
if (allowlist_ok && isAllowlisted(text, match, allowlist))
|
|
12854
|
-
continue;
|
|
12855
|
-
if (isAllowlisted(text, match, allowlist))
|
|
12856
|
-
continue;
|
|
12857
|
-
const line = text.slice(0, m.index).split(`
|
|
12858
|
-
`).length;
|
|
12859
|
-
matches.push({ pattern: name, match: match.slice(0, 12) + (match.length > 12 ? "\u2026" : ""), index: m.index, line });
|
|
12860
|
-
}
|
|
12861
|
-
}
|
|
12862
|
-
return {
|
|
12863
|
-
schema_version: SECRET_REDACTION_SCHEMA,
|
|
12864
|
-
clean: matches.length === 0,
|
|
12865
|
-
matches
|
|
12866
|
-
};
|
|
12867
|
-
}
|
|
12868
|
-
function redactText(text, options = {}) {
|
|
12869
|
-
const placeholder = options.placeholder ?? REDACTION_PLACEHOLDER2;
|
|
12870
|
-
let out = text;
|
|
12871
|
-
for (const { pattern, allowlist_ok } of DEFAULT_PATTERNS) {
|
|
12872
|
-
out = out.replace(new RegExp(pattern.source, pattern.flags), (match) => {
|
|
12873
|
-
if (allowlist_ok && isAllowlisted(out, match, [...DEFAULT_ALLOWLIST, ...options.allowlist ?? []])) {
|
|
12874
|
-
return match;
|
|
12875
|
-
}
|
|
12876
|
-
return placeholder;
|
|
12877
|
-
});
|
|
12878
|
-
}
|
|
12879
|
-
for (const custom of options.custom_patterns ?? []) {
|
|
12880
|
-
out = out.replace(custom, placeholder);
|
|
12881
|
-
}
|
|
12882
|
-
for (const fn of customRedactors) {
|
|
12883
|
-
out = fn(out);
|
|
12884
|
-
}
|
|
12885
|
-
return out;
|
|
12886
|
-
}
|
|
12887
|
-
function redactExportRecord(record) {
|
|
12888
|
-
const base = redactValue(record);
|
|
12889
|
-
for (const [key, value] of Object.entries(base)) {
|
|
12890
|
-
if (typeof value === "string") {
|
|
12891
|
-
base[key] = redactText(value);
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
return base;
|
|
12895
|
-
}
|
|
12896
|
-
var SECRET_REDACTION_SCHEMA, REDACTION_PLACEHOLDER2 = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
|
|
12897
|
-
var init_secret_redaction = __esm(() => {
|
|
12898
|
-
init_redaction();
|
|
12899
|
-
SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
|
|
12900
|
-
DEFAULT_PATTERNS = [
|
|
12901
|
-
{ name: "openai_sk", pattern: /\bsk-[a-zA-Z0-9]{10,}\b/g },
|
|
12902
|
-
{ name: "github_pat", pattern: /\bghp_[a-zA-Z0-9]{20,}\b/g },
|
|
12903
|
-
{ name: "github_oauth", pattern: /\bgho_[a-zA-Z0-9]{20,}\b/g },
|
|
12904
|
-
{ name: "aws_access_key", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
12905
|
-
{ name: "bearer_token", pattern: /\bBearer\s+[a-zA-Z0-9\-._~+/]+=*\b/gi },
|
|
12906
|
-
{ name: "jwt", pattern: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g },
|
|
12907
|
-
{ name: "private_key_block", pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g },
|
|
12908
|
-
{ name: "generic_api_key", pattern: /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[a-zA-Z0-9\-._]{8,}['"]?/gi, allowlist_ok: true }
|
|
12909
|
-
];
|
|
12910
|
-
DEFAULT_ALLOWLIST = [
|
|
12911
|
-
/\[REDACTED\]/,
|
|
12912
|
-
/example\.com/i,
|
|
12913
|
-
/your-api-key-here/i,
|
|
12914
|
-
/sk-test/i,
|
|
12915
|
-
/ghp_xxx/i
|
|
12916
|
-
];
|
|
12917
|
-
customRedactors = [];
|
|
12918
|
-
});
|
|
12919
|
-
|
|
12920
|
-
// src/lib/prewrite-secrets.ts
|
|
12921
|
-
function mergeFindings(findings) {
|
|
12922
|
-
const byPattern = new Map;
|
|
12923
|
-
for (const finding of findings) {
|
|
12924
|
-
const existing = byPattern.get(finding.pattern);
|
|
12925
|
-
if (!existing) {
|
|
12926
|
-
byPattern.set(finding.pattern, {
|
|
12927
|
-
pattern: finding.pattern,
|
|
12928
|
-
count: finding.count,
|
|
12929
|
-
lines: Array.from(new Set(finding.lines)).sort((a, b) => a - b)
|
|
12930
|
-
});
|
|
12931
|
-
continue;
|
|
12932
|
-
}
|
|
12933
|
-
existing.count += finding.count;
|
|
12934
|
-
existing.lines = Array.from(new Set([...existing.lines, ...finding.lines])).sort((a, b) => a - b);
|
|
12935
|
-
}
|
|
12936
|
-
return [...byPattern.values()].sort((left, right) => left.pattern.localeCompare(right.pattern));
|
|
12937
|
-
}
|
|
12938
|
-
function redactPreWriteText(value) {
|
|
12939
|
-
return redactText(redactEvidenceText(value));
|
|
12940
|
-
}
|
|
12941
|
-
function scanPreWriteText(value, context = "text") {
|
|
12942
|
-
const redacted = redactPreWriteText(value);
|
|
12943
|
-
if (redacted === value) {
|
|
12944
|
-
return {
|
|
12945
|
-
schema_version: PREWRITE_SECRET_SCAN_SCHEMA,
|
|
12946
|
-
clean: true,
|
|
12947
|
-
context,
|
|
12948
|
-
findings: []
|
|
12949
|
-
};
|
|
12950
|
-
}
|
|
12951
|
-
const structural = scanTextForSecrets(value).matches.map((match) => ({
|
|
12952
|
-
pattern: match.pattern,
|
|
12953
|
-
count: 1,
|
|
12954
|
-
lines: match.line ? [match.line] : []
|
|
12955
|
-
}));
|
|
12956
|
-
const configured = listSecretFindings(value).map((finding) => ({
|
|
12957
|
-
pattern: finding.pattern,
|
|
12958
|
-
count: finding.count,
|
|
12959
|
-
lines: []
|
|
12960
|
-
}));
|
|
12961
|
-
return {
|
|
12962
|
-
schema_version: PREWRITE_SECRET_SCAN_SCHEMA,
|
|
12963
|
-
clean: false,
|
|
12964
|
-
context,
|
|
12965
|
-
findings: mergeFindings([...structural, ...configured])
|
|
12966
|
-
};
|
|
12967
|
-
}
|
|
12968
|
-
function assertPreWriteTextClean(value, context = "text") {
|
|
12969
|
-
const scan = scanPreWriteText(value, context);
|
|
12970
|
-
if (!scan.clean)
|
|
12971
|
-
throw new PreWriteSecretError(context, scan.findings);
|
|
12972
|
-
}
|
|
12973
|
-
function sanitizePreWriteText(value, context = "text", options = {}) {
|
|
12974
|
-
const mode = options.mode ?? "redact";
|
|
12975
|
-
if (mode === "block") {
|
|
12976
|
-
assertPreWriteTextClean(value, context);
|
|
12977
|
-
return value;
|
|
12978
|
-
}
|
|
12979
|
-
return redactPreWriteText(value);
|
|
12980
|
-
}
|
|
12981
|
-
function sanitizePreWriteValue(value, context = "value", options = {}) {
|
|
12982
|
-
if (typeof value === "string")
|
|
12983
|
-
return sanitizePreWriteText(value, context, options);
|
|
12984
|
-
if (Array.isArray(value)) {
|
|
12985
|
-
return value.map((item, index) => sanitizePreWriteValue(item, `${context}[${index}]`, options));
|
|
12986
|
-
}
|
|
12987
|
-
if (value && typeof value === "object") {
|
|
12988
|
-
const sanitized = {};
|
|
12989
|
-
for (const [key, child] of Object.entries(value)) {
|
|
12990
|
-
const safeKey = sanitizePreWriteText(key, `${context}.$key`, options);
|
|
12991
|
-
sanitized[safeKey] = sanitizePreWriteValue(child, `${context}.${safeKey}`, options);
|
|
12992
|
-
}
|
|
12993
|
-
return redactValue(sanitized);
|
|
12994
|
-
}
|
|
12995
|
-
return value;
|
|
12996
|
-
}
|
|
12997
|
-
var PREWRITE_SECRET_SCAN_SCHEMA, PreWriteSecretError;
|
|
12998
|
-
var init_prewrite_secrets = __esm(() => {
|
|
12999
|
-
init_redaction();
|
|
13000
|
-
init_secret_redaction();
|
|
13001
|
-
PREWRITE_SECRET_SCAN_SCHEMA = ["todos", "prewrite_secret_scan", "v1"].join(".");
|
|
13002
|
-
PreWriteSecretError = class PreWriteSecretError extends Error {
|
|
13003
|
-
context;
|
|
13004
|
-
findings;
|
|
13005
|
-
constructor(context, findings) {
|
|
13006
|
-
const summary = findings.map((finding) => `${finding.pattern}:${finding.count}`).join(", ");
|
|
13007
|
-
super(`Secret pattern detected before persistence in ${context}: ${summary}`);
|
|
13008
|
-
this.name = "PreWriteSecretError";
|
|
13009
|
-
this.context = context;
|
|
13010
|
-
this.findings = findings;
|
|
13011
|
-
}
|
|
13012
|
-
};
|
|
13013
|
-
});
|
|
13014
|
-
|
|
13015
13394
|
// src/lib/activity-audit.ts
|
|
13016
13395
|
var exports_activity_audit = {};
|
|
13017
13396
|
__export(exports_activity_audit, {
|
|
@@ -13204,28 +13583,55 @@ var init_activity_audit = __esm(() => {
|
|
|
13204
13583
|
function sanitizeHistoryValue(value, context) {
|
|
13205
13584
|
return value === undefined || value === null ? null : sanitizePreWriteText(String(value), context);
|
|
13206
13585
|
}
|
|
13207
|
-
function
|
|
13586
|
+
function insertTaskHistory(entry2, db) {
|
|
13208
13587
|
const d = db || getDatabase();
|
|
13209
|
-
const
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13588
|
+
const safeEntry = {
|
|
13589
|
+
...entry2,
|
|
13590
|
+
field: entry2.field || null,
|
|
13591
|
+
old_value: sanitizeHistoryValue(entry2.old_value, "task_history.old_value"),
|
|
13592
|
+
new_value: sanitizeHistoryValue(entry2.new_value, "task_history.new_value"),
|
|
13593
|
+
agent_id: entry2.agent_id || null,
|
|
13594
|
+
machine_id: entry2.machine_id ?? currentStorageMachineId(d)
|
|
13595
|
+
};
|
|
13214
13596
|
d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
|
|
13215
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
13597
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
13598
|
+
safeEntry.id,
|
|
13599
|
+
safeEntry.task_id,
|
|
13600
|
+
safeEntry.action,
|
|
13601
|
+
safeEntry.field,
|
|
13602
|
+
safeEntry.old_value,
|
|
13603
|
+
safeEntry.new_value,
|
|
13604
|
+
safeEntry.agent_id,
|
|
13605
|
+
safeEntry.created_at,
|
|
13606
|
+
safeEntry.machine_id ?? null
|
|
13607
|
+
]);
|
|
13216
13608
|
try {
|
|
13217
13609
|
const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
|
|
13218
13610
|
logActivity2({
|
|
13219
13611
|
entity_type: "task",
|
|
13220
|
-
entity_id:
|
|
13221
|
-
action,
|
|
13222
|
-
field,
|
|
13223
|
-
old_value:
|
|
13224
|
-
new_value:
|
|
13225
|
-
actor_id:
|
|
13612
|
+
entity_id: safeEntry.task_id,
|
|
13613
|
+
action: safeEntry.action,
|
|
13614
|
+
field: safeEntry.field ?? undefined,
|
|
13615
|
+
old_value: safeEntry.old_value,
|
|
13616
|
+
new_value: safeEntry.new_value,
|
|
13617
|
+
actor_id: safeEntry.agent_id ?? undefined
|
|
13226
13618
|
}, d);
|
|
13227
13619
|
} catch {}
|
|
13228
|
-
return
|
|
13620
|
+
return safeEntry;
|
|
13621
|
+
}
|
|
13622
|
+
function logTaskChange2(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
13623
|
+
const d = db || getDatabase();
|
|
13624
|
+
return insertTaskHistory({
|
|
13625
|
+
id: uuid(),
|
|
13626
|
+
task_id: taskId,
|
|
13627
|
+
action,
|
|
13628
|
+
field: field || null,
|
|
13629
|
+
old_value: oldValue ?? null,
|
|
13630
|
+
new_value: newValue ?? null,
|
|
13631
|
+
agent_id: agentId || null,
|
|
13632
|
+
created_at: now(),
|
|
13633
|
+
machine_id: currentStorageMachineId(d)
|
|
13634
|
+
}, d);
|
|
13229
13635
|
}
|
|
13230
13636
|
function getTaskHistory(taskId, db) {
|
|
13231
13637
|
const d = db || getDatabase();
|
|
@@ -14192,6 +14598,7 @@ __export(exports_task_lifecycle, {
|
|
|
14192
14598
|
startTask: () => startTask2,
|
|
14193
14599
|
spawnNextRecurrence: () => spawnNextRecurrence,
|
|
14194
14600
|
lockTask: () => lockTask2,
|
|
14601
|
+
handoffStaleTaskLock: () => handoffStaleTaskLock,
|
|
14195
14602
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
14196
14603
|
getTaskLockStatus: () => getTaskLockStatus,
|
|
14197
14604
|
getStaleTasks: () => getStaleTasks,
|
|
@@ -14460,6 +14867,38 @@ function unlockTask2(id, agentId, db) {
|
|
|
14460
14867
|
WHERE id = ?`, [timestamp2, id]);
|
|
14461
14868
|
return true;
|
|
14462
14869
|
}
|
|
14870
|
+
function handoffStaleTaskLock(input, db) {
|
|
14871
|
+
const d = db || getDatabase();
|
|
14872
|
+
const prepared = prepareStaleLockHandoff(input);
|
|
14873
|
+
const receipt = buildStaleLockHandoffReceipt(prepared);
|
|
14874
|
+
const history = staleLockHandoffHistory(receipt, null);
|
|
14875
|
+
const transfer = d.transaction(() => {
|
|
14876
|
+
const result = d.run(`UPDATE tasks
|
|
14877
|
+
SET locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1
|
|
14878
|
+
WHERE id = ?
|
|
14879
|
+
AND locked_by = ?
|
|
14880
|
+
AND locked_at = ?
|
|
14881
|
+
AND julianday(locked_at) < julianday(?)
|
|
14882
|
+
AND status NOT IN ('completed', 'failed', 'cancelled')`, [
|
|
14883
|
+
prepared.new_holder,
|
|
14884
|
+
prepared.operation_timestamp,
|
|
14885
|
+
prepared.operation_timestamp,
|
|
14886
|
+
prepared.task_id,
|
|
14887
|
+
prepared.expected_holder,
|
|
14888
|
+
prepared.expected_lock_version,
|
|
14889
|
+
prepared.stale_cutoff
|
|
14890
|
+
]);
|
|
14891
|
+
if (result.changes === 0) {
|
|
14892
|
+
const current = getTask(prepared.task_id, d);
|
|
14893
|
+
if (!current)
|
|
14894
|
+
throw new TaskNotFoundError(prepared.task_id);
|
|
14895
|
+
throwStaleLockHandoffConflict(current, prepared);
|
|
14896
|
+
}
|
|
14897
|
+
insertTaskHistory(history, d);
|
|
14898
|
+
});
|
|
14899
|
+
transfer();
|
|
14900
|
+
return receipt;
|
|
14901
|
+
}
|
|
14463
14902
|
function getTaskLockStatus(id, db) {
|
|
14464
14903
|
const d = db || getDatabase();
|
|
14465
14904
|
const task = getTask(id, d);
|
|
@@ -14760,6 +15199,7 @@ var init_task_lifecycle = __esm(() => {
|
|
|
14760
15199
|
init_task_crud();
|
|
14761
15200
|
init_task_graph();
|
|
14762
15201
|
init_prewrite_secrets();
|
|
15202
|
+
init_stale_lock_handoff();
|
|
14763
15203
|
});
|
|
14764
15204
|
|
|
14765
15205
|
// src/db/task-crud.ts
|
|
@@ -16778,8 +17218,8 @@ var init_boards = __esm(() => {
|
|
|
16778
17218
|
});
|
|
16779
17219
|
|
|
16780
17220
|
// src/lib/artifact-store.ts
|
|
16781
|
-
import { createHash as
|
|
16782
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
17221
|
+
import { createHash as createHash6 } from "crypto";
|
|
17222
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16783
17223
|
import { basename, dirname as dirname4, join as join6, resolve as resolve7 } from "path";
|
|
16784
17224
|
import { tmpdir as tmpdir2 } from "os";
|
|
16785
17225
|
function isInMemoryDb2(path) {
|
|
@@ -16803,7 +17243,7 @@ function artifactStorePath(relativePath) {
|
|
|
16803
17243
|
return join6(artifactStoreRoot(), normalized);
|
|
16804
17244
|
}
|
|
16805
17245
|
function sha2562(buffer) {
|
|
16806
|
-
return
|
|
17246
|
+
return createHash6("sha256").update(buffer).digest("hex");
|
|
16807
17247
|
}
|
|
16808
17248
|
function isTextLike(buffer, path) {
|
|
16809
17249
|
if (buffer.includes(0))
|
|
@@ -16846,7 +17286,7 @@ function storeArtifactContent(input) {
|
|
|
16846
17286
|
const sourceStat = statSync2(sourcePath);
|
|
16847
17287
|
if (!sourceStat.isFile())
|
|
16848
17288
|
throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
|
|
16849
|
-
const sourceBuffer =
|
|
17289
|
+
const sourceBuffer = readFileSync3(sourcePath);
|
|
16850
17290
|
const sourceSha = sha2562(sourceBuffer);
|
|
16851
17291
|
const textLike = isTextLike(sourceBuffer, input.path);
|
|
16852
17292
|
let storedBuffer = sourceBuffer;
|
|
@@ -16933,7 +17373,7 @@ function verifyStoredArtifact(input) {
|
|
|
16933
17373
|
message: "stored artifact content is missing"
|
|
16934
17374
|
};
|
|
16935
17375
|
}
|
|
16936
|
-
const buffer =
|
|
17376
|
+
const buffer = readFileSync3(storedPath);
|
|
16937
17377
|
const actualSha = sha2562(buffer);
|
|
16938
17378
|
const actualSize = buffer.length;
|
|
16939
17379
|
const ok = actualSha === store.sha256 && actualSize === store.size_bytes;
|
|
@@ -16953,7 +17393,7 @@ function exportStoredArtifactContent(input) {
|
|
|
16953
17393
|
const report = verifyStoredArtifact(input);
|
|
16954
17394
|
if (report.status !== "ok" || !report.relative_path || !report.actual_sha256 || report.actual_size_bytes === null)
|
|
16955
17395
|
return null;
|
|
16956
|
-
const content =
|
|
17396
|
+
const content = readFileSync3(artifactStorePath(report.relative_path));
|
|
16957
17397
|
return {
|
|
16958
17398
|
artifact_id: input.id,
|
|
16959
17399
|
sha256: report.actual_sha256,
|
|
@@ -18332,6 +18772,7 @@ __export(exports_tasks, {
|
|
|
18332
18772
|
insertTaskTags: () => insertTaskTags,
|
|
18333
18773
|
importTaskBoardBundle: () => importTaskBoardBundle,
|
|
18334
18774
|
importCalendarIcs: () => importCalendarIcs,
|
|
18775
|
+
handoffStaleTaskLock: () => handoffStaleTaskLock,
|
|
18335
18776
|
getTimeReport: () => getTimeReport,
|
|
18336
18777
|
getTimeLogs: () => getTimeLogs,
|
|
18337
18778
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
@@ -19304,8 +19745,11 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
19304
19745
|
const existingTaskLists = d.query("SELECT id, project_id, slug FROM task_lists").all();
|
|
19305
19746
|
result.errors.push(...validateSnapshotRoutingDestinationConflicts(snapshot.projects, snapshot.taskLists, existingProjects, existingTaskLists));
|
|
19306
19747
|
}
|
|
19748
|
+
const auditImport = preflightAuditHistoryImport2(d, snapshot.auditHistory, snapshot.tombstones ?? []);
|
|
19749
|
+
result.errors.push(...auditImport.errors);
|
|
19307
19750
|
if (result.errors.length > 0)
|
|
19308
19751
|
return result;
|
|
19752
|
+
result.skipped += auditImport.identicalReplayCount;
|
|
19309
19753
|
const applyRows = (objectType, table, columns, rows, updateClockColumn, acceptEqualClock = true, afterUpsert) => {
|
|
19310
19754
|
for (const row of rows) {
|
|
19311
19755
|
try {
|
|
@@ -19340,10 +19784,72 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
19340
19784
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
19341
19785
|
}
|
|
19342
19786
|
});
|
|
19343
|
-
|
|
19787
|
+
insertAuditHistoryRows(d, auditImport.rowsToInsert, result);
|
|
19344
19788
|
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
19345
19789
|
return result;
|
|
19346
19790
|
}
|
|
19791
|
+
function preflightAuditHistoryImport2(db, rows, tombstones) {
|
|
19792
|
+
const errors = tombstones.filter((tombstone) => tombstone.object_type === "audit_history").map((tombstone) => forbiddenAuditHistoryTombstoneError(tombstone.object_id));
|
|
19793
|
+
const rowsToInsert = [];
|
|
19794
|
+
const seen = new Map;
|
|
19795
|
+
let identicalReplayCount = 0;
|
|
19796
|
+
for (const rawRow of rows) {
|
|
19797
|
+
try {
|
|
19798
|
+
const row = asRecord(rawRow);
|
|
19799
|
+
if (typeof row.id !== "string" || !row.id) {
|
|
19800
|
+
throw new Error("task_history row is missing id");
|
|
19801
|
+
}
|
|
19802
|
+
const prior = seen.get(row.id);
|
|
19803
|
+
if (prior) {
|
|
19804
|
+
if (auditHistoryRowsAreFieldIdentical(prior, row))
|
|
19805
|
+
identicalReplayCount += 1;
|
|
19806
|
+
else
|
|
19807
|
+
errors.push(divergentAuditHistoryReplayError(row.id));
|
|
19808
|
+
continue;
|
|
19809
|
+
}
|
|
19810
|
+
seen.set(row.id, row);
|
|
19811
|
+
const existing = getAuditHistoryById(db, row.id);
|
|
19812
|
+
if (!existing) {
|
|
19813
|
+
rowsToInsert.push(row);
|
|
19814
|
+
} else if (auditHistoryRowsAreFieldIdentical(existing, row)) {
|
|
19815
|
+
identicalReplayCount += 1;
|
|
19816
|
+
} else {
|
|
19817
|
+
errors.push(divergentAuditHistoryReplayError(row.id));
|
|
19818
|
+
}
|
|
19819
|
+
} catch (error) {
|
|
19820
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
19821
|
+
}
|
|
19822
|
+
}
|
|
19823
|
+
return { rowsToInsert, identicalReplayCount, errors };
|
|
19824
|
+
}
|
|
19825
|
+
function insertAuditHistoryRows(db, rows, result) {
|
|
19826
|
+
for (const rawRow of rows) {
|
|
19827
|
+
try {
|
|
19828
|
+
const row = asRecord(rawRow);
|
|
19829
|
+
const presentColumns = AUDIT_COLUMNS.filter((column) => (column in row));
|
|
19830
|
+
if (!presentColumns.includes("id"))
|
|
19831
|
+
presentColumns.unshift("id");
|
|
19832
|
+
const placeholders = presentColumns.map(() => "?").join(", ");
|
|
19833
|
+
const values = presentColumns.map((column) => valueForColumn(column, row[column]));
|
|
19834
|
+
const changes = db.run(`INSERT OR IGNORE INTO task_history (${presentColumns.join(", ")}) VALUES (${placeholders})`, values).changes;
|
|
19835
|
+
if (changes > 0) {
|
|
19836
|
+
result.inserted += 1;
|
|
19837
|
+
continue;
|
|
19838
|
+
}
|
|
19839
|
+
const existing = getAuditHistoryById(db, String(row["id"]));
|
|
19840
|
+
if (existing && auditHistoryRowsAreFieldIdentical(existing, row)) {
|
|
19841
|
+
result.skipped += 1;
|
|
19842
|
+
} else {
|
|
19843
|
+
result.errors.push(divergentAuditHistoryReplayError(String(row["id"])));
|
|
19844
|
+
}
|
|
19845
|
+
} catch (error) {
|
|
19846
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
19847
|
+
}
|
|
19848
|
+
}
|
|
19849
|
+
}
|
|
19850
|
+
function getAuditHistoryById(db, id) {
|
|
19851
|
+
return db.query(`SELECT ${AUDIT_COLUMNS.join(", ")} FROM task_history WHERE id = ? LIMIT 1`).get(id);
|
|
19852
|
+
}
|
|
19347
19853
|
function upsertById(db, table, columns, row, updateClockColumn, acceptEqualClock = true) {
|
|
19348
19854
|
const id = row["id"];
|
|
19349
19855
|
if (typeof id !== "string" || !id)
|
|
@@ -19441,7 +19947,7 @@ function tableForTombstone(objectType) {
|
|
|
19441
19947
|
return "task_templates";
|
|
19442
19948
|
if (objectType === "template_tasks")
|
|
19443
19949
|
return "template_tasks";
|
|
19444
|
-
|
|
19950
|
+
throw new Error(`unsupported storage tombstone object_type: ${String(objectType)}`);
|
|
19445
19951
|
}
|
|
19446
19952
|
function listRows(db, table, columns) {
|
|
19447
19953
|
return db.query(`SELECT ${columns.join(", ")} FROM ${table} ORDER BY id`).all();
|
|
@@ -19488,6 +19994,7 @@ var init_sqlite_snapshot = __esm(() => {
|
|
|
19488
19994
|
init_tasks();
|
|
19489
19995
|
init_templates();
|
|
19490
19996
|
init_storage_tombstones();
|
|
19997
|
+
init_audit_history_import();
|
|
19491
19998
|
PROJECT_COLUMNS = [
|
|
19492
19999
|
"id",
|
|
19493
20000
|
"name",
|
|
@@ -19752,6 +20259,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
19752
20259
|
unlockTask2(id, agentId, database());
|
|
19753
20260
|
return true;
|
|
19754
20261
|
},
|
|
20262
|
+
handoffStaleLock: (input) => handoffStaleTaskLock(input, database()),
|
|
19755
20263
|
delete: (id) => deleteTask(id, database()),
|
|
19756
20264
|
start: (id, agentId) => startTask2(id, agentId, database()),
|
|
19757
20265
|
complete: (id, agentId, options2) => completeTask2(id, agentId, database(), options2),
|
|
@@ -20468,25 +20976,25 @@ var init_sqlite = __esm(() => {
|
|
|
20468
20976
|
});
|
|
20469
20977
|
|
|
20470
20978
|
// src/project-registration/authority.ts
|
|
20471
|
-
import { createHash as
|
|
20979
|
+
import { createHash as createHash7 } from "crypto";
|
|
20472
20980
|
function canonicalProjectRegistrationJson(value) {
|
|
20473
|
-
return JSON.stringify(
|
|
20981
|
+
return JSON.stringify(canonicalize2(value));
|
|
20474
20982
|
}
|
|
20475
|
-
function
|
|
20983
|
+
function canonicalize2(value) {
|
|
20476
20984
|
if (Array.isArray(value))
|
|
20477
|
-
return value.map(
|
|
20985
|
+
return value.map(canonicalize2);
|
|
20478
20986
|
if (!value || typeof value !== "object")
|
|
20479
20987
|
return value;
|
|
20480
20988
|
const out = {};
|
|
20481
20989
|
for (const key of Object.keys(value).sort()) {
|
|
20482
20990
|
const entry2 = value[key];
|
|
20483
20991
|
if (entry2 !== undefined)
|
|
20484
|
-
out[key] =
|
|
20992
|
+
out[key] = canonicalize2(entry2);
|
|
20485
20993
|
}
|
|
20486
20994
|
return out;
|
|
20487
20995
|
}
|
|
20488
20996
|
function digestProjectRegistrationValue(value) {
|
|
20489
|
-
return
|
|
20997
|
+
return createHash7("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
|
|
20490
20998
|
}
|
|
20491
20999
|
function deriveTodosProjectRegistrationIdempotencyKey(input) {
|
|
20492
21000
|
return `prk_${digestProjectRegistrationValue({
|
|
@@ -21481,31 +21989,6 @@ var init_project_registration = __esm(() => {
|
|
|
21481
21989
|
init_types3();
|
|
21482
21990
|
});
|
|
21483
21991
|
|
|
21484
|
-
// src/task-manifest/canonical.ts
|
|
21485
|
-
import { createHash as createHash7 } from "crypto";
|
|
21486
|
-
function canonicalize2(value) {
|
|
21487
|
-
if (Array.isArray(value))
|
|
21488
|
-
return value.map(canonicalize2);
|
|
21489
|
-
if (value !== null && typeof value === "object") {
|
|
21490
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize2(entry2)]));
|
|
21491
|
-
}
|
|
21492
|
-
return value;
|
|
21493
|
-
}
|
|
21494
|
-
function canonicalJson(value) {
|
|
21495
|
-
return JSON.stringify(canonicalize2(value));
|
|
21496
|
-
}
|
|
21497
|
-
function canonicalDigest(value) {
|
|
21498
|
-
return createHash7("sha256").update(canonicalJson(value)).digest("hex");
|
|
21499
|
-
}
|
|
21500
|
-
function deterministicUuid(namespace, ...parts) {
|
|
21501
|
-
const bytes = createHash7("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
21502
|
-
bytes[6] = bytes[6] & 15 | 80;
|
|
21503
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
21504
|
-
const hex = bytes.toString("hex");
|
|
21505
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
21506
|
-
}
|
|
21507
|
-
var init_canonical = () => {};
|
|
21508
|
-
|
|
21509
21992
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
21510
21993
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
21511
21994
|
const t = typeof data;
|
|
@@ -29130,6 +29613,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
29130
29613
|
ProjectTaskListEnsureResult: projectTaskListEnsureResultSchema,
|
|
29131
29614
|
ProjectTaskListRollbackResult: projectTaskListRollbackResultSchema,
|
|
29132
29615
|
TaskComment: taskCommentSchema,
|
|
29616
|
+
StaleLockHandoffInput: staleLockHandoffInputSchema,
|
|
29617
|
+
StaleLockHandoffReceipt: staleLockHandoffReceiptSchema,
|
|
29133
29618
|
TaskGitRef: taskGitRefSchema,
|
|
29134
29619
|
Plan: planSchema,
|
|
29135
29620
|
PlanProjectLinkReceipt: planProjectLinkReceiptSchema,
|
|
@@ -29152,6 +29637,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
29152
29637
|
plan_id: { type: "string" },
|
|
29153
29638
|
assigned_to: { type: "string" },
|
|
29154
29639
|
agent_id: { type: "string" },
|
|
29640
|
+
created_by: { type: "string" },
|
|
29155
29641
|
tags: { type: "array", items: { type: "string" } }
|
|
29156
29642
|
}
|
|
29157
29643
|
},
|
|
@@ -30337,6 +30823,51 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
30337
30823
|
}
|
|
30338
30824
|
}
|
|
30339
30825
|
},
|
|
30826
|
+
"/v1/tasks/{id}/stale-lock-handoff": {
|
|
30827
|
+
post: {
|
|
30828
|
+
operationId: "handoffStaleTaskLock",
|
|
30829
|
+
summary: "Atomically transfer one exact stale task lock",
|
|
30830
|
+
description: "Compares one full task UUID, current holder, and exact locked_at version, verifies the lock is strictly older than the supplied threshold, then transfers it directly and writes an immutable task-history receipt in the same backend transaction.",
|
|
30831
|
+
parameters: [
|
|
30832
|
+
{
|
|
30833
|
+
name: "id",
|
|
30834
|
+
in: "path",
|
|
30835
|
+
required: true,
|
|
30836
|
+
schema: { type: "string", format: "uuid" },
|
|
30837
|
+
description: "Exact full task UUID. Short ids and prefixes are rejected."
|
|
30838
|
+
}
|
|
30839
|
+
],
|
|
30840
|
+
requestBody: {
|
|
30841
|
+
required: true,
|
|
30842
|
+
content: {
|
|
30843
|
+
"application/json": {
|
|
30844
|
+
schema: { $ref: "#/components/schemas/StaleLockHandoffInput" }
|
|
30845
|
+
}
|
|
30846
|
+
}
|
|
30847
|
+
},
|
|
30848
|
+
responses: {
|
|
30849
|
+
"200": {
|
|
30850
|
+
content: {
|
|
30851
|
+
"application/json": {
|
|
30852
|
+
schema: {
|
|
30853
|
+
type: "object",
|
|
30854
|
+
additionalProperties: false,
|
|
30855
|
+
required: ["receipt"],
|
|
30856
|
+
properties: {
|
|
30857
|
+
receipt: { $ref: "#/components/schemas/StaleLockHandoffReceipt" }
|
|
30858
|
+
}
|
|
30859
|
+
}
|
|
30860
|
+
}
|
|
30861
|
+
}
|
|
30862
|
+
},
|
|
30863
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
30864
|
+
"403": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
30865
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
30866
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
30867
|
+
"501": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
30868
|
+
}
|
|
30869
|
+
}
|
|
30870
|
+
},
|
|
30340
30871
|
"/v1/tasks/{id}/refs": {
|
|
30341
30872
|
get: {
|
|
30342
30873
|
operationId: "listTaskGitRefs",
|
|
@@ -30933,7 +31464,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
30933
31464
|
}
|
|
30934
31465
|
};
|
|
30935
31466
|
}
|
|
30936
|
-
var taskSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
31467
|
+
var taskSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
30937
31468
|
var init_openapi = __esm(() => {
|
|
30938
31469
|
init_package_version();
|
|
30939
31470
|
init_types();
|
|
@@ -30949,9 +31480,12 @@ var init_openapi = __esm(() => {
|
|
|
30949
31480
|
parent_id: { type: "string", nullable: true },
|
|
30950
31481
|
assigned_to: { type: "string", nullable: true },
|
|
30951
31482
|
agent_id: { type: "string", nullable: true },
|
|
31483
|
+
created_by: { type: "string", nullable: true },
|
|
30952
31484
|
reason: { type: "string", nullable: true },
|
|
30953
31485
|
tags: { type: "array", items: { type: "string" } },
|
|
30954
31486
|
version: { type: "number" },
|
|
31487
|
+
locked_by: { type: "string", nullable: true },
|
|
31488
|
+
locked_at: { type: "string", format: "date-time", nullable: true },
|
|
30955
31489
|
created_at: { type: "string" },
|
|
30956
31490
|
updated_at: { type: "string" }
|
|
30957
31491
|
}
|
|
@@ -31116,6 +31650,69 @@ var init_openapi = __esm(() => {
|
|
|
31116
31650
|
created_at: { type: "string", format: "date-time" }
|
|
31117
31651
|
}
|
|
31118
31652
|
};
|
|
31653
|
+
staleLockHandoffInputSchema = {
|
|
31654
|
+
type: "object",
|
|
31655
|
+
additionalProperties: false,
|
|
31656
|
+
required: [
|
|
31657
|
+
"expected_holder",
|
|
31658
|
+
"expected_lock_version",
|
|
31659
|
+
"stale_after_seconds",
|
|
31660
|
+
"new_holder",
|
|
31661
|
+
"reason"
|
|
31662
|
+
],
|
|
31663
|
+
properties: {
|
|
31664
|
+
expected_holder: { type: "string", minLength: 1 },
|
|
31665
|
+
expected_lock_version: {
|
|
31666
|
+
type: "string",
|
|
31667
|
+
format: "date-time",
|
|
31668
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$",
|
|
31669
|
+
description: "Exact authoritative locked_at token read from the task; no default or normalization is applied."
|
|
31670
|
+
},
|
|
31671
|
+
stale_after_seconds: {
|
|
31672
|
+
type: "integer",
|
|
31673
|
+
minimum: 1,
|
|
31674
|
+
description: "Lock age threshold supplied by the caller. The lock must be strictly older at the CAS instant."
|
|
31675
|
+
},
|
|
31676
|
+
new_holder: {
|
|
31677
|
+
type: "string",
|
|
31678
|
+
minLength: 1,
|
|
31679
|
+
description: "Must match the agent bound to the authenticated API key."
|
|
31680
|
+
},
|
|
31681
|
+
reason: { type: "string", minLength: 1, maxLength: 4096 }
|
|
31682
|
+
}
|
|
31683
|
+
};
|
|
31684
|
+
staleLockHandoffReceiptSchema = {
|
|
31685
|
+
type: "object",
|
|
31686
|
+
additionalProperties: false,
|
|
31687
|
+
required: [
|
|
31688
|
+
"schema_version",
|
|
31689
|
+
"receipt_id",
|
|
31690
|
+
"task_id",
|
|
31691
|
+
"actor",
|
|
31692
|
+
"previous_holder",
|
|
31693
|
+
"previous_lock_version",
|
|
31694
|
+
"new_holder",
|
|
31695
|
+
"new_lock_version",
|
|
31696
|
+
"stale_after_seconds",
|
|
31697
|
+
"stale_cutoff",
|
|
31698
|
+
"reason",
|
|
31699
|
+
"created_at"
|
|
31700
|
+
],
|
|
31701
|
+
properties: {
|
|
31702
|
+
schema_version: { type: "string", enum: ["todos.stale-lock-handoff.v1"] },
|
|
31703
|
+
receipt_id: { type: "string", format: "uuid" },
|
|
31704
|
+
task_id: { type: "string", format: "uuid" },
|
|
31705
|
+
actor: { type: "string" },
|
|
31706
|
+
previous_holder: { type: "string" },
|
|
31707
|
+
previous_lock_version: { type: "string", format: "date-time" },
|
|
31708
|
+
new_holder: { type: "string" },
|
|
31709
|
+
new_lock_version: { type: "string", format: "date-time" },
|
|
31710
|
+
stale_after_seconds: { type: "integer", minimum: 1 },
|
|
31711
|
+
stale_cutoff: { type: "string", format: "date-time" },
|
|
31712
|
+
reason: { type: "string" },
|
|
31713
|
+
created_at: { type: "string", format: "date-time" }
|
|
31714
|
+
}
|
|
31715
|
+
};
|
|
31119
31716
|
taskGitRefSchema = {
|
|
31120
31717
|
type: "object",
|
|
31121
31718
|
additionalProperties: false,
|
|
@@ -32357,6 +32954,45 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
32357
32954
|
return error(405, `method ${method} not allowed on /v1/tasks`);
|
|
32358
32955
|
}
|
|
32359
32956
|
if (action) {
|
|
32957
|
+
if (action === "stale-lock-handoff") {
|
|
32958
|
+
if (method !== "POST") {
|
|
32959
|
+
return error(405, "method must be POST on /v1/tasks/:id/stale-lock-handoff");
|
|
32960
|
+
}
|
|
32961
|
+
const exactId = normalizeExactTaskId(id);
|
|
32962
|
+
if (!principal.agent) {
|
|
32963
|
+
return error(403, "stale-lock handoff requires an authenticated agent-bound key", {
|
|
32964
|
+
code: "STALE_LOCK_HANDOFF_ACTOR_MISMATCH"
|
|
32965
|
+
});
|
|
32966
|
+
}
|
|
32967
|
+
if (typeof store.tasks.handoffStaleLock !== "function") {
|
|
32968
|
+
return error(501, "stale-lock handoff is not supported by this storage backend");
|
|
32969
|
+
}
|
|
32970
|
+
const body3 = await readJson3(req) ?? {};
|
|
32971
|
+
const allowed = new Set([
|
|
32972
|
+
"expected_holder",
|
|
32973
|
+
"expected_lock_version",
|
|
32974
|
+
"stale_after_seconds",
|
|
32975
|
+
"new_holder",
|
|
32976
|
+
"reason"
|
|
32977
|
+
]);
|
|
32978
|
+
const unknown = Object.keys(body3).find((key2) => !allowed.has(key2));
|
|
32979
|
+
if (unknown) {
|
|
32980
|
+
return error(400, `unknown stale-lock handoff field: ${unknown}`, {
|
|
32981
|
+
code: "STALE_LOCK_HANDOFF_INVALID_INPUT",
|
|
32982
|
+
field: unknown
|
|
32983
|
+
});
|
|
32984
|
+
}
|
|
32985
|
+
const receipt = await store.tasks.handoffStaleLock({
|
|
32986
|
+
task_id: exactId,
|
|
32987
|
+
actor: principal.agent,
|
|
32988
|
+
expected_holder: body3.expected_holder,
|
|
32989
|
+
expected_lock_version: body3.expected_lock_version,
|
|
32990
|
+
stale_after_seconds: body3.stale_after_seconds,
|
|
32991
|
+
new_holder: body3.new_holder,
|
|
32992
|
+
reason: body3.reason
|
|
32993
|
+
}, contextFromPrincipal(principal));
|
|
32994
|
+
return json5({ receipt });
|
|
32995
|
+
}
|
|
32360
32996
|
if (action === "comments") {
|
|
32361
32997
|
if (method === "GET") {
|
|
32362
32998
|
if (!await store.tasks.get(id))
|
|
@@ -33145,7 +33781,24 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
33145
33781
|
if (received === 0) {
|
|
33146
33782
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
33147
33783
|
}
|
|
33784
|
+
const forbiddenAuditTombstone = (snapshot.tombstones ?? []).find((tombstone) => tombstone.object_type === "audit_history");
|
|
33785
|
+
if (forbiddenAuditTombstone) {
|
|
33786
|
+
return error(400, forbiddenAuditHistoryTombstoneError(forbiddenAuditTombstone.object_id), {
|
|
33787
|
+
code: AUDIT_HISTORY_TOMBSTONE_FORBIDDEN,
|
|
33788
|
+
conflict: false,
|
|
33789
|
+
audit_history_id: forbiddenAuditTombstone.object_id
|
|
33790
|
+
});
|
|
33791
|
+
}
|
|
33148
33792
|
const result = await store.sync.importSnapshot(snapshot, contextFromPrincipal(principal));
|
|
33793
|
+
const auditFailureMessage = result.errors.find((message) => parseAuditHistoryImportFailure(message) !== null);
|
|
33794
|
+
if (auditFailureMessage) {
|
|
33795
|
+
const failure = parseAuditHistoryImportFailure(auditFailureMessage);
|
|
33796
|
+
return error(failure.status, auditFailureMessage, {
|
|
33797
|
+
code: failure.code,
|
|
33798
|
+
conflict: failure.conflict,
|
|
33799
|
+
audit_history_id: failure.auditHistoryId
|
|
33800
|
+
});
|
|
33801
|
+
}
|
|
33149
33802
|
return json5({ result, received });
|
|
33150
33803
|
}
|
|
33151
33804
|
return error(404, `unknown /v1 resource: ${resource ?? "(root)"}`);
|
|
@@ -33168,6 +33821,14 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
33168
33821
|
if (e instanceof TaskNotFoundError) {
|
|
33169
33822
|
return error(404, e.message, { code: TaskNotFoundError.code });
|
|
33170
33823
|
}
|
|
33824
|
+
if (e instanceof StaleLockHandoffError) {
|
|
33825
|
+
const status2 = e.code === "STALE_LOCK_HANDOFF_INVALID_TASK_ID" || e.code === "STALE_LOCK_HANDOFF_INVALID_INPUT" ? 400 : e.code === "STALE_LOCK_HANDOFF_ACTOR_MISMATCH" ? 403 : 409;
|
|
33826
|
+
return error(status2, e.message, {
|
|
33827
|
+
code: e.code,
|
|
33828
|
+
conflict: status2 === 409,
|
|
33829
|
+
...e.details
|
|
33830
|
+
});
|
|
33831
|
+
}
|
|
33171
33832
|
if (e instanceof PlanNotFoundError) {
|
|
33172
33833
|
return error(404, e.message, { code: PlanNotFoundError.code });
|
|
33173
33834
|
}
|
|
@@ -33202,6 +33863,8 @@ var init_v1 = __esm(() => {
|
|
|
33202
33863
|
init_redaction();
|
|
33203
33864
|
init_project_task_list_ensure();
|
|
33204
33865
|
init_plan_project_link();
|
|
33866
|
+
init_stale_lock_handoff();
|
|
33867
|
+
init_audit_history_import();
|
|
33205
33868
|
JSON_HEADERS4 = { "Content-Type": "application/json" };
|
|
33206
33869
|
RFC3339_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
33207
33870
|
});
|
|
@@ -50727,7 +51390,7 @@ var init_token_utils = __esm(() => {
|
|
|
50727
51390
|
});
|
|
50728
51391
|
|
|
50729
51392
|
// src/lib/assignee-validation.ts
|
|
50730
|
-
import { readFileSync as
|
|
51393
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
50731
51394
|
import { homedir as homedir3 } from "os";
|
|
50732
51395
|
import { join as join9 } from "path";
|
|
50733
51396
|
function defaultSeatRosterPath() {
|
|
@@ -50735,7 +51398,7 @@ function defaultSeatRosterPath() {
|
|
|
50735
51398
|
}
|
|
50736
51399
|
function loadSeatSlugs(path = defaultSeatRosterPath()) {
|
|
50737
51400
|
try {
|
|
50738
|
-
const parsed = JSON.parse(
|
|
51401
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
50739
51402
|
const slugs = new Set;
|
|
50740
51403
|
for (const agent of parsed.agents ?? []) {
|
|
50741
51404
|
if (typeof agent?.slug === "string" && agent.slug.trim()) {
|
|
@@ -63967,6 +64630,7 @@ var init_http_client = __esm(() => {
|
|
|
63967
64630
|
});
|
|
63968
64631
|
|
|
63969
64632
|
// src/cli/cloud-router.ts
|
|
64633
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
63970
64634
|
import { resolve as resolvePath } from "path";
|
|
63971
64635
|
function cleanMode(value) {
|
|
63972
64636
|
const normalized = value?.trim().toLowerCase();
|
|
@@ -64367,20 +65031,28 @@ async function cloudGetTask(client, id) {
|
|
|
64367
65031
|
const raw = await client.get("tasks", id);
|
|
64368
65032
|
return raw == null ? null : unwrapTask(raw);
|
|
64369
65033
|
}
|
|
64370
|
-
async function cloudCreateTask(client, input) {
|
|
65034
|
+
async function cloudCreateTask(client, input, verification2 = {}) {
|
|
64371
65035
|
const expectedParentId = typeof input["parent_id"] === "string" ? input["parent_id"] : null;
|
|
64372
|
-
const
|
|
65036
|
+
const expectedCreatedBy = typeof verification2.expectedCreatedBy === "string" && verification2.expectedCreatedBy.trim() ? verification2.expectedCreatedBy : null;
|
|
65037
|
+
if (expectedCreatedBy !== null) {
|
|
65038
|
+
await requireTaskCreatorCapability(client);
|
|
65039
|
+
}
|
|
65040
|
+
const created = unwrapTask(await requiredRemoteRoute(client, "/v1/tasks", () => client.transport.post("/tasks", input, {
|
|
65041
|
+
idempotencyKey: randomUUID4(),
|
|
65042
|
+
retry: false
|
|
65043
|
+
}), ["PARENT_TASK_NOT_FOUND"]));
|
|
64373
65044
|
if (!created || typeof created.id !== "string" || !created.id.trim()) {
|
|
64374
65045
|
throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} returned a task create ` + "response without a stored task id; no success row or local SQLite fallback is permitted");
|
|
64375
65046
|
}
|
|
64376
65047
|
const persisted = await cloudGetTask(client, created.id);
|
|
64377
|
-
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId) {
|
|
64378
|
-
|
|
65048
|
+
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId || expectedCreatedBy !== null && persisted.created_by !== expectedCreatedBy) {
|
|
65049
|
+
const creatorDetail = expectedCreatedBy === null ? "" : ` and explicit created_by=${JSON.stringify(expectedCreatedBy)} ` + `(readback ${JSON.stringify(persisted?.created_by ?? null)})`;
|
|
65050
|
+
throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + `stored task id and parent_id${creatorDetail}; no success row or local SQLite fallback is permitted`);
|
|
64379
65051
|
}
|
|
64380
65052
|
return persisted;
|
|
64381
65053
|
}
|
|
64382
65054
|
async function cloudUpdateTask(client, id, patch) {
|
|
64383
|
-
return unwrapTask(await client.
|
|
65055
|
+
return unwrapTask(await client.transport.patch(`/tasks/${encodeURIComponent(id)}`, patch));
|
|
64384
65056
|
}
|
|
64385
65057
|
async function cloudDeleteTask(client, id) {
|
|
64386
65058
|
try {
|
|
@@ -64396,6 +65068,51 @@ async function cloudTaskAction(client, id, action, body2 = {}) {
|
|
|
64396
65068
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/${action}`, body2);
|
|
64397
65069
|
return unwrapTask(raw);
|
|
64398
65070
|
}
|
|
65071
|
+
function resolveOpenApiSchema(document, schema2) {
|
|
65072
|
+
if (!schema2 || typeof schema2 !== "object" || Array.isArray(schema2))
|
|
65073
|
+
return null;
|
|
65074
|
+
const value = schema2;
|
|
65075
|
+
if (typeof value["$ref"] !== "string")
|
|
65076
|
+
return value;
|
|
65077
|
+
const reference = value["$ref"];
|
|
65078
|
+
if (!reference.startsWith("#/"))
|
|
65079
|
+
return null;
|
|
65080
|
+
let current = document;
|
|
65081
|
+
for (const segment of reference.slice(2).split("/")) {
|
|
65082
|
+
const key2 = segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
65083
|
+
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
65084
|
+
return null;
|
|
65085
|
+
current = current[key2];
|
|
65086
|
+
}
|
|
65087
|
+
return current && typeof current === "object" && !Array.isArray(current) ? current : null;
|
|
65088
|
+
}
|
|
65089
|
+
function openApiSchemaProperty(document, schemaName, propertyName) {
|
|
65090
|
+
const schema2 = resolveOpenApiSchema(document, {
|
|
65091
|
+
$ref: `#/components/schemas/${schemaName}`
|
|
65092
|
+
});
|
|
65093
|
+
const properties = schema2?.["properties"];
|
|
65094
|
+
if (!properties || typeof properties !== "object" || Array.isArray(properties))
|
|
65095
|
+
return null;
|
|
65096
|
+
const property = properties[propertyName];
|
|
65097
|
+
return property && typeof property === "object" && !Array.isArray(property) ? property : null;
|
|
65098
|
+
}
|
|
65099
|
+
async function fetchTaskCreatorCapability(client) {
|
|
65100
|
+
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
65101
|
+
const inputProperty = openApiSchemaProperty(document, "CreateTaskInput", "created_by");
|
|
65102
|
+
const outputProperty = openApiSchemaProperty(document, "Task", "created_by");
|
|
65103
|
+
return inputProperty?.["type"] === "string" && outputProperty?.["type"] === "string" && outputProperty?.["nullable"] === true;
|
|
65104
|
+
}
|
|
65105
|
+
async function requireTaskCreatorCapability(client) {
|
|
65106
|
+
const authority = remoteAuthorityBase(client);
|
|
65107
|
+
let capability = taskCreatorCapabilityCache.get(authority);
|
|
65108
|
+
if (!capability) {
|
|
65109
|
+
capability = fetchTaskCreatorCapability(client);
|
|
65110
|
+
taskCreatorCapabilityCache.set(authority, capability);
|
|
65111
|
+
}
|
|
65112
|
+
if (!await capability) {
|
|
65113
|
+
throw new Error(`REMOTE_CREATED_BY_UNSUPPORTED: configured Todos authority ${authority} does not advertise created_by ` + "on both CreateTaskInput and Task in /v1/openapi.json; no task mutation was sent; deploy a compatible " + "@hasna/todos /v1 server before retrying; local SQLite fallback is disabled");
|
|
65114
|
+
}
|
|
65115
|
+
}
|
|
64399
65116
|
async function cloudGetStats(client) {
|
|
64400
65117
|
const raw = await requiredRemoteRoute(client, "/v1/stats", () => client.transport.get("/stats"));
|
|
64401
65118
|
return raw ?? {};
|
|
@@ -64591,7 +65308,7 @@ async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
|
64591
65308
|
return input.toLowerCase();
|
|
64592
65309
|
return (await cloudResolveTaskList(client, ref, projectId)).id;
|
|
64593
65310
|
}
|
|
64594
|
-
var UUID_RE, TRANSPORT_TOKENS, completionCapabilityCache, retryCapabilityCache, gitRefCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache;
|
|
65311
|
+
var UUID_RE, TRANSPORT_TOKENS, completionCapabilityCache, retryCapabilityCache, taskCreatorCapabilityCache, gitRefCapabilityCache, remoteCommandCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache;
|
|
64595
65312
|
var init_cloud_router = __esm(() => {
|
|
64596
65313
|
init_storage();
|
|
64597
65314
|
init_mode();
|
|
@@ -64611,7 +65328,9 @@ var init_cloud_router = __esm(() => {
|
|
|
64611
65328
|
};
|
|
64612
65329
|
completionCapabilityCache = new Map;
|
|
64613
65330
|
retryCapabilityCache = new Map;
|
|
65331
|
+
taskCreatorCapabilityCache = new Map;
|
|
64614
65332
|
gitRefCapabilityCache = new Map;
|
|
65333
|
+
remoteCommandCapabilityCache = new Map;
|
|
64615
65334
|
SERVER_MODE_CANDIDATES = ["postgres", "cloud", "self_hosted"];
|
|
64616
65335
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
64617
65336
|
listTagsCapabilityCache = new Map;
|
|
@@ -65007,7 +65726,7 @@ var init_task_crud2 = __esm(() => {
|
|
|
65007
65726
|
});
|
|
65008
65727
|
|
|
65009
65728
|
// src/lib/project-bootstrap.ts
|
|
65010
|
-
import { existsSync as existsSync9, readFileSync as
|
|
65729
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
65011
65730
|
import { basename as basename3, dirname as dirname6, resolve as resolve9 } from "path";
|
|
65012
65731
|
function safeStat(path) {
|
|
65013
65732
|
try {
|
|
@@ -65041,7 +65760,7 @@ function readPackageJson(path) {
|
|
|
65041
65760
|
if (!existsSync9(file))
|
|
65042
65761
|
return null;
|
|
65043
65762
|
try {
|
|
65044
|
-
const parsed = JSON.parse(
|
|
65763
|
+
const parsed = JSON.parse(readFileSync5(file, "utf-8"));
|
|
65045
65764
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
65046
65765
|
} catch {
|
|
65047
65766
|
return null;
|
|
@@ -65603,7 +66322,7 @@ var init_retention_cleanup = __esm(() => {
|
|
|
65603
66322
|
});
|
|
65604
66323
|
|
|
65605
66324
|
// src/lib/mention-resolver.ts
|
|
65606
|
-
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as
|
|
66325
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
|
|
65607
66326
|
import { basename as basename4, isAbsolute, join as join10, relative as relative3, resolve as resolve10, sep as sep3 } from "path";
|
|
65608
66327
|
function blankResolution(parsed) {
|
|
65609
66328
|
return {
|
|
@@ -65712,7 +66431,7 @@ function resolveFile(parsed, workspace) {
|
|
|
65712
66431
|
return resolution;
|
|
65713
66432
|
}
|
|
65714
66433
|
if (parsed.line !== undefined) {
|
|
65715
|
-
const lineCount =
|
|
66434
|
+
const lineCount = readFileSync6(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
65716
66435
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
65717
66436
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
65718
66437
|
return resolution;
|
|
@@ -65765,7 +66484,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
65765
66484
|
const pattern = symbolPattern(name);
|
|
65766
66485
|
const matches = [];
|
|
65767
66486
|
for (const file of walkSourceFiles(workspace)) {
|
|
65768
|
-
const lines =
|
|
66487
|
+
const lines = readFileSync6(file, "utf-8").split(/\r?\n/);
|
|
65769
66488
|
for (let index = 0;index < lines.length; index += 1) {
|
|
65770
66489
|
const line = lines[index];
|
|
65771
66490
|
const found = pattern.exec(line);
|
|
@@ -69432,7 +70151,7 @@ var init_audit_ledger = __esm(() => {
|
|
|
69432
70151
|
});
|
|
69433
70152
|
|
|
69434
70153
|
// src/lib/release-compatibility.ts
|
|
69435
|
-
import { readFileSync as
|
|
70154
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
69436
70155
|
import { join as join11, resolve as resolve12 } from "path";
|
|
69437
70156
|
import { Database as Database2 } from "bun:sqlite";
|
|
69438
70157
|
function pass(id, message, details) {
|
|
@@ -69445,7 +70164,7 @@ function warn(id, message, details) {
|
|
|
69445
70164
|
return { id, status: "warning", message, details };
|
|
69446
70165
|
}
|
|
69447
70166
|
function readPackageJson2(root) {
|
|
69448
|
-
return JSON.parse(
|
|
70167
|
+
return JSON.parse(readFileSync7(join11(root, "package.json"), "utf8"));
|
|
69449
70168
|
}
|
|
69450
70169
|
function sortedKeys(value) {
|
|
69451
70170
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -69826,6 +70545,8 @@ function searchTaskEntities(filters, db) {
|
|
|
69826
70545
|
return tasks.filter((task2) => taskMatchesSavedFilters(task2, filters, db)).slice(0, normalizeLimit(filters.limit));
|
|
69827
70546
|
}
|
|
69828
70547
|
function searchProjects(filters, db) {
|
|
70548
|
+
if (filters.agent_id)
|
|
70549
|
+
return [];
|
|
69829
70550
|
const params = [];
|
|
69830
70551
|
let sql = "SELECT * FROM projects WHERE 1=1";
|
|
69831
70552
|
if (filters.project_id) {
|
|
@@ -99368,7 +100089,7 @@ var init_agent_run_dispatcher = __esm(() => {
|
|
|
99368
100089
|
});
|
|
99369
100090
|
|
|
99370
100091
|
// src/lib/verification-providers.ts
|
|
99371
|
-
import { existsSync as existsSync12, readFileSync as
|
|
100092
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
99372
100093
|
function normalizeName5(name) {
|
|
99373
100094
|
const normalized = name.trim().toLowerCase();
|
|
99374
100095
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -99520,7 +100241,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
99520
100241
|
};
|
|
99521
100242
|
}
|
|
99522
100243
|
function runCiLogProvider(input) {
|
|
99523
|
-
const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ?
|
|
100244
|
+
const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync8(input.log_path, "utf-8") : "");
|
|
99524
100245
|
return {
|
|
99525
100246
|
status: classifyLog(text),
|
|
99526
100247
|
attempts: 1,
|
|
@@ -102261,7 +102982,7 @@ var init_local_bridge = __esm(() => {
|
|
|
102261
102982
|
|
|
102262
102983
|
// src/lib/local-backups.ts
|
|
102263
102984
|
import { createHash as createHash12 } from "crypto";
|
|
102264
|
-
import { readFileSync as
|
|
102985
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
102265
102986
|
import { dirname as dirname7, resolve as resolve13 } from "path";
|
|
102266
102987
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
102267
102988
|
function stableJson2(value) {
|
|
@@ -102371,7 +103092,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
102371
103092
|
return path;
|
|
102372
103093
|
}
|
|
102373
103094
|
function readLocalBackupFile(path) {
|
|
102374
|
-
return JSON.parse(
|
|
103095
|
+
return JSON.parse(readFileSync9(resolve13(path), "utf-8"));
|
|
102375
103096
|
}
|
|
102376
103097
|
function verifyLocalBackup(value, options = {}, db) {
|
|
102377
103098
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -103822,7 +104543,7 @@ __export(exports_local_extensions, {
|
|
|
103822
104543
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
103823
104544
|
});
|
|
103824
104545
|
import { createHash as createHash15, createVerify } from "crypto";
|
|
103825
|
-
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as
|
|
104546
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
103826
104547
|
import { basename as basename5, join as join12, resolve as resolve14 } from "path";
|
|
103827
104548
|
function isObject3(value) {
|
|
103828
104549
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -103904,7 +104625,7 @@ function normalizeManifest(input) {
|
|
|
103904
104625
|
};
|
|
103905
104626
|
}
|
|
103906
104627
|
function parseJson2(path) {
|
|
103907
|
-
return JSON.parse(
|
|
104628
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
103908
104629
|
}
|
|
103909
104630
|
function sha2566(bytes) {
|
|
103910
104631
|
return `sha256:${createHash15("sha256").update(bytes).digest("hex")}`;
|
|
@@ -104088,7 +104809,7 @@ function inspectExtensionSource(source3) {
|
|
|
104088
104809
|
const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync13) : resolved;
|
|
104089
104810
|
if (!manifestPath)
|
|
104090
104811
|
throw new Error(`extension directory ${source3} is missing todos.extension.json`);
|
|
104091
|
-
const raw =
|
|
104812
|
+
const raw = readFileSync10(manifestPath);
|
|
104092
104813
|
const parsed = parseJson2(manifestPath);
|
|
104093
104814
|
const bundle = isObject3(parsed) && isObject3(parsed["manifest"]);
|
|
104094
104815
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -108601,7 +109322,7 @@ __export(exports_extract, {
|
|
|
108601
109322
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
108602
109323
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
108603
109324
|
});
|
|
108604
|
-
import { existsSync as existsSync14, readFileSync as
|
|
109325
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
|
|
108605
109326
|
import { createHash as createHash17 } from "crypto";
|
|
108606
109327
|
import { relative as relative5, resolve as resolve15, join as join13 } from "path";
|
|
108607
109328
|
function stableHash(value) {
|
|
@@ -108616,7 +109337,7 @@ function readGitignorePatterns(basePath) {
|
|
|
108616
109337
|
if (!existsSync14(gitignorePath))
|
|
108617
109338
|
return [];
|
|
108618
109339
|
try {
|
|
108619
|
-
return
|
|
109340
|
+
return readFileSync11(gitignorePath, "utf-8").split(`
|
|
108620
109341
|
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
|
|
108621
109342
|
} catch {
|
|
108622
109343
|
return [];
|
|
@@ -108757,7 +109478,7 @@ function buildCodebaseIndex(options) {
|
|
|
108757
109478
|
for (const file of files) {
|
|
108758
109479
|
const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
|
|
108759
109480
|
try {
|
|
108760
|
-
const source3 =
|
|
109481
|
+
const source3 = readFileSync11(fullPath, "utf-8");
|
|
108761
109482
|
const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
|
|
108762
109483
|
indexed.push({
|
|
108763
109484
|
file: relPath,
|
|
@@ -108788,7 +109509,7 @@ function extractTodos(options, db) {
|
|
|
108788
109509
|
for (const file of files) {
|
|
108789
109510
|
const fullPath = statSync7(basePath).isFile() ? basePath : join13(basePath, file);
|
|
108790
109511
|
try {
|
|
108791
|
-
const source3 =
|
|
109512
|
+
const source3 = readFileSync11(fullPath, "utf-8");
|
|
108792
109513
|
const relPath = statSync7(basePath).isFile() ? relative5(resolve15(basePath, ".."), fullPath) : file;
|
|
108793
109514
|
const comments = extractFromSource(source3, relPath, tags);
|
|
108794
109515
|
allComments.push(...comments);
|
|
@@ -110330,7 +111051,7 @@ __export(exports_environment_snapshots, {
|
|
|
110330
111051
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
110331
111052
|
});
|
|
110332
111053
|
import { createHash as createHash18 } from "crypto";
|
|
110333
|
-
import { existsSync as existsSync15, readFileSync as
|
|
111054
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
110334
111055
|
import { hostname as hostname3, platform, arch } from "os";
|
|
110335
111056
|
import { dirname as dirname8, join as join15, resolve as resolve16 } from "path";
|
|
110336
111057
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -110344,7 +111065,7 @@ function fileRecord(root, relativePath) {
|
|
|
110344
111065
|
const stat = statSync8(path);
|
|
110345
111066
|
if (!stat.isFile())
|
|
110346
111067
|
return null;
|
|
110347
|
-
const content =
|
|
111068
|
+
const content = readFileSync12(path);
|
|
110348
111069
|
return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
|
|
110349
111070
|
}
|
|
110350
111071
|
function manifestRecord(root, relativePath) {
|