pi-usereq 0.11.0 → 0.13.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 (53) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +6 -6
  3. package/package.json +1 -1
  4. package/pi-usereq/docs/REFERENCES.md +1135 -834
  5. package/pi-usereq/docs/REQUIREMENTS.md +177 -110
  6. package/pi-usereq/docs/WORKFLOW.md +244 -79
  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/config.ts +541 -180
  11. package/src/core/extension-status.ts +69 -12
  12. package/src/core/path-context.ts +19 -4
  13. package/src/core/pi-notify.ts +5 -5
  14. package/src/core/pi-usereq-tools.ts +4 -2
  15. package/src/core/prompt-command-catalog.ts +4 -5
  16. package/src/core/prompt-command-runtime.ts +183 -44
  17. package/src/core/prompts.ts +0 -2
  18. package/src/core/req-references-command.ts +175 -0
  19. package/src/core/req-reset-command.ts +323 -0
  20. package/src/core/resources.ts +6 -23
  21. package/src/core/settings-menu.ts +85 -28
  22. package/src/core/tool-runner.ts +26 -6
  23. package/src/index.ts +601 -116
  24. package/tests/attended-results-scenarios.ts +15 -9
  25. package/tests/cli-command-option-parity.test.ts +53 -35
  26. package/tests/debug-extension-harness.test.ts +8 -10
  27. package/tests/extension-registration.test.ts +1204 -205
  28. package/tests/helpers.ts +29 -6
  29. package/tests/oracle-project.test.ts +4 -4
  30. package/tests/oracle-standalone.test.ts +5 -5
  31. package/src/core/reference-payload.ts +0 -752
  32. package/src/resources/prompts/references.md +0 -64
  33. /package/tests/fixtures_attended_results/project/{references.json → summarize.json} +0 -0
  34. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_c.c.json +0 -0
  35. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_cpp.cpp.json +0 -0
  36. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_csharp.cs.json +0 -0
  37. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_elixir.ex.json +0 -0
  38. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_go.go.json +0 -0
  39. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_haskell.hs.json +0 -0
  40. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_java.java.json +0 -0
  41. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_javascript.js.json +0 -0
  42. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_kotlin.kt.json +0 -0
  43. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_lua.lua.json +0 -0
  44. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_perl.pl.json +0 -0
  45. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_php.php.json +0 -0
  46. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_python.py.json +0 -0
  47. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_ruby.rb.json +0 -0
  48. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_rust.rs.json +0 -0
  49. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_scala.scala.json +0 -0
  50. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_shell.sh.json +0 -0
  51. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_swift.swift.json +0 -0
  52. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_typescript.ts.json +0 -0
  53. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_zig.zig.json +0 -0
@@ -14,6 +14,7 @@ import type {
14
14
  ThemeColor,
15
15
  } from "@mariozechner/pi-coding-agent";
16
16
  import type { UseReqConfig } from "./config.js";
17
+ import type { PiNotifySoundLevel } from "./pi-notify.js";
17
18
  import type { PromptCommandExecutionPlan } from "./prompt-command-runtime.js";
