@wrongstack/tools 0.286.0 → 0.289.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 (120) hide show
  1. package/README.md +2 -2
  2. package/dist/_edit-match.d.ts.map +1 -1
  3. package/dist/_fetch-guard.d.ts.map +1 -1
  4. package/dist/_redact-command.d.ts.map +1 -1
  5. package/dist/_spawn-stream.d.ts.map +1 -1
  6. package/dist/audit.js +61 -14
  7. package/dist/audit.js.map +3 -3
  8. package/dist/bash.d.ts +1 -1
  9. package/dist/bash.d.ts.map +1 -1
  10. package/dist/bash.js +97 -19
  11. package/dist/bash.js.map +3 -3
  12. package/dist/batch-tool-use.d.ts +1 -1
  13. package/dist/batch-tool-use.d.ts.map +1 -1
  14. package/dist/batch-tool-use.js +26 -8
  15. package/dist/batch-tool-use.js.map +2 -2
  16. package/dist/browser/index.js +1 -1
  17. package/dist/browser/index.js.map +1 -1
  18. package/dist/browser/types.d.ts +1 -1
  19. package/dist/browser/types.d.ts.map +1 -1
  20. package/dist/builtin.d.ts +12 -10
  21. package/dist/builtin.d.ts.map +1 -1
  22. package/dist/builtin.js +2107 -864
  23. package/dist/builtin.js.map +4 -4
  24. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  25. package/dist/codebase-index/bm25.d.ts +1 -2
  26. package/dist/codebase-index/bm25.d.ts.map +1 -1
  27. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
  28. package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
  29. package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
  30. package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
  31. package/dist/codebase-index/go-parser.d.ts.map +1 -1
  32. package/dist/codebase-index/index-service.d.ts +11 -1
  33. package/dist/codebase-index/index-service.d.ts.map +1 -1
  34. package/dist/codebase-index/index.d.ts +2 -1
  35. package/dist/codebase-index/index.d.ts.map +1 -1
  36. package/dist/codebase-index/index.js +934 -283
  37. package/dist/codebase-index/index.js.map +4 -4
  38. package/dist/codebase-index/indexer.d.ts.map +1 -1
  39. package/dist/codebase-index/py-parser.d.ts.map +1 -1
  40. package/dist/codebase-index/rs-parser.d.ts.map +1 -1
  41. package/dist/codebase-index/schema.d.ts +43 -1
  42. package/dist/codebase-index/schema.d.ts.map +1 -1
  43. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  44. package/dist/codebase-index/worker-protocol.d.ts +1 -0
  45. package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
  46. package/dist/codebase-index/worker.js +793 -241
  47. package/dist/codebase-index/worker.js.map +4 -4
  48. package/dist/codebase-index/writer.d.ts +27 -1
  49. package/dist/codebase-index/writer.d.ts.map +1 -1
  50. package/dist/e2e.js +477 -0
  51. package/dist/e2e.js.map +7 -0
  52. package/dist/edit.d.ts.map +1 -1
  53. package/dist/edit.js +36 -12
  54. package/dist/edit.js.map +3 -3
  55. package/dist/exec.d.ts +1 -1
  56. package/dist/exec.d.ts.map +1 -1
  57. package/dist/exec.js +60 -5
  58. package/dist/exec.js.map +3 -3
  59. package/dist/fetch.js +2 -2
  60. package/dist/fetch.js.map +2 -2
  61. package/dist/format.js +61 -14
  62. package/dist/format.js.map +3 -3
  63. package/dist/glob.js +2 -2
  64. package/dist/glob.js.map +1 -1
  65. package/dist/grep.d.ts.map +1 -1
  66. package/dist/grep.js +2 -2
  67. package/dist/grep.js.map +2 -2
  68. package/dist/index.d.ts +3 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +2535 -865
  71. package/dist/index.js.map +4 -4
  72. package/dist/install.js +61 -14
  73. package/dist/install.js.map +3 -3
  74. package/dist/kanban.d.ts +16 -2
  75. package/dist/kanban.d.ts.map +1 -1
  76. package/dist/kanban.js +795 -532
  77. package/dist/kanban.js.map +4 -4
  78. package/dist/languages/detect.d.ts.map +1 -1
  79. package/dist/languages/index.js +61 -14
  80. package/dist/languages/index.js.map +3 -3
  81. package/dist/languages/types.d.ts +7 -0
  82. package/dist/languages/types.d.ts.map +1 -1
  83. package/dist/lint.js +61 -14
  84. package/dist/lint.js.map +3 -3
  85. package/dist/next-steps.d.ts +6 -2
  86. package/dist/next-steps.d.ts.map +1 -1
  87. package/dist/next-steps.js +3 -3
  88. package/dist/next-steps.js.map +2 -2
  89. package/dist/outdated.js +61 -14
  90. package/dist/outdated.js.map +3 -3
  91. package/dist/pack.js +2104 -861
  92. package/dist/pack.js.map +4 -4
  93. package/dist/plan.d.ts.map +1 -1
  94. package/dist/plan.js +158 -4
  95. package/dist/plan.js.map +4 -4
  96. package/dist/process-registry.js +12 -5
  97. package/dist/process-registry.js.map +2 -2
  98. package/dist/search.js +2 -2
  99. package/dist/search.js.map +2 -2
  100. package/dist/session-kanban.d.ts +47 -0
  101. package/dist/session-kanban.d.ts.map +1 -0
  102. package/dist/session-kanban.js +626 -0
  103. package/dist/session-kanban.js.map +7 -0
  104. package/dist/task.d.ts.map +1 -1
  105. package/dist/task.js +167 -4
  106. package/dist/task.js.map +4 -4
  107. package/dist/test.js +61 -14
  108. package/dist/test.js.map +3 -3
  109. package/dist/todo.d.ts.map +1 -1
  110. package/dist/todo.js +159 -7
  111. package/dist/todo.js.map +4 -4
  112. package/dist/tool-icon-map.d.ts.map +1 -1
  113. package/dist/tool-icons.d.ts.map +1 -1
  114. package/dist/tool-icons.js +7 -1
  115. package/dist/tool-icons.js.map +2 -2
  116. package/dist/tree.js +2 -2
  117. package/dist/tree.js.map +1 -1
  118. package/dist/typecheck.js +61 -14
  119. package/dist/typecheck.js.map +3 -3
  120. package/package.json +11 -3
