@wrongstack/tools 0.287.0 → 0.291.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 (102) hide show
  1. package/dist/_fetch-guard.d.ts.map +1 -1
  2. package/dist/_redact-command.d.ts.map +1 -1
  3. package/dist/_spawn-stream.d.ts.map +1 -1
  4. package/dist/audit.js +61 -14
  5. package/dist/audit.js.map +3 -3
  6. package/dist/auto-proceed-loop-guard.d.ts +128 -0
  7. package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
  8. package/dist/auto-proceed-loop-guard.js +46 -0
  9. package/dist/auto-proceed-loop-guard.js.map +7 -0
  10. package/dist/bash.d.ts +1 -1
  11. package/dist/bash.d.ts.map +1 -1
  12. package/dist/bash.js +97 -19
  13. package/dist/bash.js.map +3 -3
  14. package/dist/builtin.d.ts +12 -10
  15. package/dist/builtin.d.ts.map +1 -1
  16. package/dist/builtin.js +1766 -864
  17. package/dist/builtin.js.map +3 -3
  18. package/dist/codebase-index/background-indexer.d.ts +1 -1
  19. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  20. package/dist/codebase-index/bm25.d.ts +1 -2
  21. package/dist/codebase-index/bm25.d.ts.map +1 -1
  22. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
  23. package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
  24. package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
  25. package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
  26. package/dist/codebase-index/index-service.d.ts +11 -1
  27. package/dist/codebase-index/index-service.d.ts.map +1 -1
  28. package/dist/codebase-index/index.d.ts +2 -1
  29. package/dist/codebase-index/index.d.ts.map +1 -1
  30. package/dist/codebase-index/index.js +888 -254
  31. package/dist/codebase-index/index.js.map +3 -3
  32. package/dist/codebase-index/indexer.d.ts.map +1 -1
  33. package/dist/codebase-index/refs-extractor.d.ts +2 -17
  34. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  35. package/dist/codebase-index/schema.d.ts +43 -1
  36. package/dist/codebase-index/schema.d.ts.map +1 -1
  37. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  38. package/dist/codebase-index/worker-protocol.d.ts +1 -0
  39. package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
  40. package/dist/codebase-index/worker.js +738 -212
  41. package/dist/codebase-index/worker.js.map +3 -3
  42. package/dist/codebase-index/writer.d.ts +60 -1
  43. package/dist/codebase-index/writer.d.ts.map +1 -1
  44. package/dist/e2e.js +477 -0
  45. package/dist/e2e.js.map +7 -0
  46. package/dist/exec.d.ts +1 -1
  47. package/dist/exec.d.ts.map +1 -1
  48. package/dist/exec.js +60 -5
  49. package/dist/exec.js.map +3 -3
  50. package/dist/fetch.js +2 -2
  51. package/dist/fetch.js.map +2 -2
  52. package/dist/format.js +61 -14
  53. package/dist/format.js.map +3 -3
  54. package/dist/glob.js +2 -2
  55. package/dist/glob.js.map +1 -1
  56. package/dist/grep.d.ts.map +1 -1
  57. package/dist/grep.js +2 -2
  58. package/dist/grep.js.map +2 -2
  59. package/dist/index.d.ts +3 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2001 -873
  62. package/dist/index.js.map +3 -3
  63. package/dist/install.js +61 -14
  64. package/dist/install.js.map +3 -3
  65. package/dist/kanban.d.ts +21 -2
  66. package/dist/kanban.d.ts.map +1 -1
  67. package/dist/kanban.js +713 -572
  68. package/dist/kanban.js.map +3 -3
  69. package/dist/languages/detect.d.ts.map +1 -1
  70. package/dist/languages/index.js +61 -14
  71. package/dist/languages/index.js.map +3 -3
  72. package/dist/languages/types.d.ts +7 -0
  73. package/dist/languages/types.d.ts.map +1 -1
  74. package/dist/lint.js +61 -14
  75. package/dist/lint.js.map +3 -3
  76. package/dist/outdated.js +61 -14
  77. package/dist/outdated.js.map +3 -3
  78. package/dist/pack.js +1763 -861
  79. package/dist/pack.js.map +3 -3
  80. package/dist/plan.js +4 -0
  81. package/dist/plan.js.map +2 -2
  82. package/dist/process-registry.js +12 -5
  83. package/dist/process-registry.js.map +2 -2
  84. package/dist/read.d.ts.map +1 -1
  85. package/dist/read.js.map +2 -2
  86. package/dist/search.js +2 -2
  87. package/dist/search.js.map +2 -2
  88. package/dist/session-kanban.d.ts +9 -0
  89. package/dist/session-kanban.d.ts.map +1 -1
  90. package/dist/session-kanban.js +197 -3
  91. package/dist/session-kanban.js.map +2 -2
  92. package/dist/task.js +4 -0
  93. package/dist/task.js.map +2 -2
  94. package/dist/test.js +61 -14
  95. package/dist/test.js.map +3 -3
  96. package/dist/todo.js +4 -0
  97. package/dist/todo.js.map +2 -2
  98. package/dist/tree.js +2 -2
  99. package/dist/tree.js.map +1 -1
  100. package/dist/typecheck.js +61 -14
  101. package/dist/typecheck.js.map +3 -3
  102. package/package.json +13 -5
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Auto-proceed loop guard.
3
+ *
4
+ * ## Problem
5
+ *
6
+ * The REPL and TUI both implement an "auto" autonomy mode that, after every
7
+ * completed agent turn, feeds the next suggestion (or top auto="true" item)
8
+ * back as a fresh prompt. When the agent emits the same `<nextsteps>` block
9
+ * on every reply — which happens whenever the model's output is stable but
10
+ * autonomy treats the next step as "still actionable" — the loop self-feeds
11
+ * the same instruction 2–3 times in a row before the longer
12
+ * `autoProceedMaxIterations` cap (default 50) trips.
13
+ *
14
+ * The user reports the visible symptom: the same response, including the same
15
+ * `<nextsteps>` block, repeats. Manual input from the user is the only thing
16
+ * that breaks out. They suspect the next-steps mechanism is the source.
17
+ *
18
+ * The 50-iteration cap is intentionally loose — it is a runaway safety net,
19
+ * not a UX signal. By the time it trips, the user has already watched the
20
+ * same response 50 times.
21
+ *
22
+ * ## Approach
23
+ *
24
+ * A small, pure, browser-safe stateful helper that records the last few
25
+ * prompts fed through the auto-proceed / auto-submit path. It normalizes
26
+ * prompts (whitespace, casing) so trivial rewordings are not collapsed, but
27
+ * identical re-feeds are. When the same normalized prompt is fed
28
+ * `repeatThreshold` (default 2) times in a row, the guard returns
29
+ * `{ shouldHalt: true }`.
30
+ *
31
+ * Callers are expected to:
32
+ * 1. Call `record(prompt)` immediately before each auto-feed.
33
+ * 2. If `shouldHalt` is true, clear the suggestion + auto-suggestion store,
34
+ * cancel any pending countdown, and surface a "we detected a loop"
35
+ * message asking the user what's happening. Do NOT feed the prompt.
36
+ * 3. Call `reset()` whenever the user types anything manually (REPL manual
37
+ * input or any user-driven submit) so the next auto-feed starts clean.
38
+ *
39
+ * The default `repeatThreshold` is 2 — i.e. the second consecutive identical
40
+ * feed (the first re-feed of an already-seen prompt) halts the loop. That
41
+ * matches the user's report ("receiving the same prompt 2-3 times in a row
42
+ * causes the system to enter a loop"). Setting it to 2 catches the loop on
43
+ * the first identical re-feed rather than after the 50-iteration runaway cap trips.
44
+ *
45
+ * The window size is intentionally small (default 3). The loop we care about
46
+ * is the immediate repetition — a longer history would misfire on legitimate
47
+ * "do X, then do Y, then do X" sequences the model occasionally returns to.
48
+ *
49
+ * This module is BROWSER-SAFE — no Node-only imports — so it can be imported
50
+ * from Vite-bundled WebUI as well as Node-based CLI/TUI.
51
+ */
52
+ /**
53
+ * Normalize a prompt for repetition comparison. Whitespace is collapsed and
54
+ * trimmed; case is folded to lowercase. Nothing semantic is removed, so two
55
+ * prompts that differ in any meaningful way (extra word, different code,
56
+ * different file path) compare as distinct. Two prompts that differ only by
57
+ * leading/trailing whitespace and casing are treated as identical.
58
+ */
59
+ export declare function normalizeForRepetition(prompt: string): string;
60
+ export interface LoopGuardOptions {
61
+ /**
62
+ * How many consecutive identical prompts (after normalization) trigger a
63
+ * halt. Default 2 — i.e. the second consecutive identical feed (the first
64
+ * re-feed of an already-seen prompt) halts the loop. Clamped to >= 2.
65
+ */
66
+ repeatThreshold?: number;
67
+ /**
68
+ * How many of the most recent feeds to retain for comparison. The window
69
+ * is searched from newest to oldest; the guard cares about the run of
70
+ * identical entries ending at the most recent one, not about identical
71
+ * entries anywhere in history. Default 3.
72
+ */
73
+ windowSize?: number;
74
+ }
75
+ export interface RepetitionSignal {
76
+ /** The normalized prompt that just got recorded. */
77
+ normalized: string;
78
+ /**
79
+ * Number of consecutive identical feeds ending at the most recent feed,
80
+ * including this one. Always >= 1.
81
+ */
82
+ runLength: number;
83
+ /**
84
+ * True when the run length has crossed `repeatThreshold`. Callers must
85
+ * stop feeding and surface a user prompt instead.
86
+ */
87
+ shouldHalt: boolean;
88
+ }
89
+ export interface AutoProceedLoopGuard {
90
+ /**
91
+ * Record a prompt that is about to be auto-fed. Returns the repetition
92
+ * signal for that prompt. When `shouldHalt` is true the caller MUST NOT
93
+ * feed the prompt — it has been recorded for post-mortem inspection, but
94
+ * the loop must be broken instead.
95
+ */
96
+ record(prompt: string): RepetitionSignal;
97
+ /**
98
+ * Drop the history. Call this on any manual user input so a fresh run
99
+ * starts with no memory of the prior cycle.
100
+ */
101
+ reset(): void;
102
+ /**
103
+ * Read-only view of the most recent normalized prompts (newest last).
104
+ * Useful for diagnostics and for the message shown to the user when the
105
+ * guard halts the loop.
106
+ */
107
+ history(): readonly string[];
108
+ /**
109
+ * How many entries the guard is currently retaining.
110
+ */
111
+ size(): number;
112
+ }
113
+ /**
114
+ * Build a fresh loop guard. Defaults: `repeatThreshold = 2`, `windowSize = 3`.
115
+ *
116
+ * @example
117
+ * const guard = createAutoProceedLoopGuard();
118
+ * for (const prompt of candidates) {
119
+ * const signal = guard.record(prompt);
120
+ * if (signal.shouldHalt) {
121
+ * haltAutoProceedAndAskUser(signal);
122
+ * break;
123
+ * }
124
+ * feedPrompt(prompt);
125
+ * }
126
+ */
127
+ export declare function createAutoProceedLoopGuard(options?: LoopGuardOptions): AutoProceedLoopGuard;
128
+ //# sourceMappingURL=auto-proceed-loop-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-proceed-loop-guard.d.ts","sourceRoot":"","sources":["../src/auto-proceed-loop-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACzC;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAC;IACd;;;;OAIG;IACH,OAAO,IAAI,SAAS,MAAM,EAAE,CAAC;IAC7B;;OAEG;IACH,IAAI,IAAI,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,gBAAqB,GAAG,oBAAoB,CAmD/F"}
@@ -0,0 +1,46 @@
1
+ // src/auto-proceed-loop-guard.ts
2
+ function normalizeForRepetition(prompt) {
3
+ return prompt.replace(/\s+/g, " ").trim().toLowerCase();
4
+ }
5
+ function createAutoProceedLoopGuard(options = {}) {
6
+ const safeIntegerOption = (value, fallback) => {
7
+ if (value === void 0) return fallback;
8
+ if (!Number.isFinite(value)) return fallback;
9
+ return Math.floor(value);
10
+ };
11
+ const repeatThreshold = Math.max(2, safeIntegerOption(options.repeatThreshold, 2));
12
+ const windowSize = Math.max(repeatThreshold, safeIntegerOption(options.windowSize, 3));
13
+ let buffer = [];
14
+ function record(prompt) {
15
+ const normalized = normalizeForRepetition(prompt);
16
+ buffer.push(normalized);
17
+ if (buffer.length > windowSize) {
18
+ buffer = buffer.slice(buffer.length - windowSize);
19
+ }
20
+ let runLength = 0;
21
+ for (let i = buffer.length - 1; i >= 0; i--) {
22
+ if (buffer[i] === normalized) runLength++;
23
+ else break;
24
+ }
25
+ return {
26
+ normalized,
27
+ runLength,
28
+ shouldHalt: runLength >= repeatThreshold
29
+ };
30
+ }
31
+ function reset() {
32
+ buffer = [];
33
+ }
34
+ function history() {
35
+ return buffer.slice();
36
+ }
37
+ function size() {
38
+ return buffer.length;
39
+ }
40
+ return { record, reset, history, size };
41
+ }
42
+ export {
43
+ createAutoProceedLoopGuard,
44
+ normalizeForRepetition
45
+ };
46
+ //# sourceMappingURL=auto-proceed-loop-guard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auto-proceed-loop-guard.ts"],
4
+ "sourcesContent": ["/**\n * Auto-proceed loop guard.\n *\n * ## Problem\n *\n * The REPL and TUI both implement an \"auto\" autonomy mode that, after every\n * completed agent turn, feeds the next suggestion (or top auto=\"true\" item)\n * back as a fresh prompt. When the agent emits the same `<nextsteps>` block\n * on every reply \u2014 which happens whenever the model's output is stable but\n * autonomy treats the next step as \"still actionable\" \u2014 the loop self-feeds\n * the same instruction 2\u20133 times in a row before the longer\n * `autoProceedMaxIterations` cap (default 50) trips.\n *\n * The user reports the visible symptom: the same response, including the same\n * `<nextsteps>` block, repeats. Manual input from the user is the only thing\n * that breaks out. They suspect the next-steps mechanism is the source.\n *\n * The 50-iteration cap is intentionally loose \u2014 it is a runaway safety net,\n * not a UX signal. By the time it trips, the user has already watched the\n * same response 50 times.\n *\n * ## Approach\n *\n * A small, pure, browser-safe stateful helper that records the last few\n * prompts fed through the auto-proceed / auto-submit path. It normalizes\n * prompts (whitespace, casing) so trivial rewordings are not collapsed, but\n * identical re-feeds are. When the same normalized prompt is fed\n * `repeatThreshold` (default 2) times in a row, the guard returns\n * `{ shouldHalt: true }`.\n *\n * Callers are expected to:\n * 1. Call `record(prompt)` immediately before each auto-feed.\n * 2. If `shouldHalt` is true, clear the suggestion + auto-suggestion store,\n * cancel any pending countdown, and surface a \"we detected a loop\"\n * message asking the user what's happening. Do NOT feed the prompt.\n * 3. Call `reset()` whenever the user types anything manually (REPL manual\n * input or any user-driven submit) so the next auto-feed starts clean.\n *\n * The default `repeatThreshold` is 2 \u2014 i.e. the second consecutive identical\n * feed (the first re-feed of an already-seen prompt) halts the loop. That\n * matches the user's report (\"receiving the same prompt 2-3 times in a row\n * causes the system to enter a loop\"). Setting it to 2 catches the loop on\n * the first identical re-feed rather than after the 50-iteration runaway cap trips.\n *\n * The window size is intentionally small (default 3). The loop we care about\n * is the immediate repetition \u2014 a longer history would misfire on legitimate\n * \"do X, then do Y, then do X\" sequences the model occasionally returns to.\n *\n * This module is BROWSER-SAFE \u2014 no Node-only imports \u2014 so it can be imported\n * from Vite-bundled WebUI as well as Node-based CLI/TUI.\n */\n\n/**\n * Normalize a prompt for repetition comparison. Whitespace is collapsed and\n * trimmed; case is folded to lowercase. Nothing semantic is removed, so two\n * prompts that differ in any meaningful way (extra word, different code,\n * different file path) compare as distinct. Two prompts that differ only by\n * leading/trailing whitespace and casing are treated as identical.\n */\nexport function normalizeForRepetition(prompt: string): string {\n return prompt.replace(/\\s+/g, ' ').trim().toLowerCase();\n}\n\nexport interface LoopGuardOptions {\n /**\n * How many consecutive identical prompts (after normalization) trigger a\n * halt. Default 2 \u2014 i.e. the second consecutive identical feed (the first\n * re-feed of an already-seen prompt) halts the loop. Clamped to >= 2.\n */\n repeatThreshold?: number;\n /**\n * How many of the most recent feeds to retain for comparison. The window\n * is searched from newest to oldest; the guard cares about the run of\n * identical entries ending at the most recent one, not about identical\n * entries anywhere in history. Default 3.\n */\n windowSize?: number;\n}\n\nexport interface RepetitionSignal {\n /** The normalized prompt that just got recorded. */\n normalized: string;\n /**\n * Number of consecutive identical feeds ending at the most recent feed,\n * including this one. Always >= 1.\n */\n runLength: number;\n /**\n * True when the run length has crossed `repeatThreshold`. Callers must\n * stop feeding and surface a user prompt instead.\n */\n shouldHalt: boolean;\n}\n\nexport interface AutoProceedLoopGuard {\n /**\n * Record a prompt that is about to be auto-fed. Returns the repetition\n * signal for that prompt. When `shouldHalt` is true the caller MUST NOT\n * feed the prompt \u2014 it has been recorded for post-mortem inspection, but\n * the loop must be broken instead.\n */\n record(prompt: string): RepetitionSignal;\n /**\n * Drop the history. Call this on any manual user input so a fresh run\n * starts with no memory of the prior cycle.\n */\n reset(): void;\n /**\n * Read-only view of the most recent normalized prompts (newest last).\n * Useful for diagnostics and for the message shown to the user when the\n * guard halts the loop.\n */\n history(): readonly string[];\n /**\n * How many entries the guard is currently retaining.\n */\n size(): number;\n}\n\n/**\n * Build a fresh loop guard. Defaults: `repeatThreshold = 2`, `windowSize = 3`.\n *\n * @example\n * const guard = createAutoProceedLoopGuard();\n * for (const prompt of candidates) {\n * const signal = guard.record(prompt);\n * if (signal.shouldHalt) {\n * haltAutoProceedAndAskUser(signal);\n * break;\n * }\n * feedPrompt(prompt);\n * }\n */\nexport function createAutoProceedLoopGuard(options: LoopGuardOptions = {}): AutoProceedLoopGuard {\n // Validate options defensively. Non-finite values (NaN, Infinity) MUST\n // fall back to the documented default \u2014 a guard that silently disables\n // itself is worse than no guard at all. Specifically:\n // - `repeatThreshold: NaN` would cause `Math.floor(NaN) = NaN`,\n // `Math.max(2, NaN) = NaN`, and `runLength >= NaN` is always false,\n // making the guard permanent no-op.\n // - `windowSize: NaN` / `Infinity` would skip the trim branch entirely\n // (`buffer.length > NaN` and `buffer.length > Infinity` are both\n // false), so the buffer would grow without bound.\n const safeIntegerOption = (value: number | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value)) return fallback;\n return Math.floor(value);\n };\n const repeatThreshold = Math.max(2, safeIntegerOption(options.repeatThreshold, 2));\n const windowSize = Math.max(repeatThreshold, safeIntegerOption(options.windowSize, 3));\n let buffer: string[] = [];\n\n function record(prompt: string): RepetitionSignal {\n const normalized = normalizeForRepetition(prompt);\n buffer.push(normalized);\n if (buffer.length > windowSize) {\n buffer = buffer.slice(buffer.length - windowSize);\n }\n // Count the trailing run of identical normalized prompts.\n let runLength = 0;\n for (let i = buffer.length - 1; i >= 0; i--) {\n if (buffer[i] === normalized) runLength++;\n else break;\n }\n return {\n normalized,\n runLength,\n shouldHalt: runLength >= repeatThreshold,\n };\n }\n\n function reset(): void {\n buffer = [];\n }\n\n function history(): readonly string[] {\n return buffer.slice();\n }\n\n function size(): number {\n return buffer.length;\n }\n\n return { record, reset, history, size };\n}\n"],
5
+ "mappings": ";AA2DO,SAAS,uBAAuB,QAAwB;AAC7D,SAAO,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,YAAY;AACxD;AAwEO,SAAS,2BAA2B,UAA4B,CAAC,GAAyB;AAU/F,QAAM,oBAAoB,CAAC,OAA2B,aAA6B;AACjF,QAAI,UAAU,OAAW,QAAO;AAChC,QAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AACA,QAAM,kBAAkB,KAAK,IAAI,GAAG,kBAAkB,QAAQ,iBAAiB,CAAC,CAAC;AACjF,QAAM,aAAa,KAAK,IAAI,iBAAiB,kBAAkB,QAAQ,YAAY,CAAC,CAAC;AACrF,MAAI,SAAmB,CAAC;AAExB,WAAS,OAAO,QAAkC;AAChD,UAAM,aAAa,uBAAuB,MAAM;AAChD,WAAO,KAAK,UAAU;AACtB,QAAI,OAAO,SAAS,YAAY;AAC9B,eAAS,OAAO,MAAM,OAAO,SAAS,UAAU;AAAA,IAClD;AAEA,QAAI,YAAY;AAChB,aAAS,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK;AAC3C,UAAI,OAAO,CAAC,MAAM,WAAY;AAAA,UACzB;AAAA,IACP;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,YAAY,aAAa;AAAA,IAC3B;AAAA,EACF;AAEA,WAAS,QAAc;AACrB,aAAS,CAAC;AAAA,EACZ;AAEA,WAAS,UAA6B;AACpC,WAAO,OAAO,MAAM;AAAA,EACtB;AAEA,WAAS,OAAe;AACtB,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO,EAAE,QAAQ,OAAO,SAAS,KAAK;AACxC;",
6
+ "names": []
7
+ }
package/dist/bash.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Tool } from '@wrongstack/core';
1
+ import { type Tool } from '@wrongstack/core';
2
2
  interface BashInput {
3
3
  command: string;
4
4
  timeout_ms?: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../src/bash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,IAAI,EAAmB,MAAM,kBAAkB,CAAC;AASvE,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AA4BD,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAkmBhD,CAAC;AAGF,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../src/bash.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,IAAI,EAEV,MAAM,kBAAkB,CAAC;AAS1B,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AA4BD,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAoqBhD,CAAC;AAGF,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC"}
package/dist/bash.js CHANGED
@@ -1,6 +1,11 @@
1
1
  // src/bash.ts
2
2
  import { spawn as spawn2 } from "node:child_process";
3
3
  import * as os4 from "node:os";
4
+ import {
5
+ emitProcessCompleted,
6
+ emitProcessOutput,
7
+ emitProcessStarted
8
+ } from "@wrongstack/core";
4
9
 
5
10
  // src/_env.ts
6
11
  import { buildChildEnv } from "@wrongstack/core";
@@ -386,9 +391,14 @@ import { expectDefined } from "@wrongstack/core";
386
391
  var SENSITIVE_FLAG_PATTERNS = [
387
392
  // --flag=value or --flag "value" (value captured up to next space or comma)
388
393
  /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
389
- // -f "value" style short flags
390
- /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/,
391
- /(?<!\w)-(?:p|password)(?:\s+|\s*=\s*)[^\s,]+/gi,
394
+ // -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
395
+ // (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
396
+ // NOTE: synced with @wrongstack/core observability/redact-command.ts.
397
+ /(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
398
+ // -p|-password|-a (redis auth) short flags: attached + separated + =value.
399
+ // Same token-start anchor; over-redaction is an accepted tradeoff for a
400
+ // redaction function. Synced with core copy.
401
+ /(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
392
402
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
393
403
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
394
404
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
@@ -406,8 +416,10 @@ function redactCommand(cmd) {
406
416
  const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
407
417
  return `${flag}[REDACTED]`;
408
418
  }
409
- const flagEnd = match.match(/^--?[a-zA-Z][a-zA-Z0-9_-]*/)?.[0] ?? match;
410
- return `${flagEnd}=**redacted**`;
419
+ if (match.startsWith("--")) {
420
+ return match;
421
+ }
422
+ return `${match.slice(0, 2)}[REDACTED]`;
411
423
  });
412
424
  }
413
425
  return result;
@@ -1669,6 +1681,32 @@ var bashTool = {
1669
1681
  }
1670
1682
  }
