pi-usereq 0.4.0 → 0.5.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 (60) hide show
  1. package/.gitignore +3 -0
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +1 -1
  4. package/package.json +1 -1
  5. package/{req → pi-usereq}/docs/REFERENCES.md +196 -159
  6. package/{req → pi-usereq}/docs/REQUIREMENTS.md +65 -44
  7. package/{req → pi-usereq}/docs/WORKFLOW.md +66 -73
  8. package/src/core/config.ts +39 -12
  9. package/src/core/extension-status.ts +94 -58
  10. package/src/core/pi-notify.ts +198 -11
  11. package/src/core/runtime-project-paths.ts +3 -32
  12. package/src/core/settings-menu.ts +9 -4
  13. package/src/core/static-check.ts +35 -171
  14. package/src/index.ts +281 -86
  15. package/tests/attended-results-scenarios.ts +3 -13
  16. package/tests/cli-command-option-parity.test.ts +21 -12
  17. package/tests/debug-extension-harness.test.ts +11 -7
  18. package/tests/extension-registration.test.ts +371 -71
  19. package/tests/oracle-project.test.ts +8 -3
  20. package/tests/oracle-standalone.test.ts +7 -13
  21. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +0 -5
  22. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +0 -5
  23. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +0 -5
  24. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +0 -5
  25. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +0 -5
  26. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +0 -5
  27. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +0 -5
  28. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +0 -5
  29. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +0 -5
  30. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +0 -5
  31. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +0 -5
  32. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +0 -5
  33. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +0 -5
  34. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +0 -5
  35. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +0 -5
  36. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +0 -5
  37. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +0 -5
  38. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +0 -5
  39. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +0 -5
  40. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +0 -5
  41. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +0 -5
  42. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +0 -5
  43. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +0 -5
  44. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +0 -5
  45. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +0 -5
  46. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +0 -5
  47. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +0 -5
  48. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +0 -5
  49. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +0 -5
  50. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +0 -5
  51. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +0 -5
  52. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +0 -5
  53. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +0 -5
  54. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +0 -5
  55. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +0 -5
  56. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +0 -5
  57. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +0 -5
  58. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +0 -5
  59. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +0 -5
  60. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +0 -5
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * @file
3
- * @brief Implements pi-usereq terminal-beep and external sound-hook helpers.
4
- * @details Centralizes configuration defaults, status serialization, agent-end outcome classification, terminal notification dispatch, and successful-run external sound-command execution. Runtime is O(m + c) in `agent_end` message count plus command length. Side effects include stdout writes and detached child-process spawning.
3
+ * @brief Implements pi-usereq terminal-beep, external sound-hook, and Pushover notification helpers.
4
+ * @details Centralizes configuration defaults, status serialization, agent-end outcome classification, terminal notification dispatch, successful-run external sound-command execution, and successful-run Pushover delivery. Runtime is O(m + c + b) in `agent_end` message count plus command length and Pushover payload size. Side effects include stdout writes, detached child-process spawning, and outbound HTTPS requests.
5
5
  */
6
6
 
7
7
  import { execFile, spawn } from "node:child_process";
8
+ import * as https from "node:https";
8
9
  import type { AgentEndEvent } from "@mariozechner/pi-coding-agent";
9
10
  import { getInstallationPath } from "./path-context.js";
10
11
  import type { UseReqConfig } from "./config.js";
@@ -61,9 +62,32 @@ export const DEFAULT_PI_NOTIFY_SOUND_MID_CMD = "paplay --volume=43690 %%INSTALLA
61
62
  */
62
63
  export const DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD = "paplay --volume=65535 %%INSTALLATION_PATH%%/resources/sounds/Soft-high-tech-notification-sound-effect.mp3";
63
64
 
