@hasna/todos 0.15.20 → 0.15.25
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 +2862 -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 +641 -18
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3148 -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 +130 -72
- package/dist/mcp.js +4 -1
- package/dist/project-registration.js +70 -29
- package/dist/registry.js +606 -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 +136 -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 +4 -1
package/dist/mcp.js
CHANGED
|
@@ -41,7 +41,7 @@ var __require = import.meta.require;
|
|
|
41
41
|
// package.json
|
|
42
42
|
var package_default = {
|
|
43
43
|
name: "@hasna/todos",
|
|
44
|
-
version: "0.15.
|
|
44
|
+
version: "0.15.25",
|
|
45
45
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
46
46
|
type: "module",
|
|
47
47
|
main: "dist/index.js",
|
|
@@ -113,6 +113,8 @@ var package_default = {
|
|
|
113
113
|
"dev:serve": "bun run src/server/index.ts",
|
|
114
114
|
"verify:release": "bun run scripts/verify-public-release.ts --mode=review",
|
|
115
115
|
"verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
|
|
116
|
+
"verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
|
|
117
|
+
"test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
|
|
116
118
|
"issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
|
|
117
119
|
prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
|
|
118
120
|
postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
|
|
@@ -165,6 +167,7 @@ var package_default = {
|
|
|
165
167
|
"@types/bun": "^1.2.4",
|
|
166
168
|
"@types/react": "^18.3.18",
|
|
167
169
|
"bun-types": "1.3.9",
|
|
170
|
+
"hasna-deployment-contracts": "npm:@hasna/contracts@0.10.4",
|
|
168
171
|
typescript: "^5.7.3"
|
|
169
172
|
}
|
|
170
173
|
};
|
|
@@ -429,7 +429,7 @@ var init_creator_identity = __esm(() => {
|
|
|
429
429
|
});
|
|
430
430
|
|
|
431
431
|
// src/lib/config.ts
|
|
432
|
-
import { existsSync as existsSync2 } from "fs";
|
|
432
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
433
433
|
import { dirname, join as join2 } from "path";
|
|
434
434
|
function getConfigPath() {
|
|
435
435
|
return join2(getTodosGlobalDir(), "config.json");
|
|
@@ -461,6 +461,22 @@ function saveConfig(config) {
|
|
|
461
461
|
function updateConfig(patch) {
|
|
462
462
|
return saveConfig({ ...loadConfig(), ...patch });
|
|
463
463
|
}
|
|
464
|
+
function getTodosAiConfig() {
|
|
465
|
+
const configPath = getConfigPath();
|
|
466
|
+
if (!existsSync2(configPath))
|
|
467
|
+
return {};
|
|
468
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
469
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
470
|
+
throw new Error("Todos config must be a JSON object");
|
|
471
|
+
}
|
|
472
|
+
const ai = parsed["ai"];
|
|
473
|
+
if (ai === undefined)
|
|
474
|
+
return {};
|
|
475
|
+
if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
|
|
476
|
+
throw new Error("Todos AI configuration must be an object");
|
|
477
|
+
}
|
|
478
|
+
return { ...ai };
|
|
479
|
+
}
|
|
464
480
|
function normalizeApiUrl(value) {
|
|
465
481
|
const trimmed = value?.trim();
|
|
466
482
|
if (!trimmed)
|
|
@@ -635,7 +651,7 @@ var init_redaction = __esm(() => {
|
|
|
635
651
|
});
|
|
636
652
|
|
|
637
653
|
// src/lib/secret-redaction.ts
|
|
638
|
-
import { readFileSync as
|
|
654
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
639
655
|
function registerCustomRedactor(fn) {
|
|
640
656
|
customRedactors.push(fn);
|
|
641
657
|
}
|
|
@@ -702,7 +718,7 @@ function scanAndRedactText(text, options = {}) {
|
|
|
702
718
|
function scanFileForSecrets(path, options = {}) {
|
|
703
719
|
if (!existsSync3(path))
|
|
704
720
|
throw new Error(`File not found: ${path}`);
|
|
705
|
-
const content =
|
|
721
|
+
const content = readFileSync3(path, "utf8");
|
|
706
722
|
return scanAndRedactText(content, options);
|
|
707
723
|
}
|
|
708
724
|
function safeStringify(value, space) {
|
|
@@ -885,7 +901,7 @@ function requireCanonicalLockVersion(value) {
|
|
|
885
901
|
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" });
|
|
886
902
|
}
|
|
887
903
|
const parsed = Date.parse(value);
|
|
888
|
-
if (Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
904
|
+
if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
|
|
889
905
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
|
|
890
906
|
}
|
|
891
907
|
return value;
|
|
@@ -910,9 +926,6 @@ function prepareStaleLockHandoff(input, options = {}) {
|
|
|
910
926
|
if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
|
|
911
927
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
|
|
912
928
|
}
|
|
913
|
-
if (canonicalAgentRef(expectedHolder) === canonicalAgentRef(newHolder)) {
|
|
914
|
-
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "new_holder must differ from expected_holder", { field: "new_holder" });
|
|
915
|
-
}
|
|
916
929
|
const operationTimestamp = options.now ?? new Date().toISOString();
|
|
917
930
|
if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
|
|
918
931
|
throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
|
|
@@ -6215,7 +6228,7 @@ var init_runner_sandbox = __esm(() => {
|
|
|
6215
6228
|
});
|
|
6216
6229
|
|
|
6217
6230
|
// src/lib/event-hooks.ts
|
|
6218
|
-
import { createHash as
|
|
6231
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
6219
6232
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6220
6233
|
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
6221
6234
|
import { createConnection } from "net";
|
|
@@ -6282,7 +6295,7 @@ function buildEnvelope(type, payload, timestamp2 = new Date().toISOString()) {
|
|
|
6282
6295
|
payload: redactValue(payload ?? {}),
|
|
6283
6296
|
source: { package: "@hasna/todos", local_only: true }
|
|
6284
6297
|
};
|
|
6285
|
-
const digest =
|
|
6298
|
+
const digest = createHash4("sha256").update(canonicalEvent(base)).digest("hex");
|
|
6286
6299
|
return { ...base, integrity: { algorithm: "sha256", digest } };
|
|
6287
6300
|
}
|
|
6288
6301
|
function summarize(value) {
|
|
@@ -11210,8 +11223,8 @@ var init_boards = __esm(() => {
|
|
|
11210
11223
|
});
|
|
11211
11224
|
|
|
11212
11225
|
// src/lib/artifact-store.ts
|
|
11213
|
-
import { createHash as
|
|
11214
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
11226
|
+
import { createHash as createHash5 } from "crypto";
|
|
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) {
|
|
@@ -11235,7 +11248,7 @@ function artifactStorePath(relativePath) {
|
|
|
11235
11248
|
return join5(artifactStoreRoot(), normalized);
|
|
11236
11249
|
}
|
|
11237
11250
|
function sha256(buffer) {
|
|
11238
|
-
return
|
|
11251
|
+
return createHash5("sha256").update(buffer).digest("hex");
|
|
11239
11252
|
}
|
|
11240
11253
|
function isTextLike(buffer, path) {
|
|
11241
11254
|
if (buffer.includes(0))
|
|
@@ -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";
|
|
@@ -12644,11 +12657,11 @@ var init_tasks = __esm(() => {
|
|
|
12644
12657
|
});
|
|
12645
12658
|
|
|
12646
12659
|
// src/project-registration/authority.ts
|
|
12647
|
-
import { createHash as
|
|
12660
|
+
import { createHash as createHash6 } from "crypto";
|
|
12648
12661
|
// package.json
|
|
12649
12662
|
var package_default = {
|
|
12650
12663
|
name: "@hasna/todos",
|
|
12651
|
-
version: "0.15.
|
|
12664
|
+
version: "0.15.25",
|
|
12652
12665
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
12653
12666
|
type: "module",
|
|
12654
12667
|
main: "dist/index.js",
|
|
@@ -12720,6 +12733,8 @@ var package_default = {
|
|
|
12720
12733
|
"dev:serve": "bun run src/server/index.ts",
|
|
12721
12734
|
"verify:release": "bun run scripts/verify-public-release.ts --mode=review",
|
|
12722
12735
|
"verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
|
|
12736
|
+
"verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
|
|
12737
|
+
"test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
|
|
12723
12738
|
"issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
|
|
12724
12739
|
prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
|
|
12725
12740
|
postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
|
|
@@ -12772,6 +12787,7 @@ var package_default = {
|
|
|
12772
12787
|
"@types/bun": "^1.2.4",
|
|
12773
12788
|
"@types/react": "^18.3.18",
|
|
12774
12789
|
"bun-types": "1.3.9",
|
|
12790
|
+
"hasna-deployment-contracts": "npm:@hasna/contracts@0.10.4",
|
|
12775
12791
|
typescript: "^5.7.3"
|
|
12776
12792
|
}
|
|
12777
12793
|
};
|
|
@@ -13523,6 +13539,30 @@ function forbiddenAuditHistoryTombstoneError(id) {
|
|
|
13523
13539
|
return `${AUDIT_HISTORY_TOMBSTONE_FORBIDDEN}: audit_history tombstone ${id} is not allowed`;
|
|
13524
13540
|
}
|
|
13525
13541
|
|
|
13542
|
+
// src/task-manifest/canonical.ts
|
|
13543
|
+
import { createHash as createHash3 } from "crypto";
|
|
13544
|
+
function canonicalize2(value) {
|
|
13545
|
+
if (Array.isArray(value))
|
|
13546
|
+
return value.map(canonicalize2);
|
|
13547
|
+
if (value !== null && typeof value === "object") {
|
|
13548
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize2(entry2)]));
|
|
13549
|
+
}
|
|
13550
|
+
return value;
|
|
13551
|
+
}
|
|
13552
|
+
function canonicalJson(value) {
|
|
13553
|
+
return JSON.stringify(canonicalize2(value));
|
|
13554
|
+
}
|
|
13555
|
+
function canonicalDigest(value) {
|
|
13556
|
+
return createHash3("sha256").update(canonicalJson(value)).digest("hex");
|
|
13557
|
+
}
|
|
13558
|
+
function deterministicUuid(namespace, ...parts) {
|
|
13559
|
+
const bytes = createHash3("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
|
|
13560
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
13561
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
13562
|
+
const hex = bytes.toString("hex");
|
|
13563
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
13564
|
+
}
|
|
13565
|
+
|
|
13526
13566
|
// src/storage/postgres-adapter.ts
|
|
13527
13567
|
function createPostgresTodosStorageAdapter(options) {
|
|
13528
13568
|
const store = new PostgresJsonRecordStore(options);
|
|
@@ -13747,7 +13787,7 @@ class PostgresJsonRecordStore {
|
|
|
13747
13787
|
AND task_record.deleted_at IS NULL
|
|
13748
13788
|
AND target.payload->>'locked_by' = $3
|
|
13749
13789
|
AND target.payload->>'locked_at' = $4
|
|
13750
|
-
AND
|
|
13790
|
+
AND $4::timestamptz < $5::timestamptz
|
|
13751
13791
|
AND COALESCE(target.payload->>'status', '') NOT IN ('completed', 'failed', 'cancelled')
|
|
13752
13792
|
RETURNING task_record.payload
|
|
13753
13793
|
),
|
|
@@ -15190,16 +15230,17 @@ async function findCommit(sha, store) {
|
|
|
15190
15230
|
async function addGitRef(input, store, context) {
|
|
15191
15231
|
if (!await store.get("tasks", input.task_id))
|
|
15192
15232
|
throw new Error(`Task not found: ${input.task_id}`);
|
|
15233
|
+
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];
|
|
15193
15234
|
const timestamp = new Date().toISOString();
|
|
15194
15235
|
const gitRef = {
|
|
15195
|
-
id:
|
|
15236
|
+
id: existing?.id ?? deterministicUuid("todos:git-ref:v1", input.task_id, input.ref_type, input.name),
|
|
15196
15237
|
task_id: input.task_id,
|
|
15197
15238
|
ref_type: input.ref_type,
|
|
15198
15239
|
name: input.name,
|
|
15199
|
-
url: input.url ?? null,
|
|
15200
|
-
provider: input.provider ?? null,
|
|
15240
|
+
url: input.url ?? existing?.url ?? null,
|
|
15241
|
+
provider: input.provider ?? existing?.provider ?? null,
|
|
15201
15242
|
metadata: input.metadata ?? {},
|
|
15202
|
-
created_at: timestamp,
|
|
15243
|
+
created_at: existing?.created_at ?? timestamp,
|
|
15203
15244
|
updated_at: timestamp
|
|
15204
15245
|
};
|
|
15205
15246
|
await store.upsert("refs", gitRef, context);
|
|
@@ -18477,23 +18518,23 @@ class WriteBoundaryError extends Error {
|
|
|
18477
18518
|
}
|
|
18478
18519
|
}
|
|
18479
18520
|
function canonicalProjectRegistrationJson(value) {
|
|
18480
|
-
return JSON.stringify(
|
|
18521
|
+
return JSON.stringify(canonicalize3(value));
|
|
18481
18522
|
}
|
|
18482
|
-
function
|
|
18523
|
+
function canonicalize3(value) {
|
|
18483
18524
|
if (Array.isArray(value))
|
|
18484
|
-
return value.map(
|
|
18525
|
+
return value.map(canonicalize3);
|
|
18485
18526
|
if (!value || typeof value !== "object")
|
|
18486
18527
|
return value;
|
|
18487
18528
|
const out = {};
|
|
18488
18529
|
for (const key of Object.keys(value).sort()) {
|
|
18489
18530
|
const entry2 = value[key];
|
|
18490
18531
|
if (entry2 !== undefined)
|
|
18491
|
-
out[key] =
|
|
18532
|
+
out[key] = canonicalize3(entry2);
|
|
18492
18533
|
}
|
|
18493
18534
|
return out;
|
|
18494
18535
|
}
|
|
18495
18536
|
function digestProjectRegistrationValue(value) {
|
|
18496
|
-
return
|
|
18537
|
+
return createHash6("sha256").update(canonicalProjectRegistrationJson(value)).digest("hex");
|
|
18497
18538
|
}
|
|
18498
18539
|
function deriveTodosProjectRegistrationIdempotencyKey(input) {
|
|
18499
18540
|
return `prk_${digestProjectRegistrationValue({
|