@herbertgao/pi-extensions 2026.8.7 → 2026.8.8

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 (99) hide show
  1. package/README.md +7 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +63 -15
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +78 -16
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +13 -0
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +17 -6
  8. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  9. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +4 -4
  10. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +154 -96
  11. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +3 -0
  12. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  13. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +7 -5
  14. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  15. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +3 -0
  16. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  17. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +137 -96
  18. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +44 -32
  19. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  20. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  21. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  22. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  23. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  24. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  25. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +56 -34
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +50 -4
  28. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  29. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  30. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  31. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  32. package/node_modules/pi-lens/CHANGELOG.md +90 -0
  33. package/node_modules/pi-lens/README.md +106 -0
  34. package/node_modules/pi-lens/dist/clients/bash-file-access.js +97 -6
  35. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +16 -6
  36. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +14 -0
  37. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  38. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +22 -1
  39. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  40. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  41. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  43. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  44. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  45. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +8 -10
  46. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +10 -10
  47. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  48. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  49. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -15
  50. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +19 -11
  51. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +35 -13
  52. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +8 -8
  53. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +110 -23
  54. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  55. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +117 -1
  56. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +16 -14
  57. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +8 -9
  58. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +22 -0
  59. package/node_modules/pi-lens/dist/clients/jscpd-client.js +5 -2
  60. package/node_modules/pi-lens/dist/clients/language-profile.js +7 -2
  61. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +27 -2
  62. package/node_modules/pi-lens/dist/clients/lsp/client.js +144 -5
  63. package/node_modules/pi-lens/dist/clients/lsp/index.js +151 -6
  64. package/node_modules/pi-lens/dist/clients/module-report.js +14 -1
  65. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  66. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +18 -9
  67. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  68. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +7 -2
  69. package/node_modules/pi-lens/dist/clients/read-guard.js +88 -19
  70. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  71. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  72. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  73. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +44 -0
  74. package/node_modules/pi-lens/dist/clients/runtime-session.js +41 -0
  75. package/node_modules/pi-lens/dist/clients/runtime-turn.js +47 -2
  76. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  77. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  78. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  79. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  80. package/node_modules/pi-lens/dist/index.js +1224 -393
  81. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +10 -3
  82. package/node_modules/pi-lens/package.json +18 -5
  83. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  84. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  85. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  86. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  87. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  88. package/node_modules/pi-web-access/README.md +6 -5
  89. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  90. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  91. package/node_modules/pi-web-access/extract.ts +12 -2
  92. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  93. package/node_modules/pi-web-access/github-extract.ts +65 -36
  94. package/node_modules/pi-web-access/index.ts +19 -4
  95. package/node_modules/pi-web-access/openai-search.ts +17 -6
  96. package/node_modules/pi-web-access/package.json +1 -1
  97. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  98. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  99. package/package.json +9 -9
@@ -61,6 +61,47 @@ const OVERFLOW_UP = "↑";
61
61
  const OVERFLOW_DOWN = "↓";
62
62
  const OVERFLOW_BOTH = "↕";
63
63
 