65
+ /**
66
+ * @brief Enumerates supported Pushover priorities.
67
+ * @details Restricts persisted Pushover delivery to the canonical normal and high-priority values accepted by the user-facing configuration menu. Access complexity is O(1).
68
+ */
69
+ export const PI_NOTIFY_PUSHOVER_PRIORITIES = [0, 1] as const;
70
+
71
+ /**
72
+ * @brief Represents one supported Pushover priority value.
73
+ * @details Narrows Pushover configuration parsing and request serialization to the canonical `0|1` priority domain. Compile-time only and introduces no runtime cost.
74
+ */
75
+ export type PiNotifyPushoverPriority = (typeof PI_NOTIFY_PUSHOVER_PRIORITIES)[number];
76
+
77
+ /**
78
+ * @brief Describes one successful prompt-completion payload routed to Pushover.
79
+ * @details Stores the prompt command name, substituted prompt arguments, runtime base path, and successful completion duration used to build one Pushover API request. The interface is compile-time only and introduces no runtime side effects.
80
+ */
81
+ export interface PiNotifyPushoverRequest {
82
+ promptName: string;
83
+ promptArgs: string;
84
+ basePath: string;
85
+ completionTimeMs: number;
86
+ }
87
+
64
88
  /**
65
89
  * @brief Describes the configuration fields consumed by pi-notify helpers.
66
- * @details Narrows the full project config to the persisted notification and sound-hook fields used by status rendering, prompt-end routing, and shortcut toggles. Compile-time only and introduces no runtime cost.
90
+ * @details Narrows the full project config to the persisted notification, sound-hook, and Pushover fields used by status rendering, prompt-end routing, and shortcut toggles. Compile-time only and introduces no runtime cost.
67
91
  */
68
92
  export type PiNotifyConfigFields = Pick<
69
93
  UseReqConfig,
@@ -72,11 +96,22 @@ export type PiNotifyConfigFields = Pick<
72
96
  | "notify-beep-on-error"
73
97
  | "notify-sound"
74
98
  | "notify-sound-toggle-shortcut"
99
+ | "notify-pushover-global-disable"
100
+ | "notify-pushover-on-success"
101
+ | "notify-pushover-user-key"
102
+ | "notify-pushover-api-token"
103
+ | "notify-pushover-priority"
75
104
  | "PI_NOTIFY_SOUND_LOW_CMD"
76
105
  | "PI_NOTIFY_SOUND_MID_CMD"
77
106
  | "PI_NOTIFY_SOUND_HIGH_CMD"
78
107
  >;
79
108
 
