@wrongstack/tools 0.306.4 → 0.307.0

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.
Files changed (71) hide show
  1. package/dist/audit.js +14 -8
  2. package/dist/auto-proceed-loop-guard.js +8 -2
  3. package/dist/bash.js +57 -22
  4. package/dist/builtin.d.ts +6 -6
  5. package/dist/builtin.js +11302 -9305
  6. package/dist/clarify.d.ts +32 -0
  7. package/dist/codebase-index/ast-invariant-engine.d.ts +102 -0
  8. package/dist/codebase-index/ast-symbol-mutator.d.ts +28 -0
  9. package/dist/codebase-index/background-indexer.d.ts +9 -0
  10. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +31 -0
  11. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +40 -0
  12. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +23 -0
  13. package/dist/codebase-index/codebase-repo-map-tool.d.ts +22 -0
  14. package/dist/codebase-index/codebase-skeleton-tool.d.ts +36 -0
  15. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +30 -0
  16. package/dist/codebase-index/index.d.ts +11 -1
  17. package/dist/codebase-index/index.js +4141 -1479
  18. package/dist/codebase-index/project-server-client-state.d.ts +79 -0
  19. package/dist/codebase-index/project-server-client.d.ts +3 -71
  20. package/dist/codebase-index/project-server-protocol.d.ts +1 -0
  21. package/dist/codebase-index/project-server.js +751 -909
  22. package/dist/codebase-index/repo-map.d.ts +20 -0
  23. package/dist/codebase-index/skeleton-extractor.d.ts +64 -0
  24. package/dist/codebase-index/tree-sitter-parser.d.ts +11 -0
  25. package/dist/codebase-index/worker.js +723 -891
  26. package/dist/codebase-index/writer-mutations.d.ts +26 -0
  27. package/dist/codebase-index/writer-refs.d.ts +50 -0
  28. package/dist/codebase-index/writer-search.d.ts +30 -0
  29. package/dist/codebase-index/writer.d.ts +2 -320
  30. package/dist/edit.js +8558 -349
  31. package/dist/exec.js +73 -20
  32. package/dist/fetch.js +11 -9
  33. package/dist/format.js +14 -8
  34. package/dist/glob.js +5 -1
  35. package/dist/grep.js +19 -8
  36. package/dist/index.d.ts +3 -1
  37. package/dist/index.js +11386 -9416
  38. package/dist/install.js +14 -8
  39. package/dist/json.js +88 -15
  40. package/dist/kanban-board-actions.d.ts +4 -0
  41. package/dist/kanban-lifecycle-actions.d.ts +4 -0
  42. package/dist/kanban-serializer.d.ts +21 -0
  43. package/dist/kanban.js +985 -1034
  44. package/dist/languages/index.js +14 -8
  45. package/dist/lint.js +14 -8
  46. package/dist/logs.js +13 -3
  47. package/dist/next-steps.d.ts +8 -0
  48. package/dist/next-steps.js +18 -0
  49. package/dist/outdated.js +14 -8
  50. package/dist/pack.js +11295 -9305
  51. package/dist/patch.js +8301 -78
  52. package/dist/plan.js +1231 -1281
  53. package/dist/process-registry.js +14 -8
  54. package/dist/ps-slash.js +65 -30
  55. package/dist/read.js +751 -910
  56. package/dist/replace.js +8450 -224
  57. package/dist/search.js +21 -15
  58. package/dist/security-ast-scan-tool.d.ts +43 -0
  59. package/dist/session-kanban-graph.d.ts +9 -0
  60. package/dist/session-kanban-sync.d.ts +31 -0
  61. package/dist/session-kanban.d.ts +5 -141
  62. package/dist/session-kanban.js +367 -360
  63. package/dist/task.js +1157 -1207
  64. package/dist/test.js +14 -8
  65. package/dist/todo.js +2187 -2237
  66. package/dist/tool-diff.js +6 -1
  67. package/dist/tool-summary.js +4 -2
  68. package/dist/tool-tier.js +11302 -9305
  69. package/dist/typecheck.js +14 -8
  70. package/dist/write.js +8374 -153
  71. package/package.json +7 -7
