pi-usereq 0.5.0 → 0.7.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 +1 -1
- package/package.json +1 -1
- package/pi-usereq/docs/REFERENCES.md +24470 -4590
- package/pi-usereq/docs/REQUIREMENTS.md +89 -68
- package/pi-usereq/docs/WORKFLOW.md +133 -106
- package/src/core/agent-tool-json.ts +42 -193
- package/src/core/compress-payload.ts +7 -15
- package/src/core/config.ts +84 -29
- package/src/core/extension-status.ts +49 -177
- package/src/core/find-payload.ts +21 -44
- package/src/core/pi-notify.ts +473 -313
- package/src/core/reference-payload.ts +6 -14
- package/src/core/settings-menu.ts +21 -1
- package/src/core/token-counter.ts +58 -124
- package/src/index.ts +798 -537
- package/src/resources/images/favicon.svg +21 -0
- package/src/resources/images/pi.dev.png +0 -0
- package/tests/debug-extension-harness.test.ts +17 -23
- package/tests/extension-registration.test.ts +632 -215
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.7.0"
|
|
12
12
|
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import type {
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
type FindToolScope,
|
|
53
53
|
} from "./core/find-payload.js";
|
|
54
54
|
import {
|
|
55
|
+
DEFAULT_SRC_DIRS,
|
|
55
56
|
getDefaultConfig,
|
|
56
57
|
getProjectConfigPath,
|
|
57
58
|
loadConfig,
|
|
@@ -61,21 +62,25 @@ import {
|
|
|
61
62
|
type UseReqConfig,
|
|
62
63
|
} from "./core/config.js";
|
|
63
64
|
import {
|
|
65
|
+
DEFAULT_PI_NOTIFY_CMD,
|
|
66
|
+
DEFAULT_PI_NOTIFY_PUSHOVER_TEXT,
|
|
67
|
+
DEFAULT_PI_NOTIFY_PUSHOVER_TITLE,
|
|
68
|
+
DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD,
|
|
69
|
+
DEFAULT_PI_NOTIFY_SOUND_LOW_CMD,
|
|
70
|
+
DEFAULT_PI_NOTIFY_SOUND_MID_CMD,
|
|
64
71
|
cyclePiNotifySoundLevel,
|
|
65
|
-
formatPiNotifyBeepStatus,
|
|
66
72
|
formatPiNotifyPushoverStatus,
|
|
73
|
+
formatPiNotifyStatus,
|
|
74
|
+
normalizePiNotifyCommand,
|
|
67
75
|
normalizePiNotifyPushoverCredential,
|
|
68
76
|
normalizePiNotifyPushoverPriority,
|
|
77
|
+
normalizePiNotifyTemplateValue,
|
|
69
78
|
runPiNotifyEffects,
|
|
79
|
+
type PiNotifyEventRequest,
|
|
70
80
|
type PiNotifyPushoverPriority,
|
|
71
|
-
type PiNotifyPushoverRequest,
|
|
72
81
|
type PiNotifySoundLevel,
|
|
73
82
|
} from "./core/pi-notify.js";
|
|
74
|
-
import {
|
|
75
|
-
buildRuntimePathContext,
|
|
76
|
-
buildRuntimePathFacts,
|
|
77
|
-
formatRuntimePathForDisplay,
|
|
78
|
-
} from "./core/path-context.js";
|
|
83
|
+
import { formatRuntimePathForDisplay } from "./core/path-context.js";
|
|
79
84
|
import { resolveRuntimeGitPath } from "./core/runtime-project-paths.js";
|
|
80
85
|
import { showPiUsereqSettingsMenu, type PiUsereqSettingsMenuChoice } from "./core/settings-menu.js";
|
|
81
86
|
import {
|
|
@@ -108,9 +113,7 @@ import {
|
|
|
108
113
|
} from "./core/tool-runner.js";
|
|
109
114
|
import { LANGUAGE_TAGS } from "./core/find-constructs.js";
|
|
110
115
|
import {
|
|
111
|
-
STATIC_CHECK_MODULES,
|
|
112
116
|
getSupportedStaticCheckLanguageSupport,
|
|
113
|
-
parseEnableStaticCheck,
|
|
114
117
|
} from "./core/static-check.js";
|
|
115
118
|
import { makeRelativeIfContainsProject, shellSplit } from "./core/utils.js";
|
|
116
119
|
|
|
@@ -164,20 +167,6 @@ function getProjectBase(cwd: string): string {
|
|
|
164
167
|
return path.resolve(cwd);
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
/**
|
|
168
|
-
* @brief Builds the shared runtime path facts for the current command or tool context.
|
|
169
|
-
* @details Derives installation, execution, base, config, resource, docs, test, source, and optional git paths from the cwd-derived project configuration plus runtime-only repository probing, then converts them into prompt/tool-facing strings. Runtime is O(s + p) where s is configured source-directory count and p is aggregate path length. Side effects are limited to git subprocess execution.
|
|
170
|
-
* @param[in] cwd {string} Current working directory.
|
|
171
|
-
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
172
|
-
* @return {import("./core/path-context.js").RuntimePathFacts} Shared runtime path facts.
|
|
173
|
-
* @satisfies REQ-145, REQ-146
|
|
174
|
-
*/
|
|
175
|
-
function buildSharedRuntimePathFacts(cwd: string, config: UseReqConfig): import("./core/path-context.js").RuntimePathFacts {
|
|
176
|
-
const projectBase = getProjectBase(cwd);
|
|
177
|
-
const gitPath = resolveRuntimeGitPath(projectBase);
|
|
178
|
-
return buildRuntimePathFacts(buildRuntimePathContext(projectBase, config, { gitPath }));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
170
|
/**
|
|
182
171
|
* @brief Loads project configuration for the extension runtime.
|
|
183
172
|
* @details Resolves the project base, loads persisted config, and normalizes configured directory paths without reading or persisting runtime-derived `base-path` or `git-path` metadata. Runtime is dominated by config I/O. Side effects are limited to filesystem reads.
|
|
@@ -205,15 +194,17 @@ function saveProjectConfig(cwd: string, config: UseReqConfig): void {
|
|
|
205
194
|
|
|
206
195
|
/**
|
|
207
196
|
* @brief Formats the current project config path for top-level menu display.
|
|
208
|
-
* @details Resolves `<base-path>/.pi-usereq/config.json` from the cwd-derived
|
|
209
|
-
* project base and formats it relative to the user home when possible. Runtime
|
|
210
|
-
* is O(p) in path length. No external state is mutated.
|
|
197
|
+
* @details Resolves `<base-path>/.pi-usereq/config.json` from the cwd-derived project base, reuses the shared runtime-path formatter, and rewrites a leading POSIX `$HOME` token to `~` for the `Show configuration` row only. Runtime is O(p) in path length. No external state is mutated.
|
|
211
198
|
* @param[in] cwd {string} Current working directory.
|
|
212
|
-
* @return {string}
|
|
199
|
+
* @return {string} `~`-relative or absolute config path display value.
|
|
213
200
|
* @satisfies REQ-162
|
|
214
201
|
*/
|
|
215
202
|
function formatProjectConfigPathForMenu(cwd: string): string {
|
|
216
|
-
|
|
203
|
+
const displayPath = formatRuntimePathForDisplay(getProjectConfigPath(getProjectBase(cwd)));
|
|
204
|
+
if (displayPath === "$HOME") {
|
|
205
|
+
return "~";
|
|
206
|
+
}
|
|
207
|
+
return displayPath.startsWith("$HOME/") ? `~/${displayPath.slice("$HOME/".length)}` : displayPath;
|
|
217
208
|
}
|
|
218
209
|
|
|
219
210
|
/**
|
|
@@ -263,10 +254,12 @@ function collectProjectStaticCheckSelection(
|
|
|
263
254
|
* @return {string} Newline-delimited execution diagnostics.
|
|
264
255
|
*/
|
|
265
256
|
function buildTokenToolExecutionStderr(payload: TokenToolPayload): string {
|
|
266
|
-
const skippedLines = payload.
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
257
|
+
const skippedLines = payload.files
|
|
258
|
+
.filter((entry) => entry.status === "skipped" && entry.error_message)
|
|
259
|
+
.map((entry) => `skipped: ${entry.canonical_path}: ${entry.error_message!}`);
|
|
260
|
+
const errorLines = payload.files
|
|
261
|
+
.filter((entry) => entry.status === "error" && entry.error_message)
|
|
262
|
+
.map((entry) => `error: ${entry.canonical_path}: ${entry.error_message!}`);
|
|
270
263
|
return [...skippedLines, ...errorLines].join("\n");
|
|
271
264
|
}
|
|
272
265
|
|
|
@@ -284,10 +277,8 @@ function buildTokenToolExecuteResult(
|
|
|
284
277
|
details: TokenToolPayload & { execution: { code: number; stderr: string } };
|
|
285
278
|
} {
|
|
286
279
|
const details = {
|
|
287
|
-
request: payload.request,
|
|
288
280
|
summary: payload.summary,
|
|
289
281
|
files: payload.files,
|
|
290
|
-
guidance: payload.guidance,
|
|
291
282
|
execution: {
|
|
292
283
|
code: payload.summary.counted_file_count > 0 ? 0 : 1,
|
|
293
284
|
stderr: buildTokenToolExecutionStderr(payload),
|
|
@@ -296,6 +287,43 @@ function buildTokenToolExecuteResult(
|
|
|
296
287
|
return buildStructuredToolExecuteResult(details);
|
|
297
288
|
}
|
|
298
289
|
|
|
290
|
+
/**
|
|
291
|
+
* @brief Builds the fallback execute result returned when token counting fails before payload construction.
|
|
292
|
+
* @details Normalizes one thrown `ReqError` into the stable token-tool response shape so missing runtime dependencies or other pre-count failures do not abort extension tool execution. Runtime is O(1). No external state is mutated.
|
|
293
|
+
* @param[in] error {unknown} Thrown token-tool failure.
|
|
294
|
+
* @return {{ content: Array<{ type: "text"; text: string }>; details: TokenToolPayload & { execution: { code: number; stderr: string } } }} Structured token-tool failure result.
|
|
295
|
+
*/
|
|
296
|
+
function buildFailedTokenToolExecuteResult(
|
|
297
|
+
error: unknown,
|
|
298
|
+
): {
|
|
299
|
+
content: Array<{ type: "text"; text: string }>;
|
|
300
|
+
details: TokenToolPayload & { execution: { code: number; stderr: string } };
|
|
301
|
+
} {
|
|
302
|
+
const result = normalizeToolFailure(error);
|
|
303
|
+
const details = {
|
|
304
|
+
summary: {
|
|
305
|
+
processable_file_count: 0,
|
|
306
|
+
counted_file_count: 0,
|
|
307
|
+
error_file_count: 0,
|
|
308
|
+
skipped_file_count: 0,
|
|
309
|
+
total_token_count: 0,
|
|
310
|
+
total_character_count: 0,
|
|
311
|
+
total_byte_count: 0,
|
|
312
|
+
total_line_count: 0,
|
|
313
|
+
average_token_count_per_counted_file: 0,
|
|
314
|
+
average_character_count_per_counted_file: 0,
|
|
315
|
+
average_byte_count_per_counted_file: 0,
|
|
316
|
+
average_line_count_per_counted_file: 0,
|
|
317
|
+
},
|
|
318
|
+
files: [],
|
|
319
|
+
execution: {
|
|
320
|
+
code: result.code,
|
|
321
|
+
stderr: result.stderr,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
return buildStructuredToolExecuteResult(details);
|
|
325
|
+
}
|
|
326
|
+
|
|
299
327
|
/**
|
|
300
328
|
* @brief Builds the agent-oriented execute result returned by references tools.
|
|
301
329
|
* @details Mirrors the structured references payload into both the text `content` channel and the machine-readable `details` channel while isolating execution metadata under `execution`. Runtime is O(n) in payload size. No side effects occur.
|
|
@@ -310,7 +338,6 @@ function buildReferenceToolExecuteResult(
|
|
|
310
338
|
details: ReferenceToolPayload & { execution: { code: number; stderr: string } };
|
|
311
339
|
} {
|
|
312
340
|
const details = {
|
|
313
|
-
request: payload.request,
|
|
314
341
|
summary: payload.summary,
|
|
315
342
|
repository: payload.repository,
|
|
316
343
|
files: payload.files,
|
|
@@ -336,7 +363,6 @@ function buildCompressionToolExecuteResult(
|
|
|
336
363
|
details: CompressToolPayload & { execution: { code: number; stderr: string } };
|
|
337
364
|
} {
|
|
338
365
|
const details = {
|
|
339
|
-
request: payload.request,
|
|
340
366
|
summary: payload.summary,
|
|
341
367
|
repository: payload.repository,
|
|
342
368
|
files: payload.files,
|
|
@@ -423,7 +449,7 @@ function buildFindToolSchemaDescription(scope: FindToolScope): string {
|
|
|
423
449
|
const inputContract = scope === "explicit-files"
|
|
424
450
|
? "Input contract: tag + pattern + files[] + optional enableLineNumbers."
|
|
425
451
|
: "Input contract: tag + pattern + optional enableLineNumbers. Scope is the configured src-dir list resolved from the current project configuration.";
|
|
426
|
-
return `${inputContract} Output contract: JSON object with
|
|
452
|
+
return `${inputContract} Output contract: JSON object with summary, repository, files, and execution. Static supported-tag matrices are documented in tool registration metadata instead of runtime responses. File entries expose structured statuses, file_doxygen, and match records with typed line ranges, stripped code lines, and structured Doxygen fields. Regex matches construct names only.`;
|
|
427
453
|
}
|
|
428
454
|
|
|
429
455
|
/**
|
|
@@ -437,13 +463,13 @@ function buildFindToolPromptGuidelines(scope: FindToolScope): string[] {
|
|
|
437
463
|
? "Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute."
|
|
438
464
|
: "Scope: resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.";
|
|
439
465
|
const outputLine = scope === "explicit-files"
|
|
440
|
-
? "Output contract:
|
|
441
|
-
: "Output contract:
|
|
466
|
+
? "Output contract: summary + repository + files + execution. Repository exposes requested file scope only when it adds dynamic search context; supported tags remain documented in registration metadata. File entries expose status, line ranges, file_doxygen, and matches; match entries expose symbol_kind, signature_text, line ranges, code_lines, stripped_source_text, and structured Doxygen fields."
|
|
467
|
+
: "Output contract: summary + repository + files + execution. Repository exposes source_directory_paths and file_canonical_paths when project-scope search context varies; supported tags remain documented in registration metadata. File entries expose status, line ranges, file_doxygen, and matches; match entries expose symbol_kind, signature_text, line ranges, code_lines, stripped_source_text, and structured Doxygen fields.";
|
|
442
468
|
return [
|
|
443
469
|
scopeLine,
|
|
444
470
|
outputLine,
|
|
445
471
|
"Regex rule: pattern is applied to construct names only with JavaScript RegExp search semantics; it never matches construct bodies; use ^...$ for exact-name matching.",
|
|
446
|
-
"Tag rule: tag is pipe-separated and case-insensitive; unsupported tags are ignored; if no valid tag remains,
|
|
472
|
+
"Tag rule: tag is pipe-separated and case-insensitive; unsupported tags are ignored; if no valid tag remains, the response search_status becomes invalid_tag_filter.",
|
|
447
473
|
"Line-number behavior: enableLineNumbers changes only display_text and stripped_source_text rendering; numeric source_line_number and line_range facts remain dedicated fields.",
|
|
448
474
|
"Failure contract: invalid tag filters, invalid regex patterns, unsupported extensions, unsupported tag-language combinations, no-match files, and analysis failures are surfaced as structured statuses plus optional execution.stderr diagnostics.",
|
|
449
475
|
...buildFindToolSupportedTagGuidelines(),
|
|
@@ -465,7 +491,6 @@ function buildFindToolExecuteResult(
|
|
|
465
491
|
} {
|
|
466
492
|
const stderr = buildFindToolExecutionStderr(payload);
|
|
467
493
|
const details = {
|
|
468
|
-
request: payload.request,
|
|
469
494
|
summary: payload.summary,
|
|
470
495
|
repository: payload.repository,
|
|
471
496
|
files: payload.files,
|
|
@@ -515,19 +540,6 @@ function getConfiguredEnabledPiUsereqTools(config: UseReqConfig): string[] {
|
|
|
515
540
|
return enabledTools;
|
|
516
541
|
}
|
|
517
542
|
|
|
518
|
-
/**
|
|
519
|
-
* @brief Classifies one configurable tool as embedded or extension-owned.
|
|
520
|
-
* @details Uses the runtime `sourceInfo.source` field plus the supported embedded-name subset to produce one stable UI label. Runtime is O(1). No external state is mutated.
|
|
521
|
-
* @param[in] tool {ToolInfo} Runtime tool descriptor.
|
|
522
|
-
* @return {"builtin" | "extension"} Stable tool-kind label.
|
|
523
|
-
*/
|
|
524
|
-
function getPiUsereqToolKind(tool: ToolInfo): "builtin" | "extension" {
|
|
525
|
-
if (tool.sourceInfo?.source === "builtin" && isPiUsereqEmbeddedToolName(tool.name)) {
|
|
526
|
-
return "builtin";
|
|
527
|
-
}
|
|
528
|
-
return "extension";
|
|
529
|
-
}
|
|
530
|
-
|
|
531
543
|
/**
|
|
532
544
|
* @brief Applies the configured active-tool enablement to the current session.
|
|
533
545
|
* @details Preserves non-configurable active tools, removes every configurable tool from the active set, then re-adds only configured tools that exist in the current runtime inventory. Runtime is O(t). Side effects include `pi.setActiveTools(...)`.
|
|
@@ -560,20 +572,19 @@ function applyConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig): v
|
|
|
560
572
|
* @details Applies session-start-specific resource validation, project-config
|
|
561
573
|
* refresh, and startup-tool enablement before forwarding the originating hook
|
|
562
574
|
* name and payload into the shared `updateExtensionStatus(...)` pipeline.
|
|
563
|
-
* On `agent_end`, also dispatches configured
|
|
575
|
+
* On `agent_end`, also dispatches configured command-notify, sound, and
|
|
564
576
|
* prompt-specific Pushover effects when the current run originates from a
|
|
565
577
|
* bundled prompt command. Runtime is dominated by configuration loading during
|
|
566
578
|
* `session_start`; all other hooks are O(1). Side effects include resource
|
|
567
579
|
* checks, active-tool mutation, status updates, live-ticker disposal on
|
|
568
|
-
* shutdown,
|
|
569
|
-
* HTTPS requests.
|
|
580
|
+
* shutdown, optional child-process spawning, and outbound HTTPS requests.
|
|
570
581
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
571
582
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
572
583
|
* @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
|
|
573
584
|
* @param[in] event {unknown} Hook payload forwarded by pi.
|
|
574
585
|
* @param[in] ctx {ExtensionContext} Active extension context.
|
|
575
586
|
* @return {Promise<void>} Promise resolved when hook processing completes.
|
|
576
|
-
* @satisfies REQ-117, REQ-118, REQ-119, REQ-
|
|
587
|
+
* @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
|
|
577
588
|
*/
|
|
578
589
|
async function handleExtensionStatusEvent(
|
|
579
590
|
pi: ExtensionAPI,
|
|
@@ -582,6 +593,16 @@ async function handleExtensionStatusEvent(
|
|
|
582
593
|
event: unknown,
|
|
583
594
|
ctx: ExtensionContext,
|
|
584
595
|
): Promise<void> {
|
|
596
|
+
const notifyRequest: PiNotifyEventRequest | undefined = hookName === "agent_end"
|
|
597
|
+
&& statusController.state.activePromptRequest !== undefined
|
|
598
|
+
&& statusController.state.runStartTimeMs !== undefined
|
|
599
|
+
? {
|
|
600
|
+
promptName: statusController.state.activePromptRequest.promptName,
|
|
601
|
+
promptArgs: statusController.state.activePromptRequest.promptArgs,
|
|
602
|
+
basePath: path.resolve(ctx.cwd),
|
|
603
|
+
completionTimeMs: Math.max(0, Date.now() - statusController.state.runStartTimeMs),
|
|
604
|
+
}
|
|
605
|
+
: undefined;
|
|
585
606
|
if (hookName === "session_start") {
|
|
586
607
|
ensureBundledResourcesAccessible();
|
|
587
608
|
const config = loadProjectConfig(ctx.cwd);
|
|
@@ -591,19 +612,10 @@ async function handleExtensionStatusEvent(
|
|
|
591
612
|
updateExtensionStatus(statusController, hookName, event, ctx);
|
|
592
613
|
if (hookName === "agent_end") {
|
|
593
614
|
if (statusController.config) {
|
|
594
|
-
const pushoverRequest: PiNotifyPushoverRequest | undefined = statusController.state.activePromptRequest
|
|
595
|
-
&& statusController.state.lastRunDurationMs !== undefined
|
|
596
|
-
? {
|
|
597
|
-
promptName: statusController.state.activePromptRequest.promptName,
|
|
598
|
-
promptArgs: statusController.state.activePromptRequest.promptArgs,
|
|
599
|
-
basePath: path.resolve(ctx.cwd),
|
|
600
|
-
completionTimeMs: statusController.state.lastRunDurationMs,
|
|
601
|
-
}
|
|
602
|
-
: undefined;
|
|
603
615
|
runPiNotifyEffects(
|
|
604
616
|
statusController.config,
|
|
605
617
|
event as { messages: AgentEndEvent["messages"] },
|
|
606
|
-
|
|
618
|
+
notifyRequest,
|
|
607
619
|
);
|
|
608
620
|
}
|
|
609
621
|
statusController.state.activePromptRequest = undefined;
|
|
@@ -653,131 +665,397 @@ function setConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig, enab
|
|
|
653
665
|
}
|
|
654
666
|
|
|
655
667
|
/**
|
|
656
|
-
* @brief
|
|
657
|
-
* @details
|
|
658
|
-
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
659
|
-
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
660
|
-
* @return {string} Multiline tool-status report.
|
|
668
|
+
* @brief Represents one persisted boolean notification-setting key.
|
|
669
|
+
* @details Restricts menu toggles to the global enable flags and completed/interrupted/failed event toggles used by command-notify, sound, and Pushover configuration. Compile-time only and introduces no runtime cost.
|
|
661
670
|
*/
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
671
|
+
type PiNotifyBooleanConfigKey =
|
|
672
|
+
| "notify-enabled"
|
|
673
|
+
| "notify-on-completed"
|
|
674
|
+
| "notify-on-interrupted"
|
|
675
|
+
| "notify-on-failed"
|
|
676
|
+
| "notify-sound-on-completed"
|
|
677
|
+
| "notify-sound-on-interrupted"
|
|
678
|
+
| "notify-sound-on-failed"
|
|
679
|
+
| "notify-pushover-enabled"
|
|
680
|
+
| "notify-pushover-on-completed"
|
|
681
|
+
| "notify-pushover-on-interrupted"
|
|
682
|
+
| "notify-pushover-on-failed";
|
|
674
683
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
lines.push(` source: ${source}`);
|
|
684
|
-
if (tool.description) {
|
|
685
|
-
lines.push(` description: ${tool.description}`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
684
|
+
/**
|
|
685
|
+
* @brief Represents one persisted boolean notification event-toggle key.
|
|
686
|
+
* @details Restricts shared event-submenu mutation helpers to completed/interrupted/failed toggles and excludes global enable flags. Compile-time only and introduces no runtime cost.
|
|
687
|
+
*/
|
|
688
|
+
type PiNotifyEventBooleanConfigKey = Exclude<
|
|
689
|
+
PiNotifyBooleanConfigKey,
|
|
690
|
+
"notify-enabled" | "notify-pushover-enabled"
|
|
691
|
+
>;
|
|
688
692
|
|
|
689
|
-
|
|
693
|
+
/**
|
|
694
|
+
* @brief Represents one shared prompt-end event identifier used by notification menus.
|
|
695
|
+
* @details Restricts event-submenu rendering to the canonical completed/interrupted/failed domain shared by command-notify, sound, and Pushover routing. Compile-time only and introduces no runtime cost.
|
|
696
|
+
*/
|
|
697
|
+
type PiNotifyEventId = "completed" | "interrupted" | "failed";
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* @brief Describes one shared prompt-end event row rendered inside notification event submenus.
|
|
701
|
+
* @details Binds one canonical event identifier to the human-readable label and terminal-outcome description reused across command-notify, sound, and Pushover event menus. The interface is compile-time only and introduces no runtime cost.
|
|
702
|
+
*/
|
|
703
|
+
interface PiNotifyEventRowDefinition {
|
|
704
|
+
eventId: PiNotifyEventId;
|
|
705
|
+
label: string;
|
|
706
|
+
description: string;
|
|
690
707
|
}
|
|
691
708
|
|
|
692
709
|
/**
|
|
693
|
-
* @brief
|
|
694
|
-
* @details
|
|
695
|
-
* flags stored in project configuration. Compile-time only and introduces no
|
|
696
|
-
* runtime cost.
|
|
710
|
+
* @brief Describes one notification-system event submenu contract.
|
|
711
|
+
* @details Binds the top-level launcher row, submenu title, toast prefix, and completed/interrupted/failed config keys for one notification transport. The interface is compile-time only and introduces no runtime cost.
|
|
697
712
|
*/
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
713
|
+
interface PiNotifyEventMenuDefinition {
|
|
714
|
+
topLevelId: string;
|
|
715
|
+
topLevelLabel: string;
|
|
716
|
+
submenuTitle: string;
|
|
717
|
+
systemLabel: string;
|
|
718
|
+
keys: Record<PiNotifyEventId, PiNotifyEventBooleanConfigKey>;
|
|
719
|
+
}
|
|
702
720
|
|
|
703
721
|
/**
|
|
704
|
-
* @brief Flips one persisted
|
|
705
|
-
* @details Negates the selected
|
|
722
|
+
* @brief Flips one persisted boolean notification setting.
|
|
723
|
+
* @details Negates the selected configuration flag in place and returns the resulting boolean value so callers can emit deterministic UI feedback. Runtime is O(1). Side effect: mutates `config`.
|
|
706
724
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
707
|
-
* @param[in] key {
|
|
725
|
+
* @param[in] key {PiNotifyBooleanConfigKey} Boolean configuration key to toggle.
|
|
708
726
|
* @return {boolean} Next enabled state.
|
|
709
727
|
*/
|
|
710
|
-
function
|
|
728
|
+
function togglePiNotifyFlag(config: UseReqConfig, key: PiNotifyBooleanConfigKey): boolean {
|
|
711
729
|
config[key] = !config[key];
|
|
712
730
|
return config[key];
|
|
713
731
|
}
|
|
714
732
|
|
|
733
|
+
/**
|
|
734
|
+
* @brief Restores notification-related settings to their documented defaults.
|
|
735
|
+
* @details Copies the command-notify, sound, and Pushover configuration subtree from a fresh default config into the supplied mutable project config. Runtime is O(1). Side effect: mutates `config`.
|
|
736
|
+
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
737
|
+
* @return {void} No return value.
|
|
738
|
+
* @satisfies REQ-174, REQ-178, REQ-184, REQ-195, REQ-196
|
|
739
|
+
*/
|
|
740
|
+
function resetPiNotifyConfigToDefaults(config: UseReqConfig): void {
|
|
741
|
+
const defaults = getDefaultConfig("");
|
|
742
|
+
config["notify-enabled"] = defaults["notify-enabled"];
|
|
743
|
+
config["notify-on-completed"] = defaults["notify-on-completed"];
|
|
744
|
+
config["notify-on-interrupted"] = defaults["notify-on-interrupted"];
|
|
745
|
+
config["notify-on-failed"] = defaults["notify-on-failed"];
|
|
746
|
+
config["notify-sound"] = defaults["notify-sound"];
|
|
747
|
+
config["notify-sound-on-completed"] = defaults["notify-sound-on-completed"];
|
|
748
|
+
config["notify-sound-on-interrupted"] = defaults["notify-sound-on-interrupted"];
|
|
749
|
+
config["notify-sound-on-failed"] = defaults["notify-sound-on-failed"];
|
|
750
|
+
config["notify-sound-toggle-shortcut"] = defaults["notify-sound-toggle-shortcut"];
|
|
751
|
+
config["notify-pushover-enabled"] = defaults["notify-pushover-enabled"];
|
|
752
|
+
config["notify-pushover-on-completed"] = defaults["notify-pushover-on-completed"];
|
|
753
|
+
config["notify-pushover-on-interrupted"] = defaults["notify-pushover-on-interrupted"];
|
|
754
|
+
config["notify-pushover-on-failed"] = defaults["notify-pushover-on-failed"];
|
|
755
|
+
config["notify-pushover-user-key"] = defaults["notify-pushover-user-key"];
|
|
756
|
+
config["notify-pushover-api-token"] = defaults["notify-pushover-api-token"];
|
|
757
|
+
config["notify-pushover-priority"] = defaults["notify-pushover-priority"];
|
|
758
|
+
config["notify-pushover-title"] = defaults["notify-pushover-title"];
|
|
759
|
+
config["notify-pushover-text"] = defaults["notify-pushover-text"];
|
|
760
|
+
config.PI_NOTIFY_CMD = defaults.PI_NOTIFY_CMD;
|
|
761
|
+
config.PI_NOTIFY_SOUND_LOW_CMD = defaults.PI_NOTIFY_SOUND_LOW_CMD;
|
|
762
|
+
config.PI_NOTIFY_SOUND_MID_CMD = defaults.PI_NOTIFY_SOUND_MID_CMD;
|
|
763
|
+
config.PI_NOTIFY_SOUND_HIGH_CMD = defaults.PI_NOTIFY_SOUND_HIGH_CMD;
|
|
764
|
+
}
|
|
765
|
+
|
|
715
766
|
/**
|
|
716
767
|
* @brief Formats one persisted Pushover priority for menu display.
|
|
717
|
-
* @details Maps the canonical `0|1` priority domain to deterministic
|
|
768
|
+
* @details Maps the canonical `0|1` priority domain to deterministic `Normal|High` labels reused by the Pushover configuration UI. Runtime is O(1). No external state is mutated.
|
|
718
769
|
* @param[in] priority {PiNotifyPushoverPriority} Persisted Pushover priority.
|
|
719
770
|
* @return {string} Menu-display label.
|
|
720
|
-
* @satisfies REQ-
|
|
771
|
+
* @satisfies REQ-172
|
|
721
772
|
*/
|
|
722
773
|
function formatPiNotifyPushoverPriority(priority: PiNotifyPushoverPriority): string {
|
|
723
|
-
return priority === 1 ? "
|
|
774
|
+
return priority === 1 ? "High" : "Normal";
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* @brief Defines the shared prompt-end event rows reused across notification submenus.
|
|
779
|
+
* @details Encodes the human-readable completed/interrupted/failed labels and terminal-state descriptions required by the command-notify, sound, and Pushover event menus. Access complexity is O(1).
|
|
780
|
+
* @satisfies REQ-188, REQ-198
|
|
781
|
+
*/
|
|
782
|
+
const PI_NOTIFY_EVENT_ROW_DEFINITIONS: PiNotifyEventRowDefinition[] = [
|
|
783
|
+
{
|
|
784
|
+
eventId: "completed",
|
|
785
|
+
label: "Prompt completed",
|
|
786
|
+
description: "Toggle delivery when the prompt finishes without interruption or failure.",
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
eventId: "interrupted",
|
|
790
|
+
label: "Prompt interrupted",
|
|
791
|
+
description: "Toggle delivery when the prompt finishes with assistant stopReason `aborted`.",
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
eventId: "failed",
|
|
795
|
+
label: "Prompt failed",
|
|
796
|
+
description: "Toggle delivery when the prompt finishes with assistant stopReason `error`.",
|
|
797
|
+
},
|
|
798
|
+
];
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* @brief Defines the shared event-submenu contracts for command-notify, sound, and Pushover.
|
|
802
|
+
* @details Binds each notification transport to its top-level launcher row, submenu title, toast prefix, and completed/interrupted/failed config-key set. Access complexity is O(1).
|
|
803
|
+
* @satisfies REQ-174, REQ-178, REQ-184, REQ-198
|
|
804
|
+
*/
|
|
805
|
+
const PI_NOTIFY_EVENT_MENU_DEFINITIONS: Record<
|
|
806
|
+
"notification" | "sound" | "pushover",
|
|
807
|
+
PiNotifyEventMenuDefinition
|
|
808
|
+
> = {
|
|
809
|
+
notification: {
|
|
810
|
+
topLevelId: "notification-events",
|
|
811
|
+
topLevelLabel: "Notification events",
|
|
812
|
+
submenuTitle: "Notification events",
|
|
813
|
+
systemLabel: "Notification",
|
|
814
|
+
keys: {
|
|
815
|
+
completed: "notify-on-completed",
|
|
816
|
+
interrupted: "notify-on-interrupted",
|
|
817
|
+
failed: "notify-on-failed",
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
sound: {
|
|
821
|
+
topLevelId: "sound-events",
|
|
822
|
+
topLevelLabel: "Sound events",
|
|
823
|
+
submenuTitle: "Sound events",
|
|
824
|
+
systemLabel: "Sound",
|
|
825
|
+
keys: {
|
|
826
|
+
completed: "notify-sound-on-completed",
|
|
827
|
+
interrupted: "notify-sound-on-interrupted",
|
|
828
|
+
failed: "notify-sound-on-failed",
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
pushover: {
|
|
832
|
+
topLevelId: "pushover-events",
|
|
833
|
+
topLevelLabel: "Pushover events",
|
|
834
|
+
submenuTitle: "Pushover events",
|
|
835
|
+
systemLabel: "Pushover",
|
|
836
|
+
keys: {
|
|
837
|
+
completed: "notify-pushover-on-completed",
|
|
838
|
+
interrupted: "notify-pushover-on-interrupted",
|
|
839
|
+
failed: "notify-pushover-on-failed",
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* @brief Formats the top-level summary value for one notification event submenu.
|
|
846
|
+
* @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.
|
|
847
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
848
|
+
* @param[in] eventMenu {PiNotifyEventMenuDefinition} Notification-system event submenu contract.
|
|
849
|
+
* @return {string} Compact enabled-toggle summary.
|
|
850
|
+
* @satisfies REQ-198
|
|
851
|
+
*/
|
|
852
|
+
function formatPiNotifyEventMenuSummary(
|
|
853
|
+
config: UseReqConfig,
|
|
854
|
+
eventMenu: PiNotifyEventMenuDefinition,
|
|
855
|
+
): string {
|
|
856
|
+
const enabledCount = PI_NOTIFY_EVENT_ROW_DEFINITIONS.filter(
|
|
857
|
+
(row) => config[eventMenu.keys[row.eventId]] === true,
|
|
858
|
+
).length;
|
|
859
|
+
return `${enabledCount}/3 on`;
|
|
724
860
|
}
|
|
725
861
|
|
|
726
862
|
/**
|
|
727
|
-
* @brief Builds the
|
|
728
|
-
* @details
|
|
863
|
+
* @brief Builds the top-level launcher row for one notification event submenu.
|
|
864
|
+
* @details Reuses the shared completed/interrupted/failed summary renderer so the `Notifications` menu can expose dedicated event editors for command-notify, sound, and Pushover in a uniform shape. Runtime is O(1). No external state is mutated.
|
|
729
865
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
730
|
-
* @
|
|
731
|
-
* @
|
|
866
|
+
* @param[in] eventMenu {PiNotifyEventMenuDefinition} Notification-system event submenu contract.
|
|
867
|
+
* @return {PiUsereqSettingsMenuChoice} Launcher row for the selected event submenu.
|
|
868
|
+
* @satisfies REQ-181, REQ-183, REQ-165, REQ-198
|
|
732
869
|
*/
|
|
733
|
-
function
|
|
870
|
+
function buildPiNotifyEventLauncherChoice(
|
|
871
|
+
config: UseReqConfig,
|
|
872
|
+
eventMenu: PiNotifyEventMenuDefinition,
|
|
873
|
+
): PiUsereqSettingsMenuChoice {
|
|
874
|
+
return {
|
|
875
|
+
id: eventMenu.topLevelId,
|
|
876
|
+
label: eventMenu.topLevelLabel,
|
|
877
|
+
value: formatPiNotifyEventMenuSummary(config, eventMenu),
|
|
878
|
+
description: `Open the ${eventMenu.submenuTitle} submenu for shared prompt-end delivery events.`,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* @brief Builds the shared settings-menu choices for one notification event submenu.
|
|
884
|
+
* @details Serializes completed/interrupted/failed rows with right-aligned `on|off` values, then appends `Reset defaults` and `Save and close` for submenu-scoped mutation control. Runtime is O(1). No external state is mutated.
|
|
885
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
886
|
+
* @param[in] eventMenu {PiNotifyEventMenuDefinition} Notification-system event submenu contract.
|
|
887
|
+
* @return {PiUsereqSettingsMenuChoice[]} Ordered event-submenu choice vector.
|
|
888
|
+
* @satisfies REQ-188, REQ-193, REQ-198
|
|
889
|
+
*/
|
|
890
|
+
function buildPiNotifyEventMenuChoices(
|
|
891
|
+
config: UseReqConfig,
|
|
892
|
+
eventMenu: PiNotifyEventMenuDefinition,
|
|
893
|
+
): PiUsereqSettingsMenuChoice[] {
|
|
734
894
|
return [
|
|
895
|
+
...PI_NOTIFY_EVENT_ROW_DEFINITIONS.map((row) => ({
|
|
896
|
+
id: eventMenu.keys[row.eventId],
|
|
897
|
+
label: row.label,
|
|
898
|
+
value: config[eventMenu.keys[row.eventId]] ? "on" : "off",
|
|
899
|
+
description: `${eventMenu.systemLabel}: ${row.description}`,
|
|
900
|
+
})),
|
|
735
901
|
{
|
|
736
|
-
id: "
|
|
737
|
-
label: "
|
|
738
|
-
value:
|
|
739
|
-
description:
|
|
902
|
+
id: "reset-defaults",
|
|
903
|
+
label: "Reset defaults",
|
|
904
|
+
value: "",
|
|
905
|
+
description: `Restore the documented default ${eventMenu.systemLabel.toLowerCase()} event toggles.`,
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
id: "save-and-close",
|
|
909
|
+
label: "Save and close",
|
|
910
|
+
value: "",
|
|
911
|
+
description: "Return to the notifications menu.",
|
|
740
912
|
},
|
|
913
|
+
];
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* @brief Restores one notification event submenu to its documented defaults.
|
|
918
|
+
* @details Copies only the completed/interrupted/failed toggles referenced by the supplied submenu contract from a fresh default config into the mutable project config. Runtime is O(1). Side effect: mutates `config`.
|
|
919
|
+
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
920
|
+
* @param[in] eventMenu {PiNotifyEventMenuDefinition} Notification-system event submenu contract.
|
|
921
|
+
* @return {void} No return value.
|
|
922
|
+
* @satisfies REQ-174, REQ-178, REQ-184, REQ-195
|
|
923
|
+
*/
|
|
924
|
+
function resetPiNotifyEventMenuToDefaults(
|
|
925
|
+
config: UseReqConfig,
|
|
926
|
+
eventMenu: PiNotifyEventMenuDefinition,
|
|
927
|
+
): void {
|
|
928
|
+
const defaults = getDefaultConfig("");
|
|
929
|
+
for (const key of Object.values(eventMenu.keys) as PiNotifyEventBooleanConfigKey[]) {
|
|
930
|
+
config[key] = defaults[key];
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* @brief Resolves the human-readable event label for one event-toggle config key.
|
|
936
|
+
* @details Matches the supplied config key against the submenu contract and returns the corresponding completed/interrupted/failed menu label for deterministic notification toasts. Runtime is O(1). No external state is mutated.
|
|
937
|
+
* @param[in] key {PiNotifyEventBooleanConfigKey} Event-toggle configuration key.
|
|
938
|
+
* @param[in] eventMenu {PiNotifyEventMenuDefinition} Notification-system event submenu contract.
|
|
939
|
+
* @return {string} Human-readable event label.
|
|
940
|
+
* @satisfies REQ-188, REQ-198
|
|
941
|
+
*/
|
|
942
|
+
function resolvePiNotifyEventLabel(
|
|
943
|
+
key: PiNotifyEventBooleanConfigKey,
|
|
944
|
+
eventMenu: PiNotifyEventMenuDefinition,
|
|
945
|
+
): string {
|
|
946
|
+
return PI_NOTIFY_EVENT_ROW_DEFINITIONS.find(
|
|
947
|
+
(row) => eventMenu.keys[row.eventId] === key,
|
|
948
|
+
)?.label ?? key;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* @brief Runs one dedicated notification event submenu.
|
|
953
|
+
* @details Reuses the shared settings-menu renderer to toggle completed/interrupted/failed delivery flags, preserve row focus, and apply submenu-scoped reset semantics for command-notify, sound, or Pushover events. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
954
|
+
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
955
|
+
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
956
|
+
* @param[in] eventMenu {PiNotifyEventMenuDefinition} Notification-system event submenu contract.
|
|
957
|
+
* @return {Promise<void>} Promise resolved when the submenu closes.
|
|
958
|
+
* @satisfies REQ-188, REQ-192, REQ-193, REQ-195, REQ-198
|
|
959
|
+
*/
|
|
960
|
+
async function configurePiNotifyEventMenu(
|
|
961
|
+
ctx: ExtensionCommandContext,
|
|
962
|
+
config: UseReqConfig,
|
|
963
|
+
eventMenu: PiNotifyEventMenuDefinition,
|
|
964
|
+
): Promise<void> {
|
|
965
|
+
let focusedChoiceId: string | undefined;
|
|
966
|
+
while (true) {
|
|
967
|
+
const choice = await showPiUsereqSettingsMenu(
|
|
968
|
+
ctx,
|
|
969
|
+
eventMenu.submenuTitle,
|
|
970
|
+
buildPiNotifyEventMenuChoices(config, eventMenu),
|
|
971
|
+
{ initialSelectedId: focusedChoiceId },
|
|
972
|
+
);
|
|
973
|
+
if (!choice || choice === "save-and-close") {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
focusedChoiceId = choice;
|
|
977
|
+
if (choice === "reset-defaults") {
|
|
978
|
+
resetPiNotifyEventMenuToDefaults(config, eventMenu);
|
|
979
|
+
ctx.ui.notify(
|
|
980
|
+
`Restored default ${eventMenu.systemLabel.toLowerCase()} events`,
|
|
981
|
+
"info",
|
|
982
|
+
);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
const enabled = togglePiNotifyFlag(
|
|
986
|
+
config,
|
|
987
|
+
choice as PiNotifyEventBooleanConfigKey,
|
|
988
|
+
);
|
|
989
|
+
const eventLabel = resolvePiNotifyEventLabel(
|
|
990
|
+
choice as PiNotifyEventBooleanConfigKey,
|
|
991
|
+
eventMenu,
|
|
992
|
+
);
|
|
993
|
+
ctx.ui.notify(
|
|
994
|
+
`${eventMenu.systemLabel} ${eventLabel} ${enabled ? "enabled" : "disabled"}`,
|
|
995
|
+
"info",
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* @brief Builds the direct Pushover rows rendered inside `Notifications`.
|
|
1002
|
+
* @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. Runtime is O(1). No external state is mutated.
|
|
1003
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1004
|
+
* @return {PiUsereqSettingsMenuChoice[]} Ordered direct Pushover rows.
|
|
1005
|
+
* @satisfies REQ-163, REQ-165, REQ-172, REQ-184, REQ-185, REQ-198
|
|
1006
|
+
*/
|
|
1007
|
+
function buildPiNotifyPushoverRows(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
1008
|
+
return [
|
|
741
1009
|
{
|
|
742
|
-
id: "pushover-
|
|
743
|
-
label: "
|
|
1010
|
+
id: "notify-pushover-enabled",
|
|
1011
|
+
label: "Enable pushover",
|
|
744
1012
|
value: formatPiNotifyPushoverStatus(config),
|
|
745
|
-
description: "
|
|
1013
|
+
description: "Enable or disable all Pushover delivery globally.",
|
|
746
1014
|
},
|
|
1015
|
+
buildPiNotifyEventLauncherChoice(
|
|
1016
|
+
config,
|
|
1017
|
+
PI_NOTIFY_EVENT_MENU_DEFINITIONS.pushover,
|
|
1018
|
+
),
|
|
747
1019
|
{
|
|
748
|
-
id: "pushover-
|
|
749
|
-
label: "
|
|
750
|
-
value: config["notify-pushover-
|
|
751
|
-
description: "
|
|
1020
|
+
id: "notify-pushover-priority",
|
|
1021
|
+
label: "Pushover priority",
|
|
1022
|
+
value: formatPiNotifyPushoverPriority(config["notify-pushover-priority"]),
|
|
1023
|
+
description: "Select whether outbound Pushover messages use Normal or High priority.",
|
|
752
1024
|
},
|
|
753
1025
|
{
|
|
754
|
-
id: "pushover-
|
|
755
|
-
label: "
|
|
756
|
-
value: config["notify-pushover-
|
|
757
|
-
description: "Edit the
|
|
1026
|
+
id: "notify-pushover-title",
|
|
1027
|
+
label: "Pushover title",
|
|
1028
|
+
value: config["notify-pushover-title"],
|
|
1029
|
+
description: "Edit the title template used for outbound Pushover messages.",
|
|
758
1030
|
},
|
|
759
1031
|
{
|
|
760
|
-
id: "pushover-
|
|
761
|
-
label: "
|
|
762
|
-
value:
|
|
763
|
-
description: "
|
|
1032
|
+
id: "notify-pushover-text",
|
|
1033
|
+
label: "Pushover text",
|
|
1034
|
+
value: config["notify-pushover-text"],
|
|
1035
|
+
description: "Edit the text template used for outbound Pushover messages.",
|
|
764
1036
|
},
|
|
765
1037
|
{
|
|
766
|
-
id: "
|
|
767
|
-
label: "
|
|
768
|
-
value: "",
|
|
769
|
-
description: "
|
|
1038
|
+
id: "notify-pushover-user-key",
|
|
1039
|
+
label: "Pushover User Key/Delivery Group Key",
|
|
1040
|
+
value: config["notify-pushover-user-key"] || "(empty)",
|
|
1041
|
+
description: "Edit the Pushover user key or delivery group key used for outbound requests.",
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
id: "notify-pushover-api-token",
|
|
1045
|
+
label: "Pushover Token/API Token Key",
|
|
1046
|
+
value: config["notify-pushover-api-token"] || "(empty)",
|
|
1047
|
+
description: "Edit the Pushover application token used for outbound requests.",
|
|
770
1048
|
},
|
|
771
1049
|
];
|
|
772
1050
|
}
|
|
773
1051
|
|
|
774
1052
|
/**
|
|
775
1053
|
* @brief Opens the shared settings-menu selector for Pushover priority.
|
|
776
|
-
* @details Reuses the pi-usereq settings-menu renderer so Pushover priority selection remains stylistically aligned with the
|
|
1054
|
+
* @details Reuses the pi-usereq settings-menu renderer so Pushover priority selection remains stylistically aligned with the notification menus and returns the chosen priority or `undefined` on cancel. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
|
|
777
1055
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
778
1056
|
* @param[in] currentPriority {PiNotifyPushoverPriority} Persisted priority value.
|
|
779
1057
|
* @return {Promise<PiNotifyPushoverPriority | undefined>} Selected priority or `undefined` when cancelled.
|
|
780
|
-
* @satisfies REQ-
|
|
1058
|
+
* @satisfies REQ-172, REQ-192
|
|
781
1059
|
*/
|
|
782
1060
|
async function selectPiNotifyPushoverPriority(
|
|
783
1061
|
ctx: ExtensionCommandContext,
|
|
@@ -786,148 +1064,92 @@ async function selectPiNotifyPushoverPriority(
|
|
|
786
1064
|
const choice = await showPiUsereqSettingsMenu(ctx, "Pushover priority", [
|
|
787
1065
|
{
|
|
788
1066
|
id: "0",
|
|
789
|
-
label: "
|
|
1067
|
+
label: "Normal",
|
|
790
1068
|
value: currentPriority === 0 ? "selected" : "",
|
|
791
|
-
description: "Send
|
|
1069
|
+
description: "Send outbound Pushover messages with normal priority `0`.",
|
|
792
1070
|
},
|
|
793
1071
|
{
|
|
794
1072
|
id: "1",
|
|
795
|
-
label: "
|
|
1073
|
+
label: "High",
|
|
796
1074
|
value: currentPriority === 1 ? "selected" : "",
|
|
797
|
-
description: "Send
|
|
1075
|
+
description: "Send outbound Pushover messages with high priority `1`.",
|
|
798
1076
|
},
|
|
799
|
-
]);
|
|
1077
|
+
], { initialSelectedId: String(currentPriority) });
|
|
800
1078
|
if (!choice) {
|
|
801
1079
|
return undefined;
|
|
802
1080
|
}
|
|
803
1081
|
return normalizePiNotifyPushoverPriority(choice);
|
|
804
1082
|
}
|
|
805
1083
|
|
|
806
|
-
/**
|
|
807
|
-
* @brief Runs the interactive Pushover-configuration menu.
|
|
808
|
-
* @details Exposes the Pushover global-disable flag, successful-completion enable flag, user key, API token, and priority selector through the shared settings-menu renderer. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
809
|
-
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
810
|
-
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
811
|
-
* @return {Promise<void>} Promise resolved when the menu closes.
|
|
812
|
-
* @satisfies REQ-163, REQ-165, REQ-166, REQ-172
|
|
813
|
-
*/
|
|
814
|
-
async function configurePiNotifyPushoverMenu(
|
|
815
|
-
ctx: ExtensionCommandContext,
|
|
816
|
-
config: UseReqConfig,
|
|
817
|
-
): Promise<void> {
|
|
818
|
-
while (true) {
|
|
819
|
-
const choice = await showPiUsereqSettingsMenu(ctx, "Pushover notifications", buildPiNotifyPushoverMenuChoices(config));
|
|
820
|
-
if (!choice || choice === "back") {
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
if (choice === "pushover-global-disable") {
|
|
824
|
-
config["notify-pushover-global-disable"] = !config["notify-pushover-global-disable"];
|
|
825
|
-
ctx.ui.notify(`Pushover global disable ${config["notify-pushover-global-disable"] ? "enabled" : "disabled"}`, "info");
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
if (choice === "pushover-on-success") {
|
|
829
|
-
config["notify-pushover-on-success"] = !config["notify-pushover-on-success"];
|
|
830
|
-
ctx.ui.notify(`Pushover on success ${config["notify-pushover-on-success"] ? "enabled" : "disabled"}`, "info");
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
if (choice === "pushover-user-key") {
|
|
834
|
-
const value = await ctx.ui.input(
|
|
835
|
-
"User Key/Delivery Group Key",
|
|
836
|
-
config["notify-pushover-user-key"] || "gzfjjvp1xxmhibqwzh9m7i1zwvf83j",
|
|
837
|
-
);
|
|
838
|
-
if (value !== undefined) {
|
|
839
|
-
config["notify-pushover-user-key"] = normalizePiNotifyPushoverCredential(value);
|
|
840
|
-
ctx.ui.notify("Updated Pushover user key", "info");
|
|
841
|
-
}
|
|
842
|
-
continue;
|
|
843
|
-
}
|
|
844
|
-
if (choice === "pushover-api-token") {
|
|
845
|
-
const value = await ctx.ui.input(
|
|
846
|
-
"Token/API Token Key",
|
|
847
|
-
config["notify-pushover-api-token"] || "ah6bf5u2sj63mcvou6qamiabeoubbe",
|
|
848
|
-
);
|
|
849
|
-
if (value !== undefined) {
|
|
850
|
-
config["notify-pushover-api-token"] = normalizePiNotifyPushoverCredential(value);
|
|
851
|
-
ctx.ui.notify("Updated Pushover API token", "info");
|
|
852
|
-
}
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
if (choice === "pushover-priority") {
|
|
856
|
-
const nextPriority = await selectPiNotifyPushoverPriority(ctx, config["notify-pushover-priority"]);
|
|
857
|
-
if (nextPriority !== undefined) {
|
|
858
|
-
config["notify-pushover-priority"] = nextPriority;
|
|
859
|
-
ctx.ui.notify(`Pushover priority set to ${formatPiNotifyPushoverPriority(nextPriority)}`, "info");
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
1084
|
/**
|
|
866
1085
|
* @brief Builds the shared settings-menu choices for notification configuration.
|
|
867
|
-
* @details Serializes
|
|
1086
|
+
* @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. Runtime is O(1) plus command-length formatting. No external state is mutated.
|
|
868
1087
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
869
1088
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered notification-menu choice vector.
|
|
870
|
-
* @satisfies REQ-137, REQ-149, REQ-150, REQ-151, REQ-152, REQ-163, REQ-164, REQ-165, REQ-
|
|
1089
|
+
* @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
|
|
871
1090
|
*/
|
|
872
1091
|
function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
873
1092
|
return [
|
|
874
1093
|
{
|
|
875
|
-
id: "
|
|
876
|
-
label: "
|
|
877
|
-
value: config
|
|
878
|
-
description: "
|
|
1094
|
+
id: "notify-enabled",
|
|
1095
|
+
label: "Enable notification",
|
|
1096
|
+
value: formatPiNotifyStatus(config),
|
|
1097
|
+
description: "Enable or disable command-notify delivery globally.",
|
|
879
1098
|
},
|
|
1099
|
+
buildPiNotifyEventLauncherChoice(
|
|
1100
|
+
config,
|
|
1101
|
+
PI_NOTIFY_EVENT_MENU_DEFINITIONS.notification,
|
|
1102
|
+
),
|
|
880
1103
|
{
|
|
881
|
-
id: "
|
|
882
|
-
label: "
|
|
883
|
-
value: config
|
|
884
|
-
description: "
|
|
1104
|
+
id: "notify-command",
|
|
1105
|
+
label: "Notify command",
|
|
1106
|
+
value: config.PI_NOTIFY_CMD,
|
|
1107
|
+
description: "Edit the shell command used for command-notify delivery.",
|
|
885
1108
|
},
|
|
886
1109
|
{
|
|
887
|
-
id: "
|
|
888
|
-
label: "
|
|
889
|
-
value: config["notify-beep-on-error"] ? "on" : "off",
|
|
890
|
-
description: "Toggle terminal beep delivery for error-terminated prompt completion.",
|
|
891
|
-
},
|
|
892
|
-
{
|
|
893
|
-
id: "selected-notify-command",
|
|
894
|
-
label: "Selected notify command",
|
|
1110
|
+
id: "selected-sound-command",
|
|
1111
|
+
label: "Enable sound",
|
|
895
1112
|
value: config["notify-sound"],
|
|
896
|
-
description: "Select which
|
|
1113
|
+
description: "Select which sound command level is currently active.",
|
|
897
1114
|
},
|
|
1115
|
+
buildPiNotifyEventLauncherChoice(
|
|
1116
|
+
config,
|
|
1117
|
+
PI_NOTIFY_EVENT_MENU_DEFINITIONS.sound,
|
|
1118
|
+
),
|
|
898
1119
|
{
|
|
899
1120
|
id: "sound-toggle-hotkey-bind",
|
|
900
1121
|
label: "Sound toggle hotkey bind",
|
|
901
1122
|
value: config["notify-sound-toggle-shortcut"],
|
|
902
|
-
description: "Edit the keyboard shortcut that cycles the selected
|
|
1123
|
+
description: "Edit the keyboard shortcut that cycles the selected sound command.",
|
|
903
1124
|
},
|
|
904
1125
|
{
|
|
905
|
-
id: "
|
|
906
|
-
label: "
|
|
1126
|
+
id: "sound-command-low",
|
|
1127
|
+
label: "Sound command (low vol.)",
|
|
907
1128
|
value: config.PI_NOTIFY_SOUND_LOW_CMD,
|
|
908
|
-
description: "Edit the shell command used when the selected
|
|
1129
|
+
description: "Edit the shell command used when the selected sound command is `low`.",
|
|
909
1130
|
},
|
|
910
1131
|
{
|
|
911
|
-
id: "
|
|
912
|
-
label: "
|
|
1132
|
+
id: "sound-command-mid",
|
|
1133
|
+
label: "Sound command (mid vol.)",
|
|
913
1134
|
value: config.PI_NOTIFY_SOUND_MID_CMD,
|
|
914
|
-
description: "Edit the shell command used when the selected
|
|
1135
|
+
description: "Edit the shell command used when the selected sound command is `mid`.",
|
|
915
1136
|
},
|
|
916
1137
|
{
|
|
917
|
-
id: "
|
|
918
|
-
label: "
|
|
1138
|
+
id: "sound-command-high",
|
|
1139
|
+
label: "Sound command (high vol.)",
|
|
919
1140
|
value: config.PI_NOTIFY_SOUND_HIGH_CMD,
|
|
920
|
-
description: "Edit the shell command used when the selected
|
|
1141
|
+
description: "Edit the shell command used when the selected sound command is `high`.",
|
|
921
1142
|
},
|
|
1143
|
+
...buildPiNotifyPushoverRows(config),
|
|
922
1144
|
{
|
|
923
|
-
id: "
|
|
924
|
-
label: "
|
|
925
|
-
value:
|
|
926
|
-
description: "
|
|
1145
|
+
id: "reset-defaults",
|
|
1146
|
+
label: "Reset defaults",
|
|
1147
|
+
value: "",
|
|
1148
|
+
description: "Restore the documented notification defaults for command-notify, sound, and Pushover settings.",
|
|
927
1149
|
},
|
|
928
1150
|
{
|
|
929
|
-
id: "
|
|
930
|
-
label: "
|
|
1151
|
+
id: "save-and-close",
|
|
1152
|
+
label: "Save and close",
|
|
931
1153
|
value: "",
|
|
932
1154
|
description: "Return to the parent configuration menu.",
|
|
933
1155
|
},
|
|
@@ -935,127 +1157,229 @@ function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuCho
|
|
|
935
1157
|
}
|
|
936
1158
|
|
|
937
1159
|
/**
|
|
938
|
-
* @brief Opens the shared settings-menu selector for the
|
|
939
|
-
* @details Reuses the pi-usereq settings-menu renderer so
|
|
1160
|
+
* @brief Opens the shared settings-menu selector for the active sound level.
|
|
1161
|
+
* @details Reuses the pi-usereq settings-menu renderer so sound-level selection remains stylistically aligned with the notification menu and returns the chosen sound level or `undefined` on cancel. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
|
|
940
1162
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
941
|
-
* @param[in] currentLevel {PiNotifySoundLevel} Currently selected
|
|
1163
|
+
* @param[in] currentLevel {PiNotifySoundLevel} Currently selected sound level.
|
|
942
1164
|
* @return {Promise<PiNotifySoundLevel | undefined>} Selected sound level or `undefined` when cancelled.
|
|
943
|
-
* @satisfies REQ-131, REQ-
|
|
1165
|
+
* @satisfies REQ-131, REQ-179, REQ-192
|
|
944
1166
|
*/
|
|
945
1167
|
async function selectPiNotifySoundLevel(
|
|
946
1168
|
ctx: ExtensionCommandContext,
|
|
947
1169
|
currentLevel: PiNotifySoundLevel,
|
|
948
1170
|
): Promise<PiNotifySoundLevel | undefined> {
|
|
949
|
-
const choice = await showPiUsereqSettingsMenu(ctx, "
|
|
1171
|
+
const choice = await showPiUsereqSettingsMenu(ctx, "Enable sound", [
|
|
950
1172
|
{
|
|
951
1173
|
id: "none",
|
|
952
1174
|
label: "none",
|
|
953
1175
|
value: currentLevel === "none" ? "selected" : "",
|
|
954
|
-
description: "Disable
|
|
1176
|
+
description: "Disable sound-command delivery while preserving per-event sound toggles.",
|
|
955
1177
|
},
|
|
956
1178
|
{
|
|
957
1179
|
id: "low",
|
|
958
1180
|
label: "low",
|
|
959
1181
|
value: currentLevel === "low" ? "selected" : "",
|
|
960
|
-
description: "
|
|
1182
|
+
description: "Use the low-volume sound command when sound delivery is enabled for the current event.",
|
|
961
1183
|
},
|
|
962
1184
|
{
|
|
963
1185
|
id: "mid",
|
|
964
1186
|
label: "mid",
|
|
965
1187
|
value: currentLevel === "mid" ? "selected" : "",
|
|
966
|
-
description: "
|
|
1188
|
+
description: "Use the mid-volume sound command when sound delivery is enabled for the current event.",
|
|
967
1189
|
},
|
|
968
1190
|
{
|
|
969
1191
|
id: "high",
|
|
970
1192
|
label: "high",
|
|
971
1193
|
value: currentLevel === "high" ? "selected" : "",
|
|
972
|
-
description: "
|
|
1194
|
+
description: "Use the high-volume sound command when sound delivery is enabled for the current event.",
|
|
973
1195
|
},
|
|
974
|
-
]);
|
|
1196
|
+
], { initialSelectedId: currentLevel });
|
|
975
1197
|
return choice ? choice as PiNotifySoundLevel : undefined;
|
|
976
1198
|
}
|
|
977
1199
|
|
|
978
1200
|
/**
|
|
979
1201
|
* @brief Runs the interactive notification-configuration menu.
|
|
980
|
-
* @details Exposes
|
|
1202
|
+
* @details Exposes command-notify, sound, and Pushover controls through the shared settings-menu renderer, delegates completed/interrupted/failed toggles to dedicated event submenus, and preserves row focus across menu re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
981
1203
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
982
1204
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
983
1205
|
* @return {Promise<boolean>} `true` when the sound-toggle shortcut changed.
|
|
984
|
-
* @satisfies REQ-
|
|
1206
|
+
* @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
|
|
985
1207
|
*/
|
|
986
1208
|
async function configurePiNotifyMenu(
|
|
987
1209
|
ctx: ExtensionCommandContext,
|
|
988
1210
|
config: UseReqConfig,
|
|
989
1211
|
): Promise<boolean> {
|
|
990
1212
|
const originalShortcut = config["notify-sound-toggle-shortcut"];
|
|
1213
|
+
let focusedChoiceId: string | undefined;
|
|
991
1214
|
while (true) {
|
|
992
|
-
const choice = await showPiUsereqSettingsMenu(
|
|
993
|
-
|
|
1215
|
+
const choice = await showPiUsereqSettingsMenu(
|
|
1216
|
+
ctx,
|
|
1217
|
+
"Notifications",
|
|
1218
|
+
buildPiNotifyMenuChoices(config),
|
|
1219
|
+
{ initialSelectedId: focusedChoiceId },
|
|
1220
|
+
);
|
|
1221
|
+
if (!choice || choice === "save-and-close") {
|
|
994
1222
|
return config["notify-sound-toggle-shortcut"] !== originalShortcut;
|
|
995
1223
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1224
|
+
focusedChoiceId = choice;
|
|
1225
|
+
if (choice === "notify-enabled" || choice === "notify-pushover-enabled") {
|
|
1226
|
+
const enabled = togglePiNotifyFlag(config, choice as PiNotifyBooleanConfigKey);
|
|
1227
|
+
const labelMap: Record<string, string> = {
|
|
1228
|
+
"notify-enabled": "Notification",
|
|
1229
|
+
"notify-pushover-enabled": "Pushover",
|
|
1230
|
+
};
|
|
1231
|
+
ctx.ui.notify(`${labelMap[choice]} ${enabled ? "enabled" : "disabled"}`, "info");
|
|
999
1232
|
continue;
|
|
1000
1233
|
}
|
|
1001
|
-
if (choice ===
|
|
1002
|
-
|
|
1003
|
-
|
|
1234
|
+
if (choice === PI_NOTIFY_EVENT_MENU_DEFINITIONS.notification.topLevelId) {
|
|
1235
|
+
await configurePiNotifyEventMenu(
|
|
1236
|
+
ctx,
|
|
1237
|
+
config,
|
|
1238
|
+
PI_NOTIFY_EVENT_MENU_DEFINITIONS.notification,
|
|
1239
|
+
);
|
|
1004
1240
|
continue;
|
|
1005
1241
|
}
|
|
1006
|
-
if (choice ===
|
|
1007
|
-
|
|
1008
|
-
|
|
1242
|
+
if (choice === PI_NOTIFY_EVENT_MENU_DEFINITIONS.sound.topLevelId) {
|
|
1243
|
+
await configurePiNotifyEventMenu(
|
|
1244
|
+
ctx,
|
|
1245
|
+
config,
|
|
1246
|
+
PI_NOTIFY_EVENT_MENU_DEFINITIONS.sound,
|
|
1247
|
+
);
|
|
1009
1248
|
continue;
|
|
1010
1249
|
}
|
|
1011
|
-
if (choice ===
|
|
1250
|
+
if (choice === PI_NOTIFY_EVENT_MENU_DEFINITIONS.pushover.topLevelId) {
|
|
1251
|
+
await configurePiNotifyEventMenu(
|
|
1252
|
+
ctx,
|
|
1253
|
+
config,
|
|
1254
|
+
PI_NOTIFY_EVENT_MENU_DEFINITIONS.pushover,
|
|
1255
|
+
);
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (choice === "notify-command") {
|
|
1259
|
+
const value = await ctx.ui.input("Notify command", config.PI_NOTIFY_CMD);
|
|
1260
|
+
if (value !== undefined) {
|
|
1261
|
+
config.PI_NOTIFY_CMD = normalizePiNotifyCommand(value, DEFAULT_PI_NOTIFY_CMD);
|
|
1262
|
+
ctx.ui.notify("Updated notify command", "info");
|
|
1263
|
+
}
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (choice === "selected-sound-command") {
|
|
1012
1267
|
const nextLevel = await selectPiNotifySoundLevel(ctx, config["notify-sound"]);
|
|
1013
|
-
if (nextLevel) {
|
|
1268
|
+
if (nextLevel !== undefined) {
|
|
1014
1269
|
config["notify-sound"] = nextLevel;
|
|
1015
|
-
ctx.ui.notify(`
|
|
1270
|
+
ctx.ui.notify(`Enable sound set to ${nextLevel}`, "info");
|
|
1016
1271
|
}
|
|
1017
1272
|
continue;
|
|
1018
1273
|
}
|
|
1019
1274
|
if (choice === "sound-toggle-hotkey-bind") {
|
|
1020
|
-
const value = await ctx.ui.input(
|
|
1275
|
+
const value = await ctx.ui.input(
|
|
1276
|
+
"Sound toggle hotkey bind",
|
|
1277
|
+
config["notify-sound-toggle-shortcut"],
|
|
1278
|
+
);
|
|
1021
1279
|
if (value?.trim()) {
|
|
1022
1280
|
config["notify-sound-toggle-shortcut"] = value.trim();
|
|
1023
|
-
ctx.ui.notify(
|
|
1281
|
+
ctx.ui.notify(
|
|
1282
|
+
`Sound toggle hotkey bind set to ${config["notify-sound-toggle-shortcut"]}`,
|
|
1283
|
+
"info",
|
|
1284
|
+
);
|
|
1024
1285
|
}
|
|
1025
1286
|
continue;
|
|
1026
1287
|
}
|
|
1027
|
-
if (choice === "
|
|
1028
|
-
const value = await ctx.ui.input("
|
|
1029
|
-
if (value
|
|
1030
|
-
config.PI_NOTIFY_SOUND_LOW_CMD =
|
|
1031
|
-
|
|
1288
|
+
if (choice === "sound-command-low") {
|
|
1289
|
+
const value = await ctx.ui.input("Sound command (low vol.)", config.PI_NOTIFY_SOUND_LOW_CMD);
|
|
1290
|
+
if (value !== undefined) {
|
|
1291
|
+
config.PI_NOTIFY_SOUND_LOW_CMD = normalizePiNotifyCommand(
|
|
1292
|
+
value,
|
|
1293
|
+
DEFAULT_PI_NOTIFY_SOUND_LOW_CMD,
|
|
1294
|
+
);
|
|
1295
|
+
ctx.ui.notify("Updated sound command (low vol.)", "info");
|
|
1032
1296
|
}
|
|
1033
1297
|
continue;
|
|
1034
1298
|
}
|
|
1035
|
-
if (choice === "
|
|
1036
|
-
const value = await ctx.ui.input("
|
|
1037
|
-
if (value
|
|
1038
|
-
config.PI_NOTIFY_SOUND_MID_CMD =
|
|
1039
|
-
|
|
1299
|
+
if (choice === "sound-command-mid") {
|
|
1300
|
+
const value = await ctx.ui.input("Sound command (mid vol.)", config.PI_NOTIFY_SOUND_MID_CMD);
|
|
1301
|
+
if (value !== undefined) {
|
|
1302
|
+
config.PI_NOTIFY_SOUND_MID_CMD = normalizePiNotifyCommand(
|
|
1303
|
+
value,
|
|
1304
|
+
DEFAULT_PI_NOTIFY_SOUND_MID_CMD,
|
|
1305
|
+
);
|
|
1306
|
+
ctx.ui.notify("Updated sound command (mid vol.)", "info");
|
|
1040
1307
|
}
|
|
1041
1308
|
continue;
|
|
1042
1309
|
}
|
|
1043
|
-
if (choice === "
|
|
1044
|
-
const value = await ctx.ui.input("
|
|
1045
|
-
if (value
|
|
1046
|
-
config.PI_NOTIFY_SOUND_HIGH_CMD =
|
|
1047
|
-
|
|
1310
|
+
if (choice === "sound-command-high") {
|
|
1311
|
+
const value = await ctx.ui.input("Sound command (high vol.)", config.PI_NOTIFY_SOUND_HIGH_CMD);
|
|
1312
|
+
if (value !== undefined) {
|
|
1313
|
+
config.PI_NOTIFY_SOUND_HIGH_CMD = normalizePiNotifyCommand(
|
|
1314
|
+
value,
|
|
1315
|
+
DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD,
|
|
1316
|
+
);
|
|
1317
|
+
ctx.ui.notify("Updated sound command (high vol.)", "info");
|
|
1318
|
+
}
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (choice === "notify-pushover-priority") {
|
|
1322
|
+
const nextPriority = await selectPiNotifyPushoverPriority(ctx, config["notify-pushover-priority"]);
|
|
1323
|
+
if (nextPriority !== undefined) {
|
|
1324
|
+
config["notify-pushover-priority"] = nextPriority;
|
|
1325
|
+
ctx.ui.notify(`Pushover priority set to ${formatPiNotifyPushoverPriority(nextPriority)}`, "info");
|
|
1326
|
+
}
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
if (choice === "notify-pushover-title") {
|
|
1330
|
+
const value = await ctx.ui.input("Pushover title", config["notify-pushover-title"]);
|
|
1331
|
+
if (value !== undefined) {
|
|
1332
|
+
config["notify-pushover-title"] = normalizePiNotifyTemplateValue(
|
|
1333
|
+
value,
|
|
1334
|
+
DEFAULT_PI_NOTIFY_PUSHOVER_TITLE,
|
|
1335
|
+
);
|
|
1336
|
+
ctx.ui.notify("Updated Pushover title", "info");
|
|
1337
|
+
}
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (choice === "notify-pushover-text") {
|
|
1341
|
+
const value = await ctx.ui.input("Pushover text", config["notify-pushover-text"]);
|
|
1342
|
+
if (value !== undefined) {
|
|
1343
|
+
config["notify-pushover-text"] = normalizePiNotifyTemplateValue(
|
|
1344
|
+
value,
|
|
1345
|
+
DEFAULT_PI_NOTIFY_PUSHOVER_TEXT,
|
|
1346
|
+
);
|
|
1347
|
+
ctx.ui.notify("Updated Pushover text", "info");
|
|
1348
|
+
}
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
if (choice === "notify-pushover-user-key") {
|
|
1352
|
+
const value = await ctx.ui.input(
|
|
1353
|
+
"Pushover User Key/Delivery Group Key",
|
|
1354
|
+
config["notify-pushover-user-key"],
|
|
1355
|
+
);
|
|
1356
|
+
if (value !== undefined) {
|
|
1357
|
+
config["notify-pushover-user-key"] = normalizePiNotifyPushoverCredential(value);
|
|
1358
|
+
ctx.ui.notify("Updated Pushover user key", "info");
|
|
1048
1359
|
}
|
|
1049
1360
|
continue;
|
|
1050
1361
|
}
|
|
1051
|
-
if (choice === "pushover-
|
|
1052
|
-
await
|
|
1362
|
+
if (choice === "notify-pushover-api-token") {
|
|
1363
|
+
const value = await ctx.ui.input(
|
|
1364
|
+
"Pushover Token/API Token Key",
|
|
1365
|
+
config["notify-pushover-api-token"],
|
|
1366
|
+
);
|
|
1367
|
+
if (value !== undefined) {
|
|
1368
|
+
config["notify-pushover-api-token"] = normalizePiNotifyPushoverCredential(value);
|
|
1369
|
+
ctx.ui.notify("Updated Pushover API token", "info");
|
|
1370
|
+
}
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (choice === "reset-defaults") {
|
|
1374
|
+
resetPiNotifyConfigToDefaults(config);
|
|
1375
|
+
ctx.ui.notify("Restored notification defaults", "info");
|
|
1376
|
+
continue;
|
|
1053
1377
|
}
|
|
1054
1378
|
}
|
|
1055
1379
|
}
|
|
1056
1380
|
|
|
1057
1381
|
/**
|
|
1058
|
-
* @brief Registers the configurable
|
|
1382
|
+
* @brief Registers the configurable notification-sound shortcut when supported.
|
|
1059
1383
|
* @details Loads the current project config, registers one raw pi shortcut when
|
|
1060
1384
|
* the runtime exposes `registerShortcut(...)`, cycles persisted sound state on
|
|
1061
1385
|
* invocation, saves the config, refreshes the status bar, and emits one info
|
|
@@ -1065,7 +1389,7 @@ async function configurePiNotifyMenu(
|
|
|
1065
1389
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
1066
1390
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
1067
1391
|
* @return {void} No return value.
|
|
1068
|
-
* @satisfies REQ-131, REQ-134, REQ-
|
|
1392
|
+
* @satisfies REQ-131, REQ-134, REQ-180
|
|
1069
1393
|
*/
|
|
1070
1394
|
function registerPiNotifyShortcut(
|
|
1071
1395
|
pi: ExtensionAPI,
|
|
@@ -1077,7 +1401,7 @@ function registerPiNotifyShortcut(
|
|
|
1077
1401
|
}
|
|
1078
1402
|
const config = loadProjectConfig(process.cwd());
|
|
1079
1403
|
shortcutRegistrar.registerShortcut(config["notify-sound-toggle-shortcut"], {
|
|
1080
|
-
description: "Cycle pi-usereq
|
|
1404
|
+
description: "Cycle pi-usereq notification sound level",
|
|
1081
1405
|
handler: async (ctx) => {
|
|
1082
1406
|
const nextConfig = loadProjectConfig(ctx.cwd);
|
|
1083
1407
|
nextConfig["notify-sound"] = cyclePiNotifySoundLevel(nextConfig["notify-sound"]);
|
|
@@ -1131,17 +1455,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1131
1455
|
const gitPathSchema = Type.Object(
|
|
1132
1456
|
{},
|
|
1133
1457
|
{
|
|
1134
|
-
description: "Input contract: no params. Output contract: JSON object with
|
|
1458
|
+
description: "Input contract: no params. Output contract: JSON object with result and execution. Result exposes path_value and path_present for the cwd-derived runtime git root.",
|
|
1135
1459
|
},
|
|
1136
1460
|
);
|
|
1137
1461
|
pi.registerTool({
|
|
1138
1462
|
name: "git-path",
|
|
1139
1463
|
label: "git-path",
|
|
1140
|
-
description: "Scope: current runtime path. Return a
|
|
1464
|
+
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the resolved `git-path` value through direct-access fields without request echoes.",
|
|
1141
1465
|
promptSnippet: "Return the structured runtime git-root payload for the current project.",
|
|
1142
1466
|
promptGuidelines: [
|
|
1143
1467
|
"Input contract: no params. Scope is the cwd-derived runtime path context.",
|
|
1144
|
-
"Output contract:
|
|
1468
|
+
"Output contract: result + execution. Result exposes path_value and path_present.",
|
|
1145
1469
|
"Behavior contract: git-path is derived at runtime from the current working directory and repository ancestry rules.",
|
|
1146
1470
|
"Failure contract: configuration-loading failures surface through execution.code and execution.stderr_lines.",
|
|
1147
1471
|
],
|
|
@@ -1150,14 +1474,12 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1150
1474
|
ensureBundledResourcesAccessible();
|
|
1151
1475
|
const projectBase = getProjectBase(process.cwd());
|
|
1152
1476
|
const config = loadProjectConfig(process.cwd());
|
|
1153
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1154
1477
|
const result = runGitPath(projectBase, config);
|
|
1155
1478
|
const payload = buildPathQueryToolPayload(
|
|
1156
1479
|
"git-path",
|
|
1157
1480
|
process.cwd(),
|
|
1158
1481
|
projectBase,
|
|
1159
1482
|
result.stdout.trimEnd(),
|
|
1160
|
-
runtimePaths,
|
|
1161
1483
|
buildToolExecutionSection(result),
|
|
1162
1484
|
);
|
|
1163
1485
|
return buildStructuredToolExecuteResult(payload);
|
|
@@ -1167,17 +1489,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1167
1489
|
const basePathSchema = Type.Object(
|
|
1168
1490
|
{},
|
|
1169
1491
|
{
|
|
1170
|
-
description: "Input contract: no params. Output contract: JSON object with
|
|
1492
|
+
description: "Input contract: no params. Output contract: JSON object with result and execution. Result exposes path_value and path_present for the cwd-derived runtime base path.",
|
|
1171
1493
|
},
|
|
1172
1494
|
);
|
|
1173
1495
|
pi.registerTool({
|
|
1174
1496
|
name: "get-base-path",
|
|
1175
1497
|
label: "get-base-path",
|
|
1176
|
-
description: "Scope: current runtime path. Return a
|
|
1498
|
+
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the resolved `base-path` value through direct-access fields without request echoes.",
|
|
1177
1499
|
promptSnippet: "Return the structured runtime project-base payload.",
|
|
1178
1500
|
promptGuidelines: [
|
|
1179
1501
|
"Input contract: no params. Scope is the cwd-derived runtime path context.",
|
|
1180
|
-
"Output contract:
|
|
1502
|
+
"Output contract: result + execution. Result exposes path_value and path_present.",
|
|
1181
1503
|
"Behavior contract: base-path equals the current working directory used by the extension command or tool.",
|
|
1182
1504
|
"Failure contract: configuration-loading failures surface through execution.code and execution.stderr_lines.",
|
|
1183
1505
|
],
|
|
@@ -1185,14 +1507,12 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1185
1507
|
async execute() {
|
|
1186
1508
|
const projectBase = getProjectBase(process.cwd());
|
|
1187
1509
|
const config = loadProjectConfig(process.cwd());
|
|
1188
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1189
1510
|
const result = runGetBasePath(projectBase, config);
|
|
1190
1511
|
const payload = buildPathQueryToolPayload(
|
|
1191
1512
|
"get-base-path",
|
|
1192
1513
|
process.cwd(),
|
|
1193
1514
|
projectBase,
|
|
1194
1515
|
result.stdout.trimEnd(),
|
|
1195
|
-
runtimePaths,
|
|
1196
1516
|
buildToolExecutionSection(result),
|
|
1197
1517
|
);
|
|
1198
1518
|
return buildStructuredToolExecuteResult(payload);
|
|
@@ -1207,7 +1527,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1207
1527
|
),
|
|
1208
1528
|
},
|
|
1209
1529
|
{
|
|
1210
|
-
description: "Input contract: files[]. Output contract: JSON object with
|
|
1530
|
+
description: "Input contract: files[]. Output contract: JSON object with summary, repository, files, and execution. File entries expose canonical paths, numeric line ranges, imports, symbols, structured Doxygen fields, standalone comments, and structured status facts. Missing or unsupported inputs become skipped entries. The tool fails when no source file can be analyzed.",
|
|
1211
1531
|
},
|
|
1212
1532
|
);
|
|
1213
1533
|
const multiFileSchema = Type.Object(
|
|
@@ -1218,7 +1538,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1218
1538
|
),
|
|
1219
1539
|
},
|
|
1220
1540
|
{
|
|
1221
|
-
description: "Input contract: files[]. Output contract: JSON object with
|
|
1541
|
+
description: "Input contract: files[]. Output contract: JSON object with summary, files, and execution. File entries expose canonical paths, detected language, configured checker modules, selection status, and error facts.",
|
|
1222
1542
|
},
|
|
1223
1543
|
);
|
|
1224
1544
|
const filesTokensSchema = Type.Object(
|
|
@@ -1229,42 +1549,46 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1229
1549
|
),
|
|
1230
1550
|
},
|
|
1231
1551
|
{
|
|
1232
|
-
description: "Input contract: files[]. Output contract: JSON object with
|
|
1552
|
+
description: "Input contract: files[]. Output contract: JSON object with summary, files, and execution. File entries expose direct-access facts, token metrics, and optional heading or Doxygen metadata. Missing or non-file inputs become skipped entries. The tool fails when no processable files remain.",
|
|
1233
1553
|
},
|
|
1234
1554
|
);
|
|
1235
1555
|
|
|
1236
1556
|
pi.registerTool({
|
|
1237
1557
|
name: "files-tokens",
|
|
1238
1558
|
label: "files-tokens",
|
|
1239
|
-
description: "Scope: explicit files. Return
|
|
1559
|
+
description: "Scope: explicit files. Return a token-optimized JSON payload with summary, files, and execution sections. File entries expose direct-access path facts, status, size metrics, and optional heading or Doxygen metadata.",
|
|
1240
1560
|
promptSnippet: "Return the structured token-analysis payload for caller-selected files.",
|
|
1241
1561
|
promptGuidelines: [
|
|
1242
1562
|
"Scope: explicit files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
|
|
1243
|
-
"Output contract:
|
|
1244
|
-
"Numeric contract: counts, sizes, shares, and line ranges remain in dedicated numeric fields;
|
|
1563
|
+
"Output contract: summary + files + execution. File entries expose canonical paths, absolute paths, existence, file status, line range, line count, byte count, character count, token count, shares, and optional primary-heading or Doxygen file metadata.",
|
|
1564
|
+
"Numeric contract: counts, sizes, shares, and line ranges remain in dedicated numeric fields; static request metadata and derived guidance are omitted from runtime responses.",
|
|
1245
1565
|
"Behavior contract: missing or non-file inputs become skipped entries; read failures become error entries; the tool fails only when no processable files remain.",
|
|
1246
1566
|
],
|
|
1247
1567
|
parameters: filesTokensSchema,
|
|
1248
1568
|
async execute(_toolCallId, params) {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1569
|
+
try {
|
|
1570
|
+
const payload = buildTokenToolPayload({
|
|
1571
|
+
toolName: "files-tokens",
|
|
1572
|
+
scope: "explicit-files",
|
|
1573
|
+
baseDir: process.cwd(),
|
|
1574
|
+
requestedPaths: params.files,
|
|
1575
|
+
encodingName: TOKEN_COUNTER_ENCODING,
|
|
1576
|
+
});
|
|
1577
|
+
return buildTokenToolExecuteResult(payload);
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
return buildFailedTokenToolExecuteResult(error);
|
|
1580
|
+
}
|
|
1257
1581
|
},
|
|
1258
1582
|
});
|
|
1259
1583
|
|
|
1260
1584
|
pi.registerTool({
|
|
1261
1585
|
name: "files-references",
|
|
1262
1586
|
label: "files-references",
|
|
1263
|
-
description: "Scope: explicit source files. Return
|
|
1587
|
+
description: "Scope: explicit source files. Return a token-optimized JSON payload with summary, repository, files, and execution sections. File entries expose canonical paths, numeric line ranges, imports, symbols, structured Doxygen fields, standalone comments, and structured status facts.",
|
|
1264
1588
|
promptSnippet: "Return the structured references payload for caller-selected source files.",
|
|
1265
1589
|
promptGuidelines: [
|
|
1266
1590
|
"Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
|
|
1267
|
-
"Output contract:
|
|
1591
|
+
"Output contract: summary + repository + files + execution. File entries expose canonical paths, absolute paths, file status, line counts, line ranges, imports, symbols, child relationships, standalone comments, and structured Doxygen metadata.",
|
|
1268
1592
|
"Numeric contract: line counts, line ranges, symbol counts, import counts, comment counts, and Doxygen counts remain in dedicated numeric fields; text is limited to residual comment or signature content that cannot be split safely.",
|
|
1269
1593
|
"Behavior contract: missing inputs, non-file inputs, and unsupported extensions become structured skipped entries; analysis failures become structured error entries; the tool fails only when no source file can be analyzed.",
|
|
1270
1594
|
],
|
|
@@ -1289,18 +1613,18 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1289
1613
|
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true, `compressed_source_text` and `compressed_lines[].display_text` include original source line-number prefixes" })),
|
|
1290
1614
|
},
|
|
1291
1615
|
{
|
|
1292
|
-
description: "Input contract: files[] plus optional enableLineNumbers. Output contract: JSON object with
|
|
1616
|
+
description: "Input contract: files[] plus optional enableLineNumbers. Output contract: JSON object with summary, repository, files, and execution. File entries expose path identifiers, source and compressed line metrics, structured compressed lines, symbols, structured Doxygen fields, and stable status facts. Missing, unsupported, or invalid inputs become structured skipped entries. The tool fails when no file is compressed.",
|
|
1293
1617
|
},
|
|
1294
1618
|
);
|
|
1295
1619
|
|
|
1296
1620
|
pi.registerTool({
|
|
1297
1621
|
name: "files-compress",
|
|
1298
1622
|
label: "files-compress",
|
|
1299
|
-
description: "Scope: explicit files. Return
|
|
1623
|
+
description: "Scope: explicit files. Return a token-optimized JSON payload with summary, repository, files, and execution sections. File entries expose canonical paths, source and compressed line metrics, structured compressed lines, symbols, structured Doxygen fields, and stable status facts.",
|
|
1300
1624
|
promptSnippet: "Return the structured compression payload for caller-selected source files.",
|
|
1301
1625
|
promptGuidelines: [
|
|
1302
1626
|
"Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
|
|
1303
|
-
"Output contract:
|
|
1627
|
+
"Output contract: summary + repository + files + execution. File entries expose canonical paths, absolute paths, line_number_mode, source line counts, source line ranges, compressed line counts, removed line counts, compressed_lines, compressed_source_text, symbols, and file_doxygen.",
|
|
1304
1628
|
"Line-number behavior: enableLineNumbers changes only rendered display strings; numeric source_line_number facts remain dedicated fields on compressed_lines for direct access.",
|
|
1305
1629
|
"Behavior contract: missing inputs, non-file inputs, and unsupported extensions become structured skipped entries; compression failures become structured error entries; symbol-analysis failures retain compressed output with symbol_analysis_status=error; the tool fails only when no file is compressed.",
|
|
1306
1630
|
],
|
|
@@ -1335,7 +1659,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1335
1659
|
pi.registerTool({
|
|
1336
1660
|
name: "files-find",
|
|
1337
1661
|
label: "files-find",
|
|
1338
|
-
description: "Scope: explicit source files. Return
|
|
1662
|
+
description: "Scope: explicit source files. Return a token-optimized JSON payload with summary, repository, files, and execution sections. File entries expose structured statuses and match records with typed location, symbol, stripped-code, and Doxygen facts.",
|
|
1339
1663
|
promptSnippet: "Return the structured construct-search payload for caller-selected source files.",
|
|
1340
1664
|
promptGuidelines: buildFindToolPromptGuidelines("explicit-files"),
|
|
1341
1665
|
parameters: filesFindSchema,
|
|
@@ -1356,24 +1680,24 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1356
1680
|
const referencesSchema = Type.Object(
|
|
1357
1681
|
{},
|
|
1358
1682
|
{
|
|
1359
|
-
description: "Input contract: no params. Scope is the configured src-dir list resolved from the current project configuration. Output contract: JSON object with
|
|
1683
|
+
description: "Input contract: no params. Scope is the configured src-dir list resolved from the current project configuration. Output contract: JSON object with summary, repository, files, and execution. Repository exposes the structured directory tree; file entries expose canonical paths, numeric line ranges, imports, symbols, structured Doxygen fields, and status facts. The tool fails when no configured source file can be analyzed.",
|
|
1360
1684
|
},
|
|
1361
1685
|
);
|
|
1362
1686
|
const tokensSchema = Type.Object(
|
|
1363
1687
|
{},
|
|
1364
1688
|
{
|
|
1365
|
-
description: "Input contract: no params. Scope is the configured docs-dir plus canonical docs REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md. Output contract: same
|
|
1689
|
+
description: "Input contract: no params. Scope is the configured docs-dir plus canonical docs REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md. Output contract: same token-optimized JSON shape as files-tokens. Missing canonical docs become skipped entries. The tool fails when no processable canonical docs remain.",
|
|
1366
1690
|
},
|
|
1367
1691
|
);
|
|
1368
1692
|
|
|
1369
1693
|
pi.registerTool({
|
|
1370
1694
|
name: "references",
|
|
1371
1695
|
label: "references",
|
|
1372
|
-
description: "Scope: configured project source directories. Return
|
|
1696
|
+
description: "Scope: configured project source directories. Return a token-optimized JSON payload with summary, repository, files, and execution sections. The repository section exposes the structured directory tree; file entries expose canonical paths, numeric line ranges, imports, symbols, structured Doxygen fields, standalone comments, and status facts.",
|
|
1373
1697
|
promptSnippet: "Return the structured project references payload from the configured source directories.",
|
|
1374
1698
|
promptGuidelines: [
|
|
1375
1699
|
"Scope: no params; resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.",
|
|
1376
|
-
"Output contract:
|
|
1700
|
+
"Output contract: summary + repository + files + execution. Repository exposes source_directory_paths, file_canonical_paths, and directory_tree; file entries expose canonical paths, line counts, line ranges, imports, symbols, hierarchy, standalone comments, and structured Doxygen metadata.",
|
|
1377
1701
|
"Configuration contract: output changes with cwd-derived project config, src-dir values, and repository source discovery; the tool does not accept explicit file overrides.",
|
|
1378
1702
|
"Behavior contract: configured source files are analyzed in deterministic order, analysis failures become structured error entries, and the tool fails when no configured source file can be analyzed.",
|
|
1379
1703
|
],
|
|
@@ -1397,18 +1721,18 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1397
1721
|
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true, `compressed_source_text` and `compressed_lines[].display_text` include original source line-number prefixes" })),
|
|
1398
1722
|
},
|
|
1399
1723
|
{
|
|
1400
|
-
description: "Input contract: optional enableLineNumbers boolean. Scope is the configured src-dir list resolved from the current project configuration. Output contract: JSON object with
|
|
1724
|
+
description: "Input contract: optional enableLineNumbers boolean. Scope is the configured src-dir list resolved from the current project configuration. Output contract: JSON object with summary, repository, files, and execution. File entries expose path identifiers, source and compressed line metrics, structured compressed lines, symbols, structured Doxygen fields, and stable status facts. The tool fails when no configured source file is compressed.",
|
|
1401
1725
|
},
|
|
1402
1726
|
);
|
|
1403
1727
|
|
|
1404
1728
|
pi.registerTool({
|
|
1405
1729
|
name: "compress",
|
|
1406
1730
|
label: "compress",
|
|
1407
|
-
description: "Scope: configured project source directories. Return
|
|
1731
|
+
description: "Scope: configured project source directories. Return a token-optimized JSON payload with summary, repository, files, and execution sections. File entries expose canonical paths, source and compressed line metrics, structured compressed lines, symbols, structured Doxygen fields, and stable status facts.",
|
|
1408
1732
|
promptSnippet: "Return the structured project compression payload from the configured source directories.",
|
|
1409
1733
|
promptGuidelines: [
|
|
1410
1734
|
"Scope: resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.",
|
|
1411
|
-
"Output contract:
|
|
1735
|
+
"Output contract: summary + repository + files + execution. Repository exposes source_directory_paths and file_canonical_paths; file entries expose line_number_mode, source line counts, source line ranges, compressed line counts, removed line counts, compressed_lines, compressed_source_text, symbols, and file_doxygen.",
|
|
1412
1736
|
"Configuration contract: output changes with cwd-derived project config, src-dir values, and repository source discovery; the tool does not accept explicit file overrides.",
|
|
1413
1737
|
"Behavior contract: configured source files are processed in deterministic order, compression failures become structured error entries, symbol-analysis failures retain compressed output with symbol_analysis_status=error, and the tool fails only when no configured source file is compressed.",
|
|
1414
1738
|
],
|
|
@@ -1443,7 +1767,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1443
1767
|
pi.registerTool({
|
|
1444
1768
|
name: "find",
|
|
1445
1769
|
label: "find",
|
|
1446
|
-
description: "Scope: configured project source directories. Return
|
|
1770
|
+
description: "Scope: configured project source directories. Return a token-optimized JSON payload with summary, repository, files, and execution sections. File entries expose structured statuses and match records with typed location, symbol, stripped-code, and Doxygen facts.",
|
|
1447
1771
|
promptSnippet: "Return the structured construct-search payload from the configured source directories.",
|
|
1448
1772
|
promptGuidelines: buildFindToolPromptGuidelines("configured-source-directories"),
|
|
1449
1773
|
parameters: findSchema,
|
|
@@ -1468,41 +1792,45 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1468
1792
|
pi.registerTool({
|
|
1469
1793
|
name: "tokens",
|
|
1470
1794
|
label: "tokens",
|
|
1471
|
-
description: "Scope: canonical docs from the configured docs-dir. Return the same
|
|
1795
|
+
description: "Scope: canonical docs from the configured docs-dir. Return the same token-optimized JSON contract as files-tokens, omitting canonical-doc request echoes from runtime responses.",
|
|
1472
1796
|
promptSnippet: "Return the structured token-analysis payload for canonical documentation files.",
|
|
1473
1797
|
promptGuidelines: [
|
|
1474
1798
|
"Scope: no params; resolve docs-dir from project config; target canonical docs REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md.",
|
|
1475
|
-
"Output contract:
|
|
1476
|
-
"Numeric contract: counts, sizes, shares, and line ranges remain in dedicated numeric fields; guidance
|
|
1799
|
+
"Output contract: summary + files + execution. Static docs-dir and canonical-doc selection facts remain documented in registration metadata; file entries expose direct-access path facts, line ranges, sizes, token metrics, and optional metadata.",
|
|
1800
|
+
"Numeric contract: counts, sizes, shares, and line ranges remain in dedicated numeric fields; derived guidance is omitted from runtime responses to reduce token cost.",
|
|
1477
1801
|
"Behavior contract: missing canonical docs become skipped entries; read failures become error entries; the tool fails only when no processable canonical docs remain.",
|
|
1478
1802
|
],
|
|
1479
1803
|
parameters: tokensSchema,
|
|
1480
1804
|
async execute() {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1805
|
+
try {
|
|
1806
|
+
const projectBase = getProjectBase(process.cwd());
|
|
1807
|
+
const config = loadProjectConfig(process.cwd());
|
|
1808
|
+
const docsDir = config["docs-dir"].replace(/[/\\]+$/, "");
|
|
1809
|
+
const canonicalDocNames = ["REQUIREMENTS.md", "WORKFLOW.md", "REFERENCES.md"];
|
|
1810
|
+
const payload = buildTokenToolPayload({
|
|
1811
|
+
toolName: "tokens",
|
|
1812
|
+
scope: "canonical-docs",
|
|
1813
|
+
baseDir: projectBase,
|
|
1814
|
+
requestedPaths: canonicalDocNames.map((name) => path.join(docsDir, name)),
|
|
1815
|
+
docsDir,
|
|
1816
|
+
canonicalDocNames,
|
|
1817
|
+
encodingName: TOKEN_COUNTER_ENCODING,
|
|
1818
|
+
});
|
|
1819
|
+
return buildTokenToolExecuteResult(payload);
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
return buildFailedTokenToolExecuteResult(error);
|
|
1822
|
+
}
|
|
1495
1823
|
},
|
|
1496
1824
|
});
|
|
1497
1825
|
|
|
1498
1826
|
pi.registerTool({
|
|
1499
1827
|
name: "files-static-check",
|
|
1500
1828
|
label: "files-static-check",
|
|
1501
|
-
description: "Scope: explicit files. Return a
|
|
1829
|
+
description: "Scope: explicit files. Return a token-optimized JSON payload with summary, files, and execution sections. File entries expose canonical paths, detected language, configured checker modules, selection status, and stable error facts.",
|
|
1502
1830
|
promptSnippet: "Return the structured explicit-file static-check payload for the current project configuration.",
|
|
1503
1831
|
promptGuidelines: [
|
|
1504
1832
|
"Input contract: files[]. Scope is explicit caller-selected files resolved from the current working directory.",
|
|
1505
|
-
"Output contract:
|
|
1833
|
+
"Output contract: summary + files + execution. File entries expose canonical_path, language_name, configured_checker_modules, status, and error_message.",
|
|
1506
1834
|
"Configuration contract: checker selection is derived from the cwd-resolved static-check configuration and file extensions only.",
|
|
1507
1835
|
"Failure contract: execution.code mirrors aggregated checker failures; execution.stdout_lines and execution.stderr_lines preserve residual checker diagnostics.",
|
|
1508
1836
|
],
|
|
@@ -1510,7 +1838,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1510
1838
|
async execute(_toolCallId, params) {
|
|
1511
1839
|
const projectBase = getProjectBase(process.cwd());
|
|
1512
1840
|
const config = loadProjectConfig(process.cwd());
|
|
1513
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1514
1841
|
const staticCheckConfig = config["static-check"] ?? {};
|
|
1515
1842
|
const result = runFilesStaticCheck(params.files, projectBase, config);
|
|
1516
1843
|
const payload = buildStaticCheckToolPayload(
|
|
@@ -1521,7 +1848,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1521
1848
|
[],
|
|
1522
1849
|
[],
|
|
1523
1850
|
staticCheckConfig,
|
|
1524
|
-
runtimePaths,
|
|
1525
1851
|
buildToolExecutionSection(result),
|
|
1526
1852
|
);
|
|
1527
1853
|
return buildStructuredToolExecuteResult(payload);
|
|
@@ -1531,17 +1857,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1531
1857
|
const staticCheckSchema = Type.Object(
|
|
1532
1858
|
{},
|
|
1533
1859
|
{
|
|
1534
|
-
description: "Input contract: no params. Scope is the configured src-dir plus tests-dir selection after fixture exclusion. Output contract: JSON object with
|
|
1860
|
+
description: "Input contract: no params. Scope is the configured src-dir plus tests-dir selection after fixture exclusion. Output contract: JSON object with summary, files, and execution.",
|
|
1535
1861
|
},
|
|
1536
1862
|
);
|
|
1537
1863
|
pi.registerTool({
|
|
1538
1864
|
name: "static-check",
|
|
1539
1865
|
label: "static-check",
|
|
1540
|
-
description: "Scope: configured source and test directories. Return a
|
|
1866
|
+
description: "Scope: configured source and test directories. Return a token-optimized JSON payload with summary, files, and execution sections. File entries expose selected-path facts, checker coverage, selection status, and residual diagnostics metadata.",
|
|
1541
1867
|
promptSnippet: "Return the structured project static-check payload for the current configuration.",
|
|
1542
1868
|
promptGuidelines: [
|
|
1543
1869
|
"Input contract: no params. Scope is src-dir plus tests-dir from the cwd-derived project configuration.",
|
|
1544
|
-
"Output contract:
|
|
1870
|
+
"Output contract: summary + files + execution. Selection-directory rules remain documented in registration metadata; file entries expose configured_checker_modules and status.",
|
|
1545
1871
|
"Selection contract: tests/fixtures and <tests-dir>/fixtures are excluded before checker dispatch.",
|
|
1546
1872
|
"Failure contract: execution.code mirrors aggregated checker failures or selection failures; execution.stderr_lines preserve residual diagnostics.",
|
|
1547
1873
|
],
|
|
@@ -1549,7 +1875,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1549
1875
|
async execute() {
|
|
1550
1876
|
const projectBase = getProjectBase(process.cwd());
|
|
1551
1877
|
const config = loadProjectConfig(process.cwd());
|
|
1552
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1553
1878
|
const staticCheckConfig = config["static-check"] ?? {};
|
|
1554
1879
|
const selectionDirectoryPaths = [...config["src-dir"], config["tests-dir"]];
|
|
1555
1880
|
const testsDirRel = makeRelativeIfContainsProject(config["tests-dir"], projectBase)
|
|
@@ -1577,7 +1902,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1577
1902
|
selectionDirectoryPaths,
|
|
1578
1903
|
excludedDirectoryPaths,
|
|
1579
1904
|
staticCheckConfig,
|
|
1580
|
-
runtimePaths,
|
|
1581
1905
|
execution,
|
|
1582
1906
|
);
|
|
1583
1907
|
return buildStructuredToolExecuteResult(payload);
|
|
@@ -1587,17 +1911,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1587
1911
|
const gitCheckSchema = Type.Object(
|
|
1588
1912
|
{},
|
|
1589
1913
|
{
|
|
1590
|
-
description: "Input contract: no params. Output contract: JSON object with
|
|
1914
|
+
description: "Input contract: no params. Output contract: JSON object with result and execution. Result exposes git-path presence plus aggregate repository status fields.",
|
|
1591
1915
|
},
|
|
1592
1916
|
);
|
|
1593
1917
|
pi.registerTool({
|
|
1594
1918
|
name: "git-check",
|
|
1595
1919
|
label: "git-check",
|
|
1596
|
-
description: "Scope: current runtime path. Return a
|
|
1920
|
+
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes repository validation status through direct fields without request echoes.",
|
|
1597
1921
|
promptSnippet: "Return the structured git-validation payload for the runtime repository.",
|
|
1598
1922
|
promptGuidelines: [
|
|
1599
1923
|
"Input contract: no params. Scope is the cwd-derived runtime path context.",
|
|
1600
|
-
"Output contract:
|
|
1924
|
+
"Output contract: result + execution. Result exposes git_path_present and aggregate status.",
|
|
1601
1925
|
"Behavior contract: the tool checks work-tree membership, porcelain cleanliness, and symbolic-or-detached HEAD validity.",
|
|
1602
1926
|
"Failure contract: execution.code and execution.stderr_lines surface git-path or repository-state errors.",
|
|
1603
1927
|
],
|
|
@@ -1605,14 +1929,13 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1605
1929
|
async execute() {
|
|
1606
1930
|
const projectBase = getProjectBase(process.cwd());
|
|
1607
1931
|
const config = loadProjectConfig(process.cwd());
|
|
1608
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1609
1932
|
let execution;
|
|
1610
1933
|
try {
|
|
1611
1934
|
execution = buildToolExecutionSection(runGitCheck(projectBase, config));
|
|
1612
1935
|
} catch (error) {
|
|
1613
1936
|
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
1614
1937
|
}
|
|
1615
|
-
const payload = buildGitCheckToolPayload(projectBase, resolveRuntimeGitPath(projectBase),
|
|
1938
|
+
const payload = buildGitCheckToolPayload(projectBase, resolveRuntimeGitPath(projectBase), execution);
|
|
1616
1939
|
return buildStructuredToolExecuteResult(payload);
|
|
1617
1940
|
},
|
|
1618
1941
|
});
|
|
@@ -1620,17 +1943,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1620
1943
|
const docsCheckSchema = Type.Object(
|
|
1621
1944
|
{},
|
|
1622
1945
|
{
|
|
1623
|
-
description: "Input contract: no params. Output contract: JSON object with
|
|
1946
|
+
description: "Input contract: no params. Output contract: JSON object with summary, files, and execution. File entries expose canonical paths, prompt_command remediation, and presence status for canonical docs.",
|
|
1624
1947
|
},
|
|
1625
1948
|
);
|
|
1626
1949
|
pi.registerTool({
|
|
1627
1950
|
name: "docs-check",
|
|
1628
1951
|
label: "docs-check",
|
|
1629
|
-
description: "Scope: canonical docs. Return a
|
|
1952
|
+
description: "Scope: canonical docs. Return a token-optimized JSON payload with summary, files, and execution sections. File entries expose remediation prompt commands and direct presence facts for REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md.",
|
|
1630
1953
|
promptSnippet: "Return the structured canonical-document validation payload.",
|
|
1631
1954
|
promptGuidelines: [
|
|
1632
1955
|
"Input contract: no params. Scope is docs-dir from the cwd-derived project configuration.",
|
|
1633
|
-
"Output contract:
|
|
1956
|
+
"Output contract: summary + files + execution. File entries expose file_name, canonical_path, prompt_command, and status.",
|
|
1634
1957
|
"Specialization trigger: remediation differs per missing canonical file through prompt_command.",
|
|
1635
1958
|
"Failure contract: execution.code is non-zero when any canonical document is missing; execution.stderr_lines enumerate missing files.",
|
|
1636
1959
|
],
|
|
@@ -1638,8 +1961,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1638
1961
|
async execute() {
|
|
1639
1962
|
const projectBase = getProjectBase(process.cwd());
|
|
1640
1963
|
const config = loadProjectConfig(process.cwd());
|
|
1641
|
-
const
|
|
1642
|
-
const payload = buildDocsCheckToolPayload(projectBase, config["docs-dir"], runtimePaths);
|
|
1964
|
+
const payload = buildDocsCheckToolPayload(projectBase, config["docs-dir"]);
|
|
1643
1965
|
return buildStructuredToolExecuteResult(payload);
|
|
1644
1966
|
},
|
|
1645
1967
|
});
|
|
@@ -1647,17 +1969,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1647
1969
|
const gitWtNameSchema = Type.Object(
|
|
1648
1970
|
{},
|
|
1649
1971
|
{
|
|
1650
|
-
description: "Input contract: no params. Output contract: JSON object with
|
|
1972
|
+
description: "Input contract: no params. Output contract: JSON object with result and execution. Result exposes worktree_name when generation succeeds.",
|
|
1651
1973
|
},
|
|
1652
1974
|
);
|
|
1653
1975
|
pi.registerTool({
|
|
1654
1976
|
name: "git-wt-name",
|
|
1655
1977
|
label: "git-wt-name",
|
|
1656
|
-
description: "Scope: current runtime path. Return a
|
|
1978
|
+
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the generated worktree name while static naming rules remain in registration metadata.",
|
|
1657
1979
|
promptSnippet: "Return the structured worktree-name generation payload.",
|
|
1658
1980
|
promptGuidelines: [
|
|
1659
1981
|
"Input contract: no params. Scope is the cwd-derived runtime path context.",
|
|
1660
|
-
"Output contract:
|
|
1982
|
+
"Output contract: result + execution. Result exposes worktree_name.",
|
|
1661
1983
|
"Behavior contract: generation follows useReq-<project>-<sanitized-branch>-<YYYYMMDDHHMMSS>.",
|
|
1662
1984
|
"Failure contract: execution.code and execution.stderr_lines surface git-path or branch-resolution errors.",
|
|
1663
1985
|
],
|
|
@@ -1665,14 +1987,13 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1665
1987
|
async execute() {
|
|
1666
1988
|
const projectBase = getProjectBase(process.cwd());
|
|
1667
1989
|
const config = loadProjectConfig(process.cwd());
|
|
1668
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1669
1990
|
let execution;
|
|
1670
1991
|
try {
|
|
1671
1992
|
execution = buildToolExecutionSection(runGitWtName(projectBase, config));
|
|
1672
1993
|
} catch (error) {
|
|
1673
1994
|
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
1674
1995
|
}
|
|
1675
|
-
const payload = buildWorktreeNameToolPayload(projectBase, resolveRuntimeGitPath(projectBase),
|
|
1996
|
+
const payload = buildWorktreeNameToolPayload(projectBase, resolveRuntimeGitPath(projectBase), execution);
|
|
1676
1997
|
return buildStructuredToolExecuteResult(payload);
|
|
1677
1998
|
},
|
|
1678
1999
|
});
|
|
@@ -1682,17 +2003,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1682
2003
|
wtName: Type.String({ description: "Exact target worktree name and branch name" }),
|
|
1683
2004
|
},
|
|
1684
2005
|
{
|
|
1685
|
-
description: "Input contract: wtName. Output contract: JSON object with
|
|
2006
|
+
description: "Input contract: wtName. Output contract: JSON object with result and execution. Result exposes worktree_name and derived worktree_path.",
|
|
1686
2007
|
},
|
|
1687
2008
|
);
|
|
1688
2009
|
pi.registerTool({
|
|
1689
2010
|
name: "git-wt-create",
|
|
1690
2011
|
label: "git-wt-create",
|
|
1691
|
-
description: "Scope: current runtime path. Return a
|
|
2012
|
+
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the exact worktree name and derived path without static operation echoes.",
|
|
1692
2013
|
promptSnippet: "Return the structured worktree-creation payload for the requested name.",
|
|
1693
2014
|
promptGuidelines: [
|
|
1694
2015
|
"Input contract: wtName is required and must match the exact worktree/branch name to create.",
|
|
1695
|
-
"Output contract:
|
|
2016
|
+
"Output contract: result + execution. Result exposes worktree_name and worktree_path.",
|
|
1696
2017
|
"Specialization trigger: worktree_path depends on the runtime git root parent directory.",
|
|
1697
2018
|
"Failure contract: execution.code and execution.stderr_lines surface invalid-name, git, or finalization errors.",
|
|
1698
2019
|
],
|
|
@@ -1700,7 +2021,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1700
2021
|
async execute(_toolCallId, params) {
|
|
1701
2022
|
const projectBase = getProjectBase(process.cwd());
|
|
1702
2023
|
const config = loadProjectConfig(process.cwd());
|
|
1703
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1704
2024
|
let execution;
|
|
1705
2025
|
try {
|
|
1706
2026
|
execution = buildToolExecutionSection(runGitWtCreate(projectBase, params.wtName, config));
|
|
@@ -1712,7 +2032,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1712
2032
|
projectBase,
|
|
1713
2033
|
resolveRuntimeGitPath(projectBase),
|
|
1714
2034
|
params.wtName,
|
|
1715
|
-
runtimePaths,
|
|
1716
2035
|
execution,
|
|
1717
2036
|
);
|
|
1718
2037
|
return buildStructuredToolExecuteResult(payload);
|
|
@@ -1724,17 +2043,17 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1724
2043
|
wtName: Type.String({ description: "Exact target worktree name and branch name" }),
|
|
1725
2044
|
},
|
|
1726
2045
|
{
|
|
1727
|
-
description: "Input contract: wtName. Output contract: JSON object with
|
|
2046
|
+
description: "Input contract: wtName. Output contract: JSON object with result and execution. Result exposes worktree_name and derived worktree_path.",
|
|
1728
2047
|
},
|
|
1729
2048
|
);
|
|
1730
2049
|
pi.registerTool({
|
|
1731
2050
|
name: "git-wt-delete",
|
|
1732
2051
|
label: "git-wt-delete",
|
|
1733
|
-
description: "Scope: current runtime path. Return a
|
|
2052
|
+
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the exact worktree name and derived path without static operation echoes.",
|
|
1734
2053
|
promptSnippet: "Return the structured worktree-deletion payload for the requested name.",
|
|
1735
2054
|
promptGuidelines: [
|
|
1736
2055
|
"Input contract: wtName is required and must match the exact worktree/branch name to delete.",
|
|
1737
|
-
"Output contract:
|
|
2056
|
+
"Output contract: result + execution. Result exposes worktree_name and worktree_path.",
|
|
1738
2057
|
"Specialization trigger: worktree_path depends on the runtime git root parent directory.",
|
|
1739
2058
|
"Failure contract: execution.code and execution.stderr_lines surface missing-target or deletion errors.",
|
|
1740
2059
|
],
|
|
@@ -1742,7 +2061,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1742
2061
|
async execute(_toolCallId, params) {
|
|
1743
2062
|
const projectBase = getProjectBase(process.cwd());
|
|
1744
2063
|
const config = loadProjectConfig(process.cwd());
|
|
1745
|
-
const runtimePaths = buildSharedRuntimePathFacts(process.cwd(), config);
|
|
1746
2064
|
let execution;
|
|
1747
2065
|
try {
|
|
1748
2066
|
execution = buildToolExecutionSection(runGitWtDelete(projectBase, params.wtName, config));
|
|
@@ -1754,7 +2072,6 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1754
2072
|
projectBase,
|
|
1755
2073
|
resolveRuntimeGitPath(projectBase),
|
|
1756
2074
|
params.wtName,
|
|
1757
|
-
runtimePaths,
|
|
1758
2075
|
execution,
|
|
1759
2076
|
);
|
|
1760
2077
|
return buildStructuredToolExecuteResult(payload);
|
|
@@ -1764,26 +2081,20 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1764
2081
|
|
|
1765
2082
|
/**
|
|
1766
2083
|
* @brief Builds the shared settings-menu choices for startup-tool management.
|
|
1767
|
-
* @details Serializes startup-tool actions into right-valued menu rows consumed by the shared settings-menu renderer. Runtime is O(t) in configurable-tool count. No external state is mutated.
|
|
2084
|
+
* @details Serializes startup-tool actions into right-valued menu rows consumed by the shared settings-menu renderer while omitting the removed status-reference action. Runtime is O(t) in configurable-tool count. No external state is mutated.
|
|
1768
2085
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
1769
2086
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1770
2087
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered startup-tool menu choices.
|
|
1771
|
-
* @satisfies REQ-007, REQ-151, REQ-152, REQ-153, REQ-154
|
|
2088
|
+
* @satisfies REQ-007, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193
|
|
1772
2089
|
*/
|
|
1773
2090
|
function buildPiUsereqToolsMenuChoices(pi: ExtensionAPI, config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
1774
2091
|
const tools = getPiUsereqStartupTools(pi);
|
|
1775
2092
|
return [
|
|
1776
2093
|
{
|
|
1777
|
-
id: "
|
|
1778
|
-
label: "
|
|
1779
|
-
value: `${getConfiguredEnabledPiUsereqTools(config).length}/${tools.length} enabled`,
|
|
1780
|
-
description: "Open the full startup-tool reference report in the editor.",
|
|
1781
|
-
},
|
|
1782
|
-
{
|
|
1783
|
-
id: "toggle-tool",
|
|
1784
|
-
label: "Toggle tool",
|
|
2094
|
+
id: "enable-tools",
|
|
2095
|
+
label: "Enable tools",
|
|
1785
2096
|
value: `${getConfiguredEnabledPiUsereqTools(config).length} enabled`,
|
|
1786
|
-
description: "Open the per-tool
|
|
2097
|
+
description: "Open the per-tool enablement menu for configurable startup tools.",
|
|
1787
2098
|
},
|
|
1788
2099
|
{
|
|
1789
2100
|
id: "enable-all-tools",
|
|
@@ -1798,14 +2109,14 @@ function buildPiUsereqToolsMenuChoices(pi: ExtensionAPI, config: UseReqConfig):
|
|
|
1798
2109
|
description: "Disable every configurable startup tool for future session starts.",
|
|
1799
2110
|
},
|
|
1800
2111
|
{
|
|
1801
|
-
id: "reset-
|
|
1802
|
-
label: "Reset
|
|
2112
|
+
id: "reset-defaults",
|
|
2113
|
+
label: "Reset defaults",
|
|
1803
2114
|
value: `${normalizeEnabledPiUsereqTools(undefined).length} defaults`,
|
|
1804
2115
|
description: "Restore the documented default startup-tool selection.",
|
|
1805
2116
|
},
|
|
1806
2117
|
{
|
|
1807
|
-
id: "
|
|
1808
|
-
label: "
|
|
2118
|
+
id: "save-and-close",
|
|
2119
|
+
label: "Save and close",
|
|
1809
2120
|
value: "",
|
|
1810
2121
|
description: "Return to the parent configuration menu.",
|
|
1811
2122
|
},
|
|
@@ -1833,7 +2144,7 @@ function buildPiUsereqToolToggleChoices(pi: ExtensionAPI, config: UseReqConfig):
|
|
|
1833
2144
|
id: "back",
|
|
1834
2145
|
label: "Back",
|
|
1835
2146
|
value: "",
|
|
1836
|
-
description: "Return to the
|
|
2147
|
+
description: "Return to the Enable tools menu.",
|
|
1837
2148
|
},
|
|
1838
2149
|
];
|
|
1839
2150
|
}
|
|
@@ -1845,23 +2156,22 @@ function buildPiUsereqToolToggleChoices(pi: ExtensionAPI, config: UseReqConfig):
|
|
|
1845
2156
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
1846
2157
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
1847
2158
|
* @return {Promise<void>} Promise resolved when the menu closes.
|
|
1848
|
-
* @satisfies REQ-007, REQ-063, REQ-064, REQ-151, REQ-152, REQ-153, REQ-154
|
|
2159
|
+
* @satisfies REQ-007, REQ-063, REQ-064, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193
|
|
1849
2160
|
*/
|
|
1850
2161
|
async function configurePiUsereqToolsMenu(pi: ExtensionAPI, ctx: ExtensionCommandContext, config: UseReqConfig): Promise<void> {
|
|
1851
2162
|
applyConfiguredPiUsereqTools(pi, config);
|
|
2163
|
+
let focusedChoiceId: string | undefined;
|
|
1852
2164
|
while (true) {
|
|
1853
2165
|
const tools = getPiUsereqStartupTools(pi);
|
|
1854
2166
|
const enabledTools = new Set(getConfiguredEnabledPiUsereqTools(config));
|
|
1855
|
-
const choice = await showPiUsereqSettingsMenu(ctx, "
|
|
2167
|
+
const choice = await showPiUsereqSettingsMenu(ctx, "Enable tools", buildPiUsereqToolsMenuChoices(pi, config), {
|
|
2168
|
+
initialSelectedId: focusedChoiceId,
|
|
2169
|
+
});
|
|
1856
2170
|
|
|
1857
|
-
if (!choice || choice === "
|
|
2171
|
+
if (!choice || choice === "save-and-close") {
|
|
1858
2172
|
return;
|
|
1859
2173
|
}
|
|
1860
|
-
|
|
1861
|
-
if (choice === "show-tool-status") {
|
|
1862
|
-
ctx.ui.setEditorText(renderPiUsereqToolsReference(pi, config));
|
|
1863
|
-
continue;
|
|
1864
|
-
}
|
|
2174
|
+
focusedChoiceId = choice;
|
|
1865
2175
|
|
|
1866
2176
|
if (choice === "enable-all-tools") {
|
|
1867
2177
|
setConfiguredPiUsereqTools(pi, config, tools.map((tool) => tool.name));
|
|
@@ -1875,14 +2185,14 @@ async function configurePiUsereqToolsMenu(pi: ExtensionAPI, ctx: ExtensionComman
|
|
|
1875
2185
|
continue;
|
|
1876
2186
|
}
|
|
1877
2187
|
|
|
1878
|
-
if (choice === "reset-
|
|
2188
|
+
if (choice === "reset-defaults") {
|
|
1879
2189
|
setConfiguredPiUsereqTools(pi, config, normalizeEnabledPiUsereqTools(undefined));
|
|
1880
2190
|
ctx.ui.notify("Restored default configurable active tools", "info");
|
|
1881
2191
|
continue;
|
|
1882
2192
|
}
|
|
1883
2193
|
|
|
1884
|
-
if (choice === "
|
|
1885
|
-
const selectedToolName = await showPiUsereqSettingsMenu(ctx, "
|
|
2194
|
+
if (choice === "enable-tools") {
|
|
2195
|
+
const selectedToolName = await showPiUsereqSettingsMenu(ctx, "Enable tools", buildPiUsereqToolToggleChoices(pi, config));
|
|
1886
2196
|
if (!selectedToolName || selectedToolName === "back") {
|
|
1887
2197
|
continue;
|
|
1888
2198
|
}
|
|
@@ -1900,20 +2210,6 @@ async function configurePiUsereqToolsMenu(pi: ExtensionAPI, ctx: ExtensionComman
|
|
|
1900
2210
|
}
|
|
1901
2211
|
}
|
|
1902
2212
|
|
|
1903
|
-
/**
|
|
1904
|
-
* @brief Formats one static-check configuration entry for UI display.
|
|
1905
|
-
* @details Renders command-backed entries as `Command(cmd args...)` and all other modules as `Module(args...)`. Runtime is O(n) in parameter count. No side effects occur.
|
|
1906
|
-
* @param[in] entry {StaticCheckEntry} Static-check configuration entry.
|
|
1907
|
-
* @return {string} Human-readable entry summary.
|
|
1908
|
-
*/
|
|
1909
|
-
function formatStaticCheckEntry(entry: StaticCheckEntry): string {
|
|
1910
|
-
const params = Array.isArray(entry.params) && entry.params.length > 0 ? ` ${entry.params.join(" ")}` : "";
|
|
1911
|
-
if (entry.module === "Command") {
|
|
1912
|
-
return `${entry.module}(${entry.cmd ?? "?"}${params})`;
|
|
1913
|
-
}
|
|
1914
|
-
return `${entry.module}${params ? `(${entry.params!.join(" ")})` : ""}`;
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
2213
|
/**
|
|
1918
2214
|
* @brief Summarizes configured static-check languages.
|
|
1919
2215
|
* @details Keeps only languages with at least one configured checker, sorts them, and emits a compact `Language (count)` list. Runtime is O(l log l). No side effects occur.
|
|
@@ -1928,80 +2224,38 @@ function formatStaticCheckLanguagesSummary(config: UseReqConfig): string {
|
|
|
1928
2224
|
return languages.join(", ") || "(none)";
|
|
1929
2225
|
}
|
|
1930
2226
|
|
|
1931
|
-
/**
|
|
1932
|
-
* @brief Renders the static-check configuration reference view.
|
|
1933
|
-
* @details Produces a markdown-like summary containing configured entries, supported languages, the Command-only user module surface, and canonical example specifications. Runtime is O(l log l). No side effects occur.
|
|
1934
|
-
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1935
|
-
* @return {string} Reference text for the editor view.
|
|
1936
|
-
*/
|
|
1937
|
-
function renderStaticCheckReference(config: UseReqConfig): string {
|
|
1938
|
-
const lines = ["# Static-check configuration", "", `Configured languages: ${formatStaticCheckLanguagesSummary(config)}`, ""];
|
|
1939
|
-
const configuredLanguages = Object.entries(config["static-check"])
|
|
1940
|
-
.filter(([, entries]) => Array.isArray(entries) && entries.length > 0)
|
|
1941
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
1942
|
-
|
|
1943
|
-
if (configuredLanguages.length === 0) {
|
|
1944
|
-
lines.push("Configured entries: (none)");
|
|
1945
|
-
} else {
|
|
1946
|
-
lines.push("Configured entries:");
|
|
1947
|
-
for (const [language, entries] of configuredLanguages) {
|
|
1948
|
-
lines.push(`- ${language}: ${(entries as StaticCheckEntry[]).map(formatStaticCheckEntry).join(", ")}`);
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
lines.push("", "Supported languages:");
|
|
1953
|
-
for (const { language, extensions } of getSupportedStaticCheckLanguageSupport()) {
|
|
1954
|
-
lines.push(`- ${language}: ${extensions.join(", ")}`);
|
|
1955
|
-
}
|
|
1956
|
-
lines.push(
|
|
1957
|
-
"",
|
|
1958
|
-
`Supported modules: ${STATIC_CHECK_MODULES.join(", ")}`,
|
|
1959
|
-
"",
|
|
1960
|
-
"Examples:",
|
|
1961
|
-
"- Python=Command,mypy,--strict",
|
|
1962
|
-
"- TypeScript=Command,eslint,--max-warnings,0",
|
|
1963
|
-
);
|
|
1964
|
-
return `${lines.join("\n")}\n`;
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
2227
|
/**
|
|
1968
2228
|
* @brief Builds the shared settings-menu choices for static-check management.
|
|
1969
|
-
* @details Serializes Command-oriented static-check actions into right-valued menu rows consumed by the shared settings-menu renderer while omitting
|
|
2229
|
+
* @details Serializes guided Command-oriented static-check actions into right-valued menu rows consumed by the shared settings-menu renderer while omitting raw-spec and reference-only actions. Runtime is O(1). No external state is mutated.
|
|
1970
2230
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1971
2231
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered static-check menu choices.
|
|
1972
|
-
* @satisfies REQ-008, REQ-160, REQ-161, REQ-151, REQ-152, REQ-153, REQ-154
|
|
2232
|
+
* @satisfies REQ-008, REQ-150, REQ-160, REQ-161, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193
|
|
1973
2233
|
*/
|
|
1974
2234
|
function buildStaticCheckMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
1975
2235
|
const supportedLanguageCount = getSupportedStaticCheckLanguageSupport().length;
|
|
1976
2236
|
const configuredLanguageCount = Object.values(config["static-check"]).filter((entries) => entries.length > 0).length;
|
|
1977
2237
|
return [
|
|
1978
2238
|
{
|
|
1979
|
-
id: "add-
|
|
1980
|
-
label: "Add
|
|
2239
|
+
id: "add-static-check-entry",
|
|
2240
|
+
label: "Add static code checker",
|
|
1981
2241
|
value: `${supportedLanguageCount} languages`,
|
|
1982
|
-
description: "Select a supported language, then configure
|
|
2242
|
+
description: "Select a supported language, then configure one Command static-check executable.",
|
|
1983
2243
|
},
|
|
1984
2244
|
{
|
|
1985
|
-
id: "
|
|
1986
|
-
label: "
|
|
1987
|
-
value: "raw spec",
|
|
1988
|
-
description: "Enter one raw Command-based static-check specification string in canonical CLI format.",
|
|
1989
|
-
},
|
|
1990
|
-
{
|
|
1991
|
-
id: "remove-language-entry",
|
|
1992
|
-
label: "Remove language entry",
|
|
2245
|
+
id: "remove-static-check-entry",
|
|
2246
|
+
label: "Remove static code checker",
|
|
1993
2247
|
value: configuredLanguageCount > 0 ? `${configuredLanguageCount} configured` : "(none)",
|
|
1994
2248
|
description: "Remove every configured static-check entry for one language.",
|
|
1995
2249
|
},
|
|
1996
2250
|
{
|
|
1997
|
-
id: "
|
|
1998
|
-
label: "
|
|
1999
|
-
value: `${
|
|
2000
|
-
description: "
|
|
2251
|
+
id: "reset-defaults",
|
|
2252
|
+
label: "Reset defaults",
|
|
2253
|
+
value: configuredLanguageCount > 0 ? `${configuredLanguageCount} configured` : "(none)",
|
|
2254
|
+
description: "Remove every configured static-check entry and restore the default empty static-check configuration.",
|
|
2001
2255
|
},
|
|
2002
2256
|
{
|
|
2003
|
-
id: "
|
|
2004
|
-
label: "
|
|
2257
|
+
id: "save-and-close",
|
|
2258
|
+
label: "Save and close",
|
|
2005
2259
|
value: "",
|
|
2006
2260
|
description: "Return to the parent configuration menu.",
|
|
2007
2261
|
},
|
|
@@ -2030,7 +2284,7 @@ function buildSupportedStaticCheckLanguageChoices(config: UseReqConfig): PiUsere
|
|
|
2030
2284
|
id: "back",
|
|
2031
2285
|
label: "Back",
|
|
2032
2286
|
value: "",
|
|
2033
|
-
description: "Return to the static
|
|
2287
|
+
description: "Return to the Language static code checkers menu.",
|
|
2034
2288
|
},
|
|
2035
2289
|
];
|
|
2036
2290
|
}
|
|
@@ -2055,33 +2309,32 @@ function buildConfiguredStaticCheckLanguageChoices(config: UseReqConfig): PiUser
|
|
|
2055
2309
|
id: "back",
|
|
2056
2310
|
label: "Back",
|
|
2057
2311
|
value: "",
|
|
2058
|
-
description: "Return to the static
|
|
2312
|
+
description: "Return to the Language static code checkers menu.",
|
|
2059
2313
|
},
|
|
2060
2314
|
];
|
|
2061
2315
|
}
|
|
2062
2316
|
|
|
2063
2317
|
/**
|
|
2064
2318
|
* @brief Runs the interactive static-check configuration menu.
|
|
2065
|
-
* @details Lets the user
|
|
2319
|
+
* @details Lets the user add Command entries by guided prompts, remove configured language entries, and reset the static-check configuration through the shared settings-menu renderer until the user exits. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
2066
2320
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
2067
2321
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
2068
2322
|
* @return {Promise<void>} Promise resolved when the menu closes.
|
|
2069
|
-
* @satisfies REQ-008, REQ-160, REQ-161, REQ-151, REQ-152, REQ-153, REQ-154
|
|
2323
|
+
* @satisfies REQ-008, REQ-150, REQ-160, REQ-161, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193, REQ-195
|
|
2070
2324
|
*/
|
|
2071
2325
|
async function configureStaticCheckMenu(ctx: ExtensionCommandContext, config: UseReqConfig): Promise<void> {
|
|
2326
|
+
let focusedChoiceId: string | undefined;
|
|
2072
2327
|
while (true) {
|
|
2073
|
-
const staticChoice = await showPiUsereqSettingsMenu(ctx, "static
|
|
2328
|
+
const staticChoice = await showPiUsereqSettingsMenu(ctx, "Language static code checkers", buildStaticCheckMenuChoices(config), {
|
|
2329
|
+
initialSelectedId: focusedChoiceId,
|
|
2330
|
+
});
|
|
2074
2331
|
|
|
2075
|
-
if (!staticChoice || staticChoice === "
|
|
2332
|
+
if (!staticChoice || staticChoice === "save-and-close") {
|
|
2076
2333
|
return;
|
|
2077
2334
|
}
|
|
2335
|
+
focusedChoiceId = staticChoice;
|
|
2078
2336
|
|
|
2079
|
-
if (staticChoice === "
|
|
2080
|
-
ctx.ui.setEditorText(renderStaticCheckReference(config));
|
|
2081
|
-
continue;
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
if (staticChoice === "add-entry-supported-language") {
|
|
2337
|
+
if (staticChoice === "add-static-check-entry") {
|
|
2085
2338
|
const selectedLanguage = await showPiUsereqSettingsMenu(ctx, "static-check language", buildSupportedStaticCheckLanguageChoices(config));
|
|
2086
2339
|
if (!selectedLanguage || selectedLanguage === "back") {
|
|
2087
2340
|
continue;
|
|
@@ -2109,29 +2362,18 @@ async function configureStaticCheckMenu(ctx: ExtensionCommandContext, config: Us
|
|
|
2109
2362
|
continue;
|
|
2110
2363
|
}
|
|
2111
2364
|
|
|
2112
|
-
if (staticChoice === "
|
|
2113
|
-
const
|
|
2114
|
-
if (!spec?.trim()) {
|
|
2115
|
-
continue;
|
|
2116
|
-
}
|
|
2117
|
-
try {
|
|
2118
|
-
const [canonicalLang, entry] = parseEnableStaticCheck(spec.trim());
|
|
2119
|
-
config["static-check"][canonicalLang] ??= [];
|
|
2120
|
-
config["static-check"][canonicalLang]!.push(entry);
|
|
2121
|
-
ctx.ui.notify(`Added ${entry.module} checker for ${canonicalLang}`, "info");
|
|
2122
|
-
} catch (error) {
|
|
2123
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
2124
|
-
}
|
|
2125
|
-
continue;
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
if (staticChoice === "remove-language-entry") {
|
|
2129
|
-
const configuredLanguage = await showPiUsereqSettingsMenu(ctx, "remove static-check language", buildConfiguredStaticCheckLanguageChoices(config));
|
|
2365
|
+
if (staticChoice === "remove-static-check-entry") {
|
|
2366
|
+
const configuredLanguage = await showPiUsereqSettingsMenu(ctx, "Remove static code checker", buildConfiguredStaticCheckLanguageChoices(config));
|
|
2130
2367
|
if (!configuredLanguage || configuredLanguage === "back") {
|
|
2131
2368
|
continue;
|
|
2132
2369
|
}
|
|
2133
2370
|
delete config["static-check"][configuredLanguage];
|
|
2134
2371
|
ctx.ui.notify(`Removed static-check entries for ${configuredLanguage}`, "info");
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
if (staticChoice === "reset-defaults") {
|
|
2375
|
+
config["static-check"] = {};
|
|
2376
|
+
ctx.ui.notify("Restored default static code checker configuration", "info");
|
|
2135
2377
|
}
|
|
2136
2378
|
}
|
|
2137
2379
|
}
|
|
@@ -2142,7 +2384,7 @@ async function configureStaticCheckMenu(ctx: ExtensionCommandContext, config: Us
|
|
|
2142
2384
|
* @param[in] cwd {string} Current working directory.
|
|
2143
2385
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2144
2386
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered top-level menu choices.
|
|
2145
|
-
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162
|
|
2387
|
+
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197
|
|
2146
2388
|
*/
|
|
2147
2389
|
function buildPiUsereqMenuChoices(
|
|
2148
2390
|
cwd: string,
|
|
@@ -2151,43 +2393,43 @@ function buildPiUsereqMenuChoices(
|
|
|
2151
2393
|
return [
|
|
2152
2394
|
{
|
|
2153
2395
|
id: "docs-dir",
|
|
2154
|
-
label: "
|
|
2396
|
+
label: "Document directory",
|
|
2155
2397
|
value: config["docs-dir"],
|
|
2156
2398
|
description: "Edit the repository-relative directory that stores REQUIREMENTS, WORKFLOW, and REFERENCES documents.",
|
|
2157
2399
|
},
|
|
2158
|
-
{
|
|
2159
|
-
id: "tests-dir",
|
|
2160
|
-
label: "tests-dir",
|
|
2161
|
-
value: config["tests-dir"],
|
|
2162
|
-
description: "Edit the repository-relative directory used for project test assets and static-check selection.",
|
|
2163
|
-
},
|
|
2164
2400
|
{
|
|
2165
2401
|
id: "src-dir",
|
|
2166
|
-
label: "
|
|
2402
|
+
label: "Source-code directories",
|
|
2167
2403
|
value: config["src-dir"].join(", "),
|
|
2168
2404
|
description: "Manage the repository-relative source directories scanned by project-scope tools.",
|
|
2169
2405
|
},
|
|
2406
|
+
{
|
|
2407
|
+
id: "tests-dir",
|
|
2408
|
+
label: "Unit tests directory",
|
|
2409
|
+
value: config["tests-dir"],
|
|
2410
|
+
description: "Edit the repository-relative directory used for project test assets and static-check selection.",
|
|
2411
|
+
},
|
|
2170
2412
|
{
|
|
2171
2413
|
id: "static-check",
|
|
2172
|
-
label: "static
|
|
2414
|
+
label: "Language static code checkers",
|
|
2173
2415
|
value: formatStaticCheckLanguagesSummary(config),
|
|
2174
|
-
description: "Manage
|
|
2416
|
+
description: "Manage guided Command static-check entries by language.",
|
|
2175
2417
|
},
|
|
2176
2418
|
{
|
|
2177
2419
|
id: "startup-tools",
|
|
2178
|
-
label: "
|
|
2420
|
+
label: "Enable tools",
|
|
2179
2421
|
value: `${getConfiguredEnabledPiUsereqTools(config).length} enabled`,
|
|
2180
2422
|
description: "Manage which configurable tools become active during session_start.",
|
|
2181
2423
|
},
|
|
2182
2424
|
{
|
|
2183
2425
|
id: "notifications",
|
|
2184
|
-
label: "
|
|
2185
|
-
value: `
|
|
2186
|
-
description: "Manage
|
|
2426
|
+
label: "Notifications",
|
|
2427
|
+
value: `notification:${formatPiNotifyStatus(config)} • sound:${config["notify-sound"]} • pushover:${formatPiNotifyPushoverStatus(config)}`,
|
|
2428
|
+
description: "Manage command-notify, sound, and Pushover settings with dedicated event submenus.",
|
|
2187
2429
|
},
|
|
2188
2430
|
{
|
|
2189
2431
|
id: "show-config",
|
|
2190
|
-
label: "
|
|
2432
|
+
label: "Show configuration",
|
|
2191
2433
|
value: formatProjectConfigPathForMenu(cwd),
|
|
2192
2434
|
valueTone: "dim",
|
|
2193
2435
|
description: "Write the current project configuration JSON into the editor without saving additional changes.",
|
|
@@ -2212,25 +2454,31 @@ function buildPiUsereqMenuChoices(
|
|
|
2212
2454
|
* @details Exposes add and remove actions for `src-dir` entries through right-valued menu rows consumed by the shared settings-menu renderer. Runtime is O(s) in source-directory count. No external state is mutated.
|
|
2213
2455
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2214
2456
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered source-directory management choices.
|
|
2215
|
-
* @satisfies REQ-006, REQ-151, REQ-152, REQ-153, REQ-154
|
|
2457
|
+
* @satisfies REQ-006, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193
|
|
2216
2458
|
*/
|
|
2217
2459
|
function buildSrcDirMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
2218
2460
|
return [
|
|
2219
2461
|
{
|
|
2220
2462
|
id: "add-src-dir-entry",
|
|
2221
|
-
label: "Add
|
|
2463
|
+
label: "Add source-code directory",
|
|
2222
2464
|
value: `${config["src-dir"].length} configured`,
|
|
2223
2465
|
description: "Append one repository-relative source directory to the current configuration.",
|
|
2224
2466
|
},
|
|
2225
2467
|
{
|
|
2226
2468
|
id: "remove-src-dir-entry",
|
|
2227
|
-
label: "Remove
|
|
2469
|
+
label: "Remove source-code directory",
|
|
2228
2470
|
value: config["src-dir"].join(", "),
|
|
2229
2471
|
description: "Select one configured source directory to remove from the current configuration.",
|
|
2230
2472
|
},
|
|
2231
2473
|
{
|
|
2232
|
-
id: "
|
|
2233
|
-
label: "
|
|
2474
|
+
id: "reset-defaults",
|
|
2475
|
+
label: "Reset defaults",
|
|
2476
|
+
value: `${DEFAULT_SRC_DIRS.join(", ")}`,
|
|
2477
|
+
description: "Restore the documented default source-directory configuration.",
|
|
2478
|
+
},
|
|
2479
|
+
{
|
|
2480
|
+
id: "save-and-close",
|
|
2481
|
+
label: "Save and close",
|
|
2234
2482
|
value: "",
|
|
2235
2483
|
description: "Return to the parent configuration menu.",
|
|
2236
2484
|
},
|
|
@@ -2268,7 +2516,7 @@ function buildSrcDirRemovalChoices(config: UseReqConfig): PiUsereqSettingsMenuCh
|
|
|
2268
2516
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
2269
2517
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
2270
2518
|
* @return {Promise<void>} Promise resolved when configuration is saved and the menu closes.
|
|
2271
|
-
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-162
|
|
2519
|
+
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-162, REQ-190, REQ-191, REQ-192, REQ-194, REQ-195
|
|
2272
2520
|
*/
|
|
2273
2521
|
async function configurePiUsereq(
|
|
2274
2522
|
pi: ExtensionAPI,
|
|
@@ -2284,11 +2532,13 @@ async function configurePiUsereq(
|
|
|
2284
2532
|
renderPiUsereqStatus(statusController, ctx);
|
|
2285
2533
|
};
|
|
2286
2534
|
|
|
2535
|
+
let focusedChoiceId: string | undefined;
|
|
2287
2536
|
while (true) {
|
|
2288
2537
|
const choice = await showPiUsereqSettingsMenu(
|
|
2289
2538
|
ctx,
|
|
2290
2539
|
"pi-usereq",
|
|
2291
2540
|
buildPiUsereqMenuChoices(ctx.cwd, config),
|
|
2541
|
+
{ initialSelectedId: focusedChoiceId },
|
|
2292
2542
|
);
|
|
2293
2543
|
if (!choice || choice === "save-and-close") {
|
|
2294
2544
|
ensureSaved();
|
|
@@ -2298,37 +2548,47 @@ async function configurePiUsereq(
|
|
|
2298
2548
|
}
|
|
2299
2549
|
return;
|
|
2300
2550
|
}
|
|
2551
|
+
focusedChoiceId = choice;
|
|
2301
2552
|
if (choice === "docs-dir") {
|
|
2302
|
-
const value = await ctx.ui.input("
|
|
2553
|
+
const value = await ctx.ui.input("Document directory", config["docs-dir"]);
|
|
2303
2554
|
if (value?.trim()) config["docs-dir"] = value.trim();
|
|
2304
2555
|
continue;
|
|
2305
2556
|
}
|
|
2306
2557
|
if (choice === "tests-dir") {
|
|
2307
|
-
const value = await ctx.ui.input("tests
|
|
2558
|
+
const value = await ctx.ui.input("Unit tests directory", config["tests-dir"]);
|
|
2308
2559
|
if (value?.trim()) config["tests-dir"] = value.trim();
|
|
2309
2560
|
continue;
|
|
2310
2561
|
}
|
|
2311
2562
|
if (choice === "src-dir") {
|
|
2563
|
+
let srcFocusedChoiceId: string | undefined;
|
|
2312
2564
|
while (true) {
|
|
2313
|
-
const srcAction = await showPiUsereqSettingsMenu(ctx, "
|
|
2314
|
-
|
|
2565
|
+
const srcAction = await showPiUsereqSettingsMenu(ctx, "Source-code directories", buildSrcDirMenuChoices(config), {
|
|
2566
|
+
initialSelectedId: srcFocusedChoiceId,
|
|
2567
|
+
});
|
|
2568
|
+
if (!srcAction || srcAction === "save-and-close") {
|
|
2315
2569
|
break;
|
|
2316
2570
|
}
|
|
2571
|
+
srcFocusedChoiceId = srcAction;
|
|
2317
2572
|
if (srcAction === "add-src-dir-entry") {
|
|
2318
|
-
const value = await ctx.ui.input("New
|
|
2573
|
+
const value = await ctx.ui.input("New source-code directory", "src");
|
|
2319
2574
|
if (value?.trim()) {
|
|
2320
2575
|
config["src-dir"] = [...config["src-dir"], value.trim()];
|
|
2321
2576
|
}
|
|
2322
2577
|
continue;
|
|
2323
2578
|
}
|
|
2324
2579
|
if (srcAction === "remove-src-dir-entry") {
|
|
2325
|
-
const toRemove = await showPiUsereqSettingsMenu(ctx, "
|
|
2580
|
+
const toRemove = await showPiUsereqSettingsMenu(ctx, "Remove source-code directory", buildSrcDirRemovalChoices(config));
|
|
2326
2581
|
if (toRemove && toRemove !== "back") {
|
|
2327
2582
|
config["src-dir"] = config["src-dir"].filter((entry) => entry !== toRemove);
|
|
2328
2583
|
if (config["src-dir"].length === 0) {
|
|
2329
2584
|
config["src-dir"] = ["src"];
|
|
2330
2585
|
}
|
|
2331
2586
|
}
|
|
2587
|
+
continue;
|
|
2588
|
+
}
|
|
2589
|
+
if (srcAction === "reset-defaults") {
|
|
2590
|
+
config["src-dir"] = [...DEFAULT_SRC_DIRS];
|
|
2591
|
+
ctx.ui.notify("Restored default source-code directories", "info");
|
|
2332
2592
|
}
|
|
2333
2593
|
}
|
|
2334
2594
|
continue;
|
|
@@ -2348,6 +2608,7 @@ async function configurePiUsereq(
|
|
|
2348
2608
|
if (choice === "reset-defaults") {
|
|
2349
2609
|
config = getDefaultConfig(projectBase);
|
|
2350
2610
|
applyConfiguredPiUsereqTools(pi, config);
|
|
2611
|
+
ctx.ui.notify("Restored all default configuration values", "info");
|
|
2351
2612
|
continue;
|
|
2352
2613
|
}
|
|
2353
2614
|
if (choice === "show-config") {
|
|
@@ -2381,7 +2642,7 @@ function registerConfigCommands(
|
|
|
2381
2642
|
* @brief Registers the complete pi-usereq extension.
|
|
2382
2643
|
* @details Validates installation-owned bundled resources, registers prompt and
|
|
2383
2644
|
* configuration commands plus agent tools, registers the configurable
|
|
2384
|
-
*
|
|
2645
|
+
* notification-sound shortcut when the runtime supports shortcuts, and
|
|
2385
2646
|
* installs shared wrappers for all supported pi lifecycle hooks so status
|
|
2386
2647
|
* telemetry, context usage, prompt timing, cumulative runtime, prompt-specific
|
|
2387
2648
|
* Pushover metadata, and pi-notify effects remain synchronized with runtime
|
|
@@ -2391,7 +2652,7 @@ function registerConfigCommands(
|
|
|
2391
2652
|
* timer scheduling.
|
|
2392
2653
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
2393
2654
|
* @return {void} No return value.
|
|
2394
|
-
* @satisfies DES-002, REQ-004, REQ-005, REQ-009, REQ-044, REQ-045, 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-
|
|
2655
|
+
* @satisfies DES-002, REQ-004, REQ-005, REQ-009, REQ-044, REQ-045, 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-148, 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
|
|
2395
2656
|
*/
|
|
2396
2657
|
export default function piUsereqExtension(pi: ExtensionAPI): void {
|
|
2397
2658
|
const statusController = createPiUsereqStatusController();
|