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