pi-usereq 0.12.0 → 0.32.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.
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * @brief Declares the extension version string.
9
9
  * @details The value is exported for external inspection and packaging metadata alignment. Access complexity is O(1).
10
10
  */
11
- export const VERSION = "0.12.0";
11
+ export const VERSION = "0.32.0";
12
12
 
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
@@ -32,6 +32,7 @@ import {
32
32
  createStaticCheckLanguageConfig,
33
33
  getDefaultConfig,
34
34
  getDefaultStaticCheckConfig,
35
+ getGlobalConfigPath,
35
36
  getProjectConfigPath,
36
37
  loadConfig,
37
38
  normalizeConfigPaths,
@@ -109,6 +110,7 @@ import {
109
110
  DEFAULT_DEBUG_LOG_FILE,
110
111
  DEFAULT_DEBUG_LOG_ON_STATUS,
111
112
  DEFAULT_DEBUG_STATUS_CHANGES,
113
+ DEFAULT_DEBUG_TOOL_COMMANDS_ENABLED,
112
114
  DEFAULT_DEBUG_WORKFLOW_EVENTS,
113
115
  logDebugPromptEvent,
114
116
  logDebugPromptWorkflowEvent,
@@ -118,6 +120,7 @@ import {
118
120
  normalizeDebugLogFile,
119
121
  normalizeDebugLogOnStatus,
120
122
  normalizeDebugStatusChanges,
123
+ normalizeDebugToolCommandsEnabled,
121
124
  normalizeDebugWorkflowEvents,
122
125
  shouldLogDebugPromptWorkflowState,
123
126
  type DebugLogOnStatus,
@@ -268,12 +271,131 @@ function loadProjectConfig(cwd: string): UseReqConfig {
268
271
  }
269
272
 
270
273
  /**
271
- * @brief Persists project configuration from the extension runtime.
272
- * @details Resolves the project base, normalizes configured directory paths into project-relative form, and delegates persistence to `saveConfig` without serializing runtime-derived path metadata. Runtime is O(n) in config size. Side effects include config-file writes.
274
+ * @brief Describes the execute-result surface reused by debug tool wrapper commands.
275
+ * @details Narrows debug slash-command handlers to the same monolithic content-plus-execution wrapper returned by agent tools so editor output and notifications can reuse shared extraction helpers. The alias is compile-time only and introduces no runtime cost.
276
+ */
277
+ type DebugToolCommandExecuteResult = ReturnType<typeof buildMonolithicToolExecuteResult>;
278
+
279
+ /**
280
+ * @brief Tests whether debug tool wrapper commands should be registered for one runtime cwd.
281
+ * @details Loads the effective project configuration for the supplied cwd and returns `true` only when `DEBUG_TOOL_COMMANDS_ENABLED` resolves to `enable`. Malformed or unreadable config payloads degrade to `false` so extension activation never aborts while deciding whether to register optional debug commands. Runtime is dominated by config I/O. Side effects are limited to filesystem reads.
282
+ * @param[in] cwd {string} Candidate runtime working directory.
283
+ * @return {boolean} `true` when debug tool wrapper commands should be registered.
284
+ * @satisfies REQ-323
285
+ */
286
+ function shouldRegisterDebugToolCommands(cwd: string): boolean {
287
+ try {
288
+ return normalizeDebugToolCommandsEnabled(loadProjectConfig(cwd).DEBUG_TOOL_COMMANDS_ENABLED) === "enable";
289
+ } catch {
290
+ return false;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * @brief Writes one debug tool-wrapper result into the editor and emits a status notification.
296
+ * @details Extracts the primary monolithic content text from the wrapped tool result, forwards that exact text to the editor, and emits an informational or error notification keyed by the tool exit code. Runtime is O(n) in output length. Side effects include editor-text mutation and UI notifications.
297
+ * @param[in] ctx {ExtensionCommandContext} Active command context.
298
+ * @param[in] commandName {string} Invoked debug slash-command name without the leading `/`.
299
+ * @param[in] result {DebugToolCommandExecuteResult} Wrapped tool execution result.
300
+ * @return {void} No return value.
301
+ * @satisfies REQ-324
302
+ */
303
+ function writeDebugToolCommandResultToEditor(
304
+ ctx: ExtensionCommandContext,
305
+ commandName: string,
306
+ result: DebugToolCommandExecuteResult,
307
+ ): void {
308
+ const outputText = getMonolithicToolText(result);
309
+ ctx.ui.setEditorText(outputText);
310
+ const succeeded = result.details.execution.code === 0;
311
+ ctx.ui.notify(
312
+ succeeded
313
+ ? `Wrote /${commandName} output to the editor.`
314
+ : `FAILED: Wrote /${commandName} output to the editor.`,
315
+ succeeded ? "info" : "error",
316
+ );
317
+ }
318
+
319
+ /**
320
+ * @brief Executes one config-gated debug tool wrapper slash command.
321
+ * @details Resolves a live cwd for the invoking command context, refreshes runtime path state, loads the effective project configuration, rejects execution when debug tool wrapper commands are disabled, and otherwise writes the selected wrapped tool output into the editor. Runtime is dominated by the delegated tool runner. Side effects include runtime-path bootstrap, filesystem reads, optional tool side effects, editor-text mutation, and UI notifications.
322
+ * @param[in] ctx {ExtensionCommandContext} Active command context.
323
+ * @param[in] commandName {string} Invoked debug slash-command name without the leading `/`.
324
+ * @param[in] operation {(projectBase: string, config: UseReqConfig) => DebugToolCommandExecuteResult} Wrapped tool executor.
325
+ * @return {void} No return value.
326
+ * @throws {ReqError} Throws when debug tool wrapper commands are disabled for the active project.
327
+ * @satisfies REQ-324, REQ-325
328
+ */
329
+ function executeDebugToolCommand(
330
+ ctx: ExtensionCommandContext,
331
+ commandName: string,
332
+ operation: (projectBase: string, config: UseReqConfig) => DebugToolCommandExecuteResult,
333
+ ): void {
334
+ const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
335
+ syncContextCwdMirror(ctx, commandCwd);
336
+ bootstrapRuntimePathState(commandCwd, {
337
+ gitPath: resolveRuntimeGitPath(commandCwd),
338
+ });
339
+ const projectBase = getProjectBase(commandCwd);
340
+ const config = loadProjectConfig(commandCwd);
341
+ if (normalizeDebugToolCommandsEnabled(config.DEBUG_TOOL_COMMANDS_ENABLED) !== "enable") {
342
+ const message = `ERROR: /${commandName} is disabled by Debug > Enable debug commands for tools.`;
343
+ ctx.ui.notify(message, "error");
344
+ throw new ReqError(message, 1);
345
+ }
346
+ writeDebugToolCommandResultToEditor(ctx, commandName, operation(projectBase, config));
347
+ }
348
+
349
+ /**
350
+ * @brief Registers config-gated debug slash-command wrappers for selected built-in analysis tools.
351
+ * @details Registers `debug-compress`, `debug-references`, `debug-static-check`, and `debug-tokens` as extension commands that reuse the same runner paths as the corresponding agent tools and write the resulting monolithic text into the editor instead of the LLM content channel. Re-registering the same commands is idempotent because pi keeps the latest same-extension command definition per name. Runtime is O(1) for registration; handler cost depends on the selected runner. Side effects include command registration.
352
+ * @param[in] pi {ExtensionAPI} Active extension API instance.
353
+ * @return {void} No return value.
354
+ * @satisfies DES-015, REQ-323, REQ-324, REQ-325
355
+ */
356
+ function registerDebugToolCommands(pi: ExtensionAPI): void {
357
+ const registerDebugToolCommand = (
358
+ commandName: string,
359
+ description: string,
360
+ operation: (projectBase: string, config: UseReqConfig) => DebugToolCommandExecuteResult,
361
+ ): void => {
362
+ pi.registerCommand(commandName, {
363
+ description,
364
+ handler: async (_args, ctx) => {
365
+ executeDebugToolCommand(ctx, commandName, operation);
366
+ },
367
+ });
368
+ };
369
+
370
+ registerDebugToolCommand(
371
+ "debug-compress",
372
+ "Run compress and write the tool output into the editor",
373
+ (projectBase, config) => executeMonolithicTool(() => runCompress(projectBase, config)),
374
+ );
375
+ registerDebugToolCommand(
376
+ "debug-references",
377
+ "Run references and write the tool output into the editor",
378
+ (projectBase, config) => executeStatusTool(() => runReferences(projectBase, config)),
379
+ );
380
+ registerDebugToolCommand(
381
+ "debug-static-check",
382
+ "Run static-check and write the tool output into the editor",
383
+ (projectBase, config) => executeMonolithicTool(() => runProjectStaticCheck(projectBase, config)),
384
+ );
385
+ registerDebugToolCommand(
386
+ "debug-tokens",
387
+ "Run tokens and write the tool output into the editor",
388
+ (projectBase, config) => executeMonolithicTool(() => runTokens(projectBase, config)),
389
+ );
390
+ }
391
+
392
+ /**
393
+ * @brief Persists effective project configuration from the extension runtime.
394
+ * @details Resolves the project base, normalizes configured local directory paths into project-relative form, and delegates split local/global persistence to `saveConfig` without serializing runtime-derived path metadata. Runtime is O(n) in config size. Side effects include config-file writes.
273
395
  * @param[in] cwd {string} Current working directory.
274
- * @param[in] config {UseReqConfig} Configuration to persist.
396
+ * @param[in] config {UseReqConfig} Effective configuration to persist.
275
397
  * @return {void} No return value.
276
- * @satisfies REQ-146
398
+ * @satisfies REQ-146, REQ-315
277
399
  */
278
400
  function saveProjectConfig(cwd: string, config: UseReqConfig): void {
279
401
  const projectBase = getProjectBase(cwd);
@@ -281,18 +403,28 @@ function saveProjectConfig(cwd: string, config: UseReqConfig): void {
281
403
  }
282
404
 
283
405
  /**
284
- * @brief Formats the current project config path for top-level menu display.
285
- * @details Resolves `<base-path>/.pi-usereq.json` from the cwd-derived project base, reuses the shared runtime-path formatter, and rewrites a leading POSIX `$HOME` token to `~` for the `Show configuration` row only. Runtime is O(p) in path length. No external state is mutated.
406
+ * @brief Formats the current local config path for top-level menu display.
407
+ * @details Resolves `<base-path>/.pi-usereq.json` from the cwd-derived project base and reuses the shared runtime-path formatter so the `Show local configuration` row uses the documented `~`-relative display contract. Runtime is O(p) in path length. No external state is mutated.
286
408
  * @param[in] cwd {string} Current working directory.
287
- * @return {string} `~`-relative or absolute config path display value.
409
+ * @return {string} `~`-relative or absolute local config-path display value.
288
410
  * @satisfies REQ-162
289
411
  */
290
- function formatProjectConfigPathForMenu(cwd: string): string {
412
+ function formatLocalConfigPathForMenu(cwd: string): string {
291
413
  return formatRuntimePathForDisplay(
292
414
  getProjectConfigPath(getProjectBase(cwd)),
293
415
  );
294
416
  }
295
417
 
418
+ /**
419
+ * @brief Formats the current global config path for top-level menu display.
420
+ * @details Resolves `~/.config/pi-usereq/config.json` through the shared runtime-path formatter so the `Show global configuration` row uses the documented `~`-relative display contract. Runtime is O(p) in path length. No external state is mutated.
421
+ * @return {string} `~`-relative or absolute global config-path display value.
422
+ * @satisfies REQ-319
423
+ */
424
+ function formatGlobalConfigPathForMenu(): string {
425
+ return formatRuntimePathForDisplay(getGlobalConfigPath());
426
+ }
427
+
296
428
  /**
297
429
  * @brief Builds the standardized terminal rows appended to every configuration menu.
298
430
  * @details Returns the canonical value-less `Reset defaults` row so all configuration menus and descendant selector menus share the same terminal ordering contract without rendering `Save and close`. Runtime is O(1). No external state is mutated.
@@ -409,21 +541,46 @@ async function confirmResetChanges(
409
541
  }
410
542
 
411
543
  /**
412
- * @brief Writes the already-persisted project configuration file text into the editor.
413
- * @details Reads the current `.pi-usereq.json` file content from disk after the caller has saved any pending configuration changes and forwards that exact persisted text into the editor. Runtime is O(n) in serialized config size. Side effects include filesystem reads and editor-text mutation.
544
+ * @brief Writes one already-persisted config file text into the editor.
545
+ * @details Reads the target config file from disk after the caller has saved any pending changes and forwards the exact persisted text into the editor. Runtime is O(n) in serialized config size. Side effects include filesystem reads and editor-text mutation.
546
+ * @param[in] ctx {ExtensionCommandContext} Active command context.
547
+ * @param[in] configPath {string} Absolute persisted config path.
548
+ * @return {void} No return value.
549
+ */
550
+ function writePersistedConfigToEditor(
551
+ ctx: ExtensionCommandContext,
552
+ configPath: string,
553
+ ): void {
554
+ ctx.ui.setEditorText(fs.readFileSync(configPath, "utf8"));
555
+ }
556
+
557
+ /**
558
+ * @brief Writes the already-persisted local configuration file text into the editor.
559
+ * @details Reads `<base-path>/.pi-usereq.json` from disk after the caller has saved any pending local and global configuration changes, then forwards that exact persisted text into the editor. Runtime is O(n) in serialized config size. Side effects include filesystem reads and editor-text mutation.
414
560
  * @param[in] ctx {ExtensionCommandContext} Active command context.
415
561
  * @param[in] cwd {string} Current working directory.
416
- * @param[in] _config {UseReqConfig} Unused effective project configuration retained for stable call-site shape.
417
562
  * @return {void} No return value.
418
563
  * @satisfies REQ-031
419
564
  */
420
- function writePersistedProjectConfigToEditor(
565
+ function writePersistedLocalConfigToEditor(
421
566
  ctx: ExtensionCommandContext,
422
567
  cwd: string,
423
- _config: UseReqConfig,
424
568
  ): void {
425
569
  const projectBase = getProjectBase(cwd);
426
- ctx.ui.setEditorText(fs.readFileSync(getProjectConfigPath(projectBase), "utf8"));
570
+ writePersistedConfigToEditor(ctx, getProjectConfigPath(projectBase));
571
+ }
572
+
573
+ /**
574
+ * @brief Writes the already-persisted global configuration file text into the editor.
575
+ * @details Reads `~/.config/pi-usereq/config.json` from disk after the caller has saved any pending local and global configuration changes, then forwards that exact persisted text into the editor. Runtime is O(n) in serialized config size. Side effects include filesystem reads and editor-text mutation.
576
+ * @param[in] ctx {ExtensionCommandContext} Active command context.
577
+ * @return {void} No return value.
578
+ * @satisfies REQ-318
579
+ */
580
+ function writePersistedGlobalConfigToEditor(
581
+ ctx: ExtensionCommandContext,
582
+ ): void {
583
+ writePersistedConfigToEditor(ctx, getGlobalConfigPath());
427
584
  }
428
585
 
429
586
  /**
@@ -1375,16 +1532,17 @@ function getDebugToolToggleNames(): PiUsereqStartupToolName[] {
1375
1532
 
1376
1533
  /**
1377
1534
  * @brief Restores the debug configuration subtree to its documented defaults.
1378
- * @details Resets global debug enablement, log path, workflow-state filter, dedicated workflow-event logging, and selected tool plus prompt debug toggles without mutating unrelated settings. Runtime is O(1). Side effect: mutates `config`.
1535
+ * @details Resets global debug enablement, log path, tool-wrapper command registration, workflow-state filter, dedicated workflow-event logging, and selected tool plus prompt debug toggles without mutating unrelated settings. Runtime is O(1). Side effect: mutates `config`.
1379
1536
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
1380
1537
  * @return {void} No return value.
1381
- * @satisfies REQ-236, REQ-237, REQ-238, REQ-239, REQ-195, REQ-277
1538
+ * @satisfies REQ-236, REQ-237, REQ-238, REQ-239, REQ-195, REQ-277, REQ-322
1382
1539
  */
1383
1540
  function resetDebugConfigToDefaults(config: UseReqConfig): void {
1384
1541
  config.DEBUG_ENABLED = "disable";
1385
1542
  config.DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_FILE;
1386
1543
  config.DEBUG_STATUS_CHANGES = DEFAULT_DEBUG_STATUS_CHANGES;
1387
1544
  config.DEBUG_WORKFLOW_EVENTS = DEFAULT_DEBUG_WORKFLOW_EVENTS;
1545
+ config.DEBUG_TOOL_COMMANDS_ENABLED = DEFAULT_DEBUG_TOOL_COMMANDS_ENABLED;
1388
1546
  config.DEBUG_LOG_ON_STATUS = DEFAULT_DEBUG_LOG_ON_STATUS;
1389
1547
  config.DEBUG_ENABLED_TOOLS = [];
1390
1548
  config.DEBUG_ENABLED_PROMPTS = [];
@@ -1466,10 +1624,10 @@ async function selectDebugLogOnStatus(
1466
1624
 
1467
1625
  /**
1468
1626
  * @brief Builds the shared settings-menu choices for debug logging configuration.
1469
- * @details Serializes global debug controls plus workflow-state, dedicated workflow-event, per-tool, and per-prompt toggles into one submenu, deriving inventories from the canonical tool and prompt lists and dimming locked rows while debug is disabled. Runtime is O(t + p). No external state is mutated.
1627
+ * @details Serializes global debug controls plus tool-wrapper command registration, workflow-state, dedicated workflow-event, per-tool, and per-prompt toggles into one submenu, deriving inventories from the canonical tool and prompt lists and dimming locked rows while debug is disabled. Runtime is O(t + p). No external state is mutated.
1470
1628
  * @param[in] config {UseReqConfig} Effective project configuration.
1471
1629
  * @return {PiUsereqSettingsMenuChoice[]} Ordered debug-menu choices.
1472
- * @satisfies REQ-240, REQ-241, REQ-242, REQ-243, REQ-193, REQ-277
1630
+ * @satisfies REQ-240, REQ-241, REQ-242, REQ-243, REQ-193, REQ-277, REQ-321, REQ-322
1473
1631
  */
1474
1632
  function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
1475
1633
  const debugEnabled = config.DEBUG_ENABLED === "enable";
@@ -1492,6 +1650,16 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1492
1650
  },
1493
1651
  debugEnabled,
1494
1652
  ),
1653
+ buildDebugMenuChoice(
1654
+ {
1655
+ id: "debug-tool-commands-enabled",
1656
+ label: "Enable debug commands for tools",
1657
+ value: normalizeDebugToolCommandsEnabled(config.DEBUG_TOOL_COMMANDS_ENABLED),
1658
+ values: ["enable", "disable"],
1659
+ description: "Enable or disable slash-command wrappers for `compress`, `references`, `static-check`, and `tokens`.",
1660
+ },
1661
+ debugEnabled,
1662
+ ),
1495
1663
  buildDebugMenuChoice(
1496
1664
  {
1497
1665
  id: "debug-log-on-status",
@@ -1551,17 +1719,28 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1551
1719
 
1552
1720
  /**
1553
1721
  * @brief Runs the interactive Debug submenu.
1554
- * @details Lets the user toggle global debug enablement, edit debug file and workflow filters, toggle dedicated workflow-event logging, mutate per-tool and per-prompt debug selectors, and restore subtree defaults while preserving row focus across re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
1722
+ * @details Lets the user toggle global debug enablement, tool-wrapper command registration, debug file and workflow filters, dedicated workflow-event logging, per-tool selectors, and per-prompt selectors while preserving row focus across re-renders. Runtime depends on user interaction count. Side effects include UI updates, config mutation, and optional debug command registration.
1723
+ * @param[in] pi {ExtensionAPI} Active extension API instance.
1555
1724
  * @param[in] ctx {ExtensionCommandContext} Active command context.
1556
1725
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
1557
1726
  * @return {Promise<void>} Promise resolved when the submenu closes.
1558
- * @satisfies REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-192, REQ-193, REQ-195, REQ-277
1727
+ * @satisfies REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-192, REQ-193, REQ-195, REQ-277, REQ-321, REQ-322, REQ-323
1559
1728
  */
1560
1729
  async function configureDebugMenu(
1730
+ pi: ExtensionAPI,
1561
1731
  ctx: ExtensionCommandContext,
1562
1732
  config: UseReqConfig,
1563
1733
  onConfigChange: () => void,
1564
1734
  ): Promise<void> {
1735
+ const updateDebugToolCommandsEnabled = (nextValue: unknown): void => {
1736
+ config.DEBUG_TOOL_COMMANDS_ENABLED = normalizeDebugToolCommandsEnabled(nextValue);
1737
+ onConfigChange();
1738
+ if (config.DEBUG_TOOL_COMMANDS_ENABLED === "enable") {
1739
+ registerDebugToolCommands(pi);
1740
+ }
1741
+ ctx.ui.notify(`Debug tool commands ${config.DEBUG_TOOL_COMMANDS_ENABLED}`, "info");
1742
+ };
1743
+
1565
1744
  let focusedChoiceId: string | undefined;
1566
1745
  while (true) {
1567
1746
  const choice = await showPiUsereqSettingsMenu(ctx, "Debug", buildDebugMenuChoices(config), {
@@ -1574,6 +1753,10 @@ async function configureDebugMenu(
1574
1753
  ctx.ui.notify(`Debug ${config.DEBUG_ENABLED}`, "info");
1575
1754
  return;
1576
1755
  }
1756
+ if (choiceId === "debug-tool-commands-enabled") {
1757
+ updateDebugToolCommandsEnabled(newValue);
1758
+ return;
1759
+ }
1577
1760
  if (choiceId === "debug-status-changes") {
1578
1761
  config.DEBUG_STATUS_CHANGES = normalizeDebugStatusChanges(newValue);
1579
1762
  onConfigChange();
@@ -1634,6 +1817,7 @@ async function configureDebugMenu(
1634
1817
  const resetPreview: ResetConfirmationChange[] = [
1635
1818
  { label: "Debug", previousValue: config.DEBUG_ENABLED, nextValue: "disable" },
1636
1819
  { label: "Log file", previousValue: config.DEBUG_LOG_FILE, nextValue: DEFAULT_DEBUG_LOG_FILE },
1820
+ { label: "Enable debug commands for tools", previousValue: normalizeDebugToolCommandsEnabled(config.DEBUG_TOOL_COMMANDS_ENABLED), nextValue: DEFAULT_DEBUG_TOOL_COMMANDS_ENABLED },
1637
1821
  { label: "Status changes", previousValue: normalizeDebugStatusChanges(config.DEBUG_STATUS_CHANGES), nextValue: DEFAULT_DEBUG_STATUS_CHANGES },
1638
1822
  { label: "Workflow events", previousValue: normalizeDebugWorkflowEvents(config.DEBUG_WORKFLOW_EVENTS), nextValue: DEFAULT_DEBUG_WORKFLOW_EVENTS },
1639
1823
  { label: "Log on status", previousValue: config.DEBUG_LOG_ON_STATUS, nextValue: DEFAULT_DEBUG_LOG_ON_STATUS },
@@ -1669,6 +1853,12 @@ async function configureDebugMenu(
1669
1853
  }
1670
1854
  continue;
1671
1855
  }
1856
+ if (choice === "debug-tool-commands-enabled") {
1857
+ updateDebugToolCommandsEnabled(
1858
+ config.DEBUG_TOOL_COMMANDS_ENABLED === "enable" ? "disable" : "enable",
1859
+ );
1860
+ continue;
1861
+ }
1672
1862
  if (choice === "debug-status-changes") {
1673
1863
  config.DEBUG_STATUS_CHANGES = normalizeDebugStatusChanges(
1674
1864
  config.DEBUG_STATUS_CHANGES === "enable" ? "disable" : "enable",
@@ -2314,7 +2504,7 @@ async function selectPiNotifySoundLevel(
2314
2504
 
2315
2505
  /**
2316
2506
  * @brief Runs the interactive notification-configuration menu.
2317
- * @details Exposes command-notify, sound, and Pushover controls through the shared settings-menu renderer, delegates completed/interrupted/failed toggles to dedicated event submenus, persists boot-sound changes without altering the active runtime sound level, keeps `Enable pushover` locked until both credentials are populated, decodes escaped control-sequence input for `Pushover text`, and preserves row focus across menu re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
2507
+ * @details Exposes command-notify, sound, and Pushover controls through the shared settings-menu renderer, persists every notification subtree mutation into global configuration, delegates completed/interrupted/failed toggles to dedicated event submenus, preserves boot-sound edits without altering the active runtime sound level, keeps `Enable pushover` locked until both credentials are populated, decodes escaped control-sequence input for `Pushover text`, and preserves row focus across menu re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
2318
2508
  * @param[in] ctx {ExtensionCommandContext} Active command context.
2319
2509
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
2320
2510
  * @return {Promise<boolean>} `true` when the sound-toggle shortcut changed.
@@ -2610,10 +2800,10 @@ async function configurePiNotifyMenu(
2610
2800
 
2611
2801
  /**
2612
2802
  * @brief Registers the configurable notification-sound shortcut when supported.
2613
- * @details Loads the current project config, registers one raw pi shortcut when
2803
+ * @details Loads the current effective config, registers one raw pi shortcut when
2614
2804
  * the runtime exposes `registerShortcut(...)`, cycles only the active runtime
2615
- * sound level on invocation, leaves `.pi-usereq.json` unchanged, refreshes the
2616
- * status bar, and emits one info notification. Runtime is O(1) for registration
2805
+ * sound level on invocation, leaves persisted local and global configuration
2806
+ * unchanged, refreshes the status bar, and emits one info notification. Runtime is O(1) for registration
2617
2807
  * plus one status update per shortcut use. Side effects include shortcut
2618
2808
  * registration and status updates.
2619
2809
  * @param[in] pi {ExtensionAPI} Active extension API instance.
@@ -3325,7 +3515,7 @@ function buildPiUsereqToolToggleChoices(pi: ExtensionAPI, config: UseReqConfig):
3325
3515
 
3326
3516
  /**
3327
3517
  * @brief Runs the interactive active-tool configuration menu.
3328
- * @details Synchronizes runtime active tools with persisted config, renders startup-tool actions through the shared settings-menu UI, preserves the documented per-tool ordering, and updates configuration state in response to selections until the user exits. Runtime depends on user interaction count. Side effects include UI updates, active-tool changes, and config mutation.
3518
+ * @details Synchronizes runtime active tools with the effective config, renders startup-tool actions through the shared settings-menu UI, persists enablement changes into global configuration, preserves the documented per-tool ordering, and updates configuration state in response to selections until the user exits. Runtime depends on user interaction count. Side effects include UI updates, active-tool changes, and config mutation.
3329
3519
  * @param[in] pi {ExtensionAPI} Active extension API instance.
3330
3520
  * @param[in] ctx {ExtensionCommandContext} Active command context.
3331
3521
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
@@ -3599,7 +3789,7 @@ function buildConfiguredStaticCheckLanguageChoices(config: UseReqConfig): PiUser
3599
3789
 
3600
3790
  /**
3601
3791
  * @brief Runs the interactive static-check configuration menu.
3602
- * @details Lets the user add Command entries by guided prompts, remove configured language entries, toggle direct 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.
3792
+ * @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.
3603
3793
  * @param[in] ctx {ExtensionCommandContext} Active command context.
3604
3794
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
3605
3795
  * @return {Promise<void>} Promise resolved when the menu closes.
@@ -3755,11 +3945,11 @@ async function configureStaticCheckMenu(
3755
3945
 
3756
3946
  /**
3757
3947
  * @brief Builds the shared settings-menu choices for the top-level pi-usereq configuration UI.
3758
- * @details Serializes primary configuration actions into right-valued menu rows consumed by the shared settings-menu renderer, including automatic git-commit mode, effective prompt-command worktree state, notification summary, debug summary, locked worktree rows when automatic git commit is disabled, and the display-only config path beside `show-config`. Runtime is O(s) in source-directory count. No external state is mutated.
3948
+ * @details Serializes primary configuration actions into right-valued menu rows consumed by the shared settings-menu renderer, including automatic git-commit mode, effective prompt-command worktree state, notification summary, debug summary, locked worktree rows when automatic git commit is disabled, and display-only local plus global config paths. Runtime is O(s) in source-directory count. No external state is mutated.
3759
3949
  * @param[in] cwd {string} Current working directory.
3760
3950
  * @param[in] config {UseReqConfig} Effective project configuration.
3761
3951
  * @return {PiUsereqSettingsMenuChoice[]} Ordered top-level menu choices.
3762
- * @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240
3952
+ * @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-314, REQ-318, REQ-319, REQ-320
3763
3953
  */
3764
3954
  function buildPiUsereqMenuChoices(
3765
3955
  cwd: string,
@@ -3822,32 +4012,39 @@ function buildPiUsereqMenuChoices(
3822
4012
  id: "static-check",
3823
4013
  label: "Language static code checkers",
3824
4014
  value: formatStaticCheckLanguagesSummary(config),
3825
- description: "Manage guided Command static-check entries and per-language enable flags.",
4015
+ description: "Manage global Command static-check entries and local per-language enable flags.",
3826
4016
  },
3827
4017
  {
3828
4018
  id: "startup-tools",
3829
4019
  label: "Enable tools",
3830
4020
  value: `${getConfiguredEnabledPiUsereqTools(config).length} enabled`,
3831
- description: "Manage which configurable tools become active during session_start.",
4021
+ description: "Manage the global configurable tool set activated during session_start.",
3832
4022
  },
3833
4023
  {
3834
4024
  id: "notifications",
3835
4025
  label: "Notifications",
3836
4026
  value: `notification:${formatPiNotifyStatus(config)} • sound:${config["notify-sound"]} • pushover:${formatPiNotifyPushoverStatus(config)}`,
3837
- description: "Manage command-notify, sound, and Pushover settings with dedicated event submenus.",
4027
+ description: "Manage global command-notify, sound, and Pushover settings with dedicated event submenus.",
3838
4028
  },
3839
4029
  {
3840
4030
  id: "debug",
3841
4031
  label: "Debug",
3842
4032
  value: formatDebugMenuSummary(config),
3843
- description: "Manage debug logging for tools and `req-*` prompt orchestration.",
4033
+ description: "Manage project-local debug logging for tools and `req-*` prompt orchestration.",
4034
+ },
4035
+ {
4036
+ id: "show-local-config",
4037
+ label: "Show local configuration",
4038
+ value: formatLocalConfigPathForMenu(cwd),
4039
+ valueTone: "dim",
4040
+ description: "Persist pending configuration changes and write the exact local config file text into the editor.",
3844
4041
  },
3845
4042
  {
3846
- id: "show-config",
3847
- label: "Show configuration",
3848
- value: formatProjectConfigPathForMenu(cwd),
4043
+ id: "show-global-config",
4044
+ label: "Show global configuration",
4045
+ value: formatGlobalConfigPathForMenu(),
3849
4046
  valueTone: "dim",
3850
- description: "Persist the current project configuration file and write its exact text into the editor.",
4047
+ description: "Persist pending configuration changes and write the exact global config file text into the editor.",
3851
4048
  },
3852
4049
  ...buildTerminalSettingsMenuChoices({
3853
4050
  resetDefaultsDescription: "Restore the default pi-usereq configuration for the current project base.",
@@ -3905,12 +4102,12 @@ function buildSrcDirRemovalChoices(config: UseReqConfig): PiUsereqSettingsMenuCh
3905
4102
 
3906
4103
  /**
3907
4104
  * @brief Runs the top-level pi-usereq configuration menu.
3908
- * @details Loads project config, exposes docs/test/source/automatic-commit/worktree/static-check/startup-tool/notification/debug actions through the shared settings-menu renderer, forces worktree disablement when automatic git commit is disabled, prevents locked row edits, persists changes on exit, closes immediately after `Show configuration`, and refreshes the single-line status bar. Runtime depends on user interaction count. Side effects include UI updates, config writes, active-tool changes, and editor text updates.
4105
+ * @details Loads the effective merged config, exposes docs/test/source/automatic-commit/worktree/static-check/startup-tool/notification/debug actions through the shared settings-menu renderer, forces worktree disablement when automatic git commit is disabled, prevents locked row edits, persists changes on exit, closes immediately after `Show local configuration` or `Show global configuration`, and refreshes the single-line status bar. Runtime depends on user interaction count. Side effects include UI updates, config writes, active-tool changes, and editor text updates.
3909
4106
  * @param[in] pi {ExtensionAPI} Active extension API instance.
3910
4107
  * @param[in] ctx {ExtensionCommandContext} Active command context.
3911
4108
  * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
3912
4109
  * @return {Promise<void>} Promise resolved when configuration is saved and the menu closes.
3913
- * @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-162, REQ-190, REQ-191, REQ-192, REQ-194, REQ-195, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243
4110
+ * @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-162, REQ-190, REQ-191, REQ-192, REQ-194, REQ-195, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-314, REQ-318, REQ-319, REQ-320
3914
4111
  */
3915
4112
  async function configurePiUsereq(
3916
4113
  pi: ExtensionAPI,
@@ -4120,7 +4317,7 @@ async function configurePiUsereq(
4120
4317
  continue;
4121
4318
  }
4122
4319
  if (choice === "debug") {
4123
- await configureDebugMenu(ctx, config, persistConfigChange);
4320
+ await configureDebugMenu(pi, ctx, config, persistConfigChange);
4124
4321
  continue;
4125
4322
  }
4126
4323
  if (choice === "reset-defaults") {
@@ -4152,12 +4349,16 @@ async function configurePiUsereq(
4152
4349
  ctx.ui.notify("Restored all default configuration values", "info");
4153
4350
  continue;
4154
4351
  }
4155
- if (choice === "show-config") {
4352
+ if (choice === "show-local-config" || choice === "show-global-config") {
4156
4353
  persistConfigChange();
4157
4354
  if (config["notify-sound-toggle-shortcut"] !== initialShortcut) {
4158
4355
  ctx.ui.notify("Sound toggle hotkey bind updated; run /reload to apply the new binding", "info");
4159
4356
  }
4160
- writePersistedProjectConfigToEditor(ctx, ctx.cwd, config);
4357
+ if (choice === "show-local-config") {
4358
+ writePersistedLocalConfigToEditor(ctx, ctx.cwd);
4359
+ } else {
4360
+ writePersistedGlobalConfigToEditor(ctx);
4361
+ }
4161
4362
  return;
4162
4363
  }
4163
4364
  }
@@ -4185,10 +4386,10 @@ function registerConfigCommands(
4185
4386
 
4186
4387
  /**
4187
4388
  * @brief Registers the complete pi-usereq extension.
4188
- * @details Validates installation-owned bundled resources, registers the specialized `req-reset` and `req-references` commands plus bundled prompt-backed commands and agent tools, registers configuration commands, registers the configurable notification-sound shortcut when the runtime supports shortcuts, and installs shared wrappers for all supported pi lifecycle hooks so status telemetry, context usage, prompt timing, cumulative runtime, prompt-specific Pushover metadata, tool-result debug logging, and prompt-orchestration effects remain synchronized with runtime events. Runtime is O(h) in hook count during registration. Side effects include filesystem reads, command/tool/shortcut registration, UI updates, active-tool changes, optional debug-log writes, and timer scheduling.
4389
+ * @details Validates installation-owned bundled resources, registers the specialized `req-reset` and `req-references` commands plus bundled prompt-backed commands and agent tools, conditionally registers config-gated debug tool wrapper commands when the current project enables them, registers configuration commands, registers the configurable notification-sound shortcut when the runtime supports shortcuts, and installs shared wrappers for all supported pi lifecycle hooks so status telemetry, context usage, prompt timing, cumulative runtime, prompt-specific Pushover metadata, tool-result debug logging, and prompt-orchestration effects remain synchronized with runtime events. Runtime is O(h) in hook count during registration. Side effects include filesystem reads, command/tool/shortcut registration, UI updates, active-tool changes, optional debug-log writes, and timer scheduling.
4189
4390
  * @param[in] pi {ExtensionAPI} Active extension API instance.
4190
4391
  * @return {void} No return value.
4191
- * @satisfies DES-002, REQ-004, REQ-005, REQ-009, REQ-044, REQ-067, REQ-068, REQ-109, REQ-111, REQ-112, REQ-113, REQ-114, REQ-115, REQ-116, REQ-117, REQ-118, REQ-119, REQ-120, REQ-121, REQ-122, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-131, REQ-132, REQ-133, REQ-134, REQ-137, REQ-159, REQ-163, REQ-164, REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-174, REQ-179, REQ-180, REQ-184, REQ-188, REQ-190, REQ-191, REQ-192, REQ-193, REQ-194, REQ-195, REQ-196, REQ-197, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-244, REQ-245, REQ-246, REQ-247, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303, REQ-304, REQ-305, REQ-306, REQ-312, REQ-313
4392
+ * @satisfies DES-002, DES-015, REQ-004, REQ-005, REQ-009, REQ-044, REQ-067, REQ-068, REQ-109, REQ-111, REQ-112, REQ-113, REQ-114, REQ-115, REQ-116, REQ-117, REQ-118, REQ-119, REQ-120, REQ-121, REQ-122, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-131, REQ-132, REQ-133, REQ-134, REQ-137, REQ-159, REQ-163, REQ-164, REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-174, REQ-179, REQ-180, REQ-184, REQ-188, REQ-190, REQ-191, REQ-192, REQ-193, REQ-194, REQ-195, REQ-196, REQ-197, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-244, REQ-245, REQ-246, REQ-247, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303, REQ-304, REQ-305, REQ-306, REQ-312, REQ-313, REQ-323, REQ-324, REQ-325
4192
4393
  */
4193
4394
  export default function piUsereqExtension(pi: ExtensionAPI): void {
4194
4395
  const statusController = createPiUsereqStatusController();
@@ -4196,6 +4397,9 @@ export default function piUsereqExtension(pi: ExtensionAPI): void {
4196
4397
  registerReqResetCommand(pi, statusController);
4197
4398
  registerReqReferencesCommand(pi, statusController);
4198
4399
  registerPromptCommands(pi, statusController);
4400
+ if (shouldRegisterDebugToolCommands(getProcessCwdSafe())) {
4401
+ registerDebugToolCommands(pi);
4402
+ }
4199
4403
  registerAgentTools(pi);
4200
4404
  registerConfigCommands(pi, statusController);
4201
4405
  registerPiNotifyShortcut(pi, statusController);
@@ -14,11 +14,13 @@ import { detectLanguage } from "../src/core/generate-markdown.js";
14
14
  import { LANGUAGE_TAGS } from "../src/core/find-constructs.js";
15
15
  import {
16
16
  createStaticCheckLanguageConfig,
17
- getProjectConfigPath,
17
+ getDefaultConfig,
18
18
  type UseReqConfig,
19
19
  } from "../src/core/config.js";
20
20
  import {
21
21
  initFixtureRepo,
22
+ readGlobalConfigJson,
23
+ readProjectConfigJson,
22
24
  runNodeCli,
23
25
  runPythonCli,
24
26
  runPythonInline,
@@ -450,10 +452,14 @@ function buildProjectScenarios(): AttendedScenario[] {
450
452
  normalize: createProjectNormalizer(projectBase),
451
453
  cleanup: () => removePath(projectBase),
452
454
  postAssert: () => {
453
- const payload = JSON.parse(fs.readFileSync(getProjectConfigPath(projectBase), "utf8")) as UseReqConfig;
454
- assert.deepEqual(payload["static-check"].Python, createStaticCheckLanguageConfig([
455
+ const defaultConfig = getDefaultConfig(projectBase);
456
+ const localPayload = readProjectConfigJson(projectBase) as unknown as Record<string, unknown>;
457
+ const globalPayload = readGlobalConfigJson() as unknown as Record<string, unknown>;
458
+ assert.equal((localPayload["static-check"] as Record<string, any>).Python.enabled, "enable");
459
+ assert.deepEqual((globalPayload["static-check"] as Record<string, any>).Python.checkers, [
460
+ ...defaultConfig["static-check"].Python.checkers,
455
461
  { module: "Command", cmd: "git", params: ["--version"] },
456
- ], "enable"));
462
+ ]);
457
463
  },
458
464
  };
459
465
  },