reasonix 0.12.21 → 0.12.22
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/cli/index.js +329 -47
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -762,13 +762,13 @@ async function runHooks(opts) {
|
|
|
762
762
|
const matching = opts.hooks.filter((h) => h.event === event && matchesTool(h, toolName));
|
|
763
763
|
const outcomes = [];
|
|
764
764
|
let blocked = false;
|
|
765
|
-
const
|
|
765
|
+
const stdin5 = `${JSON.stringify(opts.payload)}
|
|
766
766
|
`;
|
|
767
767
|
for (const hook of matching) {
|
|
768
768
|
const start = Date.now();
|
|
769
769
|
const timeoutMs = hook.timeout ?? DEFAULT_TIMEOUTS_MS[event];
|
|
770
770
|
const cwd2 = hook.cwd ?? opts.payload.cwd;
|
|
771
|
-
const raw = await spawner({ command: hook.command, cwd: cwd2, stdin:
|
|
771
|
+
const raw = await spawner({ command: hook.command, cwd: cwd2, stdin: stdin5, timeoutMs });
|
|
772
772
|
const decision = decideOutcome(event, raw);
|
|
773
773
|
outcomes.push({
|
|
774
774
|
hook,
|
|
@@ -10879,8 +10879,8 @@ function ModalCard({
|
|
|
10879
10879
|
icon,
|
|
10880
10880
|
children
|
|
10881
10881
|
}) {
|
|
10882
|
-
const { stdout:
|
|
10883
|
-
const cols =
|
|
10882
|
+
const { stdout: stdout4 } = useStdout();
|
|
10883
|
+
const cols = stdout4?.columns ?? 80;
|
|
10884
10884
|
const ruleWidth = Math.min(80, Math.max(28, cols - 4));
|
|
10885
10885
|
const titleText = icon ? ` ${icon} ${title} ` : ` ${title} `;
|
|
10886
10886
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: accent }, "\u2594".repeat(ruleWidth))), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { backgroundColor: accent, color: "black", bold: true }, titleText), subtitle ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, ` ${subtitle}`) : null), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, children), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: accent, dimColor: true }, "\u2581".repeat(ruleWidth))));
|
|
@@ -11443,8 +11443,8 @@ function capLines(lines, maxLines, indent) {
|
|
|
11443
11443
|
var MODAL_OVERHEAD_ROWS = 18;
|
|
11444
11444
|
var MIN_DIFF_ROWS = 8;
|
|
11445
11445
|
function EditConfirm({ block, onChoose }) {
|
|
11446
|
-
const { stdout:
|
|
11447
|
-
const rows =
|
|
11446
|
+
const { stdout: stdout4 } = useStdout2();
|
|
11447
|
+
const rows = stdout4?.rows ?? 40;
|
|
11448
11448
|
const budget2 = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
|
|
11449
11449
|
const allLines = useMemo(
|
|
11450
11450
|
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
@@ -12794,8 +12794,8 @@ var EventRow = React11.memo(function EventRow2({
|
|
|
12794
12794
|
return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text8, null, event.text));
|
|
12795
12795
|
});
|
|
12796
12796
|
function TurnSeparator() {
|
|
12797
|
-
const { stdout:
|
|
12798
|
-
const cols =
|
|
12797
|
+
const { stdout: stdout4 } = useStdout3();
|
|
12798
|
+
const cols = stdout4?.columns ?? 80;
|
|
12799
12799
|
const width = Math.max(16, cols - 2);
|
|
12800
12800
|
const sideWidth = Math.max(2, Math.floor((width - 5) / 2));
|
|
12801
12801
|
const leftCells = gradientCells(sideWidth, "\u2500");
|
|
@@ -12954,8 +12954,8 @@ function ModeStatusBar({
|
|
|
12954
12954
|
return /* @__PURE__ */ React12.createElement(ModeBarFrame, null, /* @__PURE__ */ React12.createElement(ModePill, { label, bg, flash }), /* @__PURE__ */ React12.createElement(Text9, { dimColor: true }, ` ${mid} \xB7 Shift+Tab to flip`), jobsTag);
|
|
12955
12955
|
}
|
|
12956
12956
|
function ModeBarFrame({ children }) {
|
|
12957
|
-
const { stdout:
|
|
12958
|
-
const cols =
|
|
12957
|
+
const { stdout: stdout4 } = useStdout4();
|
|
12958
|
+
const cols = stdout4?.columns ?? 80;
|
|
12959
12959
|
const ruleWidth = Math.max(20, cols - 2);
|
|
12960
12960
|
return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text9, { color: "#475569", dimColor: true }, "\u254C".repeat(ruleWidth))), /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, children));
|
|
12961
12961
|
}
|
|
@@ -13756,8 +13756,8 @@ function PromptInput({
|
|
|
13756
13756
|
if (action.historyHandoff === "prev") onHistoryPrev?.();
|
|
13757
13757
|
if (action.historyHandoff === "next") onHistoryNext?.();
|
|
13758
13758
|
}, !disabled);
|
|
13759
|
-
const { stdout:
|
|
13760
|
-
const cols =
|
|
13759
|
+
const { stdout: stdout4 } = useStdout5();
|
|
13760
|
+
const cols = stdout4?.columns ?? 80;
|
|
13761
13761
|
const narrow = cols <= 90;
|
|
13762
13762
|
const promptBody = narrow ? "\u203A " : "you \u203A ";
|
|
13763
13763
|
const promptPrefix = BAR + promptBody;
|
|
@@ -14130,8 +14130,8 @@ function StatsPanel({
|
|
|
14130
14130
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
14131
14131
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model2] ?? DEFAULT_CONTEXT_TOKENS;
|
|
14132
14132
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
14133
|
-
const { stdout:
|
|
14134
|
-
const columns =
|
|
14133
|
+
const { stdout: stdout4 } = useStdout6();
|
|
14134
|
+
const columns = stdout4?.columns ?? 80;
|
|
14135
14135
|
const narrow = columns < NARROW_BREAKPOINT;
|
|
14136
14136
|
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
14137
14137
|
return /* @__PURE__ */ React21.createElement(Box19, { flexDirection: "column", paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
|
|
@@ -14310,8 +14310,8 @@ function formatTokens(n) {
|
|
|
14310
14310
|
import { Box as Box20, Text as Text18, useStdout as useStdout7 } from "ink";
|
|
14311
14311
|
import React22 from "react";
|
|
14312
14312
|
function WelcomeBanner({ inCodeMode }) {
|
|
14313
|
-
const { stdout:
|
|
14314
|
-
const cols =
|
|
14313
|
+
const { stdout: stdout4 } = useStdout7();
|
|
14314
|
+
const cols = stdout4?.columns ?? 80;
|
|
14315
14315
|
const ruleWidth = Math.min(60, Math.max(28, cols - 4));
|
|
14316
14316
|
return /* @__PURE__ */ React22.createElement(Box20, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth }), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.brand }, "\u25C8 welcome"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " \xB7 type a message to start")), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.primary }, "quick start")), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/help", desc: "every command + keyboard shortcut" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/skill", desc: "invoke a stored playbook" }), inCodeMode ? /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "@path", desc: "inline a file in your message" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "!cmd", desc: "run a shell command, output goes to context" })) : null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "/exit", desc: "quit (Ctrl+C also works)" }), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true, italic: true }, "tip:"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " Ctrl+J inserts a newline \xB7 trailing \\ also continues")), /* @__PURE__ */ React22.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth, thin: true })));
|
|
14317
14317
|
}
|
|
@@ -15718,8 +15718,8 @@ ${gitTail(commit2)}` };
|
|
|
15718
15718
|
}
|
|
15719
15719
|
function gitTail(res) {
|
|
15720
15720
|
const stderr = res.stderr ?? "";
|
|
15721
|
-
const
|
|
15722
|
-
const body = stderr.trim() ||
|
|
15721
|
+
const stdout4 = res.stdout ?? "";
|
|
15722
|
+
const body = stderr.trim() || stdout4.trim();
|
|
15723
15723
|
if (body) return body;
|
|
15724
15724
|
if (res.error) return res.error.message;
|
|
15725
15725
|
return "(no output from git)";
|
|
@@ -17689,19 +17689,19 @@ function App({
|
|
|
17689
17689
|
}, [busy]);
|
|
17690
17690
|
const [ongoingTool, setOngoingTool] = useState10(null);
|
|
17691
17691
|
const [toolProgress, setToolProgress] = useState10(null);
|
|
17692
|
-
const { stdout:
|
|
17692
|
+
const { stdout: stdout4 } = useStdout8();
|
|
17693
17693
|
useEffect6(() => {
|
|
17694
|
-
if (!
|
|
17695
|
-
|
|
17696
|
-
|
|
17694
|
+
if (!stdout4 || !stdout4.isTTY) return;
|
|
17695
|
+
stdout4.write("\x1B[?2004h");
|
|
17696
|
+
stdout4.write("\x1B[>4;2m");
|
|
17697
17697
|
return () => {
|
|
17698
|
-
|
|
17699
|
-
|
|
17698
|
+
stdout4.write("\x1B[?2004l");
|
|
17699
|
+
stdout4.write("\x1B[>4m");
|
|
17700
17700
|
};
|
|
17701
|
-
}, [
|
|
17701
|
+
}, [stdout4]);
|
|
17702
17702
|
const [isResizing, setIsResizing] = useState10(false);
|
|
17703
17703
|
useEffect6(() => {
|
|
17704
|
-
if (!
|
|
17704
|
+
if (!stdout4 || !stdout4.isTTY) return;
|
|
17705
17705
|
let timer = null;
|
|
17706
17706
|
const onResize = () => {
|
|
17707
17707
|
setIsResizing(true);
|
|
@@ -17711,12 +17711,12 @@ function App({
|
|
|
17711
17711
|
timer = null;
|
|
17712
17712
|
}, 400);
|
|
17713
17713
|
};
|
|
17714
|
-
|
|
17714
|
+
stdout4.on("resize", onResize);
|
|
17715
17715
|
return () => {
|
|
17716
|
-
|
|
17716
|
+
stdout4.off("resize", onResize);
|
|
17717
17717
|
if (timer) clearTimeout(timer);
|
|
17718
17718
|
};
|
|
17719
|
-
}, [
|
|
17719
|
+
}, [stdout4]);
|
|
17720
17720
|
const { activity: subagentActivity, sinkRef: subagentSinkRef } = useSubagent({
|
|
17721
17721
|
session,
|
|
17722
17722
|
setHistorical
|
|
@@ -19001,7 +19001,7 @@ function App({
|
|
|
19001
19001
|
return;
|
|
19002
19002
|
}
|
|
19003
19003
|
if (result.clear && result.info) {
|
|
19004
|
-
|
|
19004
|
+
stdout4?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
19005
19005
|
setHistorical([
|
|
19006
19006
|
{
|
|
19007
19007
|
id: `sys-${Date.now()}`,
|
|
@@ -19018,7 +19018,7 @@ function App({
|
|
|
19018
19018
|
return;
|
|
19019
19019
|
}
|
|
19020
19020
|
if (result.clear) {
|
|
19021
|
-
|
|
19021
|
+
stdout4?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
19022
19022
|
setHistorical([]);
|
|
19023
19023
|
if (codeMode) {
|
|
19024
19024
|
pendingEdits.current = [];
|
|
@@ -19601,7 +19601,7 @@ function App({
|
|
|
19601
19601
|
refreshModels,
|
|
19602
19602
|
proArmed,
|
|
19603
19603
|
persistPlanState,
|
|
19604
|
-
|
|
19604
|
+
stdout4,
|
|
19605
19605
|
stopLoop,
|
|
19606
19606
|
startLoop,
|
|
19607
19607
|
getLoopStatus,
|
|
@@ -20586,8 +20586,282 @@ async function codeCommand(opts = {}) {
|
|
|
20586
20586
|
});
|
|
20587
20587
|
}
|
|
20588
20588
|
|
|
20589
|
+
// src/cli/commands/commit.ts
|
|
20590
|
+
import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
|
|
20591
|
+
import { mkdtempSync, readFileSync as readFileSync22, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "fs";
|
|
20592
|
+
import { tmpdir } from "os";
|
|
20593
|
+
import { join as join21 } from "path";
|
|
20594
|
+
import { stdin as stdin2, stdout } from "process";
|
|
20595
|
+
import { createInterface } from "readline/promises";
|
|
20596
|
+
var DEFAULT_MODEL = "deepseek-v4-flash";
|
|
20597
|
+
var DIFF_BYTE_CAP = 80 * 1024;
|
|
20598
|
+
var LOG_COUNT = 10;
|
|
20599
|
+
var SYSTEM_PROMPT2 = `You draft git commit messages.
|
|
20600
|
+
|
|
20601
|
+
Output ONLY the commit message \u2014 no preamble, no \`\`\` fences, no "Here's a commit message:" lead-in. The first line of your output IS the commit subject.
|
|
20602
|
+
|
|
20603
|
+
Match the project's existing style:
|
|
20604
|
+
- Look at the recent commits provided. Mirror their voice, conventional-commit prefix usage (or absence), tense, length, body structure.
|
|
20605
|
+
- If recent commits use a "type(scope): summary" prefix, use it. If they don't, don't invent one.
|
|
20606
|
+
- Subject line: one line, \u226472 chars, imperative mood, no trailing period.
|
|
20607
|
+
- Body (optional): explain WHY when the diff isn't self-evident. Wrap at ~72 chars. Skip the body for trivial changes \u2014 repeating the subject in the body is noise.
|
|
20608
|
+
|
|
20609
|
+
The diff is the source of truth for what changed; describe THAT, not your guesses about the broader project. If the diff includes a deletion you can't explain from the surrounding context, name it but don't speculate about why.
|
|
20610
|
+
|
|
20611
|
+
No emojis unless the recent commits use them.
|
|
20612
|
+
No co-author trailers, no "Generated with X" footers.`;
|
|
20613
|
+
function runGit(args, opts = {}) {
|
|
20614
|
+
const result = spawnSync3("git", args, {
|
|
20615
|
+
encoding: "utf8",
|
|
20616
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20617
|
+
input: opts.input,
|
|
20618
|
+
maxBuffer: 32 * 1024 * 1024
|
|
20619
|
+
});
|
|
20620
|
+
return {
|
|
20621
|
+
stdout: result.stdout ?? "",
|
|
20622
|
+
stderr: result.stderr ?? "",
|
|
20623
|
+
status: result.status
|
|
20624
|
+
};
|
|
20625
|
+
}
|
|
20626
|
+
function dieIfNotGitRepo() {
|
|
20627
|
+
const r = runGit(["rev-parse", "--is-inside-work-tree"]);
|
|
20628
|
+
if (r.status !== 0) {
|
|
20629
|
+
process.stderr.write("reasonix commit: not inside a git repository.\n");
|
|
20630
|
+
process.exit(1);
|
|
20631
|
+
}
|
|
20632
|
+
}
|
|
20633
|
+
function readDiff() {
|
|
20634
|
+
const staged = runGit(["diff", "--staged", "--no-color"]);
|
|
20635
|
+
if (staged.status !== 0) {
|
|
20636
|
+
process.stderr.write(`reasonix commit: git diff --staged failed: ${staged.stderr.trim()}
|
|
20637
|
+
`);
|
|
20638
|
+
process.exit(1);
|
|
20639
|
+
}
|
|
20640
|
+
if (staged.stdout.trim().length > 0) {
|
|
20641
|
+
return capDiff(staged.stdout, "staged");
|
|
20642
|
+
}
|
|
20643
|
+
const wt = runGit(["diff", "--no-color"]);
|
|
20644
|
+
if (wt.stdout.trim().length === 0) {
|
|
20645
|
+
return null;
|
|
20646
|
+
}
|
|
20647
|
+
return capDiff(wt.stdout, "working-tree");
|
|
20648
|
+
}
|
|
20649
|
+
function capDiff(raw, source) {
|
|
20650
|
+
if (raw.length <= DIFF_BYTE_CAP) {
|
|
20651
|
+
return { diff: raw, source, truncated: false };
|
|
20652
|
+
}
|
|
20653
|
+
const head = raw.slice(0, Math.floor(DIFF_BYTE_CAP * 0.7));
|
|
20654
|
+
const tail = raw.slice(-Math.floor(DIFF_BYTE_CAP * 0.3));
|
|
20655
|
+
return {
|
|
20656
|
+
diff: `${head}
|
|
20657
|
+
|
|
20658
|
+
[\u2026 ${raw.length - DIFF_BYTE_CAP} bytes of diff truncated \u2026]
|
|
20659
|
+
|
|
20660
|
+
${tail}`,
|
|
20661
|
+
source,
|
|
20662
|
+
truncated: true
|
|
20663
|
+
};
|
|
20664
|
+
}
|
|
20665
|
+
function readRecentCommits() {
|
|
20666
|
+
const r = runGit(["log", `-${LOG_COUNT}`, "--no-merges", "--format=%s%n%b%n---END---"]);
|
|
20667
|
+
if (r.status !== 0) {
|
|
20668
|
+
return "";
|
|
20669
|
+
}
|
|
20670
|
+
return r.stdout.trim();
|
|
20671
|
+
}
|
|
20672
|
+
async function draftMessage(client, model2, diff, recentCommits) {
|
|
20673
|
+
const userParts = [];
|
|
20674
|
+
if (recentCommits) {
|
|
20675
|
+
userParts.push(`Recent commits (style reference):
|
|
20676
|
+
|
|
20677
|
+
${recentCommits}`);
|
|
20678
|
+
}
|
|
20679
|
+
if (diff.source === "working-tree") {
|
|
20680
|
+
userParts.push(
|
|
20681
|
+
"(NOTE: diff is from the working tree, not the staging area \u2014 nothing is staged yet. The user will stage selectively after seeing the draft.)"
|
|
20682
|
+
);
|
|
20683
|
+
}
|
|
20684
|
+
userParts.push(`Diff to summarize:
|
|
20685
|
+
|
|
20686
|
+
${diff.diff}`);
|
|
20687
|
+
const resp = await client.chat({
|
|
20688
|
+
model: model2,
|
|
20689
|
+
messages: [
|
|
20690
|
+
{ role: "system", content: SYSTEM_PROMPT2 },
|
|
20691
|
+
{ role: "user", content: userParts.join("\n\n") }
|
|
20692
|
+
],
|
|
20693
|
+
temperature: 0.2
|
|
20694
|
+
});
|
|
20695
|
+
return stripCodeFences(resp.content.trim());
|
|
20696
|
+
}
|
|
20697
|
+
function stripCodeFences(s) {
|
|
20698
|
+
const trimmed = s.trim();
|
|
20699
|
+
const fenceOpen = /^```[a-zA-Z]*\n/;
|
|
20700
|
+
const fenceClose = /\n?```$/;
|
|
20701
|
+
if (fenceOpen.test(trimmed) && fenceClose.test(trimmed)) {
|
|
20702
|
+
return trimmed.replace(fenceOpen, "").replace(fenceClose, "").trim();
|
|
20703
|
+
}
|
|
20704
|
+
return trimmed;
|
|
20705
|
+
}
|
|
20706
|
+
function printDraft(message) {
|
|
20707
|
+
const sep3 = "\u2500".repeat(60);
|
|
20708
|
+
process.stdout.write(`
|
|
20709
|
+
${sep3}
|
|
20710
|
+
${message}
|
|
20711
|
+
${sep3}
|
|
20712
|
+
|
|
20713
|
+
`);
|
|
20714
|
+
}
|
|
20715
|
+
async function promptChoice() {
|
|
20716
|
+
const rl = createInterface({ input: stdin2, output: stdout });
|
|
20717
|
+
try {
|
|
20718
|
+
const answer = await rl.question("[a]ccept / [r]egenerate / [e]dit / [c]ancel: ");
|
|
20719
|
+
const k = answer.trim().toLowerCase();
|
|
20720
|
+
if (k === "" || k === "a" || k === "y" || k === "yes") return "accept";
|
|
20721
|
+
if (k === "r" || k === "regen" || k === "regenerate") return "regen";
|
|
20722
|
+
if (k === "e" || k === "edit") return "edit";
|
|
20723
|
+
return "cancel";
|
|
20724
|
+
} finally {
|
|
20725
|
+
rl.close();
|
|
20726
|
+
}
|
|
20727
|
+
}
|
|
20728
|
+
function editInExternal(initial) {
|
|
20729
|
+
const editor = process.env.GIT_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;
|
|
20730
|
+
if (!editor) {
|
|
20731
|
+
process.stderr.write(
|
|
20732
|
+
"reasonix commit: no $EDITOR / $VISUAL / $GIT_EDITOR set \u2014 can't open editor. Pick [a]ccept and `git commit --amend` afterwards.\n"
|
|
20733
|
+
);
|
|
20734
|
+
return null;
|
|
20735
|
+
}
|
|
20736
|
+
const dir = mkdtempSync(join21(tmpdir(), "reasonix-commit-"));
|
|
20737
|
+
const path5 = join21(dir, "COMMIT_EDITMSG");
|
|
20738
|
+
writeFileSync13(path5, initial, "utf8");
|
|
20739
|
+
const result = spawnSync3(`${editor} "${path5}"`, {
|
|
20740
|
+
stdio: "inherit",
|
|
20741
|
+
shell: true
|
|
20742
|
+
});
|
|
20743
|
+
if (result.status !== 0) {
|
|
20744
|
+
try {
|
|
20745
|
+
unlinkSync6(path5);
|
|
20746
|
+
} catch {
|
|
20747
|
+
}
|
|
20748
|
+
process.stderr.write(
|
|
20749
|
+
`reasonix commit: editor exited ${result.status} \u2014 keeping prior draft.
|
|
20750
|
+
`
|
|
20751
|
+
);
|
|
20752
|
+
return null;
|
|
20753
|
+
}
|
|
20754
|
+
let edited;
|
|
20755
|
+
try {
|
|
20756
|
+
edited = readFileSync22(path5, "utf8");
|
|
20757
|
+
} catch {
|
|
20758
|
+
return null;
|
|
20759
|
+
} finally {
|
|
20760
|
+
try {
|
|
20761
|
+
unlinkSync6(path5);
|
|
20762
|
+
} catch {
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
const cleaned = edited.split(/\r?\n/).filter((line) => !/^\s*#/.test(line)).join("\n").trim();
|
|
20766
|
+
return cleaned || null;
|
|
20767
|
+
}
|
|
20768
|
+
function commitWithMessage(message) {
|
|
20769
|
+
const child = spawn6("git", ["commit", "-F", "-"], {
|
|
20770
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
20771
|
+
});
|
|
20772
|
+
child.stdin.write(message);
|
|
20773
|
+
child.stdin.end();
|
|
20774
|
+
child.on("close", (code) => {
|
|
20775
|
+
if (code !== 0) {
|
|
20776
|
+
process.stderr.write(`reasonix commit: git commit exited ${code}.
|
|
20777
|
+
`);
|
|
20778
|
+
process.exit(code ?? 1);
|
|
20779
|
+
}
|
|
20780
|
+
});
|
|
20781
|
+
}
|
|
20782
|
+
async function commitCommand(opts = {}) {
|
|
20783
|
+
loadDotenv();
|
|
20784
|
+
dieIfNotGitRepo();
|
|
20785
|
+
const apiKey = loadApiKey() ?? process.env.DEEPSEEK_API_KEY;
|
|
20786
|
+
if (!apiKey) {
|
|
20787
|
+
process.stderr.write(
|
|
20788
|
+
"reasonix commit: DEEPSEEK_API_KEY not set. Run `reasonix setup` to save one, or export it.\n"
|
|
20789
|
+
);
|
|
20790
|
+
process.exit(1);
|
|
20791
|
+
}
|
|
20792
|
+
const diff = readDiff();
|
|
20793
|
+
if (!diff) {
|
|
20794
|
+
process.stderr.write(
|
|
20795
|
+
"reasonix commit: no staged changes and working tree is clean \u2014 nothing to commit.\n"
|
|
20796
|
+
);
|
|
20797
|
+
process.exit(1);
|
|
20798
|
+
}
|
|
20799
|
+
if (diff.source === "working-tree") {
|
|
20800
|
+
process.stderr.write(
|
|
20801
|
+
"reasonix commit: nothing staged \u2014 drafting from working-tree diff. Stage your changes and re-run, or use the draft as a starting point.\n"
|
|
20802
|
+
);
|
|
20803
|
+
}
|
|
20804
|
+
if (diff.truncated) {
|
|
20805
|
+
process.stderr.write(
|
|
20806
|
+
"reasonix commit: diff exceeded 80KB; head + tail sent to the model. Large diffs often produce vague drafts \u2014 consider committing in smaller chunks.\n"
|
|
20807
|
+
);
|
|
20808
|
+
}
|
|
20809
|
+
const client = new DeepSeekClient({ apiKey });
|
|
20810
|
+
const model2 = opts.model ?? DEFAULT_MODEL;
|
|
20811
|
+
const recentCommits = readRecentCommits();
|
|
20812
|
+
let message = "";
|
|
20813
|
+
let firstPass = true;
|
|
20814
|
+
while (true) {
|
|
20815
|
+
if (firstPass) {
|
|
20816
|
+
process.stdout.write("Drafting commit message\u2026\n");
|
|
20817
|
+
} else {
|
|
20818
|
+
process.stdout.write("Regenerating\u2026\n");
|
|
20819
|
+
}
|
|
20820
|
+
firstPass = false;
|
|
20821
|
+
try {
|
|
20822
|
+
message = await draftMessage(client, model2, diff, recentCommits);
|
|
20823
|
+
} catch (err) {
|
|
20824
|
+
process.stderr.write(`reasonix commit: model call failed \u2014 ${err.message}
|
|
20825
|
+
`);
|
|
20826
|
+
process.exit(1);
|
|
20827
|
+
}
|
|
20828
|
+
if (!message) {
|
|
20829
|
+
process.stderr.write("reasonix commit: model returned an empty draft. Try again.\n");
|
|
20830
|
+
process.exit(1);
|
|
20831
|
+
}
|
|
20832
|
+
printDraft(message);
|
|
20833
|
+
if (opts.yes) break;
|
|
20834
|
+
if (diff.source === "working-tree") {
|
|
20835
|
+
process.stdout.write(
|
|
20836
|
+
"(no staged changes \u2014 draft printed above for you to copy. Stage with `git add` and re-run to commit.)\n"
|
|
20837
|
+
);
|
|
20838
|
+
return;
|
|
20839
|
+
}
|
|
20840
|
+
const choice = await promptChoice();
|
|
20841
|
+
if (choice === "accept") break;
|
|
20842
|
+
if (choice === "cancel") {
|
|
20843
|
+
process.stderr.write("commit cancelled.\n");
|
|
20844
|
+
return;
|
|
20845
|
+
}
|
|
20846
|
+
if (choice === "edit") {
|
|
20847
|
+
const edited = editInExternal(message);
|
|
20848
|
+
if (edited) {
|
|
20849
|
+
message = edited;
|
|
20850
|
+
printDraft(message);
|
|
20851
|
+
const next = await promptChoice();
|
|
20852
|
+
if (next === "accept") break;
|
|
20853
|
+
if (next === "cancel") {
|
|
20854
|
+
process.stderr.write("commit cancelled.\n");
|
|
20855
|
+
return;
|
|
20856
|
+
}
|
|
20857
|
+
}
|
|
20858
|
+
}
|
|
20859
|
+
}
|
|
20860
|
+
commitWithMessage(message);
|
|
20861
|
+
}
|
|
20862
|
+
|
|
20589
20863
|
// src/cli/commands/diff.ts
|
|
20590
|
-
import { writeFileSync as
|
|
20864
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
20591
20865
|
import { basename as basename3 } from "path";
|
|
20592
20866
|
import { render as render2 } from "ink";
|
|
20593
20867
|
import React30 from "react";
|
|
@@ -20734,7 +21008,7 @@ async function diffCommand(opts) {
|
|
|
20734
21008
|
if (wantMarkdown) {
|
|
20735
21009
|
console.log(renderSummaryTable(report));
|
|
20736
21010
|
const md = renderMarkdown(report);
|
|
20737
|
-
|
|
21011
|
+
writeFileSync14(opts.mdPath, md, "utf8");
|
|
20738
21012
|
console.log(`
|
|
20739
21013
|
markdown report written to ${opts.mdPath}`);
|
|
20740
21014
|
return;
|
|
@@ -20753,7 +21027,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
20753
21027
|
// src/cli/commands/doctor.ts
|
|
20754
21028
|
import { existsSync as existsSync23, statSync as statSync14 } from "fs";
|
|
20755
21029
|
import { homedir as homedir10 } from "os";
|
|
20756
|
-
import { dirname as dirname16, join as
|
|
21030
|
+
import { dirname as dirname16, join as join22, resolve as resolve12 } from "path";
|
|
20757
21031
|
var TTY = process.stdout.isTTY && process.env.TERM !== "dumb";
|
|
20758
21032
|
function color(text, code) {
|
|
20759
21033
|
if (!TTY) return text;
|
|
@@ -20876,7 +21150,7 @@ async function checkApiReach() {
|
|
|
20876
21150
|
}
|
|
20877
21151
|
async function checkTokenizer() {
|
|
20878
21152
|
const candidates = [
|
|
20879
|
-
|
|
21153
|
+
join22(
|
|
20880
21154
|
dirname16(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")),
|
|
20881
21155
|
"..",
|
|
20882
21156
|
"..",
|
|
@@ -20884,7 +21158,7 @@ async function checkTokenizer() {
|
|
|
20884
21158
|
"data",
|
|
20885
21159
|
"deepseek-tokenizer.json.gz"
|
|
20886
21160
|
),
|
|
20887
|
-
|
|
21161
|
+
join22(process.cwd(), "data", "deepseek-tokenizer.json.gz")
|
|
20888
21162
|
];
|
|
20889
21163
|
for (const p of candidates) {
|
|
20890
21164
|
if (existsSync23(p)) {
|
|
@@ -21007,7 +21281,7 @@ async function checkOllama(projectRoot) {
|
|
|
21007
21281
|
}
|
|
21008
21282
|
async function checkProject(projectRoot) {
|
|
21009
21283
|
const markers = [".git", "REASONIX.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
|
|
21010
|
-
const found = markers.filter((m) => existsSync23(
|
|
21284
|
+
const found = markers.filter((m) => existsSync23(join22(projectRoot, m)));
|
|
21011
21285
|
if (found.length === 0) {
|
|
21012
21286
|
return {
|
|
21013
21287
|
label: "project ",
|
|
@@ -21059,8 +21333,8 @@ async function doctorCommand() {
|
|
|
21059
21333
|
import { resolve as resolve13 } from "path";
|
|
21060
21334
|
|
|
21061
21335
|
// src/index/semantic/preflight.ts
|
|
21062
|
-
import { stdin as
|
|
21063
|
-
import { createInterface } from "readline/promises";
|
|
21336
|
+
import { stdin as stdin3, stdout as stdout2 } from "process";
|
|
21337
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
21064
21338
|
async function ollamaPreflight(opts) {
|
|
21065
21339
|
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
21066
21340
|
const status2 = await checkOllamaStatus(opts.model, opts.baseUrl);
|
|
@@ -21118,7 +21392,7 @@ async function ollamaPreflight(opts) {
|
|
|
21118
21392
|
}
|
|
21119
21393
|
async function confirm(question, defaultYes) {
|
|
21120
21394
|
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
21121
|
-
const rl =
|
|
21395
|
+
const rl = createInterface2({ input: stdin3, output: stdout2 });
|
|
21122
21396
|
try {
|
|
21123
21397
|
const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
21124
21398
|
if (raw === "") return defaultYes;
|
|
@@ -21537,12 +21811,12 @@ function oneLine2(s, max = 200) {
|
|
|
21537
21811
|
}
|
|
21538
21812
|
|
|
21539
21813
|
// src/cli/commands/run.ts
|
|
21540
|
-
import { stdin as
|
|
21541
|
-
import { createInterface as
|
|
21814
|
+
import { stdin as stdin4, stdout as stdout3 } from "process";
|
|
21815
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
21542
21816
|
async function ensureApiKey() {
|
|
21543
21817
|
const existing = loadApiKey();
|
|
21544
21818
|
if (existing) return existing;
|
|
21545
|
-
if (!
|
|
21819
|
+
if (!stdin4.isTTY) {
|
|
21546
21820
|
process.stderr.write(
|
|
21547
21821
|
"DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
|
|
21548
21822
|
);
|
|
@@ -21551,7 +21825,7 @@ async function ensureApiKey() {
|
|
|
21551
21825
|
process.stdout.write(
|
|
21552
21826
|
"DeepSeek API key not configured.\nGet one at https://platform.deepseek.com/api_keys\n"
|
|
21553
21827
|
);
|
|
21554
|
-
const rl =
|
|
21828
|
+
const rl = createInterface3({ input: stdin4, output: stdout3 });
|
|
21555
21829
|
try {
|
|
21556
21830
|
while (true) {
|
|
21557
21831
|
const answer = (await rl.question("API key \u203A ")).trim();
|
|
@@ -22022,7 +22296,7 @@ async function setupCommand(_opts = {}) {
|
|
|
22022
22296
|
}
|
|
22023
22297
|
|
|
22024
22298
|
// src/cli/commands/update.ts
|
|
22025
|
-
import { spawn as
|
|
22299
|
+
import { spawn as spawn7 } from "child_process";
|
|
22026
22300
|
function planUpdate(input) {
|
|
22027
22301
|
const diff = compareVersions(input.current, input.latest);
|
|
22028
22302
|
if (diff > 0) {
|
|
@@ -22053,7 +22327,7 @@ function planUpdate(input) {
|
|
|
22053
22327
|
}
|
|
22054
22328
|
function defaultSpawn(argv) {
|
|
22055
22329
|
return new Promise((resolve14, reject) => {
|
|
22056
|
-
const child =
|
|
22330
|
+
const child = spawn7(argv[0], argv.slice(1), {
|
|
22057
22331
|
stdio: "inherit",
|
|
22058
22332
|
shell: process.platform === "win32"
|
|
22059
22333
|
});
|
|
@@ -22348,6 +22622,14 @@ program.command("doctor").description(
|
|
|
22348
22622
|
).action(async () => {
|
|
22349
22623
|
await doctorCommand();
|
|
22350
22624
|
});
|
|
22625
|
+
program.command("commit").description(
|
|
22626
|
+
"Draft a commit message from the staged diff (or working tree, if nothing staged), matching your repo's recent commit style. Review interactively before it lands."
|
|
22627
|
+
).option("-m, --model <id>", "Override the default model (deepseek-v4-flash)").option(
|
|
22628
|
+
"-y, --yes",
|
|
22629
|
+
"Skip the [a]ccept / [r]egenerate prompt and commit the first draft. Useful in scripts."
|
|
22630
|
+
).action(async (opts) => {
|
|
22631
|
+
await commitCommand({ model: opts.model, yes: !!opts.yes });
|
|
22632
|
+
});
|
|
22351
22633
|
program.command("sessions [name]").description("List saved chat sessions, or inspect one by name.").option("-v, --verbose", "Include system prompts + tool-call metadata when inspecting").action((name, opts) => {
|
|
22352
22634
|
sessionsCommand({ name, verbose: !!opts.verbose });
|
|
22353
22635
|
});
|