@denisvieiradev/gitwise-core 1.1.1 → 1.3.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/dist/index.d.ts +54 -19
- package/dist/index.js +521 -155
- package/dist/index.js.map +1 -1
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types-BURqSokx.d.ts +36 -0
- package/package.json +2 -2
- package/dist/types-DnMpR1qf.d.ts +0 -29
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@denisvieiradev/gitwise-core",
|
|
10
|
-
version: "1.
|
|
10
|
+
version: "1.3.0",
|
|
11
11
|
description: "Shared logic for gitwise: non-interactive commit/review/pr/release commands, LLM providers, git/github primitives, prompt templates.",
|
|
12
12
|
type: "module",
|
|
13
13
|
main: "./dist/index.js",
|
|
@@ -60,7 +60,7 @@ var package_default = {
|
|
|
60
60
|
node: ">=22.12.0"
|
|
61
61
|
},
|
|
62
62
|
dependencies: {
|
|
63
|
-
"@anthropic-ai/sdk": "^0.
|
|
63
|
+
"@anthropic-ai/sdk": "^0.127.0"
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
|
|
@@ -486,10 +486,10 @@ async function stashDropNamed(cwd, stashName) {
|
|
|
486
486
|
async function cleanForced(cwd) {
|
|
487
487
|
await run(["clean", "-fd"], cwd);
|
|
488
488
|
}
|
|
489
|
-
async function showFileAtHead(cwd,
|
|
490
|
-
debug("git command", { args: ["show", `HEAD:${
|
|
489
|
+
async function showFileAtHead(cwd, path7) {
|
|
490
|
+
debug("git command", { args: ["show", `HEAD:${path7}`], cwd });
|
|
491
491
|
try {
|
|
492
|
-
const result = await exec("git", ["show", `HEAD:${
|
|
492
|
+
const result = await exec("git", ["show", `HEAD:${path7}`], {
|
|
493
493
|
cwd,
|
|
494
494
|
timeout: GIT_TIMEOUT_MS,
|
|
495
495
|
maxBuffer: GIT_MAX_BUFFER
|
|
@@ -890,20 +890,16 @@ function defaultIsProcessAlive(pid) {
|
|
|
890
890
|
}
|
|
891
891
|
|
|
892
892
|
// src/providers/claude-code.ts
|
|
893
|
+
import os2 from "os";
|
|
894
|
+
import path3 from "path";
|
|
895
|
+
|
|
896
|
+
// src/providers/cli-subprocess.ts
|
|
893
897
|
import { execSync, spawn } from "child_process";
|
|
894
898
|
import fs from "fs";
|
|
895
899
|
import os from "os";
|
|
896
900
|
import path2 from "path";
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
var COMMON_CLAUDE_PATHS = [
|
|
900
|
-
// Native installs (Homebrew, manual) — preferred over npm
|
|
901
|
-
"/opt/homebrew/bin/claude",
|
|
902
|
-
"/usr/local/bin/claude",
|
|
903
|
-
path2.join(os.homedir(), ".claude", "local", "claude"),
|
|
904
|
-
// npm global installs — fallback
|
|
905
|
-
path2.join(os.homedir(), ".npm-global", "bin", "claude")
|
|
906
|
-
];
|
|
901
|
+
import { StringDecoder } from "string_decoder";
|
|
902
|
+
import { clearTimeout, setTimeout as setTimeout2 } from "timers";
|
|
907
903
|
function isExecutable(filePath) {
|
|
908
904
|
try {
|
|
909
905
|
fs.accessSync(filePath, fs.constants.X_OK);
|
|
@@ -912,184 +908,441 @@ function isExecutable(filePath) {
|
|
|
912
908
|
return false;
|
|
913
909
|
}
|
|
914
910
|
}
|
|
915
|
-
function
|
|
916
|
-
if (customPath)
|
|
917
|
-
|
|
918
|
-
return null;
|
|
919
|
-
}
|
|
920
|
-
for (const candidate of COMMON_CLAUDE_PATHS) {
|
|
911
|
+
function resolveCliBinary(name, commonPaths, customPath) {
|
|
912
|
+
if (customPath) return isExecutable(customPath) ? customPath : null;
|
|
913
|
+
for (const candidate of commonPaths) {
|
|
921
914
|
if (isExecutable(candidate)) return candidate;
|
|
922
915
|
}
|
|
923
916
|
try {
|
|
924
|
-
const found = execSync(
|
|
917
|
+
const found = execSync(`which ${name}`, { stdio: "pipe" }).toString().trim();
|
|
925
918
|
if (found && isExecutable(found)) return found;
|
|
926
919
|
} catch {
|
|
927
920
|
}
|
|
928
921
|
const nvmDir = path2.join(os.homedir(), ".nvm", "versions", "node");
|
|
929
922
|
try {
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
const candidate = path2.join(nvmDir, version2, "bin", "claude");
|
|
923
|
+
for (const version2 of fs.readdirSync(nvmDir)) {
|
|
924
|
+
const candidate = path2.join(nvmDir, version2, "bin", name);
|
|
933
925
|
if (isExecutable(candidate)) return candidate;
|
|
934
926
|
}
|
|
935
927
|
} catch {
|
|
936
928
|
}
|
|
937
929
|
return null;
|
|
938
930
|
}
|
|
939
|
-
var
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
931
|
+
var LARGE_PROMPT_THRESHOLD = 1e5;
|
|
932
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
933
|
+
var KILL_GRACE_MS = 5e3;
|
|
934
|
+
var CliSubprocessProvider = class {
|
|
935
|
+
constructor(spec, models, cliPath) {
|
|
936
|
+
this.spec = spec;
|
|
943
937
|
this.models = models;
|
|
944
|
-
this.
|
|
938
|
+
this.binaryPath = cliPath || spec.resolveBinary() || spec.defaultCommand;
|
|
945
939
|
}
|
|
940
|
+
binaryPath;
|
|
946
941
|
async chat(req) {
|
|
947
|
-
const modelId = this.
|
|
948
|
-
debug(
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
942
|
+
const modelId = this.models[req.tier];
|
|
943
|
+
debug(`Calling ${this.spec.toolName}`, { model: modelId, tier: req.tier, binary: this.binaryPath });
|
|
944
|
+
const prompt = this.spec.foldSystemPrompt ? `${req.systemPrompt}
|
|
945
|
+
|
|
946
|
+
${req.userMessage}` : req.userMessage;
|
|
947
|
+
const large = Buffer.byteLength(prompt, "utf8") > LARGE_PROMPT_THRESHOLD;
|
|
948
|
+
const args = this.spec.buildArgs({ prompt, systemPrompt: req.systemPrompt, modelId, large });
|
|
949
|
+
const stdout = await this.spawnCli(args, large ? prompt : "");
|
|
950
|
+
const parsed = this.spec.parseOutput(stdout);
|
|
952
951
|
return {
|
|
953
|
-
content:
|
|
954
|
-
tokens: {
|
|
955
|
-
|
|
956
|
-
output: result.usage.output_tokens
|
|
957
|
-
}
|
|
952
|
+
content: parsed.content,
|
|
953
|
+
tokens: parsed.tokens ?? { input: 0, output: 0 },
|
|
954
|
+
tokensAvailable: parsed.tokens !== null
|
|
958
955
|
};
|
|
959
956
|
}
|
|
960
|
-
|
|
961
|
-
const args = [
|
|
962
|
-
"-p",
|
|
963
|
-
...userContent.length <= LARGE_PROMPT_THRESHOLD ? [userContent] : [],
|
|
964
|
-
"--system-prompt",
|
|
965
|
-
systemPrompt,
|
|
966
|
-
"--model",
|
|
967
|
-
modelId,
|
|
968
|
-
"--output-format",
|
|
969
|
-
"json"
|
|
970
|
-
];
|
|
971
|
-
return args;
|
|
972
|
-
}
|
|
973
|
-
async callViaCli(args) {
|
|
974
|
-
return this.spawnClaude(args, "");
|
|
975
|
-
}
|
|
976
|
-
async callViaStdin(args, input) {
|
|
977
|
-
return this.spawnClaude(args, input);
|
|
978
|
-
}
|
|
979
|
-
async spawnClaude(args, input) {
|
|
957
|
+
spawnCli(args, input) {
|
|
980
958
|
return new Promise((resolve, reject) => {
|
|
981
|
-
const
|
|
959
|
+
const ownGroup = process.platform !== "win32";
|
|
960
|
+
const child = spawn(this.binaryPath, args, {
|
|
982
961
|
stdio: ["pipe", "pipe", "pipe"],
|
|
983
|
-
|
|
962
|
+
detached: ownGroup
|
|
984
963
|
});
|
|
964
|
+
const killTree = (sig) => {
|
|
965
|
+
try {
|
|
966
|
+
if (ownGroup && child.pid !== void 0) process.kill(-child.pid, sig);
|
|
967
|
+
else child.kill(sig);
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
const timeoutMs = this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
972
|
+
let timedOut = false;
|
|
973
|
+
let escalation;
|
|
974
|
+
const timer = setTimeout2(() => {
|
|
975
|
+
timedOut = true;
|
|
976
|
+
killTree("SIGTERM");
|
|
977
|
+
escalation = setTimeout2(() => killTree("SIGKILL"), KILL_GRACE_MS);
|
|
978
|
+
escalation.unref();
|
|
979
|
+
}, timeoutMs);
|
|
980
|
+
const reapOnExit = () => killTree("SIGKILL");
|
|
981
|
+
let interruptedBy;
|
|
982
|
+
const onSignal = (sig) => {
|
|
983
|
+
interruptedBy = sig;
|
|
984
|
+
killTree("SIGKILL");
|
|
985
|
+
reject(new Error(`${this.spec.toolName} was interrupted by ${sig}`));
|
|
986
|
+
cleanup();
|
|
987
|
+
if (process.listenerCount(sig) === 0) process.kill(process.pid, sig);
|
|
988
|
+
};
|
|
989
|
+
const onSigint = () => onSignal("SIGINT");
|
|
990
|
+
const onSigterm = () => onSignal("SIGTERM");
|
|
991
|
+
process.once("exit", reapOnExit);
|
|
992
|
+
process.on("SIGINT", onSigint);
|
|
993
|
+
process.on("SIGTERM", onSigterm);
|
|
994
|
+
function cleanup() {
|
|
995
|
+
clearTimeout(timer);
|
|
996
|
+
clearTimeout(escalation);
|
|
997
|
+
process.off("exit", reapOnExit);
|
|
998
|
+
process.off("SIGINT", onSigint);
|
|
999
|
+
process.off("SIGTERM", onSigterm);
|
|
1000
|
+
}
|
|
985
1001
|
let stdout = "";
|
|
986
1002
|
let stderr = "";
|
|
1003
|
+
const outDecoder = new StringDecoder("utf8");
|
|
1004
|
+
const errDecoder = new StringDecoder("utf8");
|
|
987
1005
|
child.stdout.on("data", (data) => {
|
|
988
|
-
stdout +=
|
|
1006
|
+
stdout += outDecoder.write(data);
|
|
989
1007
|
});
|
|
990
1008
|
child.stderr.on("data", (data) => {
|
|
991
|
-
stderr +=
|
|
1009
|
+
stderr += errDecoder.write(data);
|
|
992
1010
|
});
|
|
993
|
-
child.on("close", (code) => {
|
|
994
|
-
if (
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1002
|
-
} catch {
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
const filteredStderr = stderr.replace(/Warning: no stdin data.*\n?/g, "").trim();
|
|
1006
|
-
reject(
|
|
1007
|
-
new Error(
|
|
1008
|
-
`Claude CLI exited with code ${code}${filteredStderr ? `: ${filteredStderr}` : ""}`
|
|
1009
|
-
)
|
|
1010
|
-
);
|
|
1011
|
+
child.on("close", (code, signal) => {
|
|
1012
|
+
if (timedOut) killTree("SIGKILL");
|
|
1013
|
+
cleanup();
|
|
1014
|
+
stdout += outDecoder.end();
|
|
1015
|
+
stderr += errDecoder.end();
|
|
1016
|
+
if (interruptedBy) return;
|
|
1017
|
+
if (timedOut) {
|
|
1018
|
+
reject(new Error(`${this.spec.toolName} timed out after ${Math.round(timeoutMs / 1e3)}s`));
|
|
1011
1019
|
return;
|
|
1012
1020
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1021
|
+
if (code === null && signal) {
|
|
1022
|
+
reject(new Error(`${this.spec.toolName} was terminated by signal ${signal}`));
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (code !== 0) {
|
|
1026
|
+
reject(new Error(this.exitErrorMessage(code, stdout, stderr)));
|
|
1027
|
+
return;
|
|
1017
1028
|
}
|
|
1029
|
+
resolve(stdout);
|
|
1018
1030
|
});
|
|
1019
1031
|
child.on("error", (err) => {
|
|
1032
|
+
cleanup();
|
|
1020
1033
|
reject(this.wrapError(err));
|
|
1021
1034
|
});
|
|
1035
|
+
child.stdin.on("error", () => void 0);
|
|
1022
1036
|
child.stdin.write(input);
|
|
1023
1037
|
child.stdin.end();
|
|
1024
1038
|
});
|
|
1025
1039
|
}
|
|
1026
|
-
|
|
1040
|
+
exitErrorMessage(code, stdout, stderr) {
|
|
1041
|
+
if (this.spec.formatExitError) return this.spec.formatExitError(code, stdout, stderr);
|
|
1042
|
+
const trimmed = stderr.trim();
|
|
1043
|
+
return `${this.spec.toolName} exited with code ${code}${trimmed ? `: ${trimmed}` : ""}`;
|
|
1044
|
+
}
|
|
1045
|
+
wrapError(err) {
|
|
1046
|
+
const message = err instanceof Error ? err.message : String(err?.message ?? err);
|
|
1047
|
+
const code = err?.code;
|
|
1048
|
+
if (code === "ENOENT" || message.includes("ENOENT")) {
|
|
1049
|
+
return new GitwiseError({
|
|
1050
|
+
code: "PROVIDER_UNAVAILABLE",
|
|
1051
|
+
message: `${this.spec.toolName} not found at "${this.binaryPath}". ${this.spec.installHint}`,
|
|
1052
|
+
exitCode: EXIT_CODES.API_FAILED,
|
|
1053
|
+
cause: err
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
return err instanceof Error ? err : new Error(message);
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
// src/providers/claude-code.ts
|
|
1061
|
+
var COMMON_CLAUDE_PATHS = [
|
|
1062
|
+
// Native installs (Homebrew, manual) — preferred over npm
|
|
1063
|
+
"/opt/homebrew/bin/claude",
|
|
1064
|
+
"/usr/local/bin/claude",
|
|
1065
|
+
path3.join(os2.homedir(), ".claude", "local", "claude"),
|
|
1066
|
+
// npm global installs — fallback
|
|
1067
|
+
path3.join(os2.homedir(), ".npm-global", "bin", "claude")
|
|
1068
|
+
];
|
|
1069
|
+
function resolveClaudeBinary(customPath) {
|
|
1070
|
+
return resolveCliBinary("claude", COMMON_CLAUDE_PATHS, customPath);
|
|
1071
|
+
}
|
|
1072
|
+
var claudeCodeSpec = {
|
|
1073
|
+
toolName: "Claude Code CLI",
|
|
1074
|
+
installHint: "Re-run `gw config` to reconfigure.",
|
|
1075
|
+
defaultCommand: "claude",
|
|
1076
|
+
foldSystemPrompt: false,
|
|
1077
|
+
resolveBinary: resolveClaudeBinary,
|
|
1078
|
+
buildArgs({ prompt, systemPrompt, modelId, large }) {
|
|
1079
|
+
return [
|
|
1080
|
+
"-p",
|
|
1081
|
+
...large ? [] : [prompt],
|
|
1082
|
+
"--system-prompt",
|
|
1083
|
+
systemPrompt,
|
|
1084
|
+
"--model",
|
|
1085
|
+
modelId,
|
|
1086
|
+
"--output-format",
|
|
1087
|
+
"json"
|
|
1088
|
+
];
|
|
1089
|
+
},
|
|
1090
|
+
parseOutput(stdout) {
|
|
1027
1091
|
const parsed = JSON.parse(stdout);
|
|
1028
1092
|
if (parsed.is_error) {
|
|
1029
1093
|
throw new Error(`Claude CLI returned error: ${parsed.result}`);
|
|
1030
1094
|
}
|
|
1031
|
-
const usage = { input_tokens: 0, output_tokens: 0 };
|
|
1032
|
-
if (parsed.usage) {
|
|
1033
|
-
usage.input_tokens = parsed.usage.input_tokens ?? 0;
|
|
1034
|
-
usage.output_tokens = parsed.usage.output_tokens ?? 0;
|
|
1035
|
-
}
|
|
1036
1095
|
return {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1096
|
+
content: parsed.result ?? "",
|
|
1097
|
+
tokens: {
|
|
1098
|
+
input: parsed.usage?.input_tokens ?? 0,
|
|
1099
|
+
output: parsed.usage?.output_tokens ?? 0
|
|
1100
|
+
}
|
|
1040
1101
|
};
|
|
1102
|
+
},
|
|
1103
|
+
formatExitError(code, stdout, stderr) {
|
|
1104
|
+
if (stdout) {
|
|
1105
|
+
try {
|
|
1106
|
+
const parsed = JSON.parse(stdout);
|
|
1107
|
+
if (parsed.is_error) return `Claude CLI error: ${parsed.result}`;
|
|
1108
|
+
} catch {
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const filteredStderr = stderr.replace(/Warning: no stdin data.*\n?/g, "").trim();
|
|
1112
|
+
return `Claude CLI exited with code ${code}${filteredStderr ? `: ${filteredStderr}` : ""}`;
|
|
1041
1113
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1114
|
+
};
|
|
1115
|
+
var ClaudeCodeProvider = class extends CliSubprocessProvider {
|
|
1116
|
+
constructor(models, claudeCliPath) {
|
|
1117
|
+
super(claudeCodeSpec, models, claudeCliPath);
|
|
1044
1118
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
// src/providers/codex.ts
|
|
1122
|
+
import os3 from "os";
|
|
1123
|
+
import path4 from "path";
|
|
1124
|
+
var COMMON_CODEX_PATHS = [
|
|
1125
|
+
// Native installs (Homebrew cask, manual, standalone installer) — preferred over npm
|
|
1126
|
+
"/opt/homebrew/bin/codex",
|
|
1127
|
+
"/usr/local/bin/codex",
|
|
1128
|
+
path4.join(os3.homedir(), ".local", "bin", "codex"),
|
|
1129
|
+
// npm global installs (`npm install -g @openai/codex`) — fallback
|
|
1130
|
+
path4.join(os3.homedir(), ".npm-global", "bin", "codex")
|
|
1131
|
+
];
|
|
1132
|
+
function resolveCodexBinary(customPath) {
|
|
1133
|
+
return resolveCliBinary("codex", COMMON_CODEX_PATHS, customPath);
|
|
1134
|
+
}
|
|
1135
|
+
function parseEvents(stdout) {
|
|
1136
|
+
const events = [];
|
|
1137
|
+
for (const line of stdout.split("\n")) {
|
|
1138
|
+
const trimmed = line.trim();
|
|
1139
|
+
if (!trimmed) continue;
|
|
1140
|
+
try {
|
|
1141
|
+
events.push(JSON.parse(trimmed));
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return events;
|
|
1146
|
+
}
|
|
1147
|
+
var codexSpec = {
|
|
1148
|
+
toolName: "Codex CLI",
|
|
1149
|
+
installHint: "Install it (`npm install -g @openai/codex`) or re-run `gw provider` to choose another provider.",
|
|
1150
|
+
defaultCommand: "codex",
|
|
1151
|
+
foldSystemPrompt: true,
|
|
1152
|
+
// Full agent turns take longer than a single Claude Code completion.
|
|
1153
|
+
timeoutMs: 3e5,
|
|
1154
|
+
resolveBinary: resolveCodexBinary,
|
|
1155
|
+
buildArgs({ prompt, modelId, large }) {
|
|
1156
|
+
return [
|
|
1157
|
+
"exec",
|
|
1158
|
+
"--json",
|
|
1159
|
+
"--ephemeral",
|
|
1160
|
+
"--skip-git-repo-check",
|
|
1161
|
+
"--sandbox",
|
|
1162
|
+
"read-only",
|
|
1163
|
+
"--model",
|
|
1164
|
+
modelId,
|
|
1165
|
+
"--",
|
|
1166
|
+
large ? "-" : prompt
|
|
1167
|
+
];
|
|
1168
|
+
},
|
|
1169
|
+
parseOutput(stdout) {
|
|
1170
|
+
let content;
|
|
1171
|
+
let tokens = null;
|
|
1172
|
+
for (const event of parseEvents(stdout)) {
|
|
1173
|
+
if (event.type === "item.completed" && event.item?.type === "agent_message") {
|
|
1174
|
+
content = event.item.text ?? "";
|
|
1175
|
+
} else if (event.type === "turn.completed" && event.usage) {
|
|
1176
|
+
tokens = {
|
|
1177
|
+
input: event.usage.input_tokens ?? 0,
|
|
1178
|
+
output: event.usage.output_tokens ?? 0
|
|
1179
|
+
};
|
|
1054
1180
|
}
|
|
1055
|
-
return err;
|
|
1056
1181
|
}
|
|
1057
|
-
|
|
1182
|
+
if (content === void 0) {
|
|
1183
|
+
throw new Error("Codex CLI returned no final agent message");
|
|
1184
|
+
}
|
|
1185
|
+
return { content, tokens };
|
|
1186
|
+
},
|
|
1187
|
+
// The CLI's own error text lives in the JSONL stream, not stderr; surface it
|
|
1188
|
+
// verbatim, falling back to stderr when the stream carries none.
|
|
1189
|
+
formatExitError(code, stdout, stderr) {
|
|
1190
|
+
const messages = /* @__PURE__ */ new Set();
|
|
1191
|
+
for (const event of parseEvents(stdout)) {
|
|
1192
|
+
if (event.type === "error" && event.message) messages.add(event.message);
|
|
1193
|
+
if (event.type === "turn.failed" && event.error?.message) messages.add(event.error.message);
|
|
1194
|
+
}
|
|
1195
|
+
const detail = messages.size > 0 ? [...messages].join("\n") : stderr.trim();
|
|
1196
|
+
return `Codex CLI exited with code ${code}${detail ? `: ${detail}` : ""}`;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
// src/providers/copilot.ts
|
|
1201
|
+
import os4 from "os";
|
|
1202
|
+
import path5 from "path";
|
|
1203
|
+
var COMMON_COPILOT_PATHS = [
|
|
1204
|
+
// Native/Homebrew installs — preferred over npm
|
|
1205
|
+
"/opt/homebrew/bin/copilot",
|
|
1206
|
+
"/usr/local/bin/copilot",
|
|
1207
|
+
// Copilot's install script (non-root) target
|
|
1208
|
+
path5.join(os4.homedir(), ".local", "bin", "copilot"),
|
|
1209
|
+
// npm global installs (`npm install -g @github/copilot`) — fallback
|
|
1210
|
+
path5.join(os4.homedir(), ".npm-global", "bin", "copilot")
|
|
1211
|
+
];
|
|
1212
|
+
function resolveCopilotBinary(customPath) {
|
|
1213
|
+
return resolveCliBinary("copilot", COMMON_COPILOT_PATHS, customPath);
|
|
1214
|
+
}
|
|
1215
|
+
var copilotSpec = {
|
|
1216
|
+
toolName: "Copilot CLI",
|
|
1217
|
+
installHint: "Install it (`npm install -g @github/copilot`) or re-run `gw provider` to choose another provider.",
|
|
1218
|
+
defaultCommand: "copilot",
|
|
1219
|
+
foldSystemPrompt: true,
|
|
1220
|
+
// Full agent turns take longer than a single Claude Code completion.
|
|
1221
|
+
timeoutMs: 3e5,
|
|
1222
|
+
resolveBinary: resolveCopilotBinary,
|
|
1223
|
+
buildArgs({ prompt, modelId, large }) {
|
|
1224
|
+
return [...large ? [] : [`--prompt=${prompt}`], "--no-ask-user", "--silent", "--model", modelId];
|
|
1225
|
+
},
|
|
1226
|
+
parseOutput(stdout) {
|
|
1227
|
+
const content = stdout.trim();
|
|
1228
|
+
if (!content) throw new Error("Copilot CLI returned an empty response");
|
|
1229
|
+
return { content, tokens: null };
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
// src/providers/kiro.ts
|
|
1234
|
+
import os5 from "os";
|
|
1235
|
+
import path6 from "path";
|
|
1236
|
+
import { stripVTControlCharacters } from "util";
|
|
1237
|
+
var COMMON_KIRO_PATHS = [
|
|
1238
|
+
// macOS app bundle and the installer's symlink location — preferred
|
|
1239
|
+
"/Applications/Kiro CLI.app/Contents/MacOS/kiro-cli",
|
|
1240
|
+
path6.join(os5.homedir(), ".local", "bin", "kiro-cli"),
|
|
1241
|
+
"/opt/homebrew/bin/kiro-cli",
|
|
1242
|
+
"/usr/local/bin/kiro-cli"
|
|
1243
|
+
];
|
|
1244
|
+
function resolveKiroBinary(customPath) {
|
|
1245
|
+
return resolveCliBinary("kiro-cli", COMMON_KIRO_PATHS, customPath);
|
|
1246
|
+
}
|
|
1247
|
+
var kiroSpec = {
|
|
1248
|
+
toolName: "Kiro CLI",
|
|
1249
|
+
installHint: "Install it from https://kiro.dev/docs/cli/ or re-run `gw provider` to choose another provider.",
|
|
1250
|
+
defaultCommand: "kiro-cli",
|
|
1251
|
+
foldSystemPrompt: true,
|
|
1252
|
+
// Full agent turns take longer than a single Claude Code completion.
|
|
1253
|
+
timeoutMs: 3e5,
|
|
1254
|
+
resolveBinary: resolveKiroBinary,
|
|
1255
|
+
buildArgs({ prompt, modelId, large }) {
|
|
1256
|
+
return [
|
|
1257
|
+
"chat",
|
|
1258
|
+
"--no-interactive",
|
|
1259
|
+
"--trust-tools=",
|
|
1260
|
+
"--wrap",
|
|
1261
|
+
"never",
|
|
1262
|
+
"--model",
|
|
1263
|
+
modelId,
|
|
1264
|
+
// `--` (standard for kiro-cli's clap-style parser) keeps a prompt that
|
|
1265
|
+
// starts with "-" from being read as an option.
|
|
1266
|
+
...large ? [] : ["--", prompt]
|
|
1267
|
+
];
|
|
1268
|
+
},
|
|
1269
|
+
parseOutput(stdout) {
|
|
1270
|
+
const content = stripVTControlCharacters(stdout).trim();
|
|
1271
|
+
if (!content) throw new Error("Kiro CLI returned an empty response");
|
|
1272
|
+
return { content, tokens: null };
|
|
1273
|
+
},
|
|
1274
|
+
formatExitError(code, stdout, stderr) {
|
|
1275
|
+
const detail = stderr.trim() || stripVTControlCharacters(stdout).trim();
|
|
1276
|
+
return `Kiro CLI exited with code ${code}${detail ? `: ${detail}` : ""}`;
|
|
1058
1277
|
}
|
|
1059
1278
|
};
|
|
1060
1279
|
|
|
1061
1280
|
// src/config/types.ts
|
|
1281
|
+
var CLAUDE_MODELS = {
|
|
1282
|
+
fast: "claude-haiku-4-5-20251001",
|
|
1283
|
+
balanced: "claude-sonnet-4-6",
|
|
1284
|
+
powerful: "claude-opus-4-7"
|
|
1285
|
+
};
|
|
1062
1286
|
var DEFAULT_USER_CONFIG = {
|
|
1063
1287
|
provider: "api",
|
|
1064
1288
|
models: {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1289
|
+
api: { ...CLAUDE_MODELS },
|
|
1290
|
+
"claude-code": { ...CLAUDE_MODELS },
|
|
1291
|
+
codex: {
|
|
1292
|
+
fast: "gpt-6-luna",
|
|
1293
|
+
balanced: "gpt-6-sol",
|
|
1294
|
+
powerful: "gpt-6-astra"
|
|
1295
|
+
},
|
|
1296
|
+
copilot: {
|
|
1297
|
+
fast: "claude-haiku-4.5",
|
|
1298
|
+
balanced: "claude-sonnet-4.6",
|
|
1299
|
+
powerful: "claude-opus-4.7"
|
|
1300
|
+
},
|
|
1301
|
+
kiro: {
|
|
1302
|
+
fast: "claude-haiku-4.5",
|
|
1303
|
+
balanced: "claude-sonnet-4.5",
|
|
1304
|
+
powerful: "claude-sonnet-4.5"
|
|
1305
|
+
}
|
|
1068
1306
|
},
|
|
1069
1307
|
language: "en",
|
|
1070
1308
|
commitConvention: "conventional"
|
|
1071
1309
|
};
|
|
1072
1310
|
|
|
1073
1311
|
// src/config/merge.ts
|
|
1074
|
-
import
|
|
1312
|
+
import os7 from "os";
|
|
1075
1313
|
|
|
1076
1314
|
// src/config/user.ts
|
|
1077
1315
|
import { join as join2 } from "path";
|
|
1078
|
-
import
|
|
1316
|
+
import os6 from "os";
|
|
1317
|
+
|
|
1318
|
+
// src/providers/types.ts
|
|
1319
|
+
var PROVIDER_KINDS = ["api", "claude-code", "codex", "copilot", "kiro"];
|
|
1320
|
+
|
|
1321
|
+
// src/config/user.ts
|
|
1079
1322
|
var GITWISE_DIR = ".gitwise";
|
|
1080
1323
|
var USER_CONFIG_FILE = "config.json";
|
|
1081
1324
|
function getUserConfigPath(homeDir) {
|
|
1082
|
-
return join2(homeDir ??
|
|
1325
|
+
return join2(homeDir ?? os6.homedir(), GITWISE_DIR, USER_CONFIG_FILE);
|
|
1326
|
+
}
|
|
1327
|
+
function isLegacyFlatModels(value) {
|
|
1328
|
+
if (!value || typeof value !== "object") return false;
|
|
1329
|
+
const v = value;
|
|
1330
|
+
return typeof v["fast"] === "string" && typeof v["balanced"] === "string" && typeof v["powerful"] === "string";
|
|
1331
|
+
}
|
|
1332
|
+
function migrateFlatModels(flat, provider) {
|
|
1333
|
+
const target = provider === void 0 ? DEFAULT_USER_CONFIG.provider : provider;
|
|
1334
|
+
const migrated = { ...DEFAULT_USER_CONFIG.models };
|
|
1335
|
+
if (typeof target === "string" && PROVIDER_KINDS.includes(target)) {
|
|
1336
|
+
migrated[target] = { ...flat };
|
|
1337
|
+
}
|
|
1338
|
+
return migrated;
|
|
1083
1339
|
}
|
|
1084
1340
|
function mergeWithDefaults(partial) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
...partial
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
...partial.models ?? {}
|
|
1091
|
-
}
|
|
1092
|
-
};
|
|
1341
|
+
const models = {};
|
|
1342
|
+
for (const kind of PROVIDER_KINDS) {
|
|
1343
|
+
models[kind] = { ...DEFAULT_USER_CONFIG.models[kind], ...partial.models?.[kind] ?? {} };
|
|
1344
|
+
}
|
|
1345
|
+
return { ...DEFAULT_USER_CONFIG, ...partial, models };
|
|
1093
1346
|
}
|
|
1094
1347
|
async function readUserConfig(homeDir) {
|
|
1095
1348
|
const configPath = getUserConfigPath(homeDir);
|
|
@@ -1098,6 +1351,17 @@ async function readUserConfig(homeDir) {
|
|
|
1098
1351
|
return { ...DEFAULT_USER_CONFIG };
|
|
1099
1352
|
}
|
|
1100
1353
|
const raw = await readJSON(configPath);
|
|
1354
|
+
if (isLegacyFlatModels(raw.models)) {
|
|
1355
|
+
const migratedModels = migrateFlatModels(raw.models, raw.provider);
|
|
1356
|
+
const merged = mergeWithDefaults({ ...raw, models: migratedModels });
|
|
1357
|
+
debug("Migrated legacy flat models config to per-provider shape", { path: configPath });
|
|
1358
|
+
try {
|
|
1359
|
+
await writeJSON(configPath, merged);
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
debug("Could not persist migrated config; using it in memory", { path: configPath, error: String(err) });
|
|
1362
|
+
}
|
|
1363
|
+
return merged;
|
|
1364
|
+
}
|
|
1101
1365
|
return mergeWithDefaults(raw);
|
|
1102
1366
|
}
|
|
1103
1367
|
async function writeUserConfig(partial, homeDir) {
|
|
@@ -1108,7 +1372,7 @@ async function writeUserConfig(partial, homeDir) {
|
|
|
1108
1372
|
await writeJSON(configPath, updated);
|
|
1109
1373
|
}
|
|
1110
1374
|
async function writeApiKey(value, homeDir) {
|
|
1111
|
-
const home = homeDir ??
|
|
1375
|
+
const home = homeDir ?? os6.homedir();
|
|
1112
1376
|
await writeEnvVar(home, "ANTHROPIC_API_KEY", value);
|
|
1113
1377
|
}
|
|
1114
1378
|
|
|
@@ -1135,6 +1399,26 @@ async function readRepoConfig(cwd) {
|
|
|
1135
1399
|
}
|
|
1136
1400
|
|
|
1137
1401
|
// src/config/merge.ts
|
|
1402
|
+
function isPlainObject(value) {
|
|
1403
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1404
|
+
}
|
|
1405
|
+
var MODEL_TIERS = ["fast", "balanced", "powerful"];
|
|
1406
|
+
function mergeRepoModels(base, override) {
|
|
1407
|
+
if (!isPlainObject(override)) return base.models;
|
|
1408
|
+
const source = override;
|
|
1409
|
+
const flat = {};
|
|
1410
|
+
for (const tier of MODEL_TIERS) {
|
|
1411
|
+
const value = source[tier];
|
|
1412
|
+
if (typeof value === "string") flat[tier] = value;
|
|
1413
|
+
}
|
|
1414
|
+
const merged = { ...base.models };
|
|
1415
|
+
merged[base.provider] = { ...base.models[base.provider], ...flat };
|
|
1416
|
+
for (const kind of PROVIDER_KINDS) {
|
|
1417
|
+
const block = source[kind];
|
|
1418
|
+
if (isPlainObject(block)) merged[kind] = { ...merged[kind], ...block };
|
|
1419
|
+
}
|
|
1420
|
+
return merged;
|
|
1421
|
+
}
|
|
1138
1422
|
function deepMerge(base, override) {
|
|
1139
1423
|
return {
|
|
1140
1424
|
...base,
|
|
@@ -1144,10 +1428,7 @@ function deepMerge(base, override) {
|
|
|
1144
1428
|
...override.templatesPath !== void 0 && { templatesPath: override.templatesPath },
|
|
1145
1429
|
...override.releaseStrategy !== void 0 && { releaseStrategy: override.releaseStrategy },
|
|
1146
1430
|
...override.developBranch !== void 0 && { developBranch: override.developBranch },
|
|
1147
|
-
models:
|
|
1148
|
-
...base.models,
|
|
1149
|
-
...override.models ?? {}
|
|
1150
|
-
}
|
|
1431
|
+
models: mergeRepoModels(base, override.models)
|
|
1151
1432
|
};
|
|
1152
1433
|
}
|
|
1153
1434
|
async function getMergedConfig(options) {
|
|
@@ -1160,7 +1441,7 @@ async function getMergedConfig(options) {
|
|
|
1160
1441
|
return deepMerge(userConfig, repoConfig);
|
|
1161
1442
|
}
|
|
1162
1443
|
async function getApiKey(homeDir) {
|
|
1163
|
-
const home = homeDir ??
|
|
1444
|
+
const home = homeDir ?? os7.homedir();
|
|
1164
1445
|
return read("ANTHROPIC_API_KEY", home);
|
|
1165
1446
|
}
|
|
1166
1447
|
|
|
@@ -1168,7 +1449,7 @@ async function getApiKey(homeDir) {
|
|
|
1168
1449
|
import { readFile as readFile4 } from "fs/promises";
|
|
1169
1450
|
import { dirname as dirname2, join as join4 } from "path";
|
|
1170
1451
|
import { fileURLToPath } from "url";
|
|
1171
|
-
import
|
|
1452
|
+
import os8 from "os";
|
|
1172
1453
|
|
|
1173
1454
|
// src/template/interpolate.ts
|
|
1174
1455
|
function interpolate(template, ctx) {
|
|
@@ -1196,7 +1477,7 @@ function validateTemplateName(name) {
|
|
|
1196
1477
|
async function loadTemplate(name, options = {}) {
|
|
1197
1478
|
validateTemplateName(name);
|
|
1198
1479
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1199
|
-
const userTemplatesPath = options.templatesPath ?? join4(
|
|
1480
|
+
const userTemplatesPath = options.templatesPath ?? join4(os8.homedir(), ".gitwise", "templates");
|
|
1200
1481
|
const repoOverride = join4(repoRoot, ".gitwise", "templates", `${name}.md`);
|
|
1201
1482
|
if (await fileExists(repoOverride)) {
|
|
1202
1483
|
debug("Loading repo-level template override", { path: repoOverride });
|
|
@@ -1437,20 +1718,22 @@ IMPORTANT: Generate exactly 3 different alternative commit messages. Return JSON
|
|
|
1437
1718
|
const response = await provider.chat({ systemPrompt: effectiveSystemPrompt, userMessage, tier });
|
|
1438
1719
|
const parsed = parseCommitResponse(response.content);
|
|
1439
1720
|
const tokens = { input: response.tokens.input, output: response.tokens.output };
|
|
1721
|
+
const tokensAvailable = response.tokensAvailable;
|
|
1440
1722
|
if (opts.generateAlternatives) {
|
|
1441
1723
|
const options = parseAlternativesResponse(response.content);
|
|
1442
1724
|
if (options && options.length > 0) {
|
|
1443
|
-
return { kind: "alternatives", options, tokens };
|
|
1725
|
+
return { kind: "alternatives", options, tokens, tokensAvailable };
|
|
1444
1726
|
}
|
|
1445
1727
|
const fallbackMsg = parsed.type === "single" ? parsed.message : parsed.commits[0]?.message ?? response.content.trim().slice(0, 100);
|
|
1446
|
-
return { kind: "alternatives", options: [fallbackMsg], tokens };
|
|
1728
|
+
return { kind: "alternatives", options: [fallbackMsg], tokens, tokensAvailable };
|
|
1447
1729
|
}
|
|
1448
1730
|
if (split === "never") {
|
|
1449
1731
|
const message2 = parsed.type === "single" ? parsed.message : parsed.commits.map((c) => c.message).join("\n\n");
|
|
1450
1732
|
return {
|
|
1451
1733
|
kind: "single",
|
|
1452
1734
|
commits: [{ message: message2, files: stagedFiles }],
|
|
1453
|
-
tokens
|
|
1735
|
+
tokens,
|
|
1736
|
+
tokensAvailable
|
|
1454
1737
|
};
|
|
1455
1738
|
}
|
|
1456
1739
|
if (parsed.type === "plan" && parsed.commits.length > 1) {
|
|
@@ -1463,7 +1746,8 @@ IMPORTANT: Generate exactly 3 different alternative commit messages. Return JSON
|
|
|
1463
1746
|
return {
|
|
1464
1747
|
kind: "split",
|
|
1465
1748
|
commits: parsed.commits,
|
|
1466
|
-
tokens
|
|
1749
|
+
tokens,
|
|
1750
|
+
tokensAvailable
|
|
1467
1751
|
};
|
|
1468
1752
|
}
|
|
1469
1753
|
}
|
|
@@ -1478,7 +1762,8 @@ IMPORTANT: Generate exactly 3 different alternative commit messages. Return JSON
|
|
|
1478
1762
|
return {
|
|
1479
1763
|
kind: "single",
|
|
1480
1764
|
commits: [{ message, files: stagedFiles }],
|
|
1481
|
-
tokens
|
|
1765
|
+
tokens,
|
|
1766
|
+
tokensAvailable
|
|
1482
1767
|
};
|
|
1483
1768
|
}
|
|
1484
1769
|
function takeNamedStashStep(cwd, stashName) {
|
|
@@ -1688,7 +1973,8 @@ Additional context: ${prompt}` : "");
|
|
|
1688
1973
|
suggestions: parsed.suggestions,
|
|
1689
1974
|
nitpicks: parsed.nitpicks,
|
|
1690
1975
|
markdown,
|
|
1691
|
-
tokens
|
|
1976
|
+
tokens,
|
|
1977
|
+
tokensAvailable: response.tokensAvailable
|
|
1692
1978
|
};
|
|
1693
1979
|
}
|
|
1694
1980
|
async function resolveBaseBranch(cwd) {
|
|
@@ -1796,7 +2082,8 @@ Additional context: ${prompt}` : "");
|
|
|
1796
2082
|
title,
|
|
1797
2083
|
body,
|
|
1798
2084
|
existingPrNumber,
|
|
1799
|
-
tokens
|
|
2085
|
+
tokens,
|
|
2086
|
+
tokensAvailable: response.tokensAvailable
|
|
1800
2087
|
};
|
|
1801
2088
|
}
|
|
1802
2089
|
async function applyPr(draft, opts) {
|
|
@@ -1913,7 +2200,7 @@ async function loadReleasePlan(cwd) {
|
|
|
1913
2200
|
exitCode: EXIT_CODES.CONFIG_INVALID
|
|
1914
2201
|
});
|
|
1915
2202
|
}
|
|
1916
|
-
return parsed;
|
|
2203
|
+
return { ...parsed, tokensAvailable: parsed.tokensAvailable ?? true };
|
|
1917
2204
|
}
|
|
1918
2205
|
function isPersistedReleasePlan(value) {
|
|
1919
2206
|
if (!value || typeof value !== "object") return false;
|
|
@@ -1936,6 +2223,7 @@ function isPersistedReleasePlan(value) {
|
|
|
1936
2223
|
const tokens = p.tokens;
|
|
1937
2224
|
if (typeof tokens.input !== "number" || !Number.isFinite(tokens.input)) return false;
|
|
1938
2225
|
if (typeof tokens.output !== "number" || !Number.isFinite(tokens.output)) return false;
|
|
2226
|
+
if (p.tokensAvailable !== void 0 && typeof p.tokensAvailable !== "boolean") return false;
|
|
1939
2227
|
return true;
|
|
1940
2228
|
}
|
|
1941
2229
|
async function deleteReleasePlan(cwd) {
|
|
@@ -2067,6 +2355,7 @@ async function release(opts) {
|
|
|
2067
2355
|
const tier = resolveModelTier("release");
|
|
2068
2356
|
let totalInput = 0;
|
|
2069
2357
|
let totalOutput = 0;
|
|
2358
|
+
let tokensAvailable = true;
|
|
2070
2359
|
let suggestedBump;
|
|
2071
2360
|
if (opts.bump) {
|
|
2072
2361
|
suggestedBump = opts.bump;
|
|
@@ -2084,6 +2373,7 @@ ${commits}`,
|
|
|
2084
2373
|
});
|
|
2085
2374
|
totalInput += versionResponse.tokens.input;
|
|
2086
2375
|
totalOutput += versionResponse.tokens.output;
|
|
2376
|
+
tokensAvailable = versionResponse.tokensAvailable;
|
|
2087
2377
|
const suggestion = parseVersionSuggestion(versionResponse.content);
|
|
2088
2378
|
suggestedBump = suggestion?.suggestion ?? heuristicBump(commits);
|
|
2089
2379
|
}
|
|
@@ -2101,6 +2391,7 @@ ${commits}`,
|
|
|
2101
2391
|
});
|
|
2102
2392
|
totalInput += changelogResponse.tokens.input;
|
|
2103
2393
|
totalOutput += changelogResponse.tokens.output;
|
|
2394
|
+
tokensAvailable = tokensAvailable && changelogResponse.tokensAvailable;
|
|
2104
2395
|
const changelog = changelogResponse.content;
|
|
2105
2396
|
const notesTemplate = await loadTemplate("release-notes", templateOpts);
|
|
2106
2397
|
const notesPrompt = interpolate(notesTemplate, {
|
|
@@ -2119,6 +2410,7 @@ ${commits}`,
|
|
|
2119
2410
|
});
|
|
2120
2411
|
totalInput += notesResponse.tokens.input;
|
|
2121
2412
|
totalOutput += notesResponse.tokens.output;
|
|
2413
|
+
tokensAvailable = tokensAvailable && notesResponse.tokensAvailable;
|
|
2122
2414
|
const notes = notesResponse.content;
|
|
2123
2415
|
return {
|
|
2124
2416
|
suggestedBump,
|
|
@@ -2127,7 +2419,8 @@ ${commits}`,
|
|
|
2127
2419
|
changelog,
|
|
2128
2420
|
notes,
|
|
2129
2421
|
commits,
|
|
2130
|
-
tokens: { input: totalInput, output: totalOutput }
|
|
2422
|
+
tokens: { input: totalInput, output: totalOutput },
|
|
2423
|
+
tokensAvailable
|
|
2131
2424
|
};
|
|
2132
2425
|
}
|
|
2133
2426
|
function createReleaseBranchStep(cwd, branchName, startPoint) {
|
|
@@ -2275,10 +2568,10 @@ async function prepareRelease(opts) {
|
|
|
2275
2568
|
});
|
|
2276
2569
|
try {
|
|
2277
2570
|
const dirtyEntries = (await status(cwd)).split("\n").map((line) => line.replace(/\s+$/, "")).filter((line) => line.length >= 3).filter((line) => {
|
|
2278
|
-
const
|
|
2279
|
-
if (
|
|
2280
|
-
if (
|
|
2281
|
-
if (
|
|
2571
|
+
const path7 = line.slice(3).trim();
|
|
2572
|
+
if (path7 === ".gitignore") return false;
|
|
2573
|
+
if (path7 === ".gitwise/" || path7 === ".gitwise") return false;
|
|
2574
|
+
if (path7.startsWith(".gitwise/")) return false;
|
|
2282
2575
|
return true;
|
|
2283
2576
|
});
|
|
2284
2577
|
if (dirtyEntries.length > 0) {
|
|
@@ -2376,7 +2669,8 @@ ${dirtyEntries.join("\n")}`,
|
|
|
2376
2669
|
baseCommit,
|
|
2377
2670
|
targetBranch,
|
|
2378
2671
|
releaseBranchCreated: releaseBranch !== null,
|
|
2379
|
-
tokens: plan.tokens
|
|
2672
|
+
tokens: plan.tokens,
|
|
2673
|
+
tokensAvailable: plan.tokensAvailable
|
|
2380
2674
|
};
|
|
2381
2675
|
await tx.run(savePlanStep(cwd, persistedPlan));
|
|
2382
2676
|
debug("release.prepare.plan.saved", {
|
|
@@ -2441,7 +2735,8 @@ ${dirty}`,
|
|
|
2441
2735
|
baseCommit: await headSha(cwd),
|
|
2442
2736
|
targetBranch: await getBranch(cwd),
|
|
2443
2737
|
releaseBranchCreated: false,
|
|
2444
|
-
tokens: plan.tokens
|
|
2738
|
+
tokens: plan.tokens,
|
|
2739
|
+
tokensAvailable: plan.tokensAvailable
|
|
2445
2740
|
};
|
|
2446
2741
|
await ensureGitignored(cwd, RELEASE_PLAN_REL_PATH);
|
|
2447
2742
|
await ensureGitignored(cwd, RELEASE_NOTES_GLOB_REL_PATH);
|
|
@@ -2790,13 +3085,35 @@ async function gitignoreMatchesPrepareOutput(cwd) {
|
|
|
2790
3085
|
expected = applyGitignoreEntry(expected, RELEASE_NOTES_GLOB_REL_PATH);
|
|
2791
3086
|
return currentContent === expected;
|
|
2792
3087
|
}
|
|
2793
|
-
|
|
3088
|
+
var CROSS_WORKSPACE_DEPENDENCY_FIELDS = [
|
|
3089
|
+
"dependencies",
|
|
3090
|
+
"devDependencies",
|
|
3091
|
+
"optionalDependencies",
|
|
3092
|
+
"peerDependencies"
|
|
3093
|
+
];
|
|
3094
|
+
function updateCrossWorkspaceDependencies(parsed, workspaceNames, newVersion) {
|
|
3095
|
+
for (const field of CROSS_WORKSPACE_DEPENDENCY_FIELDS) {
|
|
3096
|
+
const deps = parsed[field];
|
|
3097
|
+
if (!deps || typeof deps !== "object") continue;
|
|
3098
|
+
const depsRecord = deps;
|
|
3099
|
+
for (const depName of Object.keys(depsRecord)) {
|
|
3100
|
+
const spec = depsRecord[depName];
|
|
3101
|
+
if (!workspaceNames.has(depName) || typeof spec !== "string") continue;
|
|
3102
|
+
const prefix = spec.startsWith("^") || spec.startsWith("~") ? spec[0] : "";
|
|
3103
|
+
depsRecord[depName] = `${prefix}${newVersion}`;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
function writeWorkspaceVersionStep(manifestPath, newVersion, workspaceNames) {
|
|
2794
3108
|
return {
|
|
2795
3109
|
name: `write-version:${manifestPath}`,
|
|
2796
3110
|
apply: async () => {
|
|
2797
3111
|
const priorBytes = await readFile6(manifestPath);
|
|
2798
3112
|
const parsed = JSON.parse(priorBytes.toString("utf-8"));
|
|
2799
3113
|
parsed["version"] = newVersion;
|
|
3114
|
+
if (workspaceNames && workspaceNames.size > 0) {
|
|
3115
|
+
updateCrossWorkspaceDependencies(parsed, workspaceNames, newVersion);
|
|
3116
|
+
}
|
|
2800
3117
|
await writeJSON(manifestPath, parsed);
|
|
2801
3118
|
return priorBytes;
|
|
2802
3119
|
},
|
|
@@ -2835,17 +3152,28 @@ async function propagateVersionToWorkspaces(cwd, version2) {
|
|
|
2835
3152
|
async function runWorkspaceVersionStepsInto(tx, cwd, version2) {
|
|
2836
3153
|
const patterns = await readWorkspacePatterns(cwd);
|
|
2837
3154
|
const workspaceDirs = (await expandWorkspacePatterns(cwd, patterns)).sort();
|
|
3155
|
+
const workspaceNames = /* @__PURE__ */ new Set();
|
|
3156
|
+
for (const dir of workspaceDirs) {
|
|
3157
|
+
const pkgPath = join6(dir, "package.json");
|
|
3158
|
+
if (!await fileExists(pkgPath)) continue;
|
|
3159
|
+
const parsed = await readJSON(pkgPath);
|
|
3160
|
+
if (typeof parsed.name === "string") workspaceNames.add(parsed.name);
|
|
3161
|
+
}
|
|
2838
3162
|
const modified = [];
|
|
2839
3163
|
for (const dir of workspaceDirs) {
|
|
2840
3164
|
const pkgPath = join6(dir, "package.json");
|
|
2841
3165
|
if (await fileExists(pkgPath)) {
|
|
2842
|
-
await tx.run(writeWorkspaceVersionStep(pkgPath, version2));
|
|
3166
|
+
await tx.run(writeWorkspaceVersionStep(pkgPath, version2, workspaceNames));
|
|
2843
3167
|
modified.push(relative(cwd, pkgPath));
|
|
2844
3168
|
}
|
|
2845
|
-
const pluginPath
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
3169
|
+
for (const pluginPath of [
|
|
3170
|
+
join6(dir, ".claude-plugin", "plugin.json"),
|
|
3171
|
+
join6(dir, "plugin.json")
|
|
3172
|
+
]) {
|
|
3173
|
+
if (await fileExists(pluginPath)) {
|
|
3174
|
+
await tx.run(writeWorkspaceVersionStep(pluginPath, version2));
|
|
3175
|
+
modified.push(relative(cwd, pluginPath));
|
|
3176
|
+
}
|
|
2849
3177
|
}
|
|
2850
3178
|
}
|
|
2851
3179
|
return modified;
|
|
@@ -2937,6 +3265,11 @@ function segmentToRegex(segment) {
|
|
|
2937
3265
|
return new RegExp(`^${escaped}$`);
|
|
2938
3266
|
}
|
|
2939
3267
|
|
|
3268
|
+
// src/commands/token-format.ts
|
|
3269
|
+
function formatTokens(tokens, tokensAvailable) {
|
|
3270
|
+
return tokensAvailable ? `${tokens.input} in / ${tokens.output} out` : "n/a";
|
|
3271
|
+
}
|
|
3272
|
+
|
|
2940
3273
|
// src/providers/anthropic.ts
|
|
2941
3274
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2942
3275
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -2993,7 +3326,8 @@ var AnthropicProvider = class {
|
|
|
2993
3326
|
tokens: {
|
|
2994
3327
|
input: response.usage.input_tokens,
|
|
2995
3328
|
output: response.usage.output_tokens
|
|
2996
|
-
}
|
|
3329
|
+
},
|
|
3330
|
+
tokensAvailable: true
|
|
2997
3331
|
};
|
|
2998
3332
|
}
|
|
2999
3333
|
resolveModel(tier) {
|
|
@@ -3012,10 +3346,36 @@ var AnthropicProvider = class {
|
|
|
3012
3346
|
|
|
3013
3347
|
// src/providers/factory.ts
|
|
3014
3348
|
function createProvider(config) {
|
|
3015
|
-
|
|
3016
|
-
|
|
3349
|
+
switch (config.kind) {
|
|
3350
|
+
case "claude-code":
|
|
3351
|
+
return new ClaudeCodeProvider(config.models, config.claudeCliPath);
|
|
3352
|
+
case "codex":
|
|
3353
|
+
return new CliSubprocessProvider(codexSpec, config.models, config.codexCliPath);
|
|
3354
|
+
case "copilot":
|
|
3355
|
+
return new CliSubprocessProvider(copilotSpec, config.models, config.copilotCliPath);
|
|
3356
|
+
case "kiro":
|
|
3357
|
+
return new CliSubprocessProvider(kiroSpec, config.models, config.kiroCliPath);
|
|
3358
|
+
case "api":
|
|
3359
|
+
return new AnthropicProvider(config.apiKey, config.models);
|
|
3360
|
+
default: {
|
|
3361
|
+
const unhandled = config.kind;
|
|
3362
|
+
throw new GitwiseError({
|
|
3363
|
+
code: "CONFIG_INVALID",
|
|
3364
|
+
message: `Unknown provider "${String(unhandled)}" in config. Re-run \`gw provider\` to choose a supported provider.`
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3017
3367
|
}
|
|
3018
|
-
|
|
3368
|
+
}
|
|
3369
|
+
function buildProviderConfig(merged, apiKey) {
|
|
3370
|
+
return {
|
|
3371
|
+
kind: merged.provider,
|
|
3372
|
+
models: merged.models[merged.provider],
|
|
3373
|
+
apiKey,
|
|
3374
|
+
claudeCliPath: merged.claudeCliPath,
|
|
3375
|
+
codexCliPath: merged.codexCliPath,
|
|
3376
|
+
copilotCliPath: merged.copilotCliPath,
|
|
3377
|
+
kiroCliPath: merged.kiroCliPath
|
|
3378
|
+
};
|
|
3019
3379
|
}
|
|
3020
3380
|
|
|
3021
3381
|
// src/index.ts
|
|
@@ -3025,6 +3385,7 @@ export {
|
|
|
3025
3385
|
DEFAULT_USER_CONFIG,
|
|
3026
3386
|
EXIT_CODES,
|
|
3027
3387
|
GitwiseError,
|
|
3388
|
+
PROVIDER_KINDS,
|
|
3028
3389
|
STALE_LOCK_MS,
|
|
3029
3390
|
SUPPORTED_COMMANDS,
|
|
3030
3391
|
Transaction,
|
|
@@ -3035,6 +3396,7 @@ export {
|
|
|
3035
3396
|
applyOneCommitStep,
|
|
3036
3397
|
applyPr,
|
|
3037
3398
|
applyRelease,
|
|
3399
|
+
buildProviderConfig,
|
|
3038
3400
|
bumpVersion,
|
|
3039
3401
|
commit2 as commit,
|
|
3040
3402
|
createProvider,
|
|
@@ -3048,6 +3410,7 @@ export {
|
|
|
3048
3410
|
error,
|
|
3049
3411
|
fileExists,
|
|
3050
3412
|
finishRelease,
|
|
3413
|
+
formatTokens,
|
|
3051
3414
|
getApiKey,
|
|
3052
3415
|
getMergedConfig,
|
|
3053
3416
|
git_exports as git,
|
|
@@ -3068,6 +3431,9 @@ export {
|
|
|
3068
3431
|
readUserConfig,
|
|
3069
3432
|
release,
|
|
3070
3433
|
resolveClaudeBinary,
|
|
3434
|
+
resolveCodexBinary,
|
|
3435
|
+
resolveCopilotBinary,
|
|
3436
|
+
resolveKiroBinary,
|
|
3071
3437
|
resolveModelTier,
|
|
3072
3438
|
review,
|
|
3073
3439
|
runReleaseInProcess,
|