pi-usereq 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +6 -6
- package/package.json +1 -1
- package/pi-usereq/docs/REFERENCES.md +818 -691
- package/pi-usereq/docs/REQUIREMENTS.md +131 -77
- package/pi-usereq/docs/WORKFLOW.md +185 -51
- package/scripts/lib/extension-debug-harness.ts +2 -2
- package/scripts/tool-args-to-params.ts +2 -2
- package/src/cli.ts +12 -12
- package/src/core/extension-status.ts +69 -12
- package/src/core/pi-notify.ts +5 -5
- package/src/core/pi-usereq-tools.ts +4 -2
- package/src/core/prompt-command-catalog.ts +4 -5
- package/src/core/prompt-command-runtime.ts +183 -44
- package/src/core/prompts.ts +0 -2
- package/src/core/req-references-command.ts +175 -0
- package/src/core/req-reset-command.ts +323 -0
- package/src/core/resources.ts +6 -23
- package/src/core/settings-menu.ts +85 -28
- package/src/core/tool-runner.ts +26 -6
- package/src/index.ts +523 -85
- package/tests/attended-results-scenarios.ts +5 -5
- package/tests/cli-command-option-parity.test.ts +25 -25
- package/tests/debug-extension-harness.test.ts +1 -1
- package/tests/extension-registration.test.ts +1029 -82
- package/tests/oracle-project.test.ts +4 -4
- package/tests/oracle-standalone.test.ts +5 -5
- package/src/core/reference-payload.ts +0 -752
- package/src/resources/prompts/references.md +0 -64
- /package/tests/fixtures_attended_results/project/{references.json → summarize.json} +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_c.c.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_cpp.cpp.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_csharp.cs.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_elixir.ex.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_go.go.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_haskell.hs.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_java.java.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_javascript.js.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_kotlin.kt.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_lua.lua.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_perl.pl.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_php.php.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_python.py.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_ruby.rb.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_rust.rs.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_scala.scala.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_shell.sh.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_swift.swift.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_typescript.ts.json +0 -0
- /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_zig.zig.json +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
|
-
* @brief Implements prompt-command preflight and worktree orchestration.
|
|
4
|
-
* @details Centralizes `req-<prompt>` repository validation, prompt-specific required-document checks, slash-command-owned worktree naming and lifecycle handling,
|
|
3
|
+
* @brief Implements bundled prompt-command preflight and worktree orchestration.
|
|
4
|
+
* @details Centralizes prompt-template-backed `req-<prompt>` repository validation, prompt-specific required-document checks, slash-command-owned worktree naming and lifecycle handling, reusable transcript-preservation plus session-restoration helpers, persisted replacement-session context reuse for non-command lifecycle handlers, matched-success stash-assisted fast-forward merge finalization, and command-side abort cleanup. Runtime is dominated by git subprocess execution plus bounded filesystem and session-file metadata checks. Side effects include active-session replacement, worktree creation and deletion, branch merges, stash-stack mutation, and filesystem reads and writes.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs";
|
|
@@ -19,10 +19,7 @@ import {
|
|
|
19
19
|
logDebugPromptWorkflowEvent,
|
|
20
20
|
type DebugWorkflowState,
|
|
21
21
|
} from "./debug-runtime.js";
|
|
22
|
-
import {
|
|
23
|
-
PROMPT_COMMAND_NAMES,
|
|
24
|
-
type PromptCommandName,
|
|
25
|
-
} from "./prompt-command-catalog.js";
|
|
22
|
+
import { type PromptCommandName } from "./prompt-command-catalog.js";
|
|
26
23
|
import {
|
|
27
24
|
isSameOrAncestorPath,
|
|
28
25
|
normalizeRelativeDirContract,
|
|
@@ -483,9 +480,9 @@ function readPromptSessionJsonLines(
|
|
|
483
480
|
* @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan whose original and execution session files must be synchronized.
|
|
484
481
|
* @return {void} No return value.
|
|
485
482
|
* @throws {ReqError} Throws when either session file is unreadable or when appended execution records are not persisted to the original session file.
|
|
486
|
-
* @satisfies REQ-208
|
|
483
|
+
* @satisfies REQ-208, REQ-307
|
|
487
484
|
*/
|
|
488
|
-
function preservePromptCommandExecutionTranscript(plan: PromptCommandExecutionPlan): void {
|
|
485
|
+
export function preservePromptCommandExecutionTranscript(plan: PromptCommandExecutionPlan): void {
|
|
489
486
|
const normalizedOriginalSessionFile = path.resolve(plan.originalSessionFile);
|
|
490
487
|
const normalizedExecutionSessionFile = path.resolve(plan.executionSessionFile);
|
|
491
488
|
if (normalizedOriginalSessionFile === normalizedExecutionSessionFile) {
|
|
@@ -829,9 +826,6 @@ const PROMPT_REQUIRED_DOCS: Record<PromptCommandName, readonly PromptRequiredDoc
|
|
|
829
826
|
{ fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
|
|
830
827
|
{ fileName: "REFERENCES.md", promptCommand: "/req-references" },
|
|
831
828
|
],
|
|
832
|
-
references: [
|
|
833
|
-
{ fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
|
|
834
|
-
],
|
|
835
829
|
renumber: [
|
|
836
830
|
{ fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
|
|
837
831
|
{ fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
|
|
@@ -855,6 +849,166 @@ function runCapture(command: string[], cwd: string): ReturnType<typeof spawnSync
|
|
|
855
849
|
});
|
|
856
850
|
}
|
|
857
851
|
|
|
852
|
+
/**
|
|
853
|
+
* @brief Lists tracked `base-path` status rows that require stash-assisted merge handling.
|
|
854
|
+
* @details Executes `git status --porcelain`, retains only tracked rows whose index or worktree slot reports a change, and excludes untracked or ignored rows because the required `git stash` command does not preserve them. Runtime is dominated by one git subprocess plus O(n) parsing in status-line count. Side effects include process spawning.
|
|
855
|
+
* @param[in] basePath {string} Restored project base path.
|
|
856
|
+
* @return {string[]} Tracked status rows requiring stash-assisted merge handling.
|
|
857
|
+
* @throws {ReqError} Throws when git status cannot be read from `basePath`.
|
|
858
|
+
* @satisfies REQ-291
|
|
859
|
+
*/
|
|
860
|
+
function listPromptTrackedBasePathChanges(basePath: string): string[] {
|
|
861
|
+
const statusResult = runCapture(["git", "status", "--porcelain"], basePath);
|
|
862
|
+
if (statusResult.error || statusResult.status !== 0) {
|
|
863
|
+
throw new ReqError("ERROR: Unable to inspect base-path changes before merge.", 1);
|
|
864
|
+
}
|
|
865
|
+
return statusResult.stdout
|
|
866
|
+
.split(/\r?\n/)
|
|
867
|
+
.map((line) => line.trimEnd())
|
|
868
|
+
.filter((line) => line !== "")
|
|
869
|
+
.filter((line) => {
|
|
870
|
+
const statusCode = line.slice(0, 2);
|
|
871
|
+
if (statusCode === "??" || statusCode === "!!") {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
const indexStatus = statusCode[0] ?? " ";
|
|
875
|
+
const worktreeStatus = statusCode[1] ?? " ";
|
|
876
|
+
return indexStatus !== " " || worktreeStatus !== " ";
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* @brief Executes the successful-closure merge sequence from restored `base-path`.
|
|
882
|
+
* @details Detects tracked staged or unstaged `base-path` changes, wraps the existing fast-forward merge in `git stash` and `git stash pop` when required, preserves the direct merge path when no tracked changes exist, emits a warning-only result after successful local-change restoration, and writes one merge-finalization debug entry when enabled. Runtime is dominated by up to four git subprocesses plus O(n) status parsing. Side effects include stash-stack mutation, branch merge attempts, and optional debug-log writes.
|
|
883
|
+
* @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan whose branch should be merged.
|
|
884
|
+
* @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
|
|
885
|
+
* @return {{ mergeAttempted: boolean; mergeSucceeded: boolean; errorMessage?: string; warningMessage?: string }} Merge-attempt facts plus optional warning text.
|
|
886
|
+
* @satisfies REQ-208, REQ-245, REQ-291, REQ-292
|
|
887
|
+
*/
|
|
888
|
+
function finalizePromptCommandMerge(
|
|
889
|
+
plan: PromptCommandExecutionPlan,
|
|
890
|
+
debugOptions?: PromptCommandDebugOptions,
|
|
891
|
+
): {
|
|
892
|
+
mergeAttempted: boolean;
|
|
893
|
+
mergeSucceeded: boolean;
|
|
894
|
+
errorMessage?: string;
|
|
895
|
+
warningMessage?: string;
|
|
896
|
+
} {
|
|
897
|
+
const worktreeDir = plan.worktreeDir ?? plan.branchName;
|
|
898
|
+
const mergeLogInput = {
|
|
899
|
+
worktree_dir: worktreeDir,
|
|
900
|
+
branch_name: plan.branchName,
|
|
901
|
+
};
|
|
902
|
+
let trackedStatusLines: string[];
|
|
903
|
+
try {
|
|
904
|
+
trackedStatusLines = listPromptTrackedBasePathChanges(plan.basePath);
|
|
905
|
+
} catch (error) {
|
|
906
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
907
|
+
if (debugOptions) {
|
|
908
|
+
logDebugPromptEvent(
|
|
909
|
+
plan.basePath,
|
|
910
|
+
debugOptions.config,
|
|
911
|
+
debugOptions.workflowState,
|
|
912
|
+
plan.promptName,
|
|
913
|
+
"merge",
|
|
914
|
+
mergeLogInput,
|
|
915
|
+
{
|
|
916
|
+
success: false,
|
|
917
|
+
merge_attempted: false,
|
|
918
|
+
error: errorMessage,
|
|
919
|
+
},
|
|
920
|
+
true,
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
mergeAttempted: false,
|
|
925
|
+
mergeSucceeded: false,
|
|
926
|
+
errorMessage,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
const usedStash = trackedStatusLines.length > 0;
|
|
930
|
+
let stashStatus: number | null | undefined;
|
|
931
|
+
if (usedStash) {
|
|
932
|
+
const stashResult = runCapture(["git", "stash"], plan.basePath);
|
|
933
|
+
stashStatus = stashResult.status;
|
|
934
|
+
if (stashResult.error || stashResult.status !== 0) {
|
|
935
|
+
const errorMessage = `ERROR: Unable to stash base-path changes before merge for worktree ${worktreeDir}.`;
|
|
936
|
+
if (debugOptions) {
|
|
937
|
+
logDebugPromptEvent(
|
|
938
|
+
plan.basePath,
|
|
939
|
+
debugOptions.config,
|
|
940
|
+
debugOptions.workflowState,
|
|
941
|
+
plan.promptName,
|
|
942
|
+
"merge",
|
|
943
|
+
{
|
|
944
|
+
...mergeLogInput,
|
|
945
|
+
used_stash: true,
|
|
946
|
+
tracked_change_count: trackedStatusLines.length,
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
success: false,
|
|
950
|
+
merge_attempted: false,
|
|
951
|
+
error: errorMessage,
|
|
952
|
+
stash_status: stashStatus,
|
|
953
|
+
},
|
|
954
|
+
true,
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
mergeAttempted: false,
|
|
959
|
+
mergeSucceeded: false,
|
|
960
|
+
errorMessage,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const mergeResult = runCapture(
|
|
965
|
+
["git", "merge", "--ff-only", plan.branchName],
|
|
966
|
+
plan.basePath,
|
|
967
|
+
);
|
|
968
|
+
const mergeSucceeded = !mergeResult.error && mergeResult.status === 0;
|
|
969
|
+
const errorMessage = mergeSucceeded
|
|
970
|
+
? undefined
|
|
971
|
+
: `ERROR: Fast-forward merge failed for worktree ${worktreeDir}.`;
|
|
972
|
+
let stashPopStatus: number | null | undefined;
|
|
973
|
+
let warningMessage: string | undefined;
|
|
974
|
+
if (usedStash) {
|
|
975
|
+
const stashPopResult = runCapture(["git", "stash", "pop"], plan.basePath);
|
|
976
|
+
stashPopStatus = stashPopResult.status;
|
|
977
|
+
if (mergeSucceeded) {
|
|
978
|
+
warningMessage = "WARNING: Restored base-path changes after merge; base-path is not clean.";
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (debugOptions) {
|
|
982
|
+
logDebugPromptEvent(
|
|
983
|
+
plan.basePath,
|
|
984
|
+
debugOptions.config,
|
|
985
|
+
debugOptions.workflowState,
|
|
986
|
+
plan.promptName,
|
|
987
|
+
"merge",
|
|
988
|
+
{
|
|
989
|
+
...mergeLogInput,
|
|
990
|
+
used_stash: usedStash,
|
|
991
|
+
tracked_change_count: trackedStatusLines.length,
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
success: mergeSucceeded,
|
|
995
|
+
merge_attempted: true,
|
|
996
|
+
error: errorMessage,
|
|
997
|
+
warning: warningMessage,
|
|
998
|
+
stash_status: stashStatus,
|
|
999
|
+
stash_pop_status: stashPopStatus,
|
|
1000
|
+
},
|
|
1001
|
+
!mergeSucceeded,
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
mergeAttempted: true,
|
|
1006
|
+
mergeSucceeded,
|
|
1007
|
+
errorMessage,
|
|
1008
|
+
warningMessage,
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
858
1012
|
/**
|
|
859
1013
|
* @brief Stores or clears the prompt-command post-create test hook.
|
|
860
1014
|
* @details Enables deterministic simulation of post-create worktree verification failures without altering production control flow. Runtime is O(1). Side effect: mutates module-local test state.
|
|
@@ -950,15 +1104,15 @@ function throwPromptGitStatusError(): never {
|
|
|
950
1104
|
}
|
|
951
1105
|
|
|
952
1106
|
/**
|
|
953
|
-
* @brief Runs
|
|
954
|
-
* @details Validates work-tree membership, porcelain cleanliness, and symbolic or detached `HEAD` presence without invoking extension custom-tool executors. Runtime is dominated by git subprocess execution. Side effects include process spawning.
|
|
1107
|
+
* @brief Runs slash-command-owned git validation and returns the runtime git root.
|
|
1108
|
+
* @details Validates work-tree membership, porcelain cleanliness, and symbolic or detached `HEAD` presence for bundled prompt commands and `req-references` without invoking extension custom-tool executors. Runtime is dominated by git subprocess execution. Side effects include process spawning.
|
|
955
1109
|
* @param[in] projectBase {string} Absolute current project base.
|
|
956
1110
|
* @param[in] config {UseReqConfig | undefined} Optional effective project configuration used to ignore extension-owned debug-log artifacts.
|
|
957
1111
|
* @return {string} Absolute runtime git root.
|
|
958
1112
|
* @throws {ReqError} Throws the canonical prompt-command git-preflight error on any validation failure.
|
|
959
1113
|
* @satisfies REQ-200, REQ-220
|
|
960
1114
|
*/
|
|
961
|
-
function validatePromptGitState(projectBase: string, config?: UseReqConfig): string {
|
|
1115
|
+
export function validatePromptGitState(projectBase: string, config?: UseReqConfig): string {
|
|
962
1116
|
const gitPath = resolveRuntimeGitPath(projectBase);
|
|
963
1117
|
if (!gitPath) {
|
|
964
1118
|
throwPromptGitStatusError();
|
|
@@ -1280,9 +1434,9 @@ function createPromptWorktree(
|
|
|
1280
1434
|
* @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
|
|
1281
1435
|
* @return {void} No return value.
|
|
1282
1436
|
* @throws {ReqError} Throws when cleanup cannot remove the worktree and branch fully.
|
|
1283
|
-
* @satisfies REQ-208, REQ-220, REQ-245
|
|
1437
|
+
* @satisfies REQ-208, REQ-220, REQ-245, REQ-309
|
|
1284
1438
|
*/
|
|
1285
|
-
function deletePromptWorktree(
|
|
1439
|
+
export function deletePromptWorktree(
|
|
1286
1440
|
basePath: string,
|
|
1287
1441
|
worktreeDir: string,
|
|
1288
1442
|
worktreeRootPath: string,
|
|
@@ -1691,12 +1845,12 @@ export async function abortPromptCommandExecution(
|
|
|
1691
1845
|
|
|
1692
1846
|
/**
|
|
1693
1847
|
* @brief Finalizes one matched successful worktree-backed prompt execution.
|
|
1694
|
-
* @details Re-verifies persisted execution-session metadata plus worktree artifacts, copies any execution-session transcript records missing from the original session file, restores the original session-backed `base-path`, fast-forward
|
|
1848
|
+
* @details Re-verifies persisted execution-session metadata plus worktree artifacts, copies any execution-session transcript records missing from the original session file, restores the original session-backed `base-path`, executes the stash-assisted fast-forward merge sequence from `base-path`, deletes the worktree after merge success, and preserves the restored base session across closure failures. Closure intentionally treats `base-path` restoration as authoritative even when pi CLI has already started end-of-session session replacement or other housekeeping that moved the live runtime away from `worktree-path`. Runtime is dominated by session switching plus git subprocess execution. Side effects include session-file appends, active-session replacement, branch merges, stash-stack mutation, worktree deletion, and optional debug-log writes.
|
|
1695
1849
|
* @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan.
|
|
1696
1850
|
* @param[in] ctx {PromptCommandSessionContext | undefined} Optional prompt-command context.
|
|
1697
1851
|
* @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
|
|
1698
|
-
* @return {Promise<{ mergeAttempted: boolean; mergeSucceeded: boolean; cleanupSucceeded: boolean; errorMessage?: string; activeContext?: PromptCommandSessionContext }>} Finalization facts plus the last valid active prompt-command context.
|
|
1699
|
-
* @satisfies REQ-208, REQ-209, REQ-220, REQ-245, REQ-282
|
|
1852
|
+
* @return {Promise<{ mergeAttempted: boolean; mergeSucceeded: boolean; cleanupSucceeded: boolean; errorMessage?: string; warningMessage?: string; activeContext?: PromptCommandSessionContext }>} Finalization facts plus the last valid active prompt-command context.
|
|
1853
|
+
* @satisfies REQ-208, REQ-209, REQ-220, REQ-245, REQ-282, REQ-291, REQ-292
|
|
1700
1854
|
*/
|
|
1701
1855
|
export async function finalizePromptCommandExecution(
|
|
1702
1856
|
plan: PromptCommandExecutionPlan,
|
|
@@ -1707,6 +1861,7 @@ export async function finalizePromptCommandExecution(
|
|
|
1707
1861
|
mergeSucceeded: boolean;
|
|
1708
1862
|
cleanupSucceeded: boolean;
|
|
1709
1863
|
errorMessage?: string;
|
|
1864
|
+
warningMessage?: string;
|
|
1710
1865
|
activeContext?: PromptCommandSessionContext;
|
|
1711
1866
|
}> {
|
|
1712
1867
|
let activeContext = ctx;
|
|
@@ -1749,32 +1904,14 @@ export async function finalizePromptCommandExecution(
|
|
|
1749
1904
|
activeContext,
|
|
1750
1905
|
};
|
|
1751
1906
|
}
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
plan.basePath,
|
|
1755
|
-
);
|
|
1756
|
-
const mergeSucceeded = !mergeResult.error && mergeResult.status === 0;
|
|
1757
|
-
const errorMessage = mergeSucceeded
|
|
1758
|
-
? undefined
|
|
1759
|
-
: `ERROR: Fast-forward merge failed for worktree ${plan.worktreeDir}.`;
|
|
1760
|
-
if (debugOptions) {
|
|
1761
|
-
logDebugPromptEvent(
|
|
1762
|
-
plan.basePath,
|
|
1763
|
-
debugOptions.config,
|
|
1764
|
-
debugOptions.workflowState,
|
|
1765
|
-
plan.promptName,
|
|
1766
|
-
"merge",
|
|
1767
|
-
{ worktree_dir: plan.worktreeDir, branch_name: plan.branchName },
|
|
1768
|
-
{ success: mergeSucceeded, error: errorMessage },
|
|
1769
|
-
!mergeSucceeded,
|
|
1770
|
-
);
|
|
1771
|
-
}
|
|
1772
|
-
if (!mergeSucceeded) {
|
|
1907
|
+
const mergeFinalization = finalizePromptCommandMerge(plan, debugOptions);
|
|
1908
|
+
if (!mergeFinalization.mergeSucceeded) {
|
|
1773
1909
|
return {
|
|
1774
|
-
mergeAttempted:
|
|
1910
|
+
mergeAttempted: mergeFinalization.mergeAttempted,
|
|
1775
1911
|
mergeSucceeded: false,
|
|
1776
1912
|
cleanupSucceeded: true,
|
|
1777
|
-
errorMessage,
|
|
1913
|
+
errorMessage: mergeFinalization.errorMessage,
|
|
1914
|
+
warningMessage: mergeFinalization.warningMessage,
|
|
1778
1915
|
activeContext,
|
|
1779
1916
|
};
|
|
1780
1917
|
}
|
|
@@ -1788,17 +1925,19 @@ export async function finalizePromptCommandExecution(
|
|
|
1788
1925
|
);
|
|
1789
1926
|
} catch {
|
|
1790
1927
|
return {
|
|
1791
|
-
mergeAttempted:
|
|
1928
|
+
mergeAttempted: mergeFinalization.mergeAttempted,
|
|
1792
1929
|
mergeSucceeded: true,
|
|
1793
1930
|
cleanupSucceeded: false,
|
|
1794
1931
|
errorMessage: `ERROR: Unable to remove worktree or branch ${plan.worktreeDir}.`,
|
|
1932
|
+
warningMessage: mergeFinalization.warningMessage,
|
|
1795
1933
|
activeContext,
|
|
1796
1934
|
};
|
|
1797
1935
|
}
|
|
1798
1936
|
return {
|
|
1799
|
-
mergeAttempted:
|
|
1937
|
+
mergeAttempted: mergeFinalization.mergeAttempted,
|
|
1800
1938
|
mergeSucceeded: true,
|
|
1801
1939
|
cleanupSucceeded: true,
|
|
1940
|
+
warningMessage: mergeFinalization.warningMessage,
|
|
1802
1941
|
activeContext,
|
|
1803
1942
|
};
|
|
1804
1943
|
}
|
package/src/core/prompts.ts
CHANGED
|
@@ -22,14 +22,12 @@ import { readBundledInstruction, readBundledPrompt } from "./resources.js";
|
|
|
22
22
|
const TOOL_REFERENCE_REPLACEMENTS: Array<[RegExp, string]> = [
|
|
23
23
|
[/`req --find`/g, "`search` tool"],
|
|
24
24
|
[/`req --files-find`/g, "`files-search` tool"],
|
|
25
|
-
[/`req --references`/g, "`references` tool"],
|
|
26
25
|
[/`req --compress`/g, "`compress` tool"],
|
|
27
26
|
[/`req --tokens`/g, "`tokens` tool"],
|
|
28
27
|
[/`req --static-check`/g, "`static-check` tool"],
|
|
29
28
|
[/`req --files-static-check`/g, "`files-static-check` tool"],
|
|
30
29
|
[/\breq --find\b/g, "search tool"],
|
|
31
30
|
[/\breq --files-find\b/g, "files-search tool"],
|
|
32
|
-
[/\breq --references\b/g, "references tool"],
|
|
33
31
|
[/\breq --compress\b/g, "compress tool"],
|
|
34
32
|
[/\breq --tokens\b/g, "tokens tool"],
|
|
35
33
|
[/\breq --static-check\b/g, "static-check tool"],
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @brief Implements the specialized `req-references` slash-command workflow.
|
|
4
|
+
* @details Performs slash-command-owned git validation reuse, reference-file generation, targeted staging, fixed-message commit creation, and post-commit cleanliness verification without creating a worktree or starting an LLM session. Runtime is dominated by git subprocess execution plus source-summary generation and one documentation write. Side effects include filesystem writes and git index/history mutation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import type { UseReqConfig } from "./config.js";
|
|
10
|
+
import { ReqError } from "./errors.js";
|
|
11
|
+
import { validatePromptGitState } from "./prompt-command-runtime.js";
|
|
12
|
+
import { runReferences } from "./tool-runner.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @brief Declares the fixed slash-command description for `req-references`.
|
|
16
|
+
* @details Preserves the legacy human-facing command label while the runtime implementation no longer depends on a bundled prompt Markdown file. Access complexity is O(1).
|
|
17
|
+
*/
|
|
18
|
+
export const REQ_REFERENCES_COMMAND_DESCRIPTION = "Write a REFERENCES.md using the project's source code";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @brief Declares the fixed git commit message used by `req-references`.
|
|
22
|
+
* @details Keeps the commit payload deterministic so downstream tooling and tests can assert the exact commit contract without parsing prompt templates. Access complexity is O(1).
|
|
23
|
+
*/
|
|
24
|
+
export const REQ_REFERENCES_COMMIT_MESSAGE = "docs(references): Update REFERENCES.md document. [useReq]";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @brief Describes the prepared execution facts for one `req-references` run.
|
|
28
|
+
* @details Stores the validated project base, resolved git root, target references path, and fixed commit message needed by the specialized direct-write workflow. The interface is compile-time only and introduces no runtime cost.
|
|
29
|
+
*/
|
|
30
|
+
export interface ReqReferencesCommandPlan {
|
|
31
|
+
basePath: string;
|
|
32
|
+
gitPath: string;
|
|
33
|
+
referencesPath: string;
|
|
34
|
+
commitMessage: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @brief Executes one synchronous subprocess and captures UTF-8 output.
|
|
39
|
+
* @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.
|
|
40
|
+
* @param[in] command {string[]} Executable plus argument vector.
|
|
41
|
+
* @param[in] cwd {string} Working directory for the subprocess.
|
|
42
|
+
* @return {ReturnType<typeof spawnSync>} Captured subprocess result.
|
|
43
|
+
*/
|
|
44
|
+
function runCapture(command: string[], cwd: string): ReturnType<typeof spawnSync> {
|
|
45
|
+
return spawnSync(command[0]!, command.slice(1), {
|
|
46
|
+
cwd,
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @brief Builds the set of git-status paths ignored for cleanliness checks.
|
|
53
|
+
* @details Reuses the configured debug-log path exception already honored by prompt-command git validation so extension-owned debug artifacts do not block `req-references` execution or post-commit cleanliness verification. Runtime is O(p) in path length. No external state is mutated.
|
|
54
|
+
* @param[in] projectBase {string} Absolute project base path.
|
|
55
|
+
* @param[in] gitRoot {string} Absolute git root path.
|
|
56
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
57
|
+
* @return {Set<string>} Slash-normalized relative paths ignored during git-status evaluation.
|
|
58
|
+
*/
|
|
59
|
+
function buildIgnoredGitStatusPaths(
|
|
60
|
+
projectBase: string,
|
|
61
|
+
gitRoot: string,
|
|
62
|
+
config: UseReqConfig,
|
|
63
|
+
): Set<string> {
|
|
64
|
+
const ignoredStatusPaths = new Set<string>();
|
|
65
|
+
const configuredLogPath = path.isAbsolute(config.DEBUG_LOG_FILE)
|
|
66
|
+
? path.normalize(config.DEBUG_LOG_FILE)
|
|
67
|
+
: path.resolve(projectBase, config.DEBUG_LOG_FILE);
|
|
68
|
+
const relativeLogPath = path.relative(gitRoot, configuredLogPath);
|
|
69
|
+
if (relativeLogPath !== "" && !relativeLogPath.startsWith("..") && !path.isAbsolute(relativeLogPath)) {
|
|
70
|
+
ignoredStatusPaths.add(relativeLogPath.split(path.sep).join("/"));
|
|
71
|
+
}
|
|
72
|
+
return ignoredStatusPaths;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @brief Lists residual git-status rows after ignored extension-owned paths are filtered out.
|
|
77
|
+
* @details Executes `git status --porcelain`, drops the configured debug-log path when present inside the active repository, and returns all remaining staged or unstaged rows used for post-commit cleanliness verification. Runtime is dominated by one git subprocess plus O(n) parsing in status-line count. Side effects include subprocess creation.
|
|
78
|
+
* @param[in] projectBase {string} Absolute project base path.
|
|
79
|
+
* @param[in] gitRoot {string} Absolute git root path.
|
|
80
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
81
|
+
* @return {string[]} Residual status rows after ignored paths are removed.
|
|
82
|
+
* @throws {ReqError} Throws when git status cannot be inspected.
|
|
83
|
+
*/
|
|
84
|
+
function listResidualGitStatusLines(
|
|
85
|
+
projectBase: string,
|
|
86
|
+
gitRoot: string,
|
|
87
|
+
config: UseReqConfig,
|
|
88
|
+
): string[] {
|
|
89
|
+
const statusResult = runCapture(["git", "status", "--porcelain"], gitRoot);
|
|
90
|
+
if (statusResult.error || statusResult.status !== 0) {
|
|
91
|
+
throw new ReqError("ERROR: Unable to inspect git repository cleanliness after req-references commit.", 1);
|
|
92
|
+
}
|
|
93
|
+
const ignoredStatusPaths = buildIgnoredGitStatusPaths(projectBase, gitRoot, config);
|
|
94
|
+
return statusResult.stdout
|
|
95
|
+
.split(/\r?\n/)
|
|
96
|
+
.map((line) => line.trimEnd())
|
|
97
|
+
.filter((line) => line !== "")
|
|
98
|
+
.filter((line) => {
|
|
99
|
+
const statusPath = line.slice(3).split(" -> ").at(-1)?.split(path.sep).join("/") ?? "";
|
|
100
|
+
return !ignoredStatusPaths.has(statusPath);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @brief Converts one absolute repository path into the preferred git-add target syntax.
|
|
106
|
+
* @details Emits a slash-normalized relative path when the target is inside the git root and falls back to the absolute path otherwise, preserving deterministic add semantics across nested project-base layouts. Runtime is O(p) in path length. No external state is mutated.
|
|
107
|
+
* @param[in] gitRoot {string} Absolute git root path.
|
|
108
|
+
* @param[in] absolutePath {string} Absolute path to stage.
|
|
109
|
+
* @return {string} Relative or absolute git-add target path.
|
|
110
|
+
*/
|
|
111
|
+
function getGitAddTargetPath(gitRoot: string, absolutePath: string): string {
|
|
112
|
+
const relativePath = path.relative(gitRoot, absolutePath);
|
|
113
|
+
if (relativePath === "" || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
114
|
+
return absolutePath;
|
|
115
|
+
}
|
|
116
|
+
return relativePath.split(path.sep).join("/");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @brief Prepares the specialized `req-references` execution plan.
|
|
121
|
+
* @details Reuses slash-command-owned git validation, resolves the configured references document path, and returns the fixed commit metadata consumed by the direct-write workflow. Runtime is dominated by git validation subprocesses. Side effects include subprocess creation delegated through `validatePromptGitState(...)`.
|
|
122
|
+
* @param[in] projectBase {string} Absolute project base path.
|
|
123
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
124
|
+
* @return {ReqReferencesCommandPlan} Prepared execution plan for direct references regeneration.
|
|
125
|
+
* @throws {ReqError} Throws when git validation fails.
|
|
126
|
+
* @satisfies REQ-200, REQ-299
|
|
127
|
+
*/
|
|
128
|
+
export function prepareReqReferencesCommandExecution(
|
|
129
|
+
projectBase: string,
|
|
130
|
+
config: UseReqConfig,
|
|
131
|
+
): ReqReferencesCommandPlan {
|
|
132
|
+
const basePath = path.resolve(projectBase);
|
|
133
|
+
const gitPath = validatePromptGitState(basePath, config);
|
|
134
|
+
const docsDir = config["docs-dir"].replace(/[/\\]+$/, "");
|
|
135
|
+
return {
|
|
136
|
+
basePath,
|
|
137
|
+
gitPath,
|
|
138
|
+
referencesPath: path.join(basePath, docsDir, "REFERENCES.md"),
|
|
139
|
+
commitMessage: REQ_REFERENCES_COMMIT_MESSAGE,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @brief Executes the specialized `req-references` direct-write workflow.
|
|
145
|
+
* @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, 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 three git subprocesses. Side effects include documentation writes, index mutation, commit creation, and subprocess creation.
|
|
146
|
+
* @param[in] plan {ReqReferencesCommandPlan} Prepared direct-write execution plan.
|
|
147
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
148
|
+
* @return {void} No return value.
|
|
149
|
+
* @throws {ReqError} Throws when reference generation, staging, commit creation, or cleanliness verification fails.
|
|
150
|
+
* @satisfies REQ-300, REQ-301, REQ-302, REQ-303
|
|
151
|
+
*/
|
|
152
|
+
export function executeReqReferencesCommandExecution(
|
|
153
|
+
plan: ReqReferencesCommandPlan,
|
|
154
|
+
config: UseReqConfig,
|
|
155
|
+
): void {
|
|
156
|
+
runReferences(plan.basePath, config);
|
|
157
|
+
const addTargetPath = getGitAddTargetPath(plan.gitPath, plan.referencesPath);
|
|
158
|
+
const addResult = runCapture(["git", "add", "--", addTargetPath], plan.gitPath);
|
|
159
|
+
if (addResult.error || addResult.status !== 0) {
|
|
160
|
+
const diagnostic = addResult.stderr.trim() || addResult.error?.message || "unknown error";
|
|
161
|
+
throw new ReqError(`ERROR: git add failed for ${addTargetPath}: ${diagnostic}`, 1);
|
|
162
|
+
}
|
|
163
|
+
const commitResult = runCapture(["git", "commit", "-m", plan.commitMessage], plan.gitPath);
|
|
164
|
+
if (commitResult.error || commitResult.status !== 0) {
|
|
165
|
+
const diagnostic = commitResult.stderr.trim()
|
|
166
|
+
|| commitResult.stdout.trim()
|
|
167
|
+
|| commitResult.error?.message
|
|
168
|
+
|| "unknown error";
|
|
169
|
+
throw new ReqError(`ERROR: git commit failed: ${diagnostic}`, 1);
|
|
170
|
+
}
|
|
171
|
+
const residualStatusLines = listResidualGitStatusLines(plan.basePath, plan.gitPath, config);
|
|
172
|
+
if (residualStatusLines.length > 0) {
|
|
173
|
+
throw new ReqError("ERROR: Git repository is not clean after req-references commit.", 1);
|
|
174
|
+
}
|
|
175
|
+
}
|