@hasna/todos 0.15.18 → 0.15.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/index.js +457 -77
- package/dist/contracts.js +57 -2
- package/dist/db/plans.d.ts +4 -0
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/index.js +122 -8
- package/dist/mcp/index.js +254 -12
- package/dist/mcp.js +1 -1
- package/dist/project-registration.js +113 -8
- package/dist/registry.js +57 -2
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/v1.generated.d.ts +13 -1
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +254 -12
- package/dist/server/openapi.d.ts +50 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/interfaces.d.ts +10 -0
- 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/shadow.d.ts.map +1 -1
- package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
- package/dist/storage.js +121 -7
- package/dist/task-manifest.js +14 -1
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2123,7 +2123,7 @@ var package_default;
|
|
|
2123
2123
|
var init_package = __esm(() => {
|
|
2124
2124
|
package_default = {
|
|
2125
2125
|
name: "@hasna/todos",
|
|
2126
|
-
version: "0.15.
|
|
2126
|
+
version: "0.15.19",
|
|
2127
2127
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
2128
2128
|
type: "module",
|
|
2129
2129
|
main: "dist/index.js",
|
|
@@ -2269,7 +2269,7 @@ function isBlockingDependencyStatus(status) {
|
|
|
2269
2269
|
function isTerminalStatus(status) {
|
|
2270
2270
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
2271
2271
|
}
|
|
2272
|
-
var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
|
|
2272
|
+
var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanRevisionConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
|
|
2273
2273
|
var init_types = __esm(() => {
|
|
2274
2274
|
TASK_STATUSES = [
|
|
2275
2275
|
"pending",
|
|
@@ -2357,6 +2357,19 @@ var init_types = __esm(() => {
|
|
|
2357
2357
|
this.name = "ResourceConflictError";
|
|
2358
2358
|
}
|
|
2359
2359
|
};
|
|
2360
|
+
PlanRevisionConflictError = class PlanRevisionConflictError extends Error {
|
|
2361
|
+
planId;
|
|
2362
|
+
expectedUpdatedAt;
|
|
2363
|
+
currentUpdatedAt;
|
|
2364
|
+
static code = "PLAN_REVISION_CONFLICT";
|
|
2365
|
+
constructor(planId, expectedUpdatedAt, currentUpdatedAt) {
|
|
2366
|
+
super(`Plan revision conflict for ${planId}: expected ${expectedUpdatedAt}, current ${currentUpdatedAt}`);
|
|
2367
|
+
this.planId = planId;
|
|
2368
|
+
this.expectedUpdatedAt = expectedUpdatedAt;
|
|
2369
|
+
this.currentUpdatedAt = currentUpdatedAt;
|
|
2370
|
+
this.name = "PlanRevisionConflictError";
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2360
2373
|
PlanNotFoundError = class PlanNotFoundError extends Error {
|
|
2361
2374
|
planId;
|
|
2362
2375
|
static code = "PLAN_NOT_FOUND";
|
|
@@ -6004,8 +6017,16 @@ async function cloudTaskAction(client, id, action, body = {}) {
|
|
|
6004
6017
|
const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/${action}`, body);
|
|
6005
6018
|
return unwrapTask(raw);
|
|
6006
6019
|
}
|
|
6020
|
+
function isRemoteTaskIdentity(value) {
|
|
6021
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6022
|
+
return false;
|
|
6023
|
+
const id = value["id"];
|
|
6024
|
+
return typeof id === "string" && id.length > 0;
|
|
6025
|
+
}
|
|
6007
6026
|
async function cloudFailTask(client, id, body = {}) {
|
|
6008
6027
|
const route = `/v1/tasks/${encodeURIComponent(id)}/fail`;
|
|
6028
|
+
if (body.retry === true)
|
|
6029
|
+
await requireRetryCapability(client);
|
|
6009
6030
|
const raw = await requiredRemoteRoute(client, route, () => client.transport.post(`/tasks/${encodeURIComponent(id)}/fail`, body));
|
|
6010
6031
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6011
6032
|
throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} returned an invalid failure response envelope`);
|
|
@@ -6016,7 +6037,7 @@ async function cloudFailTask(client, id, body = {}) {
|
|
|
6016
6037
|
}
|
|
6017
6038
|
const task = result["task"];
|
|
6018
6039
|
const retryTask = result["retryTask"];
|
|
6019
|
-
if (!task ||
|
|
6040
|
+
if (!isRemoteTaskIdentity(task) || body.retry === true && !isRemoteTaskIdentity(retryTask) || body.retry !== true && retryTask !== undefined && !isRemoteTaskIdentity(retryTask)) {
|
|
6020
6041
|
throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} returned an invalid failure result`);
|
|
6021
6042
|
}
|
|
6022
6043
|
return result;
|
|
@@ -6039,6 +6060,36 @@ function resolveOpenApiSchema(document, schema) {
|
|
|
6039
6060
|
}
|
|
6040
6061
|
return current && typeof current === "object" && !Array.isArray(current) ? current : null;
|
|
6041
6062
|
}
|
|
6063
|
+
async function fetchRetryCapability(client) {
|
|
6064
|
+
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
6065
|
+
if (!document || typeof document !== "object" || Array.isArray(document))
|
|
6066
|
+
return false;
|
|
6067
|
+
const doc = document;
|
|
6068
|
+
const paths = doc["paths"];
|
|
6069
|
+
const failPath = paths && typeof paths === "object" && !Array.isArray(paths) ? paths["/v1/tasks/{id}/fail"] : undefined;
|
|
6070
|
+
const post = failPath && typeof failPath === "object" && !Array.isArray(failPath) ? failPath["post"] : undefined;
|
|
6071
|
+
const requestBody = post && typeof post === "object" && !Array.isArray(post) ? post["requestBody"] : undefined;
|
|
6072
|
+
const content = requestBody && typeof requestBody === "object" && !Array.isArray(requestBody) ? requestBody["content"] : undefined;
|
|
6073
|
+
const jsonContent = content && typeof content === "object" && !Array.isArray(content) ? content["application/json"] : undefined;
|
|
6074
|
+
const schema = jsonContent && typeof jsonContent === "object" && !Array.isArray(jsonContent) ? jsonContent["schema"] : undefined;
|
|
6075
|
+
const resolved = resolveOpenApiSchema(document, schema);
|
|
6076
|
+
const properties = resolved?.["properties"];
|
|
6077
|
+
if (!properties || typeof properties !== "object" || Array.isArray(properties))
|
|
6078
|
+
return false;
|
|
6079
|
+
const retry = properties["retry"];
|
|
6080
|
+
return !!retry && typeof retry === "object" && !Array.isArray(retry) && retry["type"] === "boolean";
|
|
6081
|
+
}
|
|
6082
|
+
async function requireRetryCapability(client) {
|
|
6083
|
+
const authority = remoteAuthorityBase(client);
|
|
6084
|
+
let capability = retryCapabilityCache.get(authority);
|
|
6085
|
+
if (!capability) {
|
|
6086
|
+
capability = fetchRetryCapability(client);
|
|
6087
|
+
retryCapabilityCache.set(authority, capability);
|
|
6088
|
+
}
|
|
6089
|
+
if (!await capability) {
|
|
6090
|
+
throw new Error(`REMOTE_RETRY_UNSUPPORTED: configured Todos authority ${authority} does not advertise retry in the ` + "application/json request schema for POST /v1/tasks/{id}/fail; no failure mutation was sent; " + "deploy a compatible @hasna/todos /v1 server before retrying; local SQLite fallback is disabled");
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6042
6093
|
async function fetchCompletionCapabilities(client) {
|
|
6043
6094
|
const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
|
|
6044
6095
|
if (!document || typeof document !== "object" || Array.isArray(document))
|
|
@@ -6296,11 +6347,62 @@ function unwrapPlan(raw) {
|
|
|
6296
6347
|
}
|
|
6297
6348
|
return raw;
|
|
6298
6349
|
}
|
|
6350
|
+
async function cloudGetPlanById(client, id) {
|
|
6351
|
+
const raw = await client.get("plans", id);
|
|
6352
|
+
return raw ? unwrapPlan(raw) : null;
|
|
6353
|
+
}
|
|
6354
|
+
function isUnavailablePlanPatchRoute(error) {
|
|
6355
|
+
if (!error || typeof error !== "object" || error.status !== 404)
|
|
6356
|
+
return false;
|
|
6357
|
+
const body = error.body;
|
|
6358
|
+
const remoteMessage = body && typeof body === "object" && !Array.isArray(body) ? body.error : undefined;
|
|
6359
|
+
return remoteMessage === "unknown /v1 resource: plans";
|
|
6360
|
+
}
|
|
6361
|
+
function isExactPlanCompletionPatch(patch) {
|
|
6362
|
+
const entries = Object.entries(patch).filter(([, value]) => value !== undefined);
|
|
6363
|
+
return entries.length === 1 && entries[0]?.[0] === "status" && entries[0]?.[1] === "completed";
|
|
6364
|
+
}
|
|
6299
6365
|
async function cloudCreatePlan(client, input) {
|
|
6300
6366
|
return unwrapPlan(await requiredRemoteRoute(client, "/v1/plans", () => client.create("plans", input)));
|
|
6301
6367
|
}
|
|
6302
6368
|
async function cloudUpdatePlan(client, id, patch) {
|
|
6303
|
-
|
|
6369
|
+
try {
|
|
6370
|
+
return unwrapPlan(await client.update("plans", id, patch));
|
|
6371
|
+
} catch (error) {
|
|
6372
|
+
if (!isExactPlanCompletionPatch(patch) || !isUnavailablePlanPatchRoute(error))
|
|
6373
|
+
throw error;
|
|
6374
|
+
const existing = await cloudGetPlanById(client, id);
|
|
6375
|
+
if (!existing)
|
|
6376
|
+
throw error;
|
|
6377
|
+
if (existing.status === "completed")
|
|
6378
|
+
return existing;
|
|
6379
|
+
const imported = await requiredRemoteRoute(client, "/v1/import", () => client.transport.post("/import", {
|
|
6380
|
+
source: "postgres",
|
|
6381
|
+
planCompletions: [{
|
|
6382
|
+
id,
|
|
6383
|
+
expected_updated_at: existing.updated_at,
|
|
6384
|
+
status: "completed"
|
|
6385
|
+
}]
|
|
6386
|
+
}));
|
|
6387
|
+
const envelope = imported && typeof imported === "object" && !Array.isArray(imported) ? imported : null;
|
|
6388
|
+
const completion = envelope?.planCompletions?.[0];
|
|
6389
|
+
if (envelope?.received !== 1 || !envelope.result || !Array.isArray(envelope.result.errors) || envelope.result.errors.length > 0 || envelope.planCompletions?.length !== 1 || completion?.id !== id || completion.status !== "completed" || completion.expected_updated_at !== existing.updated_at || typeof completion.result_updated_at !== "string" || typeof completion.applied !== "boolean") {
|
|
6390
|
+
throw new Error("REMOTE_API_INCOMPATIBLE: /v1/import did not confirm one atomic plan completion; " + "local SQLite fallback is disabled");
|
|
6391
|
+
}
|
|
6392
|
+
const persisted = await cloudGetPlanById(client, id);
|
|
6393
|
+
if (!persisted) {
|
|
6394
|
+
throw new Error(`REMOTE_API_INCOMPATIBLE: /v1/import acknowledged plan completion ${id} but authoritative readback returned no plan; ` + "local SQLite fallback is disabled");
|
|
6395
|
+
}
|
|
6396
|
+
if (persisted.status !== "completed" || persisted.updated_at !== completion.result_updated_at) {
|
|
6397
|
+
throw new Error(`REMOTE_API_INCOMPATIBLE: /v1/import completion readback did not match its receipt for plan ${id}; ` + "local SQLite fallback is disabled");
|
|
6398
|
+
}
|
|
6399
|
+
for (const key of PLAN_COMPLETION_PROTECTED_FIELDS) {
|
|
6400
|
+
if (persisted[key] !== existing[key]) {
|
|
6401
|
+
throw new Error(`REMOTE_PLAN_COMPLETION_CONFLICT: /v1/import completion changed protected plan field ${key}; ` + "local SQLite fallback is disabled");
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
return persisted;
|
|
6405
|
+
}
|
|
6304
6406
|
}
|
|
6305
6407
|
async function cloudDeletePlan(client, id) {
|
|
6306
6408
|
try {
|
|
@@ -7043,7 +7145,7 @@ async function cloudTimeline(client, options = {}) {
|
|
|
7043
7145
|
const limit = options.limit ?? 50;
|
|
7044
7146
|
return { entries: entries.slice(offset, offset + limit), total, limit, offset };
|
|
7045
7147
|
}
|
|
7046
|
-
var UUID_RE, TRANSPORT_TOKENS, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, gitRefCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache, RELATION_HYDRATION_CONCURRENCY = 6;
|
|
7148
|
+
var UUID_RE, TRANSPORT_TOKENS, COMPLETION_EVIDENCE_FIELDS, completionCapabilityCache, retryCapabilityCache, gitRefCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache, PLAN_COMPLETION_PROTECTED_FIELDS, RELATION_HYDRATION_CONCURRENCY = 6;
|
|
7047
7149
|
var init_cloud_router = __esm(() => {
|
|
7048
7150
|
init_types();
|
|
7049
7151
|
init_redaction();
|
|
@@ -7068,10 +7170,23 @@ var init_cloud_router = __esm(() => {
|
|
|
7068
7170
|
"confidence"
|
|
7069
7171
|
];
|
|
7070
7172
|
completionCapabilityCache = new Map;
|
|
7173
|
+
retryCapabilityCache = new Map;
|
|
7071
7174
|
gitRefCapabilityCache = new Map;
|
|
7072
7175
|
SERVER_MODE_CANDIDATES = ["postgres", "cloud", "self_hosted"];
|
|
7073
7176
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
7074
7177
|
listTagsCapabilityCache = new Map;
|
|
7178
|
+
PLAN_COMPLETION_PROTECTED_FIELDS = [
|
|
7179
|
+
"id",
|
|
7180
|
+
"slug",
|
|
7181
|
+
"project_id",
|
|
7182
|
+
"task_list_id",
|
|
7183
|
+
"agent_id",
|
|
7184
|
+
"name",
|
|
7185
|
+
"description",
|
|
7186
|
+
"created_at",
|
|
7187
|
+
"machine_id",
|
|
7188
|
+
"synced_at"
|
|
7189
|
+
];
|
|
7075
7190
|
});
|
|
7076
7191
|
|
|
7077
7192
|
// src/cli/stage-a.ts
|
|
@@ -18333,6 +18448,48 @@ function updatePlan(id, input, db) {
|
|
|
18333
18448
|
return updatePlanStored(id, input, d);
|
|
18334
18449
|
})();
|
|
18335
18450
|
}
|
|
18451
|
+
function nextPlanCompletionTimestamp(expectedUpdatedAt) {
|
|
18452
|
+
const expected = Date.parse(expectedUpdatedAt);
|
|
18453
|
+
const minimum = Number.isNaN(expected) ? Date.now() : expected + 2;
|
|
18454
|
+
return new Date(Math.max(Date.now(), minimum)).toISOString();
|
|
18455
|
+
}
|
|
18456
|
+
function completePlanAtRevision(id, expectedUpdatedAt, db) {
|
|
18457
|
+
const d = db || getDatabase();
|
|
18458
|
+
return d.transaction(() => {
|
|
18459
|
+
guardPlanRowsSqlite([id], d);
|
|
18460
|
+
const plan = getPlan(id, d);
|
|
18461
|
+
if (!plan)
|
|
18462
|
+
throw new PlanNotFoundError(id);
|
|
18463
|
+
if (plan.updated_at !== expectedUpdatedAt) {
|
|
18464
|
+
throw new PlanRevisionConflictError(id, expectedUpdatedAt, plan.updated_at);
|
|
18465
|
+
}
|
|
18466
|
+
if (plan.status === "completed")
|
|
18467
|
+
return { plan, applied: false };
|
|
18468
|
+
const updatedAt = nextPlanCompletionTimestamp(expectedUpdatedAt);
|
|
18469
|
+
const result = d.run(`UPDATE plans
|
|
18470
|
+
SET status = 'completed', updated_at = ?
|
|
18471
|
+
WHERE id = ? AND updated_at = ? AND status <> 'completed'`, [updatedAt, id, expectedUpdatedAt]);
|
|
18472
|
+
if (result.changes !== 1) {
|
|
18473
|
+
const current = getPlan(id, d);
|
|
18474
|
+
if (!current)
|
|
18475
|
+
throw new PlanNotFoundError(id);
|
|
18476
|
+
throw new PlanRevisionConflictError(id, expectedUpdatedAt, current.updated_at);
|
|
18477
|
+
}
|
|
18478
|
+
const completed = getPlan(id, d);
|
|
18479
|
+
emitLocalEventHooksQuiet({
|
|
18480
|
+
type: "plan.updated",
|
|
18481
|
+
payload: {
|
|
18482
|
+
id,
|
|
18483
|
+
old_status: plan.status,
|
|
18484
|
+
new_status: completed.status,
|
|
18485
|
+
name: completed.name,
|
|
18486
|
+
project_id: completed.project_id
|
|
18487
|
+
},
|
|
18488
|
+
databasePath: databasePathFromDatabase(d)
|
|
18489
|
+
});
|
|
18490
|
+
return { plan: completed, applied: true };
|
|
18491
|
+
})();
|
|
18492
|
+
}
|
|
18336
18493
|
function deletePlan(id, db) {
|
|
18337
18494
|
const d = db || getDatabase();
|
|
18338
18495
|
const plan = getPlan(id, d);
|
|
@@ -24233,7 +24390,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24233
24390
|
}
|
|
24234
24391
|
if (result.errors.length > 0)
|
|
24235
24392
|
return result;
|
|
24236
|
-
const applyRows = (objectType, table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
24393
|
+
const applyRows = (objectType, table, columns, rows, updateClockColumn, acceptEqualClock = true, afterUpsert) => {
|
|
24237
24394
|
for (const row of rows) {
|
|
24238
24395
|
try {
|
|
24239
24396
|
const record = asRecord(row);
|
|
@@ -24242,7 +24399,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24242
24399
|
result.skipped += 1;
|
|
24243
24400
|
continue;
|
|
24244
24401
|
}
|
|
24245
|
-
const state = upsertById(d, table, columns, record, updateClockColumn);
|
|
24402
|
+
const state = upsertById(d, table, columns, record, updateClockColumn, acceptEqualClock);
|
|
24246
24403
|
if (state === "inserted")
|
|
24247
24404
|
result.inserted += 1;
|
|
24248
24405
|
else if (state === "updated")
|
|
@@ -24259,10 +24416,10 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24259
24416
|
applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
|
|
24260
24417
|
applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
|
|
24261
24418
|
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
24262
|
-
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
24419
|
+
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at", false);
|
|
24263
24420
|
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
24264
24421
|
applyRows("template_tasks", "template_tasks", TEMPLATE_TASK_COLUMNS, snapshot.templateTasks ?? []);
|
|
24265
|
-
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks(snapshot.tasks), "updated_at", (row, changed) => {
|
|
24422
|
+
applyRows("tasks", "tasks", TASK_COLUMNS, sortedTasks(snapshot.tasks), "updated_at", true, (row, changed) => {
|
|
24266
24423
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
24267
24424
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
24268
24425
|
}
|
|
@@ -24271,7 +24428,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24271
24428
|
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
24272
24429
|
return result;
|
|
24273
24430
|
}
|
|
24274
|
-
function upsertById(db, table, columns, row, updateClockColumn) {
|
|
24431
|
+
function upsertById(db, table, columns, row, updateClockColumn, acceptEqualClock = true) {
|
|
24275
24432
|
const id = row["id"];
|
|
24276
24433
|
if (typeof id !== "string" || !id)
|
|
24277
24434
|
throw new Error(`${table} row is missing id`);
|
|
@@ -24283,7 +24440,7 @@ function upsertById(db, table, columns, row, updateClockColumn) {
|
|
|
24283
24440
|
const values = presentColumns.map((column) => valueForColumn(column, row[column]));
|
|
24284
24441
|
const updateColumns = presentColumns.filter((column) => column !== "id");
|
|
24285
24442
|
const updateSet = updateColumns.map((column) => column === "version" ? `version = MAX(COALESCE(${table}.version, 0), excluded.version)` : `${column} = excluded.${column}`).join(", ");
|
|
24286
|
-
const clockGuard = updateClockColumn && presentColumns.includes(updateClockColumn) ? ` WHERE ${table}.${updateClockColumn} IS NULL OR ${table}.${updateClockColumn} <= excluded.${updateClockColumn}` : "";
|
|
24443
|
+
const clockGuard = updateClockColumn && presentColumns.includes(updateClockColumn) ? ` WHERE ${table}.${updateClockColumn} IS NULL OR ${table}.${updateClockColumn} ${acceptEqualClock ? "<=" : "<"} excluded.${updateClockColumn}` : "";
|
|
24287
24444
|
const sql = updateSet ? `INSERT INTO ${table} (${presentColumns.join(", ")}) VALUES (${placeholders})
|
|
24288
24445
|
ON CONFLICT(id) DO UPDATE SET ${updateSet}${clockGuard}` : `INSERT OR IGNORE INTO ${table} (${presentColumns.join(", ")}) VALUES (${placeholders})`;
|
|
24289
24446
|
const changes = db.run(sql, values).changes;
|
|
@@ -24702,6 +24859,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
24702
24859
|
get: (id) => getPlan(id, database()),
|
|
24703
24860
|
list: (projectId) => listPlans(projectId, database()),
|
|
24704
24861
|
update: (id, input) => updatePlan(id, input, database()),
|
|
24862
|
+
completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
|
|
24705
24863
|
delete: (id) => deletePlan(id, database())
|
|
24706
24864
|
},
|
|
24707
24865
|
planProjectLinks: {
|
|
@@ -29234,6 +29392,7 @@ __export(exports_project_commands, {
|
|
|
29234
29392
|
registerProjectCommands: () => registerProjectCommands
|
|
29235
29393
|
});
|
|
29236
29394
|
import chalk5 from "chalk";
|
|
29395
|
+
import { readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
29237
29396
|
import { basename as basename5, resolve as resolve13, sep as sep3 } from "path";
|
|
29238
29397
|
function collectOption(value, previous = []) {
|
|
29239
29398
|
return [...previous, value];
|
|
@@ -29427,18 +29586,45 @@ function registerProjectCommands(program2) {
|
|
|
29427
29586
|
handleError(e);
|
|
29428
29587
|
}
|
|
29429
29588
|
});
|
|
29430
|
-
program2.command("comment <id>
|
|
29589
|
+
program2.command("comment <id> [text]").alias("log-progress").description("Add a comment to a task (alias: log-progress, for recording intermediate progress)").option("--file <path>", "Read comment text from a UTF-8 file").option("--pct <percent>", "Progress percentage (0-100) to record alongside the note").action(async (id, text, opts) => {
|
|
29590
|
+
const hasPositionalContent = text !== undefined;
|
|
29591
|
+
const hasFileContent = opts.file !== undefined;
|
|
29592
|
+
if (hasPositionalContent === hasFileContent) {
|
|
29593
|
+
handleError(new Error("Provide exactly one comment content source: positional text or --file <path>, not both."));
|
|
29594
|
+
}
|
|
29595
|
+
let content;
|
|
29596
|
+
if (opts.file !== undefined) {
|
|
29597
|
+
const commentFilePath = resolve13(opts.file);
|
|
29598
|
+
let isRegularFile = false;
|
|
29599
|
+
try {
|
|
29600
|
+
isRegularFile = statSync6(commentFilePath).isFile();
|
|
29601
|
+
} catch (error) {
|
|
29602
|
+
handleError(new Error(`Unable to read comment file "${opts.file}".`, { cause: error }));
|
|
29603
|
+
}
|
|
29604
|
+
if (!isRegularFile) {
|
|
29605
|
+
handleError(new Error(`Comment file "${opts.file}" must be a regular file.`));
|
|
29606
|
+
}
|
|
29607
|
+
try {
|
|
29608
|
+
content = readFileSync9(commentFilePath, "utf8");
|
|
29609
|
+
} catch (error) {
|
|
29610
|
+
handleError(new Error(`Unable to read comment file "${opts.file}".`, { cause: error }));
|
|
29611
|
+
}
|
|
29612
|
+
} else {
|
|
29613
|
+
content = text;
|
|
29614
|
+
}
|
|
29615
|
+
if (!content.trim()) {
|
|
29616
|
+
handleError(new Error("Comment content must not be empty."));
|
|
29617
|
+
}
|
|
29431
29618
|
const globalOpts = program2.opts();
|
|
29432
29619
|
const cloud = getTodosCloudClient();
|
|
29433
29620
|
const resolvedId = await resolveTaskIdForCommand(id, cloud);
|
|
29434
|
-
let content = text;
|
|
29435
29621
|
let progressPct;
|
|
29436
29622
|
if (opts.pct !== undefined) {
|
|
29437
29623
|
const pct = parseInt(opts.pct, 10);
|
|
29438
29624
|
if (!Number.isFinite(pct) || pct < 0 || pct > 100) {
|
|
29439
29625
|
handleError(new Error("--pct must be a number between 0 and 100"));
|
|
29440
29626
|
}
|
|
29441
|
-
content = `[progress ${pct}%] ${
|
|
29627
|
+
content = `[progress ${pct}%] ${content}`;
|
|
29442
29628
|
progressPct = pct;
|
|
29443
29629
|
}
|
|
29444
29630
|
const router = resolveWritableIdentity(globalOpts.agent);
|
|
@@ -30198,10 +30384,10 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
30198
30384
|
program2.command("bridge-import <file>").description("Dry-run or apply a local hasna/todos bridge export bundle").option("--apply", "Apply the import. Defaults to dry-run.").option("--decrypt", "Decrypt an encrypted bridge export before importing").option("--resolve-conflicts", "Safely merge existing local tasks by filling blank fields, unioning tags, and recording unresolved divergences").action(async (file, opts) => {
|
|
30199
30385
|
const globalOpts = program2.opts();
|
|
30200
30386
|
try {
|
|
30201
|
-
const { readFileSync:
|
|
30387
|
+
const { readFileSync: readFileSync10 } = await import("fs");
|
|
30202
30388
|
const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
|
|
30203
30389
|
const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
|
|
30204
|
-
const parsed = JSON.parse(
|
|
30390
|
+
const parsed = JSON.parse(readFileSync10(resolve13(file), "utf-8"));
|
|
30205
30391
|
const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
|
|
30206
30392
|
throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
|
|
30207
30393
|
})() : parsed;
|
|
@@ -30235,9 +30421,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
30235
30421
|
program2.command("todos-md-import <file>").alias("markdown-import").alias("import-md").description("Dry-run or apply a local todos.md Markdown import").option("--apply", "Apply the import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge embedded bridge task conflicts while preserving local divergent fields").action(async (file, opts) => {
|
|
30236
30422
|
const globalOpts = program2.opts();
|
|
30237
30423
|
try {
|
|
30238
|
-
const { readFileSync:
|
|
30424
|
+
const { readFileSync: readFileSync10 } = await import("fs");
|
|
30239
30425
|
const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
|
|
30240
|
-
const result = importTodosMarkdown2(
|
|
30426
|
+
const result = importTodosMarkdown2(readFileSync10(resolve13(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
30241
30427
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
30242
30428
|
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve13(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
|
|
30243
30429
|
if (globalOpts.json) {
|
|
@@ -30884,7 +31070,7 @@ async function findFreePort(start) {
|
|
|
30884
31070
|
var DEFAULT_PORT = 19427;
|
|
30885
31071
|
|
|
30886
31072
|
// src/lib/db-backup.ts
|
|
30887
|
-
import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as
|
|
31073
|
+
import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync10, renameSync, statSync as statSync7, writeFileSync as writeFileSync7, unlinkSync } from "fs";
|
|
30888
31074
|
import { dirname as dirname7, join as join14, resolve as resolve14 } from "path";
|
|
30889
31075
|
import { Database as Database2 } from "bun:sqlite";
|
|
30890
31076
|
function resolveDbPath(dbPath) {
|
|
@@ -30913,7 +31099,7 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
30913
31099
|
src.close();
|
|
30914
31100
|
writeFileSync7(outputPath, image);
|
|
30915
31101
|
const method = "file_copy";
|
|
30916
|
-
const bytes =
|
|
31102
|
+
const bytes = statSync7(outputPath).size;
|
|
30917
31103
|
return {
|
|
30918
31104
|
schema_version: DB_BACKUP_SCHEMA,
|
|
30919
31105
|
source_path: source,
|
|
@@ -32250,7 +32436,7 @@ __export(exports_local_extensions, {
|
|
|
32250
32436
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
32251
32437
|
});
|
|
32252
32438
|
import { createHash as createHash9, createVerify } from "crypto";
|
|
32253
|
-
import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as
|
|
32439
|
+
import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
|
|
32254
32440
|
import { basename as basename6, join as join16, resolve as resolve15 } from "path";
|
|
32255
32441
|
function isObject(value) {
|
|
32256
32442
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -32332,7 +32518,7 @@ function normalizeManifest(input) {
|
|
|
32332
32518
|
};
|
|
32333
32519
|
}
|
|
32334
32520
|
function parseJson(path) {
|
|
32335
|
-
return JSON.parse(
|
|
32521
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
32336
32522
|
}
|
|
32337
32523
|
function sha2564(bytes) {
|
|
32338
32524
|
return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
|
|
@@ -32512,11 +32698,11 @@ function inspectExtensionSource(source2) {
|
|
|
32512
32698
|
const resolved = resolve15(source2);
|
|
32513
32699
|
if (!existsSync17(resolved))
|
|
32514
32700
|
throw new Error(`extension source not found: ${source2}`);
|
|
32515
|
-
const stat =
|
|
32701
|
+
const stat = statSync8(resolved);
|
|
32516
32702
|
const manifestPath = stat.isDirectory() ? [join16(resolved, "todos.extension.json"), join16(resolved, "extension.json")].find(existsSync17) : resolved;
|
|
32517
32703
|
if (!manifestPath)
|
|
32518
32704
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
32519
|
-
const raw =
|
|
32705
|
+
const raw = readFileSync11(manifestPath);
|
|
32520
32706
|
const parsed = parseJson(manifestPath);
|
|
32521
32707
|
const bundle = isObject(parsed) && isObject(parsed["manifest"]);
|
|
32522
32708
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -32618,7 +32804,7 @@ function projectExtensionSources(projectPath) {
|
|
|
32618
32804
|
if (entry.startsWith("."))
|
|
32619
32805
|
continue;
|
|
32620
32806
|
const full = join16(extensionDir, entry);
|
|
32621
|
-
if (
|
|
32807
|
+
if (statSync8(full).isDirectory() || entry.endsWith(".json"))
|
|
32622
32808
|
candidates.push(full);
|
|
32623
32809
|
}
|
|
32624
32810
|
}
|
|
@@ -34323,6 +34509,7 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
34323
34509
|
get: (id) => store.get("plans", id),
|
|
34324
34510
|
list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
|
|
34325
34511
|
update: (id, input) => updatePlan2(id, input, store),
|
|
34512
|
+
completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
|
|
34326
34513
|
delete: (id, context) => store.deletePlan(id, context)
|
|
34327
34514
|
},
|
|
34328
34515
|
planProjectLinks: {
|
|
@@ -34810,6 +34997,54 @@ class PostgresJsonRecordStore {
|
|
|
34810
34997
|
throw new PlanNotFoundError(value.id);
|
|
34811
34998
|
return payloadRecord2(row.payload);
|
|
34812
34999
|
}
|
|
35000
|
+
async completePlanAtRevision(id, expectedUpdatedAt, context = {}) {
|
|
35001
|
+
await this.ensureSchema();
|
|
35002
|
+
const result = await this.options.client.query(`/* todos:complete-plan-revision-cas */ WITH next_clock AS (
|
|
35003
|
+
SELECT date_trunc(
|
|
35004
|
+
'milliseconds',
|
|
35005
|
+
GREATEST(clock_timestamp(), ($3::text)::timestamptz + interval '2 milliseconds')
|
|
35006
|
+
) AS completed_at
|
|
35007
|
+
), stored AS (
|
|
35008
|
+
UPDATE ${this.tableName} AS record SET
|
|
35009
|
+
payload = record.payload || jsonb_build_object(
|
|
35010
|
+
'status', 'completed',
|
|
35011
|
+
'updated_at', to_char(
|
|
35012
|
+
next_clock.completed_at AT TIME ZONE 'UTC',
|
|
35013
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
35014
|
+
)
|
|
35015
|
+
),
|
|
35016
|
+
updated_at = next_clock.completed_at,
|
|
35017
|
+
deleted_at = NULL,
|
|
35018
|
+
source_machine_id = COALESCE($4, record.source_machine_id),
|
|
35019
|
+
version = COALESCE(record.version, 0) + 1
|
|
35020
|
+
FROM next_clock
|
|
35021
|
+
WHERE record.service = $1
|
|
35022
|
+
AND record.object_type = 'plans'
|
|
35023
|
+
AND record.object_id = $2
|
|
35024
|
+
AND record.deleted_at IS NULL
|
|
35025
|
+
AND record.payload->>'updated_at' = $3::text
|
|
35026
|
+
AND record.payload->>'status' IS DISTINCT FROM 'completed'
|
|
35027
|
+
RETURNING record.payload
|
|
35028
|
+
)
|
|
35029
|
+
SELECT payload FROM stored`, [
|
|
35030
|
+
this.service,
|
|
35031
|
+
id,
|
|
35032
|
+
expectedUpdatedAt,
|
|
35033
|
+
context.requestId ?? this.sourceMachineId ?? null
|
|
35034
|
+
]);
|
|
35035
|
+
const payload = result.rows[0]?.payload;
|
|
35036
|
+
if (payload)
|
|
35037
|
+
return { plan: payloadRecord2(payload), applied: true };
|
|
35038
|
+
const current = await this.get("plans", id);
|
|
35039
|
+
if (!current)
|
|
35040
|
+
throw new PlanNotFoundError(id);
|
|
35041
|
+
if (current.updated_at !== expectedUpdatedAt) {
|
|
35042
|
+
throw new PlanRevisionConflictError(id, expectedUpdatedAt, current.updated_at);
|
|
35043
|
+
}
|
|
35044
|
+
if (current.status === "completed")
|
|
35045
|
+
return { plan: current, applied: false };
|
|
35046
|
+
throw new PlanRevisionConflictError(id, expectedUpdatedAt, current.updated_at);
|
|
35047
|
+
}
|
|
34813
35048
|
async createTemplateWithTasks(template, tasks, context = {}) {
|
|
34814
35049
|
await this.ensureSchema();
|
|
34815
35050
|
const records = [
|
|
@@ -44645,7 +44880,7 @@ var exports_doctor = {};
|
|
|
44645
44880
|
__export(exports_doctor, {
|
|
44646
44881
|
runTodosDoctor: () => runTodosDoctor
|
|
44647
44882
|
});
|
|
44648
|
-
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync18, mkdirSync as mkdirSync9, statSync as
|
|
44883
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync18, mkdirSync as mkdirSync9, statSync as statSync9 } from "fs";
|
|
44649
44884
|
import { basename as basename7, dirname as dirname8, join as join17 } from "path";
|
|
44650
44885
|
function tableExists3(db, table) {
|
|
44651
44886
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
@@ -44792,7 +45027,7 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
44792
45027
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
44793
45028
|
return false;
|
|
44794
45029
|
try {
|
|
44795
|
-
return (
|
|
45030
|
+
return (statSync9(dbPath).mode & 63) !== 0;
|
|
44796
45031
|
} catch {
|
|
44797
45032
|
return false;
|
|
44798
45033
|
}
|
|
@@ -48010,8 +48245,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
48010
48245
|
"/v1/import": {
|
|
48011
48246
|
post: {
|
|
48012
48247
|
operationId: "importSnapshot",
|
|
48013
|
-
summary: "Bulk-ingest a
|
|
48014
|
-
description: "Upserts every record
|
|
48248
|
+
summary: "Bulk-ingest a snapshot or atomically complete one observed plan",
|
|
48249
|
+
description: "Upserts every snapshot record by primary key, or accepts exactly one planCompletions operation that changes only plan status under an expected_updated_at CAS. Snapshot records and planCompletions are mutually exclusive. Requires the todos:write scope.",
|
|
48015
48250
|
requestBody: {
|
|
48016
48251
|
required: true,
|
|
48017
48252
|
content: {
|
|
@@ -48030,7 +48265,22 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
48030
48265
|
templates: { type: "array", items: { type: "object" } },
|
|
48031
48266
|
templateTasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } },
|
|
48032
48267
|
auditHistory: { type: "array", items: { type: "object" } },
|
|
48033
|
-
tombstones: { type: "array", items: { type: "object" } }
|
|
48268
|
+
tombstones: { type: "array", items: { type: "object" } },
|
|
48269
|
+
planCompletions: {
|
|
48270
|
+
type: "array",
|
|
48271
|
+
minItems: 1,
|
|
48272
|
+
maxItems: 1,
|
|
48273
|
+
items: {
|
|
48274
|
+
type: "object",
|
|
48275
|
+
additionalProperties: false,
|
|
48276
|
+
required: ["id", "expected_updated_at", "status"],
|
|
48277
|
+
properties: {
|
|
48278
|
+
id: { type: "string" },
|
|
48279
|
+
expected_updated_at: { type: "string", format: "date-time" },
|
|
48280
|
+
status: { type: "string", enum: ["completed"] }
|
|
48281
|
+
}
|
|
48282
|
+
}
|
|
48283
|
+
}
|
|
48034
48284
|
}
|
|
48035
48285
|
}
|
|
48036
48286
|
}
|
|
@@ -48053,6 +48303,26 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
48053
48303
|
skipped: { type: "number" },
|
|
48054
48304
|
errors: { type: "array", items: { type: "string" } }
|
|
48055
48305
|
}
|
|
48306
|
+
},
|
|
48307
|
+
planCompletions: {
|
|
48308
|
+
type: "array",
|
|
48309
|
+
items: {
|
|
48310
|
+
type: "object",
|
|
48311
|
+
required: [
|
|
48312
|
+
"id",
|
|
48313
|
+
"status",
|
|
48314
|
+
"expected_updated_at",
|
|
48315
|
+
"result_updated_at",
|
|
48316
|
+
"applied"
|
|
48317
|
+
],
|
|
48318
|
+
properties: {
|
|
48319
|
+
id: { type: "string" },
|
|
48320
|
+
status: { type: "string", enum: ["completed"] },
|
|
48321
|
+
expected_updated_at: { type: "string", format: "date-time" },
|
|
48322
|
+
result_updated_at: { type: "string", format: "date-time" },
|
|
48323
|
+
applied: { type: "boolean" }
|
|
48324
|
+
}
|
|
48325
|
+
}
|
|
48056
48326
|
}
|
|
48057
48327
|
}
|
|
48058
48328
|
}
|
|
@@ -48902,6 +49172,65 @@ function normalizeImportSnapshot(raw) {
|
|
|
48902
49172
|
function countSnapshotRecords(s) {
|
|
48903
49173
|
return s.tasks.length + s.projects.length + (s.projectMachinePaths?.length ?? 0) + s.plans.length + s.agents.length + s.taskLists.length + s.templates.length + s.templateTasks.length + s.auditHistory.length + (s.tombstones?.length ?? 0);
|
|
48904
49174
|
}
|
|
49175
|
+
function validatePlanCompletionImports(raw) {
|
|
49176
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
49177
|
+
return { present: false, operations: [] };
|
|
49178
|
+
}
|
|
49179
|
+
const body2 = raw;
|
|
49180
|
+
if (!Object.prototype.hasOwnProperty.call(body2, "planCompletions")) {
|
|
49181
|
+
return { present: false, operations: [] };
|
|
49182
|
+
}
|
|
49183
|
+
if (!Array.isArray(body2["planCompletions"]) || body2["planCompletions"].length !== 1) {
|
|
49184
|
+
return {
|
|
49185
|
+
present: true,
|
|
49186
|
+
operations: [],
|
|
49187
|
+
error: "planCompletions must contain exactly one completion operation"
|
|
49188
|
+
};
|
|
49189
|
+
}
|
|
49190
|
+
const operation = body2["planCompletions"][0];
|
|
49191
|
+
if (!operation || typeof operation !== "object" || Array.isArray(operation)) {
|
|
49192
|
+
return { present: true, operations: [], error: "plan completion must be an object" };
|
|
49193
|
+
}
|
|
49194
|
+
const record = operation;
|
|
49195
|
+
const allowed = new Set(["id", "expected_updated_at", "status"]);
|
|
49196
|
+
const unknown = Object.keys(record).find((key2) => !allowed.has(key2));
|
|
49197
|
+
if (unknown) {
|
|
49198
|
+
return { present: true, operations: [], error: `unknown plan completion field: ${unknown}` };
|
|
49199
|
+
}
|
|
49200
|
+
if (typeof record["id"] !== "string" || !record["id"].trim()) {
|
|
49201
|
+
return { present: true, operations: [], error: "plan completion id must be a non-empty string" };
|
|
49202
|
+
}
|
|
49203
|
+
if (record["status"] !== "completed") {
|
|
49204
|
+
return { present: true, operations: [], error: "plan completion status must be completed" };
|
|
49205
|
+
}
|
|
49206
|
+
const expectedUpdatedAt = typeof record["expected_updated_at"] === "string" ? record["expected_updated_at"] : "";
|
|
49207
|
+
const timestampMatch = RFC3339_DATE_TIME.exec(expectedUpdatedAt);
|
|
49208
|
+
const parsedTimestamp = Date.parse(expectedUpdatedAt);
|
|
49209
|
+
if (!timestampMatch || Number.isNaN(parsedTimestamp)) {
|
|
49210
|
+
return {
|
|
49211
|
+
present: true,
|
|
49212
|
+
operations: [],
|
|
49213
|
+
error: "plan completion expected_updated_at must be an RFC 3339 date-time with an explicit offset"
|
|
49214
|
+
};
|
|
49215
|
+
}
|
|
49216
|
+
const [, year, month, day] = timestampMatch;
|
|
49217
|
+
const calendarProbe = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
|
|
49218
|
+
if (calendarProbe.getUTCFullYear() !== Number(year) || calendarProbe.getUTCMonth() !== Number(month) - 1 || calendarProbe.getUTCDate() !== Number(day)) {
|
|
49219
|
+
return {
|
|
49220
|
+
present: true,
|
|
49221
|
+
operations: [],
|
|
49222
|
+
error: "plan completion expected_updated_at names a date that does not exist"
|
|
49223
|
+
};
|
|
49224
|
+
}
|
|
49225
|
+
return {
|
|
49226
|
+
present: true,
|
|
49227
|
+
operations: [{
|
|
49228
|
+
id: record["id"],
|
|
49229
|
+
expected_updated_at: expectedUpdatedAt,
|
|
49230
|
+
status: "completed"
|
|
49231
|
+
}]
|
|
49232
|
+
};
|
|
49233
|
+
}
|
|
48905
49234
|
async function handleV1Request(req, url, dependencies = {}) {
|
|
48906
49235
|
const path = url.pathname;
|
|
48907
49236
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
@@ -49838,6 +50167,36 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
49838
50167
|
return error(400, "invalid JSON body");
|
|
49839
50168
|
const snapshot = normalizeImportSnapshot(raw);
|
|
49840
50169
|
const received = countSnapshotRecords(snapshot);
|
|
50170
|
+
const completionImports = validatePlanCompletionImports(raw);
|
|
50171
|
+
if (completionImports.present) {
|
|
50172
|
+
if (completionImports.error)
|
|
50173
|
+
return error(400, completionImports.error);
|
|
50174
|
+
if (received !== 0) {
|
|
50175
|
+
return error(400, "planCompletions cannot be combined with snapshot record arrays");
|
|
50176
|
+
}
|
|
50177
|
+
if (typeof store.plans.completeAtRevision !== "function") {
|
|
50178
|
+
return error(501, "atomic plan completion is not supported by this storage backend");
|
|
50179
|
+
}
|
|
50180
|
+
const operation = completionImports.operations[0];
|
|
50181
|
+
const completed = await store.plans.completeAtRevision(operation.id, operation.expected_updated_at, contextFromPrincipal(principal));
|
|
50182
|
+
return json5({
|
|
50183
|
+
result: {
|
|
50184
|
+
inserted: 0,
|
|
50185
|
+
updated: completed.applied ? 1 : 0,
|
|
50186
|
+
deleted: 0,
|
|
50187
|
+
skipped: completed.applied ? 0 : 1,
|
|
50188
|
+
errors: []
|
|
50189
|
+
},
|
|
50190
|
+
received: 1,
|
|
50191
|
+
planCompletions: [{
|
|
50192
|
+
id: operation.id,
|
|
50193
|
+
status: "completed",
|
|
50194
|
+
expected_updated_at: operation.expected_updated_at,
|
|
50195
|
+
result_updated_at: completed.plan.updated_at,
|
|
50196
|
+
applied: completed.applied
|
|
50197
|
+
}]
|
|
50198
|
+
});
|
|
50199
|
+
}
|
|
49841
50200
|
if (received === 0) {
|
|
49842
50201
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
49843
50202
|
}
|
|
@@ -49864,6 +50223,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
49864
50223
|
if (e instanceof TaskNotFoundError) {
|
|
49865
50224
|
return error(404, e.message, { code: TaskNotFoundError.code });
|
|
49866
50225
|
}
|
|
50226
|
+
if (e instanceof PlanNotFoundError) {
|
|
50227
|
+
return error(404, e.message, { code: PlanNotFoundError.code });
|
|
50228
|
+
}
|
|
50229
|
+
if (e instanceof PlanRevisionConflictError) {
|
|
50230
|
+
return error(409, e.message, {
|
|
50231
|
+
code: PlanRevisionConflictError.code,
|
|
50232
|
+
conflict: true,
|
|
50233
|
+
plan_id: e.planId,
|
|
50234
|
+
expected_updated_at: e.expectedUpdatedAt,
|
|
50235
|
+
current_updated_at: e.currentUpdatedAt
|
|
50236
|
+
});
|
|
50237
|
+
}
|
|
49867
50238
|
if (e instanceof LockError)
|
|
49868
50239
|
return error(409, e.message, { code: LockError.code });
|
|
49869
50240
|
if (e instanceof TaskNotStartableError) {
|
|
@@ -51015,7 +51386,7 @@ var exports_mention_resolver = {};
|
|
|
51015
51386
|
__export(exports_mention_resolver, {
|
|
51016
51387
|
resolveMentions: () => resolveMentions
|
|
51017
51388
|
});
|
|
51018
|
-
import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as
|
|
51389
|
+
import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync10 } from "fs";
|
|
51019
51390
|
import { basename as basename8, isAbsolute, join as join19, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
|
|
51020
51391
|
function blankResolution(parsed) {
|
|
51021
51392
|
return {
|
|
@@ -51118,13 +51489,13 @@ function resolveFile(parsed, workspace) {
|
|
|
51118
51489
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
51119
51490
|
return resolution;
|
|
51120
51491
|
}
|
|
51121
|
-
const stats =
|
|
51492
|
+
const stats = statSync10(absolutePath);
|
|
51122
51493
|
if (!stats.isFile()) {
|
|
51123
51494
|
resolution.warnings.push("path exists but is not a file");
|
|
51124
51495
|
return resolution;
|
|
51125
51496
|
}
|
|
51126
51497
|
if (parsed.line !== undefined) {
|
|
51127
|
-
const lineCount =
|
|
51498
|
+
const lineCount = readFileSync12(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
51128
51499
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
51129
51500
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
51130
51501
|
return resolution;
|
|
@@ -51156,7 +51527,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
51156
51527
|
if (!entry2.isFile())
|
|
51157
51528
|
continue;
|
|
51158
51529
|
const extension = `.${basename8(entry2.name).split(".").pop() || ""}`;
|
|
51159
|
-
if (SOURCE_EXTENSIONS.has(extension) &&
|
|
51530
|
+
if (SOURCE_EXTENSIONS.has(extension) && statSync10(absolutePath).size <= 512 * 1024) {
|
|
51160
51531
|
files.push(absolutePath);
|
|
51161
51532
|
}
|
|
51162
51533
|
}
|
|
@@ -51177,7 +51548,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
51177
51548
|
const pattern = symbolPattern(name);
|
|
51178
51549
|
const matches = [];
|
|
51179
51550
|
for (const file of walkSourceFiles(workspace)) {
|
|
51180
|
-
const lines =
|
|
51551
|
+
const lines = readFileSync12(file, "utf-8").split(/\r?\n/);
|
|
51181
51552
|
for (let index = 0;index < lines.length; index += 1) {
|
|
51182
51553
|
const line = lines[index];
|
|
51183
51554
|
const found = pattern.exec(line);
|
|
@@ -53919,7 +54290,7 @@ __export(exports_release_compatibility, {
|
|
|
53919
54290
|
createReleaseCompatibilityReport: () => createReleaseCompatibilityReport,
|
|
53920
54291
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
53921
54292
|
});
|
|
53922
|
-
import { readFileSync as
|
|
54293
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
53923
54294
|
import { join as join20, resolve as resolve19 } from "path";
|
|
53924
54295
|
import { Database as Database3 } from "bun:sqlite";
|
|
53925
54296
|
function pass(id, message, details) {
|
|
@@ -53932,7 +54303,7 @@ function warn(id, message, details) {
|
|
|
53932
54303
|
return { id, status: "warning", message, details };
|
|
53933
54304
|
}
|
|
53934
54305
|
function readPackageJson2(root) {
|
|
53935
|
-
return JSON.parse(
|
|
54306
|
+
return JSON.parse(readFileSync13(join20(root, "package.json"), "utf8"));
|
|
53936
54307
|
}
|
|
53937
54308
|
function sortedKeys(value) {
|
|
53938
54309
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -59902,7 +60273,7 @@ __export(exports_verification_providers, {
|
|
|
59902
60273
|
getVerificationRecord: () => getVerificationRecord,
|
|
59903
60274
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
59904
60275
|
});
|
|
59905
|
-
import { existsSync as existsSync20, readFileSync as
|
|
60276
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
|
|
59906
60277
|
function normalizeName6(name) {
|
|
59907
60278
|
const normalized = name.trim().toLowerCase();
|
|
59908
60279
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -60054,7 +60425,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
60054
60425
|
};
|
|
60055
60426
|
}
|
|
60056
60427
|
function runCiLogProvider(input) {
|
|
60057
|
-
const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ?
|
|
60428
|
+
const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ? readFileSync14(input.log_path, "utf-8") : "");
|
|
60058
60429
|
return {
|
|
60059
60430
|
status: classifyLog(text),
|
|
60060
60431
|
attempts: 1,
|
|
@@ -62357,7 +62728,7 @@ __export(exports_local_backups, {
|
|
|
62357
62728
|
LOCAL_BACKUP_CHECKSUM_ALGORITHM: () => LOCAL_BACKUP_CHECKSUM_ALGORITHM
|
|
62358
62729
|
});
|
|
62359
62730
|
import { createHash as createHash14 } from "crypto";
|
|
62360
|
-
import { readFileSync as
|
|
62731
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
62361
62732
|
import { dirname as dirname9, resolve as resolve20 } from "path";
|
|
62362
62733
|
import { mkdirSync as mkdirSync10 } from "fs";
|
|
62363
62734
|
function stableJson2(value) {
|
|
@@ -62467,7 +62838,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
62467
62838
|
return path;
|
|
62468
62839
|
}
|
|
62469
62840
|
function readLocalBackupFile(path) {
|
|
62470
|
-
return JSON.parse(
|
|
62841
|
+
return JSON.parse(readFileSync15(resolve20(path), "utf-8"));
|
|
62471
62842
|
}
|
|
62472
62843
|
function verifyLocalBackup(value, options = {}, db) {
|
|
62473
62844
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -63647,7 +64018,7 @@ __export(exports_agent_replay_simulator, {
|
|
|
63647
64018
|
renderAgentReplaySimulationMarkdown: () => renderAgentReplaySimulationMarkdown
|
|
63648
64019
|
});
|
|
63649
64020
|
import { createHash as createHash16 } from "crypto";
|
|
63650
|
-
import { readFileSync as
|
|
64021
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
63651
64022
|
function isObject2(value) {
|
|
63652
64023
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
63653
64024
|
}
|
|
@@ -63880,7 +64251,7 @@ function simulateAgentReplay(input, options = {}) {
|
|
|
63880
64251
|
};
|
|
63881
64252
|
}
|
|
63882
64253
|
function simulateAgentReplayFile(path, options = {}) {
|
|
63883
|
-
const parsed = JSON.parse(
|
|
64254
|
+
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
63884
64255
|
return simulateAgentReplay(parsed, options);
|
|
63885
64256
|
}
|
|
63886
64257
|
function renderAgentReplaySimulationMarkdown(simulation) {
|
|
@@ -69219,7 +69590,7 @@ __export(exports_environment_snapshots, {
|
|
|
69219
69590
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
69220
69591
|
});
|
|
69221
69592
|
import { createHash as createHash18 } from "crypto";
|
|
69222
|
-
import { existsSync as existsSync21, readFileSync as
|
|
69593
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17, statSync as statSync11 } from "fs";
|
|
69223
69594
|
import { hostname as hostname2, platform, arch } from "os";
|
|
69224
69595
|
import { dirname as dirname10, join as join22, resolve as resolve21 } from "path";
|
|
69225
69596
|
import { tmpdir as tmpdir4 } from "os";
|
|
@@ -69230,10 +69601,10 @@ function fileRecord(root, relativePath) {
|
|
|
69230
69601
|
const path = join22(root, relativePath);
|
|
69231
69602
|
if (!existsSync21(path))
|
|
69232
69603
|
return null;
|
|
69233
|
-
const stat =
|
|
69604
|
+
const stat = statSync11(path);
|
|
69234
69605
|
if (!stat.isFile())
|
|
69235
69606
|
return null;
|
|
69236
|
-
const content =
|
|
69607
|
+
const content = readFileSync17(path);
|
|
69237
69608
|
return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
|
|
69238
69609
|
}
|
|
69239
69610
|
function manifestRecord(root, relativePath) {
|
|
@@ -72279,7 +72650,7 @@ __export(exports_config_serve_commands, {
|
|
|
72279
72650
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
72280
72651
|
});
|
|
72281
72652
|
import chalk7 from "chalk";
|
|
72282
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as
|
|
72653
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
72283
72654
|
import { dirname as dirname12, join as join24 } from "path";
|
|
72284
72655
|
function registerConfigServeCommands(program2) {
|
|
72285
72656
|
program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
|
|
@@ -72310,7 +72681,7 @@ function registerConfigServeCommands(program2) {
|
|
|
72310
72681
|
}
|
|
72311
72682
|
let config2 = {};
|
|
72312
72683
|
try {
|
|
72313
|
-
config2 = JSON.parse(
|
|
72684
|
+
config2 = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
72314
72685
|
} catch {}
|
|
72315
72686
|
const keys = key2.split(".");
|
|
72316
72687
|
let obj = config2;
|
|
@@ -72450,7 +72821,7 @@ function registerConfigServeCommands(program2) {
|
|
|
72450
72821
|
redaction.command("scan [text]").description("Scan text or a file for secret-like values without printing values").option("--file <path>", "File to scan").action(async (text2, opts) => {
|
|
72451
72822
|
const globalOpts = program2.opts();
|
|
72452
72823
|
const { listSecretFindings: listSecretFindings2 } = await Promise.resolve().then(() => (init_redaction(), exports_redaction));
|
|
72453
|
-
const value = opts.file ?
|
|
72824
|
+
const value = opts.file ? readFileSync18(opts.file, "utf-8") : text2 || "";
|
|
72454
72825
|
const findings = listSecretFindings2(value);
|
|
72455
72826
|
if (globalOpts.json) {
|
|
72456
72827
|
output({ ok: findings.length === 0, findings }, true);
|
|
@@ -74016,7 +74387,7 @@ __export(exports_task_route_sources, {
|
|
|
74016
74387
|
});
|
|
74017
74388
|
import { Database as Database4 } from "bun:sqlite";
|
|
74018
74389
|
import { createHash as createHash19 } from "crypto";
|
|
74019
|
-
import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as
|
|
74390
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as statSync12 } from "fs";
|
|
74020
74391
|
import { basename as basename10, dirname as dirname14, join as join26, resolve as resolve22 } from "path";
|
|
74021
74392
|
function normalizePath6(input) {
|
|
74022
74393
|
return resolve22(input);
|
|
@@ -74092,7 +74463,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
74092
74463
|
}
|
|
74093
74464
|
let rootStat;
|
|
74094
74465
|
try {
|
|
74095
|
-
rootStat =
|
|
74466
|
+
rootStat = statSync12(rootPath);
|
|
74096
74467
|
} catch (error2) {
|
|
74097
74468
|
const ref = createStoreRef(join26(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
74098
74469
|
errors2.push({
|
|
@@ -74825,7 +75196,7 @@ __export(exports_query_commands, {
|
|
|
74825
75196
|
registerQueryCommands: () => registerQueryCommands
|
|
74826
75197
|
});
|
|
74827
75198
|
import chalk9 from "chalk";
|
|
74828
|
-
import { readFileSync as
|
|
75199
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync12 } from "fs";
|
|
74829
75200
|
function parseJsonObjectOption2(value, label) {
|
|
74830
75201
|
if (!value)
|
|
74831
75202
|
return;
|
|
@@ -75845,13 +76216,13 @@ Findings`));
|
|
|
75845
76216
|
try {
|
|
75846
76217
|
const db = getDatabase();
|
|
75847
76218
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
75848
|
-
const { statSync:
|
|
76219
|
+
const { statSync: statSync13 } = await import("fs");
|
|
75849
76220
|
const { join: join27 } = await import("path");
|
|
75850
76221
|
const { getHomeDir: getHomeDir2 } = await Promise.resolve().then(() => (init_sync_utils(), exports_sync_utils));
|
|
75851
76222
|
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join27(getHomeDir2(), ".hasna", "todos", "todos.db");
|
|
75852
76223
|
let size = "unknown";
|
|
75853
76224
|
try {
|
|
75854
|
-
size = `${(
|
|
76225
|
+
size = `${(statSync13(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
75855
76226
|
} catch {}
|
|
75856
76227
|
checks.push({ name: "Database", ok: true, message: `${row.count} tasks \xB7 ${size} \xB7 ${chalk9.dim(dbPath)}` });
|
|
75857
76228
|
} catch (e) {
|
|
@@ -76568,7 +76939,7 @@ Findings`));
|
|
|
76568
76939
|
const sessionId = opts.session || globalOpts.session || undefined;
|
|
76569
76940
|
try {
|
|
76570
76941
|
if (opts.import) {
|
|
76571
|
-
const bundle = JSON.parse(
|
|
76942
|
+
const bundle = JSON.parse(readFileSync19(opts.import, "utf-8"));
|
|
76572
76943
|
const result = importHandoffBundle(bundle, { apply: opts.apply }, db);
|
|
76573
76944
|
if (opts.json || globalOpts.json) {
|
|
76574
76945
|
console.log(JSON.stringify(result));
|
|
@@ -76942,7 +77313,7 @@ Findings`));
|
|
|
76942
77313
|
});
|
|
76943
77314
|
calendar.command("import <path>").description("Import VEVENT entries from an ICS file as local imported calendar items").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
76944
77315
|
try {
|
|
76945
|
-
const result = importCalendarIcs(
|
|
77316
|
+
const result = importCalendarIcs(readFileSync19(path, "utf-8"));
|
|
76946
77317
|
if (opts.json || program2.opts().json) {
|
|
76947
77318
|
output(result, true);
|
|
76948
77319
|
return;
|
|
@@ -77093,7 +77464,7 @@ Findings`));
|
|
|
77093
77464
|
});
|
|
77094
77465
|
board.command("import <path>").description("Import local board definitions from a JSON bundle").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
77095
77466
|
try {
|
|
77096
|
-
const bundle = JSON.parse(
|
|
77467
|
+
const bundle = JSON.parse(readFileSync19(path, "utf-8"));
|
|
77097
77468
|
const result = importTaskBoardBundle(bundle);
|
|
77098
77469
|
if (opts.json || program2.opts().json) {
|
|
77099
77470
|
output(result, true);
|
|
@@ -77469,7 +77840,7 @@ Findings`));
|
|
|
77469
77840
|
const { importExternalIssues: importExternalIssues2 } = await Promise.resolve().then(() => (init_external_issue_importers(), exports_external_issue_importers));
|
|
77470
77841
|
let body2 = text2 || "";
|
|
77471
77842
|
if (opts.file)
|
|
77472
|
-
body2 =
|
|
77843
|
+
body2 = readFileSync19(opts.file, "utf-8");
|
|
77473
77844
|
if (!body2 && !opts.url && !process.stdin.isTTY)
|
|
77474
77845
|
body2 = await Bun.stdin.text();
|
|
77475
77846
|
if (!body2.trim() && !opts.url) {
|
|
@@ -77526,7 +77897,7 @@ Findings`));
|
|
|
77526
77897
|
} = await Promise.resolve().then(() => (init_tester_issue_reports(), exports_tester_issue_reports));
|
|
77527
77898
|
let body2 = jsonText || "";
|
|
77528
77899
|
if (opts.file)
|
|
77529
|
-
body2 =
|
|
77900
|
+
body2 = readFileSync19(opts.file, "utf-8");
|
|
77530
77901
|
if (!body2 && !process.stdin.isTTY)
|
|
77531
77902
|
body2 = await Bun.stdin.text();
|
|
77532
77903
|
if (!body2.trim()) {
|
|
@@ -77569,11 +77940,11 @@ Findings`));
|
|
|
77569
77940
|
const inbox = program2.command("inbox").description("Capture local inbox items from pasted errors, CI logs, git context, files, or GitHub issue URLs");
|
|
77570
77941
|
inbox.command("add [text]").description("Create a local inbox item and linked task from text, stdin, or a file").option("--file <path>", "Read captured context from a file").option("--source-type <type>", "pasted_error, ci_log, git_context, github_issue, file, or other").option("--source-name <name>", "Human-readable source name").option("--source-url <url>", "Source URL, including GitHub issue URLs").option("--title <title>", "Task/inbox title").option("--priority <priority>", "Task priority").option("--tags <tags>", "Comma-separated extra tags").option("--metadata <json>", "Additional JSON metadata").option("--no-task", "Only store inbox item; do not create a linked task").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
|
|
77571
77942
|
const globalOpts = program2.opts();
|
|
77572
|
-
const { readFileSync:
|
|
77943
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
77573
77944
|
const { createInboxItem: createInboxItem2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
|
|
77574
77945
|
let body2 = text2 || "";
|
|
77575
77946
|
if (opts.file)
|
|
77576
|
-
body2 =
|
|
77947
|
+
body2 = readFileSync20(opts.file, "utf-8");
|
|
77577
77948
|
if (!body2 && !process.stdin.isTTY)
|
|
77578
77949
|
body2 = await Bun.stdin.text();
|
|
77579
77950
|
if (!body2.trim()) {
|
|
@@ -77633,11 +78004,11 @@ ${diff}` : null].filter(Boolean).join(`
|
|
|
77633
78004
|
});
|
|
77634
78005
|
inbox.command("parse [text]").description("Preview or apply deterministic local natural-language task intake").option("--file <path>", "Read natural-language input from a file").option("--priority <priority>", "Default priority for parsed tasks", "medium").option("--project <id>", "Project ID for applied tasks").option("--list <id>", "Task list ID for applied tasks").option("--reference-date <iso>", "Reference date for due today/tomorrow/next week").option("--apply", "Create parsed tasks; default is dry-run preview").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
|
|
77635
78006
|
const globalOpts = program2.opts();
|
|
77636
|
-
const { readFileSync:
|
|
78007
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
77637
78008
|
const { previewNaturalLanguageIntake: previewNaturalLanguageIntake2 } = await Promise.resolve().then(() => (init_natural_language_intake(), exports_natural_language_intake));
|
|
77638
78009
|
let body2 = text2 || "";
|
|
77639
78010
|
if (opts.file)
|
|
77640
|
-
body2 =
|
|
78011
|
+
body2 = readFileSync20(opts.file, "utf-8");
|
|
77641
78012
|
if (!body2 && !process.stdin.isTTY)
|
|
77642
78013
|
body2 = await Bun.stdin.text();
|
|
77643
78014
|
if (!body2.trim()) {
|
|
@@ -77964,7 +78335,7 @@ __export(exports_mcp_hooks_commands, {
|
|
|
77964
78335
|
});
|
|
77965
78336
|
import chalk10 from "chalk";
|
|
77966
78337
|
import { execSync as execSync3 } from "child_process";
|
|
77967
|
-
import { existsSync as existsSync26, readFileSync as
|
|
78338
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
|
|
77968
78339
|
import { dirname as dirname15, join as join27 } from "path";
|
|
77969
78340
|
function getMcpBinaryPath() {
|
|
77970
78341
|
try {
|
|
@@ -77981,7 +78352,7 @@ function readJsonFile2(path) {
|
|
|
77981
78352
|
if (!existsSync26(path))
|
|
77982
78353
|
return {};
|
|
77983
78354
|
try {
|
|
77984
|
-
return JSON.parse(
|
|
78355
|
+
return JSON.parse(readFileSync20(path, "utf-8"));
|
|
77985
78356
|
} catch {
|
|
77986
78357
|
return {};
|
|
77987
78358
|
}
|
|
@@ -77996,7 +78367,7 @@ function writeJsonFile2(path, data) {
|
|
|
77996
78367
|
function readTomlFile(path) {
|
|
77997
78368
|
if (!existsSync26(path))
|
|
77998
78369
|
return "";
|
|
77999
|
-
return
|
|
78370
|
+
return readFileSync20(path, "utf-8");
|
|
78000
78371
|
}
|
|
78001
78372
|
function writeTomlFile(path, content) {
|
|
78002
78373
|
const dir = dirname15(path);
|
|
@@ -79187,7 +79558,7 @@ Artifacts:`));
|
|
|
79187
79558
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
79188
79559
|
const marker = "# todos-auto-link";
|
|
79189
79560
|
if (existsSync26(hookPath)) {
|
|
79190
|
-
const existing =
|
|
79561
|
+
const existing = readFileSync20(hookPath, "utf-8");
|
|
79191
79562
|
if (existing.includes(marker)) {
|
|
79192
79563
|
console.log(chalk10.yellow("Hook already installed."));
|
|
79193
79564
|
return;
|
|
@@ -79217,7 +79588,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
79217
79588
|
console.log(chalk10.dim("No post-commit hook found."));
|
|
79218
79589
|
return;
|
|
79219
79590
|
}
|
|
79220
|
-
const content =
|
|
79591
|
+
const content = readFileSync20(hookPath, "utf-8");
|
|
79221
79592
|
if (!content.includes(marker)) {
|
|
79222
79593
|
console.log(chalk10.dim("Hook not managed by todos."));
|
|
79223
79594
|
return;
|
|
@@ -79468,7 +79839,7 @@ var STDIN_SENTINEL = "-";
|
|
|
79468
79839
|
var init_delegation_brief = () => {};
|
|
79469
79840
|
|
|
79470
79841
|
// src/lib/delegation-policy.ts
|
|
79471
|
-
import { readFileSync as
|
|
79842
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
79472
79843
|
import { homedir as homedir4 } from "os";
|
|
79473
79844
|
import { join as join28 } from "path";
|
|
79474
79845
|
function defaultDelegationEmbargoPath() {
|
|
@@ -79476,7 +79847,7 @@ function defaultDelegationEmbargoPath() {
|
|
|
79476
79847
|
}
|
|
79477
79848
|
function loadDelegationEmbargo(path = defaultDelegationEmbargoPath()) {
|
|
79478
79849
|
try {
|
|
79479
|
-
const parsed = JSON.parse(
|
|
79850
|
+
const parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
79480
79851
|
const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.embargoed) ? parsed.embargoed : [];
|
|
79481
79852
|
const names = new Set;
|
|
79482
79853
|
for (const entry2 of entries) {
|
|
@@ -79559,15 +79930,15 @@ __export(exports_delegate, {
|
|
|
79559
79930
|
registerDelegateCommands: () => registerDelegateCommands
|
|
79560
79931
|
});
|
|
79561
79932
|
import chalk12 from "chalk";
|
|
79562
|
-
import { readFileSync as
|
|
79933
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
79563
79934
|
function registerDelegateCommands(program2) {
|
|
79564
79935
|
program2.command("delegate <task> <worker>").description("Hand a filed task to a worker in one call: brief, depth, lineage, assignment, record and notice").option("--brief <path>", "Path to the self-sufficient brief; `-` reads stdin").option("--brief-text <text>", "Inline brief, as an alternative to --brief").option("--depth-threshold <n>", "Open-task count above which this delegation parks").option("--despite-depth", "Proceed past an armed depth threshold; recorded in the [DISPATCH] comment").option("--owner-directive", "Mark as an owner-directive dispatch: depth warns and never parks").option("--seat <slug>", "Seat whose open count is read (default: the lineage parent)").option("--runtime <name>", "Worker runtime label recorded in the comment (e.g. claude-code-subagent)").option("--reports-to <agent>", "Lineage parent for the worker identity (default: the dispatcher)").option("--reuse-identity", "Skip registration and reuse an existing worker identity").option("--depth <n>", "Explicit delegation_depth (default: the task's current depth + 1)").option("--channel <name>", "Channel for the one-line notice (default: $TODOS_DELEGATE_NOTICE_CHANNEL)").option("--no-post", "Skip the channel notice").option("--claim-window <minutes>", `Minutes before the claim deadline (default: ${DEFAULT_CLAIM_WINDOW_MINUTES})`).option("--assign-seat", "Allow <worker> to name a durable seat (a seat queue has no session watching it)").option("--dry-run", "Report all seven effects and perform none").option("-j, --json", "Output as JSON").action(async (taskRef, workerInput, opts) => {
|
|
79565
79936
|
const globalOpts = program2.opts();
|
|
79566
79937
|
const useJson = Boolean(opts.json || globalOpts.json);
|
|
79567
79938
|
try {
|
|
79568
79939
|
const brief = resolveDelegationBrief({ briefPath: opts.brief, briefText: opts.briefText }, {
|
|
79569
|
-
readFile: (path) =>
|
|
79570
|
-
readStdin: () =>
|
|
79940
|
+
readFile: (path) => readFileSync22(path, "utf8"),
|
|
79941
|
+
readStdin: () => readFileSync22(0, "utf8")
|
|
79571
79942
|
});
|
|
79572
79943
|
if (!brief.ok)
|
|
79573
79944
|
handleError(new Error(brief.message));
|
|
@@ -79815,7 +80186,7 @@ __export(exports_machines, {
|
|
|
79815
80186
|
});
|
|
79816
80187
|
import chalk13 from "chalk";
|
|
79817
80188
|
import { execSync as execSync4 } from "child_process";
|
|
79818
|
-
import { readFileSync as
|
|
80189
|
+
import { readFileSync as readFileSync23, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
79819
80190
|
import { tmpdir as tmpdir5 } from "os";
|
|
79820
80191
|
import { join as join29 } from "path";
|
|
79821
80192
|
function getOrCreateLocalMachineName() {
|
|
@@ -79859,7 +80230,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
79859
80230
|
try {
|
|
79860
80231
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
79861
80232
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
79862
|
-
return JSON.parse(
|
|
80233
|
+
return JSON.parse(readFileSync23(localPath, "utf-8"));
|
|
79863
80234
|
} finally {
|
|
79864
80235
|
try {
|
|
79865
80236
|
runSsh(sshAddress, `rm -f ${shellQuote(remotePath)}`, 1e4);
|
|
@@ -84817,7 +85188,7 @@ var exports_roadmap_commands = {};
|
|
|
84817
85188
|
__export(exports_roadmap_commands, {
|
|
84818
85189
|
registerRoadmapCommands: () => registerRoadmapCommands
|
|
84819
85190
|
});
|
|
84820
|
-
import { readFileSync as
|
|
85191
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "fs";
|
|
84821
85192
|
import chalk24 from "chalk";
|
|
84822
85193
|
function splitList3(value) {
|
|
84823
85194
|
return value?.split(";").flatMap((part) => part.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
@@ -85046,7 +85417,7 @@ function registerRoadmapCommands(program2) {
|
|
|
85046
85417
|
const globalOpts = globalOptions(program2);
|
|
85047
85418
|
try {
|
|
85048
85419
|
const { importRoadmapBundle: importRoadmapBundle2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
85049
|
-
const bundle = JSON.parse(
|
|
85420
|
+
const bundle = JSON.parse(readFileSync24(path, "utf8"));
|
|
85050
85421
|
const result = importRoadmapBundle2(bundle, { apply: Boolean(opts.apply) });
|
|
85051
85422
|
if (globalOpts.json) {
|
|
85052
85423
|
output(result, true);
|
|
@@ -85806,6 +86177,15 @@ function createShadowTodosStorageAdapter(options) {
|
|
|
85806
86177
|
mirror.enqueueUpsert("plans", plan, context);
|
|
85807
86178
|
return plan;
|
|
85808
86179
|
},
|
|
86180
|
+
async completeAtRevision(id, expectedUpdatedAt, context) {
|
|
86181
|
+
if (typeof local.plans.completeAtRevision !== "function") {
|
|
86182
|
+
throw new Error("Atomic plan completion is not supported by the local shadow adapter");
|
|
86183
|
+
}
|
|
86184
|
+
const completed = await local.plans.completeAtRevision(id, expectedUpdatedAt, context);
|
|
86185
|
+
if (completed.applied)
|
|
86186
|
+
mirror.enqueueUpsert("plans", completed.plan, context);
|
|
86187
|
+
return completed;
|
|
86188
|
+
},
|
|
85809
86189
|
async delete(id, context) {
|
|
85810
86190
|
const deleted = await local.plans.delete(id, context);
|
|
85811
86191
|
if (deleted)
|