64
+ /** No-overflow path: append the residual spacer rows after the footer. */
65
+ function renderFitsTerminal(natural: string[], spacerRows: number): string[] {
66
+ return spacerRows > 0 ? [...natural, ...Array<string>(spacerRows).fill("")] : natural;
67
+ }
68
+
69
+ /** Terminal too small for any middle content — show just chrome, hard-clamped to termRows. */
70
+ function renderChromeOnly(natural: string[], topFixed: number, bottomFixed: number, termRows: number): string[] {
71
+ const chromeOnly = [...natural.slice(0, topFixed), ...natural.slice(natural.length - bottomFixed)];
72
+ return chromeOnly.length > termRows ? chromeOnly.slice(0, termRows) : chromeOnly;
73
+ }
74
+
75
+ /** Scroll window start, centered on the focused option; top-anchored when there is no interactive focus. */
76
+ function computeScrollStart(
77
+ bodyRange: [number, number] | undefined,
78
+ headingCount: number,
79
+ availableMiddle: number,
80
+ middleRows: number,
81
+ ): number {
82
+ if (!bodyRange) return 0;
83
+ const focusedRowInMiddle = headingCount + bodyRange[0];
84
+ const focusedHeight = bodyRange[1] - bodyRange[0];
85
+ // Center the focused item vertically in the available middle space.
86
+ const idealStart = focusedRowInMiddle - Math.floor(Math.max(0, availableMiddle - focusedHeight) / 2);
87
+ return Math.max(0, Math.min(idealStart, middleRows - availableMiddle));
88
+ }
89
+
90
+ /** Mark the scroll window edges with overflow arrows; combined ↕ on a single-row middle. */
91
+ function decorateOverflow(scrollableMiddle: string[], hasUp: boolean, hasDown: boolean, theme: Theme): void {
92
+ if (hasUp && hasDown && scrollableMiddle.length === 1) {
93
+ // Single-row middle: combined ↕ avoids the prior collision where ↓ overwrote ↑.
94
+ scrollableMiddle[0] = theme.fg("dim", OVERFLOW_BOTH);
95
+ return;
96
+ }
97
+ if (hasUp && scrollableMiddle.length > 0) {
98
+ scrollableMiddle[0] = theme.fg("dim", OVERFLOW_UP);
99
+ }
100
+ if (hasDown && scrollableMiddle.length > 0) {
101
+ scrollableMiddle[scrollableMiddle.length - 1] = theme.fg("dim", OVERFLOW_DOWN);
102
+ }
103
+ }
104
+
64
105
  export type DialogState = QuestionnaireState;
65
106
 
66
107
  /** Per-tick projection of dialog state. Written by the adapter; read by the strategy thunk. */
@@ -127,6 +168,7 @@ export class DialogView implements StatefulView<DialogProps> {
127
168
  theme: config.theme,
128
169
  questions: config.questions,
129
170
  submitPicker: config.submitPicker,
171
+ notesInput: config.notesInput,
130
172
  })
131
173
  : undefined;
132
174
  this.maxFooterRowCount = Math.max(this.questionStrategy.footerRowCount, this.submitStrategy?.footerRowCount ?? 0);
