pi-usereq 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +6 -6
  3. package/package.json +1 -1
  4. package/pi-usereq/docs/REFERENCES.md +818 -691
  5. package/pi-usereq/docs/REQUIREMENTS.md +131 -77
  6. package/pi-usereq/docs/WORKFLOW.md +185 -51
  7. package/scripts/lib/extension-debug-harness.ts +2 -2
  8. package/scripts/tool-args-to-params.ts +2 -2
  9. package/src/cli.ts +12 -12
  10. package/src/core/extension-status.ts +69 -12
  11. package/src/core/pi-notify.ts +5 -5
  12. package/src/core/pi-usereq-tools.ts +4 -2
  13. package/src/core/prompt-command-catalog.ts +4 -5
  14. package/src/core/prompt-command-runtime.ts +183 -44
  15. package/src/core/prompts.ts +0 -2
  16. package/src/core/req-references-command.ts +175 -0
  17. package/src/core/req-reset-command.ts +323 -0
  18. package/src/core/resources.ts +6 -23
  19. package/src/core/settings-menu.ts +85 -28
  20. package/src/core/tool-runner.ts +26 -6
  21. package/src/index.ts +523 -85
  22. package/tests/attended-results-scenarios.ts +5 -5
  23. package/tests/cli-command-option-parity.test.ts +25 -25
  24. package/tests/debug-extension-harness.test.ts +1 -1
  25. package/tests/extension-registration.test.ts +1029 -82
  26. package/tests/oracle-project.test.ts +4 -4
  27. package/tests/oracle-standalone.test.ts +5 -5
  28. package/src/core/reference-payload.ts +0 -752
  29. package/src/resources/prompts/references.md +0 -64
  30. /package/tests/fixtures_attended_results/project/{references.json → summarize.json} +0 -0
  31. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_c.c.json +0 -0
  32. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_cpp.cpp.json +0 -0
  33. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_csharp.cs.json +0 -0
  34. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_elixir.ex.json +0 -0
  35. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_go.go.json +0 -0
  36. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_haskell.hs.json +0 -0
  37. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_java.java.json +0 -0
  38. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_javascript.js.json +0 -0
  39. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_kotlin.kt.json +0 -0
  40. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_lua.lua.json +0 -0
  41. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_perl.pl.json +0 -0
  42. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_php.php.json +0 -0
  43. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_python.py.json +0 -0
  44. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_ruby.rb.json +0 -0
  45. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_rust.rs.json +0 -0
  46. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_scala.scala.json +0 -0
  47. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_shell.sh.json +0 -0
  48. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_swift.swift.json +0 -0
  49. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_typescript.ts.json +0 -0
  50. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_zig.zig.json +0 -0
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.11.0";
11
+ export const VERSION = "0.12.0";
12
12
 
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
@@ -91,6 +91,18 @@ import {
91
91
  restorePromptCommandExecution,
92
92
  type PromptCommandExecutionPlan,
93
93
  } from "./core/prompt-command-runtime.js";