18
19
  import {
19
20
  restorePersistedPromptCommandRuntimeStateForSession,
@@ -111,7 +112,7 @@ export type PiUsereqWorkflowState = "idle" | "checking" | "running" | "merging"
111
112
 
112
113
  /**
113
114
  * @brief Stores the mutable runtime facts displayed by the status bar.
114
- * @details Persists the prompt-orchestration workflow state, the latest context-usage snapshot, the active run start timestamp, the most recent normally completed run duration, the accumulated duration of all normally completed runs, and prompt-request metadata carried from command dispatch into the next runtime execution. Runtime state is mutated in-place by controller helpers. Compile-time only and introduces no runtime cost.
115
+ * @details Persists the prompt-orchestration workflow state, the latest context-usage snapshot, the active run start timestamp, the most recent normally completed run duration, the accumulated duration of all normally completed runs, the in-memory runtime sound level, and prompt-request metadata carried from command dispatch into the next runtime execution. Runtime state is mutated in-place by controller helpers. Compile-time only and introduces no runtime cost.
115
116
  */
116
117
  export interface PiUsereqStatusState {
117
118
  workflowState: PiUsereqWorkflowState;
@@ -119,6 +120,7 @@ export interface PiUsereqStatusState {
119
120
  runStartTimeMs: number | undefined;
120
121
  lastRunDurationMs: number | undefined;
121
122
  totalRunDurationMs: number | undefined;
123
+ runtimeSoundLevel: PiNotifySoundLevel | undefined;
122
124
  pendingPromptRequest: PiUsereqPromptRequest | undefined;
123
125
  activePromptRequest: PiUsereqPromptRequest | undefined;
124
126
  }
@@ -403,7 +405,7 @@ function resolveContextUsageIconText(
403
405
 
404
406
  /**
405
407
  * @brief Formats one icon-based context-usage gauge.
406
- * @details Renders the documented gauge icon with theme `error` for `>=90%`, enables terminal blink only for `>=100%`, and otherwise leaves the gauge in the default terminal color. Runtime is O(1). No external state is mutated.
408
+ * @details Renders the documented gauge icon with the same non-error status-value theme token used by `status` below `90%`, switches to theme `error` for `>=90%`, and enables terminal blink only for `>=100%`. Runtime is O(1). No external state is mutated.
407
409
  * @param[in] theme {StatusThemeAdapter} Normalized status theme.
408
410
  * @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
409
411
  * @return {string} Rendered fixed-width gauge icon.
@@ -420,7 +422,7 @@ function formatContextUsageBar(
420
422
  if (percent >= 90) {
421
423
  return theme.colorize("error", "▕█▏");
422
424
  }
423
- return resolveContextUsageIconText(contextUsage);
425
+ return theme.value(resolveContextUsageIconText(contextUsage));
424
426
  }
425
427
 
426
428
  /**
@@ -548,9 +550,24 @@ function didAgentEndAbort(messages: AgentEndEvent["messages"]): boolean {
548
550
  );
549
551
  }
550
552
 
553
+ /**
554
+ * @brief Resolves the active runtime sound level used by status and notify flows.
555
+ * @details Prefers the mutable runtime sound state, then falls back to the cached persisted boot value, and finally defaults to `none` before `session_start` loads configuration. Runtime is O(1). No external state is mutated.
556
+ * @param[in] state {PiUsereqStatusState} Mutable status state snapshot.
557
+ * @param[in] config {UseReqConfig | undefined} Cached project configuration.
558
+ * @return {PiNotifySoundLevel} Active runtime sound level.
559
+ * @satisfies REQ-180, REQ-285
560
+ */
561
+ function resolvePiUsereqRuntimeSoundLevel(
562
+ state: PiUsereqStatusState,
563
+ config: UseReqConfig | undefined,
564
+ ): PiNotifySoundLevel {
565
+ return state.runtimeSoundLevel ?? config?.["notify-sound"] ?? "none";
566
+ }
567
+
551
568
  /**
552
569
  * @brief Builds the full single-line pi-usereq status-bar payload.
553
- * @details Renders status, branch, context, elapsed, and sound fields in the canonical order with dim bullet separators, workflow-state highlighting, and the documented icon-based context gauge. Runtime is O(1). No external state is mutated.
570
+ * @details Renders status, branch, context, elapsed, and sound fields in the canonical order with dim bullet separators, workflow-state highlighting, the documented icon-based context gauge, and the active runtime sound level instead of the persisted boot value. Runtime is O(1). No external state is mutated.
554
571
  * @param[in] config {UseReqConfig} Effective project configuration.
555
572
  * @param[in] theme {StatusThemeAdapter} Normalized status theme.
556
573
  * @param[in] state {PiUsereqStatusState} Mutable status state snapshot.
@@ -567,7 +584,7 @@ function buildPiUsereqStatusText(
567
584
  nowMs: number,
568
585
  ): string {
569
586
  const elapsedText = formatElapsedStatusValue(state, nowMs);
570
- const soundText = config["notify-sound"];
587
+ const soundText = resolvePiUsereqRuntimeSoundLevel(state, config);
571
588
  return [
572
589
  formatRenderedStatusField(
573
590
  theme,
@@ -630,7 +647,7 @@ function syncPiUsereqStatusTicker(
630
647
 
631
648
  /**
632
649
  * @brief Creates an empty pi-usereq status controller.
633
- * @details Initializes the mutable status snapshot, including empty prompt-request tracking, and starts with no config, no context, and no live ticker. Runtime is O(1). No external state is mutated.
650
+ * @details Initializes the mutable status snapshot, including empty prompt-request tracking and an unset runtime sound level that later loads from persisted config during `session_start`, and starts with no config, no context, and no live ticker. Runtime is O(1). No external state is mutated.
634
651
  * @return {PiUsereqStatusController} New status controller.
635
652
  * @satisfies DES-010
636
653
  */
@@ -644,6 +661,7 @@ export function createPiUsereqStatusController(): PiUsereqStatusController {
644
661
  runStartTimeMs: undefined,
645
662
  lastRunDurationMs: undefined,
646
663
  totalRunDurationMs: undefined,
664
+ runtimeSoundLevel: undefined,
647
665
  pendingPromptRequest: undefined,
648
666
  activePromptRequest: undefined,
649
667
  },
@@ -654,8 +672,9 @@ export function createPiUsereqStatusController(): PiUsereqStatusController {
654
672
  /**
655
673
  * @brief Stores the effective project configuration used by status rendering.
656
674
  * @details Replaces the controller's cached configuration so later status
657
- * renders reuse the latest docs, tests, source-path, and pi-notify values
658
- * without reading from disk on every event. Runtime is O(1). Side effect:
675
+ * renders reuse the latest docs, tests, source-path, and persisted pi-notify
676
+ * values without reading from disk on every event, while leaving the active
677
+ * runtime sound level in `controller.state`. Runtime is O(1). Side effect:
659
678
  * mutates `controller.config`.
660
679
  * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
661
680
  * @param[in] config {UseReqConfig} Effective project configuration.
@@ -668,13 +687,47 @@ export function setPiUsereqStatusConfig(
668
687
  controller.config = config;
669
688
  }
670
689
 
690
+ /**
691
+ * @brief Returns the active runtime sound level tracked by the status controller.
692
+ * @details Exposes the in-memory runtime sound state so shortcut handlers and prompt-end notification dispatch can stay decoupled from the persisted boot value stored in global configuration. Runtime is O(1). No external state is mutated.
693
+ * @param[in] controller {PiUsereqStatusController} Mutable status controller.
694
+ * @return {PiNotifySoundLevel} Active runtime sound level.
695
+ * @satisfies REQ-180, REQ-285
696
+ */
697
+ export function getPiUsereqRuntimeSoundLevel(
698
+ controller: PiUsereqStatusController,
699
+ ): PiNotifySoundLevel {
700
+ return resolvePiUsereqRuntimeSoundLevel(controller.state, controller.config);
701
+ }
702
+
703
+ /**
704
+ * @brief Stores one new runtime sound level and refreshes the status bar.
705
+ * @details Mutates only the in-memory runtime sound state so shortcut-driven sound changes do not update persisted local or global configuration, then re-renders the footer when an active extension context is available. Runtime is O(1). Side effect: mutates `controller.state.runtimeSoundLevel` and may update `ctx.ui` status.
706
+ * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
707
+ * @param[in] runtimeSoundLevel {PiNotifySoundLevel} Next active runtime sound level.
708
+ * @param[in] ctx {ExtensionContext | undefined} Optional active extension context.
709
+ * @return {void} No return value.
710
+ * @satisfies REQ-180, REQ-286, REQ-287
711
+ */
712
+ export function setPiUsereqRuntimeSoundLevel(
713
+ controller: PiUsereqStatusController,
714
+ runtimeSoundLevel: PiNotifySoundLevel,
715
+ ctx?: ExtensionContext,
716
+ ): void {
717
+ controller.state.runtimeSoundLevel = runtimeSoundLevel;
718
+ const renderContext = ctx ?? controller.latestContext;
719
+ if (renderContext) {
720
+ renderPiUsereqStatus(controller, renderContext);
721
+ }
722
+ }
723
+
671
724
  /**
672
725
  * @brief Renders the current pi-usereq status bar into the active UI context.
673
- * @details Updates the controller's latest context pointer and writes the single-line status text only when configuration is available, including the active branch field and documented icon-based context gauge. When pi has already invalidated the supplied context after session replacement or reload, the helper clears the stale cached context and returns without surfacing the stale-instance exception. Runtime is O(1) plus git execution for branch refresh. Side effect: mutates `ctx.ui` status when the context is still active.
726
+ * @details Updates the controller's latest context pointer, refreshes the live `getContextUsage()` snapshot for direct render call sites that do not pass through `updateExtensionStatus(...)`, and writes the single-line status text only when configuration is available, including the active branch field, documented icon-based context gauge, and active runtime sound level. When pi has already invalidated the supplied context after session replacement or reload, the helper clears the stale cached context and returns without surfacing the stale-instance exception. Runtime is O(1) plus git execution for branch refresh. Side effect: mutates `controller.state.contextUsage` and `ctx.ui` status when the context is still active.
674
727
  * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
675
728
  * @param[in] ctx {ExtensionContext} Active extension context.
676
729
  * @return {void} No return value.
677
- * @satisfies REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-159, REQ-180, REQ-233, REQ-280, REQ-283, REQ-284
730
+ * @satisfies REQ-118, REQ-119, REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-159, REQ-180, REQ-233, REQ-280, REQ-283, REQ-284
678
731
  */
679
732
  export function renderPiUsereqStatus(
680
733
  controller: PiUsereqStatusController,
@@ -685,6 +738,7 @@ export function renderPiUsereqStatus(
685
738
  return;
686
739
  }
687
740
  try {
741
+ refreshContextUsage(controller.state, ctx);
688
742
  const theme = createStatusThemeAdapter(ctx.ui.theme as RawStatusTheme);
689
743
  const branchName = resolveStatusBranchValue(ctx);
690
744
  ctx.ui.setStatus(
@@ -732,13 +786,13 @@ export function setPiUsereqWorkflowState(
732
786
 
733
787
  /**
734
788
  * @brief Updates mutable status state for one intercepted lifecycle hook.
735
- * @details Refreshes stored context usage on every hook, resets or restores persisted elapsed counters during `session_start`, restores persisted prompt-command metadata when the active session matches a forked execution session, resynchronizes that metadata on later lifecycle hooks so post-switch workflow transitions performed by the initiating command handler become visible to the replacement-session runtime, resets workflow state to `idle` for documented session-start reasons, starts run timing on `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end`, accumulates successful runtime into `Σ`, preserves in-memory prompt-command state plus process-scoped persistence across switch-triggered `session_shutdown`, tolerates stale post-replacement render contexts, synchronizes the live ticker, and re-renders the status bar when configuration is available. Runtime is O(n) in `agent_end` message count and otherwise O(1). Side effects include in-memory state mutation, interval scheduling, process-scoped persistence mutation, and footer-status updates.
789
+ * @details Refreshes stored context usage on every hook, resets or restores persisted elapsed counters during `session_start`, loads the active runtime sound level from persisted config during `session_start`, restores persisted prompt-command metadata when the active session matches a forked execution session, resynchronizes that metadata on later lifecycle hooks so post-switch workflow transitions performed by the initiating command handler become visible to the replacement-session runtime, resets workflow state to `idle` for documented session-start reasons, starts run timing on `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end`, accumulates successful runtime into `Σ`, preserves in-memory prompt-command state plus process-scoped persistence across switch-triggered `session_shutdown`, tolerates stale post-replacement render contexts, synchronizes the live ticker, and re-renders the status bar when configuration is available. Runtime is O(n) in `agent_end` message count and otherwise O(1). Side effects include in-memory state mutation, interval scheduling, process-scoped persistence mutation, and footer-status updates.
736
790
  * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
737
791
  * @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
738
792
  * @param[in] event {unknown} Hook payload forwarded from the wrapper.
739
793
  * @param[in] ctx {ExtensionContext} Active extension context.
740
794
  * @return {void} No return value.
741
- * @satisfies REQ-009, REQ-117, REQ-118, REQ-119, REQ-123, REQ-124, REQ-125, REQ-159, REQ-169, REQ-217, REQ-221, REQ-278, REQ-279, REQ-280
795
+ * @satisfies REQ-009, REQ-117, REQ-118, REQ-119, REQ-123, REQ-124, REQ-125, REQ-159, REQ-169, REQ-217, REQ-221, REQ-278, REQ-279, REQ-280, REQ-285
742
796
  */
743
797
  export function updateExtensionStatus(
744
798
  controller: PiUsereqStatusController,
@@ -757,6 +811,9 @@ export function updateExtensionStatus(
757
811
  } else {
758
812
  restorePersistedElapsedState(controller.state);
759
813
  }
814
+ controller.state.runtimeSoundLevel = controller.config?.["notify-sound"]
815
+ ?? controller.state.runtimeSoundLevel
816
+ ?? "none";
760
817
  const shouldResetWorkflowState = shouldResetWorkflowStateOnSessionStart(event);
761
818
  if (shouldResetWorkflowState) {
762
819
  controller.state.workflowState = "idle";
@@ -11,11 +11,17 @@ import { fileURLToPath } from "node:url";
11
11
  import type { UseReqConfig } from "./config.js";
12
12
 
13
13
  /**
14
- * @brief Defines the per-project configuration file name.
15
- * @details The file lives directly under `base-path` and stores only persisted project configuration. Access complexity is O(1).
14
+ * @brief Defines the per-project local configuration file name.
15
+ * @details The file lives directly under `base-path` and stores only persisted project-scoped configuration. Access complexity is O(1).
16
16
  */
17
17
  export const PROJECT_CONFIG_FILENAME = ".pi-usereq.json";
18
18
 
19
+ /**
20
+ * @brief Defines the home-relative global configuration file path.
21
+ * @details The path is resolved against the current user home directory and stores persisted cross-project configuration. Access complexity is O(1).
22
+ */
23
+ export const GLOBAL_CONFIG_RELATIVE_PATH = ".config/pi-usereq/config.json";
24
+
19
25
  /**
20
26
  * @brief Defines the bundled resources directory name under the installation path.
21
27
  * @details The directory contains prompts, templates, and guidelines shipped with the installed extension payload. Access complexity is O(1).
@@ -155,15 +161,24 @@ export function normalizeRelativeDirContract(value: string): string {
155
161
  }
156
162
 
157
163
  /**
158
- * @brief Computes the absolute project config path for one base path.
164
+ * @brief Computes the absolute local project config path for one base path.
159
165
  * @details Appends `.pi-usereq.json` to the supplied base path using the canonical repository-local configuration layout. Runtime is O(1). No external state is mutated.
160
166
  * @param[in] basePath {string} Absolute or relative base path.
161
- * @return {string} Absolute config-file path.
167
+ * @return {string} Absolute local config-file path.
162
168
  */
163
169
  export function getConfigPath(basePath: string): string {
164
170
  return path.join(path.resolve(basePath), PROJECT_CONFIG_FILENAME);
165
171
  }
166
172
 
173
+ /**
174
+ * @brief Computes the absolute global config path for the current user.
175
+ * @details Resolves `~/.config/pi-usereq/config.json` from the current user home directory without consulting project state. Runtime is O(1). No external state is mutated.
176
+ * @return {string} Absolute global config-file path.
177
+ */
178
+ export function getGlobalConfigPath(): string {
179
+ return path.join(os.homedir(), GLOBAL_CONFIG_RELATIVE_PATH);
180
+ }
181
+
167
182
  /**
168
183
  * @brief Tests whether one path is identical to or an ancestor of another path.
169
184
  * @details Resolves both inputs, computes a relative traversal from the candidate ancestor to the candidate child, and accepts only exact matches or descendant traversals that stay within the ancestor subtree. Runtime is O(p) in path length. No external state is mutated.
@@ -110,7 +110,7 @@ export interface PiNotifyEventRequest {
110
110
 
111
111
  /**
112
112
  * @brief Describes the configuration fields consumed by pi-notify helpers.
113
- * @details Narrows the full project config to the persisted notify, sound, and Pushover fields used by status rendering, prompt-end routing, and shortcut toggles. Compile-time only and introduces no runtime cost.
113
+ * @details Narrows the full project config to the notify, sound, and Pushover fields used by status rendering and prompt-end routing. Callers may override `notify-sound` with the active runtime sound level before dispatch. Compile-time only and introduces no runtime cost.
114
114
  */
115
115
  export type PiNotifyConfigFields = Pick<
116
116
  UseReqConfig,
@@ -363,10 +363,10 @@ export function formatPiNotifyPushoverStatus(config: Pick<UseReqConfig, "notify-
363
363
 
364
364
  /**
365
365
  * @brief Cycles one sound level through the canonical shortcut order.
366
- * @details Advances persisted sound state in the exact order `none -> low -> mid -> high -> none`, enabling deterministic shortcut toggling and menu reuse. Runtime is O(1). No external state is mutated.
367
- * @param[in] currentLevel {PiNotifySoundLevel} Current persisted sound level.
368
- * @return {PiNotifySoundLevel} Next sound level in the cycle.
369
- * @satisfies REQ-134
366
+ * @details Advances the active runtime sound state in the exact order `none -> low -> mid -> high -> none`, enabling deterministic shortcut toggling without mutating persisted boot configuration. Runtime is O(1). No external state is mutated.
367
+ * @param[in] currentLevel {PiNotifySoundLevel} Current active runtime sound level.
368
+ * @return {PiNotifySoundLevel} Next runtime sound level in the cycle.
369
+ * @satisfies REQ-286
370
370
  */
371
371
  export function cyclePiNotifySoundLevel(currentLevel: PiNotifySoundLevel): PiNotifySoundLevel {
372
372
  const currentIndex = PI_NOTIFY_SOUND_LEVELS.indexOf(currentLevel);
@@ -10,9 +10,10 @@
10
10
  */
11
11
  export const PI_USEREQ_CUSTOM_TOOL_NAMES = [
12
12
  "files-tokens",
13
- "files-references",
13
+ "files-summarize",
14
14
  "files-compress",
15
15
  "files-search",
16
+ "summarize",
16
17
  "references",
17
18
  "compress",
18
19
  "search",
@@ -51,9 +52,10 @@ export const PI_USEREQ_STARTUP_TOOL_NAMES = [
51
52
  */
52
53
  export const PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES = [
53
54
  "files-tokens",
54
- "files-references",
55
+ "files-summarize",
55
56
  "files-compress",
56
57
  "files-search",
58
+ "summarize",
57
59
  "references",
58
60
  "compress",
59
61
  "search",
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * @file
3
- * @brief Declares the canonical bundled `req-*` prompt-command inventory.
4
- * @details Centralizes prompt-command names shared by extension registration, configuration normalization, debug-menu rendering, and prompt-runtime orchestration. The module is side-effect free. Lookup cost is O(1) per exported constant access.
3
+ * @brief Declares the canonical bundled prompt-backed `req-*` command inventory.
4
+ * @details Centralizes only prompt-template-backed command names shared by extension registration, configuration normalization, debug-menu rendering, and prompt-runtime orchestration. Specialized slash commands such as `req-references` and `req-reset` are registered outside this inventory. The module is side-effect free. Lookup cost is O(1) per exported constant access.
5
5
  */
6
6
 
7
7
  /**
8
- * @brief Lists bundled prompt-command names handled by the extension.
9
- * @details Provides the single source of truth for `req-*` prompt registration, required-document routing, debug-prompt inventory derivation, and prompt-command worktree orchestration. Access complexity is O(1).
8
+ * @brief Lists bundled prompt-backed command names handled by the extension.
9
+ * @details Provides the single source of truth for prompt-template-backed `req-*` registration, required-document routing, debug-prompt inventory derivation, and prompt-command worktree orchestration. Access complexity is O(1).
10
10
  */
11
11
  export const PROMPT_COMMAND_NAMES = [
12
12
  "analyze",
@@ -21,7 +21,6 @@ export const PROMPT_COMMAND_NAMES = [
21
21
  "readme",
22
22
  "recreate",
23
23
  "refactor",
24
- "references",
25
24
  "renumber",
26
25
  "workflow",
27
26
  "write",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file
3
- * @brief Implements prompt-command preflight and worktree orchestration.
4
- * @details Centralizes `req-<prompt>` repository validation, prompt-specific required-document checks, slash-command-owned worktree naming and lifecycle handling, session-backed cwd switching plus verification, persisted replacement-session context reuse for non-command lifecycle handlers, matched-success fast-forward merge finalization with restored-session transcript preservation, and command-side abort cleanup. Runtime is dominated by git subprocess execution plus bounded filesystem and session-file metadata checks. Side effects include active-session replacement, worktree creation and deletion, branch merges, and filesystem reads and writes.
3
+ * @brief Implements bundled prompt-command preflight and worktree orchestration.
4
+ * @details Centralizes prompt-template-backed `req-<prompt>` repository validation, prompt-specific required-document checks, slash-command-owned worktree naming and lifecycle handling, reusable transcript-preservation plus session-restoration helpers, persisted replacement-session context reuse for non-command lifecycle handlers, matched-success stash-assisted fast-forward merge finalization, and command-side abort cleanup. Runtime is dominated by git subprocess execution plus bounded filesystem and session-file metadata checks. Side effects include active-session replacement, worktree creation and deletion, branch merges, stash-stack mutation, and filesystem reads and writes.
5
5
  */
6
6
 
7
7
  import fs from "node:fs";
@@ -19,10 +19,7 @@ import {
19
19
  logDebugPromptWorkflowEvent,
20
20
  type DebugWorkflowState,
21
21
  } from "./debug-runtime.js";
22
- import {
23
- PROMPT_COMMAND_NAMES,
24
- type PromptCommandName,
25
- } from "./prompt-command-catalog.js";
22
+ import { type PromptCommandName } from "./prompt-command-catalog.js";
26
23
  import {
27
24
  isSameOrAncestorPath,
28
25
  normalizeRelativeDirContract,
@@ -483,9 +480,9 @@ function readPromptSessionJsonLines(
483
480
  * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan whose original and execution session files must be synchronized.
484
481
  * @return {void} No return value.
485
482
  * @throws {ReqError} Throws when either session file is unreadable or when appended execution records are not persisted to the original session file.
486
- * @satisfies REQ-208
483
+ * @satisfies REQ-208, REQ-307
487
484
  */
488
- function preservePromptCommandExecutionTranscript(plan: PromptCommandExecutionPlan): void {
485
+ export function preservePromptCommandExecutionTranscript(plan: PromptCommandExecutionPlan): void {
489
486
  const normalizedOriginalSessionFile = path.resolve(plan.originalSessionFile);
490
487
  const normalizedExecutionSessionFile = path.resolve(plan.executionSessionFile);
491
488
  if (normalizedOriginalSessionFile === normalizedExecutionSessionFile) {
@@ -829,9 +826,6 @@ const PROMPT_REQUIRED_DOCS: Record<PromptCommandName, readonly PromptRequiredDoc
829
826
  { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
830
827
  { fileName: "REFERENCES.md", promptCommand: "/req-references" },
831
828
  ],
832
- references: [
833
- { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
834
- ],
835
829
  renumber: [
836
830
  { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
837
831
  { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
@@ -855,6 +849,166 @@ function runCapture(command: string[], cwd: string): ReturnType<typeof spawnSync
855
849
  });
856
850
  }
857
851
 
852
+ /**
853
+ * @brief Lists tracked `base-path` status rows that require stash-assisted merge handling.
854
+ * @details Executes `git status --porcelain`, retains only tracked rows whose index or worktree slot reports a change, and excludes untracked or ignored rows because the required `git stash` command does not preserve them. Runtime is dominated by one git subprocess plus O(n) parsing in status-line count. Side effects include process spawning.
855
+ * @param[in] basePath {string} Restored project base path.
856
+ * @return {string[]} Tracked status rows requiring stash-assisted merge handling.
857
+ * @throws {ReqError} Throws when git status cannot be read from `basePath`.
858
+ * @satisfies REQ-291
859
+ */
860
+ function listPromptTrackedBasePathChanges(basePath: string): string[] {
861
+ const statusResult = runCapture(["git", "status", "--porcelain"], basePath);
862
+ if (statusResult.error || statusResult.status !== 0) {
863
+ throw new ReqError("ERROR: Unable to inspect base-path changes before merge.", 1);
864
+ }
865
+ return statusResult.stdout
866
+ .split(/\r?\n/)
867
+ .map((line) => line.trimEnd())
868
+ .filter((line) => line !== "")
869
+ .filter((line) => {
870
+ const statusCode = line.slice(0, 2);
871
+ if (statusCode === "??" || statusCode === "!!") {
872
+ return false;
873
+ }
874
+ const indexStatus = statusCode[0] ?? " ";
875
+ const worktreeStatus = statusCode[1] ?? " ";
876
+ return indexStatus !== " " || worktreeStatus !== " ";
877
+ });
878
+ }
879
+
880
+ /**
881
+ * @brief Executes the successful-closure merge sequence from restored `base-path`.
882
+ * @details Detects tracked staged or unstaged `base-path` changes, wraps the existing fast-forward merge in `git stash` and `git stash pop` when required, preserves the direct merge path when no tracked changes exist, emits a warning-only result after successful local-change restoration, and writes one merge-finalization debug entry when enabled. Runtime is dominated by up to four git subprocesses plus O(n) status parsing. Side effects include stash-stack mutation, branch merge attempts, and optional debug-log writes.
883
+ * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan whose branch should be merged.
884
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
885
+ * @return {{ mergeAttempted: boolean; mergeSucceeded: boolean; errorMessage?: string; warningMessage?: string }} Merge-attempt facts plus optional warning text.
886
+ * @satisfies REQ-208, REQ-245, REQ-291, REQ-292
887
+ */
888
+ function finalizePromptCommandMerge(
889
+ plan: PromptCommandExecutionPlan,
890
+ debugOptions?: PromptCommandDebugOptions,
891
+ ): {
892
+ mergeAttempted: boolean;
893
+ mergeSucceeded: boolean;
894
+ errorMessage?: string;
895
+ warningMessage?: string;
896
+ } {
897
+ const worktreeDir = plan.worktreeDir ?? plan.branchName;
898
+ const mergeLogInput = {
899
+ worktree_dir: worktreeDir,
900
+ branch_name: plan.branchName,
901
+ };
902
+ let trackedStatusLines: string[];
903
+ try {
904
+ trackedStatusLines = listPromptTrackedBasePathChanges(plan.basePath);
905
+ } catch (error) {
906
+ const errorMessage = error instanceof Error ? error.message : String(error);
907
+ if (debugOptions) {
908
+ logDebugPromptEvent(
909
+ plan.basePath,
910
+ debugOptions.config,
911
+ debugOptions.workflowState,
912
+ plan.promptName,
913
+ "merge",
914
+ mergeLogInput,
915
+ {
916
+ success: false,
917
+ merge_attempted: false,
918
+ error: errorMessage,
919
+ },
920
+ true,
921
+ );
922
+ }
923
+ return {
924
+ mergeAttempted: false,
925
+ mergeSucceeded: false,
926
+ errorMessage,
927
+ };
928
+ }
929
+ const usedStash = trackedStatusLines.length > 0;
930
+ let stashStatus: number | null | undefined;
931
+ if (usedStash) {
932
+ const stashResult = runCapture(["git", "stash"], plan.basePath);
933
+ stashStatus = stashResult.status;
934
+ if (stashResult.error || stashResult.status !== 0) {
935
+ const errorMessage = `ERROR: Unable to stash base-path changes before merge for worktree ${worktreeDir}.`;
936
+ if (debugOptions) {
937
+ logDebugPromptEvent(
938
+ plan.basePath,
939
+ debugOptions.config,
940
+ debugOptions.workflowState,
941
+ plan.promptName,
942
+ "merge",
943
+ {
944
+ ...mergeLogInput,
945
+ used_stash: true,
946
+ tracked_change_count: trackedStatusLines.length,
947
+ },
948
+ {
949
+ success: false,
950
+ merge_attempted: false,
951
+ error: errorMessage,
952
+ stash_status: stashStatus,
953
+ },
954
+ true,
955
+ );
956
+ }
957
+ return {
958
+ mergeAttempted: false,
959
+ mergeSucceeded: false,
960
+ errorMessage,
961
+ };
962
+ }
963
+ }
964
+ const mergeResult = runCapture(
965
+ ["git", "merge", "--ff-only", plan.branchName],
966
+ plan.basePath,
967
+ );
968
+ const mergeSucceeded = !mergeResult.error && mergeResult.status === 0;
969
+ const errorMessage = mergeSucceeded
970
+ ? undefined
971
+ : `ERROR: Fast-forward merge failed for worktree ${worktreeDir}.`;
972
+ let stashPopStatus: number | null | undefined;
973
+ let warningMessage: string | undefined;
974
+ if (usedStash) {
975
+ const stashPopResult = runCapture(["git", "stash", "pop"], plan.basePath);
976
+ stashPopStatus = stashPopResult.status;
977
+ if (mergeSucceeded) {
978
+ warningMessage = "WARNING: Restored base-path changes after merge; base-path is not clean.";
979
+ }
980
+ }
981
+ if (debugOptions) {
982
+ logDebugPromptEvent(
983
+ plan.basePath,
984
+ debugOptions.config,
985
+ debugOptions.workflowState,
986
+ plan.promptName,
987
+ "merge",
988
+ {
989
+ ...mergeLogInput,
990
+ used_stash: usedStash,
991
+ tracked_change_count: trackedStatusLines.length,
992
+ },
993
+ {
994
+ success: mergeSucceeded,
995
+ merge_attempted: true,
996
+ error: errorMessage,
997
+ warning: warningMessage,
998
+ stash_status: stashStatus,
999
+ stash_pop_status: stashPopStatus,
1000
+ },
1001
+ !mergeSucceeded,
1002
+ );
1003
+ }
1004
+ return {
1005
+ mergeAttempted: true,
1006
+ mergeSucceeded,
1007
+ errorMessage,
1008
+ warningMessage,
1009
+ };
1010
+ }
1011
+
858
1012
  /**
859
1013
  * @brief Stores or clears the prompt-command post-create test hook.
860
1014
  * @details Enables deterministic simulation of post-create worktree verification failures without altering production control flow. Runtime is O(1). Side effect: mutates module-local test state.
@@ -950,15 +1104,15 @@ function throwPromptGitStatusError(): never {
950
1104
  }
951
1105
 
952
1106
  /**
953
- * @brief Runs prompt-command-owned git validation and returns the runtime git root.
954
- * @details Validates work-tree membership, porcelain cleanliness, and symbolic or detached `HEAD` presence without invoking extension custom-tool executors. Runtime is dominated by git subprocess execution. Side effects include process spawning.
1107
+ * @brief Runs slash-command-owned git validation and returns the runtime git root.
1108
+ * @details Validates work-tree membership, porcelain cleanliness, and symbolic or detached `HEAD` presence for bundled prompt commands and `req-references` without invoking extension custom-tool executors. Runtime is dominated by git subprocess execution. Side effects include process spawning.
955
1109
  * @param[in] projectBase {string} Absolute current project base.
956
1110
  * @param[in] config {UseReqConfig | undefined} Optional effective project configuration used to ignore extension-owned debug-log artifacts.
957
1111
  * @return {string} Absolute runtime git root.
958
1112
  * @throws {ReqError} Throws the canonical prompt-command git-preflight error on any validation failure.
959
1113
  * @satisfies REQ-200, REQ-220
960
1114
  */
961
- function validatePromptGitState(projectBase: string, config?: UseReqConfig): string {
1115
+ export function validatePromptGitState(projectBase: string, config?: UseReqConfig): string {
962
1116
  const gitPath = resolveRuntimeGitPath(projectBase);
963
1117
  if (!gitPath) {
964
1118
  throwPromptGitStatusError();
@@ -1280,9 +1434,9 @@ function createPromptWorktree(
1280
1434
  * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1281
1435
  * @return {void} No return value.
1282
1436
  * @throws {ReqError} Throws when cleanup cannot remove the worktree and branch fully.
1283
- * @satisfies REQ-208, REQ-220, REQ-245
1437
+ * @satisfies REQ-208, REQ-220, REQ-245, REQ-309
1284
1438
  */
1285
- function deletePromptWorktree(
1439
+ export function deletePromptWorktree(
1286
1440
  basePath: string,
1287
1441
  worktreeDir: string,
1288
1442
  worktreeRootPath: string,
@@ -1691,12 +1845,12 @@ export async function abortPromptCommandExecution(
1691
1845
 
1692
1846
  /**
1693
1847
  * @brief Finalizes one matched successful worktree-backed prompt execution.
1694
- * @details Re-verifies persisted execution-session metadata plus worktree artifacts, copies any execution-session transcript records missing from the original session file, restores the original session-backed `base-path`, fast-forward merges the successful worktree branch from `base-path`, deletes the worktree after merge success, and preserves the restored base session across closure failures. Closure intentionally treats `base-path` restoration as authoritative even when pi CLI has already started end-of-session session replacement or other housekeeping that moved the live runtime away from `worktree-path`. Runtime is dominated by session switching plus git subprocess execution. Side effects include session-file appends, active-session replacement, branch merges, worktree deletion, and optional debug-log writes.
1848
+ * @details Re-verifies persisted execution-session metadata plus worktree artifacts, copies any execution-session transcript records missing from the original session file, restores the original session-backed `base-path`, executes the stash-assisted fast-forward merge sequence from `base-path`, deletes the worktree after merge success, and preserves the restored base session across closure failures. Closure intentionally treats `base-path` restoration as authoritative even when pi CLI has already started end-of-session session replacement or other housekeeping that moved the live runtime away from `worktree-path`. Runtime is dominated by session switching plus git subprocess execution. Side effects include session-file appends, active-session replacement, branch merges, stash-stack mutation, worktree deletion, and optional debug-log writes.
1695
1849
  * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan.
1696
1850
  * @param[in] ctx {PromptCommandSessionContext | undefined} Optional prompt-command context.
1697
1851
  * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1698
- * @return {Promise<{ mergeAttempted: boolean; mergeSucceeded: boolean; cleanupSucceeded: boolean; errorMessage?: string; activeContext?: PromptCommandSessionContext }>} Finalization facts plus the last valid active prompt-command context.
1699
- * @satisfies REQ-208, REQ-209, REQ-220, REQ-245, REQ-282
1852
+ * @return {Promise<{ mergeAttempted: boolean; mergeSucceeded: boolean; cleanupSucceeded: boolean; errorMessage?: string; warningMessage?: string; activeContext?: PromptCommandSessionContext }>} Finalization facts plus the last valid active prompt-command context.
1853
+ * @satisfies REQ-208, REQ-209, REQ-220, REQ-245, REQ-282, REQ-291, REQ-292
1700
1854
  */
1701
1855
  export async function finalizePromptCommandExecution(
1702
1856
  plan: PromptCommandExecutionPlan,
@@ -1707,6 +1861,7 @@ export async function finalizePromptCommandExecution(
1707
1861
  mergeSucceeded: boolean;
1708
1862
  cleanupSucceeded: boolean;
1709
1863
  errorMessage?: string;
1864
+ warningMessage?: string;
1710
1865
  activeContext?: PromptCommandSessionContext;
1711
1866
  }> {
1712
1867
  let activeContext = ctx;
@@ -1749,32 +1904,14 @@ export async function finalizePromptCommandExecution(
1749
1904
  activeContext,
1750
1905
  };
1751
1906
  }
1752
- const mergeResult = runCapture(
1753
- ["git", "merge", "--ff-only", plan.branchName],
1754
- plan.basePath,
1755
- );
1756
- const mergeSucceeded = !mergeResult.error && mergeResult.status === 0;
1757
- const errorMessage = mergeSucceeded
1758
- ? undefined
1759
- : `ERROR: Fast-forward merge failed for worktree ${plan.worktreeDir}.`;
1760
- if (debugOptions) {
1761
- logDebugPromptEvent(
1762
- plan.basePath,
1763
- debugOptions.config,
1764
- debugOptions.workflowState,
1765
- plan.promptName,
1766
- "merge",
1767
- { worktree_dir: plan.worktreeDir, branch_name: plan.branchName },
1768
- { success: mergeSucceeded, error: errorMessage },
1769
- !mergeSucceeded,
1770
- );
1771
- }
1772
- if (!mergeSucceeded) {
1907
+ const mergeFinalization = finalizePromptCommandMerge(plan, debugOptions);
1908
+ if (!mergeFinalization.mergeSucceeded) {
1773
1909
  return {
1774
- mergeAttempted: true,
1910
+ mergeAttempted: mergeFinalization.mergeAttempted,
1775
1911
  mergeSucceeded: false,
1776
1912
  cleanupSucceeded: true,
1777
- errorMessage,
1913
+ errorMessage: mergeFinalization.errorMessage,
1914
+ warningMessage: mergeFinalization.warningMessage,
1778
1915
  activeContext,
1779
1916
  };
1780
1917
  }
@@ -1788,17 +1925,19 @@ export async function finalizePromptCommandExecution(
1788
1925
  );
1789
1926
  } catch {
1790
1927
  return {
1791
- mergeAttempted: true,
1928
+ mergeAttempted: mergeFinalization.mergeAttempted,
1792
1929
  mergeSucceeded: true,
1793
1930
  cleanupSucceeded: false,
1794
1931
  errorMessage: `ERROR: Unable to remove worktree or branch ${plan.worktreeDir}.`,
1932
+ warningMessage: mergeFinalization.warningMessage,
1795
1933
  activeContext,
1796
1934
  };
1797
1935
  }
1798
1936
  return {
1799
- mergeAttempted: true,
1937
+ mergeAttempted: mergeFinalization.mergeAttempted,
1800
1938
  mergeSucceeded: true,
1801
1939
  cleanupSucceeded: true,
1940
+ warningMessage: mergeFinalization.warningMessage,
1802
1941
  activeContext,
1803
1942
  };
1804
1943
  }
@@ -22,14 +22,12 @@ import { readBundledInstruction, readBundledPrompt } from "./resources.js";
22
22
  const TOOL_REFERENCE_REPLACEMENTS: Array<[RegExp, string]> = [
23
23
  [/`req --find`/g, "`search` tool"],
24
24
  [/`req --files-find`/g, "`files-search` tool"],
25
- [/`req --references`/g, "`references` tool"],
26
25
  [/`req --compress`/g, "`compress` tool"],
27
26
  [/`req --tokens`/g, "`tokens` tool"],
28
27
  [/`req --static-check`/g, "`static-check` tool"],
29
28
  [/`req --files-static-check`/g, "`files-static-check` tool"],
30
29
  [/\breq --find\b/g, "search tool"],
31
30
  [/\breq --files-find\b/g, "files-search tool"],
32
- [/\breq --references\b/g, "references tool"],
33
31
  [/\breq --compress\b/g, "compress tool"],
34
32
  [/\breq --tokens\b/g, "tokens tool"],
35
33
  [/\breq --static-check\b/g, "static-check tool"],