pi-usereq 0.63.0 → 0.65.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 +1 -1
- package/scripts/pi-cli-update-src-docs.sh +370 -0
- package/scripts/pi-prompts-update.sh +338 -0
- package/src/core/context-file-size.ts +204 -7
- package/src/core/extension-status.ts +16 -0
- package/src/core/prompt-command-runtime.ts +7 -3
- package/src/core/prompts.ts +12 -5
- package/src/core/token-counter.ts +26 -6
- package/src/index.ts +41 -18
- package/src/resources/pi-prompts-version.txt +1 -0
- package/src/resources/prompts/analyze.md +12 -3
- package/src/resources/prompts/readme.md +6 -3
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# @file scripts/pi-prompts-update.sh
|
|
3
|
+
# @brief Updates bundled PI-useReq prompt resources from the latest PI-Prompts release.
|
|
4
|
+
# @details Clones the PI-Prompts repository (master branch), resolves the newest
|
|
5
|
+
# release ref, and performs a full one-way synchronization of the upstream
|
|
6
|
+
# `src/instructions`, `src/prompts`, and `src/templates` directories into the
|
|
7
|
+
# local `src/resources/instructions`, `src/resources/prompts`, and
|
|
8
|
+
# `src/resources/templates` targets. Full synchronization overwrites every
|
|
9
|
+
# present file and removes every file that no longer exists upstream by
|
|
10
|
+
# replacing each target directory with a freshly staged copy. Upstream file
|
|
11
|
+
# access and modification timestamps are preserved through `cp -p`. Before
|
|
12
|
+
# any modification, the updater prints the resolved latest PI-Prompts
|
|
13
|
+
# release version and the stored version read from
|
|
14
|
+
# `src/resources/pi-prompts-version.txt` (missing or empty files report
|
|
15
|
+
# `unknown`), then requires an explicit `Y` confirmation; any other input
|
|
16
|
+
# aborts without modifications. On success, the resolved version is written
|
|
17
|
+
# into `src/resources/pi-prompts-version.txt`. Runtime is dominated by one
|
|
18
|
+
# git clone plus one ref checkout and per-file copies. Side effects include
|
|
19
|
+
# temporary-directory creation, git subprocesses, one optional GitHub
|
|
20
|
+
# Releases API request, replacement of the three target resource
|
|
21
|
+
# directories, and overwrite of the one version marker file. No other
|
|
22
|
+
# repository content is modified.
|
|
23
|
+
# @satisfies DES-022, REQ-379, REQ-380, REQ-381, REQ-382, REQ-383, REQ-384, REQ-396, REQ-397, REQ-398, REQ-399
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
PROMPTS_REPO_URL_PRIMARY="https://github.com/Ogekuri/PI-Prompts.git"
|
|
28
|
+
PROMPTS_REPO_URL_FALLBACK="https://github.com/Ogekuri/PI-Prompts"
|
|
29
|
+
PROMPTS_RELEASE_API_URL="https://api.github.com/repos/Ogekuri/PI-Prompts/releases/latest"
|
|
30
|
+
PROMPTS_BRANCH="master"
|
|
31
|
+
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
34
|
+
TARGET_ROOT="${REPO_ROOT}/src/resources"
|
|
35
|
+
|
|
36
|
+
## @brief Stores the PI-Prompts version marker path relative to the repository root.
|
|
37
|
+
## @details Read before the update and overwritten with the resolved version after
|
|
38
|
+
## successful synchronization. The constant is immutable at runtime.
|
|
39
|
+
## @satisfies REQ-396, REQ-399
|
|
40
|
+
PROMPTS_VERSION_REL="src/resources/pi-prompts-version.txt"
|
|
41
|
+
|
|
42
|
+
## @brief Stores the ordered `upstream-relative:target-relative` synchronization pairs.
|
|
43
|
+
## @details Each entry maps one upstream repository directory to one bundled
|
|
44
|
+
## `src/resources` target directory synchronized in declaration order. The
|
|
45
|
+
## constant is immutable at runtime and costs O(1) per element access.
|
|
46
|
+
SYNC_SPECS=(
|
|
47
|
+
"src/instructions:src/resources/instructions"
|
|
48
|
+
"src/prompts:src/resources/prompts"
|
|
49
|
+
"src/templates:src/resources/templates"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
WORK_DIR=""
|
|
53
|
+
STAGING_DIR=""
|
|
54
|
+
|
|
55
|
+
## @brief Writes one progress message to stdout.
|
|
56
|
+
## @details Emits the supplied message fragments joined by single spaces followed
|
|
57
|
+
## by one newline. Runtime is O(n) in message length. Side effect: writes to
|
|
58
|
+
## `stdout`.
|
|
59
|
+
## @param[in] ... {string} Progress message fragments.
|
|
60
|
+
## @return {void} No return value.
|
|
61
|
+
log() {
|
|
62
|
+
printf '%s\n' "$*"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
## @brief Aborts the updater with one deterministic diagnostic.
|
|
66
|
+
## @details Writes the supplied diagnostic prefixed with `ERROR: ` to `stderr`
|
|
67
|
+
## and terminates the process with exit code `1`. Runtime is O(n) in message
|
|
68
|
+
## length. Side effects include `stderr` output and process exit; the `EXIT`
|
|
69
|
+
## trap still removes updater temporary directories.
|
|
70
|
+
## @param[in] message {string} Failure diagnostic.
|
|
71
|
+
## @return {never} Never returns.
|
|
72
|
+
fail() {
|
|
73
|
+
printf 'ERROR: %s\n' "$*" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
## @brief Removes updater temporary directories.
|
|
78
|
+
## @details Deletes the cloned upstream working tree and any pending staging
|
|
79
|
+
## directory registered in `WORK_DIR` plus `STAGING_DIR`. Invoked through the
|
|
80
|
+
## `EXIT` trap on every exit path. Runtime is O(1) plus filesystem removal
|
|
81
|
+
## cost. Side effects include directory removal.
|
|
82
|
+
## @return {void} No return value.
|
|
83
|
+
cleanup() {
|
|
84
|
+
if [ -n "${STAGING_DIR}" ]; then
|
|
85
|
+
rm -rf "${STAGING_DIR}"
|
|
86
|
+
fi
|
|
87
|
+
if [ -n "${WORK_DIR}" ]; then
|
|
88
|
+
rm -rf "${WORK_DIR}"
|
|
89
|
+
fi
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
## @brief Verifies that one required external tool is available.
|
|
93
|
+
## @details Probes the executable search path for the requested tool name and
|
|
94
|
+
## aborts through `fail(...)` when the probe fails. Runtime is O(1) plus one
|
|
95
|
+
## `PATH` lookup. No external state is mutated.
|
|
96
|
+
## @param[in] tool {string} Executable name to probe.
|
|
97
|
+
## @return {void} No return value.
|
|
98
|
+
require_tool() {
|
|
99
|
+
command -v "$1" >/dev/null 2>&1 || fail "Required tool not found: $1"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
## @brief Reads one stored PI-Prompts version marker file.
|
|
103
|
+
## @details Returns the first line of the marker file with all surrounding
|
|
104
|
+
## whitespace removed when the file exists and is non-empty; otherwise
|
|
105
|
+
## returns the literal `unknown` so missing or empty version markers are
|
|
106
|
+
## handled deterministically. Runtime is O(n) in file size. Side effects are
|
|
107
|
+
## limited to filesystem reads.
|
|
108
|
+
## @param[in] marker_path {string} Absolute version marker file path.
|
|
109
|
+
## @return {string} Trimmed stored version or `unknown` on `stdout`.
|
|
110
|
+
## @satisfies REQ-396
|
|
111
|
+
read_version_file() {
|
|
112
|
+
local marker_path="$1"
|
|
113
|
+
local version=""
|
|
114
|
+
if [ -f "${marker_path}" ] && [ -s "${marker_path}" ]; then
|
|
115
|
+
version="$(head -n 1 "${marker_path}" | tr -d '[:space:]' || true)"
|
|
116
|
+
fi
|
|
117
|
+
if [ -z "${version}" ]; then
|
|
118
|
+
version="unknown"
|
|
119
|
+
fi
|
|
120
|
+
printf '%s\n' "${version}"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
## @brief Derives the release version from one resolved upstream synchronization ref.
|
|
124
|
+
## @details Strips the `refs/tags/` prefix from a release-tag ref and the
|
|
125
|
+
## `refs/heads/` prefix from a branch-head ref so the printed version and
|
|
126
|
+
## the stored version marker share one deterministic release identity.
|
|
127
|
+
## Runtime is O(n) in ref length. No external state is mutated.
|
|
128
|
+
## @param[in] release_ref {string} Resolved `refs/tags/<tag>` or `refs/heads/<branch>` ref.
|
|
129
|
+
## @return {string} Release version on `stdout`.
|
|
130
|
+
resolve_release_version() {
|
|
131
|
+
local release_ref="$1"
|
|
132
|
+
case "${release_ref}" in
|
|
133
|
+
refs/tags/*)
|
|
134
|
+
printf '%s\n' "${release_ref#refs/tags/}"
|
|
135
|
+
;;
|
|
136
|
+
refs/heads/*)
|
|
137
|
+
printf '%s\n' "${release_ref#refs/heads/}"
|
|
138
|
+
;;
|
|
139
|
+
*)
|
|
140
|
+
printf '%s\n' "${release_ref}"
|
|
141
|
+
;;
|
|
142
|
+
esac
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
## @brief Requests explicit user confirmation before any modification.
|
|
146
|
+
## @details Prints the resolved latest PI-Prompts release version plus the stored
|
|
147
|
+
## version and one English confirmation sentence, reads one line from
|
|
148
|
+
## `stdin`, and returns success only when the confirmation input is exactly
|
|
149
|
+
## `Y`; any other input, including end-of-stream input, returns failure so
|
|
150
|
+
## callers abort without modifications. Runtime is O(1) plus one user
|
|
151
|
+
## interaction. Side effects include `stdout` and `stderr` writes.
|
|
152
|
+
## @param[in] latest_version {string} Resolved upstream release version.
|
|
153
|
+
## @param[in] stored_version {string} Stored version of `${PROMPTS_VERSION_REL}`.
|
|
154
|
+
## @return {integer} `0` when the user confirmed with exactly `Y`; `1` otherwise.
|
|
155
|
+
## @satisfies REQ-397, REQ-398
|
|
156
|
+
confirm_update() {
|
|
157
|
+
local latest_version="$1"
|
|
158
|
+
local stored_version="$2"
|
|
159
|
+
local answer=""
|
|
160
|
+
|
|
161
|
+
log "Latest PI-Prompts release : ${latest_version}"
|
|
162
|
+
log "Stored version (${PROMPTS_VERSION_REL}): ${stored_version}"
|
|
163
|
+
log "This update replaces the bundled prompt resources in src/resources/instructions, src/resources/prompts, and src/resources/templates with the content of the latest PI-Prompts release."
|
|
164
|
+
printf 'Proceed with the update? [Y/n] '
|
|
165
|
+
read -r answer || answer=""
|
|
166
|
+
[ "${answer}" = "Y" ]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
## @brief Clones the upstream PI-Prompts master branch into one target directory.
|
|
170
|
+
## @details Attempts a quiet, no-checkout clone of `${PROMPTS_BRANCH}` from
|
|
171
|
+
## `${PROMPTS_REPO_URL_PRIMARY}`, removes the partial destination on failure,
|
|
172
|
+
## and retries once with `${PROMPTS_REPO_URL_FALLBACK}`. Prints the resolved
|
|
173
|
+
## upstream URL on `stdout` so callers can capture provenance. Runtime is
|
|
174
|
+
## dominated by git subprocess execution. Side effects include
|
|
175
|
+
## destination-directory creation and removal.
|
|
176
|
+
## @param[in] destination {string} Target directory path for the clone.
|
|
177
|
+
## @return {string} Resolved upstream repository URL on stdout.
|
|
178
|
+
## @throws Exits through `fail(...)` when both upstream URLs are unreachable.
|
|
179
|
+
clone_upstream() {
|
|
180
|
+
local destination="$1"
|
|
181
|
+
rm -rf "${destination}"
|
|
182
|
+
if git clone --quiet --no-checkout --branch "${PROMPTS_BRANCH}" "${PROMPTS_REPO_URL_PRIMARY}" "${destination}" 2>/dev/null; then
|
|
183
|
+
printf '%s\n' "${PROMPTS_REPO_URL_PRIMARY}"
|
|
184
|
+
return 0
|
|
185
|
+
fi
|
|
186
|
+
log "Primary clone failed, retrying with the fallback repository URL ..."
|
|
187
|
+
rm -rf "${destination}"
|
|
188
|
+
if git clone --quiet --no-checkout --branch "${PROMPTS_BRANCH}" "${PROMPTS_REPO_URL_FALLBACK}" "${destination}" 2>/dev/null; then
|
|
189
|
+
printf '%s\n' "${PROMPTS_REPO_URL_FALLBACK}"
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
fail "Unable to clone PI-Prompts from ${PROMPTS_REPO_URL_PRIMARY} or ${PROMPTS_REPO_URL_FALLBACK}."
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
## @brief Resolves the upstream synchronization ref for the cloned repository.
|
|
196
|
+
## @details Prefers the newest tag reported by the GitHub Releases API when
|
|
197
|
+
## `curl` is available and that tag exists in the local clone; otherwise
|
|
198
|
+
## selects the newest version-sorted tag reachable from `${PROMPTS_BRANCH}`;
|
|
199
|
+
## otherwise falls back to the `${PROMPTS_BRANCH}` head. Prints the resolved
|
|
200
|
+
## ref name on `stdout`. Runtime is dominated by one optional HTTPS request
|
|
201
|
+
## plus git ref enumeration. No external state is mutated.
|
|
202
|
+
## @param[in] repository {string} Cloned upstream repository path.
|
|
203
|
+
## @return {string} Resolved `refs/tags/<tag>` or `refs/heads/master` ref on stdout.
|
|
204
|
+
resolve_release_ref() {
|
|
205
|
+
local repository="$1"
|
|
206
|
+
local release_tag=""
|
|
207
|
+
|
|
208
|
+
if command -v curl >/dev/null 2>&1; then
|
|
209
|
+
release_tag="$(curl -fsSL --max-time 20 "${PROMPTS_RELEASE_API_URL}" 2>/dev/null \
|
|
210
|
+
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
211
|
+
| head -n 1 || true)"
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
if [ -n "${release_tag}" ] \
|
|
215
|
+
&& git -C "${repository}" rev-parse --verify --quiet "refs/tags/${release_tag}" >/dev/null 2>&1; then
|
|
216
|
+
printf 'refs/tags/%s\n' "${release_tag}"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
release_tag="$(git -C "${repository}" tag --sort=-v:refname --merged "${PROMPTS_BRANCH}" 2>/dev/null | head -n 1 || true)"
|
|
221
|
+
if [ -n "${release_tag}" ]; then
|
|
222
|
+
printf 'refs/tags/%s\n' "${release_tag}"
|
|
223
|
+
return 0
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
log "No PI-Prompts release resolvable, falling back to the ${PROMPTS_BRANCH} head."
|
|
227
|
+
printf 'refs/heads/%s\n' "${PROMPTS_BRANCH}"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
## @brief Fully synchronizes one upstream directory into one target directory.
|
|
231
|
+
## @details Stages every upstream file below `source_dir` into a fresh sibling
|
|
232
|
+
## staging directory using timestamp-preserving copies, then replaces
|
|
233
|
+
## `target_dir` with the staged tree through a backup-and-swap sequence so
|
|
234
|
+
## files removed upstream disappear and existing files are overwritten.
|
|
235
|
+
## Runtime is dominated by per-file copy cost. Side effects include staging
|
|
236
|
+
## plus backup directory creation and removal, target-directory replacement,
|
|
237
|
+
## and one progress line on `stdout`.
|
|
238
|
+
## @param[in] source_dir {string} Absolute upstream source directory path.
|
|
239
|
+
## @param[in] target_dir {string} Absolute bundled target directory path.
|
|
240
|
+
## @return {void} No return value.
|
|
241
|
+
## @throws Exits through `fail(...)` when `source_dir` is not a directory.
|
|
242
|
+
sync_directory() {
|
|
243
|
+
local source_dir="$1"
|
|
244
|
+
local target_dir="$2"
|
|
245
|
+
|
|
246
|
+
[ -d "${source_dir}" ] || fail "Upstream directory not found: ${source_dir}"
|
|
247
|
+
|
|
248
|
+
STAGING_DIR="${target_dir}.staging.$$"
|
|
249
|
+
rm -rf "${STAGING_DIR}"
|
|
250
|
+
mkdir -p "${STAGING_DIR}"
|
|
251
|
+
|
|
252
|
+
local relative_path destination parent_dir copied=0
|
|
253
|
+
while IFS= read -r -d '' relative_path; do
|
|
254
|
+
relative_path="${relative_path#./}"
|
|
255
|
+
[ -n "${relative_path}" ] || continue
|
|
256
|
+
destination="${STAGING_DIR}/${relative_path}"
|
|
257
|
+
parent_dir="$(dirname "${destination}")"
|
|
258
|
+
mkdir -p "${parent_dir}"
|
|
259
|
+
cp -p "${source_dir}/${relative_path}" "${destination}"
|
|
260
|
+
copied=$((copied + 1))
|
|
261
|
+
done < <(cd "${source_dir}" && find . -type f -print0)
|
|
262
|
+
|
|
263
|
+
local backup_dir="${target_dir}.backup.$$"
|
|
264
|
+
rm -rf "${backup_dir}"
|
|
265
|
+
if [ -d "${target_dir}" ]; then
|
|
266
|
+
mv "${target_dir}" "${backup_dir}"
|
|
267
|
+
fi
|
|
268
|
+
mv "${STAGING_DIR}" "${target_dir}"
|
|
269
|
+
STAGING_DIR=""
|
|
270
|
+
rm -rf "${backup_dir}"
|
|
271
|
+
|
|
272
|
+
log "Synchronized ${copied} file(s) into ${target_dir#"${REPO_ROOT}"/}"
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
## @brief Executes one full bundled-resource update run.
|
|
276
|
+
## @details Validates required tools, reads the stored PI-Prompts version marker,
|
|
277
|
+
## allocates the temporary upstream working tree, clones the PI-Prompts
|
|
278
|
+
## master branch, resolves the newest release ref plus its release version,
|
|
279
|
+
## prints the resolved plus stored versions and requires an explicit `Y`
|
|
280
|
+
## confirmation, checks out the resolved release ref, synchronizes every
|
|
281
|
+
## ordered pair declared in `SYNC_SPECS`, and writes the resolved version
|
|
282
|
+
## into the version marker file. Runtime is dominated by the upstream clone
|
|
283
|
+
## plus the three directory synchronizations. Side effects include
|
|
284
|
+
## temporary-directory creation and removal, git subprocesses, one optional
|
|
285
|
+
## GitHub Releases API request, replacement of the three bundled target
|
|
286
|
+
## directories, and overwrite of the version marker file.
|
|
287
|
+
## @return {void} No return value.
|
|
288
|
+
## @throws Exits through `fail(...)` when any required tool, clone, checkout, or
|
|
289
|
+
## synchronization step fails.
|
|
290
|
+
## @satisfies REQ-396, REQ-397, REQ-398, REQ-399
|
|
291
|
+
main() {
|
|
292
|
+
require_tool git
|
|
293
|
+
require_tool find
|
|
294
|
+
require_tool mkdir
|
|
295
|
+
require_tool cp
|
|
296
|
+
|
|
297
|
+
local prompts_version
|
|
298
|
+
prompts_version="$(read_version_file "${REPO_ROOT}/${PROMPTS_VERSION_REL}")"
|
|
299
|
+
|
|
300
|
+
mkdir -p "${TARGET_ROOT}"
|
|
301
|
+
|
|
302
|
+
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/pi-prompts-update.XXXXXX")" \
|
|
303
|
+
|| fail "Unable to create the temporary working directory."
|
|
304
|
+
local repository="${WORK_DIR}/PI-Prompts"
|
|
305
|
+
|
|
306
|
+
local upstream_url
|
|
307
|
+
upstream_url="$(clone_upstream "${repository}")"
|
|
308
|
+
log "Upstream repository: ${upstream_url}"
|
|
309
|
+
|
|
310
|
+
local release_ref
|
|
311
|
+
release_ref="$(resolve_release_ref "${repository}")"
|
|
312
|
+
|
|
313
|
+
local release_version
|
|
314
|
+
release_version="$(resolve_release_version "${release_ref}")"
|
|
315
|
+
|
|
316
|
+
if ! confirm_update "${release_version}" "${prompts_version}"; then
|
|
317
|
+
log "Update aborted; no repository content was modified."
|
|
318
|
+
return 0
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
git -C "${repository}" checkout --quiet --detach "${release_ref}" \
|
|
322
|
+
|| fail "Unable to check out ${release_ref}."
|
|
323
|
+
log "Synchronizing from ref: ${release_ref}"
|
|
324
|
+
|
|
325
|
+
local spec source_rel target_rel
|
|
326
|
+
for spec in "${SYNC_SPECS[@]}"; do
|
|
327
|
+
source_rel="${spec%%:*}"
|
|
328
|
+
target_rel="${spec#*:}"
|
|
329
|
+
sync_directory "${repository}/${source_rel}" "${REPO_ROOT}/${target_rel}"
|
|
330
|
+
done
|
|
331
|
+
|
|
332
|
+
printf '%s\n' "${release_version}" > "${REPO_ROOT}/${PROMPTS_VERSION_REL}"
|
|
333
|
+
|
|
334
|
+
log "Bundled prompt resources updated to ${release_version}."
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
trap cleanup EXIT
|
|
338
|
+
main "$@"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
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)
|
|
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. Measurements are memoized per resolved path against the file `mtimeMs` plus `size` signature and reuses the process-cached shared tokenizer, so repeated menu renders resolve unchanged files through filesystem stats only. Runtime is O(n) on first measurement per content revision and O(1) per unchanged remeasure. Side effects are limited to filesystem reads and module-local cache mutation.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs";
|
|
@@ -9,6 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import { DEFAULT_DOCS_DIR, type UseReqConfig } from "./config.js";
|
|
10
10
|
import { normalizeRelativeDirContract } from "./path-context.js";
|
|
11
11
|
import { countFileMetrics } from "./token-counter.js";
|
|
12
|
+
import { ReqError } from "./errors.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @brief Stores the canonical context-file names in their documented injection order.
|
|
@@ -41,6 +42,27 @@ export interface ContextFileSizeFacts {
|
|
|
41
42
|
*/
|
|
42
43
|
export const MISSING_CONTEXT_FILE_SIZE: ContextFileSizeFacts = Object.freeze({ exists: false, chars: 0, tokens: 0 });
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @brief Stores one memoized context-file measurement with its staleness signature.
|
|
47
|
+
* @details Pairs the measured facts with the `mtimeMs` plus `size` filesystem signature observed at measurement time so later probes can decide, through one stat call, whether the cached facts still describe the current file content. The interface is compile-time only and introduces no runtime cost.
|
|
48
|
+
*/
|
|
49
|
+
interface CachedContextFileSizeEntry {
|
|
50
|
+
readonly statSignature: string;
|
|
51
|
+
readonly facts: ContextFileSizeFacts;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @brief Bounds the module-local context-file measurement cache.
|
|
56
|
+
* @details Worktree-backed prompt runs measure three context files per execution path, so unbounded caching would accumulate one entry triplet per generated worktree in long-lived pi hosts. Exceeding the bound clears the cache completely, which only costs one re-measurement per live path. Lookup complexity is O(1).
|
|
57
|
+
*/
|
|
58
|
+
const CONTEXT_FILE_SIZE_CACHE_LIMIT = 64;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @brief Stores the module-local context-file measurement cache keyed by resolved path.
|
|
62
|
+
* @details Maps each measured absolute context-file path to its stat-signed facts so repeated configuration-menu renders and command summaries reuse identical measurements while any content revision invalidates only the affected entry. Cache state is module-local and never serialized into configuration.
|
|
63
|
+
*/
|
|
64
|
+
const contextFileSizeCache = new Map<string, CachedContextFileSizeEntry>();
|
|
65
|
+
|
|
44
66
|
/**
|
|
45
67
|
* @brief Resolves one canonical context-file absolute path for one project base.
|
|
46
68
|
* @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.
|
|
@@ -61,18 +83,34 @@ export function resolveContextFilePath(
|
|
|
61
83
|
|
|
62
84
|
/**
|
|
63
85
|
* @brief Measures one context file into deterministic size facts.
|
|
64
|
-
* @details Probes
|
|
86
|
+
* @details Probes the target with one `stat` call, returns the memoized facts when the observed `mtimeMs` plus `size` signature matches the cached entry, and otherwise reads UTF-8 content and reuses the process-cached shared tokenizer behind `countFileMetrics` for the `cl100k_base` token estimate before storing the fresh facts in the bounded cache. Missing, non-file, and unreadable targets return `MISSING_CONTEXT_FILE_SIZE` facts without throwing and without caching. Runtime is O(1) for unchanged remeasures and O(n) in file size on first measurement per content revision. Side effects are limited to filesystem reads and module-local cache mutation.
|
|
65
87
|
* @param[in] filePath {string} Absolute context-file path to measure.
|
|
66
88
|
* @return {ContextFileSizeFacts} Measured size facts for the target.
|
|
67
89
|
* @satisfies REQ-373, REQ-374
|
|
68
90
|
*/
|
|
69
91
|
export function measureContextFileSize(filePath: string): ContextFileSizeFacts {
|
|
92
|
+
let stat: fs.Stats | undefined;
|
|
93
|
+
try {
|
|
94
|
+
stat = fs.statSync(filePath);
|
|
95
|
+
} catch {
|
|
96
|
+
stat = undefined;
|
|
97
|
+
}
|
|
98
|
+
if (!stat || !stat.isFile()) {
|
|
99
|
+
return MISSING_CONTEXT_FILE_SIZE;
|
|
100
|
+
}
|
|
101
|
+
const statSignature = `${stat.mtimeMs}:${stat.size}`;
|
|
102
|
+
const cachedEntry = contextFileSizeCache.get(filePath);
|
|
103
|
+
if (cachedEntry && cachedEntry.statSignature === statSignature) {
|
|
104
|
+
return cachedEntry.facts;
|
|
105
|
+
}
|
|
70
106
|
try {
|
|
71
|
-
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
72
|
-
return MISSING_CONTEXT_FILE_SIZE;
|
|
73
|
-
}
|
|
74
107
|
const metrics = countFileMetrics(fs.readFileSync(filePath, "utf8"));
|
|
75
|
-
|
|
108
|
+
const facts: ContextFileSizeFacts = { exists: true, chars: metrics.chars, tokens: metrics.tokens };
|
|
109
|
+
if (contextFileSizeCache.size >= CONTEXT_FILE_SIZE_CACHE_LIMIT) {
|
|
110
|
+
contextFileSizeCache.clear();
|
|
111
|
+
}
|
|
112
|
+
contextFileSizeCache.set(filePath, { statSignature, facts });
|
|
113
|
+
return facts;
|
|
76
114
|
} catch {
|
|
77
115
|
return MISSING_CONTEXT_FILE_SIZE;
|
|
78
116
|
}
|
|
@@ -80,7 +118,7 @@ export function measureContextFileSize(filePath: string): ContextFileSizeFacts {
|
|
|
80
118
|
|
|
81
119
|
/**
|
|
82
120
|
* @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.
|
|
121
|
+
* @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. Repeated invocations with unchanged files resolve through the stat-signed measurement cache. Runtime is O(n) in aggregate context-file size on first measurement per content revision and O(1) per unchanged remeasure. Side effects are limited to filesystem reads and module-local cache mutation.
|
|
84
122
|
* @param[in] projectBase {string} Absolute project root path.
|
|
85
123
|
* @param[in] config {UseReqConfig} Effective project configuration supplying the docs directory.
|
|
86
124
|
* @return {Record<ContextFileName, ContextFileSizeFacts>} Measured size facts keyed by canonical file name.
|
|
@@ -107,3 +145,162 @@ export function measureContextFileSizes(
|
|
|
107
145
|
export function formatContextFileSize(facts: ContextFileSizeFacts): string {
|
|
108
146
|
return `${facts.chars}c/${facts.tokens}t`;
|
|
109
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @brief Stores the documented fallback context-window token budget.
|
|
151
|
+
* @details Used as the occupancy computation basis and as the early-check limit whenever the selected model max input context is unknown, and always renders behind the `*` marker so consumers can detect the assumed basis. Lookup complexity is O(1).
|
|
152
|
+
* @satisfies REQ-403
|
|
153
|
+
*/
|
|
154
|
+
export const CONTEXT_OCCUPANCY_FALLBACK_CONTEXT_WINDOW_TOKENS = 1_000_000;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @brief Maps every canonical context file to its persisted configuration flag key.
|
|
158
|
+
* @details Single mapping source shared by the token summation and segment renderers so enabled-file selection stays identical across every occupancy surface. Lookup complexity is O(1). No external state is mutated.
|
|
159
|
+
*/
|
|
160
|
+
const CONTEXT_FILE_FLAG_KEYS: Record<ContextFileName, "context-files-requirements" | "context-files-references" | "context-files-workflow"> = {
|
|
161
|
+
"REQUIREMENTS.md": "context-files-requirements",
|
|
162
|
+
"REFERENCES.md": "context-files-references",
|
|
163
|
+
"WORKFLOW.md": "context-files-workflow",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @brief Describes the computed context-occupancy facts of one enabled context-file set.
|
|
168
|
+
* @details Stores the aggregate token total, the resolved selected-model max input context (undefined when unknown), the effective basis tokens used for the percentage, the rendered percentage label, the rendered max-context label, and the final bracketed occupancy suffix. The interface is compile-time only and introduces no runtime cost.
|
|
169
|
+
* @satisfies REQ-400
|
|
170
|
+
*/
|
|
171
|
+
export interface ContextOccupancyFacts {
|
|
172
|
+
readonly totalTokens: number;
|
|
173
|
+
readonly maxContextTokens: number | undefined;
|
|
174
|
+
readonly basisTokens: number;
|
|
175
|
+
readonly percentLabel: string;
|
|
176
|
+
readonly maxLabel: string;
|
|
177
|
+
readonly suffix: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @brief Sums the `cl100k_base` token estimates of every enabled existing context file.
|
|
182
|
+
* @details Iterates `CONTEXT_FILE_NAMES` in documented order, skips disabled flags and missing or unreadable files, and accumulates the measured token estimates so occupancy surfaces and the prompt-dispatch early check share one total. Runtime is O(1) in file count. No external state is mutated.
|
|
183
|
+
* @param[in] config {Pick<UseReqConfig, "context-files-requirements" | "context-files-references" | "context-files-workflow">} Effective configuration supplying the three context-file flags.
|
|
184
|
+
* @param[in] sizes {Record<string, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
185
|
+
* @return {number} Aggregate token estimate of enabled existing context files.
|
|
186
|
+
* @satisfies REQ-400
|
|
187
|
+
*/
|
|
188
|
+
export function sumEnabledContextFileTokens(
|
|
189
|
+
config: Pick<UseReqConfig, "context-files-requirements" | "context-files-references" | "context-files-workflow">,
|
|
190
|
+
sizes: Record<string, ContextFileSizeFacts>,
|
|
191
|
+
): number {
|
|
192
|
+
let totalTokens = 0;
|
|
193
|
+
for (const fileName of CONTEXT_FILE_NAMES) {
|
|
194
|
+
const facts = sizes[fileName];
|
|
195
|
+
if (!config[CONTEXT_FILE_FLAG_KEYS[fileName]] || !facts || !facts.exists) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
totalTokens += facts.tokens;
|
|
199
|
+
}
|
|
200
|
+
return totalTokens;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @brief Formats one context-window token count as a compact human-readable label.
|
|
205
|
+
* @details Renders token counts of one million or more as `<x.x>M`, counts of one thousand or more as `<x.x>K`, and smaller counts as rounded integers so the documented `1.0M` fallback shape stays deterministic. Runtime is O(1). No external state is mutated.
|
|
206
|
+
* @param[in] tokens {number} Context-window token count to label.
|
|
207
|
+
* @return {string} Compact context-window label such as `1.0M`, `200.0K`, or `512`.
|
|
208
|
+
* @satisfies REQ-403
|
|
209
|
+
*/
|
|
210
|
+
export function formatContextWindowTokensLabel(tokens: number): string {
|
|
211
|
+
if (tokens >= 1_000_000) {
|
|
212
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
213
|
+
}
|
|
214
|
+
if (tokens >= 1_000) {
|
|
215
|
+
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
216
|
+
}
|
|
217
|
+
return String(Math.round(tokens));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @brief Formats one occupancy percentage as a compact label with one decimal place.
|
|
222
|
+
* @details Rounds the percentage to one decimal place and removes a trailing `.0` so whole percentages render as `36%` and fractional values keep their decimal digit. Runtime is O(1). No external state is mutated.
|
|
223
|
+
* @param[in] percent {number} Occupancy percentage value.
|
|
224
|
+
* @return {string} Percentage label such as `36%`, `0.4%`, or `0%`.
|
|
225
|
+
* @satisfies REQ-402
|
|
226
|
+
*/
|
|
227
|
+
export function formatContextOccupancyPercentLabel(percent: number): string {
|
|
228
|
+
const fixed = percent.toFixed(1);
|
|
229
|
+
return fixed.endsWith(".0") ? fixed.slice(0, -2) : fixed;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @brief Computes the context-occupancy facts of one enabled context-file token total.
|
|
234
|
+
* @details Resolves the effective basis from the supplied max input context or the documented 1,000,000-token fallback, derives the percentage label, derives the max label with the `*` marker only for the unknown fallback, and renders `[<percent>% context]` for a known max or `[<percent>%/1.0M* context]` for the fallback basis. Runtime is O(1). No external state is mutated.
|
|
235
|
+
* @param[in] totalTokens {number} Aggregate enabled existing context-file token estimate.
|
|
236
|
+
* @param[in] maxContextTokens {number | undefined} Selected model max input context tokens; undefined selects the fallback basis.
|
|
237
|
+
* @return {ContextOccupancyFacts} Computed occupancy facts including the rendered suffix.
|
|
238
|
+
* @satisfies REQ-400, REQ-402, REQ-403
|
|
239
|
+
*/
|
|
240
|
+
export function computeContextOccupancyFacts(
|
|
241
|
+
totalTokens: number,
|
|
242
|
+
maxContextTokens: number | undefined,
|
|
243
|
+
): ContextOccupancyFacts {
|
|
244
|
+
const knownMax = typeof maxContextTokens === "number" && Number.isFinite(maxContextTokens) && maxContextTokens > 0
|
|
245
|
+
? maxContextTokens
|
|
246
|
+
: undefined;
|
|
247
|
+
const basisTokens = knownMax ?? CONTEXT_OCCUPANCY_FALLBACK_CONTEXT_WINDOW_TOKENS;
|
|
248
|
+
const percentLabel = formatContextOccupancyPercentLabel((totalTokens / basisTokens) * 100);
|
|
249
|
+
const maxLabel = knownMax === undefined
|
|
250
|
+
? `${formatContextWindowTokensLabel(basisTokens)}*`
|
|
251
|
+
: formatContextWindowTokensLabel(knownMax);
|
|
252
|
+
const suffix = knownMax === undefined
|
|
253
|
+
? `[${percentLabel}%/${maxLabel} context]`
|
|
254
|
+
: `[${percentLabel}% context]`;
|
|
255
|
+
return { totalTokens, maxContextTokens: knownMax, basisTokens, percentLabel, maxLabel, suffix };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @brief Renders every enabled existing context file as a `name(<chars>c/<tokens>t)` segment list.
|
|
260
|
+
* @details Iterates `CONTEXT_FILE_NAMES` in documented order, skips disabled flags and missing or unreadable files, and joins the remaining segments with the documented ` • ` separator so early-check diagnostics reuse the same segment shape as the configuration summary. Runtime is O(1) in file count. No external state is mutated.
|
|
261
|
+
* @param[in] config {Pick<UseReqConfig, "context-files-requirements" | "context-files-references" | "context-files-workflow">} Effective configuration supplying the three context-file flags.
|
|
262
|
+
* @param[in] sizes {Record<string, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
263
|
+
* @return {string} Joined segment list, or the empty string when no enabled existing context file contributes.
|
|
264
|
+
* @satisfies REQ-410
|
|
265
|
+
*/
|
|
266
|
+
export function formatEnabledContextFileSegments(
|
|
267
|
+
config: Pick<UseReqConfig, "context-files-requirements" | "context-files-references" | "context-files-workflow">,
|
|
268
|
+
sizes: Record<string, ContextFileSizeFacts>,
|
|
269
|
+
): string {
|
|
270
|
+
const segments: string[] = [];
|
|
271
|
+
for (const fileName of CONTEXT_FILE_NAMES) {
|
|
272
|
+
const facts = sizes[fileName];
|
|
273
|
+
if (!config[CONTEXT_FILE_FLAG_KEYS[fileName]] || !facts || !facts.exists) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
segments.push(`${fileName}(${formatContextFileSize(facts)})`);
|
|
277
|
+
}
|
|
278
|
+
return segments.join(" • ");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @brief Enforces the context-occupancy early check for one prepared prompt command.
|
|
283
|
+
* @details Sums the enabled existing context-file tokens, computes the occupancy facts against the supplied max input context or the documented fallback, and throws one deterministic ReqError diagnostic listing every enabled existing file segment, the aggregate token total, and the occupancy suffix whenever the total strictly exceeds the basis. Runtime is O(1) in file count. No external state is mutated.
|
|
284
|
+
* @param[in] config {Pick<UseReqConfig, "context-files-requirements" | "context-files-references" | "context-files-workflow">} Effective configuration supplying the three context-file flags.
|
|
285
|
+
* @param[in] sizes {Record<string, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
286
|
+
* @param[in] maxContextTokens {number | undefined} Selected model max input context tokens; undefined selects the fallback basis.
|
|
287
|
+
* @return {ContextOccupancyFacts} Computed occupancy facts when the check passes.
|
|
288
|
+
* @throws {ReqError} Throws when the enabled token total strictly exceeds the effective basis.
|
|
289
|
+
* @satisfies REQ-408, REQ-409, REQ-410
|
|
290
|
+
*/
|
|
291
|
+
export function enforceContextOccupancyLimit(
|
|
292
|
+
config: Pick<UseReqConfig, "context-files-requirements" | "context-files-references" | "context-files-workflow">,
|
|
293
|
+
sizes: Record<string, ContextFileSizeFacts>,
|
|
294
|
+
maxContextTokens: number | undefined,
|
|
295
|
+
): ContextOccupancyFacts {
|
|
296
|
+
const facts = computeContextOccupancyFacts(sumEnabledContextFileTokens(config, sizes), maxContextTokens);
|
|
297
|
+
const segments = formatEnabledContextFileSegments(config, sizes);
|
|
298
|
+
if (segments.length > 0 && facts.totalTokens > facts.basisTokens) {
|
|
299
|
+
throw new ReqError(
|
|
300
|
+
`Context occupancy early check failed: enabled context files total ${facts.totalTokens}t exceed ` +
|
|
301
|
+
`${facts.maxLabel} selected model input context. ${segments} ${facts.suffix}`,
|
|
302
|
+
1,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return facts;
|
|
306
|
+
}
|
|
@@ -364,6 +364,22 @@ function normalizeContextUsage(
|
|
|
364
364
|
};
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/**
|
|
368
|
+
* @brief Resolves the selected model max input context tokens from one normalized context-usage snapshot.
|
|
369
|
+
* @details Accepts only finite positive `contextWindow` numbers so missing, non-numeric, and non-positive runtime values degrade to `undefined`, which every context-occupancy consumer treats as the documented unknown basis. Runtime is O(1). No external state is mutated.
|
|
370
|
+
* @param[in] contextUsage {ContextUsage | undefined} Normalized context-usage snapshot from the status controller state.
|
|
371
|
+
* @return {number | undefined} Selected model max input context tokens, or undefined when unknown.
|
|
372
|
+
* @satisfies REQ-401
|
|
373
|
+
*/
|
|
374
|
+
export function resolveModelContextWindowTokens(
|
|
375
|
+
contextUsage: ContextUsage | undefined,
|
|
376
|
+
): number | undefined {
|
|
377
|
+
const contextWindow = contextUsage?.contextWindow;
|
|
378
|
+
return typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0
|
|
379
|
+
? contextWindow
|
|
380
|
+
: undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
367
383
|
/**
|
|
368
384
|
* @brief Refreshes the stored context-usage snapshot from the active extension context.
|
|
369
385
|
* @details Calls `ctx.getContextUsage()` on every intercepted event so the
|
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
|
|
10
10
|
import { ReqError } from "./errors.js";
|
|
11
|
+
import { enforceContextOccupancyLimit, measureContextFileSizes } from "./context-file-size.js";
|
|
11
12
|
import { classifyPiNotifyOutcome, type PiNotifyOutcome } from "./pi-notify.js";
|
|
12
13
|
import {
|
|
13
14
|
normalizeGitWorktreePrefix,
|
|
@@ -1695,7 +1696,7 @@ export function validatePromptRequiredDocs(
|
|
|
1695
1696
|
|
|
1696
1697
|
/**
|
|
1697
1698
|
* @brief Prepares prompt-command execution for one bundled prompt.
|
|
1698
|
-
* @details Runs slash-command-owned git validation, enforces the prompt-specific required-doc matrix, resolves persisted origin and execution session files, applies the effective worktree policy, generates and verifies a dedicated worktree when enabled, and returns the execution plan consumed by prompt rendering plus lifecycle hooks. Worktree-backed execution reuses the active session directory for the forked session file. Runtime is dominated by git subprocesses, worktree creation, and optional session-file cloning. Side effects include worktree creation, session-file creation, filesystem reads, and optional prompt debug-log writes.
|
|
1699
|
+
* @details Runs slash-command-owned git validation, enforces the context-occupancy early check against the selected model max input context, enforces the prompt-specific required-doc matrix, resolves persisted origin and execution session files, applies the effective worktree policy, generates and verifies a dedicated worktree when enabled, and returns the execution plan consumed by prompt rendering plus lifecycle hooks. Worktree-backed execution reuses the active session directory for the forked session file. Runtime is dominated by git subprocesses, worktree creation, and optional session-file cloning. Side effects include worktree creation, session-file creation, filesystem reads, and optional prompt debug-log writes.
|
|
1699
1700
|
* @param[in] promptName {PromptCommandName} Bundled prompt identifier.
|
|
1700
1701
|
* @param[in] promptArgs {string} Raw prompt argument string.
|
|
1701
1702
|
* @param[in] projectBase {string} Absolute current project base.
|
|
@@ -1706,9 +1707,10 @@ export function validatePromptRequiredDocs(
|
|
|
1706
1707
|
* @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
|
|
1707
1708
|
* @param[in] originalModel {PromptCommandModelSelection | undefined} Active model provider plus identifier captured by the command handler for later re-application.
|
|
1708
1709
|
* @param[in] originalThinkingLevel {string | undefined} Active thinking level captured by the command handler for later re-application.
|
|
1710
|
+
* @param[in] maxContextTokens {number | undefined} Selected model max input context tokens consumed by the early occupancy check; undefined selects the documented 1,000,000-token fallback.
|
|
1709
1711
|
* @return {PromptCommandExecutionPlan} Prepared execution plan.
|
|
1710
|
-
* @throws {ReqError} Throws when repository validation, required-doc validation, worktree creation, or session preparation fails.
|
|
1711
|
-
* @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271, REQ-366
|
|
1712
|
+
* @throws {ReqError} Throws when repository validation, the context-occupancy early check, required-doc validation, worktree creation, or session preparation fails.
|
|
1713
|
+
* @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271, REQ-366, REQ-408, REQ-409, REQ-410, REQ-411
|
|
1712
1714
|
*/
|
|
1713
1715
|
export function preparePromptCommandExecution(
|
|
1714
1716
|
promptName: PromptCommandName,
|
|
@@ -1721,8 +1723,10 @@ export function preparePromptCommandExecution(
|
|
|
1721
1723
|
debugOptions?: PromptCommandDebugOptions,
|
|
1722
1724
|
originalModel?: PromptCommandModelSelection,
|
|
1723
1725
|
originalThinkingLevel?: string,
|
|
1726
|
+
maxContextTokens?: number | undefined,
|
|
1724
1727
|
): PromptCommandExecutionPlan {
|
|
1725
1728
|
const gitPath = validatePromptGitState(projectBase, config);
|
|
1729
|
+
enforceContextOccupancyLimit(config, measureContextFileSizes(projectBase, config), maxContextTokens);
|
|
1726
1730
|
const reuseCurrentSessionFile = isUsablePromptSessionFile(currentSessionFile, projectBase);
|
|
1727
1731
|
const originalSessionFile = resolvePromptSessionFile(
|
|
1728
1732
|
reuseCurrentSessionFile ? currentSessionFile : undefined,
|