@hasna/todos 0.15.17 → 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 +7 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +478 -88
- 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 +268 -21
- 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 +268 -21
- 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 {
|
|
@@ -6840,7 +6942,7 @@ function resolveTaskListFromCandidates(lists, input) {
|
|
|
6840
6942
|
];
|
|
6841
6943
|
for (const matches of matchGroups) {
|
|
6842
6944
|
if (matches.length === 1)
|
|
6843
|
-
return matches[0]
|
|
6945
|
+
return matches[0];
|
|
6844
6946
|
if (matches.length > 1) {
|
|
6845
6947
|
throw new Error(`Task list reference is ambiguous: "${input}"`);
|
|
6846
6948
|
}
|
|
@@ -6853,27 +6955,26 @@ async function legacyProjectTaskLists(client, projectId) {
|
|
|
6853
6955
|
return [];
|
|
6854
6956
|
return (await cloudListTaskLists(client)).filter((list) => list.project_id == null && list.slug === project.task_list_id);
|
|
6855
6957
|
}
|
|
6856
|
-
async function
|
|
6958
|
+
async function cloudResolveTaskList(client, ref, projectId) {
|
|
6857
6959
|
const input = ref.trim();
|
|
6858
6960
|
const normalizedIdRef = input.toLowerCase();
|
|
6859
|
-
if (UUID_RE.test(input)
|
|
6860
|
-
return normalizedIdRef;
|
|
6861
|
-
if (UUID_RE.test(input) && projectId) {
|
|
6961
|
+
if (UUID_RE.test(input)) {
|
|
6862
6962
|
const direct = await cloudGetTaskList(client, normalizedIdRef);
|
|
6863
6963
|
if (direct?.id?.toLowerCase() === normalizedIdRef) {
|
|
6864
|
-
if (direct.project_id === projectId)
|
|
6865
|
-
return direct
|
|
6964
|
+
if (!projectId || direct.project_id === projectId)
|
|
6965
|
+
return direct;
|
|
6866
6966
|
if (direct.project_id == null) {
|
|
6867
6967
|
const project = await cloudGetProjectById(client, projectId);
|
|
6868
6968
|
if (project?.task_list_id === direct.slug)
|
|
6869
|
-
return direct
|
|
6969
|
+
return direct;
|
|
6870
6970
|
}
|
|
6871
6971
|
throw new Error(`Task list not found: "${input}"`);
|
|
6872
6972
|
}
|
|
6873
6973
|
}
|
|
6874
6974
|
const scopedMatch = resolveTaskListFromCandidates(await cloudListTaskLists(client, projectId), input);
|
|
6875
|
-
if (scopedMatch)
|
|
6975
|
+
if (scopedMatch && (!projectId || scopedMatch.project_id === projectId)) {
|
|
6876
6976
|
return scopedMatch;
|
|
6977
|
+
}
|
|
6877
6978
|
if (projectId) {
|
|
6878
6979
|
const legacyMatch = resolveTaskListFromCandidates(await legacyProjectTaskLists(client, projectId), input);
|
|
6879
6980
|
if (legacyMatch)
|
|
@@ -6881,6 +6982,12 @@ async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
|
6881
6982
|
}
|
|
6882
6983
|
throw new Error(`Task list not found: "${input}"`);
|
|
6883
6984
|
}
|
|
6985
|
+
async function cloudResolveTaskListRef(client, ref, projectId) {
|
|
6986
|
+
const input = ref.trim();
|
|
6987
|
+
if (UUID_RE.test(input) && !projectId)
|
|
6988
|
+
return input.toLowerCase();
|
|
6989
|
+
return (await cloudResolveTaskList(client, ref, projectId)).id;
|
|
6990
|
+
}
|
|
6884
6991
|
async function cloudCreateTaskList(client, input) {
|
|
6885
6992
|
return unwrapTaskList(await requiredRemoteRoute(client, "/v1/task-lists", () => client.transport.post("/task-lists", input)));
|
|
6886
6993
|
}
|
|
@@ -7038,7 +7145,7 @@ async function cloudTimeline(client, options = {}) {
|
|
|
7038
7145
|
const limit = options.limit ?? 50;
|
|
7039
7146
|
return { entries: entries.slice(offset, offset + limit), total, limit, offset };
|
|
7040
7147
|
}
|
|
7041
|
-
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;
|
|
7042
7149
|
var init_cloud_router = __esm(() => {
|
|
7043
7150
|
init_types();
|
|
7044
7151
|
init_redaction();
|
|
@@ -7063,10 +7170,23 @@ var init_cloud_router = __esm(() => {
|
|
|
7063
7170
|
"confidence"
|
|
7064
7171
|
];
|
|
7065
7172
|
completionCapabilityCache = new Map;
|
|
7173
|
+
retryCapabilityCache = new Map;
|
|
7066
7174
|
gitRefCapabilityCache = new Map;
|
|
7067
7175
|
SERVER_MODE_CANDIDATES = ["postgres", "cloud", "self_hosted"];
|
|
7068
7176
|
PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
7069
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
|
+
];
|
|
7070
7190
|
});
|
|
7071
7191
|
|
|
7072
7192
|
// src/cli/stage-a.ts
|
|
@@ -18328,6 +18448,48 @@ function updatePlan(id, input, db) {
|
|
|
18328
18448
|
return updatePlanStored(id, input, d);
|
|
18329
18449
|
})();
|
|
18330
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
|
+
}
|
|
18331
18493
|
function deletePlan(id, db) {
|
|
18332
18494
|
const d = db || getDatabase();
|
|
18333
18495
|
const plan = getPlan(id, d);
|
|
@@ -22125,7 +22287,11 @@ function registerTaskCommands(program2) {
|
|
|
22125
22287
|
filter["project_id"] = projectId;
|
|
22126
22288
|
}
|
|
22127
22289
|
if (opts.list && cloud) {
|
|
22128
|
-
|
|
22290
|
+
const resolvedTaskList = await cloudResolveTaskList(cloud, opts.list, projectId);
|
|
22291
|
+
filter["task_list_id"] = resolvedTaskList.id;
|
|
22292
|
+
if (!projectId && resolvedTaskList.project_id) {
|
|
22293
|
+
filter["project_id"] = resolvedTaskList.project_id;
|
|
22294
|
+
}
|
|
22129
22295
|
} else if (opts.list) {
|
|
22130
22296
|
const db = getDatabase();
|
|
22131
22297
|
const listId = resolvePartialId(db, "task_lists", opts.list);
|
|
@@ -22193,7 +22359,8 @@ function registerTaskCommands(program2) {
|
|
|
22193
22359
|
const requestedLimit = filter["limit"];
|
|
22194
22360
|
const combinesScalarStatusPages = Boolean(cloud && Array.isArray(filter["status"]) && filter["status"].length > 1);
|
|
22195
22361
|
const reordersAfterQuery = Boolean(opts.sort) || combinesScalarStatusPages;
|
|
22196
|
-
const
|
|
22362
|
+
const taskListFilterActive = Boolean(cloud && filter["task_list_id"]);
|
|
22363
|
+
const narrowsAfterQuery = Boolean(opts.dueToday) || Boolean(opts.overdue) || creatorFilterActive && cloud || taskListFilterActive;
|
|
22197
22364
|
const withholdLimit = requestedLimit !== undefined && (reordersAfterQuery || narrowsAfterQuery);
|
|
22198
22365
|
const scanCeiling = cloud && (withholdLimit || requestedLimit === undefined) ? Math.max(requestedLimit ?? 0, listScanLimit()) : undefined;
|
|
22199
22366
|
const serverFilter = (() => {
|
|
@@ -24223,7 +24390,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24223
24390
|
}
|
|
24224
24391
|
if (result.errors.length > 0)
|
|
24225
24392
|
return result;
|
|
24226
|
-
const applyRows = (objectType, table, columns, rows, updateClockColumn, afterUpsert) => {
|
|
24393
|
+
const applyRows = (objectType, table, columns, rows, updateClockColumn, acceptEqualClock = true, afterUpsert) => {
|
|
24227
24394
|
for (const row of rows) {
|
|
24228
24395
|
try {
|
|
24229
24396
|
const record = asRecord(row);
|
|
@@ -24232,7 +24399,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24232
24399
|
result.skipped += 1;
|
|
24233
24400
|
continue;
|
|
24234
24401
|
}
|
|
24235
|
-
const state = upsertById(d, table, columns, record, updateClockColumn);
|
|
24402
|
+
const state = upsertById(d, table, columns, record, updateClockColumn, acceptEqualClock);
|
|
24236
24403
|
if (state === "inserted")
|
|
24237
24404
|
result.inserted += 1;
|
|
24238
24405
|
else if (state === "updated")
|
|
@@ -24249,10 +24416,10 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24249
24416
|
applyRows("project_machine_paths", "project_machine_paths", PROJECT_MACHINE_PATH_COLUMNS, snapshot.projectMachinePaths ?? [], "updated_at");
|
|
24250
24417
|
applyRows("agents", "agents", AGENT_COLUMNS, snapshot.agents, "last_seen_at");
|
|
24251
24418
|
applyRows("task_lists", "task_lists", TASK_LIST_COLUMNS, snapshot.taskLists, "updated_at");
|
|
24252
|
-
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at");
|
|
24419
|
+
applyRows("plans", "plans", PLAN_COLUMNS, snapshot.plans, "updated_at", false);
|
|
24253
24420
|
applyRows("templates", "task_templates", TEMPLATE_COLUMNS, snapshot.templates);
|
|
24254
24421
|
applyRows("template_tasks", "template_tasks", TEMPLATE_TASK_COLUMNS, snapshot.templateTasks ?? []);
|
|
24255
|
-
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) => {
|
|
24256
24423
|
if (changed && Array.isArray(row["tags"]) && typeof row["id"] === "string") {
|
|
24257
24424
|
replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
|
|
24258
24425
|
}
|
|
@@ -24261,7 +24428,7 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
|
|
|
24261
24428
|
applyTombstones(d, snapshot.tombstones ?? [], result);
|
|
24262
24429
|
return result;
|
|
24263
24430
|
}
|
|
24264
|
-
function upsertById(db, table, columns, row, updateClockColumn) {
|
|
24431
|
+
function upsertById(db, table, columns, row, updateClockColumn, acceptEqualClock = true) {
|
|
24265
24432
|
const id = row["id"];
|
|
24266
24433
|
if (typeof id !== "string" || !id)
|
|
24267
24434
|
throw new Error(`${table} row is missing id`);
|
|
@@ -24273,7 +24440,7 @@ function upsertById(db, table, columns, row, updateClockColumn) {
|
|
|
24273
24440
|
const values = presentColumns.map((column) => valueForColumn(column, row[column]));
|
|
24274
24441
|
const updateColumns = presentColumns.filter((column) => column !== "id");
|
|
24275
24442
|
const updateSet = updateColumns.map((column) => column === "version" ? `version = MAX(COALESCE(${table}.version, 0), excluded.version)` : `${column} = excluded.${column}`).join(", ");
|
|
24276
|
-
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}` : "";
|
|
24277
24444
|
const sql = updateSet ? `INSERT INTO ${table} (${presentColumns.join(", ")}) VALUES (${placeholders})
|
|
24278
24445
|
ON CONFLICT(id) DO UPDATE SET ${updateSet}${clockGuard}` : `INSERT OR IGNORE INTO ${table} (${presentColumns.join(", ")}) VALUES (${placeholders})`;
|
|
24279
24446
|
const changes = db.run(sql, values).changes;
|
|
@@ -24692,6 +24859,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
24692
24859
|
get: (id) => getPlan(id, database()),
|
|
24693
24860
|
list: (projectId) => listPlans(projectId, database()),
|
|
24694
24861
|
update: (id, input) => updatePlan(id, input, database()),
|
|
24862
|
+
completeAtRevision: (id, expectedUpdatedAt) => completePlanAtRevision(id, expectedUpdatedAt, database()),
|
|
24695
24863
|
delete: (id) => deletePlan(id, database())
|
|
24696
24864
|
},
|
|
24697
24865
|
planProjectLinks: {
|
|
@@ -29224,6 +29392,7 @@ __export(exports_project_commands, {
|
|
|
29224
29392
|
registerProjectCommands: () => registerProjectCommands
|
|
29225
29393
|
});
|
|
29226
29394
|
import chalk5 from "chalk";
|
|
29395
|
+
import { readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
29227
29396
|
import { basename as basename5, resolve as resolve13, sep as sep3 } from "path";
|
|
29228
29397
|
function collectOption(value, previous = []) {
|
|
29229
29398
|
return [...previous, value];
|
|
@@ -29417,18 +29586,45 @@ function registerProjectCommands(program2) {
|
|
|
29417
29586
|
handleError(e);
|
|
29418
29587
|
}
|
|
29419
29588
|
});
|
|
29420
|
-
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
|
+
}
|
|
29421
29618
|
const globalOpts = program2.opts();
|
|
29422
29619
|
const cloud = getTodosCloudClient();
|
|
29423
29620
|
const resolvedId = await resolveTaskIdForCommand(id, cloud);
|
|
29424
|
-
let content = text;
|
|
29425
29621
|
let progressPct;
|
|
29426
29622
|
if (opts.pct !== undefined) {
|
|
29427
29623
|
const pct = parseInt(opts.pct, 10);
|
|
29428
29624
|
if (!Number.isFinite(pct) || pct < 0 || pct > 100) {
|
|
29429
29625
|
handleError(new Error("--pct must be a number between 0 and 100"));
|
|
29430
29626
|
}
|
|
29431
|
-
content = `[progress ${pct}%] ${
|
|
29627
|
+
content = `[progress ${pct}%] ${content}`;
|
|
29432
29628
|
progressPct = pct;
|
|
29433
29629
|
}
|
|
29434
29630
|
const router = resolveWritableIdentity(globalOpts.agent);
|
|
@@ -30188,10 +30384,10 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
30188
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) => {
|
|
30189
30385
|
const globalOpts = program2.opts();
|
|
30190
30386
|
try {
|
|
30191
|
-
const { readFileSync:
|
|
30387
|
+
const { readFileSync: readFileSync10 } = await import("fs");
|
|
30192
30388
|
const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
|
|
30193
30389
|
const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
|
|
30194
|
-
const parsed = JSON.parse(
|
|
30390
|
+
const parsed = JSON.parse(readFileSync10(resolve13(file), "utf-8"));
|
|
30195
30391
|
const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
|
|
30196
30392
|
throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
|
|
30197
30393
|
})() : parsed;
|
|
@@ -30225,9 +30421,9 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
30225
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) => {
|
|
30226
30422
|
const globalOpts = program2.opts();
|
|
30227
30423
|
try {
|
|
30228
|
-
const { readFileSync:
|
|
30424
|
+
const { readFileSync: readFileSync10 } = await import("fs");
|
|
30229
30425
|
const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
|
|
30230
|
-
const result = importTodosMarkdown2(
|
|
30426
|
+
const result = importTodosMarkdown2(readFileSync10(resolve13(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
30231
30427
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
30232
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 } });
|
|
30233
30429
|
if (globalOpts.json) {
|
|
@@ -30874,7 +31070,7 @@ async function findFreePort(start) {
|
|
|
30874
31070
|
var DEFAULT_PORT = 19427;
|
|
30875
31071
|
|
|
30876
31072
|
// src/lib/db-backup.ts
|
|
30877
|
-
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";
|
|
30878
31074
|
import { dirname as dirname7, join as join14, resolve as resolve14 } from "path";
|
|
30879
31075
|
import { Database as Database2 } from "bun:sqlite";
|
|
30880
31076
|
function resolveDbPath(dbPath) {
|
|
@@ -30903,7 +31099,7 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
30903
31099
|
src.close();
|
|
30904
31100
|
writeFileSync7(outputPath, image);
|
|
30905
31101
|
const method = "file_copy";
|
|
30906
|
-
const bytes =
|
|
31102
|
+
const bytes = statSync7(outputPath).size;
|
|
30907
31103
|
return {
|
|
30908
31104
|
schema_version: DB_BACKUP_SCHEMA,
|
|
30909
31105
|
source_path: source,
|
|
@@ -32240,7 +32436,7 @@ __export(exports_local_extensions, {
|
|
|
32240
32436
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
32241
32437
|
});
|
|
32242
32438
|
import { createHash as createHash9, createVerify } from "crypto";
|
|
32243
|
-
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";
|
|
32244
32440
|
import { basename as basename6, join as join16, resolve as resolve15 } from "path";
|
|
32245
32441
|
function isObject(value) {
|
|
32246
32442
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -32322,7 +32518,7 @@ function normalizeManifest(input) {
|
|
|
32322
32518
|
};
|
|
32323
32519
|
}
|
|
32324
32520
|
function parseJson(path) {
|
|
32325
|
-
return JSON.parse(
|
|
32521
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
32326
32522
|
}
|
|
32327
32523
|
function sha2564(bytes) {
|
|
32328
32524
|
return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
|
|
@@ -32502,11 +32698,11 @@ function inspectExtensionSource(source2) {
|
|
|
32502
32698
|
const resolved = resolve15(source2);
|
|
32503
32699
|
if (!existsSync17(resolved))
|
|
32504
32700
|
throw new Error(`extension source not found: ${source2}`);
|
|
32505
|
-
const stat =
|
|
32701
|
+
const stat = statSync8(resolved);
|
|
32506
32702
|
const manifestPath = stat.isDirectory() ? [join16(resolved, "todos.extension.json"), join16(resolved, "extension.json")].find(existsSync17) : resolved;
|
|
32507
32703
|
if (!manifestPath)
|
|
32508
32704
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
32509
|
-
const raw =
|
|
32705
|
+
const raw = readFileSync11(manifestPath);
|
|
32510
32706
|
const parsed = parseJson(manifestPath);
|
|
32511
32707
|
const bundle = isObject(parsed) && isObject(parsed["manifest"]);
|
|
32512
32708
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -32608,7 +32804,7 @@ function projectExtensionSources(projectPath) {
|
|
|
32608
32804
|
if (entry.startsWith("."))
|
|
32609
32805
|
continue;
|
|
32610
32806
|
const full = join16(extensionDir, entry);
|
|
32611
|
-
if (
|
|
32807
|
+
if (statSync8(full).isDirectory() || entry.endsWith(".json"))
|
|
32612
32808
|
candidates.push(full);
|
|
32613
32809
|
}
|
|
32614
32810
|
}
|
|
@@ -34313,6 +34509,7 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
34313
34509
|
get: (id) => store.get("plans", id),
|
|
34314
34510
|
list: async (projectId) => (await store.list("plans")).filter((plan) => projectId === undefined || plan.project_id === projectId).sort((a, b) => a.name.localeCompare(b.name)),
|
|
34315
34511
|
update: (id, input) => updatePlan2(id, input, store),
|
|
34512
|
+
completeAtRevision: (id, expectedUpdatedAt, context) => store.completePlanAtRevision(id, expectedUpdatedAt, context),
|
|
34316
34513
|
delete: (id, context) => store.deletePlan(id, context)
|
|
34317
34514
|
},
|
|
34318
34515
|
planProjectLinks: {
|
|
@@ -34800,6 +34997,54 @@ class PostgresJsonRecordStore {
|
|
|
34800
34997
|
throw new PlanNotFoundError(value.id);
|
|
34801
34998
|
return payloadRecord2(row.payload);
|
|
34802
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
|
+
}
|
|
34803
35048
|
async createTemplateWithTasks(template, tasks, context = {}) {
|
|
34804
35049
|
await this.ensureSchema();
|
|
34805
35050
|
const records = [
|
|
@@ -44635,7 +44880,7 @@ var exports_doctor = {};
|
|
|
44635
44880
|
__export(exports_doctor, {
|
|
44636
44881
|
runTodosDoctor: () => runTodosDoctor
|
|
44637
44882
|
});
|
|
44638
|
-
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";
|
|
44639
44884
|
import { basename as basename7, dirname as dirname8, join as join17 } from "path";
|
|
44640
44885
|
function tableExists3(db, table) {
|
|
44641
44886
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
@@ -44782,7 +45027,7 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
44782
45027
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
44783
45028
|
return false;
|
|
44784
45029
|
try {
|
|
44785
|
-
return (
|
|
45030
|
+
return (statSync9(dbPath).mode & 63) !== 0;
|
|
44786
45031
|
} catch {
|
|
44787
45032
|
return false;
|
|
44788
45033
|
}
|
|
@@ -48000,8 +48245,8 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
48000
48245
|
"/v1/import": {
|
|
48001
48246
|
post: {
|
|
48002
48247
|
operationId: "importSnapshot",
|
|
48003
|
-
summary: "Bulk-ingest a
|
|
48004
|
-
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.",
|
|
48005
48250
|
requestBody: {
|
|
48006
48251
|
required: true,
|
|
48007
48252
|
content: {
|
|
@@ -48020,7 +48265,22 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
48020
48265
|
templates: { type: "array", items: { type: "object" } },
|
|
48021
48266
|
templateTasks: { type: "array", items: { $ref: "#/components/schemas/TemplateTask" } },
|
|
48022
48267
|
auditHistory: { type: "array", items: { type: "object" } },
|
|
48023
|
-
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
|
+
}
|
|
48024
48284
|
}
|
|
48025
48285
|
}
|
|
48026
48286
|
}
|
|
@@ -48043,6 +48303,26 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
48043
48303
|
skipped: { type: "number" },
|
|
48044
48304
|
errors: { type: "array", items: { type: "string" } }
|
|
48045
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
|
+
}
|
|
48046
48326
|
}
|
|
48047
48327
|
}
|
|
48048
48328
|
}
|
|
@@ -48892,6 +49172,65 @@ function normalizeImportSnapshot(raw) {
|
|
|
48892
49172
|
function countSnapshotRecords(s) {
|
|
48893
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);
|
|
48894
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
|
+
}
|
|
48895
49234
|
async function handleV1Request(req, url, dependencies = {}) {
|
|
48896
49235
|
const path = url.pathname;
|
|
48897
49236
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
@@ -49828,6 +50167,36 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
49828
50167
|
return error(400, "invalid JSON body");
|
|
49829
50168
|
const snapshot = normalizeImportSnapshot(raw);
|
|
49830
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
|
+
}
|
|
49831
50200
|
if (received === 0) {
|
|
49832
50201
|
return error(400, "empty snapshot: provide at least one record array (tasks/projects/plans/...)");
|
|
49833
50202
|
}
|
|
@@ -49854,6 +50223,18 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
49854
50223
|
if (e instanceof TaskNotFoundError) {
|
|
49855
50224
|
return error(404, e.message, { code: TaskNotFoundError.code });
|
|
49856
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
|
+
}
|
|
49857
50238
|
if (e instanceof LockError)
|
|
49858
50239
|
return error(409, e.message, { code: LockError.code });
|
|
49859
50240
|
if (e instanceof TaskNotStartableError) {
|
|
@@ -51005,7 +51386,7 @@ var exports_mention_resolver = {};
|
|
|
51005
51386
|
__export(exports_mention_resolver, {
|
|
51006
51387
|
resolveMentions: () => resolveMentions
|
|
51007
51388
|
});
|
|
51008
|
-
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";
|
|
51009
51390
|
import { basename as basename8, isAbsolute, join as join19, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
|
|
51010
51391
|
function blankResolution(parsed) {
|
|
51011
51392
|
return {
|
|
@@ -51108,13 +51489,13 @@ function resolveFile(parsed, workspace) {
|
|
|
51108
51489
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
51109
51490
|
return resolution;
|
|
51110
51491
|
}
|
|
51111
|
-
const stats =
|
|
51492
|
+
const stats = statSync10(absolutePath);
|
|
51112
51493
|
if (!stats.isFile()) {
|
|
51113
51494
|
resolution.warnings.push("path exists but is not a file");
|
|
51114
51495
|
return resolution;
|
|
51115
51496
|
}
|
|
51116
51497
|
if (parsed.line !== undefined) {
|
|
51117
|
-
const lineCount =
|
|
51498
|
+
const lineCount = readFileSync12(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
51118
51499
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
51119
51500
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
51120
51501
|
return resolution;
|
|
@@ -51146,7 +51527,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
51146
51527
|
if (!entry2.isFile())
|
|
51147
51528
|
continue;
|
|
51148
51529
|
const extension = `.${basename8(entry2.name).split(".").pop() || ""}`;
|
|
51149
|
-
if (SOURCE_EXTENSIONS.has(extension) &&
|
|
51530
|
+
if (SOURCE_EXTENSIONS.has(extension) && statSync10(absolutePath).size <= 512 * 1024) {
|
|
51150
51531
|
files.push(absolutePath);
|
|
51151
51532
|
}
|
|
51152
51533
|
}
|
|
@@ -51167,7 +51548,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
51167
51548
|
const pattern = symbolPattern(name);
|
|
51168
51549
|
const matches = [];
|
|
51169
51550
|
for (const file of walkSourceFiles(workspace)) {
|
|
51170
|
-
const lines =
|
|
51551
|
+
const lines = readFileSync12(file, "utf-8").split(/\r?\n/);
|
|
51171
51552
|
for (let index = 0;index < lines.length; index += 1) {
|
|
51172
51553
|
const line = lines[index];
|
|
51173
51554
|
const found = pattern.exec(line);
|
|
@@ -53909,7 +54290,7 @@ __export(exports_release_compatibility, {
|
|
|
53909
54290
|
createReleaseCompatibilityReport: () => createReleaseCompatibilityReport,
|
|
53910
54291
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
53911
54292
|
});
|
|
53912
|
-
import { readFileSync as
|
|
54293
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
53913
54294
|
import { join as join20, resolve as resolve19 } from "path";
|
|
53914
54295
|
import { Database as Database3 } from "bun:sqlite";
|
|
53915
54296
|
function pass(id, message, details) {
|
|
@@ -53922,7 +54303,7 @@ function warn(id, message, details) {
|
|
|
53922
54303
|
return { id, status: "warning", message, details };
|
|
53923
54304
|
}
|
|
53924
54305
|
function readPackageJson2(root) {
|
|
53925
|
-
return JSON.parse(
|
|
54306
|
+
return JSON.parse(readFileSync13(join20(root, "package.json"), "utf8"));
|
|
53926
54307
|
}
|
|
53927
54308
|
function sortedKeys(value) {
|
|
53928
54309
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -59892,7 +60273,7 @@ __export(exports_verification_providers, {
|
|
|
59892
60273
|
getVerificationRecord: () => getVerificationRecord,
|
|
59893
60274
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
59894
60275
|
});
|
|
59895
|
-
import { existsSync as existsSync20, readFileSync as
|
|
60276
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
|
|
59896
60277
|
function normalizeName6(name) {
|
|
59897
60278
|
const normalized = name.trim().toLowerCase();
|
|
59898
60279
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -60044,7 +60425,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
60044
60425
|
};
|
|
60045
60426
|
}
|
|
60046
60427
|
function runCiLogProvider(input) {
|
|
60047
|
-
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") : "");
|
|
60048
60429
|
return {
|
|
60049
60430
|
status: classifyLog(text),
|
|
60050
60431
|
attempts: 1,
|
|
@@ -62347,7 +62728,7 @@ __export(exports_local_backups, {
|
|
|
62347
62728
|
LOCAL_BACKUP_CHECKSUM_ALGORITHM: () => LOCAL_BACKUP_CHECKSUM_ALGORITHM
|
|
62348
62729
|
});
|
|
62349
62730
|
import { createHash as createHash14 } from "crypto";
|
|
62350
|
-
import { readFileSync as
|
|
62731
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
62351
62732
|
import { dirname as dirname9, resolve as resolve20 } from "path";
|
|
62352
62733
|
import { mkdirSync as mkdirSync10 } from "fs";
|
|
62353
62734
|
function stableJson2(value) {
|
|
@@ -62457,7 +62838,7 @@ function writeLocalBackupFile(backup, outputPath) {
|
|
|
62457
62838
|
return path;
|
|
62458
62839
|
}
|
|
62459
62840
|
function readLocalBackupFile(path) {
|
|
62460
|
-
return JSON.parse(
|
|
62841
|
+
return JSON.parse(readFileSync15(resolve20(path), "utf-8"));
|
|
62461
62842
|
}
|
|
62462
62843
|
function verifyLocalBackup(value, options = {}, db) {
|
|
62463
62844
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -63637,7 +64018,7 @@ __export(exports_agent_replay_simulator, {
|
|
|
63637
64018
|
renderAgentReplaySimulationMarkdown: () => renderAgentReplaySimulationMarkdown
|
|
63638
64019
|
});
|
|
63639
64020
|
import { createHash as createHash16 } from "crypto";
|
|
63640
|
-
import { readFileSync as
|
|
64021
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
63641
64022
|
function isObject2(value) {
|
|
63642
64023
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
63643
64024
|
}
|
|
@@ -63870,7 +64251,7 @@ function simulateAgentReplay(input, options = {}) {
|
|
|
63870
64251
|
};
|
|
63871
64252
|
}
|
|
63872
64253
|
function simulateAgentReplayFile(path, options = {}) {
|
|
63873
|
-
const parsed = JSON.parse(
|
|
64254
|
+
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
63874
64255
|
return simulateAgentReplay(parsed, options);
|
|
63875
64256
|
}
|
|
63876
64257
|
function renderAgentReplaySimulationMarkdown(simulation) {
|
|
@@ -69209,7 +69590,7 @@ __export(exports_environment_snapshots, {
|
|
|
69209
69590
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
69210
69591
|
});
|
|
69211
69592
|
import { createHash as createHash18 } from "crypto";
|
|
69212
|
-
import { existsSync as existsSync21, readFileSync as
|
|
69593
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17, statSync as statSync11 } from "fs";
|
|
69213
69594
|
import { hostname as hostname2, platform, arch } from "os";
|
|
69214
69595
|
import { dirname as dirname10, join as join22, resolve as resolve21 } from "path";
|
|
69215
69596
|
import { tmpdir as tmpdir4 } from "os";
|
|
@@ -69220,10 +69601,10 @@ function fileRecord(root, relativePath) {
|
|
|
69220
69601
|
const path = join22(root, relativePath);
|
|
69221
69602
|
if (!existsSync21(path))
|
|
69222
69603
|
return null;
|
|
69223
|
-
const stat =
|
|
69604
|
+
const stat = statSync11(path);
|
|
69224
69605
|
if (!stat.isFile())
|
|
69225
69606
|
return null;
|
|
69226
|
-
const content =
|
|
69607
|
+
const content = readFileSync17(path);
|
|
69227
69608
|
return { path: relativePath, sha256: sha2567(content), size_bytes: content.length };
|
|
69228
69609
|
}
|
|
69229
69610
|
function manifestRecord(root, relativePath) {
|
|
@@ -72269,7 +72650,7 @@ __export(exports_config_serve_commands, {
|
|
|
72269
72650
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
72270
72651
|
});
|
|
72271
72652
|
import chalk7 from "chalk";
|
|
72272
|
-
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";
|
|
72273
72654
|
import { dirname as dirname12, join as join24 } from "path";
|
|
72274
72655
|
function registerConfigServeCommands(program2) {
|
|
72275
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) => {
|
|
@@ -72300,7 +72681,7 @@ function registerConfigServeCommands(program2) {
|
|
|
72300
72681
|
}
|
|
72301
72682
|
let config2 = {};
|
|
72302
72683
|
try {
|
|
72303
|
-
config2 = JSON.parse(
|
|
72684
|
+
config2 = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
72304
72685
|
} catch {}
|
|
72305
72686
|
const keys = key2.split(".");
|
|
72306
72687
|
let obj = config2;
|
|
@@ -72440,7 +72821,7 @@ function registerConfigServeCommands(program2) {
|
|
|
72440
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) => {
|
|
72441
72822
|
const globalOpts = program2.opts();
|
|
72442
72823
|
const { listSecretFindings: listSecretFindings2 } = await Promise.resolve().then(() => (init_redaction(), exports_redaction));
|
|
72443
|
-
const value = opts.file ?
|
|
72824
|
+
const value = opts.file ? readFileSync18(opts.file, "utf-8") : text2 || "";
|
|
72444
72825
|
const findings = listSecretFindings2(value);
|
|
72445
72826
|
if (globalOpts.json) {
|
|
72446
72827
|
output({ ok: findings.length === 0, findings }, true);
|
|
@@ -74006,7 +74387,7 @@ __export(exports_task_route_sources, {
|
|
|
74006
74387
|
});
|
|
74007
74388
|
import { Database as Database4 } from "bun:sqlite";
|
|
74008
74389
|
import { createHash as createHash19 } from "crypto";
|
|
74009
|
-
import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as
|
|
74390
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as statSync12 } from "fs";
|
|
74010
74391
|
import { basename as basename10, dirname as dirname14, join as join26, resolve as resolve22 } from "path";
|
|
74011
74392
|
function normalizePath6(input) {
|
|
74012
74393
|
return resolve22(input);
|
|
@@ -74082,7 +74463,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
74082
74463
|
}
|
|
74083
74464
|
let rootStat;
|
|
74084
74465
|
try {
|
|
74085
|
-
rootStat =
|
|
74466
|
+
rootStat = statSync12(rootPath);
|
|
74086
74467
|
} catch (error2) {
|
|
74087
74468
|
const ref = createStoreRef(join26(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
74088
74469
|
errors2.push({
|
|
@@ -74815,7 +75196,7 @@ __export(exports_query_commands, {
|
|
|
74815
75196
|
registerQueryCommands: () => registerQueryCommands
|
|
74816
75197
|
});
|
|
74817
75198
|
import chalk9 from "chalk";
|
|
74818
|
-
import { readFileSync as
|
|
75199
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync12 } from "fs";
|
|
74819
75200
|
function parseJsonObjectOption2(value, label) {
|
|
74820
75201
|
if (!value)
|
|
74821
75202
|
return;
|
|
@@ -75835,13 +76216,13 @@ Findings`));
|
|
|
75835
76216
|
try {
|
|
75836
76217
|
const db = getDatabase();
|
|
75837
76218
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
75838
|
-
const { statSync:
|
|
76219
|
+
const { statSync: statSync13 } = await import("fs");
|
|
75839
76220
|
const { join: join27 } = await import("path");
|
|
75840
76221
|
const { getHomeDir: getHomeDir2 } = await Promise.resolve().then(() => (init_sync_utils(), exports_sync_utils));
|
|
75841
76222
|
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join27(getHomeDir2(), ".hasna", "todos", "todos.db");
|
|
75842
76223
|
let size = "unknown";
|
|
75843
76224
|
try {
|
|
75844
|
-
size = `${(
|
|
76225
|
+
size = `${(statSync13(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
75845
76226
|
} catch {}
|
|
75846
76227
|
checks.push({ name: "Database", ok: true, message: `${row.count} tasks \xB7 ${size} \xB7 ${chalk9.dim(dbPath)}` });
|
|
75847
76228
|
} catch (e) {
|
|
@@ -76558,7 +76939,7 @@ Findings`));
|
|
|
76558
76939
|
const sessionId = opts.session || globalOpts.session || undefined;
|
|
76559
76940
|
try {
|
|
76560
76941
|
if (opts.import) {
|
|
76561
|
-
const bundle = JSON.parse(
|
|
76942
|
+
const bundle = JSON.parse(readFileSync19(opts.import, "utf-8"));
|
|
76562
76943
|
const result = importHandoffBundle(bundle, { apply: opts.apply }, db);
|
|
76563
76944
|
if (opts.json || globalOpts.json) {
|
|
76564
76945
|
console.log(JSON.stringify(result));
|
|
@@ -76932,7 +77313,7 @@ Findings`));
|
|
|
76932
77313
|
});
|
|
76933
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) => {
|
|
76934
77315
|
try {
|
|
76935
|
-
const result = importCalendarIcs(
|
|
77316
|
+
const result = importCalendarIcs(readFileSync19(path, "utf-8"));
|
|
76936
77317
|
if (opts.json || program2.opts().json) {
|
|
76937
77318
|
output(result, true);
|
|
76938
77319
|
return;
|
|
@@ -77083,7 +77464,7 @@ Findings`));
|
|
|
77083
77464
|
});
|
|
77084
77465
|
board.command("import <path>").description("Import local board definitions from a JSON bundle").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
77085
77466
|
try {
|
|
77086
|
-
const bundle = JSON.parse(
|
|
77467
|
+
const bundle = JSON.parse(readFileSync19(path, "utf-8"));
|
|
77087
77468
|
const result = importTaskBoardBundle(bundle);
|
|
77088
77469
|
if (opts.json || program2.opts().json) {
|
|
77089
77470
|
output(result, true);
|
|
@@ -77459,7 +77840,7 @@ Findings`));
|
|
|
77459
77840
|
const { importExternalIssues: importExternalIssues2 } = await Promise.resolve().then(() => (init_external_issue_importers(), exports_external_issue_importers));
|
|
77460
77841
|
let body2 = text2 || "";
|
|
77461
77842
|
if (opts.file)
|
|
77462
|
-
body2 =
|
|
77843
|
+
body2 = readFileSync19(opts.file, "utf-8");
|
|
77463
77844
|
if (!body2 && !opts.url && !process.stdin.isTTY)
|
|
77464
77845
|
body2 = await Bun.stdin.text();
|
|
77465
77846
|
if (!body2.trim() && !opts.url) {
|
|
@@ -77516,7 +77897,7 @@ Findings`));
|
|
|
77516
77897
|
} = await Promise.resolve().then(() => (init_tester_issue_reports(), exports_tester_issue_reports));
|
|
77517
77898
|
let body2 = jsonText || "";
|
|
77518
77899
|
if (opts.file)
|
|
77519
|
-
body2 =
|
|
77900
|
+
body2 = readFileSync19(opts.file, "utf-8");
|
|
77520
77901
|
if (!body2 && !process.stdin.isTTY)
|
|
77521
77902
|
body2 = await Bun.stdin.text();
|
|
77522
77903
|
if (!body2.trim()) {
|
|
@@ -77559,11 +77940,11 @@ Findings`));
|
|
|
77559
77940
|
const inbox = program2.command("inbox").description("Capture local inbox items from pasted errors, CI logs, git context, files, or GitHub issue URLs");
|
|
77560
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) => {
|
|
77561
77942
|
const globalOpts = program2.opts();
|
|
77562
|
-
const { readFileSync:
|
|
77943
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
77563
77944
|
const { createInboxItem: createInboxItem2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
|
|
77564
77945
|
let body2 = text2 || "";
|
|
77565
77946
|
if (opts.file)
|
|
77566
|
-
body2 =
|
|
77947
|
+
body2 = readFileSync20(opts.file, "utf-8");
|
|
77567
77948
|
if (!body2 && !process.stdin.isTTY)
|
|
77568
77949
|
body2 = await Bun.stdin.text();
|
|
77569
77950
|
if (!body2.trim()) {
|
|
@@ -77623,11 +78004,11 @@ ${diff}` : null].filter(Boolean).join(`
|
|
|
77623
78004
|
});
|
|
77624
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) => {
|
|
77625
78006
|
const globalOpts = program2.opts();
|
|
77626
|
-
const { readFileSync:
|
|
78007
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
77627
78008
|
const { previewNaturalLanguageIntake: previewNaturalLanguageIntake2 } = await Promise.resolve().then(() => (init_natural_language_intake(), exports_natural_language_intake));
|
|
77628
78009
|
let body2 = text2 || "";
|
|
77629
78010
|
if (opts.file)
|
|
77630
|
-
body2 =
|
|
78011
|
+
body2 = readFileSync20(opts.file, "utf-8");
|
|
77631
78012
|
if (!body2 && !process.stdin.isTTY)
|
|
77632
78013
|
body2 = await Bun.stdin.text();
|
|
77633
78014
|
if (!body2.trim()) {
|
|
@@ -77954,7 +78335,7 @@ __export(exports_mcp_hooks_commands, {
|
|
|
77954
78335
|
});
|
|
77955
78336
|
import chalk10 from "chalk";
|
|
77956
78337
|
import { execSync as execSync3 } from "child_process";
|
|
77957
|
-
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";
|
|
77958
78339
|
import { dirname as dirname15, join as join27 } from "path";
|
|
77959
78340
|
function getMcpBinaryPath() {
|
|
77960
78341
|
try {
|
|
@@ -77971,7 +78352,7 @@ function readJsonFile2(path) {
|
|
|
77971
78352
|
if (!existsSync26(path))
|
|
77972
78353
|
return {};
|
|
77973
78354
|
try {
|
|
77974
|
-
return JSON.parse(
|
|
78355
|
+
return JSON.parse(readFileSync20(path, "utf-8"));
|
|
77975
78356
|
} catch {
|
|
77976
78357
|
return {};
|
|
77977
78358
|
}
|
|
@@ -77986,7 +78367,7 @@ function writeJsonFile2(path, data) {
|
|
|
77986
78367
|
function readTomlFile(path) {
|
|
77987
78368
|
if (!existsSync26(path))
|
|
77988
78369
|
return "";
|
|
77989
|
-
return
|
|
78370
|
+
return readFileSync20(path, "utf-8");
|
|
77990
78371
|
}
|
|
77991
78372
|
function writeTomlFile(path, content) {
|
|
77992
78373
|
const dir = dirname15(path);
|
|
@@ -79177,7 +79558,7 @@ Artifacts:`));
|
|
|
79177
79558
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
79178
79559
|
const marker = "# todos-auto-link";
|
|
79179
79560
|
if (existsSync26(hookPath)) {
|
|
79180
|
-
const existing =
|
|
79561
|
+
const existing = readFileSync20(hookPath, "utf-8");
|
|
79181
79562
|
if (existing.includes(marker)) {
|
|
79182
79563
|
console.log(chalk10.yellow("Hook already installed."));
|
|
79183
79564
|
return;
|
|
@@ -79207,7 +79588,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
79207
79588
|
console.log(chalk10.dim("No post-commit hook found."));
|
|
79208
79589
|
return;
|
|
79209
79590
|
}
|
|
79210
|
-
const content =
|
|
79591
|
+
const content = readFileSync20(hookPath, "utf-8");
|
|
79211
79592
|
if (!content.includes(marker)) {
|
|
79212
79593
|
console.log(chalk10.dim("Hook not managed by todos."));
|
|
79213
79594
|
return;
|
|
@@ -79458,7 +79839,7 @@ var STDIN_SENTINEL = "-";
|
|
|
79458
79839
|
var init_delegation_brief = () => {};
|
|
79459
79840
|
|
|
79460
79841
|
// src/lib/delegation-policy.ts
|
|
79461
|
-
import { readFileSync as
|
|
79842
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
79462
79843
|
import { homedir as homedir4 } from "os";
|
|
79463
79844
|
import { join as join28 } from "path";
|
|
79464
79845
|
function defaultDelegationEmbargoPath() {
|
|
@@ -79466,7 +79847,7 @@ function defaultDelegationEmbargoPath() {
|
|
|
79466
79847
|
}
|
|
79467
79848
|
function loadDelegationEmbargo(path = defaultDelegationEmbargoPath()) {
|
|
79468
79849
|
try {
|
|
79469
|
-
const parsed = JSON.parse(
|
|
79850
|
+
const parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
79470
79851
|
const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.embargoed) ? parsed.embargoed : [];
|
|
79471
79852
|
const names = new Set;
|
|
79472
79853
|
for (const entry2 of entries) {
|
|
@@ -79549,15 +79930,15 @@ __export(exports_delegate, {
|
|
|
79549
79930
|
registerDelegateCommands: () => registerDelegateCommands
|
|
79550
79931
|
});
|
|
79551
79932
|
import chalk12 from "chalk";
|
|
79552
|
-
import { readFileSync as
|
|
79933
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
79553
79934
|
function registerDelegateCommands(program2) {
|
|
79554
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) => {
|
|
79555
79936
|
const globalOpts = program2.opts();
|
|
79556
79937
|
const useJson = Boolean(opts.json || globalOpts.json);
|
|
79557
79938
|
try {
|
|
79558
79939
|
const brief = resolveDelegationBrief({ briefPath: opts.brief, briefText: opts.briefText }, {
|
|
79559
|
-
readFile: (path) =>
|
|
79560
|
-
readStdin: () =>
|
|
79940
|
+
readFile: (path) => readFileSync22(path, "utf8"),
|
|
79941
|
+
readStdin: () => readFileSync22(0, "utf8")
|
|
79561
79942
|
});
|
|
79562
79943
|
if (!brief.ok)
|
|
79563
79944
|
handleError(new Error(brief.message));
|
|
@@ -79805,7 +80186,7 @@ __export(exports_machines, {
|
|
|
79805
80186
|
});
|
|
79806
80187
|
import chalk13 from "chalk";
|
|
79807
80188
|
import { execSync as execSync4 } from "child_process";
|
|
79808
|
-
import { readFileSync as
|
|
80189
|
+
import { readFileSync as readFileSync23, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
79809
80190
|
import { tmpdir as tmpdir5 } from "os";
|
|
79810
80191
|
import { join as join29 } from "path";
|
|
79811
80192
|
function getOrCreateLocalMachineName() {
|
|
@@ -79849,7 +80230,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
79849
80230
|
try {
|
|
79850
80231
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
79851
80232
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
79852
|
-
return JSON.parse(
|
|
80233
|
+
return JSON.parse(readFileSync23(localPath, "utf-8"));
|
|
79853
80234
|
} finally {
|
|
79854
80235
|
try {
|
|
79855
80236
|
runSsh(sshAddress, `rm -f ${shellQuote(remotePath)}`, 1e4);
|
|
@@ -84807,7 +85188,7 @@ var exports_roadmap_commands = {};
|
|
|
84807
85188
|
__export(exports_roadmap_commands, {
|
|
84808
85189
|
registerRoadmapCommands: () => registerRoadmapCommands
|
|
84809
85190
|
});
|
|
84810
|
-
import { readFileSync as
|
|
85191
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "fs";
|
|
84811
85192
|
import chalk24 from "chalk";
|
|
84812
85193
|
function splitList3(value) {
|
|
84813
85194
|
return value?.split(";").flatMap((part) => part.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
@@ -85036,7 +85417,7 @@ function registerRoadmapCommands(program2) {
|
|
|
85036
85417
|
const globalOpts = globalOptions(program2);
|
|
85037
85418
|
try {
|
|
85038
85419
|
const { importRoadmapBundle: importRoadmapBundle2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
85039
|
-
const bundle = JSON.parse(
|
|
85420
|
+
const bundle = JSON.parse(readFileSync24(path, "utf8"));
|
|
85040
85421
|
const result = importRoadmapBundle2(bundle, { apply: Boolean(opts.apply) });
|
|
85041
85422
|
if (globalOpts.json) {
|
|
85042
85423
|
output(result, true);
|
|
@@ -85796,6 +86177,15 @@ function createShadowTodosStorageAdapter(options) {
|
|
|
85796
86177
|
mirror.enqueueUpsert("plans", plan, context);
|
|
85797
86178
|
return plan;
|
|
85798
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
|
+
},
|
|
85799
86189
|
async delete(id, context) {
|
|
85800
86190
|
const deleted = await local.plans.delete(id, context);
|
|
85801
86191
|
if (deleted)
|