@wrongstack/tools 0.282.2 → 0.283.1

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/builtin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, execFileSync } from 'node:child_process';
2
2
  import * as Core from '@wrongstack/core';
3
- import { buildChildEnv, getDesignKitLoader, isDesignStack, loadActiveKit, applyTokenOverrides, setActiveKit, recordKitChoice, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, ToolValidationError, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, ToolError, FetchError, isPrivateIPv4, isPrivateIPv6, assessCommitSafety, compileGlob, expectDefined, recordPackageAction, detectPackageEcosystem, deepMerge, addLinkToTask, addNoteToTask, updateCheckOnTask, addCheckToTask, updateGoalMetricOnTask, addGoalMetricToTask, addDependency, updateTaskAssignment, assignTask, releaseTaskClaim, claimReadyTask, getTaskChain, setTaskChain, removeTask, moveTask, updateTask, getTask, transferTaskToBoard, copyTaskToBoard, mergeTasks, splitTask, addTask, removeColumn, updateColumn, addColumn, getKanbanOrchestrationSnapshot, listReadyTasks, searchKanban, deserializeTaskGraph, syncBoardFromTaskGraph, exportBoardToTaskGraph, serializeTaskGraph, exportBoardAsMarkdown, generateBoardFromDescription, createBoard, parseLinesIntoTasks, getBoard, removeBoard, duplicateBoard, updateBoard, listBoards, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, mutateTasks, formatTaskList, formatPlan, FsError, toErrorMessage as toErrorMessage$2, computeTaskItemProgress, loadPlan, savePlan, loadTasks, saveTasks, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
3
+ import { buildChildEnv, getDesignKitLoader, isDesignStack, loadActiveKit, applyTokenOverrides, setActiveKit, recordKitChoice, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, ToolValidationError, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, ToolError, FetchError, isPrivateIPv4, isPrivateIPv6, assessCommitSafety, compileGlob, expectDefined, recordPackageAction, detectPackageEcosystem, deepMerge, addLinkToTask, addNoteToTask, updateCheckOnTask, addCheckToTask, updateGoalMetricOnTask, addGoalMetricToTask, addDependency, getKanbanQueueHealth, listKanbanEvents, recoverStaleTaskAssignments, heartbeatTaskAssignment, updateTaskAssignment, assignTask, releaseTaskClaim, claimReadyTask, getTaskChain, setTaskChain, removeTask, moveTask, updateTask, getTask, transferTaskToBoard, copyTaskToBoard, mergeTasks, splitTask, addTask, removeColumn, updateColumn, addColumn, getKanbanOrchestrationSnapshot, listReadyTasks, searchKanban, deserializeTaskGraph, syncBoardFromTaskGraph, exportBoardToTaskGraph, serializeTaskGraph, exportBoardAsMarkdown, generateBoardFromDescription, createBoard, parseLinesIntoTasks, getBoard, removeBoard, duplicateBoard, updateBoard, listBoards, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, mutateTasks, formatTaskList, formatPlan, FsError, toErrorMessage as toErrorMessage$2, computeTaskItemProgress, loadPlan, savePlan, loadTasks, saveTasks, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
4
4
  import * as fs from 'node:fs';
5
5
  import { statSync, mkdirSync, createWriteStream } from 'node:fs';
6
6
  import * as fs2 from 'node:fs/promises';
@@ -2675,6 +2675,24 @@ var IndexStore = class {
2675
2675
  * When false, ranked search falls back to the LIKE + in-process BM25 path.
2676
2676
  */
2677
2677
  ftsAvailable = false;
2678
+ /**
2679
+ * Cache of prepared statements keyed by their SQL text. `DatabaseSync`
2680
+ * compiles SQL on every `.prepare()` call; for the fixed-SQL methods
2681
+ * (upsertFile, getFileMeta, deleteFile, insertRefs, …) that runs thousands
2682
+ * of times during a full reindex. `StatementSync` objects are reusable
2683
+ * across calls on the same connection, so we compile each distinct SQL once
2684
+ * and reuse it. Cleared in {@link close} when the connection is torn down.
2685
+ */
2686
+ stmtCache = /* @__PURE__ */ new Map();
2687
+ /** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
2688
+ stmt(sql) {
2689
+ let s = this.stmtCache.get(sql);
2690
+ if (s === void 0) {
2691
+ s = this.db.prepare(sql);
2692
+ this.stmtCache.set(sql, s);
2693
+ }
2694
+ return s;
2695
+ }
2678
2696
  /**
2679
2697
  * Execute a SQLite write operation with automatic retry on lock conflicts.
2680
2698
  *
@@ -2844,9 +2862,9 @@ var IndexStore = class {
2844
2862
  deleteSymbolsForFile(file) {
2845
2863
  this.runWithRetry(() => {
2846
2864
  if (this.ftsAvailable) {
2847
- this.db.prepare("DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
2865
+ this.stmt("DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)").run(file);
2848
2866
  }
2849
- this.db.prepare("DELETE FROM symbols WHERE file_fk = ?").run(file);
2867
+ this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
2850
2868
  });
2851
2869
  }
2852
2870
  /**
@@ -2858,13 +2876,13 @@ var IndexStore = class {
2858
2876
  this.runWithRetry(() => {
2859
2877
  this.deleteRefsForFile(file);
2860
2878
  this.deleteSymbolsForFile(file);
2861
- this.db.prepare("DELETE FROM files WHERE file = ?").run(file);
2879
+ this.stmt("DELETE FROM files WHERE file = ?").run(file);
2862
2880
  });
2863
2881
  }
2864
2882
  // ─── File metadata ──────────────────────────────────────────────────────────
2865
2883
  upsertFile(meta) {
2866
2884
  this.runWithRetry(() => {
2867
- this.db.prepare(
2885
+ this.stmt(
2868
2886
  `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
2869
2887
  VALUES (?, ?, ?, ?, ?)
2870
2888
  ON CONFLICT(file) DO UPDATE SET
@@ -2876,7 +2894,7 @@ var IndexStore = class {
2876
2894
  });
2877
2895
  }
2878
2896
  getFileMeta(file) {
2879
- const rows = this.db.prepare(
2897
+ const rows = this.stmt(
2880
2898
  "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files WHERE file = ?"
2881
2899
  ).all(file);
2882
2900
  if (!rows.length) return null;
@@ -3099,7 +3117,7 @@ var IndexStore = class {
3099
3117
  */
3100
3118
  insertRefs(fromId, refs) {
3101
3119
  this.runWithRetry(() => {
3102
- this.db.prepare("DELETE FROM refs WHERE from_id = ?").run(fromId);
3120
+ this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
3103
3121
  if (refs.length === 0) return;
3104
3122
  const stmt = this.db.prepare(
3105
3123
  `INSERT INTO refs(from_id, to_name, to_id, call_type, line)
@@ -3300,6 +3318,7 @@ var IndexStore = class {
3300
3318
  }
3301
3319
  }
3302
3320
  close() {
3321
+ this.stmtCache.clear();
3303
3322
  try {
3304
3323
  this.db.close();
3305
3324
  } catch {
@@ -9330,6 +9349,10 @@ var kanbanTool = {
9330
9349
  "release_task",
9331
9350
  "assign_task",
9332
9351
  "mark_assignment",
9352
+ "heartbeat_assignment",
9353
+ "recover_stale",
9354
+ "events",
9355
+ "queue_health",
9333
9356
  "add_dependency",
9334
9357
  "add_goal_metric",
9335
9358
  "update_goal_metric",
@@ -9376,6 +9399,12 @@ var kanbanTool = {
9376
9399
  fallbackModels: { type: "array", items: { type: "string" } },
9377
9400
  tools: { type: "array", items: { type: "string" } },
9378
9401
  allowedCapabilities: { type: "array", items: { type: "string" } },
9402
+ leaseId: { type: "string" },
9403
+ claimedAt: { type: "string" },
9404
+ heartbeatAt: { type: "string" },
9405
+ leaseExpiresAt: { type: "string" },
9406
+ attempt: { type: "number" },
9407
+ maxAttempts: { type: "number" },
9379
9408
  subagentId: { type: "string" },
9380
9409
  runTaskId: { type: "string" },
9381
9410
  lastResult: { type: "string" },
@@ -9387,6 +9416,8 @@ var kanbanTool = {
9387
9416
  releaseStatus: { type: "string", enum: ["pending", "ready", "blocked"] },
9388
9417
  releaseReason: { type: "string" },
9389
9418
  clearAssignee: { type: "boolean" },
9419
+ recoveryMode: { type: "string", enum: ["release", "retry", "fail"] },
9420
+ recoveryNow: { type: "string" },
9390
9421
  taskGraph: { type: "object" },
9391
9422
  graphId: { type: "string" },
9392
9423
  specId: { type: "string" },
@@ -9802,10 +9833,80 @@ var kanbanTool = {
9802
9833
  ...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
9803
9834
  ...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
9804
9835
  ...input.error !== void 0 ? { error: input.error } : {},
9805
- ...input.agentId !== void 0 ? { agentId: input.agentId } : {}
9836
+ ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
9837
+ ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
9838
+ ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
9839
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
9840
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
9841
+ ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
9842
+ ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
9806
9843
  });
9807
9844
  return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
9808
9845
  }
9846
+ case "heartbeat_assignment": {
9847
+ if (!input.boardId || !input.taskId) {
9848
+ return fail("heartbeat_assignment requires boardId and taskId.");
9849
+ }
9850
+ const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
9851
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
9852
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
9853
+ });
9854
+ return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
9855
+ }
9856
+ case "recover_stale": {
9857
+ if (!input.boardId) return fail("recover_stale requires boardId.");
9858
+ const policyFields = [
9859
+ input.recoveryPolicyFailOnCostCeiling !== void 0,
9860
+ input.recoveryPolicyReleaseOnFailureKinds !== void 0,
9861
+ input.recoveryPolicyReleaseOnHeartbeatDue !== void 0,
9862
+ input.recoveryPolicyRetryPolicyOverride !== void 0
9863
+ ].some(Boolean);
9864
+ const result = await recoverStaleTaskAssignments(projectRoot, input.boardId, {
9865
+ ...input.recoveryMode !== void 0 ? { mode: input.recoveryMode } : {},
9866
+ ...input.recoveryNow !== void 0 ? { now: input.recoveryNow } : {},
9867
+ ...input.releaseReason !== void 0 ? { reason: input.releaseReason } : {},
9868
+ ...input.clearAssignee !== void 0 ? { clearAssignee: input.clearAssignee } : {},
9869
+ ...policyFields ? {
9870
+ policy: {
9871
+ ...input.recoveryPolicyFailOnCostCeiling !== void 0 ? { failWhenCostCeilingSet: input.recoveryPolicyFailOnCostCeiling } : {},
9872
+ ...input.recoveryPolicyReleaseOnFailureKinds !== void 0 ? {
9873
+ releaseOnFailureKinds: input.recoveryPolicyReleaseOnFailureKinds
9874
+ } : {},
9875
+ ...input.recoveryPolicyReleaseOnHeartbeatDue !== void 0 ? {
9876
+ releaseOnHeartbeatDue: input.recoveryPolicyReleaseOnHeartbeatDue
9877
+ } : {},
9878
+ ...input.recoveryPolicyRetryPolicyOverride !== void 0 ? {
9879
+ retryPolicyOverride: input.recoveryPolicyRetryPolicyOverride
9880
+ } : {}
9881
+ }
9882
+ } : {}
9883
+ });
9884
+ return result ? {
9885
+ ok: true,
9886
+ message: `Recovered ${result.tasks.length} stale assignment(s).`,
9887
+ board: result.board,
9888
+ recoveredTasks: result.tasks
9889
+ } : { ok: true, message: "No stale assignment matched.", recoveredTasks: [] };
9890
+ }
9891
+ case "events": {
9892
+ if (!input.boardId) return fail("events requires boardId.");
9893
+ const eventList = await listKanbanEvents(projectRoot, input.boardId);
9894
+ return {
9895
+ ok: true,
9896
+ message: `${eventList.length} event(s).`,
9897
+ events: eventList
9898
+ };
9899
+ }
9900
+ case "queue_health": {
9901
+ const health = await getKanbanQueueHealth(projectRoot, {
9902
+ ...input.boardId !== void 0 ? { boardId: input.boardId } : {}
9903
+ });
9904
+ return {
9905
+ ok: true,
9906
+ message: `Counts: ready=${health.counts.ready}, running=${health.counts.running}, stale=${health.staleAssignments.count}.`,
9907
+ queueHealth: health
9908
+ };
9909
+ }
9809
9910
  case "add_dependency": {
9810
9911
  if (!input.boardId || !input.taskId || !input.dependencyTaskId) {
9811
9912
  return fail("add_dependency requires boardId, taskId, and dependencyTaskId.");
@@ -9957,11 +10058,20 @@ function assignmentInput(input) {
9957
10058
  fallbackModels: input.fallbackModels,
9958
10059
  tools: input.tools,
9959
10060
  allowedCapabilities: input.allowedCapabilities,
9960
- assignee: input.assignee
10061
+ assignee: input.assignee,
10062
+ leaseId: input.leaseId,
10063
+ claimedAt: input.claimedAt,
10064
+ heartbeatAt: input.heartbeatAt,
10065
+ leaseExpiresAt: input.leaseExpiresAt,
10066
+ attempt: input.attempt,
10067
+ maxAttempts: input.maxAttempts,
10068
+ costCeilingUsd: input.costCeilingUsd,
10069
+ retryPolicy: input.retryPolicy,
10070
+ lastFailureKind: input.lastFailureKind
9961
10071
  };
9962
10072
  }
9963
10073
  function hasAssignmentInput(input) {
9964
- return input.agentId !== void 0 || input.name !== void 0 || input.role !== void 0 || input.provider !== void 0 || input.model !== void 0 || input.fallbackProfile !== void 0 || input.fallbackModels !== void 0 || input.tools !== void 0 || input.allowedCapabilities !== void 0 || input.assignee !== void 0 || input.assignmentStatus !== void 0;
10074
+ return input.agentId !== void 0 || input.name !== void 0 || input.role !== void 0 || input.provider !== void 0 || input.model !== void 0 || input.fallbackProfile !== void 0 || input.fallbackModels !== void 0 || input.tools !== void 0 || input.allowedCapabilities !== void 0 || input.assignee !== void 0 || input.leaseId !== void 0 || input.claimedAt !== void 0 || input.heartbeatAt !== void 0 || input.leaseExpiresAt !== void 0 || input.attempt !== void 0 || input.maxAttempts !== void 0 || input.costCeilingUsd !== void 0 || input.retryPolicy !== void 0 || input.lastFailureKind !== void 0 || input.assignmentStatus !== void 0;
9965
10075
  }
9966
10076
  function assignmentForTaskCreate(input) {
9967
10077
  return {
@@ -9974,7 +10084,16 @@ function assignmentForTaskCreate(input) {
9974
10084
  ...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
9975
10085
  ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
9976
10086
  ...input.tools !== void 0 ? { tools: input.tools } : {},
9977
- ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {}
10087
+ ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
10088
+ ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
10089
+ ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
10090
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
10091
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
10092
+ ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
10093
+ ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
10094
+ ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
10095
+ ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
10096
+ ...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
9978
10097
  };
9979
10098
  }
9980
10099