@hasna/todos 0.15.10 → 0.15.12

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/contracts.js CHANGED
@@ -12379,7 +12379,7 @@ var init_tasks = __esm(() => {
12379
12379
  // package.json
12380
12380
  var package_default = {
12381
12381
  name: "@hasna/todos",
12382
- version: "0.15.10",
12382
+ version: "0.15.12",
12383
12383
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12384
12384
  type: "module",
12385
12385
  main: "dist/index.js",
package/dist/index.js CHANGED
@@ -12492,7 +12492,7 @@ var init_dispatches = __esm(() => {
12492
12492
  // package.json
12493
12493
  var package_default = {
12494
12494
  name: "@hasna/todos",
12495
- version: "0.15.10",
12495
+ version: "0.15.12",
12496
12496
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12497
12497
  type: "module",
12498
12498
  main: "dist/index.js",
@@ -25321,6 +25321,7 @@ function scanSqliteIntegrity(db = getDatabase()) {
25321
25321
  }
25322
25322
 
25323
25323
  // src/lib/plan-project-link-contract.ts
25324
+ init_types();
25324
25325
  import { createHash as createHash10 } from "crypto";
25325
25326
  var PLAN_PROJECT_LINK_SCHEMA_VERSION = "todos.plan-project-link.v1";
25326
25327
 
@@ -1,4 +1,4 @@
1
- import type { Plan, PlanProjectLinkReceipt, Task } from "../types/index.js";
1
+ import type { Plan, PlanProjectLinkReceipt, PlanProjectLinkResult, PlanProjectLinkRollbackResult, Task } from "../types/index.js";
2
2
  export declare const PLAN_PROJECT_LINK_SCHEMA_VERSION: "todos.plan-project-link.v1";
3
3
  export type PlanProjectLinkErrorCode = "PLAN_PROJECT_LINK_PLAN_NOT_FOUND" | "PLAN_PROJECT_LINK_PROJECT_NOT_FOUND" | "PLAN_PROJECT_LINK_PLAN_REVISION_CONFLICT" | "PLAN_PROJECT_LINK_PROJECT_REVISION_CONFLICT" | "PLAN_PROJECT_LINK_SCOPE_COLLISION" | "PLAN_PROJECT_LINK_IDEMPOTENCY_KEY_INVALID" | "PLAN_PROJECT_LINK_IDEMPOTENCY_CONFLICT" | "PLAN_PROJECT_LINK_RECEIPT_NOT_FOUND" | "PLAN_PROJECT_LINK_RESULT_DRIFT" | "PLAN_PROJECT_LINK_ROLLBACK_CONFLICT" | "PLAN_PROJECT_LINK_UNSUPPORTED";
4
4
  export declare class PlanProjectLinkError extends Error {
@@ -14,4 +14,19 @@ export declare function planProjectLinkRollbackReceiptId(receiptId: string): str
14
14
  export declare function planProjectLinkRequestHash(planId: string, projectId: string): string;
15
15
  export declare function planProjectLinkResultDigest(plan: Pick<Plan, "id" | "project_id">, tasks: Task[]): string;
16
16
  export declare function assertPlanProjectLinkReceipt(value: unknown): PlanProjectLinkReceipt;
17
+ type PlanProjectLinkResponseExpectation = {
18
+ mode: "plan" | "apply";
19
+ plan_id: string;
20
+ project_id: string;
21
+ idempotency_key?: string;
22
+ };
23
+ type PlanProjectLinkRollbackResponseExpectation = {
24
+ plan_id: string;
25
+ receipt_id: string;
26
+ };
27
+ /** Validate an untrusted HTTP plan/apply response before the CLI can report success. */
28
+ export declare function assertPlanProjectLinkResponse(value: unknown, expectation: PlanProjectLinkResponseExpectation): PlanProjectLinkResult;
29
+ /** Validate an untrusted HTTP rollback response before the CLI can report success. */
30
+ export declare function assertPlanProjectLinkRollbackResponse(value: unknown, expectation: PlanProjectLinkRollbackResponseExpectation): PlanProjectLinkRollbackResult;
31
+ export {};
17
32
  //# sourceMappingURL=plan-project-link-contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan-project-link-contract.d.ts","sourceRoot":"","sources":["../../src/lib/plan-project-link-contract.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE5E,eAAO,MAAM,gCAAgC,EAAG,4BAAqC,CAAC;AAEtF,MAAM,MAAM,wBAAwB,GAChC,kCAAkC,GAClC,qCAAqC,GACrC,0CAA0C,GAC1C,6CAA6C,GAC7C,mCAAmC,GACnC,2CAA2C,GAC3C,wCAAwC,GACxC,qCAAqC,GACrC,gCAAgC,GAChC,qCAAqC,GACrC,+BAA+B,CAAC;AAEpC,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,wBAAwB;IAEvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFhC,IAAI,EAAE,wBAAwB,EACvC,OAAO,EAAE,MAAM,EACN,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAKjD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQnE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE5D;AAED,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CASxF;AAED,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAQxG;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAsBnF"}
1
+ {"version":3,"file":"plan-project-link-contract.d.ts","sourceRoot":"","sources":["../../src/lib/plan-project-link-contract.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,IAAI,EACJ,sBAAsB,EACtB,qBAAqB,EACrB,6BAA6B,EAC7B,IAAI,EACL,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,gCAAgC,EAAG,4BAAqC,CAAC;AAEtF,MAAM,MAAM,wBAAwB,GAChC,kCAAkC,GAClC,qCAAqC,GACrC,0CAA0C,GAC1C,6CAA6C,GAC7C,mCAAmC,GACnC,2CAA2C,GAC3C,wCAAwC,GACxC,qCAAqC,GACrC,gCAAgC,GAChC,qCAAqC,GACrC,+BAA+B,CAAC;AAEpC,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,wBAAwB;IAEvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFhC,IAAI,EAAE,wBAAwB,EACvC,OAAO,EAAE,MAAM,EACN,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAKjD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQnE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE5D;AAED,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CASxF;AAED,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAQxG;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAsBnF;AAED,KAAK,kCAAkC,GAAG;IACxC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,KAAK,0CAA0C,GAAG;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AA8RF,wFAAwF;AACxF,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,kCAAkC,GAC9C,qBAAqB,CA2CvB;AAED,sFAAsF;AACtF,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,0CAA0C,GACtD,6BAA6B,CA6B/B"}
package/dist/mcp/index.js CHANGED
@@ -18443,6 +18443,63 @@ var init_assignee_context = __esm(() => {
18443
18443
  init_assignee_validation();
18444
18444
  });
18445
18445
 
18446
+ // src/lib/plan-project-link-contract.ts
18447
+ import { createHash as createHash3 } from "crypto";
18448
+ function canonicalPlanProjectLinkJson(value) {
18449
+ if (value === null || typeof value !== "object")
18450
+ return JSON.stringify(value);
18451
+ if (Array.isArray(value))
18452
+ return `[${value.map(canonicalPlanProjectLinkJson).join(",")}]`;
18453
+ return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalPlanProjectLinkJson(item)}`).join(",")}}`;
18454
+ }
18455
+ function planProjectLinkDigest(value) {
18456
+ return createHash3("sha256").update(canonicalPlanProjectLinkJson(value)).digest("hex");
18457
+ }
18458
+ function normalizePlanProjectLinkIdempotencyKey(value) {
18459
+ const key = value?.trim() ?? "";
18460
+ if (key.length < 8 || key.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(key)) {
18461
+ throw new PlanProjectLinkError("PLAN_PROJECT_LINK_IDEMPOTENCY_KEY_INVALID", "idempotency_key must be 8-128 ASCII letters, digits, dots, underscores, colons, or hyphens");
18462
+ }
18463
+ return key;
18464
+ }
18465
+ function planProjectLinkReceiptId(idempotencyKey) {
18466
+ return `pplr_${planProjectLinkDigest({ idempotency_key: idempotencyKey }).slice(0, 48)}`;
18467
+ }
18468
+ function planProjectLinkRollbackReceiptId(receiptId) {
18469
+ return `pplr_inverse_${planProjectLinkDigest({ accepted_receipt_id: receiptId }).slice(0, 38)}`;
18470
+ }
18471
+ function planProjectLinkRequestHash(planId, projectId) {
18472
+ return planProjectLinkDigest({ plan_id: planId, project_id: projectId });
18473
+ }
18474
+ function planProjectLinkResultDigest(plan, tasks) {
18475
+ return planProjectLinkDigest({
18476
+ plan_id: plan.id,
18477
+ plan_project_id: plan.project_id,
18478
+ tasks: tasks.map((task) => ({ id: task.id, plan_id: task.plan_id, project_id: task.project_id })).sort((left, right) => left.id.localeCompare(right.id))
18479
+ });
18480
+ }
18481
+ function assertPlanProjectLinkReceipt(value) {
18482
+ const receipt = value;
18483
+ if (!receipt || typeof receipt !== "object" || receipt.schema_version !== PLAN_PROJECT_LINK_SCHEMA_VERSION || typeof receipt.receipt_id !== "string" || typeof receipt.idempotency_key !== "string" || typeof receipt.plan_id !== "string" || typeof receipt.project_id !== "string" || !Array.isArray(receipt.task_ids) || receipt.task_ids.some((id) => typeof id !== "string") || !receipt.prior_task_project_ids || typeof receipt.prior_task_project_ids !== "object" || typeof receipt.result_digest !== "string") {
18484
+ throw new PlanProjectLinkError("PLAN_PROJECT_LINK_RECEIPT_NOT_FOUND", "Stored plan-project-link receipt is invalid");
18485
+ }
18486
+ return receipt;
18487
+ }
18488
+ var PLAN_PROJECT_LINK_SCHEMA_VERSION = "todos.plan-project-link.v1", PlanProjectLinkError;
18489
+ var init_plan_project_link_contract = __esm(() => {
18490
+ init_types();
18491
+ PlanProjectLinkError = class PlanProjectLinkError extends Error {
18492
+ code;
18493
+ details;
18494
+ constructor(code, message, details = {}) {
18495
+ super(message);
18496
+ this.code = code;
18497
+ this.details = details;
18498
+ this.name = "PlanProjectLinkError";
18499
+ }
18500
+ };
18501
+ });
18502
+
18446
18503
  // src/pr-groups/types.ts
18447
18504
  var PR_GROUP_LEDGER_SCHEMA_VERSION = 1, PR_GROUP_REPAIR_CYCLE_LIMIT = 2, PrGroupLedgerError;
18448
18505
  var init_types3 = __esm(() => {
@@ -18459,9 +18516,9 @@ var init_types3 = __esm(() => {
18459
18516
  });
18460
18517
 
18461
18518
  // src/pr-groups/ledger.ts
18462
- import { createHash as createHash3 } from "crypto";
18519
+ import { createHash as createHash4 } from "crypto";
18463
18520
  function sha2562(value) {
18464
- return createHash3("sha256").update(value).digest("hex");
18521
+ return createHash4("sha256").update(value).digest("hex");
18465
18522
  }
18466
18523
  function stableValue(value) {
18467
18524
  if (Array.isArray(value))
@@ -20957,6 +21014,12 @@ function toListQuery(filter = {}) {
20957
21014
  query["offset"] = filter.offset;
20958
21015
  return query;
20959
21016
  }
21017
+ function priorityRank(priority) {
21018
+ return PRIORITY_RANK[priority ?? ""] ?? 4;
21019
+ }
21020
+ function compareCloudTaskOrder(a, b) {
21021
+ return priorityRank(a.priority) - priorityRank(b.priority) || b.created_at.localeCompare(a.created_at) || a.id.localeCompare(b.id);
21022
+ }
20960
21023
  async function fetchListTagsCapability(client) {
20961
21024
  const document = await requiredRemoteRoute(client, "/v1/openapi.json", () => client.transport.get("/openapi.json"));
20962
21025
  if (!document || typeof document !== "object" || Array.isArray(document))
@@ -20978,13 +21041,40 @@ async function requireTagsFilterCapability(client) {
20978
21041
  throw new Error(`REMOTE_TAGS_FILTER_UNSUPPORTED: configured Todos authority ${authority} does not advertise the tags ` + "query param on GET /v1/tasks; deploy the current @hasna/todos /v1 server to filter by tag; " + "no unfiltered task read was issued");
20979
21042
  }
20980
21043
  }
20981
- async function cloudListTasks(client, filter = {}) {
20982
- if (filter.tags?.length)
20983
- await requireTagsFilterCapability(client);
21044
+ async function requestCloudTaskPage(client, filter) {
20984
21045
  const res = await requiredRemoteRoute(client, "/v1/tasks", () => client.list("tasks", { query: toListQuery(filter) }));
20985
21046
  const envelope = res.raw;
20986
21047
  return Array.isArray(envelope?.tasks) ? envelope.tasks : res.items;
20987
21048
  }
21049
+ async function cloudListTasks(client, filter = {}) {
21050
+ if (filter.tags?.length)
21051
+ await requireTagsFilterCapability(client);
21052
+ const statuses = Array.isArray(filter.status) ? filter.status : undefined;
21053
+ if (!statuses)
21054
+ return requestCloudTaskPage(client, filter);
21055
+ if (statuses.length === 0)
21056
+ return [];
21057
+ if (statuses.length === 1) {
21058
+ return requestCloudTaskPage(client, { ...filter, status: statuses[0] });
21059
+ }
21060
+ const { status: _status, limit, offset, ...baseFilter } = filter;
21061
+ const start = offset ?? 0;
21062
+ const windowEnd = typeof limit === "number" ? start + limit : undefined;
21063
+ const pages = await Promise.all(statuses.map((status) => requestCloudTaskPage(client, {
21064
+ ...baseFilter,
21065
+ status,
21066
+ ...windowEnd === undefined ? {} : { limit: windowEnd }
21067
+ })));
21068
+ const seen = new Set;
21069
+ const union = pages.flat().filter((task) => {
21070
+ if (seen.has(task.id))
21071
+ return false;
21072
+ seen.add(task.id);
21073
+ return true;
21074
+ });
21075
+ union.sort(compareCloudTaskOrder);
21076
+ return union.slice(start, windowEnd);
21077
+ }
20988
21078
  async function cloudGetTask(client, id) {
20989
21079
  const raw = await client.get("tasks", id);
20990
21080
  return raw == null ? null : unwrapTask(raw);
@@ -21199,10 +21289,11 @@ async function cloudResolveTaskListRef(client, ref, projectId) {
21199
21289
  }
21200
21290
  throw new Error(`Task list not found: "${input}"`);
21201
21291
  }
21202
- var UUID_RE, TRANSPORT_TOKENS, completionCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, listTagsCapabilityCache;
21292
+ var UUID_RE, TRANSPORT_TOKENS, completionCapabilityCache, gitRefCapabilityCache, SERVER_MODE_CANDIDATES, cachedServerMode = null, PRIORITY_RANK, listTagsCapabilityCache;
21203
21293
  var init_cloud_router = __esm(() => {
21204
21294
  init_types();
21205
21295
  init_redaction();
21296
+ init_plan_project_link_contract();
21206
21297
  init_http_client();
21207
21298
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
21208
21299
  TRANSPORT_TOKENS = {
@@ -21215,7 +21306,9 @@ var init_cloud_router = __esm(() => {
21215
21306
  hybrid: "http"
21216
21307
  };
21217
21308
  completionCapabilityCache = new Map;
21309
+ gitRefCapabilityCache = new Map;
21218
21310
  SERVER_MODE_CANDIDATES = ["postgres", "cloud", "self_hosted"];
21311
+ PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
21219
21312
  listTagsCapabilityCache = new Map;
21220
21313
  });
21221
21314
 
@@ -23608,12 +23701,12 @@ var init_local_notifications = __esm(() => {
23608
23701
  });
23609
23702
 
23610
23703
  // src/lib/local-encryption.ts
23611
- import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
23704
+ import { createCipheriv, createDecipheriv, createHash as createHash5, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
23612
23705
  function now3() {
23613
23706
  return new Date().toISOString();
23614
23707
  }
23615
23708
  function sha2563(value) {
23616
- return createHash4("sha256").update(value).digest("hex");
23709
+ return createHash5("sha256").update(value).digest("hex");
23617
23710
  }
23618
23711
  function normalizeProfileName(value) {
23619
23712
  const name = (value || DEFAULT_ENCRYPTION_PROFILE).trim();
@@ -25751,7 +25844,7 @@ var init_capacity_forecasts = __esm(() => {
25751
25844
  });
25752
25845
 
25753
25846
  // src/lib/audit-ledger.ts
25754
- import { createHash as createHash5 } from "crypto";
25847
+ import { createHash as createHash6 } from "crypto";
25755
25848
  function canonicalize(value) {
25756
25849
  if (value === null || typeof value !== "object")
25757
25850
  return JSON.stringify(value);
@@ -25761,7 +25854,7 @@ function canonicalize(value) {
25761
25854
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(object[key])}`).join(",")}}`;
25762
25855
  }
25763
25856
  function hash(value) {
25764
- return createHash5("sha256").update(value).digest("hex");
25857
+ return createHash6("sha256").update(value).digest("hex");
25765
25858
  }
25766
25859
  function parsePayload2(value) {
25767
25860
  if (!value)
@@ -35309,7 +35402,7 @@ var package_default;
35309
35402
  var init_package = __esm(() => {
35310
35403
  package_default = {
35311
35404
  name: "@hasna/todos",
35312
- version: "0.15.10",
35405
+ version: "0.15.12",
35313
35406
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
35314
35407
  type: "module",
35315
35408
  main: "dist/index.js",
@@ -36023,7 +36116,7 @@ var init_local_bridge = __esm(() => {
36023
36116
  });
36024
36117
 
36025
36118
  // src/lib/local-backups.ts
36026
- import { createHash as createHash6 } from "crypto";
36119
+ import { createHash as createHash7 } from "crypto";
36027
36120
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
36028
36121
  import { dirname as dirname7, resolve as resolve12 } from "path";
36029
36122
  import { mkdirSync as mkdirSync6 } from "fs";
@@ -36036,7 +36129,7 @@ function stableJson2(value) {
36036
36129
  return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson2(record[key])}`).join(",")}}`;
36037
36130
  }
36038
36131
  function sha2564(value) {
36039
- return createHash6("sha256").update(stableJson2(value)).digest("hex");
36132
+ return createHash7("sha256").update(stableJson2(value)).digest("hex");
36040
36133
  }
36041
36134
  function sqliteIntegrity(db) {
36042
36135
  let quick = "unknown";
@@ -36686,7 +36779,7 @@ var init_onboarding_fixtures = __esm(() => {
36686
36779
  });
36687
36780
 
36688
36781
  // src/lib/local-snapshots.ts
36689
- import { createHash as createHash7 } from "crypto";
36782
+ import { createHash as createHash8 } from "crypto";
36690
36783
  function source(version) {
36691
36784
  return {
36692
36785
  packageName: "@hasna/todos",
@@ -36710,7 +36803,7 @@ function stable(value) {
36710
36803
  return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stable(item)]));
36711
36804
  }
36712
36805
  function sha2565(value) {
36713
- return createHash7("sha256").update(JSON.stringify(stable(value))).digest("hex");
36806
+ return createHash8("sha256").update(JSON.stringify(stable(value))).digest("hex");
36714
36807
  }
36715
36808
  function latestTimestamp2(items, fallback) {
36716
36809
  const timestamps = [];
@@ -37276,7 +37369,7 @@ var init_retrospectives = __esm(() => {
37276
37369
  });
37277
37370
 
37278
37371
  // src/lib/agent-replay-simulator.ts
37279
- import { createHash as createHash8 } from "crypto";
37372
+ import { createHash as createHash9 } from "crypto";
37280
37373
  function isObject(value) {
37281
37374
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
37282
37375
  }
@@ -37297,7 +37390,7 @@ function stable2(value) {
37297
37390
  return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
37298
37391
  }
37299
37392
  function fingerprint2(value) {
37300
- return createHash8("sha256").update(JSON.stringify(stable2(value))).digest("hex");
37393
+ return createHash9("sha256").update(JSON.stringify(stable2(value))).digest("hex");
37301
37394
  }
37302
37395
  function unpackFixture(input) {
37303
37396
  if (!isObject(input))
@@ -37584,7 +37677,7 @@ __export(exports_local_extensions, {
37584
37677
  getLocalExtension: () => getLocalExtension,
37585
37678
  discoverLocalExtensions: () => discoverLocalExtensions
37586
37679
  });
37587
- import { createHash as createHash9, createVerify } from "crypto";
37680
+ import { createHash as createHash10, createVerify } from "crypto";
37588
37681
  import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
37589
37682
  import { basename as basename5, join as join11, resolve as resolve13 } from "path";
37590
37683
  function isObject2(value) {
@@ -37670,7 +37763,7 @@ function parseJson(path) {
37670
37763
  return JSON.parse(readFileSync9(path, "utf8"));
37671
37764
  }
37672
37765
  function sha2566(bytes) {
37673
- return `sha256:${createHash9("sha256").update(bytes).digest("hex")}`;
37766
+ return `sha256:${createHash10("sha256").update(bytes).digest("hex")}`;
37674
37767
  }
37675
37768
  function compareVersions(a, b) {
37676
37769
  const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -38145,7 +38238,7 @@ function issueToTask(issue, opts) {
38145
38238
  var init_github = () => {};
38146
38239
 
38147
38240
  // src/db/inbox.ts
38148
- import { createHash as createHash10 } from "crypto";
38241
+ import { createHash as createHash11 } from "crypto";
38149
38242
  function parseMetadata3(value) {
38150
38243
  if (!value)
38151
38244
  return {};
@@ -38165,7 +38258,7 @@ function compactWhitespace(value) {
38165
38258
  function fingerprintInboxInput(input) {
38166
38259
  const sourceType = input.source_type || detectInboxSourceType(input.body, input.source_url);
38167
38260
  const normalized = compactWhitespace(sanitizePreWriteText(input.body, "inbox.fingerprint")).slice(0, 8000);
38168
- return createHash10("sha256").update(`${sourceType}
38261
+ return createHash11("sha256").update(`${sourceType}
38169
38262
  ${input.source_url || ""}
38170
38263
  ${normalized}`).digest("hex");
38171
38264
  }
@@ -42365,10 +42458,10 @@ __export(exports_extract, {
42365
42458
  EXTRACT_TAGS: () => EXTRACT_TAGS
42366
42459
  });
42367
42460
  import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
42368
- import { createHash as createHash11 } from "crypto";
42461
+ import { createHash as createHash12 } from "crypto";
42369
42462
  import { relative as relative5, resolve as resolve14, join as join12 } from "path";
42370
42463
  function stableHash(value) {
42371
- return createHash11("sha256").update(value).digest("hex");
42464
+ return createHash12("sha256").update(value).digest("hex");
42372
42465
  }
42373
42466
  function normalizePathForMatch(value) {
42374
42467
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -44092,13 +44185,13 @@ __export(exports_environment_snapshots, {
44092
44185
  compareEnvironmentSnapshotFiles: () => compareEnvironmentSnapshotFiles,
44093
44186
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
44094
44187
  });
44095
- import { createHash as createHash12 } from "crypto";
44188
+ import { createHash as createHash13 } from "crypto";
44096
44189
  import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
44097
44190
  import { hostname as hostname2, platform, arch } from "os";
44098
44191
  import { dirname as dirname8, join as join14, resolve as resolve15 } from "path";
44099
44192
  import { tmpdir as tmpdir3 } from "os";
44100
44193
  function sha2567(value) {
44101
- return createHash12("sha256").update(value).digest("hex");
44194
+ return createHash13("sha256").update(value).digest("hex");
44102
44195
  }
44103
44196
  function fileRecord(root, relativePath) {
44104
44197
  const path = join14(root, relativePath);
@@ -44689,62 +44782,6 @@ var init_cloud_client = __esm(() => {
44689
44782
  init_config();
44690
44783
  });
44691
44784
 
44692
- // src/lib/plan-project-link-contract.ts
44693
- import { createHash as createHash13 } from "crypto";
44694
- function canonicalPlanProjectLinkJson(value) {
44695
- if (value === null || typeof value !== "object")
44696
- return JSON.stringify(value);
44697
- if (Array.isArray(value))
44698
- return `[${value.map(canonicalPlanProjectLinkJson).join(",")}]`;
44699
- return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalPlanProjectLinkJson(item)}`).join(",")}}`;
44700
- }
44701
- function planProjectLinkDigest(value) {
44702
- return createHash13("sha256").update(canonicalPlanProjectLinkJson(value)).digest("hex");
44703
- }
44704
- function normalizePlanProjectLinkIdempotencyKey(value) {
44705
- const key = value?.trim() ?? "";
44706
- if (key.length < 8 || key.length > 128 || !/^[A-Za-z0-9._:-]+$/.test(key)) {
44707
- throw new PlanProjectLinkError("PLAN_PROJECT_LINK_IDEMPOTENCY_KEY_INVALID", "idempotency_key must be 8-128 ASCII letters, digits, dots, underscores, colons, or hyphens");
44708
- }
44709
- return key;
44710
- }
44711
- function planProjectLinkReceiptId(idempotencyKey) {
44712
- return `pplr_${planProjectLinkDigest({ idempotency_key: idempotencyKey }).slice(0, 48)}`;
44713
- }
44714
- function planProjectLinkRollbackReceiptId(receiptId) {
44715
- return `pplr_inverse_${planProjectLinkDigest({ accepted_receipt_id: receiptId }).slice(0, 38)}`;
44716
- }
44717
- function planProjectLinkRequestHash(planId, projectId) {
44718
- return planProjectLinkDigest({ plan_id: planId, project_id: projectId });
44719
- }
44720
- function planProjectLinkResultDigest(plan, tasks) {
44721
- return planProjectLinkDigest({
44722
- plan_id: plan.id,
44723
- plan_project_id: plan.project_id,
44724
- tasks: tasks.map((task2) => ({ id: task2.id, plan_id: task2.plan_id, project_id: task2.project_id })).sort((left, right) => left.id.localeCompare(right.id))
44725
- });
44726
- }
44727
- function assertPlanProjectLinkReceipt(value) {
44728
- const receipt = value;
44729
- if (!receipt || typeof receipt !== "object" || receipt.schema_version !== PLAN_PROJECT_LINK_SCHEMA_VERSION || typeof receipt.receipt_id !== "string" || typeof receipt.idempotency_key !== "string" || typeof receipt.plan_id !== "string" || typeof receipt.project_id !== "string" || !Array.isArray(receipt.task_ids) || receipt.task_ids.some((id) => typeof id !== "string") || !receipt.prior_task_project_ids || typeof receipt.prior_task_project_ids !== "object" || typeof receipt.result_digest !== "string") {
44730
- throw new PlanProjectLinkError("PLAN_PROJECT_LINK_RECEIPT_NOT_FOUND", "Stored plan-project-link receipt is invalid");
44731
- }
44732
- return receipt;
44733
- }
44734
- var PLAN_PROJECT_LINK_SCHEMA_VERSION = "todos.plan-project-link.v1", PlanProjectLinkError;
44735
- var init_plan_project_link_contract = __esm(() => {
44736
- PlanProjectLinkError = class PlanProjectLinkError extends Error {
44737
- code;
44738
- details;
44739
- constructor(code, message, details = {}) {
44740
- super(message);
44741
- this.code = code;
44742
- this.details = details;
44743
- this.name = "PlanProjectLinkError";
44744
- }
44745
- };
44746
- });
44747
-
44748
44785
  // src/db/plan-project-links.ts
44749
44786
  function getPlanProjectLinkReceipt(receiptId, db) {
44750
44787
  const d = db || getDatabase();
@@ -52350,6 +52387,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
52350
52387
  ProjectTaskListEnsureResult: projectTaskListEnsureResultSchema,
52351
52388
  ProjectTaskListRollbackResult: projectTaskListRollbackResultSchema,
52352
52389
  TaskComment: taskCommentSchema,
52390
+ TaskGitRef: taskGitRefSchema,
52353
52391
  Plan: planSchema,
52354
52392
  PlanProjectLinkReceipt: planProjectLinkReceiptSchema,
52355
52393
  PlanProjectLinkResult: planProjectLinkResultSchema,
@@ -53511,6 +53549,92 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
53511
53549
  }
53512
53550
  }
53513
53551
  },
53552
+ "/v1/tasks/{id}/refs": {
53553
+ get: {
53554
+ operationId: "listTaskGitRefs",
53555
+ summary: "List git branch and pull-request refs linked to a task",
53556
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
53557
+ responses: {
53558
+ "200": {
53559
+ content: {
53560
+ "application/json": {
53561
+ schema: {
53562
+ type: "object",
53563
+ additionalProperties: false,
53564
+ required: ["refs", "count"],
53565
+ properties: {
53566
+ refs: { type: "array", items: { $ref: "#/components/schemas/TaskGitRef" } },
53567
+ count: { type: "integer", minimum: 0 }
53568
+ }
53569
+ }
53570
+ }
53571
+ }
53572
+ }
53573
+ }
53574
+ },
53575
+ post: {
53576
+ operationId: "linkTaskGitRef",
53577
+ summary: "Link a git branch or pull-request ref to a task",
53578
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
53579
+ requestBody: {
53580
+ required: true,
53581
+ content: {
53582
+ "application/json": {
53583
+ schema: {
53584
+ type: "object",
53585
+ additionalProperties: false,
53586
+ required: ["ref_type", "name"],
53587
+ properties: {
53588
+ ref_type: { type: "string", enum: ["branch", "pull_request"] },
53589
+ name: { type: "string", minLength: 1 },
53590
+ url: { type: "string" },
53591
+ provider: { type: "string" },
53592
+ metadata: { type: "object", additionalProperties: true }
53593
+ }
53594
+ }
53595
+ }
53596
+ }
53597
+ },
53598
+ responses: {
53599
+ "201": {
53600
+ content: {
53601
+ "application/json": {
53602
+ schema: {
53603
+ type: "object",
53604
+ additionalProperties: false,
53605
+ required: ["ref"],
53606
+ properties: { ref: { $ref: "#/components/schemas/TaskGitRef" } }
53607
+ }
53608
+ }
53609
+ }
53610
+ }
53611
+ }
53612
+ }
53613
+ },
53614
+ "/v1/refs/{ref}": {
53615
+ get: {
53616
+ operationId: "findTaskGitRefs",
53617
+ summary: "Find task links by git branch or pull-request ref",
53618
+ parameters: [{ name: "ref", in: "path", required: true, schema: { type: "string" } }],
53619
+ responses: {
53620
+ "200": {
53621
+ content: {
53622
+ "application/json": {
53623
+ schema: {
53624
+ type: "object",
53625
+ additionalProperties: false,
53626
+ required: ["refs", "count"],
53627
+ properties: {
53628
+ refs: { type: "array", items: { $ref: "#/components/schemas/TaskGitRef" } },
53629
+ count: { type: "integer", minimum: 0 }
53630
+ }
53631
+ }
53632
+ }
53633
+ }
53634
+ }
53635
+ }
53636
+ }
53637
+ },
53514
53638
  "/v1/tasks/{id}/start": {
53515
53639
  post: {
53516
53640
  operationId: "startTask",
@@ -53962,7 +54086,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
53962
54086
  }
53963
54087
  };
53964
54088
  }
53965
- var taskSchema, projectSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
54089
+ var taskSchema, projectSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
53966
54090
  var init_openapi = __esm(() => {
53967
54091
  init_package_version();
53968
54092
  init_types();
@@ -54098,6 +54222,32 @@ var init_openapi = __esm(() => {
54098
54222
  created_at: { type: "string", format: "date-time" }
54099
54223
  }
54100
54224
  };
54225
+ taskGitRefSchema = {
54226
+ type: "object",
54227
+ additionalProperties: false,
54228
+ required: [
54229
+ "id",
54230
+ "task_id",
54231
+ "ref_type",
54232
+ "name",
54233
+ "url",
54234
+ "provider",
54235
+ "metadata",
54236
+ "created_at",
54237
+ "updated_at"
54238
+ ],
54239
+ properties: {
54240
+ id: { type: "string", minLength: 1 },
54241
+ task_id: { type: "string", minLength: 1 },
54242
+ ref_type: { type: "string", enum: ["branch", "pull_request"] },
54243
+ name: { type: "string", minLength: 1 },
54244
+ url: { type: "string", nullable: true },
54245
+ provider: { type: "string", nullable: true },
54246
+ metadata: { type: "object", additionalProperties: true },
54247
+ created_at: { type: "string", format: "date-time" },
54248
+ updated_at: { type: "string", format: "date-time" }
54249
+ }
54250
+ };
54101
54251
  planSchema = {
54102
54252
  type: "object",
54103
54253
  required: ["id", "slug", "name", "status", "created_at", "updated_at"],
package/dist/mcp.js CHANGED
@@ -41,7 +41,7 @@ var __require = import.meta.require;
41
41
  // package.json
42
42
  var package_default = {
43
43
  name: "@hasna/todos",
44
- version: "0.15.10",
44
+ version: "0.15.12",
45
45
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
46
46
  type: "module",
47
47
  main: "dist/index.js",
@@ -12382,7 +12382,7 @@ import { createHash as createHash5 } from "crypto";
12382
12382
  // package.json
12383
12383
  var package_default = {
12384
12384
  name: "@hasna/todos",
12385
- version: "0.15.10",
12385
+ version: "0.15.12",
12386
12386
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12387
12387
  type: "module",
12388
12388
  main: "dist/index.js",
@@ -12525,6 +12525,7 @@ function normalizeAgentNameInput(name) {
12525
12525
  init_creator_identity();
12526
12526
 
12527
12527
  // src/lib/plan-project-link-contract.ts
12528
+ init_types();
12528
12529
  import { createHash as createHash2 } from "crypto";
12529
12530
  var PLAN_PROJECT_LINK_SCHEMA_VERSION = "todos.plan-project-link.v1";
12530
12531
 
package/dist/registry.js CHANGED
@@ -12379,7 +12379,7 @@ var init_tasks = __esm(() => {
12379
12379
  // package.json
12380
12380
  var package_default = {
12381
12381
  name: "@hasna/todos",
12382
- version: "0.15.10",
12382
+ version: "0.15.12",
12383
12383
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12384
12384
  type: "module",
12385
12385
  main: "dist/index.js",
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "packageName": "@hasna/todos",
3
- "packageVersion": "0.15.10",
3
+ "packageVersion": "0.15.12",
4
4
  "repository": "https://github.com/hasna/todos.git",
5
- "gitCommit": "dd84cfb6a3378e752b50bc39216cadb5580b940c",
6
- "gitTree": "f16ba8feaa88db61a16ab2f1e9870907b956f0dc",
7
- "sourceTreeSha256": "3640eb1e471e33dc0ab91cab9ec4e7529e5b70940a4aa09401b4069573b6055c",
8
- "generatedAt": "2026-08-08T05:45:27.000Z"
5
+ "gitCommit": "dae785f5a49e6533a9405bef55feaaae3ee7ff6c",
6
+ "gitTree": "64227c25323ab56c0fecb8512f12b96ac0b3645e",
7
+ "sourceTreeSha256": "2284d1b1074d2fb2376c1b184d011e43337effe8fea1eb50241c0e07b272c380",
8
+ "generatedAt": "2026-08-08T11:31:06.000Z"
9
9
  }
package/dist/sdk/index.js CHANGED
@@ -868,6 +868,13 @@ class TodosV1Client {
868
868
  init
869
869
  });
870
870
  }
871
+ async findTaskGitRefs(ref, init) {
872
+ return this.request("GET", `/v1/refs/${encodeURIComponent(String(ref))}`, {
873
+ body: undefined,
874
+ query: undefined,
875
+ init
876
+ });
877
+ }
871
878
  async getStats(init) {
872
879
  return this.request("GET", `/v1/stats`, {
873
880
  body: undefined,
@@ -966,6 +973,20 @@ class TodosV1Client {
966
973
  init
967
974
  });
968
975
  }
976
+ async listTaskGitRefs(id, init) {
977
+ return this.request("GET", `/v1/tasks/${encodeURIComponent(String(id))}/refs`, {
978
+ body: undefined,
979
+ query: undefined,
980
+ init
981
+ });
982
+ }
983
+ async linkTaskGitRef(id, body, init) {
984
+ return this.request("POST", `/v1/tasks/${encodeURIComponent(String(id))}/refs`, {
985
+ body,
986
+ query: undefined,
987
+ init
988
+ });
989
+ }
969
990
  async startTask(id, init) {
970
991
  return this.request("POST", `/v1/tasks/${encodeURIComponent(String(id))}/start`, {
971
992
  body: undefined,