pi-usereq 0.83.0 → 0.84.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 CHANGED
@@ -1,4 +1,4 @@
1
- # PI-useReq/pi-usereq (0.83.0)
1
+ # PI-useReq/pi-usereq (0.84.0)
2
2
 
3
3
  <p align="center">
4
4
  <img src="https://img.shields.io/badge/python-3.11%2B-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python 3.11+">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-usereq",
3
- "version": "0.83.0",
3
+ "version": "0.84.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Ogekuri/PI-useReq.git"
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { spawnSync, type SpawnSyncReturns } from "node:child_process";
8
+ import fs from "node:fs";
8
9
  import path from "node:path";
9
10
  import type { UseReqConfig } from "./config.js";
10
11
  import { ReqError } from "./errors.js";
@@ -34,6 +35,22 @@ export interface ReqReferencesCommandPlan {
34
35
  commitMessage: string;
35
36
  }
36
37
 
38
+ /**
39
+ * @brief Describes the per-operation outcome facts of one completed `req-references` run.
40
+ * @details Reports whether the regenerated `REFERENCES.md` content differs from the pre-run file and whether the guarded commit created a commit or was skipped, so the extension command handler can emit notifications that conform to the operations actually performed regardless of the update and commit outcomes. The interface is compile-time only and introduces no runtime cost.
41
+ * @satisfies REQ-436
42
+ */
43
+ export interface ReqReferencesCommandExecutionResult {
44
+ /** Absolute path of the regenerated `REFERENCES.md` document. */
45
+ referencesPath: string;
46
+ /** `true` when the regenerated content differs from the pre-run file content or the file was absent. */
47
+ referencesChanged: boolean;
48
+ /** Fixed commit message that a created commit carries. */
49
+ commitMessage: string;
50
+ /** `true` when the guarded commit created a commit; `false` when it was skipped because content matched `HEAD`. */
51
+ commitCreated: boolean;
52
+ }
53
+
37
54
  /**
38
55
  * @brief Executes one synchronous subprocess and captures UTF-8 output.
39
56
  * @details Delegates to `spawnSync(...)`, preserves the supplied working directory, and returns the raw result so callers can interpret git exit status plus diagnostics deterministically. Runtime is dominated by external process execution. Side effects include subprocess creation.
@@ -135,17 +152,17 @@ function hasStagedChangesForPaths(gitRoot: string, targetPaths: string[]): boole
135
152
 
136
153
  /**
137
154
  * @brief Executes one guarded `git commit` invocation for the staged target paths.
138
- * @details Runs a staged-changes precheck against the cached index and returns without creating a commit when no staged difference exists for the target paths, preventing empty-commit failures such as `nothing to commit, working tree clean`. When a staged difference exists, delegates to `git commit -m <commitMessage>` and converts any non-zero result into a deterministic `ReqError`. Runtime is dominated by up to two git subprocesses. Side effects include subprocess creation and conditional commit creation.
155
+ * @details Runs a staged-changes precheck against the cached index and returns `false` without creating a commit when no staged difference exists for the target paths, preventing empty-commit failures such as `nothing to commit, working tree clean`. When a staged difference exists, delegates to `git commit -m <commitMessage>`, converts any non-zero result into a deterministic `ReqError`, and returns `true`. Runtime is dominated by up to two git subprocesses. Side effects include subprocess creation and conditional commit creation.
139
156
  * @param[in] gitRoot {string} Absolute git root path.
140
157
  * @param[in] targetPaths {string[]} Git-add target paths inspected by the staged-changes precheck.
141
158
  * @param[in] commitMessage {string} Commit message used when a staged difference exists.
142
- * @return {void} No return value.
159
+ * @return {boolean} `true` when the commit was created; `false` when it was skipped because the cached index held no staged difference for the target paths.
143
160
  * @throws {ReqError} Throws when staged-difference inspection or commit creation fails.
144
161
  * @satisfies REQ-357, REQ-358
145
162
  */
