pi-usereq 0.43.0 → 0.45.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.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  title: "PI-useReq Requirements"
3
3
  description: Software requirements specification
4
- version: "0.0.75"
5
- date: "2026-07-10"
4
+ version: "0.0.77"
5
+ date: "2026-07-13"
6
6
  author: "OpenAI Codex"
7
7
  scope:
8
8
  paths:
@@ -93,6 +93,8 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
93
93
  - **DES-016**: MUST deliver rendered bundled-prompt content to the LLM through `sendMessage` as a `display:false` custom message with `triggerTurn:true` and MUST NOT use `sendUserMessage` when `sendMessage` is available.
94
94
  - **DES-017**: MUST declare `pyright`, `ruff`, and `eslint` as pinned caret-range npm dependencies and register a `postinstall` script that installs bundled static checkers.
95
95
  - **DES-018**: MUST resolve checker executables by probing bundled `node_modules/.bin` paths relative to the installation path before falling back to `PATH` scan.
96
+ - **DES-019**: MUST resolve the `%%INSTALLATION_PATH%%` keyword inside static-check `Command` `cmd` fields to the runtime installation path within `resolveCheckerExecutable` before executable probing.
97
+ - **DES-020**: MUST extend `scripts/install-static-checkers.ts` to best-effort approve pending npm install scripts for bundled checker dependencies before probing bundled executables.
96
98
 
97
99
  ### 3.2 Functions
98
100
  - **REQ-001**: MUST access bundled prompts, git execution instructions, templates, and guidelines from `<installation-path>/resources` without requiring user-home resource copies before prompt or tool execution.
@@ -166,10 +168,10 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
166
168
  - **REQ-336**: MUST render the command invocation summary with the command name without the `req-` prefix in uppercase and the user request arguments.
167
169
  - **REQ-337**: MUST include `docs-dir`, `src-dir`, `tests-dir`, enabled context files, `AUTO_GIT_COMMIT`, effective `GIT_WORKTREE_ENABLED`, `GIT_WORKTREE_PREFIX`, enabled static-check languages, and `enabled-tools` in the command invocation summary.
168
170
  - **REQ-338**: MUST render `none` for the command invocation summary `context files`, `static code checks`, and `enabled tools` fields whenever their respective enabled-item list is empty.
169
- - **REQ-008**: MUST provide a `Language static code checkers` submenu that adds global Command entries by guided language flow, removes configured global checker entries, toggles local per-language enablement, and resets static-check configuration.
171
+ - **REQ-008**: MUST provide a `Language static code checkers` submenu that adds, views, confirms-before-removes, and resets global Command checker entries, plus toggles local per-language enablement.
170
172
  - **REQ-160**: MUST hardcode `Command` as the only user-configurable static-check module and omit module-selection UI from static-check configuration menus.
171
173
  - **REQ-161**: MUST hide `Dummy` from user-configurable static-check menus while preserving existing-config parsing and debug-driver support for `Dummy` entries.
172
- - **REQ-248**: MUST render 20 per-language static-check toggle rows between `Remove static code checker` and `Reset defaults`, with right-aligned `on|off` values derived from persisted local enablement.
174
+ - **REQ-248**: MUST render 20 per-language static-check toggle rows between `Reset static code checker` and `Reset defaults`, with right-aligned `on|off` values derived from persisted local enablement.
173
175
  - **REQ-249**: MUST persist local `static-check.<language>.enabled` with allowed values `enable|disable` and global `static-check.<language>.checkers` as ordered arrays.
174
176
  - **REQ-250**: MUST default global `static-check.C` and `static-check.C++` checker arrays to documented `cppcheck` and `clang-format` Command entries.
175
177
  - **REQ-251**: MUST default global `static-check.Python`, `JavaScript`, and `TypeScript` checker arrays to their documented Command entries.
@@ -258,7 +260,7 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
258
260
  - **REQ-290**: MUST preserve the successful worktree execution transcript in the restored client-visible session after successful closure.
259
261
  - **REQ-291**: MUST detect staged or unstaged `base-path` changes before successful closure merge and execute `git stash`, merge, and `git stash pop` in that order when changes exist.
260
262
  - **REQ-292**: MUST complete successful stash-assisted closure without surfacing an error and MUST emit a warning that the restored `base-path` is not clean after merge.
261
- - **REQ-209**: MUST restore the original session-backed `base-path`, notify the pi CLI of closure failure, and retain the worktree plus branch when orchestrated session closure is interrupted, failed, aborted, or incomplete.
263
+ - **REQ-209**: MUST retain the worktree execution session, worktree, and branch, notify the pi CLI of closure failure, park workflow state in `error`, and leave the original base-path session unresumed when orchestrated session closure is interrupted, failed, aborted, or incomplete.
262
264
  - **REQ-221**: MUST maintain one prompt-orchestration state machine with states `idle`, `checking`, `running`, `merging`, and `error`.
263
265
  - **REQ-222**: MUST render `status` as the first single-line status field and refresh it on every internal or pi CLI state transition.
264
266
  - **REQ-223**: MUST render `status:error` with theme `error` plus terminal blink when supported, and other `status` values with the existing non-error status-bar convention.
@@ -283,7 +285,7 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
283
285
  - **REQ-313**: MUST make failing `req-reset` surface restoration or cleanup failures, transition workflow state to `error`, and preserve already-completed cleanup effects.
284
286
  - **REQ-228**: MUST transition workflow state to `merging` immediately before merge and worktree/branch deletion begin during successful orchestrated session closure.
285
287
  - **REQ-229**: MUST transition workflow state to `error` and notify the pi CLI when base-path restoration, merge, or worktree/branch deletion verification fails during orchestrated session closure.
286
- - **REQ-230**: MUST transition workflow state to `idle` before the orchestrated session-closure handler returns.
288
+ - **REQ-230**: MUST transition workflow state to `idle` before the orchestrated session-closure handler returns for completed runs.
287
289
  - **REQ-219**: MUST verify created worktree registration via `git worktree list`, branch presence via `git branch` list, and filesystem path existence before changing the prompt execution path or dispatching a prompt message.
