pi-usereq 0.60.0 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +4 -4
- package/src/core/context-file-size.ts +109 -0
- package/src/core/prompt-command-runtime.ts +131 -3
- package/src/core/prompts.ts +10 -3
- package/src/core/static-check.ts +1 -1
- package/src/index.ts +251 -40
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-usereq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Ogekuri/PI-useReq.git"
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@earendil-works/pi-ai": "^0.
|
|
36
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
37
|
-
"@earendil-works/pi-tui": "^0.
|
|
35
|
+
"@earendil-works/pi-ai": "^1.0.0",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "^1.0.0",
|
|
37
|
+
"@earendil-works/pi-tui": "^1.0.0",
|
|
38
38
|
"@sinclair/typebox": "^0.34.49"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @brief Measures runtime character and token sizes of the canonical context files.
|
|
4
|
+
* @details Provides reusable helpers that compute character and `cl100k_base` token estimates for `REQUIREMENTS.md`, `REFERENCES.md`, and `WORKFLOW.md` under `<base-path>/<docs-dir>` so configuration menus and command summaries share one measurement contract. Runtime is O(n) in aggregate context-file size. Side effects are limited to filesystem reads.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { DEFAULT_DOCS_DIR, type UseReqConfig } from "./config.js";
|
|
10
|
+
import { normalizeRelativeDirContract } from "./path-context.js";
|
|
11
|
+
import { countFileMetrics } from "./token-counter.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @brief Stores the canonical context-file names in their documented injection order.
|
|
15
|
+
* @details The tuple is the single ordering source reused by measurement helpers and size renderers so every caller reports `REQUIREMENTS.md`, `REFERENCES.md`, and `WORKFLOW.md` consistently. Lookup complexity is O(1).
|
|
16
|
+
* @satisfies REQ-373
|
|
17
|
+
*/
|
|
18
|
+
export const CONTEXT_FILE_NAMES = ["REQUIREMENTS.md", "REFERENCES.md", "WORKFLOW.md"] as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @brief Narrows context-file identifiers to the canonical measured document set.
|
|
22
|
+
* @details Compile-time alias derived from `CONTEXT_FILE_NAMES` and reused by the measured-facts record keys. The alias introduces no runtime cost.
|
|
23
|
+
*/
|
|
24
|
+
export type ContextFileName = (typeof CONTEXT_FILE_NAMES)[number];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @brief Describes the measured size facts of one canonical context file.
|
|
28
|
+
* @details Stores the filesystem existence flag plus the exact character count and the `cl100k_base` token estimate computed from the file content. The interface is compile-time only and introduces no runtime cost.
|
|
29
|
+
* @satisfies REQ-374
|
|
30
|
+
*/
|
|
31
|
+
export interface ContextFileSizeFacts {
|
|
32
|
+
readonly exists: boolean;
|
|
33
|
+
readonly chars: number;
|
|
34
|
+
readonly tokens: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @brief Stores the canonical missing-file size facts.
|
|
39
|
+
* @details Reused as the deterministic result for missing, non-file, and unreadable targets so callers never receive `undefined` facts. Lookup complexity is O(1).
|
|
40
|
+
* @satisfies REQ-374
|
|
41
|
+
*/
|
|
42
|
+
export const MISSING_CONTEXT_FILE_SIZE: ContextFileSizeFacts = Object.freeze({ exists: false, chars: 0, tokens: 0 });
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @brief Resolves one canonical context-file absolute path for one project base.
|
|
46
|
+
* @details Joins the project base with the trailing-slash-free configured `docs-dir` (falling back to `DEFAULT_DOCS_DIR`) and the supplied canonical file name so menus, summaries, and the `%%CONTEXT_FILES%%` renderer address identical targets. Runtime is O(p) in path length. No external state is mutated.
|
|
47
|
+
* @param[in] projectBase {string} Absolute project root path.
|
|
48
|
+
* @param[in] config {UseReqConfig} Effective project configuration supplying the docs directory.
|
|
49
|
+
* @param[in] fileName {ContextFileName} Canonical context-file name.
|
|
50
|
+
* @return {string} Absolute context-file path.
|
|
51
|
+
* @satisfies REQ-373
|
|
52
|
+
*/
|
|
53
|
+
export function resolveContextFilePath(
|
|
54
|
+
projectBase: string,
|
|
55
|
+
config: UseReqConfig,
|
|
56
|
+
fileName: ContextFileName,
|
|
57
|
+
): string {
|
|
58
|
+
const normalizedDocsDir = normalizeRelativeDirContract(config["docs-dir"]) || DEFAULT_DOCS_DIR;
|
|
59
|
+
return path.join(projectBase, normalizedDocsDir, fileName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @brief Measures one context file into deterministic size facts.
|
|
64
|
+
* @details Probes filesystem existence and file kind, reads UTF-8 content, and reuses the shared `countFileMetrics` tokenizer for the `cl100k_base` token estimate. Missing, non-file, and unreadable targets return `MISSING_CONTEXT_FILE_SIZE` facts without throwing. Runtime is O(n) in file size. Side effects are limited to filesystem reads.
|
|
65
|
+
* @param[in] filePath {string} Absolute context-file path to measure.
|
|
66
|
+
* @return {ContextFileSizeFacts} Measured size facts for the target.
|
|
67
|
+
* @satisfies REQ-373, REQ-374
|
|
68
|
+
*/
|
|
69
|
+
export function measureContextFileSize(filePath: string): ContextFileSizeFacts {
|
|
70
|
+
try {
|
|
71
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
72
|
+
return MISSING_CONTEXT_FILE_SIZE;
|
|
73
|
+
}
|
|
74
|
+
const metrics = countFileMetrics(fs.readFileSync(filePath, "utf8"));
|
|
75
|
+
return { exists: true, chars: metrics.chars, tokens: metrics.tokens };
|
|
76
|
+
} catch {
|
|
77
|
+
return MISSING_CONTEXT_FILE_SIZE;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @brief Measures every canonical context file for one project base.
|
|
83
|
+
* @details Iterates `CONTEXT_FILE_NAMES` in documented order, resolves each configured `<base-path>/<docs-dir>` target through `resolveContextFilePath`, and returns the keyed facts record consumed by configuration menus and command summaries. Runtime is O(n) in aggregate context-file size. Side effects are limited to filesystem reads.
|
|
84
|
+
* @param[in] projectBase {string} Absolute project root path.
|
|
85
|
+
* @param[in] config {UseReqConfig} Effective project configuration supplying the docs directory.
|
|
86
|
+
* @return {Record<ContextFileName, ContextFileSizeFacts>} Measured size facts keyed by canonical file name.
|
|
87
|
+
* @satisfies REQ-373, REQ-374
|
|
88
|
+
*/
|
|
89
|
+
export function measureContextFileSizes(
|
|
90
|
+
projectBase: string,
|
|
91
|
+
config: UseReqConfig,
|
|
92
|
+
): Record<ContextFileName, ContextFileSizeFacts> {
|
|
93
|
+
const facts = {} as Record<ContextFileName, ContextFileSizeFacts>;
|
|
94
|
+
for (const fileName of CONTEXT_FILE_NAMES) {
|
|
95
|
+
facts[fileName] = measureContextFileSize(resolveContextFilePath(projectBase, config, fileName));
|
|
96
|
+
}
|
|
97
|
+
return facts;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @brief Formats one measured context-file size as a compact character and token estimate.
|
|
102
|
+
* @details Emits the deterministic `<chars>c/<tokens>t` shape reused by menu rows, the top-level summary, and the command invocation summary so every surface exposes identical size facts. Runtime is O(n) in rendered length. No external state is mutated.
|
|
103
|
+
* @param[in] facts {ContextFileSizeFacts} Measured context-file size facts.
|
|
104
|
+
* @return {string} Compact `<chars>c/<tokens>t` size string.
|
|
105
|
+
* @satisfies REQ-375, REQ-376, REQ-377
|
|
106
|
+
*/
|
|
107
|
+
export function formatContextFileSize(facts: ContextFileSizeFacts): string {
|
|
108
|
+
return `${facts.chars}c/${facts.tokens}t`;
|
|
109
|
+
}
|
|
@@ -63,6 +63,126 @@ export interface PromptCommandExecutionPlan {
|
|
|
63
63
|
worktreeDir?: string;
|
|
64
64
|
worktreePath?: string;
|
|
65
65
|
worktreeRootPath?: string;
|
|
66
|
+
originalModel?: PromptCommandModelSelection;
|
|
67
|
+
originalThinkingLevel?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @brief Describes one serializable model-selection fact captured at prompt-command preflight.
|
|
72
|
+
* @details Stores the active model provider and identifier as plain strings so the selection can travel inside the prompt execution plan and process-scoped persistence across session replacement without retaining live SDK model objects, and MUST NOT be persisted into pi-usereq configuration files per CTN-022. The interface is compile-time only and introduces no runtime cost.
|
|
73
|
+
* @satisfies REQ-366, CTN-022
|
|
74
|
+
*/
|
|
75
|
+
export interface PromptCommandModelSelection {
|
|
76
|
+
provider: string;
|
|
77
|
+
modelId: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @brief Describes the loosely-typed runtime surface required to re-apply one captured selection.
|
|
82
|
+
* @details Narrows the pi runtime to the model registry lookup, the active model plus thinking-level probes used by the duplicate-skip guard, and the session-scoped `setModel`/`setThinkingLevel` appliers that leave the configured default model untouched. All members stay optional so legacy hosts and offline replays degrade to a documented warning instead of aborting orchestration. The interface is compile-time only and introduces no runtime cost.
|
|
83
|
+
* @satisfies REQ-367, REQ-370, REQ-371
|
|
84
|
+
*/
|
|
85
|
+
export interface PromptCommandModelSelectionSurface {
|
|
86
|
+
modelRegistry?: { find?: (provider: unknown, modelId: unknown) => unknown };
|
|
87
|
+
model?: unknown;
|
|
88
|
+
thinkingLevel?: unknown;
|
|
89
|
+
setModel?: (model: unknown) => Promise<boolean>;
|
|
90
|
+
setThinkingLevel?: (level: unknown) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @brief Describes the re-apply request consumed by `reapplyPromptCommandSessionSelection`.
|
|
95
|
+
* @details Carries the captured selection plus thinking level, the runtime surface, and the optional stale-context-safe warning callback so callers keep notification ownership inside `src/index.ts` while the helper stays independent from extension UI types per DES-012. The interface is compile-time only and introduces no runtime cost.
|
|
96
|
+
*/
|
|
97
|
+
export interface PromptCommandModelSelectionReapplyOptions {
|
|
98
|
+
selection: PromptCommandModelSelection | undefined;
|
|
99
|
+
thinkingLevel: string | undefined;
|
|
100
|
+
surface: PromptCommandModelSelectionSurface | undefined;
|
|
101
|
+
notifyWarning?: (message: string) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @brief Describes the deterministic outcome facts of one guarded selection re-application.
|
|
106
|
+
* @details Distinguishes not-attempted skips (missing selection, missing surface, or post-switch state already matching the captured selection) from attempted applications, and carries the normalized warning message emitted when resolution, authentication, or the runtime applier fails. The interface is compile-time only and introduces no runtime cost.
|
|
107
|
+
* @satisfies REQ-370, REQ-372
|
|
108
|
+
*/
|
|
109
|
+
export interface PromptCommandModelSelectionReapplyResult {
|
|
110
|
+
attempted: boolean;
|
|
111
|
+
applied: boolean;
|
|
112
|
+
skipped: boolean;
|
|
113
|
+
warningMessage?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @brief Re-applies one captured model selection onto the active post-switch session.
|
|
118
|
+
* @details Resolves the captured provider plus identifier through the surface model registry, skips the whole operation when the selection or surface is unavailable and when the active model and thinking level already match the captured selection so no duplicate `model_change` session entry is emitted, otherwise awaits the session-scoped `setModel` applier, then re-applies the captured thinking level when the active level differs. Every resolution, authentication, and applier failure degrades into one warning notification through the supplied callback without throwing, so prompt orchestration and worktree closure continue unchanged. Runtime is O(1) plus one awaited model mutation. No external state is mutated beyond the applied session selection.
|
|
119
|
+
* @param[in] options {PromptCommandModelSelectionReapplyOptions} Captured selection, runtime surface, and warning callback.
|
|
120
|
+
* @return {Promise<PromptCommandModelSelectionReapplyResult>} Attempted, applied, skipped, and warning facts for the re-application.
|
|
121
|
+
* @satisfies REQ-366, REQ-367, REQ-368, REQ-369, REQ-370, REQ-371, REQ-372, DES-021
|
|
122
|
+
*/
|
|
123
|
+
export async function reapplyPromptCommandSessionSelection(
|
|
124
|
+
options: PromptCommandModelSelectionReapplyOptions,
|
|
125
|
+
): Promise<PromptCommandModelSelectionReapplyResult> {
|
|
126
|
+
const selection = options.selection;
|
|
127
|
+
const surface = options.surface;
|
|
128
|
+
if (!selection || !surface) {
|
|
129
|
+
return { attempted: false, applied: false, skipped: true };
|
|
130
|
+
}
|
|
131
|
+
const notifyWarning = options.notifyWarning;
|
|
132
|
+
const registryFind = surface.modelRegistry?.find;
|
|
133
|
+
const resolvedCandidate = typeof registryFind === "function"
|
|
134
|
+
? registryFind.call(surface.modelRegistry, selection.provider, selection.modelId)
|
|
135
|
+
: undefined;
|
|
136
|
+
const resolved = resolvedCandidate as { provider?: unknown; id?: unknown } | null | undefined;
|
|
137
|
+
if (!resolved || typeof resolved.provider !== "string" || typeof resolved.id !== "string") {
|
|
138
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: model is not available in the active model registry.`;
|
|
139
|
+
notifyWarning?.(message);
|
|
140
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
141
|
+
}
|
|
142
|
+
const currentModel = surface.model as { provider?: unknown; id?: unknown } | null | undefined;
|
|
143
|
+
const modelMatches = currentModel !== null
|
|
144
|
+
&& currentModel !== undefined
|
|
145
|
+
&& currentModel.provider === selection.provider
|
|
146
|
+
&& currentModel.id === selection.modelId;
|
|
147
|
+
const levelMatches = typeof options.thinkingLevel !== "string"
|
|
148
|
+
|| surface.thinkingLevel === options.thinkingLevel;
|
|
149
|
+
if (modelMatches && levelMatches) {
|
|
150
|
+
return { attempted: false, applied: false, skipped: true };
|
|
151
|
+
}
|
|
152
|
+
if (!modelMatches) {
|
|
153
|
+
const setModelFn = surface.setModel;
|
|
154
|
+
if (typeof setModelFn !== "function") {
|
|
155
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: the runtime does not expose setModel.`;
|
|
156
|
+
notifyWarning?.(message);
|
|
157
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
158
|
+
}
|
|
159
|
+
let setModelResult: unknown;
|
|
160
|
+
try {
|
|
161
|
+
setModelResult = await setModelFn.call(surface, resolved);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: ${error instanceof Error ? error.message : String(error)}.`;
|
|
164
|
+
notifyWarning?.(message);
|
|
165
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
166
|
+
}
|
|
167
|
+
if (setModelResult === false) {
|
|
168
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: no API key configured for ${selection.provider}.`;
|
|
169
|
+
notifyWarning?.(message);
|
|
170
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const setThinkingLevelFn = surface.setThinkingLevel;
|
|
174
|
+
if (
|
|
175
|
+
typeof options.thinkingLevel === "string"
|
|
176
|
+
&& surface.thinkingLevel !== options.thinkingLevel
|
|
177
|
+
&& typeof setThinkingLevelFn === "function"
|
|
178
|
+
) {
|
|
179
|
+
try {
|
|
180
|
+
setThinkingLevelFn.call(surface, options.thinkingLevel);
|
|
181
|
+
} catch {
|
|
182
|
+
// REQ-372: thinking-level failures are advisory-only and MUST NOT abort orchestration.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { attempted: true, applied: true, skipped: false };
|
|
66
186
|
}
|
|
67
187
|
|
|
68
188
|
/**
|
|
@@ -109,11 +229,11 @@ interface PromptCommandSessionSwitchOptions {
|
|
|
109
229
|
|
|
110
230
|
/**
|
|
111
231
|
* @brief Describes the minimal session-bound surface available after session replacement.
|
|
112
|
-
* @details Extends the shared prompt-command context with `sendUserMessage(...)` so prompt dispatch can target the replacement session without reusing stale pre-switch runtime objects. The interface is compile-time only and introduces no runtime cost.
|
|
232
|
+
* @details Extends the shared prompt-command context with a string-only `sendUserMessage(...)` probe so prompt dispatch can target the replacement session without reusing stale pre-switch runtime objects. The string content parameter mirrors the prompt-delivery contract consumed by `deliverPromptCommand(...)` while remaining structurally compatible with the pi 1.0.0 replacement-session delivery signatures. The interface is compile-time only and introduces no runtime cost.
|
|
113
233
|
*/
|
|
114
234
|
interface PromptCommandActiveContext extends PromptCommandSessionContext {
|
|
115
235
|
sendUserMessage?: (
|
|
116
|
-
content: string
|
|
236
|
+
content: string,
|
|
117
237
|
options?: PromptCommandSessionMessageOptions,
|
|
118
238
|
) => Promise<void> | void;
|
|
119
239
|
}
|
|
@@ -1584,9 +1704,11 @@ export function validatePromptRequiredDocs(
|
|
|
1584
1704
|
* @param[in] currentSessionDir {string | undefined} Active session directory reused when the execution session is forked.
|
|
1585
1705
|
* @param[in] currentSessionBranch {PromptCommandSessionEntry[] | undefined} Active in-memory session branch copied when the origin session file is not flushed yet.
|
|
1586
1706
|
* @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
|
|
1707
|
+
* @param[in] originalModel {PromptCommandModelSelection | undefined} Active model provider plus identifier captured by the command handler for later re-application.
|
|
1708
|
+
* @param[in] originalThinkingLevel {string | undefined} Active thinking level captured by the command handler for later re-application.
|
|
1587
1709
|
* @return {PromptCommandExecutionPlan} Prepared execution plan.
|
|
1588
1710
|
* @throws {ReqError} Throws when repository validation, required-doc validation, worktree creation, or session preparation fails.
|
|
1589
|
-
* @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271
|
|
1711
|
+
* @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271, REQ-366
|
|
1590
1712
|
*/
|
|
1591
1713
|
export function preparePromptCommandExecution(
|
|
1592
1714
|
promptName: PromptCommandName,
|
|
@@ -1597,6 +1719,8 @@ export function preparePromptCommandExecution(
|
|
|
1597
1719
|
currentSessionDir: string | undefined,
|
|
1598
1720
|
currentSessionBranch: PromptCommandSessionEntry[] | undefined,
|
|
1599
1721
|
debugOptions?: PromptCommandDebugOptions,
|
|
1722
|
+
originalModel?: PromptCommandModelSelection,
|
|
1723
|
+
originalThinkingLevel?: string,
|
|
1600
1724
|
): PromptCommandExecutionPlan {
|
|
1601
1725
|
const gitPath = validatePromptGitState(projectBase, config);
|
|
1602
1726
|
const reuseCurrentSessionFile = isUsablePromptSessionFile(currentSessionFile, projectBase);
|
|
@@ -1624,6 +1748,8 @@ export function preparePromptCommandExecution(
|
|
|
1624
1748
|
baseDir: staticWorktreePaths.baseDir,
|
|
1625
1749
|
originalSessionFile,
|
|
1626
1750
|
executionSessionFile: originalSessionFile,
|
|
1751
|
+
originalModel,
|
|
1752
|
+
originalThinkingLevel,
|
|
1627
1753
|
};
|
|
1628
1754
|
}
|
|
1629
1755
|
const worktreeDir = buildPromptWorktreeName(gitPath, config);
|
|
@@ -1663,6 +1789,8 @@ export function preparePromptCommandExecution(
|
|
|
1663
1789
|
worktreeDir: worktreePaths.worktreeDir,
|
|
1664
1790
|
worktreePath: worktreePaths.worktreePath,
|
|
1665
1791
|
worktreeRootPath: worktreePaths.worktreeRootPath,
|
|
1792
|
+
originalModel,
|
|
1793
|
+
originalThinkingLevel,
|
|
1666
1794
|
};
|
|
1667
1795
|
}
|
|
1668
1796
|
|
package/src/core/prompts.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
comparePiUsereqStartupToolNames,
|
|
18
18
|
type PiUsereqStartupToolName,
|
|
19
19
|
} from "./pi-usereq-tools.js";
|
|
20
|
+
import { formatContextFileSize, type ContextFileSizeFacts } from "./context-file-size.js";
|
|
20
21
|
import { formatRuntimePathForDisplay, normalizeRelativeDirContract } from "./path-context.js";
|
|
21
22
|
import type {
|
|
22
23
|
PromptCommandExecutionPlan,
|
|
@@ -374,21 +375,27 @@ export const PROMPT_COMMAND_SUMMARY_CUSTOM_TYPE = "pi-usereq-prompt-command";
|
|
|
374
375
|
|
|
375
376
|
/**
|
|
376
377
|
* @brief Builds the on-screen command invocation summary for one bundled prompt-backed `req-<prompt>` command.
|
|
377
|
-
* @details Renders the command name without the `req-` prefix in uppercase, the user request arguments, and the active configuration fields (`docs-dir`, `src-dir`, `tests-dir`, enabled context files, `AUTO_GIT_COMMIT`, effective `GIT_WORKTREE_ENABLED`, `GIT_WORKTREE_PREFIX`, enabled static-check languages, and `enabled-tools`) so the TUI shows only a compact summary while the full rendered prompt is delivered hidden to the LLM agent. Static-check languages are emitted in canonical `DEFAULT_STATIC_CHECK_LANGUAGES` order; enabled tools are emitted in documented menu order via `comparePiUsereqStartupToolNames`. The `context files`, `static code checks`, and `enabled tools` fields render the literal `none` placeholder whenever their respective enabled-item list is empty so the summary never shows a blank value. Sections are emitted in the order `Command:`, `Configuration:` with its bullet list, then `User's Request:`, each separated by one blank line. Runtime is O(l + t log t) where l is language count and t is enabled-tool count. No external state is mutated.
|
|
378
|
+
* @details Renders the command name without the `req-` prefix in uppercase, the user request arguments, and the active configuration fields (`docs-dir`, `src-dir`, `tests-dir`, enabled context files, `AUTO_GIT_COMMIT`, effective `GIT_WORKTREE_ENABLED`, `GIT_WORKTREE_PREFIX`, enabled static-check languages, and `enabled-tools`) so the TUI shows only a compact summary while the full rendered prompt is delivered hidden to the LLM agent. When measured context-file size facts are supplied, every enabled existing context file in the `context files` field is rendered as `name(<chars>c/<tokens>t)`; without facts, or for enabled missing files, the plain `name` form is rendered. Static-check languages are emitted in canonical `DEFAULT_STATIC_CHECK_LANGUAGES` order; enabled tools are emitted in documented menu order via `comparePiUsereqStartupToolNames`. The `context files`, `static code checks`, and `enabled tools` fields render the literal `none` placeholder whenever their respective enabled-item list is empty so the summary never shows a blank value. Sections are emitted in the order `Command:`, `Configuration:` with its bullet list, then `User's Request:`, each separated by one blank line. Runtime is O(l + t log t) where l is language count and t is enabled-tool count. No external state is mutated.
|
|
378
379
|
* @param[in] promptName {string} Bundled prompt name without the `req-` prefix.
|
|
379
380
|
* @param[in] args {string} User request arguments passed to the slash command.
|
|
380
381
|
* @param[in] config {UseReqConfig} Effective project configuration supplying directory, git, static-check, and tool fields.
|
|
382
|
+
* @param[in] contextFileSizes {Record<string, ContextFileSizeFacts> | undefined} Optional measured context-file size facts keyed by canonical file name, consumed for `context files` size suffixes.
|
|
381
383
|
* @return {string} Multi-line command invocation summary text.
|
|
382
|
-
* @satisfies REQ-335, REQ-336, REQ-337, REQ-338, REQ-353
|
|
384
|
+
* @satisfies REQ-335, REQ-336, REQ-337, REQ-338, REQ-353, REQ-377
|
|
383
385
|
*/
|
|
384
386
|
export function renderPromptCommandSummary(
|
|
385
387
|
promptName: string,
|
|
386
388
|
args: string,
|
|
387
389
|
config: UseReqConfig,
|
|
390
|
+
contextFileSizes?: Record<string, ContextFileSizeFacts>,
|
|
388
391
|
): string {
|
|
389
392
|
const contextFiles = CONTEXT_FILE_DESCRIPTORS
|
|
390
393
|
.filter((descriptor) => config[descriptor.flagKey])
|
|
391
|
-
.map((descriptor) =>
|
|
394
|
+
.map((descriptor) => {
|
|
395
|
+
const baseName = descriptor.fileName.replace(/\.md$/, "").toLowerCase();
|
|
396
|
+
const facts = contextFileSizes?.[descriptor.fileName];
|
|
397
|
+
return facts?.exists ? `${baseName}(${formatContextFileSize(facts)})` : baseName;
|
|
398
|
+
})
|
|
392
399
|
.join(", ") || "none";
|
|
393
400
|
const enabledLanguages = DEFAULT_STATIC_CHECK_LANGUAGES
|
|
394
401
|
.filter((language) => config["static-check"][language]?.enabled === "enable")
|
package/src/core/static-check.ts
CHANGED
|
@@ -404,7 +404,7 @@ export class StaticCheckCommand extends StaticCheckBase {
|
|
|
404
404
|
throw new ReqError(`Error: external command '${cmd}' not found on PATH.`, 1);
|
|
405
405
|
}
|
|
406
406
|
super(inputs, extraArgs, failOnly);
|
|
407
|
-
this.cmd =
|
|
407
|
+
this.cmd = resolvedCmd;
|
|
408
408
|
this.label = `Command[${cmd}]`;
|
|
409
409
|
}
|
|
410
410
|
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,12 @@ import {
|
|
|
83
83
|
normalizeEnabledPiUsereqTools,
|
|
84
84
|
type PiUsereqStartupToolName,
|
|
85
85
|
} from "./core/pi-usereq-tools.js";
|
|
86
|
+
import {
|
|
87
|
+
formatContextFileSize,
|
|
88
|
+
measureContextFileSizes,
|
|
89
|
+
type ContextFileName,
|
|
90
|
+
type ContextFileSizeFacts,
|
|
91
|
+
} from "./core/context-file-size.js";
|
|
86
92
|
import {
|
|
87
93
|
PROMPT_COMMAND_SUMMARY_CUSTOM_TYPE,
|
|
88
94
|
renderPrompt,
|
|
@@ -95,8 +101,11 @@ import {
|
|
|
95
101
|
finalizePromptCommandExecution,
|
|
96
102
|
getPromptCommandErrorContext,
|
|
97
103
|
preparePromptCommandExecution,
|
|
104
|
+
reapplyPromptCommandSessionSelection,
|
|
98
105
|
restorePromptCommandExecution,
|
|
99
106
|
type PromptCommandExecutionPlan,
|
|
107
|
+
type PromptCommandModelSelection,
|
|
108
|
+
type PromptCommandModelSelectionSurface,
|
|
100
109
|
} from "./core/prompt-command-runtime.js";
|
|
101
110
|
import {
|
|
102
111
|
REQ_REFERENCES_COMMAND_DESCRIPTION,
|
|
@@ -1301,13 +1310,15 @@ function isPiAgentSettledEventSupported(
|
|
|
1301
1310
|
/**
|
|
1302
1311
|
* @brief Finalizes a matched successful worktree-backed prompt at the current lifecycle point.
|
|
1303
1312
|
* @details Executes the deferred stash-assisted merge, transcript preservation, base-path restore, and worktree plus branch deletion through `finalizePromptCommandExecution(...)`, surfaces `error` or warning-only notifications, clears the pending finalization outcome plus prompt state, and transitions workflow state through `merging` to `idle`. Reused by the `agent_settled` handler on 0.80.4+ hosts and directly by the `agent_end` fallback on legacy hosts that never emit `agent_settled`. Runtime is dominated by git finalization. Side effects include branch merges, worktree deletion, notifications, and workflow-state transitions.
|
|
1313
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance supplying the model re-apply appliers.
|
|
1304
1314
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller whose pending and active prompt state is cleared.
|
|
1305
1315
|
* @param[in] promptRequest {PiUsereqPromptRequest} Matched successful worktree-backed prompt execution plan.
|
|
1306
1316
|
* @param[in] ctx {ExtensionContext} Active extension context used for finalization and notifications.
|
|
1307
1317
|
* @return {Promise<void>} Promise resolved when finalization and state transitions complete.
|
|
1308
|
-
* @satisfies REQ-208, REQ-228, REQ-229, REQ-230, REQ-282, REQ-291, REQ-292, REQ-354, REQ-355
|
|
1318
|
+
* @satisfies REQ-208, REQ-228, REQ-229, REQ-230, REQ-282, REQ-291, REQ-292, REQ-354, REQ-355, REQ-368, REQ-370, REQ-371, REQ-372
|
|
1309
1319
|
*/
|
|
1310
1320
|
async function finalizeMatchedPromptSuccess(
|
|
1321
|
+
pi: ExtensionAPI,
|
|
1311
1322
|
statusController: PiUsereqStatusController,
|
|
1312
1323
|
promptRequest: PiUsereqPromptRequest,
|
|
1313
1324
|
ctx: ExtensionContext,
|
|
@@ -1333,6 +1344,14 @@ async function finalizeMatchedPromptSuccess(
|
|
|
1333
1344
|
: undefined,
|
|
1334
1345
|
);
|
|
1335
1346
|
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1347
|
+
if (finalization.cleanupSucceeded && !finalization.errorMessage) {
|
|
1348
|
+
await reapplyCapturedPromptModelSelection(
|
|
1349
|
+
promptRequest.originalModel,
|
|
1350
|
+
promptRequest.originalThinkingLevel,
|
|
1351
|
+
pi,
|
|
1352
|
+
promptContext,
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1336
1355
|
} catch (error) {
|
|
1337
1356
|
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1338
1357
|
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1346,6 +1365,12 @@ async function finalizeMatchedPromptSuccess(
|
|
|
1346
1365
|
: undefined,
|
|
1347
1366
|
) ?? promptContext) as typeof ctx;
|
|
1348
1367
|
cleanupSucceeded = true;
|
|
1368
|
+
await reapplyCapturedPromptModelSelection(
|
|
1369
|
+
promptRequest.originalModel,
|
|
1370
|
+
promptRequest.originalThinkingLevel,
|
|
1371
|
+
pi,
|
|
1372
|
+
promptContext,
|
|
1373
|
+
);
|
|
1349
1374
|
} catch (restoreError) {
|
|
1350
1375
|
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1351
1376
|
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
@@ -1451,6 +1476,12 @@ async function handleExtensionStatusEvent(
|
|
|
1451
1476
|
const requestForActivation = activePromptRequest ?? pendingPromptRequest;
|
|
1452
1477
|
if (requestForActivation !== undefined) {
|
|
1453
1478
|
await activatePromptCommandExecution(requestForActivation, ctx);
|
|
1479
|
+
await reapplyCapturedPromptModelSelection(
|
|
1480
|
+
requestForActivation.originalModel,
|
|
1481
|
+
requestForActivation.originalThinkingLevel,
|
|
1482
|
+
pi,
|
|
1483
|
+
ctx,
|
|
1484
|
+
);
|
|
1454
1485
|
}
|
|
1455
1486
|
}
|
|
1456
1487
|
const notifyRequest: PiNotifyEventRequest | undefined = hookName === "agent_end"
|
|
@@ -1555,6 +1586,7 @@ async function handleExtensionStatusEvent(
|
|
|
1555
1586
|
// directly at `agent_end` to avoid parking in `merging` forever.
|
|
1556
1587
|
statusController.state.pendingFinalizationOutcome = outcome;
|
|
1557
1588
|
await finalizeMatchedPromptSuccess(
|
|
1589
|
+
pi,
|
|
1558
1590
|
statusController,
|
|
1559
1591
|
activePromptRequest,
|
|
1560
1592
|
promptContext,
|
|
@@ -1635,7 +1667,7 @@ async function handleExtensionStatusEvent(
|
|
|
1635
1667
|
&& settledPromptRequest.worktreeDir !== undefined
|
|
1636
1668
|
&& pendingOutcome === "completed"
|
|
1637
1669
|
) {
|
|
1638
|
-
await finalizeMatchedPromptSuccess(statusController, settledPromptRequest, ctx);
|
|
1670
|
+
await finalizeMatchedPromptSuccess(pi, statusController, settledPromptRequest, ctx);
|
|
1639
1671
|
}
|
|
1640
1672
|
}
|
|
1641
1673
|
if (hookName === "session_shutdown") {
|
|
@@ -3041,6 +3073,139 @@ function registerPiNotifyShortcut(
|
|
|
3041
3073
|
});
|
|
3042
3074
|
}
|
|
3043
3075
|
|
|
3076
|
+
/**
|
|
3077
|
+
* @brief Describes the loosely-typed pi extension API surface required by model re-application.
|
|
3078
|
+
* @details Narrows the runtime API to the model appliers and thinking-level probe accessed defensively so legacy hosts and offline harnesses without `setModel`, `setThinkingLevel`, or `getThinkingLevel` degrade to a documented warning instead of a runtime exception. The alias is compile-time only and introduces no runtime cost.
|
|
3079
|
+
*/
|
|
3080
|
+
type PromptModelSelectionApiSurface = {
|
|
3081
|
+
setModel?: (model: unknown) => unknown;
|
|
3082
|
+
setThinkingLevel?: (level: unknown) => unknown;
|
|
3083
|
+
getThinkingLevel?: () => unknown;
|
|
3084
|
+
};
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* @brief Describes the loosely-typed context surface required by model re-application.
|
|
3088
|
+
* @details Narrows extension contexts to the active model, thinking level, and model registry probe used to capture the pre-switch selection and to evaluate the duplicate-skip guard after session replacement. The alias is compile-time only and introduces no runtime cost.
|
|
3089
|
+
*/
|
|
3090
|
+
type PromptModelSelectionContextSurface = {
|
|
3091
|
+
model?: unknown;
|
|
3092
|
+
thinkingLevel?: unknown;
|
|
3093
|
+
modelRegistry?: { find?: (provider: unknown, modelId: unknown) => unknown };
|
|
3094
|
+
};
|
|
3095
|
+
|
|
3096
|
+
/**
|
|
3097
|
+
* @brief Captures the active model provider and identifier from one loosely-typed context model.
|
|
3098
|
+
* @details Validates that the runtime model object exposes non-empty string provider and identifier fields and emits the serializable selection fact stored inside the prompt execution plan, returning undefined when the runtime exposes no usable model so preflight capture never throws. Runtime is O(1). No external state is mutated.
|
|
3099
|
+
* @param[in] model {unknown} Active runtime model object from `ctx.model`.
|
|
3100
|
+
* @return {PromptCommandModelSelection | undefined} Serializable provider plus identifier fact, or undefined when unavailable.
|
|
3101
|
+
* @satisfies REQ-366
|
|
3102
|
+
*/
|
|
3103
|
+
function resolveCurrentModelSelection(
|
|
3104
|
+
model: unknown,
|
|
3105
|
+
): PromptCommandModelSelection | undefined {
|
|
3106
|
+
const candidate = model as { provider?: unknown; id?: unknown } | null | undefined;
|
|
3107
|
+
if (candidate === null || candidate === undefined) {
|
|
3108
|
+
return undefined;
|
|
3109
|
+
}
|
|
3110
|
+
if (
|
|
3111
|
+
typeof candidate.provider !== "string"
|
|
3112
|
+
|| typeof candidate.id !== "string"
|
|
3113
|
+
|| candidate.provider === ""
|
|
3114
|
+
|| candidate.id === ""
|
|
3115
|
+
) {
|
|
3116
|
+
return undefined;
|
|
3117
|
+
}
|
|
3118
|
+
return { provider: candidate.provider, modelId: candidate.id };
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
/**
|
|
3122
|
+
* @brief Captures the active thinking level from the runtime API or fallback context.
|
|
3123
|
+
* @details Prefers `pi.getThinkingLevel()` when the host exposes it and falls back to the context `thinkingLevel` probe, returning undefined for legacy hosts or offline harnesses that provide neither so capture stays advisory. Runtime is O(1). No external state is mutated.
|
|
3124
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
3125
|
+
* @param[in] ctx {unknown} Active extension or command context used as fallback probe.
|
|
3126
|
+
* @return {string | undefined} Active thinking level, or undefined when unavailable.
|
|
3127
|
+
* @satisfies REQ-366, REQ-371
|
|
3128
|
+
*/
|
|
3129
|
+
function resolveCurrentThinkingLevel(pi: ExtensionAPI, ctx: unknown): string | undefined {
|
|
3130
|
+
const api = pi as unknown as PromptModelSelectionApiSurface;
|
|
3131
|
+
if (typeof api.getThinkingLevel === "function") {
|
|
3132
|
+
const level = api.getThinkingLevel();
|
|
3133
|
+
if (typeof level === "string" && level !== "") {
|
|
3134
|
+
return level;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
const context = ctx as PromptModelSelectionContextSurface | null | undefined;
|
|
3138
|
+
if (
|
|
3139
|
+
context !== null
|
|
3140
|
+
&& context !== undefined
|
|
3141
|
+
&& typeof context.thinkingLevel === "string"
|
|
3142
|
+
&& context.thinkingLevel !== ""
|
|
3143
|
+
) {
|
|
3144
|
+
return context.thinkingLevel;
|
|
3145
|
+
}
|
|
3146
|
+
return undefined;
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
/**
|
|
3150
|
+
* @brief Builds the loosely-typed model-selection surface from the runtime API plus context.
|
|
3151
|
+
* @details Wraps `setModel` and `setThinkingLevel` with defensive function probes so the surface only exposes appliers the running host actually implements, and copies the context model, thinking level, and model registry used by the guarded re-apply comparison. Returns undefined when no context is available so re-application degrades to a not-attempted skip. Runtime is O(1). No external state is mutated.
|
|
3152
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance supplying the session-scoped model appliers.
|
|
3153
|
+
* @param[in] ctx {unknown} Active extension or command context supplying model probes.
|
|
3154
|
+
* @return {PromptCommandModelSelectionSurface | undefined} Re-apply surface, or undefined when no context exists.
|
|
3155
|
+
* @satisfies REQ-367, REQ-370, REQ-371
|
|
3156
|
+
*/
|
|
3157
|
+
function resolvePromptCommandModelSurface(
|
|
3158
|
+
pi: ExtensionAPI,
|
|
3159
|
+
ctx: unknown,
|
|
3160
|
+
): PromptCommandModelSelectionSurface | undefined {
|
|
3161
|
+
const context = ctx as PromptModelSelectionContextSurface | null | undefined;
|
|
3162
|
+
if (context === null || context === undefined) {
|
|
3163
|
+
return undefined;
|
|
3164
|
+
}
|
|
3165
|
+
const api = pi as unknown as PromptModelSelectionApiSurface;
|
|
3166
|
+
const setModelFn = api.setModel;
|
|
3167
|
+
const setThinkingLevelFn = api.setThinkingLevel;
|
|
3168
|
+
return {
|
|
3169
|
+
modelRegistry: context.modelRegistry,
|
|
3170
|
+
model: context.model,
|
|
3171
|
+
thinkingLevel: context.thinkingLevel,
|
|
3172
|
+
setModel: typeof setModelFn === "function"
|
|
3173
|
+
? (model: unknown) => setModelFn.call(pi, model) as Promise<boolean>
|
|
3174
|
+
: undefined,
|
|
3175
|
+
setThinkingLevel: typeof setThinkingLevelFn === "function"
|
|
3176
|
+
? (level: unknown) => {
|
|
3177
|
+
setThinkingLevelFn.call(pi, level);
|
|
3178
|
+
}
|
|
3179
|
+
: undefined,
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
/**
|
|
3184
|
+
* @brief Re-applies one captured model selection onto the active post-switch session.
|
|
3185
|
+
* @details Builds the guarded re-apply surface from the runtime API plus context, forwards the captured selection plus thinking level into `reapplyPromptCommandSessionSelection(...)`, and routes every warning through `notifyContextSafely(...)` so stale replacement contexts after session replacement never abort orchestration. Runtime is O(1) plus one awaited model mutation. Side effects include session-scoped model and thinking-level mutation plus stale-safe warning notifications.
|
|
3186
|
+
* @param[in] selection {PromptCommandModelSelection | undefined} Captured model provider plus identifier.
|
|
3187
|
+
* @param[in] thinkingLevel {string | undefined} Captured thinking level.
|
|
3188
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
3189
|
+
* @param[in] ctx {unknown} Active extension or command context used for probes and warning delivery.
|
|
3190
|
+
* @return {Promise<void>} Promise resolved once the guarded re-application completed.
|
|
3191
|
+
* @satisfies REQ-367, REQ-368, REQ-369, REQ-370, REQ-371, REQ-372
|
|
3192
|
+
*/
|
|
3193
|
+
async function reapplyCapturedPromptModelSelection(
|
|
3194
|
+
selection: PromptCommandModelSelection | undefined,
|
|
3195
|
+
thinkingLevel: string | undefined,
|
|
3196
|
+
pi: ExtensionAPI,
|
|
3197
|
+
ctx: unknown,
|
|
3198
|
+
): Promise<void> {
|
|
3199
|
+
await reapplyPromptCommandSessionSelection({
|
|
3200
|
+
selection,
|
|
3201
|
+
thinkingLevel,
|
|
3202
|
+
surface: resolvePromptCommandModelSurface(pi, ctx),
|
|
3203
|
+
notifyWarning: (message) => {
|
|
3204
|
+
notifyContextSafely(ctx as ExtensionContext, message, "error");
|
|
3205
|
+
},
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3044
3209
|
/**
|
|
3045
3210
|
* @brief Resolves the prompt execution plan targeted by `req-reset` recovery.
|
|
3046
3211
|
* @details Prefers the current in-memory active request, then the current in-memory pending request, then the process-scoped persisted prompt runtime state so the dedicated reset command can recover from same-host unclean prompt termination after session replacement. Runtime is O(1). No external state is mutated.
|
|
@@ -3091,6 +3256,10 @@ function registerReqResetCommand(
|
|
|
3091
3256
|
description: REQ_RESET_COMMAND_DESCRIPTION,
|
|
3092
3257
|
handler: async (_args, ctx) => {
|
|
3093
3258
|
const promptRequest = resolveReqResetPromptRequest(statusController);
|
|
3259
|
+
const resetModelSelection = promptRequest?.originalModel
|
|
3260
|
+
?? resolveCurrentModelSelection((ctx as unknown as PromptModelSelectionContextSurface).model);
|
|
3261
|
+
const resetThinkingLevel = promptRequest?.originalThinkingLevel
|
|
3262
|
+
?? resolveCurrentThinkingLevel(pi, ctx);
|
|
3094
3263
|
const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
|
|
3095
3264
|
syncContextCwdMirror(ctx, commandCwd);
|
|
3096
3265
|
const projectBase = path.resolve(promptRequest?.basePath ?? getProjectBase(commandCwd));
|
|
@@ -3109,6 +3278,12 @@ function registerReqResetCommand(
|
|
|
3109
3278
|
executionPlan = prepareReqResetCommandExecution(projectBase, config, promptRequest);
|
|
3110
3279
|
executionResult = await executeReqResetCommandExecution(executionPlan, ctx);
|
|
3111
3280
|
resetContext = (executionResult.activeContext ?? resetContext) as typeof ctx;
|
|
3281
|
+
await reapplyCapturedPromptModelSelection(
|
|
3282
|
+
resetModelSelection,
|
|
3283
|
+
resetThinkingLevel,
|
|
3284
|
+
pi,
|
|
3285
|
+
resetContext,
|
|
3286
|
+
);
|
|
3112
3287
|
} catch (error) {
|
|
3113
3288
|
const message = error instanceof Error ? error.message : String(error);
|
|
3114
3289
|
setPiUsereqWorkflowState(statusController, "error", resetContext);
|
|
@@ -3197,7 +3372,7 @@ function registerReqReferencesCommand(
|
|
|
3197
3372
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
3198
3373
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
3199
3374
|
* @return {void} No return value.
|
|
3200
|
-
* @satisfies REQ-004, REQ-067, REQ-068, REQ-169, REQ-200, REQ-201, REQ-202, REQ-203, REQ-206, REQ-207, REQ-219, REQ-220, REQ-221, REQ-224, REQ-225, REQ-226, REQ-227, REQ-245, REQ-246, REQ-247, REQ-277, REQ-281
|
|
3375
|
+
* @satisfies REQ-004, REQ-067, REQ-068, REQ-169, REQ-200, REQ-201, REQ-202, REQ-203, REQ-206, REQ-207, REQ-219, REQ-220, REQ-221, REQ-224, REQ-225, REQ-226, REQ-227, REQ-245, REQ-246, REQ-247, REQ-277, REQ-281, REQ-377
|
|
3201
3376
|
*/
|
|
3202
3377
|
function registerPromptCommands(
|
|
3203
3378
|
pi: ExtensionAPI,
|
|
@@ -3228,6 +3403,10 @@ function registerPromptCommands(
|
|
|
3228
3403
|
ensureBundledResourcesAccessible();
|
|
3229
3404
|
let executionPlan: PromptCommandExecutionPlan | undefined;
|
|
3230
3405
|
let promptContext = ctx;
|
|
3406
|
+
const originalModel = resolveCurrentModelSelection(
|
|
3407
|
+
(ctx as unknown as PromptModelSelectionContextSurface).model,
|
|
3408
|
+
);
|
|
3409
|
+
const originalThinkingLevel = resolveCurrentThinkingLevel(pi, ctx);
|
|
3231
3410
|
try {
|
|
3232
3411
|
executionPlan = preparePromptCommandExecution(
|
|
3233
3412
|
promptName,
|
|
@@ -3241,6 +3420,8 @@ function registerPromptCommands(
|
|
|
3241
3420
|
config,
|
|
3242
3421
|
workflowState: statusController.state.workflowState,
|
|
3243
3422
|
},
|
|
3423
|
+
originalModel,
|
|
3424
|
+
originalThinkingLevel,
|
|
3244
3425
|
);
|
|
3245
3426
|
const content = renderPrompt(
|
|
3246
3427
|
promptName,
|
|
@@ -3256,6 +3437,12 @@ function registerPromptCommands(
|
|
|
3256
3437
|
activePromptRequest: statusController.state.activePromptRequest,
|
|
3257
3438
|
});
|
|
3258
3439
|
promptContext = (await activatePromptCommandExecution(executionPlan, ctx) ?? ctx) as typeof ctx;
|
|
3440
|
+
await reapplyCapturedPromptModelSelection(
|
|
3441
|
+
executionPlan.originalModel,
|
|
3442
|
+
executionPlan.originalThinkingLevel,
|
|
3443
|
+
pi,
|
|
3444
|
+
promptContext,
|
|
3445
|
+
);
|
|
3259
3446
|
logPromptWorkflowEvent(
|
|
3260
3447
|
projectBase,
|
|
3261
3448
|
config,
|
|
@@ -3277,6 +3464,7 @@ function registerPromptCommands(
|
|
|
3277
3464
|
promptName,
|
|
3278
3465
|
args,
|
|
3279
3466
|
config,
|
|
3467
|
+
measureContextFileSizes(projectBase, config),
|
|
3280
3468
|
);
|
|
3281
3469
|
const promptDelivery = deliverPromptCommand(pi, content, commandSummary, promptContext);
|
|
3282
3470
|
transitionPromptWorkflowState(
|
|
@@ -4352,46 +4540,68 @@ async function configureStaticCheckMenu(
|
|
|
4352
4540
|
}
|
|
4353
4541
|
|
|
4354
4542
|
/**
|
|
4355
|
-
* @brief Summarizes the `Context Files` flag state for the top-level menu value column.
|
|
4356
|
-
* @details Renders the three context-file flags as compact `name:on|off` segments in the documented order so the top-level row
|
|
4543
|
+
* @brief Summarizes the `Context Files` flag state with measured sizes for the top-level menu value column.
|
|
4544
|
+
* @details Renders the three context-file flags as compact `name:on|off` segments in the documented order, appending `(<chars>c/<tokens>t)` measured size facts to every enabled segment so the top-level row exposes a runtime estimate of the injected context payload while disabled segments stay plain `name:off`. Runtime is O(1) in segment count. No external state is mutated.
|
|
4357
4545
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
4546
|
+
* @param[in] sizes {Record<ContextFileName, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
4358
4547
|
* @return {string} Compact `Context Files` summary string.
|
|
4359
|
-
* @satisfies REQ-327
|
|
4548
|
+
* @satisfies REQ-327, REQ-376
|
|
4360
4549
|
*/
|
|
4361
|
-
function formatContextFilesSummary(
|
|
4362
|
-
|
|
4550
|
+
function formatContextFilesSummary(
|
|
4551
|
+
config: UseReqConfig,
|
|
4552
|
+
sizes: Record<ContextFileName, ContextFileSizeFacts>,
|
|
4553
|
+
): string {
|
|
4554
|
+
const segments = [
|
|
4555
|
+
`requirements:${config["context-files-requirements"] ? `on(${formatContextFileSize(sizes["REQUIREMENTS.md"])})` : "off"}`,
|
|
4556
|
+
`references:${config["context-files-references"] ? `on(${formatContextFileSize(sizes["REFERENCES.md"])})` : "off"}`,
|
|
4557
|
+
`workflow:${config["context-files-workflow"] ? `on(${formatContextFileSize(sizes["WORKFLOW.md"])})` : "off"}`,
|
|
4558
|
+
];
|
|
4559
|
+
return segments.join(" \u2022 ");
|
|
4363
4560
|
}
|
|
4364
4561
|
|
|
4365
4562
|
/**
|
|
4366
4563
|
* @brief Builds the shared settings-menu choices for the `Context Files` submenu.
|
|
4367
|
-
* @details Exposes one inline
|
|
4564
|
+
* @details Exposes one inline toggle row per context file in the documented `REQUIREMENTS.md`, `REFERENCES.md`, `WORKFLOW.md` order whose value renders `on|off • <chars>c/<tokens>t` measured size facts plus a value-less subtree-local `Reset defaults` row. Cycle values embed the same measured facts so inline toggling keeps the size estimate visible while persisting the on|off state. Runtime is O(1) in row count. No external state is mutated.
|
|
4368
4565
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
4566
|
+
* @param[in] sizes {Record<ContextFileName, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
4369
4567
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered `Context Files` submenu choices.
|
|
4370
|
-
* @satisfies REQ-327, REQ-328, REQ-333
|
|
4568
|
+
* @satisfies REQ-327, REQ-328, REQ-333, REQ-375
|
|
4371
4569
|
*/
|
|
4372
|
-
function buildContextFilesMenuChoices(
|
|
4570
|
+
function buildContextFilesMenuChoices(
|
|
4571
|
+
config: UseReqConfig,
|
|
4572
|
+
sizes: Record<ContextFileName, ContextFileSizeFacts>,
|
|
4573
|
+
): PiUsereqSettingsMenuChoice[] {
|
|
4574
|
+
const buildRow = (
|
|
4575
|
+
id: "context-files-requirements" | "context-files-references" | "context-files-workflow",
|
|
4576
|
+
fileName: ContextFileName,
|
|
4577
|
+
description: string,
|
|
4578
|
+
): PiUsereqSettingsMenuChoice => {
|
|
4579
|
+
const sizedOn = `on \u2022 ${formatContextFileSize(sizes[fileName])}`;
|
|
4580
|
+
const sizedOff = `off \u2022 ${formatContextFileSize(sizes[fileName])}`;
|
|
4581
|
+
return {
|
|
4582
|
+
id,
|
|
4583
|
+
label: fileName,
|
|
4584
|
+
value: config[id] ? sizedOn : sizedOff,
|
|
4585
|
+
values: [sizedOn, sizedOff],
|
|
4586
|
+
description,
|
|
4587
|
+
};
|
|
4588
|
+
};
|
|
4373
4589
|
return [
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
id: "context-files-workflow",
|
|
4390
|
-
label: "WORKFLOW.md",
|
|
4391
|
-
value: config["context-files-workflow"] ? "on" : "off",
|
|
4392
|
-
values: ["on", "off"],
|
|
4393
|
-
description: "Toggle injection of WORKFLOW.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4394
|
-
},
|
|
4590
|
+
buildRow(
|
|
4591
|
+
"context-files-requirements",
|
|
4592
|
+
"REQUIREMENTS.md",
|
|
4593
|
+
"Toggle injection of REQUIREMENTS.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4594
|
+
),
|
|
4595
|
+
buildRow(
|
|
4596
|
+
"context-files-references",
|
|
4597
|
+
"REFERENCES.md",
|
|
4598
|
+
"Toggle injection of REFERENCES.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4599
|
+
),
|
|
4600
|
+
buildRow(
|
|
4601
|
+
"context-files-workflow",
|
|
4602
|
+
"WORKFLOW.md",
|
|
4603
|
+
"Toggle injection of WORKFLOW.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4604
|
+
),
|
|
4395
4605
|
...buildTerminalSettingsMenuChoices({
|
|
4396
4606
|
resetDefaultsDescription: "Restore the default Context Files configuration (all three files enabled).",
|
|
4397
4607
|
}),
|
|
@@ -4412,6 +4622,7 @@ async function configureContextFilesMenu(
|
|
|
4412
4622
|
config: UseReqConfig,
|
|
4413
4623
|
onConfigChange: () => void,
|
|
4414
4624
|
): Promise<void> {
|
|
4625
|
+
const contextFileSizes = measureContextFileSizes(getProjectBase(ctx.cwd), config);
|
|
4415
4626
|
const setFlag = (flagKey: "context-files-requirements" | "context-files-references" | "context-files-workflow", enabled: boolean): void => {
|
|
4416
4627
|
config[flagKey] = enabled;
|
|
4417
4628
|
onConfigChange();
|
|
@@ -4419,20 +4630,20 @@ async function configureContextFilesMenu(
|
|
|
4419
4630
|
};
|
|
4420
4631
|
let focusedChoiceId: string | undefined;
|
|
4421
4632
|
while (true) {
|
|
4422
|
-
const choice = await showPiUsereqSettingsMenu(ctx, "Context Files", buildContextFilesMenuChoices(config), {
|
|
4633
|
+
const choice = await showPiUsereqSettingsMenu(ctx, "Context Files", buildContextFilesMenuChoices(config, contextFileSizes), {
|
|
4423
4634
|
initialSelectedId: focusedChoiceId,
|
|
4424
|
-
getChoices: () => buildContextFilesMenuChoices(config),
|
|
4635
|
+
getChoices: () => buildContextFilesMenuChoices(config, contextFileSizes),
|
|
4425
4636
|
onChange: (choiceId, newValue) => {
|
|
4426
4637
|
if (choiceId === "context-files-requirements") {
|
|
4427
|
-
setFlag("context-files-requirements", newValue
|
|
4638
|
+
setFlag("context-files-requirements", newValue.startsWith("on"));
|
|
4428
4639
|
return;
|
|
4429
4640
|
}
|
|
4430
4641
|
if (choiceId === "context-files-references") {
|
|
4431
|
-
setFlag("context-files-references", newValue
|
|
4642
|
+
setFlag("context-files-references", newValue.startsWith("on"));
|
|
4432
4643
|
return;
|
|
4433
4644
|
}
|
|
4434
4645
|
if (choiceId === "context-files-workflow") {
|
|
4435
|
-
setFlag("context-files-workflow", newValue
|
|
4646
|
+
setFlag("context-files-workflow", newValue.startsWith("on"));
|
|
4436
4647
|
}
|
|
4437
4648
|
},
|
|
4438
4649
|
});
|
|
@@ -4473,7 +4684,7 @@ async function configureContextFilesMenu(
|
|
|
4473
4684
|
* @param[in] cwd {string} Current working directory.
|
|
4474
4685
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
4475
4686
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered top-level menu choices.
|
|
4476
|
-
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-314, REQ-318, REQ-319, REQ-320, REQ-326
|
|
4687
|
+
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-314, REQ-318, REQ-319, REQ-320, REQ-326, REQ-376
|
|
4477
4688
|
*/
|
|
4478
4689
|
function buildPiUsereqMenuChoices(
|
|
4479
4690
|
cwd: string,
|
|
@@ -4506,7 +4717,7 @@ function buildPiUsereqMenuChoices(
|
|
|
4506
4717
|
{
|
|
4507
4718
|
id: "context-files",
|
|
4508
4719
|
label: "Context Files",
|
|
4509
|
-
value: formatContextFilesSummary(config),
|
|
4720
|
+
value: formatContextFilesSummary(config, measureContextFileSizes(cwd, config)),
|
|
4510
4721
|
description: "Toggle injection of REQUIREMENTS.md, REFERENCES.md, and WORKFLOW.md into prompt context through `%%CONTEXT_FILES%%`.",
|
|
4511
4722
|
},
|
|
4512
4723
|
{
|
|
@@ -4863,7 +5074,7 @@ async function configurePiUsereq(
|
|
|
4863
5074
|
{ label: "Document directory", previousValue: config["docs-dir"], nextValue: defaultConfig["docs-dir"] },
|
|
4864
5075
|
{ label: "Source-code directories", previousValue: config["src-dir"].join(", "), nextValue: defaultConfig["src-dir"].join(", ") },
|
|
4865
5076
|
{ label: "Unit tests directory", previousValue: config["tests-dir"], nextValue: defaultConfig["tests-dir"] },
|
|
4866
|
-
{ label: "Context Files", previousValue: formatContextFilesSummary(config), nextValue: formatContextFilesSummary(defaultConfig) },
|
|
5077
|
+
{ label: "Context Files", previousValue: formatContextFilesSummary(config, measureContextFileSizes(ctx.cwd, config)), nextValue: formatContextFilesSummary(defaultConfig, measureContextFileSizes(ctx.cwd, defaultConfig)) },
|
|
4867
5078
|
{ label: "Auto git commit", previousValue: config.AUTO_GIT_COMMIT, nextValue: defaultConfig.AUTO_GIT_COMMIT },
|
|
4868
5079
|
{ label: "Git worktree", previousValue: config.GIT_WORKTREE_ENABLED, nextValue: defaultConfig.GIT_WORKTREE_ENABLED },
|
|
4869
5080
|
{ label: "Worktree prefix", previousValue: config.GIT_WORKTREE_PREFIX, nextValue: defaultConfig.GIT_WORKTREE_PREFIX },
|