1671
1683
  const pid2 = child2.pid;
1684
+ let stdoutBytes2 = 0;
1685
+ let stderrBytes2 = 0;
1686
+ let telemetryCompleted2 = false;
1687
+ emitProcessStarted({
1688
+ ...pid2 !== void 0 ? { pid: pid2 } : {},
1689
+ parentPid: process.pid,
1690
+ command: redactCommand(`${shell} ${args.join(" ")}`),
1691
+ args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
1692
+ cwd: ctx.projectRoot,
1693
+ background: true,
1694
+ startedAt: new Date(startedAt).toISOString()
1695
+ });
1696
+ const completeBackground = (exitCode, signal) => {
1697
+ if (telemetryCompleted2) return;
1698
+ telemetryCompleted2 = true;
1699
+ emitProcessCompleted({
1700
+ ...pid2 !== void 0 ? { pid: pid2 } : {},
1701
+ exitCode,
1702
+ ...signal ? { signal } : {},
1703
+ durationMs: Date.now() - startedAt,
1704
+ stdoutBytes: stdoutBytes2,
1705
+ stderrBytes: stderrBytes2,
1706
+ timedOut: false,
1707
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
1708
+ });
1709
+ };
1672
1710
  if (typeof pid2 === "number") {
1673
1711
  registry.register({
1674
1712
  pid: pid2,
@@ -1681,7 +1719,10 @@ var bashTool = {
1681
1719
  });
1682
1720
  child2.on("close", () => registry.unregister(pid2));
1683
1721
  }
1684
- const onBgData = (chunk) => {
1722
+ const onBgData = (chunk, stream) => {
1723
+ if (stream === "stdout") stdoutBytes2 += chunk.byteLength;
1724
+ else stderrBytes2 += chunk.byteLength;
1725
+ emitProcessOutput({ pid: pid2, stream, chunk });
1685
1726
  if (truncated) return;
1686
1727
  const remain = MAX_OUTPUT - buf2.length;
1687
1728
  if (remain > 0) {
@@ -1689,24 +1730,28 @@ var bashTool = {
1689
1730
  }
1690
1731
  if (buf2.length >= MAX_OUTPUT) {
1691
1732
  truncated = true;
1692
- child2.stdout?.off("data", onBgData);
1693
- child2.stderr?.off("data", onBgData);
1733
+ child2.stdout?.off("data", onBgStdout);
1734
+ child2.stderr?.off("data", onBgStderr);
1694
1735
  }
1695
1736
  };
1696
- child2.stdout?.on("data", onBgData);
1697
- child2.stderr?.on("data", onBgData);
1737
+ const onBgStdout = (chunk) => onBgData(chunk, "stdout");
1738
+ const onBgStderr = (chunk) => onBgData(chunk, "stderr");
1739
+ child2.stdout?.on("data", onBgStdout);
1740
+ child2.stderr?.on("data", onBgStderr);
1698
1741
  const cleanupBackground = () => {
1699
- child2.stdout?.off("data", onBgData);
1700
- child2.stderr?.off("data", onBgData);
1742
+ child2.stdout?.off("data", onBgStdout);
1743
+ child2.stderr?.off("data", onBgStderr);
1701
1744
  };
1702
1745
  child2.on("error", () => {
1703
1746
  cleanupBackground();
1704
1747
  if (typeof pid2 === "number") registry.unregister(pid2);
1705
1748
  registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
1749
+ completeBackground(1);
1706
1750
  });
1707
- child2.on("close", () => {
1751
+ child2.on("close", (code, signal) => {
1708
1752
  cleanupBackground();
1709
1753
  registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
1754
+ completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
1710
1755
  });
1711
1756
  if (typeof pid2 === "number") child2.unref();
1712
1757
  yield {
@@ -1746,6 +1791,32 @@ var bashTool = {
1746
1791
  }
1747
1792
  }
1748
1793
  const pid = child.pid;
1794
+ let stdoutBytes = 0;
1795
+ let stderrBytes = 0;
1796
+ let telemetryCompleted = false;
1797
+ emitProcessStarted({
1798
+ ...pid !== void 0 ? { pid } : {},
1799
+ parentPid: process.pid,
1800
+ command: redactCommand(`${shell} ${args.join(" ")}`),
1801
+ args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
1802
+ cwd: ctx.projectRoot,
1803
+ background: false,
1804
+ startedAt: new Date(startedAt).toISOString()
1805
+ });
1806
+ const completeForeground = (exitCode, signal) => {
1807
+ if (telemetryCompleted) return;
1808
+ telemetryCompleted = true;
1809
+ emitProcessCompleted({
1810
+ ...pid !== void 0 ? { pid } : {},
1811
+ exitCode,
1812
+ ...signal ? { signal } : {},
1813
+ durationMs: Date.now() - startedAt,
1814
+ stdoutBytes,
1815
+ stderrBytes,
1816
+ timedOut,
1817
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
1818
+ });
1819
+ };
1749
1820
  if (typeof pid === "number") {
1750
1821
  registry.register({
1751
1822
  pid,
@@ -1847,8 +1918,11 @@ var bashTool = {
1847
1918
  child.stderr?.resume();
1848
1919
  }
1849
1920
  };
1850
- const onData = (chunk) => {
1921
+ const onData = (chunk, stream) => {
1851
1922
  const text = chunk.toString();
1923
+ if (stream === "stdout") stdoutBytes += chunk.byteLength;
1924
+ else stderrBytes += chunk.byteLength;
1925
+ emitProcessOutput({ pid, stream, chunk });
1852
1926
  if (buf.length < MAX_OUTPUT) {
1853
1927
  buf += text.slice(0, MAX_OUTPUT - buf.length);
1854
1928
  }
@@ -1857,17 +1931,21 @@ var bashTool = {
1857
1931
  push({ kind: "data", text });
1858
1932
  pauseIfFlooded();
1859
1933
  };
1860
- child.stdout?.on("data", onData);
1861
- child.stderr?.on("data", onData);
1934
+ const onStdoutData = (chunk) => onData(chunk, "stdout");
1935
+ const onStderrData = (chunk) => onData(chunk, "stderr");
1936
+ child.stdout?.on("data", onStdoutData);
1937
+ child.stderr?.on("data", onStderrData);
1862
1938
  child.on("error", (err) => {
1863
1939
  for (const t of timers) clearTimeout(t);
1864
1940
  registry.afterCall(Date.now() - startedAt, true);
1941
+ completeForeground(1);
1865
1942
  push({ kind: "error", err });
1866
1943
  });
1867
- child.on("close", (code) => {
1944
+ child.on("close", (code, signal) => {
1868
1945
  for (const t of timers) clearTimeout(t);
1869
1946
  if (typeof pid === "number") registry.unregister(pid);
1870
1947
  registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);
1948
+ completeForeground(timedOut ? 124 : code ?? (signal ? 1 : 0), signal ?? void 0);
1871
1949
  push({ kind: "end", code });
1872
1950
  });
1873
1951
  try {
@@ -1912,8 +1990,8 @@ ${hint}` : ""),
1912
1990
  for (const t of timers) clearTimeout(t);
1913
1991
  spool.finalize();
1914
1992
  if (isWin2) opts.signal.removeEventListener("abort", onAbort);
1915
- child.stdout?.off("data", onData);
1916
- child.stderr?.off("data", onData);
1993
+ child.stdout?.off("data", onStdoutData);
1994
+ child.stderr?.off("data", onStderrData);
1917
1995
  child.stdout?.destroy();
1918
1996
  child.stderr?.destroy();
1919
1997
  if (child.exitCode === null && !child.killed) {