288
290
  - **REQ-256**: MUST store `base-path`, `context-path`, `git-path`, `parent-path`, `base-dir`, `worktree-dir`, `worktree-path`, branch name, original session file, and execution session file inside prompt-orchestration runtime state.
289
291
  - **REQ-272**: MUST verify post-switch execution targets before agent start using replacement-session context when available, the persisted execution-session header cwd, and `process.cwd()`, and MUST NOT rely on stale pre-switch session-bound objects.
@@ -411,6 +413,14 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
411
413
  - **REQ-342**: MUST keep `STATIC_CHECK_MODULES`, `dispatchStaticCheckForFile` signature, and `StaticCheckEntry` shape unchanged when adding bundled executable resolution.
412
414
  - **REQ-343**: MUST NOT transition workflow state or abort `session_start` when one or more enabled static checkers are missing.
413
415
  - **REQ-344**: MUST place the `session_start` missing-checker helper in `src/core/static-check.ts` so `src/index.ts` remains thin.
416
+ - **REQ-345**: MUST expose `View static code checker` immediately before `Remove static code checker` inside the `Language static code checkers` submenu.
417
+ - **REQ-346**: MUST make `View static code checker` render configured checker entries as read-only disabled inspection rows terminated by a `Close` row without mutating configuration.
418
+ - **REQ-347**: MUST expose `Reset static code checker` immediately after `Remove static code checker` inside the `Language static code checkers` submenu.
419
+ - **REQ-348**: MUST make `Reset static code checker` restore global checker arrays plus derived local enable flags to embedded defaults after explicit user confirmation.
420
+ - **REQ-349**: MUST render the targeted configured checker entries and require explicit user confirmation before `Remove static code checker` removes any global checker entries.
421
+ - **REQ-350**: MUST substitute the `%%INSTALLATION_PATH%%` keyword with the runtime extension installation path inside static-check `Command` `cmd` fields before executable resolution and execution.
422
+ - **REQ-351**: MUST resolve bundled static-check executables from the extension installation `node_modules/.bin` so default configuration requires no target-project module installation.
423
+ - **REQ-352**: MUST best-effort approve pending npm install scripts for bundled checker dependencies during extension install or run so embedded checkers execute without manual approval.
414
424
 
415
425
  ## 4. Test Requirements
416
426
  - **TST-001**: MUST verify extension activation registers every documented prompt command, agent tool, and configuration command while omitting tool-name slash commands, `test-static-check`, and the removed standalone config-viewer command.
@@ -421,7 +431,7 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
421
431
  - **TST-005**: MUST verify the configuration menu saves `docs-dir` locally and `AUTO_GIT_COMMIT`, `GIT_WORKTREE_ENABLED`, plus `GIT_WORKTREE_PREFIX` globally immediately after each change.
422
432
  - **TST-084**: MUST verify local configuration persistence trims trailing `/` and keeps `docs-dir`, `tests-dir`, and `src-dir` relative.
423
433
  - **TST-046**: MUST verify `Language static code checkers` omits module selection, raw-spec actions, supported-language reference actions, and hides `Dummy`, `Pylance`, and `Ruff` from user-configurable actions.
424
- - **TST-077**: MUST verify `Language static code checkers` renders 20 per-language `on|off` toggle rows between `Remove static code checker` and `Reset defaults`, and persists toggle changes only in local configuration.
434
+ - **TST-077**: MUST verify `Language static code checkers` renders 20 per-language `on|off` toggle rows between `Reset static code checker` and `Reset defaults`, and persists toggle changes only in local configuration.
425
435
  - **TST-078**: MUST verify default configuration stores documented per-language `enabled` flags locally and checker lists globally for `C`, `C++`, `Python`, `JavaScript`, and `TypeScript`.
426
436
  - **TST-108**: MUST verify guided static-check entry addition updates global checker lists and sets the targeted local enable flag to `enable`, and guided removal updates global checker lists.
427
437
  - **TST-112**: MUST verify `--enable-static-check` stores checker commands in global configuration and preserves local enablement according to the documented defaulting rules.
@@ -510,7 +520,7 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
510
520
  - **TST-055**: MUST verify bundled prompt-backed `req-<prompt>` commands enforce the documented required-doc matrix, create no worktree, and dispatch no prompt when a required doc is missing.
511
521
  - **TST-056**: MUST verify worktree-backed `req-<prompt>` commands derive `worktree-dir` from persisted `GIT_WORKTREE_PREFIX` for both default and override values.
512
522
  - **TST-057**: MUST verify `req-<prompt>` commands create `worktree-path`, dispatch prompts through the replacement-session context of the switched execution session, and run tool executions against prepared `context-path`.
513
- - **TST-058**: MUST verify worktree-backed `req-<prompt>` commands skip merge plus worktree/branch deletion, restore `base-path`, and notify pi CLI of closure failure when the matched run ends interrupted, failed, or aborted.
523
+ - **TST-058**: MUST verify worktree-backed `req-<prompt>` commands skip merge plus worktree/branch deletion, retain the worktree execution session, notify closure failure, and park workflow state `error` when the matched run ends interrupted, failed, or aborted.
514
524
  - **TST-061**: MUST verify `req-<prompt>` commands skip worktree creation when `AUTO_GIT_COMMIT=disable`, even if persisted `GIT_WORKTREE_ENABLED=enable`.
515
525
  - **TST-064**: MUST verify worktree-backed `req-<prompt>` commands confirm created worktree directory plus branch existence before prompt dispatch and abort without dispatch when verification fails.
516
526
  - **TST-067**: MUST verify `status:error` renders with blinking error styling and non-error `status` values follow the documented status-bar theme convention.
@@ -536,6 +546,11 @@ PI-useReq is a TypeScript pi extension plus companion Node CLI and standalone ex
536
546
  - **TST-123**: MUST verify `resolveCheckerExecutable` probes bundled `node_modules/.bin` paths before `PATH` scan.
537
547
  - **TST-124**: MUST verify `scripts/install-static-checkers.ts` always returns exit code `0` regardless of probe or install outcomes.
538
548
  - **TST-125**: MUST verify `session_start` emits one warning notification for missing enabled checkers without aborting or transitioning workflow state.
