ctx-switch 2.0.4 → 2.0.6
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/dist/index.mjs +509 -56
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import path from "node:path";
|
|
|
14
14
|
// src/types.ts
|
|
15
15
|
var SUPPORTED_COMMANDS = ["continue", "doctor", "sessions"];
|
|
16
16
|
var SUPPORTED_PROVIDERS = ["openrouter"];
|
|
17
|
-
var SUPPORTED_TARGETS = ["generic", "codex", "cursor", "chatgpt"];
|
|
17
|
+
var SUPPORTED_TARGETS = ["generic", "claude", "codex", "cursor", "chatgpt"];
|
|
18
18
|
var SUPPORTED_SOURCES = ["claude", "codex", "opencode"];
|
|
19
19
|
|
|
20
20
|
// src/args.ts
|
|
@@ -142,7 +142,7 @@ function getHelpText({ name, version }) {
|
|
|
142
142
|
" -o, --output <file> Write the final prompt to a file",
|
|
143
143
|
" --source <name> Session source: claude, codex, opencode (interactive if omitted)",
|
|
144
144
|
" --session <id|path> Use a specific session file or session id",
|
|
145
|
-
" --target <name> Prompt target: generic, codex, cursor, chatgpt",
|
|
145
|
+
" --target <name> Prompt target: generic, claude, codex, cursor, chatgpt",
|
|
146
146
|
" -n, --limit <count> Limit rows for the sessions command (default: 10)",
|
|
147
147
|
"",
|
|
148
148
|
"Refinement (optional)",
|
|
@@ -160,6 +160,7 @@ function getHelpText({ name, version }) {
|
|
|
160
160
|
"Examples",
|
|
161
161
|
` ${name} # interactive source picker`,
|
|
162
162
|
` ${name} --source claude # use Claude Code sessions`,
|
|
163
|
+
` ${name} --source codex --target claude`,
|
|
163
164
|
` ${name} --source codex --target codex`,
|
|
164
165
|
` ${name} --source opencode`,
|
|
165
166
|
` ${name} --refine --model openrouter/free`,
|
|
@@ -600,6 +601,60 @@ import path5 from "node:path";
|
|
|
600
601
|
var CODEX_DIR = path5.join(os3.homedir(), ".codex");
|
|
601
602
|
var CODEX_SESSIONS_DIR = path5.join(CODEX_DIR, "sessions");
|
|
602
603
|
var CODEX_INDEX_PATH = path5.join(CODEX_DIR, "session_index.jsonl");
|
|
604
|
+
function parseJsonRecord(line) {
|
|
605
|
+
try {
|
|
606
|
+
return JSON.parse(line);
|
|
607
|
+
} catch {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function parseFunctionArguments(raw) {
|
|
612
|
+
if (!raw) return {};
|
|
613
|
+
try {
|
|
614
|
+
const parsed = JSON.parse(raw);
|
|
615
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
616
|
+
} catch {
|
|
617
|
+
return {};
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function parseCustomToolInput(toolName, raw) {
|
|
621
|
+
if (!raw) return {};
|
|
622
|
+
if (typeof raw !== "string") return raw;
|
|
623
|
+
const trimmed = raw.trim();
|
|
624
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
625
|
+
try {
|
|
626
|
+
const parsed = JSON.parse(trimmed);
|
|
627
|
+
if (parsed && typeof parsed === "object") {
|
|
628
|
+
return parsed;
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (toolName === "apply_patch") {
|
|
634
|
+
return { patch: raw };
|
|
635
|
+
}
|
|
636
|
+
return { input: raw };
|
|
637
|
+
}
|
|
638
|
+
function parseToolOutput(raw) {
|
|
639
|
+
if (!raw) return { text: "" };
|
|
640
|
+
let text = raw;
|
|
641
|
+
let exitCode;
|
|
642
|
+
try {
|
|
643
|
+
const parsed = JSON.parse(raw);
|
|
644
|
+
if (typeof parsed.output === "string") {
|
|
645
|
+
text = parsed.output;
|
|
646
|
+
}
|
|
647
|
+
if (typeof parsed.metadata?.exit_code === "number") {
|
|
648
|
+
exitCode = parsed.metadata.exit_code;
|
|
649
|
+
}
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
const isError = exitCode !== void 0 ? exitCode !== 0 : /Process exited with code [^0]/.test(text) || /error|Error|ERROR/.test(text.slice(0, 200));
|
|
653
|
+
return {
|
|
654
|
+
text: text.slice(0, 1500),
|
|
655
|
+
isError: isError || void 0
|
|
656
|
+
};
|
|
657
|
+
}
|
|
603
658
|
function listSessionsForProject2(cwd) {
|
|
604
659
|
if (!fs4.existsSync(CODEX_SESSIONS_DIR)) {
|
|
605
660
|
return [];
|
|
@@ -680,12 +735,8 @@ function parseSession2(sessionPath) {
|
|
|
680
735
|
}
|
|
681
736
|
}
|
|
682
737
|
for (const line of lines) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
record = JSON.parse(line);
|
|
686
|
-
} catch {
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
738
|
+
const record = parseJsonRecord(line);
|
|
739
|
+
if (!record) continue;
|
|
689
740
|
if (record.type === "session_meta") {
|
|
690
741
|
const payload2 = record.payload;
|
|
691
742
|
if (payload2.cwd) meta.cwd = payload2.cwd;
|
|
@@ -693,6 +744,21 @@ function parseSession2(sessionPath) {
|
|
|
693
744
|
if (payload2.id) meta.sessionId = payload2.id;
|
|
694
745
|
continue;
|
|
695
746
|
}
|
|
747
|
+
if (record.type === "event_msg" && record.payload.type === "exec_command_end" && record.payload.call_id) {
|
|
748
|
+
const tc = pendingCalls.get(record.payload.call_id);
|
|
749
|
+
if (tc) {
|
|
750
|
+
if (Array.isArray(record.payload.parsed_cmd)) {
|
|
751
|
+
tc.input.parsed_cmd = record.payload.parsed_cmd;
|
|
752
|
+
}
|
|
753
|
+
if (typeof record.payload.aggregated_output === "string" && !tc.result) {
|
|
754
|
+
tc.result = record.payload.aggregated_output.slice(0, 1500);
|
|
755
|
+
}
|
|
756
|
+
if (typeof record.payload.exit_code === "number") {
|
|
757
|
+
tc.isError = record.payload.exit_code !== 0;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
696
762
|
if (record.type !== "response_item") continue;
|
|
697
763
|
const payload = record.payload;
|
|
698
764
|
const payloadType = payload.type;
|
|
@@ -718,11 +784,7 @@ function parseSession2(sessionPath) {
|
|
|
718
784
|
continue;
|
|
719
785
|
}
|
|
720
786
|
if (payloadType === "function_call" && payload.name) {
|
|
721
|
-
|
|
722
|
-
try {
|
|
723
|
-
input = JSON.parse(payload.arguments || "{}");
|
|
724
|
-
} catch {
|
|
725
|
-
}
|
|
787
|
+
const input = parseFunctionArguments(payload.arguments);
|
|
726
788
|
const tc = {
|
|
727
789
|
id: payload.call_id || null,
|
|
728
790
|
tool: payload.name,
|
|
@@ -734,12 +796,26 @@ function parseSession2(sessionPath) {
|
|
|
734
796
|
}
|
|
735
797
|
continue;
|
|
736
798
|
}
|
|
737
|
-
if (payloadType === "
|
|
799
|
+
if (payloadType === "custom_tool_call" && payload.name) {
|
|
800
|
+
const tc = {
|
|
801
|
+
id: payload.call_id || null,
|
|
802
|
+
tool: payload.name,
|
|
803
|
+
input: parseCustomToolInput(payload.name, payload.input)
|
|
804
|
+
};
|
|
805
|
+
currentToolCalls.push(tc);
|
|
806
|
+
if (payload.call_id) {
|
|
807
|
+
pendingCalls.set(payload.call_id, tc);
|
|
808
|
+
}
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if ((payloadType === "function_call_output" || payloadType === "custom_tool_call_output") && payload.call_id) {
|
|
738
812
|
const tc = pendingCalls.get(payload.call_id);
|
|
739
813
|
if (tc) {
|
|
740
|
-
const
|
|
741
|
-
tc.result =
|
|
742
|
-
|
|
814
|
+
const parsed = parseToolOutput(payload.output);
|
|
815
|
+
tc.result = parsed.text;
|
|
816
|
+
if (parsed.isError !== void 0) {
|
|
817
|
+
tc.isError = parsed.isError;
|
|
818
|
+
}
|
|
743
819
|
}
|
|
744
820
|
continue;
|
|
745
821
|
}
|
|
@@ -753,7 +829,10 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
753
829
|
import os4 from "node:os";
|
|
754
830
|
import path6 from "node:path";
|
|
755
831
|
var OPENCODE_DB_PATH = path6.join(os4.homedir(), ".local", "share", "opencode", "opencode.db");
|
|
756
|
-
function
|
|
832
|
+
function getOpenCodeDbPath() {
|
|
833
|
+
return process.env.CTX_SWITCH_OPENCODE_DB_PATH || OPENCODE_DB_PATH;
|
|
834
|
+
}
|
|
835
|
+
function runSqlite(query, dbPath = getOpenCodeDbPath()) {
|
|
757
836
|
try {
|
|
758
837
|
const stdout = execFileSync2("sqlite3", ["-json", dbPath, query], {
|
|
759
838
|
encoding: "utf8",
|
|
@@ -806,7 +885,31 @@ function resolveSessionPath3(selection, cwd) {
|
|
|
806
885
|
const match = sessions.find((s) => s.id === selection || s.id.includes(selection));
|
|
807
886
|
return match ? match.id : null;
|
|
808
887
|
}
|
|
888
|
+
function detectToolError(tool, status, output) {
|
|
889
|
+
if (status === "error") return true;
|
|
890
|
+
if (!output) return void 0;
|
|
891
|
+
if (/Process exited with code [1-9]\d*/.test(output)) return true;
|
|
892
|
+
if (/^npm ERR!/m.test(output)) return true;
|
|
893
|
+
if (/^(Error|TypeError|ReferenceError|SyntaxError):/m.test(output)) return true;
|
|
894
|
+
if (/^(bash|sh|zsh):/m.test(output)) return true;
|
|
895
|
+
if (tool === "read" || tool === "glob" || tool === "grep") return void 0;
|
|
896
|
+
return void 0;
|
|
897
|
+
}
|
|
898
|
+
function extractDelegatedSessionId(part) {
|
|
899
|
+
const metadataSessionId = part.state?.metadata?.sessionId;
|
|
900
|
+
if (typeof metadataSessionId === "string" && metadataSessionId.trim()) {
|
|
901
|
+
return metadataSessionId.trim();
|
|
902
|
+
}
|
|
903
|
+
const output = part.state?.output;
|
|
904
|
+
if (typeof output !== "string") return null;
|
|
905
|
+
const match = output.match(/\btask_id:\s*(ses_[^\s)]+)/);
|
|
906
|
+
return match?.[1] || null;
|
|
907
|
+
}
|
|
809
908
|
function parseSession3(sessionId) {
|
|
909
|
+
return parseSessionInternal(sessionId, /* @__PURE__ */ new Set());
|
|
910
|
+
}
|
|
911
|
+
function parseSessionInternal(sessionId, seenSessions) {
|
|
912
|
+
seenSessions.add(sessionId);
|
|
810
913
|
const messages = [];
|
|
811
914
|
const meta = {
|
|
812
915
|
cwd: null,
|
|
@@ -842,6 +945,7 @@ function parseSession3(sessionId) {
|
|
|
842
945
|
if (!role || role !== "user" && role !== "assistant") continue;
|
|
843
946
|
const textParts = [];
|
|
844
947
|
const toolCalls = [];
|
|
948
|
+
const delegatedAssistantMessages = [];
|
|
845
949
|
for (const partRow of partRows) {
|
|
846
950
|
let part;
|
|
847
951
|
try {
|
|
@@ -854,9 +958,17 @@ function parseSession3(sessionId) {
|
|
|
854
958
|
} else if (part.type === "tool" && part.tool) {
|
|
855
959
|
const input = part.state?.input || {};
|
|
856
960
|
const output = part.state?.output || "";
|
|
857
|
-
const isError = part.state?.status
|
|
961
|
+
const isError = detectToolError(part.tool, part.state?.status, output);
|
|
858
962
|
const toolName = normalizeToolName(part.tool);
|
|
859
963
|
const normalizedInput = normalizeInput(part.tool, input);
|
|
964
|
+
if (toolName === "task") {
|
|
965
|
+
const delegatedSessionId = extractDelegatedSessionId(part);
|
|
966
|
+
if (delegatedSessionId && !seenSessions.has(delegatedSessionId)) {
|
|
967
|
+
normalizedInput.delegated_session_id = delegatedSessionId;
|
|
968
|
+
const delegated = parseSessionInternal(delegatedSessionId, seenSessions);
|
|
969
|
+
delegatedAssistantMessages.push(...delegated.messages.filter((message) => message.role === "assistant"));
|
|
970
|
+
}
|
|
971
|
+
}
|
|
860
972
|
toolCalls.push({
|
|
861
973
|
id: part.callID || null,
|
|
862
974
|
tool: toolName,
|
|
@@ -874,11 +986,15 @@ function parseSession3(sessionId) {
|
|
|
874
986
|
toolCalls,
|
|
875
987
|
timestamp: null
|
|
876
988
|
});
|
|
989
|
+
if (role === "assistant" && delegatedAssistantMessages.length > 0) {
|
|
990
|
+
messages.push(...delegatedAssistantMessages);
|
|
991
|
+
}
|
|
877
992
|
}
|
|
878
993
|
return { messages, meta };
|
|
879
994
|
}
|
|
880
995
|
function normalizeToolName(tool) {
|
|
881
996
|
const mapping = {
|
|
997
|
+
apply_patch: "apply_patch",
|
|
882
998
|
read: "read",
|
|
883
999
|
edit: "edit",
|
|
884
1000
|
write: "write",
|
|
@@ -894,10 +1010,14 @@ function normalizeToolName(tool) {
|
|
|
894
1010
|
return mapping[tool.toLowerCase()] || tool;
|
|
895
1011
|
}
|
|
896
1012
|
function normalizeInput(tool, input) {
|
|
897
|
-
|
|
898
|
-
|
|
1013
|
+
const normalized = { ...input };
|
|
1014
|
+
if (normalized.filePath && !normalized.file_path) {
|
|
1015
|
+
normalized.file_path = normalized.filePath;
|
|
1016
|
+
}
|
|
1017
|
+
if (tool === "apply_patch" && typeof normalized.patchText === "string" && !normalized.patch) {
|
|
1018
|
+
normalized.patch = normalized.patchText;
|
|
899
1019
|
}
|
|
900
|
-
return
|
|
1020
|
+
return normalized;
|
|
901
1021
|
}
|
|
902
1022
|
|
|
903
1023
|
// src/session.ts
|
|
@@ -950,6 +1070,37 @@ function extractCommand(input) {
|
|
|
950
1070
|
const value = input.command || input.cmd;
|
|
951
1071
|
return typeof value === "string" ? value : null;
|
|
952
1072
|
}
|
|
1073
|
+
function resolveSessionFilePath(filePath, cwd) {
|
|
1074
|
+
return path7.isAbsolute(filePath) ? filePath : path7.resolve(cwd, filePath);
|
|
1075
|
+
}
|
|
1076
|
+
function extractPatchFilePaths(input, cwd) {
|
|
1077
|
+
const patch = input.patch;
|
|
1078
|
+
if (typeof patch !== "string") return [];
|
|
1079
|
+
const matches = [...patch.matchAll(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/gm)];
|
|
1080
|
+
return matches.map((match) => resolveSessionFilePath(match[1].trim(), cwd));
|
|
1081
|
+
}
|
|
1082
|
+
function extractParsedCommands(input) {
|
|
1083
|
+
const parsed = input.parsed_cmd;
|
|
1084
|
+
if (!Array.isArray(parsed)) return [];
|
|
1085
|
+
return parsed.filter((entry) => Boolean(entry) && typeof entry === "object");
|
|
1086
|
+
}
|
|
1087
|
+
function tokenizeShellCommand(command) {
|
|
1088
|
+
const matches = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
1089
|
+
return matches.map((token) => token.replace(/^['"]|['"]$/g, ""));
|
|
1090
|
+
}
|
|
1091
|
+
function looksLikeFilePath(token) {
|
|
1092
|
+
if (!token || token.startsWith("-")) return false;
|
|
1093
|
+
if (/[|<>;$()]/.test(token)) return false;
|
|
1094
|
+
return token.includes("/") || token.includes(".");
|
|
1095
|
+
}
|
|
1096
|
+
function extractReadPathsFromCommand(command, cwd) {
|
|
1097
|
+
const tokens = tokenizeShellCommand(command.trim());
|
|
1098
|
+
if (tokens.length === 0) return [];
|
|
1099
|
+
const base = path7.basename(tokens[0]);
|
|
1100
|
+
const readCommands = /* @__PURE__ */ new Set(["cat", "head", "tail", "sed", "nl", "wc"]);
|
|
1101
|
+
if (!readCommands.has(base)) return [];
|
|
1102
|
+
return tokens.slice(1).filter((token) => looksLikeFilePath(token)).filter((token) => !/^\d+(,\d+)?p?$/.test(token)).map((token) => resolveSessionFilePath(token, cwd));
|
|
1103
|
+
}
|
|
953
1104
|
function buildSessionContext({
|
|
954
1105
|
messages,
|
|
955
1106
|
meta,
|
|
@@ -967,10 +1118,23 @@ function buildSessionContext({
|
|
|
967
1118
|
const toolName = String(toolCall.tool || "").toLowerCase();
|
|
968
1119
|
const filePath = extractFilePath(toolCall.input);
|
|
969
1120
|
const command = extractCommand(toolCall.input);
|
|
1121
|
+
const patchPaths = extractPatchFilePaths(toolCall.input, cwd);
|
|
1122
|
+
const parsedCommands = extractParsedCommands(toolCall.input);
|
|
1123
|
+
const commandReadPaths = command ? extractReadPathsFromCommand(command, cwd) : [];
|
|
970
1124
|
if (filePath && /(edit|write|create|multi_edit)/.test(toolName)) {
|
|
971
|
-
filesModified.add(filePath);
|
|
1125
|
+
filesModified.add(resolveSessionFilePath(filePath, cwd));
|
|
972
1126
|
} else if (filePath && /(read|grep|glob|search)/.test(toolName)) {
|
|
973
|
-
filesRead.add(filePath);
|
|
1127
|
+
filesRead.add(resolveSessionFilePath(filePath, cwd));
|
|
1128
|
+
}
|
|
1129
|
+
for (const patchPath of patchPaths) {
|
|
1130
|
+
filesModified.add(patchPath);
|
|
1131
|
+
}
|
|
1132
|
+
for (const parsedCommand of parsedCommands) {
|
|
1133
|
+
if (parsedCommand.type !== "read" || typeof parsedCommand.path !== "string") continue;
|
|
1134
|
+
filesRead.add(resolveSessionFilePath(parsedCommand.path, cwd));
|
|
1135
|
+
}
|
|
1136
|
+
for (const readPath of commandReadPaths) {
|
|
1137
|
+
filesRead.add(readPath);
|
|
974
1138
|
}
|
|
975
1139
|
if (command && /(bash|command|run|exec_command)/.test(toolName)) {
|
|
976
1140
|
commands.push(command);
|
|
@@ -985,8 +1149,10 @@ function buildSessionContext({
|
|
|
985
1149
|
const toolSummary = message.toolCalls.map((toolCall) => {
|
|
986
1150
|
const filePath = extractFilePath(toolCall.input);
|
|
987
1151
|
const command = extractCommand(toolCall.input);
|
|
1152
|
+
const patchPaths = extractPatchFilePaths(toolCall.input, cwd);
|
|
988
1153
|
let summary = "";
|
|
989
|
-
if (filePath) summary = `${toolCall.tool} ${filePath}`;
|
|
1154
|
+
if (filePath) summary = `${toolCall.tool} ${resolveSessionFilePath(filePath, cwd)}`;
|
|
1155
|
+
else if (patchPaths.length > 0) summary = `${toolCall.tool} ${patchPaths.join(", ")}`;
|
|
990
1156
|
else if (command) summary = `${toolCall.tool}: ${command}`;
|
|
991
1157
|
else summary = toolCall.tool;
|
|
992
1158
|
if (toolCall.isError && toolCall.result) {
|
|
@@ -1156,8 +1322,18 @@ function compactText(text, maxChars = 800) {
|
|
|
1156
1322
|
function unique(list) {
|
|
1157
1323
|
return [...new Set(list.filter(Boolean))];
|
|
1158
1324
|
}
|
|
1325
|
+
function extractFilePath2(input) {
|
|
1326
|
+
const value = input.file_path || input.path || input.target_file || input.filePath;
|
|
1327
|
+
return typeof value === "string" ? value : null;
|
|
1328
|
+
}
|
|
1329
|
+
function extractCommand2(input) {
|
|
1330
|
+
const value = input.command || input.cmd;
|
|
1331
|
+
return typeof value === "string" ? value : null;
|
|
1332
|
+
}
|
|
1159
1333
|
function buildTargetGuidance(target) {
|
|
1160
1334
|
switch (target) {
|
|
1335
|
+
case "claude":
|
|
1336
|
+
return "The next agent is Claude Code. It should read the active files first, inspect the current workspace with git commands, and continue the implementation or debugging directly.";
|
|
1161
1337
|
case "codex":
|
|
1162
1338
|
return "The next agent is Codex. It should inspect the current files first, avoid redoing completed work, and finish any remaining implementation or verification.";
|
|
1163
1339
|
case "cursor":
|
|
@@ -1165,19 +1341,20 @@ function buildTargetGuidance(target) {
|
|
|
1165
1341
|
case "chatgpt":
|
|
1166
1342
|
return "The next agent is ChatGPT. It should reason from the current workspace state, explain what remains, and provide explicit next actions.";
|
|
1167
1343
|
default:
|
|
1168
|
-
return "The next agent should
|
|
1344
|
+
return "The next agent should read the active files first, inspect the current workspace with git commands, continue the interrupted work directly, and avoid redoing completed steps.";
|
|
1169
1345
|
}
|
|
1170
1346
|
}
|
|
1171
1347
|
function isNoiseMessage(text) {
|
|
1172
1348
|
const trimmed = text.trim().toLowerCase();
|
|
1349
|
+
const normalized = trimmed.replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1173
1350
|
if (trimmed.length < 5) return true;
|
|
1174
1351
|
const noise = [
|
|
1175
1352
|
"yes",
|
|
1353
|
+
"yes please",
|
|
1176
1354
|
"no",
|
|
1177
1355
|
"ok",
|
|
1178
1356
|
"okay",
|
|
1179
1357
|
"try",
|
|
1180
|
-
"try?",
|
|
1181
1358
|
"sure",
|
|
1182
1359
|
"do it",
|
|
1183
1360
|
"go ahead",
|
|
@@ -1194,11 +1371,20 @@ function isNoiseMessage(text) {
|
|
|
1194
1371
|
"try turn off thinking",
|
|
1195
1372
|
"try without timeout"
|
|
1196
1373
|
];
|
|
1197
|
-
if (noise.includes(
|
|
1198
|
-
if (/^(try|yes|ok|sure|test|run)\s/i.test(
|
|
1374
|
+
if (noise.includes(normalized)) return true;
|
|
1375
|
+
if (/^(try|yes|ok|sure|test|run)\s/i.test(normalized) && normalized.length < 40) return true;
|
|
1199
1376
|
if (trimmed.startsWith("[request interrupted")) return true;
|
|
1200
1377
|
return false;
|
|
1201
1378
|
}
|
|
1379
|
+
function isReferentialMessage(text) {
|
|
1380
|
+
const normalized = text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1381
|
+
if (!normalized || normalized.length > 220) return false;
|
|
1382
|
+
return /^(ok|okay|alright|now|so)\b/.test(normalized) || /\b(it|that|again|better|same|continue|still|also|another|more)\b/.test(normalized);
|
|
1383
|
+
}
|
|
1384
|
+
function isMetaQualityAssistantMessage(text) {
|
|
1385
|
+
const lower = text.toLowerCase();
|
|
1386
|
+
return /\b(handoff|prompt)\b/.test(lower) && /\b(good|bad|better|worse|quality)\b/.test(lower);
|
|
1387
|
+
}
|
|
1202
1388
|
function filterUserMessages(messages) {
|
|
1203
1389
|
const all = messages.filter((m) => m.role === "user" && m.content).map((m) => m.content.trim());
|
|
1204
1390
|
if (all.length <= 2) return all;
|
|
@@ -1227,31 +1413,267 @@ function extractKeyDecisions(messages) {
|
|
|
1227
1413
|
const decisions = [];
|
|
1228
1414
|
for (const msg of messages) {
|
|
1229
1415
|
if (msg.role !== "assistant" || !msg.content) continue;
|
|
1416
|
+
if (isMetaQualityAssistantMessage(msg.content)) {
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1230
1419
|
const lower = msg.content.toLowerCase();
|
|
1231
|
-
if (
|
|
1420
|
+
if (/\b(root cause|the issue is|the problem is|caused by|failed because|failing because|need to)\b/.test(lower) || /\b(exposed|revealed|showed)\b.*\b(gap|issue|problem|bug)\b/.test(lower) || /\bmissing\b/.test(lower)) {
|
|
1232
1421
|
decisions.push(compactText(msg.content, 300));
|
|
1233
1422
|
}
|
|
1234
1423
|
}
|
|
1235
1424
|
return decisions.slice(-5);
|
|
1236
1425
|
}
|
|
1426
|
+
function findFocusedWindow(messages) {
|
|
1427
|
+
if (messages.length === 0) {
|
|
1428
|
+
return { messages, sessionAppearsComplete: false };
|
|
1429
|
+
}
|
|
1430
|
+
const substantiveUserIndexes = messages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user" && message.content && !isNoiseMessage(message.content)).map(({ index }) => index);
|
|
1431
|
+
if (substantiveUserIndexes.length === 0) {
|
|
1432
|
+
return { messages, sessionAppearsComplete: false };
|
|
1433
|
+
}
|
|
1434
|
+
const lastToolIndex = messages.reduce(
|
|
1435
|
+
(last, message, index) => message.role === "assistant" && message.toolCalls.length > 0 ? index : last,
|
|
1436
|
+
-1
|
|
1437
|
+
);
|
|
1438
|
+
const postToolUsers = substantiveUserIndexes.filter((index) => index > lastToolIndex);
|
|
1439
|
+
let startIndex = 0;
|
|
1440
|
+
if (postToolUsers.length > 0) {
|
|
1441
|
+
startIndex = postToolUsers[0];
|
|
1442
|
+
} else if (lastToolIndex >= 0) {
|
|
1443
|
+
startIndex = substantiveUserIndexes.filter((index) => index <= lastToolIndex).at(-1) ?? 0;
|
|
1444
|
+
} else {
|
|
1445
|
+
startIndex = substantiveUserIndexes.at(-1) ?? 0;
|
|
1446
|
+
}
|
|
1447
|
+
const startMessage = messages[startIndex];
|
|
1448
|
+
if (startMessage?.role === "user" && isReferentialMessage(startMessage.content)) {
|
|
1449
|
+
const previousSubstantive = substantiveUserIndexes.filter((index) => index < startIndex).at(-1);
|
|
1450
|
+
if (typeof previousSubstantive === "number") {
|
|
1451
|
+
startIndex = previousSubstantive;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
const focused = messages.slice(startIndex);
|
|
1455
|
+
const hasToolActivity = focused.some((message) => message.role === "assistant" && message.toolCalls.length > 0);
|
|
1456
|
+
const lastMessage = focused.at(-1);
|
|
1457
|
+
const sessionAppearsComplete = Boolean(lastMessage) && lastMessage.role === "assistant" && lastMessage.toolCalls.length === 0 && !hasToolActivity;
|
|
1458
|
+
return { messages: focused, sessionAppearsComplete };
|
|
1459
|
+
}
|
|
1460
|
+
function extractWorkSummary(messages) {
|
|
1461
|
+
const filesModified = /* @__PURE__ */ new Set();
|
|
1462
|
+
const commands = [];
|
|
1463
|
+
for (const message of messages) {
|
|
1464
|
+
if (message.role !== "assistant" || message.toolCalls.length === 0) continue;
|
|
1465
|
+
for (const toolCall of message.toolCalls) {
|
|
1466
|
+
const toolName = String(toolCall.tool || "").toLowerCase();
|
|
1467
|
+
const filePath = extractFilePath2(toolCall.input);
|
|
1468
|
+
const command = extractCommand2(toolCall.input);
|
|
1469
|
+
if (filePath && /(edit|write|create|multi_edit)/.test(toolName)) {
|
|
1470
|
+
filesModified.add(filePath);
|
|
1471
|
+
}
|
|
1472
|
+
if (command && /(bash|command|run|exec_command)/.test(toolName)) {
|
|
1473
|
+
commands.push(command);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
filesModified: [...filesModified],
|
|
1479
|
+
commands
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function extractLastAssistantAnswer(messages) {
|
|
1483
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1484
|
+
const message = messages[i];
|
|
1485
|
+
if (message.role === "assistant" && message.content.trim()) {
|
|
1486
|
+
return compactText(message.content, 500);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
function summarizeToolCall(toolCall) {
|
|
1492
|
+
const filePath = extractFilePath2(toolCall.input);
|
|
1493
|
+
const command = extractCommand2(toolCall.input);
|
|
1494
|
+
if (filePath) return `${toolCall.tool} ${filePath}`;
|
|
1495
|
+
if (command) return `${toolCall.tool}: ${summarizeCommand(command)}`;
|
|
1496
|
+
return toolCall.tool;
|
|
1497
|
+
}
|
|
1498
|
+
function findLastActiveAssistant(messages) {
|
|
1499
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1500
|
+
const message = messages[i];
|
|
1501
|
+
if (message.role !== "assistant") continue;
|
|
1502
|
+
if (message.content.trim() || message.toolCalls.length > 0) {
|
|
1503
|
+
return message;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
function buildCurrentStatus(messages, errors, sessionAppearsComplete) {
|
|
1509
|
+
const lastAssistant = findLastActiveAssistant(messages);
|
|
1510
|
+
const lastStep = lastAssistant?.content?.trim() ? compactText(lastAssistant.content, 400) : null;
|
|
1511
|
+
const lastToolActions = lastAssistant ? lastAssistant.toolCalls.slice(-4).map(summarizeToolCall) : [];
|
|
1512
|
+
let status = "In progress";
|
|
1513
|
+
if (sessionAppearsComplete) {
|
|
1514
|
+
status = "Latest exchange complete";
|
|
1515
|
+
} else if (errors.length > 0) {
|
|
1516
|
+
status = "Blocked by unresolved errors";
|
|
1517
|
+
} else if (lastAssistant?.toolCalls.length) {
|
|
1518
|
+
status = "Mid-task after recent tool activity";
|
|
1519
|
+
} else if (lastAssistant?.content.trim()) {
|
|
1520
|
+
status = "Awaiting the next concrete action";
|
|
1521
|
+
}
|
|
1522
|
+
return { status, lastStep, lastToolActions };
|
|
1523
|
+
}
|
|
1524
|
+
function buildRemainingWorkHints({
|
|
1525
|
+
sessionAppearsComplete,
|
|
1526
|
+
errors,
|
|
1527
|
+
work,
|
|
1528
|
+
focusFiles,
|
|
1529
|
+
recentCommands
|
|
1530
|
+
}) {
|
|
1531
|
+
if (sessionAppearsComplete) return [];
|
|
1532
|
+
const hints = [];
|
|
1533
|
+
if (errors.length > 0) {
|
|
1534
|
+
hints.push("Resolve the unresolved errors above before extending the implementation.");
|
|
1535
|
+
}
|
|
1536
|
+
if (work.filesModified.length > 0) {
|
|
1537
|
+
hints.push(`Inspect the in-progress changes in ${work.filesModified.map((filePath) => `\`${filePath}\``).join(", ")} and decide what still needs to be finished or verified.`);
|
|
1538
|
+
} else if (focusFiles.length > 0) {
|
|
1539
|
+
hints.push(`Start by reading ${focusFiles.map((filePath) => `\`${filePath}\``).join(", ")} to reconstruct the current working set.`);
|
|
1540
|
+
}
|
|
1541
|
+
if (recentCommands.length > 0) {
|
|
1542
|
+
hints.push("Rerun or extend the recent checks to confirm the current state before making further changes.");
|
|
1543
|
+
}
|
|
1544
|
+
if (focusFiles.length > 0) {
|
|
1545
|
+
hints.push("Run `git diff --` on the active files to see the exact in-progress changes before editing further.");
|
|
1546
|
+
}
|
|
1547
|
+
if (hints.length === 0) {
|
|
1548
|
+
hints.push("Inspect the active files and run `git diff` to determine the next concrete implementation step.");
|
|
1549
|
+
}
|
|
1550
|
+
return hints;
|
|
1551
|
+
}
|
|
1552
|
+
function selectSessionHistoryMessages(focusedMessages, allMessages, sessionAppearsComplete) {
|
|
1553
|
+
if (sessionAppearsComplete) return focusedMessages;
|
|
1554
|
+
const hasAssistantActivity = focusedMessages.some(
|
|
1555
|
+
(message) => message.role === "assistant" && (message.content.trim() || message.toolCalls.length > 0)
|
|
1556
|
+
);
|
|
1557
|
+
if (focusedMessages.length >= 3 && hasAssistantActivity) return focusedMessages;
|
|
1558
|
+
const filtered = allMessages.filter((message) => {
|
|
1559
|
+
if (message.role === "assistant" && message.content.trim() && isMetaQualityAssistantMessage(message.content)) {
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
if (message.role === "user" && message.content && isNoiseMessage(message.content)) {
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
return Boolean(message.content.trim()) || message.toolCalls.length > 0;
|
|
1566
|
+
});
|
|
1567
|
+
return filtered.slice(-8);
|
|
1568
|
+
}
|
|
1569
|
+
function buildSessionHistory(focusedMessages, allMessages, sessionAppearsComplete) {
|
|
1570
|
+
const historyMessages = selectSessionHistoryMessages(focusedMessages, allMessages, sessionAppearsComplete);
|
|
1571
|
+
const entries = historyMessages.map((message) => {
|
|
1572
|
+
const parts = [];
|
|
1573
|
+
if (message.content.trim()) {
|
|
1574
|
+
if (message.role === "assistant" && isMetaQualityAssistantMessage(message.content)) {
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
parts.push(compactText(message.content, 220));
|
|
1578
|
+
}
|
|
1579
|
+
if (message.role === "assistant" && message.toolCalls.length > 0) {
|
|
1580
|
+
parts.push(`[tools] ${message.toolCalls.slice(-4).map(summarizeToolCall).join(", ")}`);
|
|
1581
|
+
}
|
|
1582
|
+
if (parts.length === 0) return null;
|
|
1583
|
+
return `${message.role.toUpperCase()}: ${parts.join(" | ")}`;
|
|
1584
|
+
}).filter((entry) => Boolean(entry));
|
|
1585
|
+
if (entries.length <= 8) return entries;
|
|
1586
|
+
return [entries[0], "...", ...entries.slice(-6)];
|
|
1587
|
+
}
|
|
1588
|
+
function summarizeCommand(command) {
|
|
1589
|
+
return compactText(command.replace(/\s+/g, " ").trim(), 140);
|
|
1590
|
+
}
|
|
1591
|
+
function extractRecentCommands(commands) {
|
|
1592
|
+
return unique(commands.map(summarizeCommand)).slice(-6);
|
|
1593
|
+
}
|
|
1594
|
+
function extractStatusPaths(status) {
|
|
1595
|
+
if (!status) return [];
|
|
1596
|
+
return status.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
1597
|
+
const renamed = line.includes(" -> ") ? line.split(" -> ").at(-1)?.trim() || "" : "";
|
|
1598
|
+
if (renamed) {
|
|
1599
|
+
return renamed.replace(/^(?:\?\?|[A-Z?!]{1,2})\s+/, "");
|
|
1600
|
+
}
|
|
1601
|
+
const match = line.match(/^(?:\?\?|[A-Z?!]{1,2})\s+(.*)$/);
|
|
1602
|
+
return match?.[1]?.trim() || line;
|
|
1603
|
+
}).filter(Boolean);
|
|
1604
|
+
}
|
|
1605
|
+
function extractFocusFiles(ctx, work) {
|
|
1606
|
+
return unique([
|
|
1607
|
+
...work.filesModified,
|
|
1608
|
+
...extractStatusPaths(ctx.gitContext.status),
|
|
1609
|
+
...ctx.gitContext.untracked.map((file) => file.path)
|
|
1610
|
+
]).slice(0, 6);
|
|
1611
|
+
}
|
|
1237
1612
|
function buildRawPrompt(ctx, options = {}) {
|
|
1238
|
-
const
|
|
1239
|
-
const
|
|
1240
|
-
const
|
|
1613
|
+
const focused = findFocusedWindow(ctx.messages);
|
|
1614
|
+
const userMessages = filterUserMessages(focused.messages);
|
|
1615
|
+
const errors = extractUnresolvedErrors(focused.messages);
|
|
1616
|
+
const decisions = extractKeyDecisions(focused.messages);
|
|
1617
|
+
const work = extractWorkSummary(focused.messages);
|
|
1618
|
+
const focusFiles = extractFocusFiles(ctx, work);
|
|
1619
|
+
const recentCommands = extractRecentCommands(work.commands);
|
|
1620
|
+
const lastAssistantAnswer = extractLastAssistantAnswer(focused.messages);
|
|
1621
|
+
const currentStatus = buildCurrentStatus(focused.messages, errors, focused.sessionAppearsComplete);
|
|
1622
|
+
const remainingWorkHints = buildRemainingWorkHints({
|
|
1623
|
+
sessionAppearsComplete: focused.sessionAppearsComplete,
|
|
1624
|
+
errors,
|
|
1625
|
+
work,
|
|
1626
|
+
focusFiles,
|
|
1627
|
+
recentCommands
|
|
1628
|
+
});
|
|
1629
|
+
const sessionHistory = buildSessionHistory(focused.messages, ctx.messages, focused.sessionAppearsComplete);
|
|
1241
1630
|
let prompt = "";
|
|
1242
1631
|
prompt += "# Task\n\n";
|
|
1243
1632
|
prompt += `Project: \`${ctx.sessionCwd}\`
|
|
1244
1633
|
`;
|
|
1245
1634
|
if (ctx.branch) prompt += `Branch: \`${ctx.branch}\`
|
|
1246
1635
|
`;
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1636
|
+
if (focused.sessionAppearsComplete) {
|
|
1637
|
+
prompt += "\nThe latest exchange in this session appears complete. ";
|
|
1638
|
+
prompt += "Use the focused context below only if the user wants to continue from that point.\n\n";
|
|
1639
|
+
} else {
|
|
1640
|
+
prompt += "\nThis is a continuation of an interrupted AI coding session. ";
|
|
1641
|
+
prompt += "The previous agent was working on the task below. Pick up where it left off.\n\n";
|
|
1642
|
+
}
|
|
1643
|
+
prompt += `## What The User Asked (${focused.sessionAppearsComplete ? "recent focus" : "chronological"})
|
|
1644
|
+
|
|
1645
|
+
`;
|
|
1250
1646
|
for (const msg of userMessages) {
|
|
1251
1647
|
prompt += `- ${compactText(msg, 500)}
|
|
1252
1648
|
`;
|
|
1253
1649
|
}
|
|
1254
1650
|
prompt += "\n";
|
|
1651
|
+
if (focused.sessionAppearsComplete && lastAssistantAnswer) {
|
|
1652
|
+
prompt += "## Last Answer Already Given\n\n";
|
|
1653
|
+
prompt += `- ${lastAssistantAnswer}
|
|
1654
|
+
|
|
1655
|
+
`;
|
|
1656
|
+
}
|
|
1657
|
+
prompt += "## Current Status\n\n";
|
|
1658
|
+
prompt += `- Status: ${currentStatus.status}
|
|
1659
|
+
`;
|
|
1660
|
+
if (currentStatus.lastStep) {
|
|
1661
|
+
prompt += `- Last active step: ${currentStatus.lastStep}
|
|
1662
|
+
`;
|
|
1663
|
+
}
|
|
1664
|
+
if (currentStatus.lastToolActions.length > 0) {
|
|
1665
|
+
prompt += `- Last tool actions: ${currentStatus.lastToolActions.join(", ")}
|
|
1666
|
+
`;
|
|
1667
|
+
}
|
|
1668
|
+
prompt += "\n";
|
|
1669
|
+
if (sessionHistory.length > 0) {
|
|
1670
|
+
prompt += "## Session History\n\n";
|
|
1671
|
+
for (const entry of sessionHistory) {
|
|
1672
|
+
prompt += `- ${entry}
|
|
1673
|
+
`;
|
|
1674
|
+
}
|
|
1675
|
+
prompt += "\n";
|
|
1676
|
+
}
|
|
1255
1677
|
if (errors.length > 0) {
|
|
1256
1678
|
prompt += "## DO NOT REPEAT \u2014 Unresolved Errors\n\n";
|
|
1257
1679
|
prompt += "These errors occurred and were NOT fixed. Avoid the same approaches.\n\n";
|
|
@@ -1269,39 +1691,59 @@ function buildRawPrompt(ctx, options = {}) {
|
|
|
1269
1691
|
}
|
|
1270
1692
|
prompt += "\n";
|
|
1271
1693
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1694
|
+
if (work.filesModified.length > 0) {
|
|
1695
|
+
prompt += "## Work Already Completed\n\n";
|
|
1696
|
+
}
|
|
1697
|
+
if (work.filesModified.length > 0) {
|
|
1274
1698
|
prompt += "**Files modified:**\n";
|
|
1275
|
-
for (const filePath of unique(
|
|
1699
|
+
for (const filePath of unique(work.filesModified)) {
|
|
1276
1700
|
prompt += `- \`${filePath}\`
|
|
1277
1701
|
`;
|
|
1278
1702
|
}
|
|
1279
1703
|
prompt += "\n";
|
|
1280
1704
|
}
|
|
1281
|
-
if (ctx.gitContext.recentCommits) {
|
|
1705
|
+
if (!focused.sessionAppearsComplete && work.filesModified.length > 0 && ctx.gitContext.recentCommits) {
|
|
1282
1706
|
prompt += "**Recent commits:**\n```\n";
|
|
1283
1707
|
prompt += `${ctx.gitContext.recentCommits}
|
|
1284
1708
|
`;
|
|
1285
1709
|
prompt += "```\n\n";
|
|
1286
1710
|
}
|
|
1287
|
-
if (ctx.gitContext.committedDiff) {
|
|
1711
|
+
if (!focused.sessionAppearsComplete && work.filesModified.length > 0 && ctx.gitContext.committedDiff) {
|
|
1288
1712
|
prompt += "**Files changed in recent commits:**\n```\n";
|
|
1289
1713
|
prompt += `${ctx.gitContext.committedDiff}
|
|
1290
1714
|
`;
|
|
1291
1715
|
prompt += "```\n\n";
|
|
1292
1716
|
}
|
|
1717
|
+
if (recentCommands.length > 0) {
|
|
1718
|
+
prompt += "## Recent Commands / Checks\n\n";
|
|
1719
|
+
for (const command of recentCommands) {
|
|
1720
|
+
prompt += `- \`${command}\`
|
|
1721
|
+
`;
|
|
1722
|
+
}
|
|
1723
|
+
prompt += "\n";
|
|
1724
|
+
}
|
|
1725
|
+
if (focusFiles.length > 0) {
|
|
1726
|
+
prompt += "## Read These Files First\n\n";
|
|
1727
|
+
for (const filePath of focusFiles) {
|
|
1728
|
+
prompt += `- \`${filePath}\`
|
|
1729
|
+
`;
|
|
1730
|
+
}
|
|
1731
|
+
prompt += "\n";
|
|
1732
|
+
}
|
|
1733
|
+
if (remainingWorkHints.length > 0) {
|
|
1734
|
+
prompt += "## Likely Remaining Work\n\n";
|
|
1735
|
+
for (const hint of remainingWorkHints) {
|
|
1736
|
+
prompt += `- ${hint}
|
|
1737
|
+
`;
|
|
1738
|
+
}
|
|
1739
|
+
prompt += "\n";
|
|
1740
|
+
}
|
|
1293
1741
|
const git = ctx.gitContext;
|
|
1294
1742
|
if (git.isGitRepo && git.hasChanges) {
|
|
1295
1743
|
prompt += "## Uncommitted Changes\n\n";
|
|
1296
1744
|
if (git.status) {
|
|
1297
1745
|
prompt += "```\n" + git.status + "\n```\n\n";
|
|
1298
1746
|
}
|
|
1299
|
-
if (git.staged.diff) {
|
|
1300
|
-
prompt += "**Staged diff:**\n```diff\n" + git.staged.diff + "\n```\n\n";
|
|
1301
|
-
}
|
|
1302
|
-
if (git.unstaged.diff) {
|
|
1303
|
-
prompt += "**Unstaged diff:**\n```diff\n" + git.unstaged.diff + "\n```\n\n";
|
|
1304
|
-
}
|
|
1305
1747
|
if (git.untracked.length > 0) {
|
|
1306
1748
|
const shown = git.untracked.slice(0, 6);
|
|
1307
1749
|
prompt += "**Untracked files:**\n";
|
|
@@ -1317,19 +1759,30 @@ function buildRawPrompt(ctx, options = {}) {
|
|
|
1317
1759
|
}
|
|
1318
1760
|
}
|
|
1319
1761
|
prompt += "## Your Instructions\n\n";
|
|
1320
|
-
|
|
1762
|
+
if (focused.sessionAppearsComplete) {
|
|
1763
|
+
prompt += "The latest thread appears finished. Do not resume older tasks unless the user explicitly asks for them.\n\n";
|
|
1764
|
+
prompt += "1. **Start from the recent focus above** \u2014 ignore stale history unless the user points back to it.\n";
|
|
1765
|
+
prompt += "2. **Use the last answer as prior context** \u2014 avoid restating or redoing already completed work.\n";
|
|
1766
|
+
prompt += `3. **Inspect the workspace only as needed** \u2014 respond to follow-up questions or new work from the current repo state${focusFiles.length > 0 ? `, starting with ${focusFiles.map((filePath) => `\`${filePath}\``).join(", ")}` : ""}.
|
|
1767
|
+
`;
|
|
1768
|
+
} else {
|
|
1769
|
+
prompt += `${buildTargetGuidance(options.target)}
|
|
1321
1770
|
|
|
1322
1771
|
`;
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1772
|
+
prompt += `1. **Read the active files first** \u2014 verify their current state before changing anything${focusFiles.length > 0 ? `: ${focusFiles.map((filePath) => `\`${filePath}\``).join(", ")}` : ""}.
|
|
1773
|
+
`;
|
|
1774
|
+
if (errors.length > 0) {
|
|
1775
|
+
prompt += "2. **Check the errors above** \u2014 do NOT repeat failed approaches. Try a different strategy.\n";
|
|
1776
|
+
}
|
|
1777
|
+
prompt += `${errors.length > 0 ? "3" : "2"}. **Inspect the workspace state explicitly** \u2014 run \`git status --short\`, \`git diff --stat\`, and \`git diff -- ${focusFiles.length > 0 ? focusFiles.slice(0, 4).join(" ") : "."}\` before changing code.
|
|
1328
1778
|
`;
|
|
1329
|
-
|
|
1779
|
+
prompt += `${errors.length > 0 ? "4" : "3"}. **Identify what's done vs what remains** \u2014 use the Current Status, Session History, Likely Remaining Work, recent commands, active files, and git state above as the source of truth for the current thread.
|
|
1330
1780
|
`;
|
|
1331
|
-
|
|
1781
|
+
prompt += `${errors.length > 0 ? "5" : "4"}. **Continue from the last active step** \u2014 if the stop point is still ambiguous, inspect the read-first files and rerun the recent commands before changing code.
|
|
1332
1782
|
`;
|
|
1783
|
+
prompt += `${errors.length > 0 ? "6" : "5"}. **Verify** \u2014 rerun or extend the relevant commands/checks above to confirm everything works.
|
|
1784
|
+
`;
|
|
1785
|
+
}
|
|
1333
1786
|
return prompt;
|
|
1334
1787
|
}
|
|
1335
1788
|
function buildRefinementDump(ctx, options = {}) {
|
|
@@ -1828,7 +2281,7 @@ async function promptForSource() {
|
|
|
1828
2281
|
}
|
|
1829
2282
|
async function main(argv = process.argv.slice(2)) {
|
|
1830
2283
|
const options = parseArgs(argv);
|
|
1831
|
-
const pkgInfo = { name: "ctx-switch", version: "2.0.
|
|
2284
|
+
const pkgInfo = { name: "ctx-switch", version: "2.0.6" };
|
|
1832
2285
|
const ui = createTheme(process.stderr);
|
|
1833
2286
|
if (options.help) {
|
|
1834
2287
|
process.stdout.write(`${getHelpText(pkgInfo)}
|
package/package.json
CHANGED