@@ -19,6 +19,10 @@
19
19
  * Supported assistant-output format:
20
20
  * <nextsteps> (canonical XML tag format)
21
21
  *
22
+ * The parser also tolerates attributes on the opening tag so malformed model
23
+ * output such as `<nextsteps auto="true">` does not leak into the UI. Opening
24
+ * tag attributes are ignored; `auto="true"` has meaning only on the first item.
25
+ *
22
26
  * This module is BROWSER-SAFE — no Node-only imports — so it is safe to import
23
27
  * from Vite-bundled WebUI as well as Node-based CLI/TUI. Mirrors the proven
24
28
  * `tool-summary` / `tool-icons` extraction pattern (PRs #236 / #237 / #238).
@@ -26,7 +30,7 @@
26
30
  export interface ParsedNextStep {
27
31
  index: number;
28
32
  text: string;
29
- /** Whether this item has auto="true" attribute for YOLO+auto autonomy mode. */
33
+ /** Whether the first item has auto="true" for YOLO+auto autonomy mode. */
30
34
  auto?: boolean;
31
35
  }
32
36
  export interface ParseNextStepsResult {
@@ -39,7 +43,7 @@ export interface ParseNextStepsResult {
39
43
  * Used by entry.tsx to strip suggestions from the rendered message body.
40
44
  */
41
45
  stripped: string;
42
- /** Flat string array texts of items with auto="true" attribute only. */
46
+ /** The first item's text when it has auto="true"; otherwise empty. */
43
47
  autoTexts: string[];
44
48
  }
45
49
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"next-steps.d.ts","sourceRoot":"","sources":["../src/next-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,oEAAoE;IACpE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAeD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,UAAQ,EACd,cAAc,UAAO,GACpB,oBAAoB,CAKtB;AAoKD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxD"}
1
+ {"version":3,"file":"next-steps.d.ts","sourceRoot":"","sources":["../src/next-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,oEAAoE;IACpE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,UAAQ,EACd,cAAc,UAAO,GACpB,oBAAoB,CAKtB;AAuKD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxD"}
@@ -1,5 +1,5 @@
1
1
  // src/next-steps.ts
2
- var NEXT_STEPS_TAG_RE = /<nextsteps>\s*\n+/i;
2
+ var NEXT_STEPS_TAG_RE = /<nextsteps\b[^>]*>\s*\n+/i;
3
3
  var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+?)(\s+auto="true")?$/;
4
4
  var MAX_STEPS = 6;
5
5
  function parseNextSteps(content, strict = false, requireHeading = true) {
@@ -22,7 +22,6 @@ function parseRawNumbered(content) {
22
22
  if (!m) continue;
23
23
  const numPart = m[1];
24
24
  const text = m[2].trim();
25
- const hasAuto = !!m[3];
26
25
  let index;
27
26
  if (numPart !== void 0) {
28
27
  index = Number.parseInt(numPart, 10);
@@ -31,6 +30,7 @@ function parseRawNumbered(content) {
31
30
  }
32
31
  if (seenNumbers.has(index)) continue;
33
32
  seenNumbers.add(index);
33
+ const hasAuto = !!m[3] && steps.length === 0 && index === 1;
34
34
  pushStep(steps, index, text, hasAuto);
35
35
  if (steps.length >= MAX_STEPS) break;
36
36
  }
@@ -58,7 +58,6 @@ function parseWithHeading(content, _strict) {
58
58
  if (!m) break;
59
59
  const numPart = m[1];
60
60
  const text = m[2].trim();
61
- const hasAuto = !!m[3];
62
61
  let index;
63
62
  if (numPart !== void 0) {
64
63
  index = Number.parseInt(numPart, 10);
@@ -67,6 +66,7 @@ function parseWithHeading(content, _strict) {
67
66
  }
68
67
  if (seenNumbers.has(index)) continue;
69
68
  seenNumbers.add(index);
69
+ const hasAuto = !!m[3] && steps.length === 0 && index === 1;
70
70
  pushStep(steps, index, text, hasAuto);
71
71
  if (steps.length >= MAX_STEPS) break;
72
72
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/next-steps.ts"],
4
- "sourcesContent": ["/**\n * Canonical `<nextsteps>` block parser \u2014 the single source of truth shared by\n * the TUI history renderer, the CLI REPL suggestion store, the CLI /suggest\n * subagent output, and the WebUI MessageBubble/NextStepsBar.\n *\n * Three code paths feed into the parser:\n * 1. TUI rendering \u2014 entry.tsx parses only \"<nextsteps>\" from assistant output\n * 2. REPL store \u2014 repl.ts parses only \"<nextsteps>\" from final agent output\n * 3. /suggest output \u2014 suggest.ts parses LLM-generated numbered lists (raw mode)\n *\n * Heading mode (`requireHeading = true`, the default):\n * Assistant-output paths accept only balanced <nextsteps>...</nextsteps> blocks.\n * Loose headings like \"Next steps:\" are intentionally ignored so `/next` only\n * activates for the canonical machine-readable format.\n *\n * Raw mode (`requireHeading = false`):\n * Parses numbered/bullet items from anywhere in text (subagent /suggest output).\n *\n * Supported assistant-output format:\n * <nextsteps> (canonical XML tag format)\n *\n * This module is BROWSER-SAFE \u2014 no Node-only imports \u2014 so it is safe to import\n * from Vite-bundled WebUI as well as Node-based CLI/TUI. Mirrors the proven\n * `tool-summary` / `tool-icons` extraction pattern (PRs #236 / #237 / #238).\n */\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ParsedNextStep {\n index: number;\n text: string;\n /** Whether this item has auto=\"true\" attribute for YOLO+auto autonomy mode. */\n auto?: boolean;\n}\n\nexport interface ParseNextStepsResult {\n /** Matched steps with their original index and stripped text. */\n steps: ParsedNextStep[];\n /** Flat string array \u2014 what gets stored in the suggestion store. */\n texts: string[];\n /**\n * Content with the entire canonical \"<nextsteps>\" block removed.\n * Used by entry.tsx to strip suggestions from the rendered message body.\n */\n stripped: string;\n /** Flat string array \u2014 texts of items with auto=\"true\" attribute only. */\n autoTexts: string[];\n}\n\n// \u2500\u2500 Patterns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Matches the canonical <nextsteps> tag before numbered items. */\nconst NEXT_STEPS_TAG_RE = /<nextsteps>\\s*\\n+/i;\n\n/** Matches an item line: \"1. text\", \"1) text\", \"- text\", \"* text\". */\n/** Also captures optional auto=\"true\" attribute at the end. */\nconst ITEM_RE = /^(?:(\\d+)[.)]\\s*|[-*\u2022]\\s*)(.+?)(\\s+auto=\"true\")?$/;\n\nconst MAX_STEPS = 6;\n\n// \u2500\u2500 Core parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Parse canonical \"<nextsteps>\" blocks from assistant output (or raw numbered lines).\n *\n * @param content \u2014 raw assistant message text or subagent output\n * @param strict \u2014 retained for compatibility; assistant-output paths always require the canonical XML tag.\n * @param requireHeading \u2014 when true, a canonical XML tag must precede the item list.\n * when false, numbered/bullet items are parsed from anywhere in text\n * (used by /suggest subagent output which has no heading).\n */\nexport function parseNextSteps(\n content: string,\n strict = false,\n requireHeading = true,\n): ParseNextStepsResult {\n if (requireHeading) {\n return parseWithHeading(content, strict);\n }\n return parseRawNumbered(content);\n}\n\n/** Push a step into the array, omitting `auto` when false (cleaner shape, matches WebUI parity). */\nfunction pushStep(\n steps: ParsedNextStep[],\n index: number,\n text: string,\n hasAuto: boolean,\n): void {\n steps.push(hasAuto ? { index, text, auto: true } : { index, text });\n}\n\n/** Parse numbered/bullet items from raw text without a heading. */\nfunction parseRawNumbered(content: string): ParseNextStepsResult {\n const lines = content.split('\\n');\n const steps: ParsedNextStep[] = [];\n const seenNumbers = new Set<number>();\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line) continue;\n\n const m = ITEM_RE.exec(line);\n if (!m) continue; // skip non-item lines in raw mode\n\n const numPart = m[1];\n const text = m[2]!.trim();\n const hasAuto = !!m[3]; // auto=\"true\" captured in group 3\n let index: number;\n\n if (numPart !== undefined) {\n index = Number.parseInt(numPart, 10);\n } else {\n index = steps.length + 1; // bullet items get sequential indices\n }\n\n if (seenNumbers.has(index)) continue;\n seenNumbers.add(index);\n pushStep(steps, index, text, hasAuto);\n\n if (steps.length >= MAX_STEPS) break;\n }\n\n return {\n steps,\n texts: steps.map((s) => s.text),\n stripped: content,\n autoTexts: steps.filter((s) => s.auto).map((s) => s.text),\n };\n}\n\n/** Parse a heading + item block (the main assistant-message path). */\nfunction parseWithHeading(content: string, _strict: boolean): ParseNextStepsResult {\n const headingMatch = NEXT_STEPS_TAG_RE.exec(content);\n\n if (!headingMatch) {\n return { steps: [], texts: [], stripped: content, autoTexts: [] };\n }\n\n const headingEnd = headingMatch.index + headingMatch[0]!.length;\n const afterHeading = content.slice(headingEnd);\n const lines = afterHeading.split('\\n');\n const steps: ParsedNextStep[] = [];\n const seenNumbers = new Set<number>();\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line) continue;\n\n const m = ITEM_RE.exec(line);\n if (!m) break; // non-item line \u2014 block ends\n\n const numPart = m[1];\n const text = m[2]!.trim();\n const hasAuto = !!m[3]; // auto=\"true\" captured in group 3\n let index: number;\n\n if (numPart !== undefined) {\n index = Number.parseInt(numPart, 10);\n } else {\n index = steps.length + 1;\n }\n\n if (seenNumbers.has(index)) continue;\n seenNumbers.add(index);\n pushStep(steps, index, text, hasAuto);\n\n if (steps.length >= MAX_STEPS) break;\n }\n\n if (steps.length === 0) {\n return { steps: [], texts: [], stripped: content, autoTexts: [] };\n }\n\n // Require a closing tag. Malformed XML is rejected so raw text remains\n // visible instead of being partially consumed as automation input.\n if (!afterHeading.includes('</nextsteps>')) {\n return { steps: [], texts: [], stripped: content, autoTexts: [] };\n }\n\n const texts = steps.map((s) => s.text);\n const autoTexts = steps.filter((s) => s.auto).map((s) => s.text);\n\n // Strip the entire XML block from the content. `blockEnd` is the LENGTH of\n // that block, so `content.slice(blockStart + blockEnd)` is the rest of the content.\n const blockStart = headingMatch.index;\n const blockEnd = headingMatch[0]!.length + findBlockEnd(afterHeading, steps.length);\n const stripped =\n (content.slice(0, blockStart) + content.slice(blockStart + blockEnd))\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n\n return { steps, texts, stripped, autoTexts };\n}\n\n/**\n * Find the byte offset in `afterHeading` where the block ends.\n *\n * The block to strip is the items (one per line) plus the `</nextsteps>`\n * closing tag (and the trailing newline after it).\n *\n * Returns the byte offset of the first character AFTER the block. The\n * caller's `content.slice(0, blockStart) + content.slice(blockStart + offset)`\n * then produces the stripped content.\n *\n * Walks line-by-line. Stops at the first non-item line, the closing XML\n * tag, or the end of the input \u2014 whichever comes first.\n */\nfunction findBlockEnd(afterHeading: string, stepCount: number): number {\n // Fast path: if the block is the <nextsteps> XML form, find the closing\n // tag and return its end (consuming the tag + trailing newline).\n const closeIdx = afterHeading.indexOf('</nextsteps>');\n if (closeIdx !== -1) {\n let end = closeIdx + '</nextsteps>'.length;\n if (afterHeading[end] === '\\n') end += 1;\n return end;\n }\n\n // Defensive fallback for malformed input that reached this helper without a\n // closing tag. The caller rejects such input before using the stripped text.\n const lines = afterHeading.split('\\n');\n let consumed = 0;\n let found = 0;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line) break; // blank line ends the block\n\n const m = ITEM_RE.exec(line);\n if (!m) break; // non-item line ends the block\n\n consumed += rawLine.length + 1; // +1 for the \\n separator\n found++;\n if (found >= stepCount) {\n // Don't include the trailing newline of the last item \u2014 the slice\n // logic in the caller handles whitespace cleanup.\n consumed -= 1;\n break;\n }\n }\n\n return consumed;\n}\n\n/**\n * Strip <nextsteps>...</nextsteps> blocks from subagent output text.\n * Subagent results should not contain suggestion blocks \u2014 those belong to\n * the main assistant's output. This prevents raw XML tags from appearing\n * as literal text in the fleet panel.\n *\n * Also strips the legacy `<next_steps>` spelling that older persisted\n * subagent output may contain (WebUI parity).\n */\nexport function stripNextStepsBlock(text: string): string {\n return text\n .replace(/<next_?steps\\b[^>]*>[\\s\\S]*?<\\/next_?steps>/gi, '')\n .replace(/<next_?steps\\b[^>]*\\/?>/gi, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Canonical `<nextsteps>` block parser \u2014 the single source of truth shared by\n * the TUI history renderer, the CLI REPL suggestion store, the CLI /suggest\n * subagent output, and the WebUI MessageBubble/NextStepsBar.\n *\n * Three code paths feed into the parser:\n * 1. TUI rendering \u2014 entry.tsx parses only \"<nextsteps>\" from assistant output\n * 2. REPL store \u2014 repl.ts parses only \"<nextsteps>\" from final agent output\n * 3. /suggest output \u2014 suggest.ts parses LLM-generated numbered lists (raw mode)\n *\n * Heading mode (`requireHeading = true`, the default):\n * Assistant-output paths accept only balanced <nextsteps>...</nextsteps> blocks.\n * Loose headings like \"Next steps:\" are intentionally ignored so `/next` only\n * activates for the canonical machine-readable format.\n *\n * Raw mode (`requireHeading = false`):\n * Parses numbered/bullet items from anywhere in text (subagent /suggest output).\n *\n * Supported assistant-output format:\n * <nextsteps> (canonical XML tag format)\n *\n * The parser also tolerates attributes on the opening tag so malformed model\n * output such as `<nextsteps auto=\"true\">` does not leak into the UI. Opening\n * tag attributes are ignored; `auto=\"true\"` has meaning only on the first item.\n *\n * This module is BROWSER-SAFE \u2014 no Node-only imports \u2014 so it is safe to import\n * from Vite-bundled WebUI as well as Node-based CLI/TUI. Mirrors the proven\n * `tool-summary` / `tool-icons` extraction pattern (PRs #236 / #237 / #238).\n */\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ParsedNextStep {\n index: number;\n text: string;\n /** Whether the first item has auto=\"true\" for YOLO+auto autonomy mode. */\n auto?: boolean;\n}\n\nexport interface ParseNextStepsResult {\n /** Matched steps with their original index and stripped text. */\n steps: ParsedNextStep[];\n /** Flat string array \u2014 what gets stored in the suggestion store. */\n texts: string[];\n /**\n * Content with the entire canonical \"<nextsteps>\" block removed.\n * Used by entry.tsx to strip suggestions from the rendered message body.\n */\n stripped: string;\n /** The first item's text when it has auto=\"true\"; otherwise empty. */\n autoTexts: string[];\n}\n\n// \u2500\u2500 Patterns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Matches the canonical <nextsteps> tag before numbered items.\n *\n * Attributes are accepted defensively because models occasionally attach the\n * item-only `auto=\"true\"` marker to this tag. They never affect parser state.\n */\nconst NEXT_STEPS_TAG_RE = /<nextsteps\\b[^>]*>\\s*\\n+/i;\n\n/** Matches an item line: \"1. text\", \"1) text\", \"- text\", \"* text\". */\n/** Also captures optional auto=\"true\" attribute at the end. */\nconst ITEM_RE = /^(?:(\\d+)[.)]\\s*|[-*\u2022]\\s*)(.+?)(\\s+auto=\"true\")?$/;\n\nconst MAX_STEPS = 6;\n\n// \u2500\u2500 Core parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Parse canonical \"<nextsteps>\" blocks from assistant output (or raw numbered lines).\n *\n * @param content \u2014 raw assistant message text or subagent output\n * @param strict \u2014 retained for compatibility; assistant-output paths always require the canonical XML tag.\n * @param requireHeading \u2014 when true, a canonical XML tag must precede the item list.\n * when false, numbered/bullet items are parsed from anywhere in text\n * (used by /suggest subagent output which has no heading).\n */\nexport function parseNextSteps(\n content: string,\n strict = false,\n requireHeading = true,\n): ParseNextStepsResult {\n if (requireHeading) {\n return parseWithHeading(content, strict);\n }\n return parseRawNumbered(content);\n}\n\n/** Push a step into the array, omitting `auto` when false (cleaner shape, matches WebUI parity). */\nfunction pushStep(\n steps: ParsedNextStep[],\n index: number,\n text: string,\n hasAuto: boolean,\n): void {\n steps.push(hasAuto ? { index, text, auto: true } : { index, text });\n}\n\n/** Parse numbered/bullet items from raw text without a heading. */\nfunction parseRawNumbered(content: string): ParseNextStepsResult {\n const lines = content.split('\\n');\n const steps: ParsedNextStep[] = [];\n const seenNumbers = new Set<number>();\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line) continue;\n\n const m = ITEM_RE.exec(line);\n if (!m) continue; // skip non-item lines in raw mode\n\n const numPart = m[1];\n const text = m[2]!.trim();\n let index: number;\n\n if (numPart !== undefined) {\n index = Number.parseInt(numPart, 10);\n } else {\n index = steps.length + 1; // bullet items get sequential indices\n }\n\n if (seenNumbers.has(index)) continue;\n seenNumbers.add(index);\n // Auto-submit is deliberately restricted to the first accepted item.\n // Later misplaced markers are stripped from display text but ignored.\n const hasAuto = !!m[3] && steps.length === 0 && index === 1;\n pushStep(steps, index, text, hasAuto);\n\n if (steps.length >= MAX_STEPS) break;\n }\n\n return {\n steps,\n texts: steps.map((s) => s.text),\n stripped: content,\n autoTexts: steps.filter((s) => s.auto).map((s) => s.text),\n };\n}\n\n/** Parse a heading + item block (the main assistant-message path). */\nfunction parseWithHeading(content: string, _strict: boolean): ParseNextStepsResult {\n const headingMatch = NEXT_STEPS_TAG_RE.exec(content);\n\n if (!headingMatch) {\n return { steps: [], texts: [], stripped: content, autoTexts: [] };\n }\n\n const headingEnd = headingMatch.index + headingMatch[0]!.length;\n const afterHeading = content.slice(headingEnd);\n const lines = afterHeading.split('\\n');\n const steps: ParsedNextStep[] = [];\n const seenNumbers = new Set<number>();\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line) continue;\n\n const m = ITEM_RE.exec(line);\n if (!m) break; // non-item line \u2014 block ends\n\n const numPart = m[1];\n const text = m[2]!.trim();\n let index: number;\n\n if (numPart !== undefined) {\n index = Number.parseInt(numPart, 10);\n } else {\n index = steps.length + 1;\n }\n\n if (seenNumbers.has(index)) continue;\n seenNumbers.add(index);\n // Only the first accepted item may opt into unattended auto-submit.\n const hasAuto = !!m[3] && steps.length === 0 && index === 1;\n pushStep(steps, index, text, hasAuto);\n\n if (steps.length >= MAX_STEPS) break;\n }\n\n if (steps.length === 0) {\n return { steps: [], texts: [], stripped: content, autoTexts: [] };\n }\n\n // Require a closing tag. Malformed XML is rejected so raw text remains\n // visible instead of being partially consumed as automation input.\n if (!afterHeading.includes('</nextsteps>')) {\n return { steps: [], texts: [], stripped: content, autoTexts: [] };\n }\n\n const texts = steps.map((s) => s.text);\n const autoTexts = steps.filter((s) => s.auto).map((s) => s.text);\n\n // Strip the entire XML block from the content. `blockEnd` is the LENGTH of\n // that block, so `content.slice(blockStart + blockEnd)` is the rest of the content.\n const blockStart = headingMatch.index;\n const blockEnd = headingMatch[0]!.length + findBlockEnd(afterHeading, steps.length);\n const stripped =\n (content.slice(0, blockStart) + content.slice(blockStart + blockEnd))\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n\n return { steps, texts, stripped, autoTexts };\n}\n\n/**\n * Find the byte offset in `afterHeading` where the block ends.\n *\n * The block to strip is the items (one per line) plus the `</nextsteps>`\n * closing tag (and the trailing newline after it).\n *\n * Returns the byte offset of the first character AFTER the block. The\n * caller's `content.slice(0, blockStart) + content.slice(blockStart + offset)`\n * then produces the stripped content.\n *\n * Walks line-by-line. Stops at the first non-item line, the closing XML\n * tag, or the end of the input \u2014 whichever comes first.\n */\nfunction findBlockEnd(afterHeading: string, stepCount: number): number {\n // Fast path: if the block is the <nextsteps> XML form, find the closing\n // tag and return its end (consuming the tag + trailing newline).\n const closeIdx = afterHeading.indexOf('</nextsteps>');\n if (closeIdx !== -1) {\n let end = closeIdx + '</nextsteps>'.length;\n if (afterHeading[end] === '\\n') end += 1;\n return end;\n }\n\n // Defensive fallback for malformed input that reached this helper without a\n // closing tag. The caller rejects such input before using the stripped text.\n const lines = afterHeading.split('\\n');\n let consumed = 0;\n let found = 0;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line) break; // blank line ends the block\n\n const m = ITEM_RE.exec(line);\n if (!m) break; // non-item line ends the block\n\n consumed += rawLine.length + 1; // +1 for the \\n separator\n found++;\n if (found >= stepCount) {\n // Don't include the trailing newline of the last item \u2014 the slice\n // logic in the caller handles whitespace cleanup.\n consumed -= 1;\n break;\n }\n }\n\n return consumed;\n}\n\n/**\n * Strip <nextsteps>...</nextsteps> blocks from subagent output text.\n * Subagent results should not contain suggestion blocks \u2014 those belong to\n * the main assistant's output. This prevents raw XML tags from appearing\n * as literal text in the fleet panel.\n *\n * Also strips the legacy `<next_steps>` spelling that older persisted\n * subagent output may contain (WebUI parity).\n */\nexport function stripNextStepsBlock(text: string): string {\n return text\n .replace(/<next_?steps\\b[^>]*>[\\s\\S]*?<\\/next_?steps>/gi, '')\n .replace(/<next_?steps\\b[^>]*\\/?>/gi, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/outdated.js CHANGED
@@ -1577,13 +1577,14 @@ async function detectLanguageWorkspaces(options) {
1577
1577
  const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
1578
1578
  const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
1579
1579
  const limits = normalizeLimits(options.limits);
1580
+ const extraIgnores = new Set(options.ignoredDirectories ?? []);
1580
1581
  const state = {
1581
1582
  entries: 0,
1582
1583
  truncated: false,
1583
1584
  sourcePaths: /* @__PURE__ */ new Map(),
1584
1585
  candidates: /* @__PURE__ */ new Map()
1585
1586
  };
1586
- await scanDirectory(projectRoot, 0, profiles, limits, state, options.signal);
1587
+ await scanDirectory(projectRoot, 0, profiles, limits, state, extraIgnores, options.signal);
1587
1588
  addSourceFallbacks(projectRoot, profiles, state);
1588
1589
  if (target) addTargetEvidence(target, projectRoot, profiles, state);
1589
1590
  const workspaces = await Promise.all(
@@ -1597,7 +1598,7 @@ async function detectLanguageWorkspaces(options) {
1597
1598
  workspaces
1598
1599
  };
1599
1600
  }
1600
- async function scanDirectory(directory, depth, profiles, limits, state, signal) {
1601
+ async function scanDirectory(directory, depth, profiles, limits, state, extraIgnores, signal) {
1601
1602
  signal?.throwIfAborted();
1602
1603
  if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
1603
1604
  state.truncated = true;
@@ -1620,12 +1621,12 @@ async function scanDirectory(directory, depth, profiles, limits, state, signal)
1620
1621
  const fullPath = path3.join(directory, entry.name);
1621
1622
  if (entry.isSymbolicLink()) continue;
1622
1623
  if (entry.isDirectory()) {
1623
- if (shouldIgnoreDirectory(entry.name, profiles)) continue;
1624
+ if (shouldIgnoreDirectory(entry.name, profiles, extraIgnores)) continue;
1624
1625
  if (depth >= limits.maxDepth) {
1625
1626
  state.truncated = true;
1626
1627
  continue;
1627
1628
  }
1628
- await scanDirectory(fullPath, depth + 1, profiles, limits, state, signal);
1629
+ await scanDirectory(fullPath, depth + 1, profiles, limits, state, extraIgnores, signal);
1629
1630
  continue;
1630
1631
  }
1631
1632
  if (!entry.isFile()) continue;
@@ -1754,8 +1755,8 @@ function getCandidate(state, profile, root) {
1754
1755
  }
1755
1756
  return candidate;
1756
1757
  }
1757
- function shouldIgnoreDirectory(name, profiles) {
1758
- if (GLOBAL_IGNORES.has(name) || name.startsWith(".")) return true;
1758
+ function shouldIgnoreDirectory(name, profiles, extraIgnores) {
1759
+ if (GLOBAL_IGNORES.has(name) || extraIgnores.has(name) || name.startsWith(".")) return true;
1759
1760
  return profiles.some((profile) => profile.ignoredDirectories.includes(name));
1760
1761
  }
1761
1762
  function normalizeLimits(input) {
@@ -2648,8 +2649,10 @@ function redactCommand(cmd) {
2648
2649
  const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
2649
2650
  return `${flag}[REDACTED]`;
2650
2651
  }
2651
- const flagEnd = match.match(/^--?[a-zA-Z][a-zA-Z0-9_-]*/)?.[0] ?? match;
2652
- return `${flagEnd}=**redacted**`;
2652
+ if (match.startsWith("--")) {
2653
+ return match;
2654
+ }
2655
+ return `${match.slice(0, 2)}[REDACTED]`;
2653
2656
  });
2654
2657
  }
2655
2658
  return result;
@@ -2661,9 +2664,14 @@ var init_redact_command = __esm({
2661
2664
  SENSITIVE_FLAG_PATTERNS = [
2662
2665
  // --flag=value or --flag "value" (value captured up to next space or comma)
2663
2666
  /--(?: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,
2664
- // -f "value" style short flags
2665
- /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/,
2666
- /(?<!\w)-(?:p|password)(?:\s+|\s*=\s*)[^\s,]+/gi,
2667
+ // -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
2668
+ // (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
2669
+ // NOTE: synced with @wrongstack/core observability/redact-command.ts.
2670
+ /(?<![-\w])-t(?:[=\s]+)?[^\s,-]+/,
2671
+ // -p|-password|-a (redis auth) short flags: attached + separated + =value.
2672
+ // Same token-start anchor; over-redaction is an accepted tradeoff for a
2673
+ // redaction function. Synced with core copy.
2674
+ /(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
2667
2675
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
2668
2676
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
2669
2677
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
@@ -3043,7 +3051,12 @@ var init_process_registry = __esm({
3043
3051
 
3044
3052
  // src/_spawn-stream.ts
3045
3053
  import { spawn as spawn2 } from "node:child_process";
3046
- import { buildChildEnv } from "@wrongstack/core";
3054
+ import {
3055
+ buildChildEnv,
3056
+ emitProcessCompleted,
3057
+ emitProcessOutput,
3058
+ emitProcessStarted
3059
+ } from "@wrongstack/core";
3047
3060
  async function* spawnStream(opts) {
3048
3061
  const max = opts.maxBytes ?? 2e5;
3049
3062
  const flushAt = opts.flushBytes ?? 4 * 1024;
@@ -3068,6 +3081,19 @@ async function* spawnStream(opts) {
3068
3081
  });
3069
3082
  const registry = getProcessRegistry();
3070
3083
  const pid = child.pid;
3084
+ const processStartedAt = Date.now();
3085
+ let stdoutBytes = 0;
3086
+ let stderrBytes = 0;
3087
+ let telemetryCompleted = false;
3088
+ emitProcessStarted({
3089
+ ...pid !== void 0 ? { pid } : {},
3090
+ parentPid: process.pid,
3091
+ command: redactCommand(`${opts.cmd} ${opts.args.join(" ")}`),
3092
+ args: redactCommand(opts.args.join(" ")).split(" ").filter(Boolean),
3093
+ cwd: opts.cwd,
3094
+ background: false,
3095
+ startedAt: new Date(processStartedAt).toISOString()
3096
+ });
3071
3097
  if (typeof pid === "number") {
3072
3098
  registry.register({
3073
3099
  pid,
@@ -3096,6 +3122,8 @@ async function* spawnStream(opts) {
3096
3122
  };
3097
3123
  const onOut = (c) => {
3098
3124
  const s = c.toString();
3125
+ stdoutBytes += c.byteLength;
3126
+ emitProcessOutput({ pid, stream: "stdout", chunk: c });
3099
3127
  if (stdout.length < max) stdout += s;
3100
3128
  spool.write(s);
3101
3129
  queue.push({ kind: "out", data: s });
@@ -3108,6 +3136,8 @@ async function* spawnStream(opts) {
3108
3136
  };
3109
3137
  const onErr = (c) => {
3110
3138
  const s = c.toString();
3139
+ stderrBytes += c.byteLength;
3140
+ emitProcessOutput({ pid, stream: "stderr", chunk: c });
3111
3141
  if (stderr.length < max) stderr += s;
3112
3142
  spool.write(s);
3113
3143
  queue.push({ kind: "err", data: s });
@@ -3125,9 +3155,25 @@ async function* spawnStream(opts) {
3125
3155
  queue.push({ kind: "error", data: e.message });
3126
3156
  wake();
3127
3157
  });
3128
- child.on("close", (code) => {
3158
+ const completeTelemetry = (code, signal, timedOut = false) => {
3159
+ if (telemetryCompleted) return;
3160
+ telemetryCompleted = true;
3161
+ emitProcessCompleted({
3162
+ ...pid !== void 0 ? { pid } : {},
3163
+ exitCode: code,
3164
+ ...signal ? { signal } : {},
3165
+ durationMs: Date.now() - processStartedAt,
3166
+ stdoutBytes,
3167
+ stderrBytes,
3168
+ timedOut,
3169
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
3170
+ });
3171
+ };
3172
+ child.on("close", (code, signal) => {
3129
3173
  if (typeof pid === "number") registry.unregister(pid);
3130
- queue.push({ kind: "close", data: "", code: code ?? 0 });
3174
+ const exitCode2 = code ?? (signal ? 1 : 0);
3175
+ completeTelemetry(exitCode2, signal ?? void 0);
3176
+ queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
3131
3177
  wake();
3132
3178
  });
3133
3179
  const onAbort = () => {
@@ -3140,6 +3186,7 @@ async function* spawnStream(opts) {
3140
3186
  }
3141
3187
  }
3142
3188
  queue.push({ kind: "close", data: "", code: 124 });
3189
+ completeTelemetry(124, "SIGKILL", true);
3143
3190
  wake();
3144
3191
  };
3145
3192
  if (isWin) {