549
+ - **TST-126**: MUST verify `View static code checker` appears before `Remove static code checker` and renders configured checkers as read-only inspection rows.
550
+ - **TST-127**: MUST verify `Reset static code checker` appears after `Remove static code checker` and restores embedded defaults after explicit confirmation.
551
+ - **TST-128**: MUST verify `Remove static code checker` previews targeted checker entries and requires explicit confirmation before removing configured global checker entries.
552
+ - **TST-129**: MUST verify `resolveCheckerExecutable` substitutes `%%INSTALLATION_PATH%%` with the runtime installation path inside static-check `Command` `cmd` fields.
553
+ - **TST-130**: MUST verify `scripts/install-static-checkers.ts` best-effort approves pending npm install scripts and always returns exit code `0`.
539
554
 
540
555
  ## 5. Observed Component Model
541
556
 
@@ -51,7 +51,7 @@
51
51
  - Threads: no explicit threads detected
52
52
  - ID: `PROC:install-static-checkers`
53
53
  - Type: Process
54
- - Role: Best-effort npm `postinstall` installer that probes bundled static-checker executables, attempts `npm install` on miss, and prints platform guidance for native checkers.
54
+ - Role: Best-effort npm `postinstall` installer that approves pending install scripts, probes bundled static-checker executables, attempts `npm install` on miss, and prints platform guidance for native checkers.
55
55
  - Entrypoints:
56
56
  - `main(...)` [`scripts/install-static-checkers.ts`]
57
57
  - Parent Process: none
@@ -990,7 +990,7 @@
990
990
  - `setPiUsereqRuntimeSoundLevel(...)`: store the cycled runtime sound level and refresh the footer [`src/core/extension-status.ts`]
991
991
  - `renderPiUsereqStatus(...)`: refresh the single-line footer after shortcut-driven runtime sound changes [`src/core/extension-status.ts`]
992
992
  - `registerExtensionStatusHooks(...)`: register shared wrappers for lifecycle telemetry hooks and suppress the documented stale-extension-context callbacks that can arrive after successful session replacement [`src/index.ts`]
993
- - `handleExtensionStatusEvent(...)`: process intercepted hook payloads through shared status helpers, tool-result debug logging, dedicated workflow-closure plus shutdown diagnostics built from pre-shutdown prompt snapshots, workflow-state transitions, recover a live bootstrap cwd during `session_start` when stale context mirrors still point at deleted worktrees, matched-success prompt-command restoration to `base-path` plus stash-assisted merge and worktree deletion even after a busy-command rejection moved workflow state to `error`, warning-only closure notifications when restored `base-path` changes are reapplied, stale-safe closure notifications, and matched-failure base-path restoration plus closure-error notification using persisted replacement-session contexts when event hooks omit `switchSession()` [`src/index.ts`]
993
+ - `handleExtensionStatusEvent(...)`: process intercepted hook payloads through shared status helpers, tool-result debug logging, dedicated workflow-closure plus shutdown diagnostics built from pre-shutdown prompt snapshots, workflow-state transitions, recover a live bootstrap cwd during `session_start` when stale context mirrors still point at deleted worktrees, matched-success prompt-command restoration to `base-path` plus stash-assisted merge and worktree deletion even after a busy-command rejection moved workflow state to `error`, warning-only closure notifications when restored `base-path` changes are reapplied, stale-safe closure notifications, and matched-failure worktree-execution-session retention plus closure-error notification and workflow `error` parking without resuming `base-path` so the failure stays visible, leaving `base-path` restoration plus worktree and branch cleanup to `req-reset` [`src/index.ts`]
994
994
  - `resolveLiveBootstrapCwd(...)`: recover a live bootstrap cwd from the startup context, runtime path state, or process cwd when stale worktree paths no longer exist [`src/index.ts`]
995
995
  - `syncContextCwdMirror(...)`: best-effort realign the startup context `cwd` mirror to the recovered live cwd before startup config loads [`src/index.ts`]
996
996
  - `loadProjectConfig(...)`: session-start configuration load from the recovered live cwd [`src/index.ts`]
@@ -1098,7 +1098,7 @@
1098
1098
  - `finalizePromptCommandMerge(...)`: inspect tracked `base-path` changes, run `git stash` plus `git stash pop` around the existing `--ff-only` merge when tracked changes exist, emit a warning-only merge result after reapplying local changes, and append selected merge debug entries [`src/core/prompt-command-runtime.ts`]
1099
1099
  - `listPromptTrackedBasePathChanges(...)`: parse tracked staged or unstaged `git status --porcelain` rows and ignore untracked or ignored rows before stash-assisted merge selection [`src/core/prompt-command-runtime.ts`]
1100
1100
  - `deletePromptWorktree(...)`: delete the merged worktree and branch after successful stash-assisted fast-forward merge [`src/core/prompt-command-runtime.ts`]
1101
- - `restorePromptCommandExecution(...)`: switch back to the original session-backed base path when interrupted, failed, or unmatched prompt-end events skip merge and retain the worktree for manual recovery, reusing the persisted replacement-session context when lifecycle hooks omit `switchSession()` [`src/core/prompt-command-runtime.ts`]
1101
+ - `restorePromptCommandExecution(...)`: reset runtime path state for non-worktree prompt-end events; worktree-backed interrupted, failed, aborted, or incomplete runs skip restoration, retain the worktree execution session, park workflow state in `error`, and leave `base-path` restoration plus worktree and branch cleanup to `req-reset` [`src/core/prompt-command-runtime.ts`]
1102
1102
  - `shouldRunPiNotifyCommand(...)`: validate prompt-scoped command-notify prerequisites [`src/core/pi-notify.ts`]
1103
1103
  - `isPiNotifyOutcomeEnabled(...)`: map `completed|interrupted|failed` to the selected toggle set [`src/core/pi-notify.ts`]
1104
1104
  - `runPiNotifyCommand(...)`: execute `PI_NOTIFY_CMD` with runtime placeholder substitution [`src/core/pi-notify.ts`]
@@ -1191,12 +1191,12 @@
1191
1191
  - `formatElapsedStatusValue(...)`: compose `⏱︎`, `⚑`, and `⌛︎` timer segments [`src/core/extension-status.ts`]