109
+ /**
110
+ * @brief Stores the currently configured native HTTPS request function used for Pushover delivery.
111
+ * @details Defaults to `node:https.request` and can be replaced by deterministic tests so Pushover dispatch remains observable without real network I/O. Access complexity is O(1). Side effect: mutated only through the dedicated test hook.
112
+ */
113
+ let piNotifyHttpsRequest: typeof https.request = https.request;
114
+
80
115
  /**
81
116
  * @brief Normalizes one persisted sound level.
82
117
  * @details Accepts only canonical `none|low|mid|high` values and falls back to `none` for missing or invalid payloads. Runtime is O(1). No external state is mutated.
@@ -115,6 +150,28 @@ export function normalizePiNotifyCommand(value: unknown, fallback: string): stri
115
150
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
116
151
  }
117
152
 
153
+ /**
154
+ * @brief Normalizes one persisted Pushover credential string.
155
+ * @details Accepts any trimmed string so the project config can store raw Pushover user and token values verbatim and falls back to the empty string for missing or invalid payloads. Runtime is O(n) in credential length. No external state is mutated.
156
+ * @param[in] value {unknown} Raw persisted credential payload.
157
+ * @return {string} Canonical credential string.
158
+ * @satisfies REQ-163
159
+ */
160
+ export function normalizePiNotifyPushoverCredential(value: unknown): string {
161
+ return typeof value === "string" ? value.trim() : "";
162
+ }
163
+
164
+ /**
165
+ * @brief Normalizes one persisted Pushover priority value.
166
+ * @details Accepts only canonical `0` and `1` values, treating numeric-string `"1"` as high priority and every other payload as normal priority. Runtime is O(1). No external state is mutated.
167
+ * @param[in] value {unknown} Raw persisted priority payload.
168
+ * @return {PiNotifyPushoverPriority} Canonical priority value.
169
+ * @satisfies REQ-163
170
+ */
171
+ export function normalizePiNotifyPushoverPriority(value: unknown): PiNotifyPushoverPriority {
172
+ return value === 1 || value === "1" ? 1 : 0;
173
+ }
174
+
118
175
  /**
119
176
  * @brief Formats enabled terminal-beep flags for status rendering.
120
177
  * @details Emits the canonical comma-ordered enabled outcome tokens `end`, `esc`, and `err`, or `none` when all prompt-end beep flags are disabled. Runtime is O(1). No external state is mutated.
@@ -136,6 +193,17 @@ export function formatPiNotifyBeepStatus(config: PiNotifyConfigFields): string {
136
193
  return enabledOutcomes.length > 0 ? enabledOutcomes.join(",") : "none";
137
194
  }
138
195
 
196
+ /**
197
+ * @brief Formats the Pushover enable flag for status rendering.
198
+ * @details Serializes only the persisted successful-prompt enable setting so the status bar reports the dedicated Pushover toggle independently from any global-disable override. Runtime is O(1). No external state is mutated.
199
+ * @param[in] config {Pick<UseReqConfig, "notify-pushover-on-success">} Effective Pushover configuration subset.
200
+ * @return {string} `on` when successful-prompt Pushover delivery is enabled; otherwise `off`.
201
+ * @satisfies REQ-171
202
+ */
203
+ export function formatPiNotifyPushoverStatus(config: Pick<UseReqConfig, "notify-pushover-on-success">): string {
204
+ return config["notify-pushover-on-success"] ? "on" : "off";
205
+ }
206
+
139
207
  /**
140
208
  * @brief Cycles one sound level through the canonical shortcut order.
141
209
  * @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.
@@ -324,6 +392,120 @@ function buildPiNotifyBody(outcome: PiNotifyOutcome): string {
324
392
  }
325
393
  }
326
394
 
395
+ /**
396
+ * @brief Formats one successful prompt duration for Pushover titles.
397
+ * @details Floors the supplied completion duration to whole seconds, keeps minutes unbounded above 59, and zero-pads seconds to two digits so Pushover titles align with status-bar elapsed formatting. Runtime is O(1). No external state is mutated.
398
+ * @param[in] durationMs {number} Successful prompt duration in milliseconds.
399
+ * @return {string} Duration rendered as `M:SS`.
400
+ * @satisfies REQ-169
401
+ */
402
+ function formatPiNotifyPushoverDuration(durationMs: number): string {
403
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
404
+ const minutes = Math.floor(totalSeconds / 60);
405
+ const seconds = totalSeconds % 60;
406
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
407
+ }
408
+
409
+ /**
410
+ * @brief Builds the Pushover notification title for one successful prompt.
411
+ * @details Serializes the prompt name, absolute runtime base path, and successful completion duration into the canonical `<prompt> @ <base-path> [<time>]` title required by the repository feature contract. Runtime is O(n) in path length. No external state is mutated.
412
+ * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
413
+ * @return {string} Pushover title string.
414
+ * @satisfies REQ-169
415
+ */
416
+ function buildPiNotifyPushoverTitle(request: PiNotifyPushoverRequest): string {
417
+ return `${request.promptName} @ ${request.basePath} [${formatPiNotifyPushoverDuration(request.completionTimeMs)}]`;
418
+ }
419
+
420
+ /**
421
+ * @brief Builds the Pushover message body for one successful prompt.
422
+ * @details Reuses the raw prompt argument string substituted into `%%ARGS%%` so the pushed body remains traceable to the executed prompt invocation. Runtime is O(1). No external state is mutated.
423
+ * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
424
+ * @return {string} Pushover message body.
425
+ * @satisfies REQ-169
426
+ */
427
+ function buildPiNotifyPushoverBody(request: PiNotifyPushoverRequest): string {
428
+ return request.promptArgs;
429
+ }
430
+
431
+ /**
432
+ * @brief Determines whether one successful prompt should trigger Pushover delivery.
433
+ * @details Requires a captured prompt request, the successful-prompt enable flag, the Pushover global-disable flag to remain false, and non-empty user plus token credentials. Runtime is O(1). No external state is mutated.
434
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
435
+ * @param[in] request {PiNotifyPushoverRequest | undefined} Successful prompt metadata.
436
+ * @return {boolean} `true` when Pushover delivery prerequisites are satisfied.
437
+ * @satisfies REQ-166, REQ-168, REQ-172
438
+ */
439
+ function shouldRunPiNotifyPushover(
440
+ config: PiNotifyConfigFields,
441
+ request: PiNotifyPushoverRequest | undefined,
442
+ ): boolean {
443
+ return request !== undefined
444
+ && config["notify-pushover-on-success"]
445
+ && !config["notify-pushover-global-disable"]
446
+ && config["notify-pushover-user-key"] !== ""
447
+ && config["notify-pushover-api-token"] !== "";
448
+ }
449
+
450
+ /**
451
+ * @brief Builds the Pushover API payload for one successful prompt.
452
+ * @details Encodes the configured token, user key, canonical title, priority, and substituted prompt-argument body as `application/x-www-form-urlencoded` fields accepted by the Pushover Message API. Runtime is O(n) in payload size. No external state is mutated.
453
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
454
+ * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
455
+ * @return {URLSearchParams} Encoded Pushover request payload.
456
+ * @satisfies REQ-167, REQ-169
457
+ */
458
+ function buildPiNotifyPushoverPayload(
459
+ config: PiNotifyConfigFields,
460
+ request: PiNotifyPushoverRequest,
461
+ ): URLSearchParams {
462
+ return new URLSearchParams({
463
+ token: config["notify-pushover-api-token"],
464
+ user: config["notify-pushover-user-key"],
465
+ title: buildPiNotifyPushoverTitle(request),
466
+ priority: String(config["notify-pushover-priority"]),
467
+ message: buildPiNotifyPushoverBody(request),
468
+ });
469
+ }
470
+
471
+ /**
472
+ * @brief Dispatches one native HTTPS request to the Pushover Message API.
473
+ * @details Serializes the request body as URL-encoded form data, posts it to `https://api.pushover.net/1/messages.json`, drains the response, and ignores transport failures so prompt-end handling remains non-blocking. Runtime is dominated by outbound I/O. Side effects include one HTTPS request.
474
+ * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
475
+ * @param[in] request {PiNotifyPushoverRequest} Successful prompt metadata.
476
+ * @return {void} No return value.
477
+ * @satisfies REQ-167, REQ-169
478
+ */
479
+ function runPiNotifyPushoverRequest(
480
+ config: PiNotifyConfigFields,
481
+ request: PiNotifyPushoverRequest,
482
+ ): void {
483
+ const url = new URL("https://api.pushover.net/1/messages.json");
484
+ const body = buildPiNotifyPushoverPayload(config, request).toString();
485
+ const httpRequest = piNotifyHttpsRequest(url, {
486
+ method: "POST",
487
+ headers: {
488
+ "content-type": "application/x-www-form-urlencoded",
489
+ "content-length": String(Buffer.byteLength(body)),
490
+ },
491
+ }, (response) => {
492
+ response.on?.("error", () => undefined);
493
+ response.resume?.();
494
+ });
495
+ httpRequest.on("error", () => undefined);
496
+ httpRequest.end(body);
497
+ }
498
+
499
+ /**
500
+ * @brief Replaces the native HTTPS request function used for Pushover delivery in deterministic tests.
501
+ * @details Accepts a drop-in `node:https.request` replacement and restores the native implementation when `undefined` is supplied. Runtime is O(1). Side effect: mutates the module-local Pushover transport hook.
502
+ * @param[in] requestImpl {typeof https.request | undefined} Replacement HTTPS request function.
503
+ * @return {void} No return value.
504
+ */
505
+ export function setPiNotifyHttpsRequestForTests(requestImpl: typeof https.request | undefined): void {
506
+ piNotifyHttpsRequest = requestImpl ?? https.request;
507
+ }
508
+
327
509
  /**
328
510
  * @brief Quotes one installation path for shell substitution.
329
511
  * @details Emits POSIX single-quoted literals for `sh -lc` execution and CMD double-quoted literals for `cmd.exe /c` execution so `%%INSTALLATION_PATH%%` substitutions preserve whitespace safely. Runtime is O(n) in path length. No external state is mutated.
@@ -397,16 +579,18 @@ export function runPiNotifySoundCommand(
397
579
  }
398
580
 
399
581
  /**
400
- * @brief Dispatches prompt-end beep and sound effects for one agent-end payload.
401
- * @details Classifies the terminal outcome, emits the configured terminal notification only for the enabled outcome flag, and executes the configured external sound command only for successful completion with a non-disabled sound level. Runtime is O(m + c) in message count plus command length. Side effects include stdout writes and child-process spawning.
582
+ * @brief Dispatches prompt-end beep, sound, and optional Pushover effects for one agent-end payload.
583
+ * @details Classifies the terminal outcome, emits the configured terminal notification only for the enabled outcome flag, executes the configured external sound command only for successful completion with a non-disabled sound level, and dispatches the native Pushover request when successful-run Pushover prerequisites are satisfied. Runtime is O(m + c + b) in message count, command length, and Pushover payload size. Side effects include stdout writes, child-process spawning, and outbound HTTPS requests.
402
584
  * @param[in] config {PiNotifyConfigFields} Effective notification configuration.
403
585
  * @param[in] event {Pick<AgentEndEvent, "messages">} Agent-end payload subset.
586
+ * @param[in] pushoverRequest {PiNotifyPushoverRequest | undefined} Optional successful prompt metadata used for Pushover delivery.
404
587
  * @return {void} No return value.
405
- * @satisfies REQ-129, REQ-130, REQ-131, REQ-132, REQ-133
588
+ * @satisfies REQ-129, REQ-130, REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172
406
589
  */