@@ -173,48 +215,28 @@ export class DialogView implements StatefulView<DialogProps> {
173
215
  const termRows = this.config.getTerminalRows();
174
216
 
175
217
  if (natural.length + spacerRows <= termRows) {
176
- // No overflow — add residual spacer rows after footer.
177
- return spacerRows > 0 ? [...natural, ...Array<string>(spacerRows).fill("")] : natural;
218
+ return renderFitsTerminal(natural, spacerRows);
178
219
  }
179
220
 
180
221
  // OVERFLOW — apply 3-region partition with scroll-to-focus.
181
222
  const availableMiddle = Math.max(0, termRows - topFixed - bottomFixed);
182
223
  if (availableMiddle === 0) {
183
- // Terminal too small for any middle content — show just chrome.
184
- const chromeOnly = [...natural.slice(0, topFixed), ...natural.slice(natural.length - bottomFixed)];
185
- return chromeOnly.length > termRows ? chromeOnly.slice(0, termRows) : chromeOnly;
186
- }
187
-
188
- const bodyRange = strategy.focusedItemRowRange(width, state);
189
-
190
- // Compute scroll window centered on the focused option.
191
- let scrollStart: number;
192
- if (bodyRange) {
193
- const focusedRowInMiddle = headingCount + bodyRange[0];
194
- const focusedHeight = bodyRange[1] - bodyRange[0];
195
- // Center the focused item vertically in the available middle space.
196
- const idealStart = focusedRowInMiddle - Math.floor(Math.max(0, availableMiddle - focusedHeight) / 2);
197
- scrollStart = Math.max(0, Math.min(idealStart, middleRows - availableMiddle));
198
- } else {
199
- // No interactive focus (submit tab) — top-anchor the middle.
200
- scrollStart = 0;
224
+ return renderChromeOnly(natural, topFixed, bottomFixed, termRows);
201
225
  }
202
226
 
227
+ const scrollStart = computeScrollStart(
228
+ strategy.focusedItemRowRange(width, state),
229
+ headingCount,
230
+ availableMiddle,
231
+ middleRows,
232
+ );
203
233
  const scrollableMiddle = natural.slice(topFixed + scrollStart, topFixed + scrollStart + availableMiddle);
204
-
205
- const hasUp = scrollStart > 0;
206
- const hasDown = scrollStart + availableMiddle < middleRows;
207
- if (hasUp && hasDown && scrollableMiddle.length === 1) {
208
- // Single-row middle: combined ↕ avoids the prior collision where ↓ overwrote ↑.
209
- scrollableMiddle[0] = this.config.theme.fg("dim", OVERFLOW_BOTH);
210
- } else {
211
- if (hasUp && scrollableMiddle.length > 0) {
212
- scrollableMiddle[0] = this.config.theme.fg("dim", OVERFLOW_UP);
213
- }
214
- if (hasDown && scrollableMiddle.length > 0) {
215
- scrollableMiddle[scrollableMiddle.length - 1] = this.config.theme.fg("dim", OVERFLOW_DOWN);
216
- }
217
- }
234
+ decorateOverflow(
235
+ scrollableMiddle,
236
+ scrollStart > 0,
237
+ scrollStart + availableMiddle < middleRows,
238
+ this.config.theme,
239
+ );
218
240
 
219
241
  const result = [
220
242
  ...natural.slice(0, topFixed),
@@ -25,6 +25,9 @@ import type { StatefulView } from "./stateful-view.js";
25
25
  import type { TabComponents } from "./tab-components.js";
26
26
 
27
27
  const NOTES_HEADER = "Notes:";
28
+ const GLOBAL_NOTES_HEADER = "Global note:";
29
+ const REVIEW_GLOBAL_HINT = "n to add a note";
30
+ const REVIEW_NOTE_LABEL = "Note";
28
31
 
29
32
  /**
30
33
  * Single-row, width-clipped chrome cell. The footer row count is invariant
@@ -160,10 +163,12 @@ export interface SubmitTabStrategyConfig {
160
163
  theme: Theme;
161
164
  questions: readonly QuestionData[];
162
165
  submitPicker: Component | undefined;
166
+ /** Shared notes Editor — mounted into midRows while the global-note editor is open on this tab. */
167
+ notesInput: Editor;
163
168
  }
164
169
 
165
170
  export class SubmitTabStrategy implements TabContentStrategy {
166
- /** Spacer(1) + Text(prompt, 1) + Spacer(1) + submitPicker(2) = 5 rendered rows. Fallback path lands at 5 via 2 trailing Spacer(1)s. */
171
+ /** Spacer(1) + Text(prompt, 1) + submitPicker(2) + OneLineClippedText(hint, 1) = 5 rendered rows. Fallback path lands at 5 via 2 Spacer(1)s in the picker slot. */
167
172
  readonly footerRowCount = 5;
168
173
 
169
174
  constructor(private readonly config: SubmitTabStrategyConfig) {}
@@ -191,6 +196,18 @@ export class SubmitTabStrategy implements TabContentStrategy {
191
196
  c.addChild(new Text(this.config.theme.fg("dim", ` notes: ${a.notes}`), 1, 0));
192
197
  }
193
198
  }
199
+ // Committed global note (#182) as a review entry — pressing `n` gets visible
200
+ // feedback and the note is reviewable before submit. Same presence predicate as
201
+ // the reducer's doneFor lift. Hidden while the editor is open: the midRows editor
202
+ // (seeded with this text) is the live surface then, and a stale copy above it
203
+ // would read as a second note.
204
+ const globalNote = state.notesByTab.get(this.config.questions.length);
205
+ if (!state.notesVisible && globalNote && globalNote.length > 0) {
206
+ c.addChild(new Text(this.config.theme.fg("muted", ` ● ${t("review.note_label", REVIEW_NOTE_LABEL)}`), 1, 0));
207
+ c.addChild(
208
+ new Text(` ${this.config.theme.fg("muted", "→")} ${this.config.theme.fg("text", globalNote)}`, 1, 0),
209
+ );
210
+ }
194
211
  return c;
195
212
  }
