@hasna/todos 0.15.20 → 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 +10 -1
- 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/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +2856 -547
- package/dist/cli/stage-a.d.ts +27 -16
- 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 +638 -18
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3142 -1089
- package/dist/lib/cli-help.d.ts +2 -2
- 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.map +1 -1
- package/dist/mcp/index.js +127 -72
- package/dist/mcp.js +1 -1
- package/dist/project-registration.js +67 -29
- package/dist/registry.js +603 -18
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/v1.generated.d.ts +2 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +133 -78
- package/dist/server/openapi.d.ts +7 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage.js +60 -20
- package/dist/task-manifest.js +19 -3
- package/package.json +1 -1
package/dist/storage.js
CHANGED
|
@@ -4731,7 +4731,7 @@ var init_database = __esm(() => {
|
|
|
4731
4731
|
});
|
|
4732
4732
|
|
|
4733
4733
|
// src/lib/config.ts
|
|
4734
|
-
import { existsSync as existsSync4 } from "fs";
|
|
4734
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
4735
4735
|
import { dirname as dirname2, join as join3 } from "path";
|
|
4736
4736
|
function getConfigPath() {
|
|
4737
4737
|
return join3(getTodosGlobalDir(), "config.json");
|
|
@@ -4763,6 +4763,22 @@ function saveConfig(config) {
|
|
|
4763
4763
|
function updateConfig(patch) {
|
|
4764
4764
|
return saveConfig({ ...loadConfig(), ...patch });
|
|
4765
4765
|
}
|
|
4766
|
+
function getTodosAiConfig() {
|
|
4767
|
+
const configPath = getConfigPath();
|
|
4768
|
+
if (!existsSync4(configPath))
|
|
4769
|
+
return {};
|
|
4770
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
4771
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4772
|
+
throw new Error("Todos config must be a JSON object");
|
|
4773
|
+
}
|
|
4774
|
+
const ai = parsed["ai"];
|
|
4775
|
+
if (ai === undefined)
|
|
4776
|
+
return {};
|
|
4777
|
+
if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
|
|
4778
|
+
throw new Error("Todos AI configuration must be an object");
|
|
4779
|
+
}
|
|
4780
|
+
return { ...ai };
|
|
4781
|
+
}
|
|
4766
4782
|
function normalizeApiUrl(value) {
|
|
4767
4783
|
const trimmed = value?.trim();
|
|
4768
4784
|
if (!trimmed)
|
|
@@ -7093,7 +7109,7 @@ var init_shared_events = __esm(() => {
|
|
|
7093
7109
|
});
|
|
7094
7110
|
|
|
7095
7111
|
// src/lib/secret-redaction.ts
|
|
7096
|
-
import { readFileSync as
|
|
7112
|
+
import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
|
|
7097
7113
|
function registerCustomRedactor(fn) {
|
|
7098
7114
|
customRedactors.push(fn);
|
|
7099
7115
|
}
|
|
@@ -7160,7 +7176,7 @@ function scanAndRedactText(text, options = {}) {
|
|
|
7160
7176
|
function scanFileForSecrets(path, options = {}) {
|
|
7161
7177
|
if (!existsSync6(path))
|
|
7162
7178
|
throw new Error(`File not found: ${path}`);
|
|
7163
|
-
const content =
|
|
7179
|
+
const content = readFileSync3(path, "utf8");
|
|
7164
7180
|
return scanAndRedactText(content, options);
|
|
7165
7181
|
}
|
|
7166
7182
|
function safeStringify(value, space) {
|
|
@@ -8606,7 +8622,7 @@ function requireCanonicalLockVersion(value) {
|
|
|
8606
8622
|
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" });
|
|
8607
8623
|
}
|
|
8608
8624
|
const parsed = Date.parse(value);
|
|
8609
|
-
if (Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
8625
|
+
if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
8610
8626
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
|
|
8611
8627
|
}
|
|
8612
8628
|
return value;
|
|
@@ -8631,9 +8647,6 @@ function prepareStaleLockHandoff(input, options = {}) {
|
|
|
8631
8647
|
if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
|
|
8632
8648
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
|
|
8633
8649
|
}
|
|
8634
|
-
if (canonicalAgentRef(expectedHolder) === canonicalAgentRef(newHolder)) {
|
|
8635
|
-
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "new_holder must differ from expected_holder", { field: "new_holder" });
|
|
8636
|
-
}
|
|
8637
8650
|
const operationTimestamp = options.now ?? new Date().toISOString();
|
|
8638
8651
|
if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
|
|
8639
8652
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
|
|
@@ -11211,7 +11224,7 @@ var init_boards = __esm(() => {
|
|
|
11211
11224
|
|
|
11212
11225
|
// src/lib/artifact-store.ts
|
|
11213
11226
|
import { createHash as createHash3 } from "crypto";
|
|
11214
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
11227
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
11215
11228
|
import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
|
|
11216
11229
|
import { tmpdir as tmpdir2 } from "os";
|
|
11217
11230
|
function isInMemoryDb2(path) {
|
|
@@ -11278,7 +11291,7 @@ function storeArtifactContent(input) {
|
|
|
11278
11291
|
const sourceStat = statSync2(sourcePath);
|
|
11279
11292
|
if (!sourceStat.isFile())
|
|
11280
11293
|
throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
|
|
11281
|
-
const sourceBuffer =
|
|
11294
|
+
const sourceBuffer = readFileSync4(sourcePath);
|
|
11282
11295
|
const sourceSha = sha256(sourceBuffer);
|
|
11283
11296
|
const textLike = isTextLike(sourceBuffer, input.path);
|
|
11284
11297
|
let storedBuffer = sourceBuffer;
|
|
@@ -11365,7 +11378,7 @@ function verifyStoredArtifact(input) {
|
|
|
11365
11378
|
message: "stored artifact content is missing"
|
|
11366
11379
|
};
|
|
11367
11380
|
}
|
|
11368
|
-
const buffer =
|
|
11381
|
+
const buffer = readFileSync4(storedPath);
|
|
11369
11382
|
const actualSha = sha256(buffer);
|
|
11370
11383
|
const actualSize = buffer.length;
|
|
11371
11384
|
const ok = actualSha === store.sha256 && actualSize === store.size_bytes;
|
|
@@ -11385,7 +11398,7 @@ function exportStoredArtifactContent(input) {
|
|
|
11385
11398
|
const report = verifyStoredArtifact(input);
|
|
11386
11399
|
if (report.status !== "ok" || !report.relative_path || !report.actual_sha256 || report.actual_size_bytes === null)
|
|
11387
11400
|
return null;
|
|
11388
|
-
const content =
|
|
11401
|
+
const content = readFileSync4(artifactStorePath(report.relative_path));
|
|
11389
11402
|
return {
|
|
11390
11403
|
artifact_id: input.id,
|
|
11391
11404
|
sha256: report.actual_sha256,
|
|
@@ -11436,7 +11449,7 @@ function getArtifactStoreRoot(dbPath) {
|
|
|
11436
11449
|
return join5(dirname4(resolve7(path)), "artifacts");
|
|
11437
11450
|
}
|
|
11438
11451
|
function computeContentHash(path) {
|
|
11439
|
-
return sha256(
|
|
11452
|
+
return sha256(readFileSync4(resolve7(path)));
|
|
11440
11453
|
}
|
|
11441
11454
|
function storeArtifactFile(input) {
|
|
11442
11455
|
const sourcePath = resolve7(input.sourcePath);
|
|
@@ -11446,7 +11459,7 @@ function storeArtifactFile(input) {
|
|
|
11446
11459
|
if (!statSync2(sourcePath).isFile()) {
|
|
11447
11460
|
throw new Error(`Source path is not a file: ${input.sourcePath}`);
|
|
11448
11461
|
}
|
|
11449
|
-
const buffer =
|
|
11462
|
+
const buffer = readFileSync4(sourcePath);
|
|
11450
11463
|
const contentHash = sha256(buffer);
|
|
11451
11464
|
const mimeType = mediaTypeFor(sourcePath, isTextLike(buffer, sourcePath));
|
|
11452
11465
|
const storageMode = input.storageMode ?? "copy";
|
|
@@ -15040,6 +15053,32 @@ function assertSafeIdentifier(value) {
|
|
|
15040
15053
|
|
|
15041
15054
|
// src/storage/postgres-adapter.ts
|
|
15042
15055
|
init_redaction();
|
|
15056
|
+
|
|
15057
|
+
// src/task-manifest/canonical.ts
|
|
15058
|
+
import { createHash as createHash5 } from "crypto";
|
|
15059
|
+
function canonicalize2(value) {
|
|
15060
|
+
if (Array.isArray(value))
|
|
15061
|
+
return value.map(canonicalize2);
|
|
15062
|
+
if (value !== null && typeof value === "object") {
|
|
15063
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize2(entry2)]));
|
|
15064
|
+
}
|
|
15065
|
+
return value;
|
|
15066
|
+
}
|
|
15067
|
+
function canonicalJson(value) {
|
|
15068
|
+
return JSON.stringify(canonicalize2(value));
|
|
15069
|
+
}
|
|
15070
|
+
function canonicalDigest(value) {
|
|
15071
|
+
return createHash5("sha256").update(canonicalJson(value)).digest("hex");
|
|
15072
|
+
}
|
|
15073
|
+
function deterministicUuid(namespace, ...parts) {
|
|
15074
|
+
const bytes = createHash5("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
15075
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
15076
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
15077
|
+
const hex = bytes.toString("hex");
|
|
15078
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
15079
|
+
}
|
|
15080
|
+
|
|
15081
|
+
// src/storage/postgres-adapter.ts
|
|
15043
15082
|
function createPostgresTodosStorageAdapter(options) {
|
|
15044
15083
|
const store = new PostgresJsonRecordStore(options);
|
|
15045
15084
|
const adapter = {
|
|
@@ -15263,7 +15302,7 @@ class PostgresJsonRecordStore {
|
|
|
15263
15302
|
AND task_record.deleted_at IS NULL
|
|
15264
15303
|
AND target.payload->>'locked_by' = $3
|
|
15265
15304
|
AND target.payload->>'locked_at' = $4
|
|
15266
|
-
AND
|
|
15305
|
+
AND $4::timestamptz < $5::timestamptz
|
|
15267
15306
|
AND COALESCE(target.payload->>'status', '') NOT IN ('completed', 'failed', 'cancelled')
|
|
15268
15307
|
RETURNING task_record.payload
|
|
15269
15308
|
),
|
|
@@ -16706,16 +16745,17 @@ async function findCommit(sha, store) {
|
|
|
16706
16745
|
async function addGitRef(input, store, context) {
|
|
16707
16746
|
if (!await store.get("tasks", input.task_id))
|
|
16708
16747
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
16748
|
+
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];
|
|
16709
16749
|
const timestamp2 = new Date().toISOString();
|
|
16710
16750
|
const gitRef = {
|
|
16711
|
-
id:
|
|
16751
|
+
id: existing?.id ?? deterministicUuid("todos:git-ref:v1", input.task_id, input.ref_type, input.name),
|
|
16712
16752
|
task_id: input.task_id,
|
|
16713
16753
|
ref_type: input.ref_type,
|
|
16714
16754
|
name: input.name,
|
|
16715
|
-
url: input.url ?? null,
|
|
16716
|
-
provider: input.provider ?? null,
|
|
16755
|
+
url: input.url ?? existing?.url ?? null,
|
|
16756
|
+
provider: input.provider ?? existing?.provider ?? null,
|
|
16717
16757
|
metadata: input.metadata ?? {},
|
|
16718
|
-
created_at: timestamp2,
|
|
16758
|
+
created_at: existing?.created_at ?? timestamp2,
|
|
16719
16759
|
updated_at: timestamp2
|
|
16720
16760
|
};
|
|
16721
16761
|
await store.upsert("refs", gitRef, context);
|
|
@@ -18226,7 +18266,7 @@ function isCommentRedactionBackfillComplete(result) {
|
|
|
18226
18266
|
return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
|
|
18227
18267
|
}
|
|
18228
18268
|
// src/storage/s3-artifacts.ts
|
|
18229
|
-
import { createHash as
|
|
18269
|
+
import { createHash as createHash6, createHmac as createHmac2 } from "crypto";
|
|
18230
18270
|
function createTodosS3ArtifactStore(options) {
|
|
18231
18271
|
const requestFetch = options.fetch ?? fetch;
|
|
18232
18272
|
const now3 = options.now ?? (() => new Date);
|
|
@@ -18398,7 +18438,7 @@ function toAmzDate(date) {
|
|
|
18398
18438
|
return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
18399
18439
|
}
|
|
18400
18440
|
function sha256Hex(value) {
|
|
18401
|
-
return
|
|
18441
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
18402
18442
|
}
|
|
18403
18443
|
function hmac(key, value) {
|
|
18404
18444
|
return createHmac2("sha256", key).update(value).digest();
|
package/dist/task-manifest.js
CHANGED
|
@@ -421,7 +421,7 @@ var init_sync_utils = __esm(() => {
|
|
|
421
421
|
});
|
|
422
422
|
|
|
423
423
|
// src/lib/config.ts
|
|
424
|
-
import { existsSync as existsSync2 } from "fs";
|
|
424
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
425
425
|
import { dirname, join as join2 } from "path";
|
|
426
426
|
function getConfigPath() {
|
|
427
427
|
return join2(getTodosGlobalDir(), "config.json");
|
|
@@ -453,6 +453,22 @@ function saveConfig(config) {
|
|
|
453
453
|
function updateConfig(patch) {
|
|
454
454
|
return saveConfig({ ...loadConfig(), ...patch });
|
|
455
455
|
}
|
|
456
|
+
function getTodosAiConfig() {
|
|
457
|
+
const configPath = getConfigPath();
|
|
458
|
+
if (!existsSync2(configPath))
|
|
459
|
+
return {};
|
|
460
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
461
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
462
|
+
throw new Error("Todos config must be a JSON object");
|
|
463
|
+
}
|
|
464
|
+
const ai = parsed["ai"];
|
|
465
|
+
if (ai === undefined)
|
|
466
|
+
return {};
|
|
467
|
+
if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
|
|
468
|
+
throw new Error("Todos AI configuration must be an object");
|
|
469
|
+
}
|
|
470
|
+
return { ...ai };
|
|
471
|
+
}
|
|
456
472
|
function normalizeApiUrl(value) {
|
|
457
473
|
const trimmed = value?.trim();
|
|
458
474
|
if (!trimmed)
|
|
@@ -627,7 +643,7 @@ var init_redaction = __esm(() => {
|
|
|
627
643
|
});
|
|
628
644
|
|
|
629
645
|
// src/lib/secret-redaction.ts
|
|
630
|
-
import { readFileSync as
|
|
646
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
631
647
|
function registerCustomRedactor(fn) {
|
|
632
648
|
customRedactors.push(fn);
|
|
633
649
|
}
|
|
@@ -694,7 +710,7 @@ function scanAndRedactText(text, options = {}) {
|
|
|
694
710
|
function scanFileForSecrets(path, options = {}) {
|
|
695
711
|
if (!existsSync3(path))
|
|
696
712
|
throw new Error(`File not found: ${path}`);
|
|
697
|
-
const content =
|
|
713
|
+
const content = readFileSync3(path, "utf8");
|
|
698
714
|
return scanAndRedactText(content, options);
|
|
699
715
|
}
|
|
700
716
|
function safeStringify(value, space) {
|