407
590
  export function runPiNotifyEffects(
408
591
  config: PiNotifyConfigFields,
409
592
  event: Pick<AgentEndEvent, "messages">,
593
+ pushoverRequest?: PiNotifyPushoverRequest,
410
594
  ): void {
411
595
  const outcome = classifyPiNotifyOutcome(event);
412
596
  const shouldNotify = (
@@ -420,11 +604,14 @@ export function runPiNotifyEffects(
420
604
  if (outcome !== "end") {
421
605
  return;
422
606
  }
423
- if (config["notify-sound"] === "none") {
607
+ if (config["notify-sound"] !== "none") {
608
+ runPiNotifySoundCommand(
609
+ config,
610
+ config["notify-sound"] as Exclude<PiNotifySoundLevel, "none">,
611
+ );
612
+ }
613
+ if (!shouldRunPiNotifyPushover(config, pushoverRequest)) {
424
614
  return;
425
615
  }
426
- runPiNotifySoundCommand(
427
- config,
428
- config["notify-sound"] as Exclude<PiNotifySoundLevel, "none">,
429
- );
616
+ runPiNotifyPushoverRequest(config, pushoverRequest as PiNotifyPushoverRequest);
430
617
  }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @file
3
- * @brief Derives runtime-only repository and base-path facts.
4
- * @details Centralizes git-repository probing, repository-root resolution, and base-path-to-git-path formatting for extension status, tool execution, and CLI flows. Runtime is dominated by git subprocess execution plus path normalization. Side effects are limited to subprocess spawning.
3
+ * @brief Derives runtime-only repository facts.
4
+ * @details Centralizes git-repository probing and repository-root resolution for extension status, tool execution, and CLI flows. Runtime is dominated by git subprocess execution plus path normalization. Side effects are limited to subprocess spawning.
5
5
  */
6
6
 
7
7
  import path from "node:path";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { ReqError } from "./errors.js";
10
- import { isSameOrAncestorPath, normalizePathSlashes } from "./path-context.js";
10
+ import { isSameOrAncestorPath } from "./path-context.js";
11
11
 
12
12
  /**
13
13
  * @brief Executes one git subprocess and captures UTF-8 output.
@@ -67,33 +67,4 @@ export function resolveRuntimeGitPath(executionPath: string): string | undefined
67
67
  return isSameOrAncestorPath(gitRoot, normalizedExecutionPath) ? gitRoot : undefined;
68
68
  }
69
69
 
70
- /**
71
- * @brief Formats the runtime `base-path` relative to the runtime `git-path`.
72
- * @details Returns `.` when the repository root is unavailable or identical to the base path. Otherwise returns the slash-normalized relative path from `git-path` to `base-path`. Runtime is O(p) in path length. No external state is mutated.
73
- * @param[in] basePath {string} Runtime base path.
74
- * @param[in] gitPath {string | undefined} Runtime repository root.
75
- * @return {string} Relative base-path token for status rendering.
76
- * @satisfies REQ-148
77
- */
78
- export function formatBasePathRelativeToGitPath(basePath: string, gitPath: string | undefined): string {
79
- const normalizedBasePath = path.resolve(basePath);
80
- if (!gitPath) {
81
- return ".";
82
- }
83
- const normalizedGitPath = path.resolve(gitPath);
84
- if (normalizedBasePath === normalizedGitPath) {
85
- return ".";
86
- }
87
- return normalizePathSlashes(path.relative(normalizedGitPath, normalizedBasePath)) || ".";
88
- }
89
70
 
90
- /**
91
- * @brief Formats the runtime git path for status rendering.
92
- * @details Returns a slash-normalized absolute path or an empty string when no repository root is available. Runtime is O(p) in path length. No external state is mutated.
93
- * @param[in] gitPath {string | undefined} Runtime repository root.
94
- * @return {string} Absolute repository path or an empty string.
95
- * @satisfies REQ-147
96
- */
97
- export function formatAbsoluteGitPath(gitPath: string | undefined): string {
98
- return gitPath ? normalizePathSlashes(path.resolve(gitPath)) : "";
99
- }
@@ -9,12 +9,13 @@ import { Container, SettingsList, Text, type Component, type SettingItem, type S
9
9
 
10
10
  /**
11
11
  * @brief Describes one selectable pi-usereq settings-menu choice.
12
- * @details Stores the stable action identifier, left-column label, right-column current value, and bottom-line description consumed by the shared settings-menu renderer. The interface is compile-time only and introduces no runtime cost.
12
+ * @details Stores the stable action identifier, left-column label, right-column current value, optional value-tone override, and bottom-line description consumed by the shared settings-menu renderer. The interface is compile-time only and introduces no runtime cost.
13
13
  */
14
14
  export interface PiUsereqSettingsMenuChoice {
15
15
  id: string;
16
16
  label: string;
17
17
  value: string;
18
+ valueTone?: "default" | "dim";
18
19
  description: string;
19
20
  }
20
21
 
@@ -148,12 +149,14 @@ function createImmediateSelectionComponent(choiceId: string, done: (value?: stri
148
149
 
149
150
  /**
150
151
  * @brief Builds `SettingsList` items from one menu-choice vector.
151
- * @details Copies labels, current values, and descriptions into `SettingItem` records and attaches a submenu that resolves the outer custom UI with the selected choice identifier. Runtime is O(n) in choice count. No external state is mutated.
152
+ * @details Copies labels, current values, value-tone overrides, and descriptions into `SettingItem` records and attaches a submenu that resolves the outer custom UI with the selected choice identifier. Runtime is O(n) in choice count. No external state is mutated.
153
+ * @param[in] theme {PiUsereqSettingsTheme} Callback-local pi theme adapter.
152
154
  * @param[in] choices {PiUsereqSettingsMenuChoice[]} Ordered menu-choice vector.
153
155
  * @param[in] done {(value?: string) => void} Outer custom-UI completion callback.
154
156
  * @return {SettingItem[]} `SettingsList` item vector.
155
157
  */
156
158
  function buildSettingItems(
159
+ theme: PiUsereqSettingsTheme,
157
160
  choices: PiUsereqSettingsMenuChoice[],
158
161
  done: (value?: string) => void,
159
162
  ): SettingItem[] {
@@ -161,7 +164,9 @@ function buildSettingItems(
161
164
  id: choice.id,
162
165
  label: choice.label,
163
166
  description: choice.description,
164
- currentValue: choice.value,
167
+ currentValue: choice.valueTone === "dim"
168
+ ? theme.fg("dim", choice.value)
169
+ : choice.value,
165
170
  submenu: () => createImmediateSelectionComponent(choice.id, done),
166
171
  }));
167
172
  }
@@ -188,7 +193,7 @@ export async function showPiUsereqSettingsMenu(
188
193
  0,
189
194
  );
190
195
  const settingsList = new SettingsList(
191
- buildSettingItems(choices, done),
196
+ buildSettingItems(theme, choices, done),
192
197
  Math.min(Math.max(choices.length, 1), 12),
193
198
  buildPiUsereqSettingsListTheme(theme),
194
199
  () => undefined,