1192
1192
  - `formatCompletedStatusDuration(...)`: render completed timers or the unset placeholder [`src/core/extension-status.ts`]
1193
1193
  - `formatStatusDuration(...)`: render `M:SS` durations [`src/core/extension-status.ts`]
1194
- - `configureStaticCheckMenu(...)`: interactive editor that adds and removes global Command entries while toggling local per-language enable flags [`src/index.ts`]
1194
+ - `configureStaticCheckMenu(...)`: interactive editor that adds, inspects, confirms-before-removes, and resets global Command entries while toggling local per-language enable flags [`src/index.ts`]
1195
1195
  - `getStaticCheckLanguageConfigForMenu(...)`: resolve a menu-facing per-language static-check object [`src/index.ts`]
1196
1196
  - `countConfiguredStaticCheckLanguages(...)`: count languages that retain global checker entries [`src/index.ts`]
1197
1197
  - `countEnabledStaticCheckLanguages(...)`: count languages whose local enable flag is on [`src/index.ts`]
1198
1198
  - `formatStaticCheckLanguagesSummary(...)`: summarize enabled languages and configured global checker languages [`src/index.ts`]
1199
- - `buildStaticCheckMenuChoices(...)`: serialize guided add/remove actions plus direct per-language local toggle rows [`src/index.ts`]
1199
+ - `buildStaticCheckMenuChoices(...)`: serialize guided add/view/remove/reset actions plus direct per-language local toggle rows [`src/index.ts`]
1200
1200
  - `getSupportedStaticCheckLanguageSupport(...)`: enumerate supported languages and extensions [`src/core/static-check.ts`]
1201
1201
  - `getSupportedStaticCheckLanguages(...)`: enumerate canonical supported languages [`src/core/static-check.ts`]
1202
1202
  - `buildTerminalSettingsMenuChoices(...)`: append the canonical value-less `Reset defaults` row to the selector menu [`src/index.ts`]
@@ -1208,6 +1208,11 @@
1208
1208
  - `getSupportedStaticCheckLanguageSupport(...)`: enumerate supported languages and extensions [`src/core/static-check.ts`]
1209
1209
  - `getSupportedStaticCheckLanguages(...)`: enumerate canonical supported languages [`src/core/static-check.ts`]
1210
1210
  - `buildTerminalSettingsMenuChoices(...)`: append the canonical value-less `Reset defaults` row to the selector menu [`src/index.ts`]
1211
+ - `formatStaticCheckCheckerEntry(...)`: format one checker entry as a shell-like command summary for inspection and confirmation menus [`src/index.ts`]
1212
+ - `formatStaticCheckLanguageCheckerSummary(...)`: summarize configured checkers for one language as a delimited command list [`src/index.ts`]
1213
+ - `buildStaticCheckViewChoices(...)`: serialize configured checkers as read-only disabled inspection rows terminated by a `Close` row [`src/index.ts`]
1214
+ - `buildStaticCheckRemovalConfirmationChoices(...)`: serialize targeted checker preview rows plus approve and abort actions for removal confirmation [`src/index.ts`]
1215
+ - `confirmStaticCheckRemoval(...)`: open one explicit removal-confirmation submenu and return the approval result [`src/index.ts`]
1211
1216
  - `resetStaticCheckConfig(...)`: restore documented per-language static-check defaults [`src/index.ts`]
1212
1217
  - `getDefaultStaticCheckConfig(...)`: build documented per-language static-check defaults [`src/core/config.ts`]
1213
1218
  - `createStaticCheckLanguageConfig(...)`: clone checker entries and derive the enable flag [`src/core/config.ts`]
@@ -1302,8 +1307,10 @@
1302
1307
  - Looping model: single-pass probe plus optional install with no persistent loop.
1303
1308
  - Threads: no explicit threads detected.
1304
1309
  - Internal Call-Trace Tree:
1305
- - `main(...)`: probe each bundled npm checker, attempt best-effort install on miss, print native-checker guidance, and return `0` [`scripts/install-static-checkers.ts`]
1306
- - `resolveCheckerExecutable(...)`: resolve one checker across bundled `node_modules/.bin` locations and PATH scan [`src/core/static-check.ts`]
1310
+ - `main(...)`: best-effort approve pending npm install scripts, probe each bundled npm checker, attempt best-effort install on miss, print native-checker guidance, and return `0` [`scripts/install-static-checkers.ts`]
1311
+ - `approvePendingInstallScripts(...)`: best-effort approve pending npm install scripts for bundled checker dependencies before probing [`scripts/install-static-checkers.ts`]
1312
+ - `hasPendingInstallScriptPackages(...)`: report whether the parsed pending-scripts payload references install-script packages [`scripts/install-static-checkers.ts`]
1313
+ - `resolveCheckerExecutable(...)`: resolve one checker across `%%INSTALLATION_PATH%%` keyword, bundled `node_modules/.bin`, and PATH scan locations [`src/core/static-check.ts`]
1307
1314
  - `findExecutable(...)`: resolve executable on PATH or explicit path [`src/core/static-check.ts`]
1308
1315
  - `isExecutableFile(...)`: verify executable access bits [`src/core/static-check.ts`]
1309
1316
  - `attemptBundledInstall(...)`: best-effort `npm install <pkg>@<range> --no-save --prefix <install-root>` swallowing all errors [`scripts/install-static-checkers.ts`]
@@ -42,6 +42,52 @@ function bundledCheckerRange(pkg: string): string {
42
42
  }
43
43
  }
44
44
 