146
- function runGuardedGitCommit(gitRoot: string, targetPaths: string[], commitMessage: string): void {
163
+ function runGuardedGitCommit(gitRoot: string, targetPaths: string[], commitMessage: string): boolean {
147
164
  if (!hasStagedChangesForPaths(gitRoot, targetPaths)) {
148
- return;
165
+ return false;
149
166
  }
150
167
  const commitResult = runCapture(["git", "commit", "-m", commitMessage], gitRoot);
151
168
  if (commitResult.error || commitResult.status !== 0) {
@@ -155,6 +172,7 @@ function runGuardedGitCommit(gitRoot: string, targetPaths: string[], commitMessa
155
172
  || "unknown error";
156
173
  throw new ReqError(`ERROR: git commit failed: ${diagnostic}`, 1);
157
174
  }
175
+ return true;
158
176
  }
159
177
 
160
178
  /**
@@ -182,28 +200,50 @@ export function prepareReqReferencesCommandExecution(
182
200
  }
183
201
 
184
202
  /**
185
- * @brief Executes the specialized `req-references` direct-write workflow.
186
- * @details Regenerates `REFERENCES.md` through the same source-summary path used by the `references` tool, stages only the target file, creates the fixed-message commit through the guarded commit helper whenever a staged difference exists, and verifies that no residual git-status rows remain after ignored extension-owned debug artifacts are filtered out. Runtime is dominated by summary generation plus two to four git subprocesses. Side effects include documentation writes, index mutation, conditional commit creation, and subprocess creation.
203
+ * @brief Executes the specialized `req-references` direct-write workflow and reports per-operation outcome facts.
204
+ * @details Snapshots the pre-run `REFERENCES.md` content, regenerates the document through the same source-summary path used by the `references` tool, compares the regenerated content against the pre-run snapshot, stages only the target file, creates the fixed-message commit through the guarded commit helper whenever a staged difference exists, verifies that no residual git-status rows remain after ignored extension-owned debug artifacts are filtered out, and returns whether the references content changed and whether the commit was created or skipped so callers can emit outcome-conformant notifications. Runtime is dominated by summary generation plus two to four git subprocesses. Side effects include documentation writes, index mutation, conditional commit creation, and subprocess creation.
187
205
  * @param[in] plan {ReqReferencesCommandPlan} Prepared direct-write execution plan.
188
206
  * @param[in] config {UseReqConfig} Effective project configuration.
189
- * @return {void} No return value.
190
- * @throws {ReqError} Throws when reference generation, staging, guarded commit creation, or cleanliness verification fails.
191
- * @satisfies REQ-300, REQ-301, REQ-302, REQ-303, REQ-357, REQ-358
207
+ * @return {ReqReferencesCommandExecutionResult} Per-operation outcome facts including `referencesChanged` and `commitCreated`.
208
+ * @throws {ReqError} Throws when reference generation, post-run content readback, staging, guarded commit creation, or cleanliness verification fails.
209
+ * @satisfies REQ-300, REQ-301, REQ-302, REQ-303, REQ-357, REQ-358, REQ-436
192
210
  */
193
211
  export function executeReqReferencesCommandExecution(
194
212
  plan: ReqReferencesCommandPlan,
195
213
  config: UseReqConfig,
196
- ): void {
214
+ ): ReqReferencesCommandExecutionResult {
215
+ let previousReferencesContent: string | undefined;
216
+ try {
217
+ previousReferencesContent = fs.readFileSync(plan.referencesPath, "utf8");
218
+ } catch {
219
+ previousReferencesContent = undefined;
220
+ }
197
221
  runReferences(plan.basePath, config);
222
+ let regeneratedReferencesContent: string | undefined;
223
+ try {
224
+ regeneratedReferencesContent = fs.readFileSync(plan.referencesPath, "utf8");
225
+ } catch {
226
+ regeneratedReferencesContent = undefined;
227
+ }
228
+ if (regeneratedReferencesContent === undefined) {
229
+ throw new ReqError(`ERROR: Unable to read the regenerated REFERENCES.md document at ${plan.referencesPath}.`, 1);
230
+ }
231
+ const referencesChanged = regeneratedReferencesContent !== previousReferencesContent;
198
232
  const addTargetPath = getGitAddTargetPath(plan.gitPath, plan.referencesPath);
199
233
  const addResult = runCapture(["git", "add", "--", addTargetPath], plan.gitPath);
200
234
  if (addResult.error || addResult.status !== 0) {
201
235
  const diagnostic = addResult.stderr.trim() || addResult.error?.message || "unknown error";
202
236
  throw new ReqError(`ERROR: git add failed for ${addTargetPath}: ${diagnostic}`, 1);
203
237
  }
204
- runGuardedGitCommit(plan.gitPath, [addTargetPath], plan.commitMessage);
238
+ const commitCreated = runGuardedGitCommit(plan.gitPath, [addTargetPath], plan.commitMessage);
205
239
  const residualStatusLines = listResidualGitStatusLines(plan.basePath, plan.gitPath, config);
206
240
  if (residualStatusLines.length > 0) {
207
241
  throw new ReqError("ERROR: Git repository is not clean after req-references commit.", 1);
208
242
  }
243
+ return {
244
+ referencesPath: plan.referencesPath,
245
+ referencesChanged,
246
+ commitMessage: plan.commitMessage,
247
+ commitCreated,
248
+ };
209
249
  }
package/src/index.ts CHANGED
@@ -3480,11 +3480,11 @@ function registerReqResetCommand(
3480
3480
 
3481
3481
  /**
3482
3482
  * @brief Registers the specialized `req-references` slash command.
3483
- * @details Registers the non-agentic references-maintenance command that rejects non-`idle` invocations by transitioning workflow state to `error` before direct execution, otherwise reuses slash-command-owned git validation, transitions workflow state through `checking|running|idle`, regenerates `REFERENCES.md` directly from configured source directories, stages only the generated file, creates the fixed-message git commit, verifies repository cleanliness, and notifies pi without starting an LLM session or creating a worktree. Runtime is dominated by git subprocess execution plus source-summary generation. Side effects include command registration, status-controller mutation, filesystem writes, git index/history mutation, and user notifications.
3483
+ * @details Registers the non-agentic references-maintenance command that rejects non-`idle` invocations by transitioning workflow state to `error` before direct execution, otherwise reuses slash-command-owned git validation, transitions workflow state through `checking|running|idle`, regenerates `REFERENCES.md` directly from configured source directories, stages only the generated file, creates the fixed-message git commit, verifies repository cleanliness, and emits execution notifications that conform to the operations actually performed — one stating whether the references file was updated or already matched the generated content and one stating whether the fixed-message commit was created or skipped — regardless of the update and commit outcomes, without starting an LLM session or creating a worktree. Runtime is dominated by git subprocess execution plus source-summary generation. Side effects include command registration, status-controller mutation, filesystem writes, git index/history mutation, and user notifications.
3484
3484
  * @param[in] pi {ExtensionAPI} Active extension API instance.
3485
3485
  * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
3486
3486
  * @return {void} No return value.
3487
- * @satisfies REQ-200, REQ-221, REQ-224, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303
3487
+ * @satisfies REQ-200, REQ-221, REQ-224, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303, REQ-437, REQ-438
3488
3488
  */
3489
3489
  function registerReqReferencesCommand(
3490
3490
  pi: ExtensionAPI,
@@ -3510,10 +3510,25 @@ function registerReqReferencesCommand(
3510
3510
  try {
3511
3511
  const executionPlan = prepareReqReferencesCommandExecution(projectBase, config);
3512
3512
  setPiUsereqWorkflowState(statusController, "running", ctx);
3513
- executeReqReferencesCommandExecution(executionPlan, config);
3513
+ const executionResult = executeReqReferencesCommandExecution(executionPlan, config);
3514
3514
  setPiUsereqWorkflowState(statusController, "idle", ctx);
3515
+ const referencesDisplayPath = formatRuntimePathForDisplay(executionResult.referencesPath);
3515
3516
  ctx.ui.notify(
3516
- `SUCCESS: Updated ${formatRuntimePathForDisplay(executionPlan.referencesPath)} and committed changes.`,
3517
+ executionResult.referencesChanged
3518
+ ? `req-references: Updated ${referencesDisplayPath}.`
3519
+ : `req-references: ${referencesDisplayPath} already matches the generated references content; no file update was needed.`,
3520
+ "info",
3521
+ );
3522
+ ctx.ui.notify(
3523
+ executionResult.commitCreated
3524
+ ? `req-references: Created commit ${executionResult.commitMessage}`
3525
+ : "req-references: Skipped the git commit because the references content already matched HEAD.",
3526
+ "info",
3527
+ );
3528
+ ctx.ui.notify(
3529
+ executionResult.referencesChanged && executionResult.commitCreated
3530
+ ? `SUCCESS: Updated ${referencesDisplayPath} and committed changes.`
3531
+ : `SUCCESS: req-references completed with a clean repository (${executionResult.referencesChanged ? "references updated" : "references unchanged"}; ${executionResult.commitCreated ? "commit created" : "commit skipped"}).`,
3517
3532
  "info",
3518
3533
  );
3519
3534
  notifyContextRetentionReminder(statusController, ctx);