package/dist/search.js CHANGED
@@ -5,10 +5,7 @@ import { expectDefined } from "@wrongstack/core/utils";
5
5
  // src/_fetch-guard.ts
6
6
  import * as dns from "node:dns/promises";
7
7
  import * as net from "node:net";
8
- import {
9
- isPrivateIPv4,
10
- isPrivateIPv6
11
- } from "@wrongstack/core/utils";
8
+ import { isPrivateIPv4, isPrivateIPv6 } from "@wrongstack/core/utils";
12
9
  import { FetchError, ToolValidationError } from "@wrongstack/core/types";
13
10
  import { Agent, fetch as undiciFetch } from "undici";
14
11
  var nativeGlobalFetch = globalThis.fetch;
@@ -491,18 +488,27 @@ async function bingSearch(query, signal) {
491
488
  }
492
489
  function parseBingResults(html, num) {
493
490
  const results = [];
494
- const blocks = [...html.matchAll(/<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\1[^>]*>([\s\S]*?)(?=<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\3|<\/ol>)/gi)].map((m) => expectDefined(m[2]));
491
+ const blocks = [
492
+ ...html.matchAll(
493
+ /<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\1[^>]*>([\s\S]*?)(?=<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\3|<\/ol>)/gi
494
+ )
495
+ ].map((m) => expectDefined(m[2]));
495
496
  const candidates = blocks.length > 0 ? blocks : [html];
496
- const entries = takeFrom(candidates.flatMap((block) => {
497
- const titleMatch = /<h2[^>]*>\s*<a\b([^>]*)>([\s\S]*?)<\/a>\s*<\/h2>/i.exec(block);
498
- if (!titleMatch) return [];
499
- const href = getHtmlAttr(expectDefined(titleMatch[1]), "href");
500
- const title = stripTags(expectDefined(titleMatch[2]));
501
- if (!href || !title) return [];
502
- const snippetMatch = /<p\b[^>]*class=(["'])[^"']*\b(?:b_paractl|b_lineclamp\d*)\b[^"']*\1[^>]*>([\s\S]*?)<\/p>/i.exec(block) ?? /<p\b[^>]*>([\s\S]*?)<\/p>/i.exec(block);
503
- const snippet = snippetMatch ? capSnippet(stripTags(expectDefined(snippetMatch.at(-1)))) : "";
504
- return [{ url: normalizeBingUrl(href), title, snippet, score: 1 }];
505
- }), num);
497
+ const entries = takeFrom(
498
+ candidates.flatMap((block) => {
499
+ const titleMatch = /<h2[^>]*>\s*<a\b([^>]*)>([\s\S]*?)<\/a>\s*<\/h2>/i.exec(block);
500
+ if (!titleMatch) return [];
501
+ const href = getHtmlAttr(expectDefined(titleMatch[1]), "href");
502
+ const title = stripTags(expectDefined(titleMatch[2]));
503
+ if (!href || !title) return [];
504
+ const snippetMatch = /<p\b[^>]*class=(["'])[^"']*\b(?:b_paractl|b_lineclamp\d*)\b[^"']*\1[^>]*>([\s\S]*?)<\/p>/i.exec(
505
+ block
506
+ ) ?? /<p\b[^>]*>([\s\S]*?)<\/p>/i.exec(block);
507
+ const snippet = snippetMatch ? capSnippet(stripTags(expectDefined(snippetMatch.at(-1)))) : "";
508
+ return [{ url: normalizeBingUrl(href), title, snippet, score: 1 }];
509
+ }),
510
+ num
511
+ );
506
512
  for (let i = 0; i < entries.length; i++) {
507
513
  const entry = entries[i];
508
514
  if (entry) {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `security-ast-scan` tool — Contract-based Security & Performance AST Radar.
3
+ *
4
+ * Scans code files or snippets for anti-patterns that static types cannot catch:
5
+ * 1. N+1 Database Query loops (Performance critical)
6
+ * 2. SQL Injection / Raw query interpolation (Security critical)
7
+ * 3. Hardcoded secrets, API tokens, and private keys
8
+ * 4. Prototype Pollution vulnerabilities
9
+ * 5. ReDoS (Regular Expression Denial of Service) nested backtracking
10
+ * 6. Unsafe dynamic execution (eval, new Function, unsanitized child_process.exec)
11
+ */
12
+ import type { Tool } from '@wrongstack/core/types';
13
+ export interface SecurityFinding {
14
+ ruleId: 'perf/n-plus-one-query' | 'sec/sql-injection' | 'sec/hardcoded-secret' | 'sec/prototype-pollution' | 'sec/redos-vulnerable-regex' | 'sec/unsafe-dynamic-exec';
15
+ category: 'security' | 'performance';
16
+ severity: 'critical' | 'warning' | 'info';
17
+ file: string;
18
+ line: number;
19
+ message: string;
20
+ snippet: string;
21
+ recommendation: string;
22
+ }
23
+ export interface SecurityScanInput {
24
+ /** File path to scan (relative to project root or absolute). */
25
+ file?: string | undefined;
26
+ /** Direct code content to scan (optional, if scanning unwritten draft). */
27
+ content?: string | undefined;
28
+ /** Specific rule IDs to enable or filter by. */
29
+ rules?: string[] | undefined;
30
+ }
31
+ export interface SecurityScanOutput {
32
+ status: 'clean' | 'findings_detected' | 'error';
33
+ filesScanned: number;
34
+ totalFindings: number;
35
+ criticalCount: number;
36
+ warningCount: number;
37
+ findings: SecurityFinding[];
38
+ summary: string;
39
+ error?: string | undefined;
40
+ }
41
+ export declare function analyzeSecurityAndPerformance(filePath: string, content: string): SecurityFinding[];
42
+ export declare const securityAstScanTool: Tool<SecurityScanInput, SecurityScanOutput>;
43
+ //# sourceMappingURL=security-ast-scan-tool.d.ts.map
@@ -0,0 +1,9 @@
1
+ import type { TodoItem } from '@wrongstack/core/agent';
2
+ import type { PlanItem } from '@wrongstack/core/storage';
3
+ import type { SerializedTaskGraph, TaskStatus } from '@wrongstack/core/types';
4
+ import type { TaskItem } from '@wrongstack/core/utils';
5
+ export declare const PLAN_STATUS_TO_TASK: Record<PlanItem['status'], TaskStatus>;
6
+ export declare function todoListToSerializedGraph(todos: readonly TodoItem[], sessionId: string): SerializedTaskGraph;
7
+ export declare function taskFileToSerializedGraph(tasks: readonly TaskItem[], sessionId: string): SerializedTaskGraph;
8
+ export declare function planFileToSerializedGraph(items: readonly PlanItem[], sessionId: string): SerializedTaskGraph;
9
+ //# sourceMappingURL=session-kanban-graph.d.ts.map
@@ -0,0 +1,31 @@
1
+ import type { Context, TodoItem } from '@wrongstack/core/agent';
2
+ import { type PlanFile, type TaskFile } from '@wrongstack/core/storage';
3
+ import type { TaskStatus } from '@wrongstack/core/types';
4
+ import { type KanbanBoard, type KanbanTask } from '@wrongstack/kanban';
5
+ export interface SessionKanbanSourceUpdate {
6
+ source: 'todo' | 'task' | 'plan' | null;
7
+ todos?: TodoItem[] | undefined;
8
+ tasks?: TaskFile | undefined;
9
+ plan?: PlanFile | undefined;
10
+ }
11
+ export declare function sourceStatus(task: KanbanTask): TaskStatus;
12
+ export declare function todoStatus(task: KanbanTask): TodoItem['status'];
13
+ export declare function sessionTodoFromTask(task: KanbanTask, board?: KanbanBoard): TodoItem;
14
+ export declare function managedTodoFromTask(task: KanbanTask, board: KanbanBoard): TodoItem;
15
+ export declare function blockingTitles(board: KanbanBoard, task: KanbanTask): string[];
16
+ export declare function orderTasksForTodos(board: KanbanBoard, tasks: readonly KanbanTask[]): KanbanTask[];
17
+ export declare function sameTodos(left: readonly TodoItem[], right: readonly TodoItem[]): boolean;
18
+ export declare function broadcastTodoUpdate(context: Context, todos: readonly TodoItem[]): void;
19
+ export declare function notifyTodoUpdate(context: Context, todos: readonly TodoItem[]): void;
20
+ export declare function todosNeedingSessionMirror(todos: readonly TodoItem[], activeBoardId: string | undefined): readonly TodoItem[];
21
+ export declare function applySessionKanbanBoardToTodos(context: Context, board: KanbanBoard, options: {
22
+ sessionIdFromTags: (tags: readonly string[] | undefined) => string | null;
23
+ isOwnedSessionBoard: (tags: readonly string[] | undefined) => boolean;
24
+ hasInFlightTodoMirror: (projectRoot: string | undefined, sessionId: string) => boolean;
25
+ suppressedTodoMirrors: WeakSet<Context>;
26
+ }): TodoItem[];
27
+ export declare function applyManagedKanbanBoardToTodos(context: Context, board: KanbanBoard, suppressedTodoMirrors: WeakSet<Context>): TodoItem[];
28
+ export declare function applySessionKanbanTaskToSource(context: Context, task: KanbanTask, suppressedTodoMirrors: WeakSet<Context>, options?: {
29
+ remove?: boolean | undefined;
30
+ }): Promise<SessionKanbanSourceUpdate>;
31
+ //# sourceMappingURL=session-kanban-sync.d.ts.map
@@ -1,167 +1,31 @@
1
1
  import type { Context, TodoItem } from '@wrongstack/core/agent';
2
- import { type PlanFile, type PlanItem, type TaskFile } from '@wrongstack/core/storage';
3
- import type { SerializedTaskGraph } from '@wrongstack/core/types';
4
- import { type TaskItem } from '@wrongstack/core/utils';
2
+ import { type PlanItem } from '@wrongstack/core/storage';
3
+ import type { TaskItem } from '@wrongstack/core/utils';
5
4
  import { type KanbanBoard, type KanbanColumn, type KanbanTask } from '@wrongstack/kanban';
6
- /**
7
- * The canonical workflow shared by WebUI and TUI session boards.
8
- *
9
- * Session boards now use the same 5 standard columns as every other board
10
- * (backlog, todo, in-progress, review, done). Formerly this shipped a 4-column
11
- * variant without a backlog; the column lock unified the layout. Existing
12
- * 4-column session_mirror boards are migrated automatically on the next session
13
- * touch: `ensureSessionKanbanBoard`'s `sameColumns` check detects the drift
14
- * and `updateBoard` reconciles to DEFAULT_COLUMNS.
15
- */
5
+ import { type SessionKanbanSourceUpdate } from './session-kanban-sync.js';
6
+ export { PLAN_STATUS_TO_TASK, planFileToSerializedGraph, taskFileToSerializedGraph, todoListToSerializedGraph, } from './session-kanban-graph.js';
7
+ export { blockingTitles, orderTasksForTodos, type SessionKanbanSourceUpdate, todosNeedingSessionMirror, } from './session-kanban-sync.js';
16
8
  export declare const SESSION_KANBAN_COLUMNS: KanbanColumn[];
17
- /** Create (or migrate) the single Kanban board owned by a session. */
18
9
  export declare function ensureSessionKanbanBoard(projectRoot: string | undefined, sessionId: string): Promise<KanbanBoard | null>;
19
- /**
20
- * Remove an inactive session's observational mirror, including its cards.
21
- * The durable source remains the session todo/task/plan files; retaining the
22
- * mirror after detach only creates historical duplicate work in project views.
23
- */
24
10
  export declare function cleanupSessionKanbanBoard(projectRoot: string | undefined, sessionId: string): Promise<string[]>;
25
- /** Remove a particular inactive session's system-owned board when it has no cards. */
26
11
  export declare function cleanupSessionKanbanBoardIfEmpty(projectRoot: string | undefined, sessionId: string): Promise<string[]>;
27
- /** Prune stale empty session boards while preserving manual and live boards. */
28
12
  export declare function cleanupEmptySessionKanbanBoards(projectRoot: string | undefined, activeSessionId?: string): Promise<string[]>;
29
- /**
30
- * Consume the last unreported mirror failure for a session, if any.
31
- *
32
- * The mirror is asynchronous and fire-and-forget, so a rejected projection
33
- * used to be observable only in stdout. The `todo` tool drains this on its
34
- * next call and reports it as a warning, so a board that stopped tracking the
35
- * session is surfaced where the divergence actually matters.
36
- */
37
13
  export declare function takeSessionMirrorFailure(projectRoot: string | undefined, sessionId: string): string | undefined;
38
- export declare function todoListToSerializedGraph(todos: readonly TodoItem[], sessionId: string): SerializedTaskGraph;
39
- export declare function taskFileToSerializedGraph(tasks: readonly TaskItem[], sessionId: string): SerializedTaskGraph;
40
- export declare function planFileToSerializedGraph(items: readonly PlanItem[], sessionId: string): SerializedTaskGraph;
41
14
  export declare function projectSessionTodosToKanban(projectRoot: string | undefined, todos: readonly TodoItem[], sessionId: string): Promise<KanbanBoard | null>;
42
15
  export declare function projectSessionTasksToKanban(projectRoot: string | undefined, tasks: readonly TaskItem[], sessionId: string): Promise<KanbanBoard | null>;
43
16
  export declare function projectSessionPlanToKanban(projectRoot: string | undefined, items: readonly PlanItem[], sessionId: string): Promise<KanbanBoard | null>;
44
- /**
45
- * The todo rows that still need the session mirror: those not already cards on
46
- * the board this session is working on.
47
- *
48
- * The rule used to be all-or-nothing — the mirror was skipped only when EVERY
49
- * row was bound. A single unbound row (a todo the model added to the chat list
50
- * without also filing a card) sent the whole list to a separate session board,
51
- * so work already tracked on the real board showed up a second time there, and
52
- * one stray row was enough to leave a session board holding a single card.
53
- *
54
- * With no active board every row still needs mirroring, since there is no other
55
- * place the work is being recorded.
56
- */
57
- export declare function todosNeedingSessionMirror(todos: readonly TodoItem[], activeBoardId: string | undefined): readonly TodoItem[];
58
17
  export declare function mirrorSessionTodosToKanban(projectRoot: string | undefined, todos: readonly TodoItem[], sessionId: string): void;
59
18
  export declare function mirrorSessionTasksToKanban(projectRoot: string | undefined, tasks: readonly TaskItem[], sessionId: string): void;
60
19
  export declare function mirrorSessionPlanToKanban(projectRoot: string | undefined, items: readonly PlanItem[], sessionId: string): void;
61
- /**
62
- * Bind all live session work paths to Kanban. Todo changes are observed from
63
- * ConversationState; plan/task sidecars are watched so slash commands, WebUI,
64
- * plugins, and tools all pass through the same board.
65
- */
66
20
  export declare function attachSessionKanbanMirror(context: Context): () => void;
67
- /** Fully hydrate a session board before a host announces the session as ready. */
68
- /**
69
- * Re-bind the card this session was working, after a restart or `--resume`.
70
- *
71
- * The binding produced by `start_task` lives in `ctx.meta.kanban`, which is
72
- * conversation state — nothing restores it. So a resumed session came back
73
- * with `currentKanbanTaskId` undefined while its card still sat in Running on
74
- * the board: file events lost their task attribution, and under
75
- * `tools.kanbanGovernance` the run was un-bound until the model happened to
76
- * call `start_task` again.
77
- *
78
- * Board presence is the durable anchor. It is keyed `<sessionId>:<agentId>`,
79
- * carries the `taskId`, and is stored on the board record itself, so it
80
- * survives the restart even though its derived `active` flag does not (the TTL
81
- * is two minutes). Matching it against the cards that are actually still
82
- * running gives an unambiguous answer without guessing from free-text agent
83
- * ids.
84
- *
85
- * Deliberately conservative — it prefers to do nothing over binding the wrong
86
- * card:
87
- * - never overrides a binding this process already has;
88
- * - only considers cards whose assignment is still `running`;
89
- * - skips an expired lease, because `recover_stale` may have reassigned that
90
- * card to another worker;
91
- * - picks the most recently seen entry when a session touched several.
92
- *
93
- * Session mirrors and archived boards are excluded by the snapshot's default
94
- * board-kind filter, which is correct: `start_task` never targets them.
95
- */
96
21
  export declare function rebindSessionKanbanTask(context: Context): Promise<{
97
22
  boardId: string;
98
23
  taskId: string;
99
24
  } | null>;
100
- /**
101
- * Why session Kanban is unavailable, or undefined while it is healthy.
102
- *
103
- * Surfaces exist so a startup path can tell the user their board will not sync
104
- * without having to catch and classify the failure itself. Cleared on the
105
- * first hydrate that succeeds, so a daemon that comes back stops being
106
- * reported as down.
107
- */
108
25
  export declare function sessionKanbanDegradation(): string | undefined;
109
- /**
110
- * Project the session's todos, plan, and tasks onto its Kanban board.
111
- *
112
- * Kanban is a projection: every card here is derived from state that lives
113
- * somewhere else and is authoritative there. Losing the board costs the user a
114
- * view, not any work — so an unreachable Kanban daemon degrades this to `null`
115
- * rather than propagating.
116
- *
117
- * That distinction used to be missing, and it cost an entire session. When a
118
- * killed daemon left a stale IPC endpoint behind, the connect error thrown
119
- * here unwound through `setupSession` and killed `wstack` at startup: no
120
- * prompt, no session, just a socket path in a stack trace — for a board the
121
- * user had not asked to see. `bindProjectEndpoint` now stops the daemon from
122
- * wedging in the first place, but the call site must still hold: an optional
123
- * view may never take down the session it decorates.
124
- */
125
26
  export declare function hydrateSessionKanban(context: Context): Promise<KanbanBoard | null>;
126
- export interface SessionKanbanSourceUpdate {
127
- source: 'todo' | 'task' | 'plan' | null;
128
- todos?: TodoItem[] | undefined;
129
- tasks?: TaskFile | undefined;
130
- plan?: PlanFile | undefined;
131
- }
132
- /**
133
- * Human-readable titles of the unfinished dependencies blocking a card.
134
- *
135
- * The board recomputes readiness on every mutation; this only carries the
136
- * result to the todo surface. Titles rather than ids, because the row is read
137
- * by a model and a human, neither of whom can resolve a raw card id.
138
- */
139
- export declare function blockingTitles(board: KanbanBoard, task: KanbanTask): string[];
140
- /**
141
- * Order cards the way they must actually be worked: dependencies first, then
142
- * priority, then the board's own column/manual order.
143
- *
144
- * The managed projection previously sorted by `createdAt` — the moment a card
145
- * happened to be created — which is unrelated to execution order, so the list
146
- * the model reads top-to-bottom was not a plan. A dependency-respecting
147
- * (Kahn) order makes the visible sequence the executable sequence; ties fall
148
- * back to the deterministic board order so the list never reshuffles on its
149
- * own. Any card left over by a dependency cycle is appended rather than
150
- * dropped — the board rejects cycles, but a projection must never lose work.
151
- */
152
- export declare function orderTasksForTodos(board: KanbanBoard, tasks: readonly KanbanTask[]): KanbanTask[];
153
- /**
154
- * Replace the tactical todo list from the current cards on a session-owned board.
155
- *
156
- * Existing mirrored todo cards retain their stable source ids. New cards created
157
- * by a Kanban worker become todos under their card ids, so reassessment can add,
158
- * split, merge, reprioritize, or remove work without being overwritten by the
159
- * todo list that happened to exist when the run started.
160
- */
161
27
  export declare function applySessionKanbanBoardToTodos(context: Context, board: KanbanBoard): TodoItem[];
162
- /** Project a durable managed board into the session's compact todo surface. */
163
28
  export declare function applyManagedKanbanBoardToTodos(context: Context, board: KanbanBoard): TodoItem[];
164
- /** Reflect a TUI/WebUI Kanban card edit back to its originating work list. */
165
29
  export declare function applySessionKanbanTaskToSource(context: Context, task: KanbanTask, options?: {
166
30
  remove?: boolean | undefined;
167
31
  }): Promise<SessionKanbanSourceUpdate>;