45
+ /**
46
+ * @brief Reports whether one parsed `npm approve-scripts --allow-scripts-pending` payload references pending install-script packages.
47
+ * @details Accepts an array of package specifiers or a map of package specifiers to pending-script descriptors, treating any non-empty array or non-empty object with at least one entry as pending. Runtime is O(n) in pending entry count. No external state is mutated.
48
+ * @param[in] pending {unknown} Parsed JSON payload emitted by `npm approve-scripts --allow-scripts-pending`.
49
+ * @return {boolean} `true` when at least one pending install-script package is referenced.
50
+ */
51
+ function hasPendingInstallScriptPackages(pending: unknown): boolean {
52
+ if (Array.isArray(pending)) {
53
+ return pending.length > 0;
54
+ }
55
+ if (pending && typeof pending === "object") {
56
+ return Object.keys(pending as Record<string, unknown>).length > 0;
57
+ }
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * @brief Best-effort approves pending npm install scripts for bundled checker dependencies.
63
+ * @details Probes `npm approve-scripts --allow-scripts-pending`, parses the JSON payload when available, and runs `npm approve-scripts --all` only when pending script-bearing packages are detected so bundled checkers and their transitive dependencies execute without manual script approval. Swallows all errors so the postinstall flow never fails. Runtime is dominated by npm execution. Side effects include optional writes to the installation-owned `package.json` allowScripts map and subprocess spawning.
64
+ * @return {void} No return value.
65
+ * @satisfies DES-020, REQ-352
66
+ */
67
+ function approvePendingInstallScripts(): void {
68
+ const pendingResult = spawnSync(
69
+ "npm",
70
+ ["approve-scripts", "--allow-scripts-pending", "--json"],
71
+ { encoding: "utf8" },
72
+ );
73
+ if (pendingResult.error || (pendingResult.status !== null && pendingResult.status !== 0)) {
74
+ return;
75
+ }
76
+ let pendingPayload: unknown;
77
+ try {
78
+ pendingPayload = JSON.parse(pendingResult.stdout ?? "");
79
+ } catch {
80
+ return;
81
+ }
82
+ if (!hasPendingInstallScriptPackages(pendingPayload)) {
83
+ return;
84
+ }
85
+ const approveResult = spawnSync("npm", ["approve-scripts", "--all"], { encoding: "utf8" });
86
+ if (approveResult.error) {
87
+ process.stderr.write(`Warning: failed to approve pending install scripts: ${approveResult.error.message}\n`);
88
+ }
89
+ }
90
+
45
91
  /**
46
92
  * @brief Prints platform-specific install guidance for native checkers.
47
93
  * @details Detects the current platform and emits one consolidated stderr line per native checker describing the recommended system package manager command. Runtime is O(1). Side effect: writes to stderr.
@@ -101,10 +147,11 @@ function attemptBundledInstall(pkg: string): void {
101
147
  * @details Probes each bundled npm checker through `resolveCheckerExecutable`, attempts a best-effort install on miss, prints native-checker guidance for unresolvable native checkers, and always returns `0` so `npm install` never fails because of missing optional checkers. Runtime is dominated by PATH probing and optional npm execution. Side effects include stdout/stderr writes and best-effort `npm install` subprocess spawning. The script never modifies git-tracked files.
102
148
  * @param[in] argv {string[]} Raw CLI arguments (unused, retained for CLI convention parity).
103
149
  * @return {number} Always returns `0`.
104
- * @satisfies REQ-339, DES-017
150
+ * @satisfies REQ-339, DES-017, DES-020, REQ-352
105
151
  */
106
152
  export function main(argv = process.argv.slice(2)): number {
107
153
  void argv;
154
+ approvePendingInstallScripts();
108
155
  for (const checker of BUNDLED_NPM_CHECKERS) {
109
156
  if (resolveCheckerExecutable(checker)) {
110
157
  process.stdout.write(`Bundled static checker '${checker}' is available.\n`);
@@ -399,11 +399,12 @@ export class StaticCheckCommand extends StaticCheckBase {
399
399
  * @throws {ReqError} Throws when the executable cannot be found on PATH.
400
400
  */
401
401
  constructor(cmd: string, inputs: string[], extraArgs?: string[], failOnly = false) {
402
- if (!resolveCheckerExecutable(cmd)) {
402
+ const resolvedCmd = resolveCheckerExecutable(cmd);
403
+ if (!resolvedCmd) {
403
404
  throw new ReqError(`Error: external command '${cmd}' not found on PATH.`, 1);
404
405
  }
405
406
  super(inputs, extraArgs, failOnly);
406
- this.cmd = cmd;
407
+ this.cmd = cmd.includes("%%INSTALLATION_PATH%%") ? resolvedCmd : cmd;
407
408
  this.label = `Command[${cmd}]`;
408
409
  }
409
410
 
@@ -475,20 +476,25 @@ export function findExecutable(cmd: string): string | undefined {
475
476
  }
476
477
 
477
478
  /**
478
- * @brief Resolves one checker executable across bundled `node_modules/.bin` locations and PATH.
479
- * @details Probes the installation-owned `node_modules/.bin` directory, the project-scope parent `node_modules/.bin` directory used by `--prefix` layouts, and finally the system PATH scan, returning the first executable match. Runtime is O(p) in PATH entry count plus bounded filesystem metadata checks. Side effects are limited to filesystem reads.
480
- * @param[in] cmd {string} Executable name or relative path to resolve.
479
+ * @brief Resolves one checker executable across installation-keyword, bundled `node_modules/.bin`, and PATH locations.
480
+ * @details Substitutes the `%%INSTALLATION_PATH%%` keyword with the runtime installation path and verifies the resulting explicit path when present, then probes the installation-owned `node_modules/.bin` directory, the project-scope parent `node_modules/.bin` directory used by `--prefix` layouts, and finally the system PATH scan, returning the first executable match so default configuration requires no target-project module installation. Runtime is O(p) in PATH entry count plus bounded filesystem metadata checks. Side effects are limited to filesystem reads.
481
+ * @param[in] cmd {string} Executable name, explicit path, or `%%INSTALLATION_PATH%%`-anchored path to resolve.
481
482
  * @return {string | undefined} Absolute executable path, or `undefined` when not found in any probed location.
482
- * @satisfies REQ-023, REQ-037, DES-018
483
+ * @satisfies REQ-023, REQ-037, DES-018, DES-019, REQ-350, REQ-351
483
484
  */
484
485
  export function resolveCheckerExecutable(cmd: string): string | undefined {
486
+ if (cmd.includes("%%INSTALLATION_PATH%%")) {
487
+ const installationPath = getInstallationPath();
488
+ const resolvedPath = cmd.split("%%INSTALLATION_PATH%%").join(installationPath);
489
+ return isExecutableFile(resolvedPath) ? path.resolve(resolvedPath) : undefined;
490
+ }
485
491
  if (cmd.includes(path.sep)) {
486
492
  return isExecutableFile(cmd) ? path.resolve(cmd) : undefined;
487
493
  }
488
494
  const installationPath = getInstallationPath();
489
495
  const bundledBinCandidates = [
490
496
  path.join(installationPath, "..", "node_modules", ".bin", cmd),
491
- path.join(installationPath, "..", "..", "node_modules", ".bin", cmd),
497
+ path.join(installationPath, "..", "..", ".bin", cmd),
492
498
  ];
493
499
  for (const candidate of bundledBinCandidates) {
494
500
  if (isExecutableFile(candidate)) {
package/src/index.ts CHANGED
@@ -1454,6 +1454,26 @@ async function handleExtensionStatusEvent(
1454
1454
  ) {
1455
1455
  notifyContextSafely(promptContext, finalization.warningMessage, "info");
1456
1456
  }
1457
+ } else if (closureFailureMessage !== undefined) {
1458
+ // Worktree-backed run that ended interrupted, failed, aborted, or
1459
+ // incomplete (REQ-209): keep the worktree execution session visible,
1460
+ // surface the closure-failure error, park the workflow in `error`, and
1461
+ // retain the active request so `req-reset` can restore `base-path` and
1462
+ // remove the retained worktree plus branch. The original base-path
1463
+ // session MUST NOT be resumed here so the failure stays visible.
1464
+ notifyContextSafely(promptContext, closureFailureMessage, "error");
1465
+ if (debugConfig) {
1466
+ transitionPromptWorkflowState(
1467
+ statusController,
1468
+ promptContext,
1469
+ activePromptRequest.basePath,
1470
+ debugConfig,
1471
+ activePromptRequest.promptName,
1472
+ "error",
1473
+ );
1474
+ } else {
1475
+ setPiUsereqWorkflowState(statusController, "error", promptContext);
1476
+ }
1457
1477
  } else {
1458
1478
  try {
1459
1479
  promptContext = (await restorePromptCommandExecution(
@@ -1463,9 +1483,6 @@ async function handleExtensionStatusEvent(
1463
1483
  ? { config: debugConfig, workflowState: statusController.state.workflowState }
1464
1484
  : undefined,
1465
1485
  ) ?? promptContext) as typeof ctx;
1466
- if (closureFailureMessage !== undefined) {
1467
- notifyContextSafely(promptContext, closureFailureMessage, "error");
1468
- }
1469
1486
  } catch (error) {
1470
1487
  promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
1471
1488
  if (debugConfig) {
@@ -1483,19 +1500,21 @@ async function handleExtensionStatusEvent(
1483
1500
  notifyContextSafely(promptContext, error instanceof Error ? error.message : String(error), "error");
1484
1501
  }
1485
1502
  }
1486
- statusController.state.pendingPromptRequest = undefined;
1487
- statusController.state.activePromptRequest = undefined;
1488
- if (debugConfig) {
1489
- transitionPromptWorkflowState(
1490
- statusController,
1491
- promptContext,
1492
- activePromptRequest.basePath,
1493
- debugConfig,
1494
- activePromptRequest.promptName,
1495
- "idle",
1496
- );
1497
- } else {
1498
- setPiUsereqWorkflowState(statusController, "idle", promptContext);
1503
+ if (closureFailureMessage === undefined) {
1504
+ statusController.state.pendingPromptRequest = undefined;
1505
+ statusController.state.activePromptRequest = undefined;
1506
+ if (debugConfig) {
1507
+ transitionPromptWorkflowState(
1508
+ statusController,
1509
+ promptContext,
1510
+ activePromptRequest.basePath,
1511
+ debugConfig,
1512
+ activePromptRequest.promptName,
1513
+ "idle",
1514
+ );
1515
+ } else {
1516
+ setPiUsereqWorkflowState(statusController, "idle", promptContext);
1517
+ }
1499
1518
  }
1500
1519
  }
1501
1520
  }
@@ -3772,7 +3791,7 @@ function formatStaticCheckLanguagesSummary(config: UseReqConfig): string {
3772
3791
  * @details Serializes guided Command-oriented add and remove actions, renders one direct on/off toggle row for every supported language, and appends canonical terminal rows while omitting raw-spec and reference-only actions. Runtime is O(l). No external state is mutated.
3773
3792
  * @param[in] config {UseReqConfig} Effective project configuration.
3774
3793
  * @return {PiUsereqSettingsMenuChoice[]} Ordered static-check menu choices.
3775
- * @satisfies REQ-008, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-160, REQ-161, REQ-193, REQ-248
3794
+ * @satisfies REQ-008, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-160, REQ-161, REQ-193, REQ-248, REQ-345, REQ-347
3776
3795
  */
3777
3796
  function buildStaticCheckMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
3778
3797
  const supportedLanguages = getSupportedStaticCheckLanguageSupport();
@@ -3784,11 +3803,23 @@ function buildStaticCheckMenuChoices(config: UseReqConfig): PiUsereqSettingsMenu
3784
3803
  value: `${supportedLanguages.length} languages`,
3785
3804
  description: "Select a supported language, then configure one Command static-check executable.",
3786
3805
  },
3806
+ {
3807
+ id: "view-static-check-entry",
3808
+ label: "View static code checker",
3809
+ value: configuredLanguageCount > 0 ? `${configuredLanguageCount} configured` : "(none)",
3810
+ description: "Inspect the configured Command static-check entries for every language without mutating configuration.",
3811
+ },
3787
3812
  {
3788
3813
  id: "remove-static-check-entry",
3789
3814
  label: "Remove static code checker",
3790
3815
  value: configuredLanguageCount > 0 ? `${configuredLanguageCount} configured` : "(none)",
3791
- description: "Remove every configured static-check entry for one language.",
3816
+ description: "Preview and confirm removal of every configured static-check entry for one language.",
3817
+ },
3818
+ {
3819
+ id: "reset-static-check-entry",
3820
+ label: "Reset static code checker",
3821
+ value: "embedded defaults",
3822
+ description: "Restore the documented per-language static-check defaults after explicit confirmation.",
3792
3823
  },
3793
3824
  ...supportedLanguages.map(({ language, extensions }) => {
3794
3825
  const languageConfig = getStaticCheckLanguageConfigForMenu(config, language);
@@ -3858,13 +3889,164 @@ function buildConfiguredStaticCheckLanguageChoices(config: UseReqConfig): PiUser
3858
3889
  ];
3859
3890
  }
3860
3891
 
3892
+ /**
3893
+ * @brief Formats one static-check checker entry as a compact command summary.
3894
+ * @details Joins the module command plus its parameter list into a single shell-like token sequence so inspection and confirmation menus can render checker identity deterministically. Runtime is O(p) in parameter count. No external state is mutated.
3895
+ * @param[in] entry {StaticCheckEntry} Static-check configuration entry.
3896
+ * @return {string} Compact command summary string.
3897
+ */
3898
+ function formatStaticCheckCheckerEntry(entry: StaticCheckEntry): string {
3899
+ const cmd = typeof entry.cmd === "string" ? entry.cmd.trim() : "";
3900
+ const params = Array.isArray(entry.params) ? entry.params.map(String) : [];
3901
+ return [cmd, ...params].filter((value) => value !== "").join(" ");
3902
+ }
3903
+
3904
+ /**
3905
+ * @brief Summarizes every configured checker for one language as a delimited command list.
3906
+ * @details Joins each checker entry summary with `; ` so the value column exposes the full language configuration in one row. Runtime is O(c * p). No external state is mutated.
3907
+ * @param[in] config {UseReqConfig} Effective project configuration.
3908
+ * @param[in] language {string} Canonical language name.
3909
+ * @return {string} Delimited checker summary string, or `(none)` when no checkers are configured.
3910
+ */
3911
+ function formatStaticCheckLanguageCheckerSummary(config: UseReqConfig, language: string): string {
3912
+ const checkers = getStaticCheckLanguageConfigForMenu(config, language).checkers;
3913
+ if (checkers.length === 0) {
3914
+ return "(none)";
3915
+ }
3916
+ return checkers.map(formatStaticCheckCheckerEntry).join("; ");
3917
+ }
3918
+
3919
+ /**
3920
+ * @brief Builds the shared settings-menu choices for the read-only static-check inspection submenu.
3921
+ * @details Exposes only configured languages as disabled rows whose value column renders the full checker command list, appends one selectable `Close` row, and emits a disabled placeholder when no language is configured so the submenu never mutates configuration. Runtime is O(l * c * p). No external state is mutated.
3922
+ * @param[in] config {UseReqConfig} Effective project configuration.
3923
+ * @return {PiUsereqSettingsMenuChoice[]} Ordered read-only inspection choices.
3924
+ * @satisfies REQ-346
3925
+ */
3926
+ function buildStaticCheckViewChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
3927
+ const configured = getSupportedStaticCheckLanguageSupport()
3928
+ .filter(({ language }) => getStaticCheckLanguageConfigForMenu(config, language).checkers.length > 0);
3929
+ if (configured.length === 0) {
3930
+ return [
3931
+ {
3932
+ id: "view-static-check-empty",
3933
+ label: "No configured static checkers",
3934
+ value: "(none)",
3935
+ description: "Configure at least one Command static-check entry before inspecting configuration.",
3936
+ disabled: true,
3937
+ labelTone: "dim",
3938
+ valueTone: "dim",
3939
+ },
3940
+ {
3941
+ id: "view-static-check-close",
3942
+ label: "Close",
3943
+ value: "",
3944
+ description: "Close the static-check inspection view and return to the previous menu.",
3945
+ },
3946
+ ];
3947
+ }
3948
+ return [
3949
+ ...configured.map(({ language, extensions }) => {
3950
+ const languageConfig = getStaticCheckLanguageConfigForMenu(config, language);
3951
+ const summary = formatStaticCheckLanguageCheckerSummary(config, language);
3952
+ const checkerCount = languageConfig.checkers.length;
3953
+ const suffix = checkerCount === 1 ? "checker" : "checkers";
3954
+ return {
3955
+ id: `view-static-check-language:${language}`,
3956
+ label: language,
3957
+ value: summary,
3958
+ description: `Read-only inspection of the configured Command static-check entries for ${language}. Supported extensions: ${extensions.join(", ")}. ${checkerCount} ${suffix}.`,
3959
+ disabled: true,
3960
+ labelTone: "dim" as const,
3961
+ valueTone: "dim" as const,
3962
+ };
3963
+ }),
3964
+ {
3965
+ id: "view-static-check-close",
3966
+ label: "Close",
3967
+ value: "",
3968
+ description: "Close the static-check inspection view and return to the previous menu.",
3969
+ },
3970
+ ];
3971
+ }
3972
+
3973
+ /**
3974
+ * @brief Builds the confirmation submenu choices for removing one configured static-check language.
3975
+ * @details Renders each configured checker as a disabled preview row, appends explicit approve and abort actions, and falls back to one disabled no-op row when the language has no checkers. Runtime is O(c * p). No external state is mutated.
3976
+ * @param[in] language {string} Canonical language name targeted for removal.
3977
+ * @param[in] checkers {StaticCheckEntry[]} Configured checker entries that removal would clear.
3978
+ * @return {PiUsereqSettingsMenuChoice[]} Ordered removal-confirmation choices.
3979
+ * @satisfies REQ-349
3980
+ */
3981
+ function buildStaticCheckRemovalConfirmationChoices(
3982
+ language: string,
3983
+ checkers: StaticCheckEntry[],
3984
+ ): PiUsereqSettingsMenuChoice[] {
3985
+ const previewRows = checkers.length > 0
3986
+ ? checkers.map((entry, index) => ({
3987
+ id: `removal-preview:${language}:${index}`,
3988
+ label: formatStaticCheckCheckerEntry(entry),
3989
+ value: `${language} checker`,
3990
+ description: `Remove the configured Command static-check entry ${formatStaticCheckCheckerEntry(entry)} from ${language}.`,
3991
+ disabled: true,
3992
+ labelTone: "dim" as const,
3993
+ valueTone: "dim" as const,
3994
+ }))
3995
+ : [{
3996
+ id: `removal-preview:${language}:none`,
3997
+ label: `No configured checkers for ${language}`,
3998
+ value: "nothing to remove",
3999
+ description: `No configured static-check entries exist for ${language}.`,
4000
+ disabled: true,
4001
+ labelTone: "dim" as const,
4002
+ valueTone: "dim" as const,
4003
+ }];
4004
+ return [
4005
+ ...previewRows,
4006
+ {
4007
+ id: "removal-approve",
4008
+ label: "Approve removal",
4009
+ value: `${checkers.length} ${checkers.length === 1 ? "checker" : "checkers"}`,
4010
+ description: `Remove every configured static-check entry for ${language}.`,
4011
+ },
4012
+ {
4013
+ id: "removal-abort",
4014
+ label: "Abort removal",
4015
+ value: "keep current",
4016
+ description: `Keep the configured static-check entries for ${language}.`,
4017
+ },
4018
+ ];
4019
+ }
4020
+
4021
+ /**
4022
+ * @brief Opens one explicit removal-confirmation submenu for a configured static-check language.
4023
+ * @details Renders the targeted checker entries before removal and returns `true` only when the user selects the explicit approval action. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
4024
+ * @param[in] ctx {ExtensionCommandContext} Active command context.
4025
+ * @param[in] language {string} Canonical language name targeted for removal.
4026
+ * @param[in] checkers {StaticCheckEntry[]} Configured checker entries that removal would clear.
4027
+ * @return {Promise<boolean>} `true` when the removal is explicitly approved.
4028
+ * @satisfies REQ-349
4029
+ */
4030
+ async function confirmStaticCheckRemoval(
4031
+ ctx: ExtensionCommandContext,
4032
+ language: string,
4033
+ checkers: StaticCheckEntry[],
4034
+ ): Promise<boolean> {
4035
+ const choice = await showPiUsereqSettingsMenu(
4036
+ ctx,
4037
+ `Remove static code checker: ${language}`,
4038
+ buildStaticCheckRemovalConfirmationChoices(language, checkers),
4039
+ );
4040
+ return choice === "removal-approve";
4041
+ }
4042
+
3861
4043
  /**
3862
4044
  * @brief Runs the interactive static-check configuration menu.
3863
- * @details Lets the user add and remove global Command entries, toggle direct local per-language enable flags, and reset the subtree to documented defaults through the shared settings-menu renderer until the user exits. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
4045
+ * @details Lets the user add, inspect, confirm-before-remove, and reset global Command entries, toggle direct local per-language enable flags, and reset the subtree to documented defaults through the shared settings-menu renderer until the user exits. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
3864
4046
  * @param[in] ctx {ExtensionCommandContext} Active command context.
3865
4047
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
3866
4048
  * @return {Promise<void>} Promise resolved when the menu closes.
3867
- * @satisfies REQ-008, REQ-151, REQ-152, REQ-153, REQ-154, REQ-160, REQ-161, REQ-193, REQ-195, REQ-248, REQ-253
4049
+ * @satisfies REQ-008, REQ-151, REQ-152, REQ-153, REQ-154, REQ-160, REQ-161, REQ-193, REQ-195, REQ-248, REQ-253, REQ-345, REQ-346, REQ-347, REQ-348, REQ-349
3868
4050
  */
3869
4051
  async function configureStaticCheckMenu(
3870
4052
  ctx: ExtensionCommandContext,
@@ -3959,6 +4141,11 @@ async function configureStaticCheckMenu(
3959
4141
  continue;
3960
4142
  }
3961
4143
 
4144
+ if (staticChoice === "view-static-check-entry") {
4145
+ await showPiUsereqSettingsMenu(ctx, "View static code checker", buildStaticCheckViewChoices(config));
4146
+ continue;
4147
+ }
4148
+
3962
4149
  if (staticChoice === "remove-static-check-entry") {
3963
4150
  const configuredLanguage = await showPiUsereqSettingsMenu(ctx, "Remove static code checker", buildConfiguredStaticCheckLanguageChoices(config));
3964
4151
  if (!configuredLanguage) {
@@ -3985,12 +4172,40 @@ async function configureStaticCheckMenu(
3985
4172
  ctx.ui.notify("Restored default static code checker configuration", "info");
3986
4173
  continue;
3987
4174
  }
4175
+ const targetedCheckers = getStaticCheckLanguageConfigForMenu(config, configuredLanguage).checkers;
4176
+ const removalApproved = await confirmStaticCheckRemoval(ctx, configuredLanguage, targetedCheckers);
4177
+ if (!removalApproved) {
4178
+ ctx.ui.notify(`Aborted removal of static-check entries for ${configuredLanguage}`, "info");
4179
+ continue;
4180
+ }
3988
4181
  config["static-check"][configuredLanguage] = createStaticCheckLanguageConfig([]);
3989
4182
  onConfigChange();
3990
4183
  ctx.ui.notify(`Removed static-check entries for ${configuredLanguage}`, "info");
3991
4184
  continue;
3992
4185
  }
3993
4186
 
4187
+ if (staticChoice === "reset-static-check-entry") {
4188
+ const approved = await confirmResetChanges(
4189
+ ctx,
4190
+ "Confirm static-check reset",
4191
+ [{
4192
+ label: "Language static code checkers",
4193
+ previousValue: formatStaticCheckLanguagesSummary(config),
4194
+ nextValue: formatStaticCheckLanguagesSummary({ ...config, "static-check": getDefaultStaticCheckConfig() }),
4195
+ }].filter((change) => change.previousValue !== change.nextValue),
4196
+ "Approve restoring the documented per-language static-check defaults.",
4197
+ "Abort the static-check reset and keep the current values.",
4198
+ );
4199
+ if (!approved) {
4200
+ ctx.ui.notify("Aborted static-check reset", "info");
4201
+ continue;
4202
+ }
4203
+ resetStaticCheckConfig(config);
4204
+ onConfigChange();
4205
+ ctx.ui.notify("Restored default static code checker configuration", "info");
4206
+ continue;
4207
+ }
4208
+
3994
4209
  if (staticChoice === "reset-defaults") {
3995
4210
  const approved = await confirmResetChanges(
3996
4211
  ctx,