94
+ import {
95
+ REQ_REFERENCES_COMMAND_DESCRIPTION,
96
+ executeReqReferencesCommandExecution,
97
+ prepareReqReferencesCommandExecution,
98
+ } from "./core/req-references-command.js";
99
+ import {
100
+ REQ_RESET_COMMAND_DESCRIPTION,
101
+ executeReqResetCommandExecution,
102
+ prepareReqResetCommandExecution,
103
+ type ReqResetCommandExecutionResult,
104
+ type ReqResetCommandPlan,
105
+ } from "./core/req-reset-command.js";
94
106
  import {
95
107
  DEBUG_PROMPT_NAMES,
96
108
  DEBUG_WORKFLOW_STATES,
@@ -114,6 +126,7 @@ import {
114
126
  import { PROMPT_COMMAND_NAMES } from "./core/prompt-command-catalog.js";
115
127
  import { resolveRuntimeGitPath } from "./core/runtime-project-paths.js";
116
128
  import {
129
+ clearPersistedPromptCommandRuntimeState,
117
130
  readPersistedPromptCommandRuntimeState,
118
131
  writePersistedPromptCommandRuntimeState,
119
132
  } from "./core/prompt-command-state.js";
@@ -123,8 +136,10 @@ import {
123
136
  PI_USEREQ_STATUS_HOOK_NAMES,
124
137
  createPiUsereqStatusController,
125
138
  disposePiUsereqStatusController,
139
+ getPiUsereqRuntimeSoundLevel,
126
140
  isStaleExtensionContextError,
127
141
  renderPiUsereqStatus,
142
+ setPiUsereqRuntimeSoundLevel,
128
143
  setPiUsereqStatusConfig,
129
144
  setPiUsereqWorkflowState,
130
145
  shouldPreservePromptCommandStateOnShutdown,
@@ -135,13 +150,14 @@ import {
135
150
  import {
136
151
  runCompress,
137
152
  runFilesCompress,
138
- runFilesReferences,
139
153
  runFilesSearch,
140
154
  runFilesStaticCheck,
155
+ runFilesSummarize,
141
156
  runFilesTokens,
142
157
  runProjectStaticCheck,
143
158
  runReferences,
144
159
  runSearch,
160
+ runSummarize,
145
161
  runTokens,
146
162
  type ToolResult,
147
163
  } from "./core/tool-runner.js";
@@ -686,6 +702,44 @@ function executeMonolithicTool(operation: () => ToolResult): ReturnType<typeof b
686
702
  }
687
703
  }
688
704
 
705
+ /**
706
+ * @brief Executes one CLI-style runner for a status-only agent tool.
707
+ * @details Reuses the standalone tool-runner contract, preserves `content[0].text` as the status-only `success` or `error: <diagnostic>` payload, and strips success-path `stdout_lines` so `details.execution` stays limited to the numeric code plus optional residual stderr diagnostics. Runtime is dominated by the delegated runner. Side effects depend on the selected tool.
708
+ * @param[in] operation {() => ToolResult} Runner callback.
709
+ * @return {ReturnType<typeof buildMonolithicToolExecuteResult>} Status-only tool execute result.
710
+ * @satisfies REQ-294, REQ-295, REQ-296
711
+ */
712
+ function executeStatusTool(operation: () => ToolResult): ReturnType<typeof buildMonolithicToolExecuteResult> {
713
+ const normalizeExecution = (
714
+ payload: ReturnType<typeof buildMonolithicToolExecuteResult>,
715
+ ): ReturnType<typeof buildMonolithicToolExecuteResult> => {
716
+ const execution = payload.details.execution;
717
+ return {
718
+ content: payload.content,
719
+ details: {
720
+ execution: {
721
+ code: execution.code,
722
+ ...(Array.isArray(execution.stderr_lines) && execution.stderr_lines.length > 0
723
+ ? { stderr_lines: execution.stderr_lines }
724
+ : {}),
725
+ },
726
+ },
727
+ };
728
+ };
729
+
730
+ try {
731
+ return normalizeExecution(buildMonolithicToolExecuteResult(operation()));
732
+ } catch (error) {
733
+ const failure = normalizeToolFailure(error);
734
+ const diagnostic = failure.stderr.trim().replace(/^(Error|error):\s*/u, "") || "unknown failure";
735
+ return normalizeExecution(buildMonolithicToolExecuteResult({
736
+ stdout: "",
737
+ stderr: `error: ${diagnostic}`,
738
+ code: failure.code,
739
+ }));
740
+ }
741
+ }
742
+
689
743
  /**
690
744
  * @brief Starts delivery of one rendered prompt into the current active session.
691
745
  * @details Prefers the replacement-session `sendUserMessage(...)` helper exposed by `withSession(...)` callbacks after session replacement so post-switch prompt delivery never reuses stale pre-switch session-bound extension objects. Returns the underlying delivery promise without awaiting it so callers can record the `running` workflow transition as soon as prompt handoff is accepted instead of waiting for the full agent turn to complete on runtimes whose async replacement-session helpers resolve only after `agent_end`. When pi later invalidates that replacement-session context during successful prompt-end restoration, the helper suppresses the documented stale-extension-context rejection because the prompt was already accepted and late rethrow would surface a false orchestration failure. Falls back to `pi.sendUserMessage(...)` only for non-replacement flows or runtimes that do not expose replacement-session helpers. Runtime is O(n) in prompt length. Side effects are limited to user-message delivery.
@@ -838,7 +892,7 @@ function transitionPromptWorkflowState(
838
892
 
839
893
  /**
840
894
  * @brief Resolves the runtime slash-command description for one bundled prompt.
841
- * @details Reads the bundled prompt front matter, extracts its normalized `description` field, and falls back to the historical generated label when the prompt metadata omits a description. Runtime is O(n) in prompt length. Side effects are limited to filesystem reads.
895
+ * @details Reads the bundled prompt markdown, extracts the first `# ` heading payload, and falls back to the historical generated label when the prompt omits a level-one heading. Runtime is O(n) in prompt length. Side effects are limited to filesystem reads.
842
896
  * @param[in] promptName {import("./core/prompt-command-catalog.js").PromptCommandName} Bundled prompt name.
843
897
  * @return {string} Runtime command description.
844
898
  */
@@ -889,6 +943,38 @@ function notifyContextSafely(
889
943
  }
890
944
  }
891
945
 
946
+ /**
947
+ * @brief Rejects one non-`idle` req-command invocation and records the workflow error state.
948
+ * @details Builds a deterministic busy-state diagnostic from the current workflow state, transitions the shared workflow state to `error`, preserves any pending or active prompt execution metadata for later closure handling, emits an error notification, and throws `ReqError`. Bundled prompt commands reuse `transitionPromptWorkflowState(...)` when cached configuration is available so prompt debug logging captures the actual state transition; specialized non-prompt commands fall back to direct status mutation. Runtime is O(1). Side effects include workflow-state mutation, status-bar rendering, optional debug-log writes, and user notification delivery.
949
+ * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
950
+ * @param[in] ctx {ExtensionContext | ExtensionCommandContext} Active extension context.
951
+ * @param[in] promptName {import("./core/prompt-command-catalog.js").PromptCommandName | undefined} Optional bundled prompt name used for prompt debug logging.
952
+ * @return {never} This helper always throws a deterministic `ReqError`.
953
+ * @throws {ReqError} Always throws because non-`idle` req commands are rejected.
954
+ * @satisfies REQ-224
955
+ */
956
+ function rejectNonIdleReqCommand(
957
+ statusController: PiUsereqStatusController,
958
+ ctx: ExtensionContext | ExtensionCommandContext,
959
+ promptName?: import("./core/prompt-command-catalog.js").PromptCommandName,
960
+ ): never {
961
+ const message = `ERROR: Prompt workflow state is ${statusController.state.workflowState}, expected idle.`;
962
+ if (promptName !== undefined && statusController.config !== undefined) {
963
+ transitionPromptWorkflowState(
964
+ statusController,
965
+ ctx,
966
+ resolveDebugProjectBase(ctx.cwd, statusController),
967
+ statusController.config,
968
+ promptName,
969
+ "error",
970
+ );
971
+ } else {
972
+ setPiUsereqWorkflowState(statusController, "error", ctx);
973
+ }
974
+ notifyContextSafely(ctx, message, "error");
975
+ throw new ReqError(message, 1);
976
+ }
977
+
892
978
  /**
893
979
  * @brief Returns the configurable active-tool inventory visible to the extension.
894
980
  * @details Filters runtime tools against the canonical configurable-tool set, keeps only builtin-backed embedded tools, and orders the result by the documented custom/files/embedded/default-disabled grouping. Runtime is O(t log t). No external state is mutated.
@@ -947,14 +1033,14 @@ function applyConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig): v
947
1033
 
948
1034
  /**
949
1035
  * @brief Handles one intercepted pi lifecycle hook for pi-usereq status updates.
950
- * @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, restores the original session-backed `base-path` for every matched worktree-backed completion by reusing persisted replacement-session command contexts when event contexts omit `switchSession()`, merges and deletes the worktree only for matched successful completions, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful `agent_end` handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
1036
+ * @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, restores the original session-backed `base-path` for every matched worktree-backed completion by reusing persisted replacement-session command contexts when event contexts omit `switchSession()`, executes the stash-assisted merge-and-delete finalization path for every matched successful worktree-backed completion even when a later busy-command rejection already moved workflow state to `error`, emits a warning-only notification when restored `base-path` changes are reapplied after merge, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful `agent_end` handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
951
1037
  * @param[in] pi {ExtensionAPI} Active extension API instance.
952
1038
  * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
953
1039
  * @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
954
1040
  * @param[in] event {unknown} Hook payload forwarded by pi.
955
1041
  * @param[in] ctx {ExtensionContext} Active extension context.
956
1042
  * @return {Promise<void>} Promise resolved when hook processing completes.
957
- * @satisfies REQ-117, REQ-118, REQ-119, REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-176, REQ-178, REQ-184, REQ-185, REQ-186, REQ-187, REQ-208, REQ-209, REQ-221, REQ-228, REQ-229, REQ-230, REQ-244, REQ-245, REQ-246, REQ-247, REQ-276, REQ-277, REQ-278, REQ-279, REQ-280
1043
+ * @satisfies REQ-117, REQ-118, REQ-119, REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-176, REQ-178, REQ-184, REQ-185, REQ-186, REQ-187, REQ-208, REQ-209, REQ-221, REQ-228, REQ-229, REQ-230, REQ-244, REQ-245, REQ-246, REQ-247, REQ-276, REQ-277, REQ-278, REQ-279, REQ-280, REQ-291, REQ-292
958
1044
  */
959
1045
  async function handleExtensionStatusEvent(
960
1046
  pi: ExtensionAPI,
@@ -1025,7 +1111,10 @@ async function handleExtensionStatusEvent(
1025
1111
  if (hookName === "agent_end") {
1026
1112
  if (statusController.config) {
1027
1113
  runPiNotifyEffects(
1028
- statusController.config,
1114
+ {
1115
+ ...statusController.config,
1116
+ "notify-sound": getPiUsereqRuntimeSoundLevel(statusController),
1117
+ },
1029
1118
  event as { messages: AgentEndEvent["messages"] },
1030
1119
  notifyRequest,
1031
1120
  );
@@ -1041,7 +1130,6 @@ async function handleExtensionStatusEvent(
1041
1130
  ? `ERROR: Prompt closure retained worktree ${activePromptRequest.worktreeDir} after ${outcome} outcome.`
1042
1131
  : undefined;
1043
1132
  const shouldFinalizeMatchedSuccess = outcome === "completed"
1044
- && statusController.state.workflowState === "running"
1045
1133
  && activePromptRequest.worktreeDir !== undefined;
1046
1134
  if (debugConfig) {
1047
1135
  logPromptWorkflowEvent(
@@ -1080,6 +1168,7 @@ async function handleExtensionStatusEvent(
1080
1168
  mergeSucceeded: boolean;
1081
1169
  cleanupSucceeded: boolean;
1082
1170
  errorMessage?: string;
1171
+ warningMessage?: string;
1083
1172
  activeContext?: unknown;
1084
1173
  }
1085
1174
  | undefined;
@@ -1134,6 +1223,14 @@ async function handleExtensionStatusEvent(
1134
1223
  }
1135
1224
  notifyContextSafely(promptContext, finalization.errorMessage, "error");
1136
1225
  }
1226
+ if (
1227
+ finalization.warningMessage
1228
+ && finalization.cleanupSucceeded
1229
+ && finalization.mergeSucceeded
1230
+ && !finalization.errorMessage
1231
+ ) {
1232
+ notifyContextSafely(promptContext, finalization.warningMessage, "info");
1233
+ }
1137
1234
  } else {
1138
1235
  try {
1139
1236
  promptContext = (await restorePromptCommandExecution(
@@ -1383,6 +1480,7 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1383
1480
  id: "debug-enabled",
1384
1481
  label: "Debug",
1385
1482
  value: config.DEBUG_ENABLED,
1483
+ values: ["enable", "disable"],
1386
1484
  description: "Enable or disable all debug logging behavior and unlock the remaining Debug rows.",
1387
1485
  },
1388
1486
  buildDebugMenuChoice(
@@ -1408,6 +1506,7 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1408
1506
  id: "debug-status-changes",
1409
1507
  label: "Status changes",
1410
1508
  value: normalizeDebugStatusChanges(config.DEBUG_STATUS_CHANGES),
1509
+ values: ["enable", "disable"],
1411
1510
  description: "Enable or disable `workflow_state` debug entries for prompt-orchestration transitions.",
1412
1511
  },
1413
1512
  debugEnabled,
@@ -1417,6 +1516,7 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1417
1516
  id: "debug-workflow-events",
1418
1517
  label: "Workflow events",
1419
1518
  value: normalizeDebugWorkflowEvents(config.DEBUG_WORKFLOW_EVENTS),
1519
+ values: ["enable", "disable"],
1420
1520
  description: "Enable or disable dedicated workflow debug entries for activation, restoration, closure, and session-shutdown diagnostics.",
1421
1521
  },
1422
1522
  debugEnabled,
@@ -1426,6 +1526,7 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1426
1526
  id: `debug-tool:${toolName}`,
1427
1527
  label: toolName,
1428
1528
  value: enabledTools.has(toolName) ? "enable" : "disable",
1529
+ values: ["enable", "disable"],
1429
1530
  description: PI_USEREQ_CUSTOM_TOOL_NAMES.includes(toolName as never)
1430
1531
  ? `Toggle debug logging for custom tool ${toolName}.`
1431
1532
  : `Toggle debug logging for embedded tool ${toolName}.`,
@@ -1437,6 +1538,7 @@ function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice
1437
1538
  id: `debug-prompt:${promptName}`,
1438
1539
  label: promptName,
1439
1540
  value: enabledPrompts.has(promptName) ? "enable" : "disable",
1541
+ values: ["enable", "disable"],
1440
1542
  description: `Toggle prompt-orchestration debug logging for /${promptName}.`,
1441
1543
  },
1442
1544
  debugEnabled,
@@ -1464,6 +1566,58 @@ async function configureDebugMenu(
1464
1566
  while (true) {
1465
1567
  const choice = await showPiUsereqSettingsMenu(ctx, "Debug", buildDebugMenuChoices(config), {
1466
1568
  initialSelectedId: focusedChoiceId,
1569
+ getChoices: () => buildDebugMenuChoices(config),
1570
+ onChange: (choiceId, newValue) => {
1571
+ if (choiceId === "debug-enabled") {
1572
+ config.DEBUG_ENABLED = newValue === "enable" ? "enable" : "disable";
1573
+ onConfigChange();
1574
+ ctx.ui.notify(`Debug ${config.DEBUG_ENABLED}`, "info");
1575
+ return;
1576
+ }
1577
+ if (choiceId === "debug-status-changes") {
1578
+ config.DEBUG_STATUS_CHANGES = normalizeDebugStatusChanges(newValue);
1579
+ onConfigChange();
1580
+ ctx.ui.notify(`Debug status-change logging ${config.DEBUG_STATUS_CHANGES}`, "info");
1581
+ return;
1582
+ }
1583
+ if (choiceId === "debug-workflow-events") {
1584
+ config.DEBUG_WORKFLOW_EVENTS = normalizeDebugWorkflowEvents(newValue);
1585
+ onConfigChange();
1586
+ ctx.ui.notify(`Debug workflow-event logging ${config.DEBUG_WORKFLOW_EVENTS}`, "info");
1587
+ return;
1588
+ }
1589
+ if (choiceId.startsWith("debug-tool:")) {
1590
+ const toolName = choiceId.slice("debug-tool:".length) as PiUsereqStartupToolName;
1591
+ const enabledTools = new Set(normalizeDebugEnabledTools(config.DEBUG_ENABLED_TOOLS));
1592
+ if (newValue === "enable") {
1593
+ enabledTools.add(toolName);
1594
+ } else {
1595
+ enabledTools.delete(toolName);
1596
+ }
1597
+ config.DEBUG_ENABLED_TOOLS = getDebugToolToggleNames().filter((name) => enabledTools.has(name));
1598
+ onConfigChange();
1599
+ ctx.ui.notify(
1600
+ `${newValue === "enable" ? "Enabled" : "Disabled"} debug logging for ${toolName}`,
1601
+ "info",
1602
+ );
1603
+ return;
1604
+ }
1605
+ if (choiceId.startsWith("debug-prompt:")) {
1606
+ const promptName = choiceId.slice("debug-prompt:".length) as (typeof DEBUG_PROMPT_NAMES)[number];
1607
+ const enabledPrompts = new Set(normalizeDebugEnabledPrompts(config.DEBUG_ENABLED_PROMPTS));
1608
+ if (newValue === "enable") {
1609
+ enabledPrompts.add(promptName);
1610
+ } else {
1611
+ enabledPrompts.delete(promptName);
1612
+ }
1613
+ config.DEBUG_ENABLED_PROMPTS = DEBUG_PROMPT_NAMES.filter((name) => enabledPrompts.has(name));
1614
+ onConfigChange();
1615
+ ctx.ui.notify(
1616
+ `${newValue === "enable" ? "Enabled" : "Disabled"} debug logging for ${promptName}`,
1617
+ "info",
1618
+ );
1619
+ }
1620
+ },
1467
1621
  });
1468
1622
  if (!choice) {
1469
1623
  return;
@@ -1751,6 +1905,13 @@ const PI_NOTIFY_EVENT_MENU_DEFINITIONS: Record<
1751
1905
  },
1752
1906
  };
1753
1907
 
1908
+ /**
1909
+ * @brief Defines the canonical label used for persisted boot-sound menu rows.
1910
+ * @details Reuses one shared string literal across notification menu rows, selectors, reset previews, and tests so the persisted boot-sound terminology remains stable. Access complexity is O(1).
1911
+ * @satisfies REQ-149, REQ-179
1912
+ */
1913
+ const PI_NOTIFY_BOOT_SOUND_LABEL = "Enable sound (boot value)";
1914
+
1754
1915
  /**
1755
1916
  * @brief Formats the top-level summary value for one notification event submenu.
1756
1917
  * @details Counts enabled completed/interrupted/failed toggles for the selected transport and renders the result as `n/3 on` for right-aligned menu display. Runtime is O(1). No external state is mutated.
@@ -1806,6 +1967,7 @@ function buildPiNotifyEventMenuChoices(
1806
1967
  id: eventMenu.keys[row.eventId],
1807
1968
  label: row.label,
1808
1969
  value: config[eventMenu.keys[row.eventId]] ? "on" : "off",
1970
+ values: ["on", "off"],
1809
1971
  description: `${eventMenu.systemLabel}: ${row.description}`,
1810
1972
  })),
1811
1973
  ...buildTerminalSettingsMenuChoices({
@@ -1870,7 +2032,23 @@ async function configurePiNotifyEventMenu(
1870
2032
  ctx,
1871
2033
  eventMenu.submenuTitle,
1872
2034
  buildPiNotifyEventMenuChoices(config, eventMenu),
1873
- { initialSelectedId: focusedChoiceId },
2035
+ {
2036
+ initialSelectedId: focusedChoiceId,
2037
+ getChoices: () => buildPiNotifyEventMenuChoices(config, eventMenu),
2038
+ onChange: (choiceId, newValue) => {
2039
+ const enabled = newValue === "on";
2040
+ config[choiceId as PiNotifyEventBooleanConfigKey] = enabled;
2041
+ onConfigChange();
2042
+ const eventLabel = resolvePiNotifyEventLabel(
2043
+ choiceId as PiNotifyEventBooleanConfigKey,
2044
+ eventMenu,
2045
+ );
2046
+ ctx.ui.notify(
2047
+ `${eventMenu.systemLabel} ${eventLabel} ${enabled ? "enabled" : "disabled"}`,
2048
+ "info",
2049
+ );
2050
+ },
2051
+ },
1874
2052
  );
1875
2053
  if (!choice) {
1876
2054
  return;
@@ -1922,7 +2100,7 @@ async function configurePiNotifyEventMenu(
1922
2100
 
1923
2101
  /**
1924
2102
  * @brief Builds the direct Pushover rows rendered inside `Notifications`.
1925
- * @details Serializes the global enable flag, shared-event submenu launcher, priority, title, text, and credential rows into right-valued menu items appended after the sound-command rows, dims and disables the enable row until both credentials are populated, and escapes control characters for the single-line `Pushover text` value. Runtime is O(n) in the rendered text-template length. No external state is mutated.
2103
+ * @details Serializes the global enable flag, shared-event submenu launcher, priority, title, text, and credential rows into right-valued menu items appended after the sound-command rows, dims and disables the enable row until both credentials are populated, renders the locked value as `configure user/token keys first`, and escapes control characters for the single-line `Pushover text` value. Runtime is O(n) in the rendered text-template length. No external state is mutated.
1926
2104
  * @param[in] config {UseReqConfig} Effective project configuration.
1927
2105
  * @return {PiUsereqSettingsMenuChoice[]} Ordered direct Pushover rows.
1928
2106
  * @satisfies REQ-163, REQ-165, REQ-172, REQ-184, REQ-185, REQ-198, REQ-234, REQ-235
@@ -1934,8 +2112,9 @@ function buildPiNotifyPushoverRows(config: UseReqConfig): PiUsereqSettingsMenuCh
1934
2112
  id: "notify-pushover-enabled",
1935
2113
  label: "Enable pushover",
1936
2114
  labelTone: pushoverCredentialsReady ? undefined : "dim",
1937
- value: pushoverCredentialsReady ? formatPiNotifyPushoverStatus(config) : "off",
2115
+ value: pushoverCredentialsReady ? formatPiNotifyPushoverStatus(config) : "configure user/token keys first",
1938
2116
  valueTone: pushoverCredentialsReady ? undefined : "dim",
2117
+ values: ["on", "off"],
1939
2118
  disabled: !pushoverCredentialsReady,
1940
2119
  description: pushoverCredentialsReady
1941
2120
  ? "Enable or disable all Pushover delivery globally."
@@ -2018,10 +2197,10 @@ async function selectPiNotifyPushoverPriority(
2018
2197
 
2019
2198
  /**
2020
2199
  * @brief Builds the shared settings-menu choices for notification configuration.
2021
- * @details Serializes command-notify, sound, and Pushover blocks with dedicated shared-event submenu launchers so the settings-menu renderer can expose one unified but modular configuration surface, including locked Pushover enablement and escaped single-line rendering for `Pushover text`. Runtime is O(n) in the longest rendered command or text field. No external state is mutated.
2200
+ * @details Serializes command-notify, sound, and Pushover blocks with dedicated shared-event submenu launchers so the settings-menu renderer can expose one unified but modular configuration surface, including locked Pushover enablement, persisted boot-sound rows that stay decoupled from the active runtime sound level, and escaped single-line rendering for `Pushover text`. Runtime is O(n) in the longest rendered command or text field. No external state is mutated.
2022
2201
  * @param[in] config {UseReqConfig} Effective project configuration.
2023
2202
  * @return {PiUsereqSettingsMenuChoice[]} Ordered notification-menu choice vector.
2024
- * @satisfies REQ-137, REQ-149, REQ-150, REQ-151, REQ-152, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-188, REQ-193, REQ-198, REQ-234, REQ-235
2203
+ * @satisfies REQ-137, REQ-149, REQ-150, REQ-151, REQ-152, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-188, REQ-193, REQ-198, REQ-234, REQ-235, REQ-289
2025
2204
  */
2026
2205
  function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
2027
2206
  return [
@@ -2029,6 +2208,7 @@ function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuCho
2029
2208
  id: "notify-enabled",
2030
2209
  label: "Enable notification",
2031
2210
  value: formatPiNotifyStatus(config),
2211
+ values: ["on", "off"],
2032
2212
  description: "Enable or disable command-notify delivery globally.",
2033
2213
  },
2034
2214
  buildPiNotifyEventLauncherChoice(
@@ -2043,9 +2223,9 @@ function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuCho
2043
2223
  },
2044
2224
  {
2045
2225
  id: "selected-sound-command",
2046
- label: "Enable sound",
2226
+ label: PI_NOTIFY_BOOT_SOUND_LABEL,
2047
2227
  value: config["notify-sound"],
2048
- description: "Select which sound command level is currently active.",
2228
+ description: "Edit the persisted boot sound level without changing the active runtime sound level.",
2049
2229
  },
2050
2230
  buildPiNotifyEventLauncherChoice(
2051
2231
  config,
@@ -2055,25 +2235,25 @@ function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuCho
2055
2235
  id: "sound-toggle-hotkey-bind",
2056
2236
  label: "Sound toggle hotkey bind",
2057
2237
  value: config["notify-sound-toggle-shortcut"],
2058
- description: "Edit the keyboard shortcut that cycles the selected sound command.",
2238
+ description: "Edit the keyboard shortcut that cycles the active runtime sound level.",
2059
2239
  },
2060
2240
  {
2061
2241
  id: "sound-command-low",
2062
2242
  label: "Sound command (low vol.)",
2063
2243
  value: config.PI_NOTIFY_SOUND_LOW_CMD,
2064
- description: "Edit the shell command used when the selected sound command is `low`.",
2244
+ description: "Edit the shell command used when the active runtime sound level is `low`.",
2065
2245
  },
2066
2246
  {
2067
2247
  id: "sound-command-mid",
2068
2248
  label: "Sound command (mid vol.)",
2069
2249
  value: config.PI_NOTIFY_SOUND_MID_CMD,
2070
- description: "Edit the shell command used when the selected sound command is `mid`.",
2250
+ description: "Edit the shell command used when the active runtime sound level is `mid`.",
2071
2251
  },
2072
2252
  {
2073
2253
  id: "sound-command-high",
2074
2254
  label: "Sound command (high vol.)",
2075
2255
  value: config.PI_NOTIFY_SOUND_HIGH_CMD,
2076
- description: "Edit the shell command used when the selected sound command is `high`.",
2256
+ description: "Edit the shell command used when the active runtime sound level is `high`.",
2077
2257
  },
2078
2258
  ...buildPiNotifyPushoverRows(config),
2079
2259
  ...buildTerminalSettingsMenuChoices({
@@ -2083,44 +2263,44 @@ function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuCho
2083
2263
  }
2084
2264
 
2085
2265
  /**
2086
- * @brief Opens the shared settings-menu selector for the active sound level.
2087
- * @details Reuses the pi-usereq settings-menu renderer so sound-level selection remains stylistically aligned with the notification menu and appends a value-less subtree-local `Reset defaults` row. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
2266
+ * @brief Opens the shared settings-menu selector for the persisted boot sound level.
2267
+ * @details Reuses the pi-usereq settings-menu renderer so boot-sound selection remains stylistically aligned with the notification menu, keeps the active runtime sound level unchanged, and appends a value-less subtree-local `Reset defaults` row. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
2088
2268
  * @param[in] ctx {ExtensionCommandContext} Active command context.
2089
- * @param[in] currentLevel {PiNotifySoundLevel} Currently selected sound level.
2090
- * @return {Promise<PiNotifySoundLevel | "reset-defaults" | undefined>} Selected sound level, reset action, or `undefined` when cancelled.
2091
- * @satisfies REQ-131, REQ-179, REQ-192
2269
+ * @param[in] currentLevel {PiNotifySoundLevel} Persisted boot sound level.
2270
+ * @return {Promise<PiNotifySoundLevel | "reset-defaults" | undefined>} Selected boot sound level, reset action, or `undefined` when cancelled.
2271
+ * @satisfies REQ-131, REQ-179, REQ-192, REQ-289
2092
2272
  */
2093
2273
  async function selectPiNotifySoundLevel(
2094
2274
  ctx: ExtensionCommandContext,
2095
2275
  currentLevel: PiNotifySoundLevel,
2096
2276
  ): Promise<PiNotifySoundLevel | "reset-defaults" | undefined> {
2097
- const choice = await showPiUsereqSettingsMenu(ctx, "Enable sound", [
2277
+ const choice = await showPiUsereqSettingsMenu(ctx, PI_NOTIFY_BOOT_SOUND_LABEL, [
2098
2278
  {
2099
2279
  id: "none",
2100
2280
  label: "none",
2101
2281
  value: currentLevel === "none" ? "selected" : "",
2102
- description: "Disable sound-command delivery while preserving per-event sound toggles.",
2282
+ description: "Persist `none` as the boot sound level loaded during the next session start.",
2103
2283
  },
2104
2284
  {
2105
2285
  id: "low",
2106
2286
  label: "low",
2107
2287
  value: currentLevel === "low" ? "selected" : "",
2108
- description: "Use the low-volume sound command when sound delivery is enabled for the current event.",
2288
+ description: "Persist `low` as the boot sound level loaded during the next session start.",
2109
2289
  },
2110
2290
  {
2111
2291
  id: "mid",
2112
2292
  label: "mid",
2113
2293
  value: currentLevel === "mid" ? "selected" : "",
2114
- description: "Use the mid-volume sound command when sound delivery is enabled for the current event.",
2294
+ description: "Persist `mid` as the boot sound level loaded during the next session start.",
2115
2295
  },
2116
2296
  {
2117
2297
  id: "high",
2118
2298
  label: "high",
2119
2299
  value: currentLevel === "high" ? "selected" : "",
2120
- description: "Use the high-volume sound command when sound delivery is enabled for the current event.",
2300
+ description: "Persist `high` as the boot sound level loaded during the next session start.",
2121
2301
  },
2122
2302
  ...buildTerminalSettingsMenuChoices({
2123
- resetDefaultsDescription: "Restore the documented default sound level.",
2303
+ resetDefaultsDescription: "Restore the documented default boot sound level.",
2124
2304
  }),
2125
2305
  ], { initialSelectedId: currentLevel });
2126
2306
  if (!choice) {
@@ -2134,11 +2314,11 @@ async function selectPiNotifySoundLevel(
2134
2314
 
2135
2315
  /**
2136
2316
  * @brief Runs the interactive notification-configuration menu.
2137
- * @details Exposes command-notify, sound, and Pushover controls through the shared settings-menu renderer, delegates completed/interrupted/failed toggles to dedicated event submenus, 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.
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.
2138
2318
  * @param[in] ctx {ExtensionCommandContext} Active command context.
2139
2319
  * @param[in,out] config {UseReqConfig} Mutable configuration object.
2140
2320
  * @return {Promise<boolean>} `true` when the sound-toggle shortcut changed.
2141
- * @satisfies REQ-131, REQ-133, REQ-134, REQ-137, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-184, REQ-188, REQ-192, REQ-193, REQ-195, REQ-196, REQ-198, REQ-234, REQ-235
2321
+ * @satisfies REQ-131, REQ-133, REQ-134, REQ-137, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-184, REQ-188, REQ-192, REQ-193, REQ-195, REQ-196, REQ-198, REQ-234, REQ-235, REQ-288, REQ-289
2142
2322
  */
2143
2323
  async function configurePiNotifyMenu(
2144
2324
  ctx: ExtensionCommandContext,
@@ -2152,7 +2332,27 @@ async function configurePiNotifyMenu(
2152
2332
  ctx,
2153
2333
  "Notifications",
2154
2334
  buildPiNotifyMenuChoices(config),
2155
- { initialSelectedId: focusedChoiceId },
2335
+ {
2336
+ initialSelectedId: focusedChoiceId,
2337
+ getChoices: () => buildPiNotifyMenuChoices(config),
2338
+ onChange: (choiceId, newValue) => {
2339
+ if (choiceId === "notify-enabled" || choiceId === "notify-pushover-enabled") {
2340
+ if (choiceId === "notify-pushover-enabled" && !hasPiNotifyPushoverCredentials(config)) {
2341
+ config["notify-pushover-enabled"] = false;
2342
+ ctx.ui.notify("Populate both Pushover credential fields before enabling Pushover", "info");
2343
+ return;
2344
+ }
2345
+ const enabled = newValue === "on";
2346
+ config[choiceId as PiNotifyBooleanConfigKey] = enabled;
2347
+ onConfigChange();
2348
+ const labelMap: Record<string, string> = {
2349
+ "notify-enabled": "Notification",
2350
+ "notify-pushover-enabled": "Pushover",
2351
+ };
2352
+ ctx.ui.notify(`${labelMap[choiceId]} ${enabled ? "enabled" : "disabled"}`, "info");
2353
+ }
2354
+ },
2355
+ },
2156
2356
  );
2157
2357
  if (!choice) {
2158
2358
  return config["notify-sound-toggle-shortcut"] !== originalShortcut;
@@ -2216,22 +2416,22 @@ async function configurePiNotifyMenu(
2216
2416
  const approved = await confirmResetChanges(
2217
2417
  ctx,
2218
2418
  "Confirm sound reset",
2219
- [{ label: "Enable sound", previousValue: config["notify-sound"], nextValue: defaultSoundLevel }]
2419
+ [{ label: PI_NOTIFY_BOOT_SOUND_LABEL, previousValue: config["notify-sound"], nextValue: defaultSoundLevel }]
2220
2420
  .filter((change) => change.previousValue !== change.nextValue),
2221
- "Approve restoring the documented default sound level.",
2222
- "Abort the sound reset and keep the current value.",
2421
+ "Approve restoring the documented default boot sound level.",
2422
+ "Abort the boot sound reset and keep the current value.",
2223
2423
  );
2224
2424
  if (!approved) {
2225
- ctx.ui.notify("Aborted sound reset", "info");
2425
+ ctx.ui.notify("Aborted boot sound reset", "info");
2226
2426
  } else {
2227
2427
  config["notify-sound"] = defaultSoundLevel;
2228
2428
  onConfigChange();
2229
- ctx.ui.notify("Restored default sound level", "info");
2429
+ ctx.ui.notify("Restored default boot sound level; active runtime sound is unchanged", "info");
2230
2430
  }
2231
2431
  } else if (nextLevel !== undefined) {
2232
2432
  config["notify-sound"] = nextLevel;
2233
2433
  onConfigChange();
2234
- ctx.ui.notify(`Enable sound set to ${nextLevel}`, "info");
2434
+ ctx.ui.notify(`Stored ${PI_NOTIFY_BOOT_SOUND_LABEL.toLowerCase()} as ${nextLevel}; active runtime sound is unchanged`, "info");
2235
2435
  }
2236
2436
  continue;
2237
2437
  }
@@ -2376,7 +2576,7 @@ async function configurePiNotifyMenu(
2376
2576
  const defaults = getDefaultConfig("");
2377
2577
  const resetPreview: ResetConfirmationChange[] = [
2378
2578
  { label: "Enable notification", previousValue: formatPiNotifyStatus(config), nextValue: formatPiNotifyStatus(defaults) },
2379
- { label: "Enable sound", previousValue: config["notify-sound"], nextValue: defaults["notify-sound"] },
2579
+ { label: PI_NOTIFY_BOOT_SOUND_LABEL, previousValue: config["notify-sound"], nextValue: defaults["notify-sound"] },
2380
2580
  { label: "Sound toggle hotkey bind", previousValue: config["notify-sound-toggle-shortcut"], nextValue: defaults["notify-sound-toggle-shortcut"] },
2381
2581
  { label: "Notify command", previousValue: config.PI_NOTIFY_CMD, nextValue: defaults.PI_NOTIFY_CMD },
2382
2582
  { label: "Sound command (low vol.)", previousValue: config.PI_NOTIFY_SOUND_LOW_CMD, nextValue: defaults.PI_NOTIFY_SOUND_LOW_CMD },
@@ -2411,15 +2611,15 @@ async function configurePiNotifyMenu(
2411
2611
  /**
2412
2612
  * @brief Registers the configurable notification-sound shortcut when supported.
2413
2613
  * @details Loads the current project config, registers one raw pi shortcut when
2414
- * the runtime exposes `registerShortcut(...)`, cycles persisted sound state on
2415
- * invocation, saves the config, refreshes the status bar, and emits one info
2416
- * notification. Runtime is O(1) for registration plus config I/O per shortcut
2417
- * use. Side effects include shortcut registration, config writes, and status
2418
- * updates.
2614
+ * 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
2617
+ * plus one status update per shortcut use. Side effects include shortcut
2618
+ * registration and status updates.
2419
2619
  * @param[in] pi {ExtensionAPI} Active extension API instance.
2420
2620
  * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
2421
2621
  * @return {void} No return value.
2422
- * @satisfies REQ-131, REQ-134, REQ-180
2622
+ * @satisfies REQ-134, REQ-180, REQ-286, REQ-287
2423
2623
  */
2424
2624
  function registerPiNotifyShortcut(
2425
2625
  pi: ExtensionAPI,
@@ -2433,19 +2633,170 @@ function registerPiNotifyShortcut(
2433
2633
  shortcutRegistrar.registerShortcut(config["notify-sound-toggle-shortcut"], {
2434
2634
  description: "Cycle pi-usereq notification sound level",
2435
2635
  handler: async (ctx) => {
2436
- const nextConfig = loadProjectConfig(ctx.cwd);
2437
- nextConfig["notify-sound"] = cyclePiNotifySoundLevel(nextConfig["notify-sound"]);
2438
- saveProjectConfig(ctx.cwd, nextConfig);
2439
- setPiUsereqStatusConfig(statusController, nextConfig);
2440
- renderPiUsereqStatus(statusController, ctx);
2441
- ctx.ui.notify(`pi-usereq sound:${nextConfig["notify-sound"]}`, "info");
2636
+ const nextRuntimeSoundLevel = cyclePiNotifySoundLevel(
2637
+ getPiUsereqRuntimeSoundLevel(statusController),
2638
+ );
2639
+ setPiUsereqRuntimeSoundLevel(
2640
+ statusController,
2641
+ nextRuntimeSoundLevel,
2642
+ ctx,
2643
+ );
2644
+ ctx.ui.notify(`pi-usereq sound:${nextRuntimeSoundLevel}`, "info");
2442
2645
  },
2443
2646
  });
2444
2647
  }
2445
2648
 
2446
2649
  /**
2447
- * @brief Registers bundled prompt commands with the extension.
2448
- * @details Creates one `req-<prompt>` command per bundled prompt name. Each handler rejects non-`idle` workflow state, transitions the shared workflow state through `checking`, `error`, and `running`, runs dedicated prompt-command git and required-doc preflight checks, optionally prepares a dedicated worktree execution plan using the active session directory, persists the prompt metadata needed for switch-triggered rebinding, switches the active session to the verified execution cwd before prompt handoff, logs dedicated workflow-activation diagnostics, renders the prompt, starts prompt delivery into the forked active session, records `running` immediately after delivery handoff begins, and then awaits the wrapped prompt-delivery promise whose stale post-restore rejections are suppressed. Runtime is O(p) for registration; handler cost depends on prompt preflight, worktree preparation, session switching, prompt rendering, prompt dispatch, and optional debug logging. Side effects include command registration, status-controller mutation, worktree creation, active-session replacement, optional worktree rollback, user-message delivery during execution, and optional debug-log writes.
2650
+ * @brief Resolves the prompt execution plan targeted by `req-reset` recovery.
2651
+ * @details Prefers the current in-memory active request, then the current in-memory pending request, then the process-scoped persisted prompt runtime state so the dedicated reset command can recover from same-host unclean prompt termination after session replacement. Runtime is O(1). No external state is mutated.
2652
+ * @param[in] statusController {PiUsereqStatusController} Mutable status controller.
2653
+ * @return {PromptCommandExecutionPlan | undefined} Recoverable prompt execution plan when one remains available.
2654
+ */
2655
+ function resolveReqResetPromptRequest(
2656
+ statusController: PiUsereqStatusController,
2657
+ ): PromptCommandExecutionPlan | undefined {
2658
+ const isWorktreeBacked = (request: PromptCommandExecutionPlan | undefined): request is PromptCommandExecutionPlan =>
2659
+ request?.worktreeDir !== undefined
2660
+ && request.worktreeRootPath !== undefined
2661
+ && request.worktreePath !== undefined;
2662
+ const inMemoryActiveRequest = statusController.state.activePromptRequest;
2663
+ if (isWorktreeBacked(inMemoryActiveRequest)) {
2664
+ return inMemoryActiveRequest;
2665
+ }
2666
+ const inMemoryPendingRequest = statusController.state.pendingPromptRequest;
2667
+ if (isWorktreeBacked(inMemoryPendingRequest)) {
2668
+ return inMemoryPendingRequest;
2669
+ }
2670
+ const persistedRuntimeState = readPersistedPromptCommandRuntimeState();
2671
+ if (isWorktreeBacked(persistedRuntimeState.activePromptRequest)) {
2672
+ return persistedRuntimeState.activePromptRequest;
2673
+ }
2674
+ if (isWorktreeBacked(persistedRuntimeState.pendingPromptRequest)) {
2675
+ return persistedRuntimeState.pendingPromptRequest;
2676
+ }
2677
+ return inMemoryActiveRequest
2678
+ ?? inMemoryPendingRequest
2679
+ ?? persistedRuntimeState.activePromptRequest
2680
+ ?? persistedRuntimeState.pendingPromptRequest;
2681
+ }
2682
+
2683
+ /**
2684
+ * @brief Registers the specialized `req-reset` slash command.
2685
+ * @details Registers the non-agentic prompt-recovery command that accepts any current workflow state, reuses persisted prompt runtime state when available, restores the original session-backed `base-path`, force-removes matching generated worktrees plus branches, clears recoverable prompt state when restoration succeeds, and notifies pi without starting an LLM session or creating a worktree. Runtime is dominated by session restoration plus git cleanup. Side effects include command registration, status-controller mutation, active-session replacement, worktree deletion, branch deletion, and user notifications.
2686
+ * @param[in] pi {ExtensionAPI} Active extension API instance.
2687
+ * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
2688
+ * @return {void} No return value.
2689
+ * @satisfies REQ-304, REQ-305, REQ-306, REQ-307, REQ-308, REQ-309, REQ-310, REQ-311, REQ-312, REQ-313
2690
+ */
2691
+ function registerReqResetCommand(
2692
+ pi: ExtensionAPI,
2693
+ statusController: PiUsereqStatusController,
2694
+ ): void {
2695
+ pi.registerCommand("req-reset", {
2696
+ description: REQ_RESET_COMMAND_DESCRIPTION,
2697
+ handler: async (_args, ctx) => {
2698
+ const promptRequest = resolveReqResetPromptRequest(statusController);
2699
+ const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
2700
+ syncContextCwdMirror(ctx, commandCwd);
2701
+ const projectBase = path.resolve(promptRequest?.basePath ?? getProjectBase(commandCwd));
2702
+ bootstrapRuntimePathState(projectBase, {
2703
+ gitPath: resolveRuntimeGitPath(projectBase),
2704
+ });
2705
+ const config = loadProjectConfig(projectBase);
2706
+ let executionPlan: ReqResetCommandPlan;
2707
+ let executionResult: ReqResetCommandExecutionResult;
2708
+ let resetContext = ctx;
2709
+
2710
+ setPiUsereqStatusConfig(statusController, config);
2711
+ setPiUsereqWorkflowState(statusController, "running", ctx);
2712
+
2713
+ try {
2714
+ executionPlan = prepareReqResetCommandExecution(projectBase, config, promptRequest);
2715
+ executionResult = await executeReqResetCommandExecution(executionPlan, ctx);
2716
+ resetContext = (executionResult.activeContext ?? resetContext) as typeof ctx;
2717
+ } catch (error) {
2718
+ const message = error instanceof Error ? error.message : String(error);
2719
+ setPiUsereqWorkflowState(statusController, "error", resetContext);
2720
+ notifyContextSafely(resetContext, message, "error");
2721
+ throw error;
2722
+ }
2723
+
2724
+ const shouldClearPromptState = executionResult.restoredBasePath
2725
+ || executionPlan.promptRequest === undefined;
2726
+ if (shouldClearPromptState) {
2727
+ statusController.state.pendingPromptRequest = undefined;
2728
+ statusController.state.activePromptRequest = undefined;
2729
+ clearPersistedPromptCommandRuntimeState();
2730
+ }
2731
+
2732
+ if (executionResult.errorMessage) {
2733
+ setPiUsereqWorkflowState(statusController, "error", resetContext);
2734
+ notifyContextSafely(resetContext, executionResult.errorMessage, "error");
2735
+ throw new ReqError(executionResult.errorMessage, 1);
2736
+ }
2737
+
2738
+ setPiUsereqWorkflowState(statusController, "idle", resetContext);
2739
+ const successMessage = executionPlan.promptRequest !== undefined
2740
+ ? `SUCCESS: req-reset restored base-path and removed ${executionResult.removedWorktreeDirs.length} worktree(s) plus ${executionResult.removedBranchNames.length} branch(es).`
2741
+ : `SUCCESS: req-reset removed ${executionResult.removedWorktreeDirs.length} worktree(s) plus ${executionResult.removedBranchNames.length} branch(es) and restored idle state.`;
2742
+ notifyContextSafely(resetContext, successMessage, "info");
2743
+ },
2744
+ });
2745
+ }
2746
+
2747
+ /**
2748
+ * @brief Registers the specialized `req-references` slash command.
2749
+ * @details Registers the non-agentic references-maintenance command that rejects non-`idle` invocations by transitioning workflow state to `error` before direct execution, otherwise reuses slash-command-owned git validation, transitions workflow state through `checking|running|idle`, regenerates `REFERENCES.md` directly from configured source directories, stages only the generated file, creates the fixed-message git commit, verifies repository cleanliness, and notifies pi without starting an LLM session or creating a worktree. Runtime is dominated by git subprocess execution plus source-summary generation. Side effects include command registration, status-controller mutation, filesystem writes, git index/history mutation, and user notifications.
2750
+ * @param[in] pi {ExtensionAPI} Active extension API instance.
2751
+ * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
2752
+ * @return {void} No return value.
2753
+ * @satisfies REQ-200, REQ-221, REQ-224, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303
2754
+ */
2755
+ function registerReqReferencesCommand(
2756
+ pi: ExtensionAPI,
2757
+ statusController: PiUsereqStatusController,
2758
+ ): void {
2759
+ pi.registerCommand("req-references", {
2760
+ description: REQ_REFERENCES_COMMAND_DESCRIPTION,
2761
+ handler: async (_args, ctx) => {
2762
+ if (statusController.state.workflowState !== "idle") {
2763
+ rejectNonIdleReqCommand(statusController, ctx);
2764
+ }
2765
+ const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
2766
+ syncContextCwdMirror(ctx, commandCwd);
2767
+ bootstrapRuntimePathState(commandCwd, {
2768
+ gitPath: resolveRuntimeGitPath(commandCwd),
2769
+ });
2770
+ const projectBase = getProjectBase(commandCwd);
2771
+ const config = loadProjectConfig(commandCwd);
2772
+ setPiUsereqStatusConfig(statusController, config);
2773
+ statusController.state.pendingPromptRequest = undefined;
2774
+ statusController.state.activePromptRequest = undefined;
2775
+ setPiUsereqWorkflowState(statusController, "checking", ctx);
2776
+ try {
2777
+ const executionPlan = prepareReqReferencesCommandExecution(projectBase, config);
2778
+ setPiUsereqWorkflowState(statusController, "running", ctx);
2779
+ executeReqReferencesCommandExecution(executionPlan, config);
2780
+ setPiUsereqWorkflowState(statusController, "idle", ctx);
2781
+ ctx.ui.notify(
2782
+ `SUCCESS: Updated ${formatRuntimePathForDisplay(executionPlan.referencesPath)} and committed changes.`,
2783
+ "info",
2784
+ );
2785
+ } catch (error) {
2786
+ statusController.state.pendingPromptRequest = undefined;
2787
+ statusController.state.activePromptRequest = undefined;
2788
+ setPiUsereqWorkflowState(statusController, "error", ctx);
2789
+ const message = error instanceof Error ? error.message : String(error);
2790
+ ctx.ui.notify(message, "error");
2791
+ throw error;
2792
+ }
2793
+ },
2794
+ });
2795
+ }
2796
+
2797
+ /**
2798
+ * @brief Registers bundled prompt-backed commands with the extension.
2799
+ * @details Creates one prompt-template-backed `req-<prompt>` command per bundled prompt name. Each handler rejects non-`idle` workflow state by transitioning the shared workflow state to `error` before command-side preflight, otherwise transitions the shared workflow state through `checking`, `error`, and `running`, runs dedicated prompt-command git and required-doc preflight checks, optionally prepares a dedicated worktree execution plan using the active session directory, persists the prompt metadata needed for switch-triggered rebinding, switches the active session to the verified execution cwd before prompt handoff, logs dedicated workflow-activation diagnostics, renders the prompt, starts prompt delivery into the forked active session, records `running` immediately after delivery handoff begins, and then awaits the wrapped prompt-delivery promise whose stale post-restore rejections are suppressed. Runtime is O(p) for registration; handler cost depends on prompt preflight, worktree preparation, session switching, prompt rendering, prompt dispatch, and optional debug logging. Side effects include command registration, status-controller mutation, worktree creation, active-session replacement, optional worktree rollback, user-message delivery during execution, and optional debug-log writes.
2449
2800
  * @param[in] pi {ExtensionAPI} Active extension API instance.
2450
2801
  * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
2451
2802
  * @return {void} No return value.
@@ -2460,9 +2811,7 @@ function registerPromptCommands(
2460
2811
  description: resolvePromptCommandDescription(promptName),
2461
2812
  handler: async (args, ctx) => {
2462
2813
  if (statusController.state.workflowState !== "idle") {
2463
- const message = `ERROR: Prompt workflow state is ${statusController.state.workflowState}, expected idle.`;
2464
- ctx.ui.notify(message, "error");
2465
- throw new ReqError(message, 1);
2814
+ rejectNonIdleReqCommand(statusController, ctx, promptName);
2466
2815
  }
2467
2816
  const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
2468
2817
  syncContextCwdMirror(ctx, commandCwd);
@@ -2577,10 +2926,10 @@ function registerPromptCommands(
2577
2926
  * @details Defines the tool schemas, prompt metadata, and execution handlers that bridge extension tool calls into tool-runner operations without registering duplicate custom slash commands for the same capabilities. Runtime is O(t) for registration; execution cost depends on the selected tool. Side effects include tool registration.
2578
2927
  * @param[in] pi {ExtensionAPI} Active extension API instance.
2579
2928
  * @return {void} No return value.
2580
- * @satisfies REQ-005, REQ-010, REQ-011, REQ-014, REQ-017, REQ-044, REQ-069, REQ-070, REQ-071, REQ-072, REQ-073, REQ-074, REQ-075, REQ-076, REQ-077, REQ-078, REQ-079, REQ-080, REQ-089, REQ-090, REQ-091, REQ-092, REQ-093, REQ-094, REQ-095, REQ-096, REQ-097, REQ-098, REQ-099, REQ-100, REQ-101, REQ-102
2929
+ * @satisfies REQ-005, REQ-010, REQ-011, REQ-014, REQ-017, REQ-044, REQ-069, REQ-070, REQ-071, REQ-072, REQ-073, REQ-074, REQ-075, REQ-076, REQ-077, REQ-078, REQ-079, REQ-080, REQ-089, REQ-090, REQ-091, REQ-092, REQ-093, REQ-094, REQ-095, REQ-096, REQ-097, REQ-098, REQ-099, REQ-100, REQ-101, REQ-102, REQ-293, REQ-294, REQ-295, REQ-296, REQ-297
2581
2930
  */
2582
2931
  function registerAgentTools(pi: ExtensionAPI): void {
2583
- const filesReferencesSchema = Type.Object(
2932
+ const filesSummarizeSchema = Type.Object(
2584
2933
  {
2585
2934
  files: Type.Array(
2586
2935
  Type.String({ description: "Project-relative or absolute source file path resolved from the current working directory when not already absolute" }),
@@ -2632,21 +2981,21 @@ function registerAgentTools(pi: ExtensionAPI): void {
2632
2981
  });
2633
2982
 
2634
2983
  pi.registerTool({
2635
- name: "files-references",
2636
- label: "files-references",
2637
- description: "Scope: explicit source files. Return the monolithic references markdown report in content[0].text and keep only execution metadata in details.execution.",
2638
- promptSnippet: "Return the monolithic references markdown report for caller-selected source files.",
2984
+ name: "files-summarize",
2985
+ label: "files-summarize",
2986
+ description: "Scope: explicit source files. Return the monolithic summary markdown report in content[0].text and keep only execution metadata in details.execution.",
2987
+ promptSnippet: "Return the monolithic summary markdown report for caller-selected source files.",
2639
2988
  promptGuidelines: [
2640
2989
  "Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
2641
2990
  "Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
2642
- "Formatting contract: content matches the Python reference renderer used by `generate_markdown.py`.",
2991
+ "Formatting contract: content matches the Python summary renderer used by `generate_markdown.py`.",
2643
2992
  "Behavior contract: missing inputs, non-file inputs, unsupported extensions, and analysis failures surface through details.execution diagnostics.",
2644
2993
  ],
2645
- renderResult: buildStructuredToolRenderResult("files-references"),
2646
- parameters: filesReferencesSchema,
2994
+ renderResult: buildStructuredToolRenderResult("files-summarize"),
2995
+ parameters: filesSummarizeSchema,
2647
2996
  async execute(_toolCallId, params) {
2648
2997
  const contextPath = getRuntimeContextPath(process.cwd());
2649
- return executeMonolithicTool(() => runFilesReferences(params.files, contextPath));
2998
+ return executeMonolithicTool(() => runFilesSummarize(params.files, contextPath));
2650
2999
  },
2651
3000
  });
2652
3001
 
@@ -2717,12 +3066,18 @@ function registerAgentTools(pi: ExtensionAPI): void {
2717
3066
  },
2718
3067
  });
2719
3068
 
2720
- const referencesSchema = Type.Object(
3069
+ const summarizeSchema = Type.Object(
2721
3070
  {},
2722
3071
  {
2723
3072
  description: "Input contract: no params. Scope is the configured src-dir list resolved from the current project configuration. Output contract: monolithic markdown in content[0].text plus details.execution diagnostics.",
2724
3073
  },
2725
3074
  );
3075
+ const referencesSchema = Type.Object(
3076
+ {},
3077
+ {
3078
+ description: "Input contract: no params. Scope is the configured src-dir list plus configured docs-dir resolved from the current project configuration. Output contract: content[0].text returns only `success` or `error: <diagnostic>`; the tool overwrites `<docs-dir>/REFERENCES.md` and keeps details.execution diagnostics.",
3079
+ },
3080
+ );
2726
3081
  const tokensSchema = Type.Object(
2727
3082
  {},
2728
3083
  {
@@ -2731,23 +3086,44 @@ function registerAgentTools(pi: ExtensionAPI): void {
2731
3086
  );
2732
3087
 
2733
3088
  pi.registerTool({
2734
- name: "references",
2735
- label: "references",
2736
- description: "Scope: configured project source directories. Return the monolithic references markdown report in content[0].text and keep only execution metadata in details.execution.",
2737
- promptSnippet: "Return the monolithic project references markdown report from the configured source directories.",
3089
+ name: "summarize",
3090
+ label: "summarize",
3091
+ description: "Scope: configured project source directories. Return the monolithic summary markdown report in content[0].text and keep only execution metadata in details.execution.",
3092
+ promptSnippet: "Return the monolithic project summary markdown report from the configured source directories.",
2738
3093
  promptGuidelines: [
2739
3094
  "Scope: no params; resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.",
2740
3095
  "Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
2741
3096
  "Formatting contract: content prepends the file-structure markdown block before the per-file markdown produced by `generate_markdown.py`.",
2742
3097
  "Configuration contract: output changes with cwd-derived project config and src-dir values; the tool does not accept explicit file overrides.",
2743
3098
  ],
3099
+ renderResult: buildStructuredToolRenderResult("summarize"),
3100
+ parameters: summarizeSchema,
3101
+ async execute() {
3102
+ const contextPath = getRuntimeContextPath(process.cwd());
3103
+ const projectBase = getProjectBase(contextPath);
3104
+ const config = loadProjectConfig(projectBase);
3105
+ return executeMonolithicTool(() => runSummarize(contextPath, config));
3106
+ },
3107
+ });
3108
+
3109
+ pi.registerTool({
3110
+ name: "references",
3111
+ label: "references",
3112
+ description: "Scope: configured project source directories and configured docs-dir. Overwrite REFERENCES.md and return only success or error in content[0].text while keeping execution metadata in details.execution.",
3113
+ promptSnippet: "Generate REFERENCES.md from the configured source directories without returning the generated markdown.",
3114
+ promptGuidelines: [
3115
+ "Scope: no params; resolve src-dir and docs-dir from the current project configuration and current working directory.",
3116
+ "Behavior contract: generate the same file-structure-plus-summary markdown as `summarize` and overwrite `<docs-dir>/REFERENCES.md`.",
3117
+ "Output contract: `content[0].text` is `success` on success or `error: <diagnostic>` on failure; generated markdown is never returned to the LLM.",
3118
+ "Failure contract: source discovery, markdown generation, and file-write errors surface through the status text plus details.execution diagnostics.",
3119
+ ],
2744
3120
  renderResult: buildStructuredToolRenderResult("references"),
2745
3121
  parameters: referencesSchema,
2746
3122
  async execute() {
2747
3123
  const contextPath = getRuntimeContextPath(process.cwd());
2748
3124
  const projectBase = getProjectBase(contextPath);
2749
3125
  const config = loadProjectConfig(projectBase);
2750
- return executeMonolithicTool(() => runReferences(contextPath, config));
3126
+ return executeStatusTool(() => runReferences(contextPath, config));
2751
3127
  },
2752
3128
  });
2753
3129
 
@@ -2938,6 +3314,7 @@ function buildPiUsereqToolToggleChoices(pi: ExtensionAPI, config: UseReqConfig):
2938
3314
  id: tool.name,
2939
3315
  label: tool.name,
2940
3316
  value: enabledTools.has(tool.name) ? "on" : "off",
3317
+ values: ["on", "off"],
2941
3318
  description: tool.description ?? `Toggle startup activation for ${tool.name}.`,
2942
3319
  })),
2943
3320
  ...buildTerminalSettingsMenuChoices({
@@ -3012,7 +3389,29 @@ async function configurePiUsereqToolsMenu(
3012
3389
  }
3013
3390
 
3014
3391
  if (choice === "enable-tools") {
3015
- const selectedToolName = await showPiUsereqSettingsMenu(ctx, "Enable tools", buildPiUsereqToolToggleChoices(pi, config));
3392
+ const selectedToolName = await showPiUsereqSettingsMenu(ctx, "Enable tools", buildPiUsereqToolToggleChoices(pi, config), {
3393
+ getChoices: () => buildPiUsereqToolToggleChoices(pi, config),
3394
+ onChange: (toolName, newValue) => {
3395
+ const enabledTools = new Set(getConfiguredEnabledPiUsereqTools(config));
3396
+ if (newValue === "on") {
3397
+ enabledTools.add(toolName as PiUsereqStartupToolName);
3398
+ } else {
3399
+ enabledTools.delete(toolName as PiUsereqStartupToolName);
3400
+ }
3401
+ setConfiguredPiUsereqTools(
3402
+ pi,
3403
+ config,
3404
+ getPiUsereqStartupTools(pi)
3405
+ .map((tool) => tool.name)
3406
+ .filter((currentToolName) => enabledTools.has(currentToolName)),
3407
+ );
3408
+ onConfigChange();
3409
+ ctx.ui.notify(
3410
+ `${newValue === "on" ? "Enabled" : "Disabled"} ${toolName}`,
3411
+ "info",
3412
+ );
3413
+ },
3414
+ });
3016
3415
  if (!selectedToolName) {
3017
3416
  continue;
3018
3417
  }
@@ -3138,6 +3537,7 @@ function buildStaticCheckMenuChoices(config: UseReqConfig): PiUsereqSettingsMenu
3138
3537
  id: `toggle-static-check-language:${language}`,
3139
3538
  label: language,
3140
3539
  value: languageConfig.enabled === "enable" ? "on" : "off",
3540
+ values: ["on", "off"],
3141
3541
  description: `Toggle static-check execution for ${language}. Configured ${configuredCount} ${suffix}. Supported extensions: ${extensions.join(", ")}.`,
3142
3542
  };
3143
3543
  }),
@@ -3214,6 +3614,19 @@ async function configureStaticCheckMenu(
3214
3614
  while (true) {
3215
3615
  const staticChoice = await showPiUsereqSettingsMenu(ctx, "Language static code checkers", buildStaticCheckMenuChoices(config), {
3216
3616
  initialSelectedId: focusedChoiceId,
3617
+ getChoices: () => buildStaticCheckMenuChoices(config),
3618
+ onChange: (choiceId, newValue) => {
3619
+ if (choiceId.startsWith("toggle-static-check-language:")) {
3620
+ const language = choiceId.slice("toggle-static-check-language:".length);
3621
+ config["static-check"][language] ??= createStaticCheckLanguageConfig([]);
3622
+ config["static-check"][language]!.enabled = newValue === "on" ? "enable" : "disable";
3623
+ onConfigChange();
3624
+ ctx.ui.notify(
3625
+ `${newValue === "on" ? "Enabled" : "Disabled"} static-check for ${language}`,
3626
+ "info",
3627
+ );
3628
+ }
3629
+ },
3217
3630
  });
3218
3631
 
3219
3632
  if (!staticChoice) {
@@ -3380,6 +3793,7 @@ function buildPiUsereqMenuChoices(
3380
3793
  id: "auto-git-commit",
3381
3794
  label: "Auto git commit",
3382
3795
  value: config.AUTO_GIT_COMMIT,
3796
+ values: ["enable", "disable"],
3383
3797
  description: "Select bundled `git_commit.md` or `git_read-only.md` for `%%COMMIT%%`; disabling also forces prompt-command worktrees off.",
3384
3798
  },
3385
3799
  {
@@ -3388,6 +3802,8 @@ function buildPiUsereqMenuChoices(
3388
3802
  labelTone: autoGitCommitDisabled ? "dim" : undefined,
3389
3803
  value: effectiveGitWorktreeEnabled,
3390
3804
  valueTone: autoGitCommitDisabled ? "dim" : undefined,
3805
+ values: ["enable", "disable"],
3806
+ disabled: autoGitCommitDisabled,
3391
3807
  description: autoGitCommitDisabled
3392
3808
  ? "Forced to `disable` while `Auto git commit` is disabled."
3393
3809
  : "Enable or disable prompt-command worktree orchestration.",
@@ -3520,7 +3936,36 @@ async function configurePiUsereq(
3520
3936
  ctx,
3521
3937
  "pi-usereq",
3522
3938
  buildPiUsereqMenuChoices(ctx.cwd, config),
3523
- { initialSelectedId: focusedChoiceId },
3939
+ {
3940
+ initialSelectedId: focusedChoiceId,
3941
+ getChoices: () => buildPiUsereqMenuChoices(ctx.cwd, config),
3942
+ onChange: (choiceId, newValue) => {
3943
+ if (choiceId === "auto-git-commit") {
3944
+ config.AUTO_GIT_COMMIT = newValue === "enable" ? "enable" : "disable";
3945
+ if (config.AUTO_GIT_COMMIT === "disable") {
3946
+ config.GIT_WORKTREE_ENABLED = "disable";
3947
+ persistConfigChange();
3948
+ ctx.ui.notify("Auto git commit disabled; Git worktree forced off", "info");
3949
+ } else {
3950
+ persistConfigChange();
3951
+ ctx.ui.notify("Auto git commit enabled", "info");
3952
+ }
3953
+ return;
3954
+ }
3955
+ if (choiceId === "git-worktree-enabled") {
3956
+ if (config.AUTO_GIT_COMMIT === "disable") {
3957
+ ctx.ui.notify("Git worktree is locked while Auto git commit is disabled", "info");
3958
+ return;
3959
+ }
3960
+ config.GIT_WORKTREE_ENABLED = newValue === "enable" ? "enable" : "disable";
3961
+ persistConfigChange();
3962
+ ctx.ui.notify(
3963
+ `Git worktree ${resolveEffectiveGitWorktreeEnabled(config.AUTO_GIT_COMMIT, config.GIT_WORKTREE_ENABLED) === "enable" ? "enabled" : "disabled"}`,
3964
+ "info",
3965
+ );
3966
+ }
3967
+ },
3968
+ },
3524
3969
  );
3525
3970
  if (!choice) {
3526
3971
  if (config["notify-sound-toggle-shortcut"] !== initialShortcut) {
@@ -3740,23 +4185,16 @@ function registerConfigCommands(
3740
4185
 
3741
4186
  /**
3742
4187
  * @brief Registers the complete pi-usereq extension.
3743
- * @details Validates installation-owned bundled resources, registers prompt and
3744
- * configuration commands plus agent tools, registers the configurable
3745
- * notification-sound shortcut when the runtime supports shortcuts, and
3746
- * installs shared wrappers for all supported pi lifecycle hooks so status
3747
- * telemetry, context usage, prompt timing, cumulative runtime, prompt-specific
3748
- * Pushover metadata, tool-result debug logging, and prompt-orchestration debug
3749
- * effects remain synchronized with runtime events. Runtime is O(h) in hook
3750
- * count during registration. Side effects include filesystem reads,
3751
- * command/tool/shortcut registration, UI updates, active-tool changes,
3752
- * optional debug-log writes, and timer scheduling.
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.
3753
4189
  * @param[in] pi {ExtensionAPI} Active extension API instance.
3754
4190
  * @return {void} No return value.
3755
- * @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
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
3756
4192
  */
3757
4193
  export default function piUsereqExtension(pi: ExtensionAPI): void {
3758
4194
  const statusController = createPiUsereqStatusController();
3759
4195
  ensureBundledResourcesAccessible();
4196
+ registerReqResetCommand(pi, statusController);
4197
+ registerReqReferencesCommand(pi, statusController);
3760
4198
  registerPromptCommands(pi, statusController);
3761
4199
  registerAgentTools(pi);
3762
4200
  registerConfigCommands(pi, statusController);