pi-usereq 0.58.0 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PI-useReq/pi-usereq (0.58.0)
1
+ # PI-useReq/pi-usereq (0.60.0)
2
2
 
3
3
  <p align="center">
4
4
  <img src="https://img.shields.io/badge/python-3.11%2B-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python 3.11+">
@@ -51,6 +51,7 @@ in sync with the repository. All capabilities are exposed as slash commands and
51
51
  - **Git repository** - every `req-*` command runs slash-command-owned git validation: the project must be inside a git work tree, the tracked working tree must be clean, and `HEAD` must resolve (a detached `HEAD` is tolerated; a working branch is recommended because the branch name is embedded in generated worktree names).
52
52
  - **Requirements documentation** - the configured `docs-dir` (default `pi-usereq/docs`) must contain the canonical documents required by each command (`REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md`); commands such as `/req-write`, `/req-create`, and `/req-workflow` are the entry points that generate them.
53
53
  - **Static code checkers** - the bundled checkers (`pyright`, `ruff`, `eslint`) install automatically through the `postinstall` script; the native C/C++ checkers (`cppcheck`, `clang-format`) require a one-line system install (see [Install](#install)). Default configured languages: C, C++, JavaScript, Python, TypeScript.
54
+ - **Models** - model selection and model configuration are managed by the pi client, not by pi-usereq: the extension bundles no model files and exposes no model-related configuration or management surface (custom providers and models are configured in the pi client, e.g. `~/.pi/agent/models.json`, and selected through its model picker). The tool is tested with the OpenRouter model `~deepseek/deepseek-v4-flash-latest`; the extension is model-agnostic and also works with other models exposed by the pi client.
54
55
 
55
56
 
56
57
  ## Feature Highlights
@@ -73,6 +74,17 @@ isolated worktree, render the bundled prompt with the project context, and on su
73
74
  repository clean. `/req-references` and `/req-reset` are non-agentic: they execute directly without starting an LLM session or
74
75
  creating a worktree.
75
76
 
77
+ > **Clean context between commands** — every `/req-*` orchestration keeps the session context content in place when it
78
+ > completes, so a subsequent command continues with the retained transcript. It is therefore recommended to start a new
79
+ > session with a clean context (run `/new`, pi's `Start a new session` action) before each new `/req-*` command, unless you
80
+ > actually need to keep the previous context (for example when the next command builds on the outputs of the previous one).
81
+
82
+ > **Recommended context for analysis** — for the most effective analysis it is recommended to place the requirements,
83
+ > reference, and workflow documents in the prompt context by enabling the `Context Files` toggles for `REQUIREMENTS.md`,
84
+ > `REFERENCES.md`, and `WORKFLOW.md` (the settings-menu row then reads `Context Files requirements:on • references:on • workflow:on`).
85
+ > The enabled canonical documents are injected into every bundled prompt through `%%CONTEXT_FILES%%`, so the agent analyzes
86
+ > the requirements, the reference index, and the workflow documentation together with the source code.
87
+
76
88
  | Command | Description | Required docs |
77
89
  | --- | --- | --- |
78
90
  | `/req-write` | Produce a *SRS* draft based on the User Request description | none |
@@ -269,7 +281,7 @@ The interactive configuration menu exposes every user-facing setting; changes ar
269
281
  - **Documentation directory** — `docs-dir` (default `pi-usereq/docs`) used for the canonical documents.
270
282
  - **Unit tests directory** — `tests-dir` (default `tests`).
271
283
  - **Source directories** — `src-dir` (default `["src"]`) used by the analysis tools.
272
- - **Context Files** — toggles to inject `REQUIREMENTS.md`, `WORKFLOW.md`, and `REFERENCES.md` into the prompt context through `%%CONTEXT_FILES%%`.
284
+ - **Context Files** — toggles to inject `REQUIREMENTS.md`, `WORKFLOW.md`, and `REFERENCES.md` into the prompt context through `%%CONTEXT_FILES%%`. It is recommended to keep all three toggles enabled (`requirements:on • references:on • workflow:on`) so the analysis commands receive the canonical documents in their context.
273
285
  - **Auto git commit** — `enable` (default) injects git commit instructions into every prompt; `disable` forces read-only git behavior (`git_read-only.md`) and turns worktree orchestration off.
274
286
  - **Git worktree** / **Worktree prefix** — enable/disable prompt-command worktree isolation and set the name prefix (default `PI-useReq-`).
275
287
  - **Language static code checkers** — per-language `enable`/`disable` flags and the global `Command`-module checker definitions (view/remove/reset with confirmation).
@@ -278,6 +290,8 @@ The interactive configuration menu exposes every user-facing setting; changes ar
278
290
  - **Debug** — local debug logging: log file, log-on-status filter, status-change/workflow-event toggles, enabled tools/prompts, and `Enable debug commands for tools`.
279
291
  - **Show local/global configuration** — write the exact config file contents into the editor.
280
292
  - **Reset defaults** — restore the default configuration with a confirmation preview.
293
+ - **Models** (not a setting) — the menu exposes no model management: models, custom providers, and authentication are configured at the pi client level and simply shared with pi-usereq commands at runtime.
294
+ - **Editor/IDE** (not a setting) — the menu exposes no editor or IDE management: the extension bundles, generates, reads, and requires no Visual Studio Code files (e.g. `.vscode/settings.json`), and all settings are persisted only in `.pi-usereq.json` and `~/.config/pi-usereq/config.json`.
281
295
 
282
296
  ### Extension Side Features
283
297
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-usereq",
3
- "version": "0.58.0",
3
+ "version": "0.60.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Ogekuri/PI-useReq.git"
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file
3
3
  * @brief Implements SDK-parity probing and comparison for the standalone debug harness.
4
- * @details Dynamically loads the official pi SDK when available, inventories extension-owned commands and tools from the runtime surface, passes the 0.80.4+ `authPath` and `modelsPath` `createAgentSession` options, probes support for the new 0.80.4+ event surface, normalizes provenance metadata, and compares the result against the offline recorder snapshot. Runtime is O(c + t + e) in command, tool, and probed-event counts plus the cost of SDK session creation. Side effects are limited to dynamic module loading, optional SDK-managed filesystem reads, and any extension-owned startup behavior triggered by the official runtime.
4
+ * @details Dynamically loads the official pi SDK when available, inventories extension-owned commands and tools from the runtime surface, probes support for the new 0.80.4+ event surface, normalizes provenance metadata, and compares the result against the offline recorder snapshot. Runtime is O(c + t + e) in command, tool, and probed-event counts plus the cost of SDK session creation. Side effects are limited to dynamic module loading, optional SDK-managed filesystem reads, and any extension-owned startup behavior triggered by the official runtime.
5
5
  */
6
6
 
7
7
  import path from "node:path";
@@ -469,7 +469,7 @@ export function buildParityReport(offline: OfflineContractSnapshot, sdk: SdkCont
469
469
 
470
470
  /**
471
471
  * @brief Loads the official pi SDK runtime and extracts the extension-owned command and tool inventories.
472
- * @details Dynamically imports `@earendil-works/pi-coding-agent`, creates a `DefaultResourceLoader` with the requested extension path, creates an SDK session with the 0.80.4+ `authPath` and `modelsPath` options, extracts inventory methods from the returned runtime surface, probes support for the new 0.80.4+ event surface, and filters to extension-owned commands and tools only. Runtime is dominated by SDK startup. Side effects include SDK-managed resource loading and extension startup behavior.
472
+ * @details Dynamically imports `@earendil-works/pi-coding-agent`, creates a `DefaultResourceLoader` with the requested extension path, creates an SDK session, extracts inventory methods from the returned runtime surface, probes support for the new 0.80.4+ event surface, and filters to extension-owned commands and tools only. Runtime is dominated by SDK startup. Side effects include SDK-managed resource loading and extension startup behavior.
473
473
  * @param[in] cwd {string | undefined} Requested working directory.
474
474
  * @param[in] extensionPath {string | undefined} Requested extension entry path.
475
475
  * @return {Promise<SdkContractSnapshot>} Normalized SDK inventory snapshot.
@@ -509,8 +509,6 @@ export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Pro
509
509
  resourceLoader,
510
510
  sessionManager: SessionManager.inMemory(),
511
511
  settingsManager: typeof SettingsManager?.inMemory === "function" ? SettingsManager.inMemory({}) : undefined,
512
- authPath: path.join(paths.cwd, ".pi-usereq-agent-auth.json"),
513
- modelsPath: path.join(paths.cwd, ".pi-usereq-agent-models.json"),
514
512
  });
515
513
  } catch (error) {
516
514
  throw new ReqError(`Error: SDK parity loading failed: ${error instanceof Error ? error.message : String(error)}`, 1);
@@ -692,7 +692,7 @@ function verifyPromptCommandClosureArtifacts(
692
692
  * @throws {ReqError} Throws when the context cannot switch sessions, when the host cancels the switch, or when later verification proves the target session never became active.
693
693
  * @satisfies REQ-068, REQ-271, REQ-272
694
694
  */
695
- async function switchPromptCommandSession(
695
+ export async function switchPromptCommandSession(
696
696
  sessionFile: string,
697
697
  ctx?: PromptCommandSessionContext,
698
698
  ): Promise<PromptCommandSessionContext | undefined> {
@@ -7,6 +7,7 @@
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import { spawnSync, type SpawnSyncReturns } from "node:child_process";
10
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
10
11
  import {
11
12
  normalizeGitWorktreePrefix,
12
13
  type UseReqConfig,
@@ -17,8 +18,14 @@ import {
17
18
  getPromptCommandErrorContext,
18
19
  preservePromptCommandExecutionTranscript,
19
20
  restorePromptCommandExecution,
21
+ switchPromptCommandSession,
20
22
  type PromptCommandExecutionPlan,
21
23
  } from "./prompt-command-runtime.js";
24
+ import {
25
+ isSameOrAncestorPath,
26
+ setRuntimeContextPath,
27
+ setRuntimeWorktreePathState,
28
+ } from "./path-context.js";
22
29
  import { resolveRuntimeGitPath } from "./runtime-project-paths.js";
23
30
 
24
31
  /**
@@ -282,6 +289,190 @@ function ensureReqResetMainBranch(gitRoot: string, worktreeNamePattern: RegExp):
282
289
  }
283
290
  }
284
291
 
292
+ /**
293
+ * @brief Reads the persisted header record from one session file.
294
+ * @details Parses the first non-empty JSONL line and returns its `cwd` plus `parentSession` fields when the file is readable and the header is a JSON object. Runtime is O(n) in header size. No external state is mutated.
295
+ * @param[in] sessionFile {string} Absolute session-file path.
296
+ * @return {{ cwd?: string; parentSession?: string } | undefined} Parsed header fields or `undefined` when unreadable.
297
+ */
298
+ function readReqResetSessionHeader(sessionFile: string): { cwd?: string; parentSession?: string } | undefined {
299
+ let raw: string;
300
+ try {
301
+ raw = fs.readFileSync(sessionFile, "utf8");
302
+ } catch {
303
+ return undefined;
304
+ }
305
+ const newlineIndex = raw.indexOf("\n");
306
+ const firstLine = newlineIndex >= 0 ? raw.slice(0, newlineIndex) : raw;
307
+ const trimmed = firstLine.trim();
308
+ if (trimmed === "") {
309
+ return undefined;
310
+ }
311
+ let parsed: unknown;
312
+ try {
313
+ parsed = JSON.parse(trimmed);
314
+ } catch {
315
+ return undefined;
316
+ }
317
+ if (typeof parsed !== "object" || parsed === null) {
318
+ return undefined;
319
+ }
320
+ const header = parsed as { cwd?: unknown; parentSession?: unknown };
321
+ const parsedCwd = typeof header.cwd === "string" ? header.cwd : undefined;
322
+ const parentSession = typeof header.parentSession === "string" ? header.parentSession : undefined;
323
+ return { cwd: parsedCwd, parentSession };
324
+ }
325
+
326
+ /**
327
+ * @brief Resolves the original base-path session file from the active session header.
328
+ * @details Reads the current session file exposed by the supplied command context, accepts only a persisted `parentSession` link whose own header `cwd` remains inside the main base path, and returns it so `req-reset` can switch the pi CLI back to the original session-backed base path. Runtime is O(n) in session-header size plus bounded filesystem probes. No external state is mutated.
329
+ * @param[in] activeContext {ReqResetCommandContext | undefined} Session-bound command context whose current session file links the base-path session.
330
+ * @param[in] basePath {string} Absolute main repository base path that must contain the parent session cwd.
331
+ * @return {string | undefined} Original base-path session file or `undefined` when unavailable.
332
+ */
333
+ function resolveReqResetParentSessionFile(
334
+ activeContext: ReqResetCommandContext | undefined,
335
+ basePath: string,
336
+ ): string | undefined {
337
+ let currentSessionFile: string | undefined;
338
+ try {
339
+ currentSessionFile = typeof activeContext?.sessionManager?.getSessionFile === "function"
340
+ ? activeContext.sessionManager.getSessionFile()
341
+ : undefined;
342
+ } catch {
343
+ return undefined;
344
+ }
345
+ if (typeof currentSessionFile !== "string" || currentSessionFile === "") {
346
+ return undefined;
347
+ }
348
+ const header = readReqResetSessionHeader(path.resolve(currentSessionFile));
349
+ if (header?.parentSession === undefined || !fs.existsSync(header.parentSession)) {
350
+ return undefined;
351
+ }
352
+ const resolvedParentSession = path.resolve(header.parentSession);
353
+ const parentHeader = readReqResetSessionHeader(resolvedParentSession);
354
+ if (parentHeader?.cwd === undefined || !fs.existsSync(parentHeader.cwd)) {
355
+ return undefined;
356
+ }
357
+ return isSameOrAncestorPath(path.resolve(basePath), path.resolve(parentHeader.cwd))
358
+ ? resolvedParentSession
359
+ : undefined;
360
+ }
361
+
362
+ /**
363
+ * @brief Resolves the most recent persisted session file rooted at the main base path.
364
+ * @details Uses the pi SDK default session discovery for the supplied cwd and returns the persisted session file only when it already exists on disk, so the pi CLI can resume the recent base-path session instead of being created a fresh one. Runtime is O(s) in session-directory listing cost. No external state is mutated.
365
+ * @param[in] basePath {string} Absolute main repository base path.
366
+ * @return {string | undefined} Most recent persisted base-path session file or `undefined` when none exists.
367
+ */
368
+ function resolveReqResetMainSessionFile(basePath: string): string | undefined {
369
+ try {
370
+ const sessionManager = SessionManager.continueRecent(path.resolve(basePath));
371
+ const sessionFile = sessionManager.getSessionFile();
372
+ return typeof sessionFile === "string" && fs.existsSync(sessionFile)
373
+ ? path.resolve(sessionFile)
374
+ : undefined;
375
+ } catch {
376
+ return undefined;
377
+ }
378
+ }
379
+
380
+ /**
381
+ * @brief Tests whether one execution path must be re-anchored to the main base path before cleanup.
382
+ * @details Returns `true` when the path does not exist anymore, or when it is anchored inside any generated worktree root targeted by `req-reset` and the base path is not an ancestor of it. Existing paths already inside the main base path never need a redirect. Runtime is O(r) in matched-worktree count plus bounded filesystem probes. No external state is mutated.
383
+ * @param[in] executionPath {string | undefined} Live process or context execution path.
384
+ * @param[in] basePath {string} Absolute main repository base path.
385
+ * @param[in] matchingWorktreeRoots {string[]} Absolute generated worktree roots targeted for deletion.
386
+ * @return {boolean} `true` when the execution path must be switched to the main base path first.
387
+ */
388
+ function reqResetExecutionPathNeedsRedirect(
389
+ executionPath: string | undefined,
390
+ basePath: string,
391
+ matchingWorktreeRoots: string[],
392
+ ): boolean {
393
+ if (typeof executionPath !== "string" || executionPath === "") {
394
+ return false;
395
+ }
396
+ const normalizedExecutionPath = path.resolve(executionPath);
397
+ const normalizedBasePath = path.resolve(basePath);
398
+ if (isSameOrAncestorPath(normalizedBasePath, normalizedExecutionPath)) {
399
+ return false;
400
+ }
401
+ if (!fs.existsSync(normalizedExecutionPath)) {
402
+ return true;
403
+ }
404
+ return matchingWorktreeRoots.some((worktreeRootPath) => {
405
+ const normalizedRoot = path.resolve(worktreeRootPath);
406
+ return normalizedExecutionPath === normalizedRoot
407
+ || normalizedExecutionPath.startsWith(`${normalizedRoot}${path.sep}`);
408
+ });
409
+ }
410
+
411
+ /**
412
+ * @brief Returns the pi CLI to the main base path before generated worktrees are removed.
413
+ * @details When the host process cwd or the supplied context cwd is still anchored inside a generated worktree targeted for deletion or points at a deleted path, best-effort switches the active pi session back to the original or recent base-path session, re-anchors `process.cwd()` and the context `cwd` mirror onto the main base path, and clears the runtime worktree path state so no post-cleanup surface keeps probing the removed worktree directory. Session switching failures are swallowed because the process and context surfaces remain authoritative for cleanup; session switching is skipped entirely when no `switchSession(...)` hook is available or no base-path session exists. Runtime is dominated by one optional session switch plus bounded filesystem probes. Side effects include active-session replacement, host-process cwd mutation, and optional context mirror mutation.
414
+ * @param[in] basePath {string} Absolute main repository base path.
415
+ * @param[in,out] activeContext {ReqResetCommandContext | undefined} Mutated session context mirror and potential replacement-session context.
416
+ * @param[in] matchingWorktreeRoots {string[]} Absolute generated worktree roots targeted for deletion.
417
+ * @return {Promise<ReqResetCommandContext | undefined>} Replacement-session context when the runtime provides one; otherwise the caller-supplied context.
418
+ * @satisfies REQ-257, REQ-307
419
+ */
420
+ async function redirectReqResetExecutionToMainBranch(
421
+ basePath: string,
422
+ activeContext: ReqResetCommandContext | undefined,
423
+ matchingWorktreeRoots: string[],
424
+ ): Promise<ReqResetCommandContext | undefined> {
425
+ const normalizedBasePath = path.resolve(basePath);
426
+ let processCwd: string | undefined;
427
+ try {
428
+ processCwd = path.resolve(process.cwd());
429
+ } catch {
430
+ processCwd = undefined;
431
+ }
432
+ const contextCwd = typeof activeContext?.cwd === "string" ? activeContext.cwd : undefined;
433
+ const processNeedsRedirect = reqResetExecutionPathNeedsRedirect(
434
+ processCwd,
435
+ normalizedBasePath,
436
+ matchingWorktreeRoots,
437
+ );
438
+ const contextNeedsRedirect = reqResetExecutionPathNeedsRedirect(
439
+ contextCwd,
440
+ normalizedBasePath,
441
+ matchingWorktreeRoots,
442
+ );
443
+ if (!processNeedsRedirect && !contextNeedsRedirect) {
444
+ return activeContext;
445
+ }
446
+ if (typeof activeContext?.switchSession === "function") {
447
+ const mainSessionFile = resolveReqResetParentSessionFile(activeContext, normalizedBasePath)
448
+ ?? resolveReqResetMainSessionFile(normalizedBasePath);
449
+ if (mainSessionFile !== undefined) {
450
+ try {
451
+ activeContext = await switchPromptCommandSession(mainSessionFile, activeContext);
452
+ } catch {
453
+ // Best-effort; host process and context surfaces below remain authoritative.
454
+ }
455
+ }
456
+ }
457
+ try {
458
+ process.chdir(normalizedBasePath);
459
+ } catch {
460
+ // Best-effort; cleanup facts remain authoritative.
461
+ }
462
+ if (activeContext !== undefined) {
463
+ try {
464
+ if (path.resolve(activeContext.cwd ?? "") !== normalizedBasePath) {
465
+ Reflect.set(activeContext, "cwd", normalizedBasePath);
466
+ }
467
+ } catch {
468
+ // Best-effort context mirror mutation.
469
+ }
470
+ }
471
+ setRuntimeContextPath(normalizedBasePath);
472
+ setRuntimeWorktreePathState({});
473
+ return activeContext;
474
+ }
475
+
285
476
  /**
286
477
  * @brief Restores live process and context cwd surfaces after worktree removal.
287
478
  * @details Best-effort re-points `process.cwd()` and the supplied context `cwd` mirror to the main repository base path when the previously live cwd was removed by `req-reset` cleanup, so subsequent status rendering and notifications never probe deleted worktree paths. Runtime is O(1) plus bounded filesystem probes. Side effects include process cwd mutation and optional context mirror mutation.
@@ -356,7 +547,7 @@ export function prepareReqResetCommandExecution(
356
547
 
357
548
  /**
358
549
  * @brief Executes the specialized `req-reset` recovery and cleanup workflow.
359
- * @details Preserves the execution-session transcript into the original session file when a worktree-backed prompt execution plan is still available, restores the original session-backed `base-path` through the shared prompt-command restoration helper, ensures the main repository HEAD is on the main branch so generated branch deletion cannot hit git's checked-out-worktree guard, force-removes every matching sibling worktree directory, force-removes every remaining matching local branch, restores deleted live cwd surfaces, and aggregates any failure diagnostics without rolling back successful cleanup steps. Runtime is dominated by session switching plus git subprocess execution. Side effects include session-file reads and writes, active-session replacement, host-process cwd mutation, branch switching, worktree deletion, branch deletion, and filesystem reads.
550
+ * @details Preserves the execution-session transcript into the original session file when a worktree-backed prompt execution plan is still available, restores the original session-backed `base-path` through the shared prompt-command restoration helper, ensures the main repository HEAD is on the main branch so generated branch deletion cannot hit git's checked-out-worktree guard, enumerates every matching sibling worktree, returns the pi CLI execution surfaces to the main base path before any matching worktree directory is removed, force-removes every matching sibling worktree directory, force-removes every remaining matching local branch, restores deleted live cwd surfaces, and aggregates any failure diagnostics without rolling back successful cleanup steps. Runtime is dominated by session switching plus git subprocess execution. Side effects include session-file reads and writes, active-session replacement, host-process cwd mutation, branch switching, worktree deletion, branch deletion, and filesystem reads.
360
551
  * @param[in] plan {ReqResetCommandPlan} Prepared recovery and cleanup plan.
361
552
  * @param[in] ctx {ReqResetCommandContext | undefined} Optional session-bound command context.
362
553
  * @return {Promise<ReqResetCommandExecutionResult>} Recovery and cleanup outcome facts.
@@ -413,6 +604,12 @@ export async function executeReqResetCommandExecution(
413
604
  errorMessages.push(error instanceof Error ? error.message : String(error));
414
605
  }
415
606
 
607
+ activeContext = await redirectReqResetExecutionToMainBranch(
608
+ plan.basePath,
609
+ activeContext,
610
+ matchingWorktreeRoots,
611
+ );
612
+
416
613
  for (const worktreeRootPath of matchingWorktreeRoots) {
417
614
  const worktreeDir = path.basename(worktreeRootPath);
418
615
  try {
package/src/index.ts CHANGED
@@ -1160,6 +1160,29 @@ function notifyContextSafely(
1160
1160
  }
1161
1161
  }
1162
1162
 
1163
+ /**
1164
+ * @brief Emits the final context-retention reminder info notification for a successfully completed `/req-*` orchestration.
1165
+ * @details Builds one message stating that the session context content was retained, recommending a clean-context session start before the next `/req-*` command, and appending the current context-usage percentage when the controller holds a normalized snapshot. Delivers exclusively through `notifyContextSafely(...)`, so stale replacement-session contexts are suppressed, and performs no session message send and no session switch. Runtime is O(1). Side effect is user notification delivery only when the supplied context is still active.
1166
+ * @param[in] statusController {PiUsereqStatusController} Mutable status controller supplying the latest normalized context-usage snapshot.
1167
+ * @param[in] ctx {(ExtensionContext | ExtensionCommandContext) | undefined} Candidate UI context for notification delivery.
1168
+ * @return {void} No return value.
1169
+ * @satisfies REQ-360, REQ-361, REQ-362, REQ-363, REQ-364, REQ-365
1170
+ */
1171
+ function notifyContextRetentionReminder(
1172
+ statusController: PiUsereqStatusController,
1173
+ ctx: (ExtensionContext | ExtensionCommandContext) | undefined,
1174
+ ): void {
1175
+ const percent = statusController.state.contextUsage?.percent;
1176
+ const percentText = typeof percent === "number" && Number.isFinite(percent)
1177
+ ? ` Current context usage: ${Math.round(percent)}%.`
1178
+ : "";
1179
+ notifyContextSafely(
1180
+ ctx,
1181
+ `Context retained.${percentText} Start a new session with a clean context (/new) before the next /req-* command.`,
1182
+ "info",
1183
+ );
1184
+ }
1185
+
1163
1186
  /**
1164
1187
  * @brief Rejects one non-`idle` req-command invocation and records the workflow error state.
1165
1188
  * @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.
@@ -1375,6 +1398,9 @@ async function finalizeMatchedPromptSuccess(
1375
1398
  } else {
1376
1399
  setPiUsereqWorkflowState(statusController, "idle", promptContext);
1377
1400
  }
1401
+ if (!finalization.errorMessage) {
1402
+ notifyContextRetentionReminder(statusController, promptContext);
1403
+ }
1378
1404
  }
1379
1405
 
1380
1406
  /**
@@ -1595,6 +1621,9 @@ async function handleExtensionStatusEvent(
1595
1621
  } else {
1596
1622
  setPiUsereqWorkflowState(statusController, "idle", promptContext);
1597
1623
  }
1624
+ if (outcome === "completed") {
1625
+ notifyContextRetentionReminder(statusController, promptContext);
1626
+ }
1598
1627
  }
1599
1628
  }
1600
1629
  }
@@ -3106,6 +3135,7 @@ function registerReqResetCommand(
3106
3135
  ? `SUCCESS: req-reset restored base-path and removed ${executionResult.removedWorktreeDirs.length} worktree(s) plus ${executionResult.removedBranchNames.length} branch(es).`
3107
3136
  : `SUCCESS: req-reset removed ${executionResult.removedWorktreeDirs.length} worktree(s) plus ${executionResult.removedBranchNames.length} branch(es) and restored idle state.`;
3108
3137
  notifyContextSafely(resetContext, successMessage, "info");
3138
+ notifyContextRetentionReminder(statusController, resetContext);
3109
3139
  },
3110
3140
  });
3111
3141
  }
@@ -3148,6 +3178,7 @@ function registerReqReferencesCommand(
3148
3178
  `SUCCESS: Updated ${formatRuntimePathForDisplay(executionPlan.referencesPath)} and committed changes.`,
3149
3179
  "info",
3150
3180
  );
3181
+ notifyContextRetentionReminder(statusController, ctx);
3151
3182
  } catch (error) {
3152
3183
  statusController.state.pendingPromptRequest = undefined;
3153
3184
  statusController.state.activePromptRequest = undefined;