196
213
 
@@ -198,8 +215,17 @@ export class SubmitTabStrategy implements TabContentStrategy {
198
215
  return this.bodyComponent(state).render(width).length;
199
216
  }
200
217
 
201
- midRows(_state: DialogState): Component[] {
202
- return [];
218
+ midRows(state: DialogState): Component[] {
219
+ // notesVisible-gated mirror of QuestionTabStrategy.midRows: while the global-note
220
+ // editor is open, the shared notesInput mounts below the answer summary under its
221
+ // own header. The pseudo-index draft (notesByTab[questions.length]) is reducer-owned
222
+ // state; the strategy only renders.
223
+ if (!state.notesVisible) return [];
224
+ return [
225
+ new Text(this.config.theme.fg("muted", t("notes.global_header", GLOBAL_NOTES_HEADER)), 1, 0),
226
+ this.config.notesInput,
227
+ new Spacer(1),
228
+ ];
203
229
  }
204
230
 
205
231
  footerRows(state: DialogState): Component[] {
@@ -217,7 +243,7 @@ export class SubmitTabStrategy implements TabContentStrategy {
217
243
  "warning",
218
244
  `${t("review.incomplete", INCOMPLETE_WARNING_PREFIX)} ${missing.join(", ")}`,
219
245
  );
220
- const out: Component[] = [new Spacer(1), new Text(promptText, 1, 0), new Spacer(1)];
246
+ const out: Component[] = [new Spacer(1), new Text(promptText, 1, 0)];
221
247
  if (this.config.submitPicker) {
222
248
  out.push(this.config.submitPicker);
223
249
  } else {
@@ -225,6 +251,12 @@ export class SubmitTabStrategy implements TabContentStrategy {
225
251
  out.push(new Spacer(1));
226
252
  out.push(new Spacer(1));
227
253
  }
254
+ // Bottom key-hint row, mirroring QuestionTabStrategy's footer idiom (one dim
255
+ // `·`-joined line below everything) — the prompt reads straight into its picker
256
+ // with no hint wedged between them. Always present so footerRowCount stays
257
+ // exactly 5. OneLineClippedText (not pi-tui Text) — a wrapped hint would inflate
258
+ // the footer past footerRowCount and desync the chrome's cross-tab height math.
259
+ out.push(new OneLineClippedText(this.config.theme.fg("dim", buildSubmitHintText(state)), 1));
228
260
  return out;
229
261
  }
230
262
 
@@ -268,3 +300,17 @@ export function buildHintText(
268
300
  if (state.inputMode) parts.push(t("hint.clear", HINT_PART_CLEAR));
269
301
  return parts.join(" · ");
270
302
  }
303
+
304
+ /**
305
+ * Submit-tab counterpart of `buildHintText` — the same `·`-joined bottom-row idiom.
306
+ * Resting: Enter · ↑/↓ · `n to add a note` · Esc. While the global-note editor is
307
+ * open the note part drops (the editor is the affordance then) and the Shift+Enter
308
+ * newline hint is appended after cancel, mirroring the question tabs' notes-open shape.
309
+ */
310
+ export function buildSubmitHintText(state: DialogState): string {
311
+ const parts: string[] = [t("hint.enter", HINT_PART_ENTER), t("hint.navigate", HINT_PART_NAV)];
312
+ if (!state.notesVisible) parts.push(t("review.global_hint", REVIEW_GLOBAL_HINT));
313
+ parts.push(t("hint.cancel", HINT_PART_CANCEL));
314
+ if (state.notesVisible) parts.push(t("hint.newline", HINT_PART_NEW_LINE));
315
+ return parts.join(" · ");
316
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-btw",
3
- "version": "0.55.0",
3
+ "version": "0.55.1",
4
4
  "description": "Pi extension that adds a /btw side-question command.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "directory": "packages/pi-btw"
48
48
  },
49
49
  "dependencies": {
50
- "@narumitw/pi-tui-kit": "^0.56.0"
50
+ "@narumitw/pi-tui-kit": "^0.57.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@earendil-works/pi-ai": "*",