@astrosheep/keiyaku 0.1.77 → 0.1.78
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/build/.tsbuildinfo +1 -1
- package/build/config/apply-argument-descriptions.js +1 -1
- package/build/config/base-rules.js +2 -1
- package/build/config/dotenv.js +2 -1
- package/build/config/settings.js +2 -7
- package/build/config/term-presets/resolver.js +0 -3
- package/build/errno.js +3 -0
- package/build/flow-error.js +2 -0
- package/build/generated/version.js +1 -1
- package/build/git/diff/constants.js +1 -0
- package/build/git/diff/filter.js +3 -18
- package/build/git/diff/parsers.js +149 -61
- package/build/git/diff/preview.js +16 -2
- package/build/git/diff/read.js +32 -20
- package/build/git/snapshot.js +5 -24
- package/build/git/worktree.js +5 -4
- package/build/mcp/server.js +61 -69
- package/build/protocol/draft-artifacts.js +2 -1
- package/build/protocol/file-guards.js +2 -1
- package/build/protocol/markdown/lex.js +52 -14
- package/build/protocol/markdown/normalization.js +3 -2
- package/build/protocol/markdown/parser.js +2 -2
- package/build/protocol/response-history.js +44 -5
- package/build/protocol/status-previews.js +20 -8
- package/build/protocol/summon-draft.js +3 -2
- package/build/protocol/summon-input.js +1 -0
- package/build/tools/amend/index.js +11 -21
- package/build/tools/ask/index.js +11 -18
- package/build/tools/ask/persist.js +60 -37
- package/build/tools/ask/run.js +15 -5
- package/build/tools/create-handler.js +31 -0
- package/build/tools/drive/index.js +11 -24
- package/build/tools/drive/run.js +9 -5
- package/build/tools/petition/claim-gates.js +23 -1
- package/build/tools/petition/claim.js +19 -2
- package/build/tools/petition/forfeit.js +4 -1
- package/build/tools/petition/index.js +43 -58
- package/build/tools/petition/run.js +12 -0
- package/build/tools/round/head-guard.js +10 -0
- package/build/tools/round/incremental-diff.js +6 -2
- package/build/tools/round/report.js +24 -2
- package/build/tools/round/run.js +6 -0
- package/build/tools/round/worktree.js +6 -2
- package/build/tools/status/index.js +11 -24
- package/build/tools/status/read.js +6 -4
- package/build/tools/summon/index.js +17 -27
- package/build/tools/summon/run.js +21 -18
- package/package.json +6 -6
- package/build/git/diff/stat.js +0 -9
|
@@ -4,3 +4,4 @@ export const MAX_DIFF_LINES_PER_FILE = 80;
|
|
|
4
4
|
export const INCREMENTAL_DIFF_RANGE = "HEAD~1...HEAD";
|
|
5
5
|
export const TARGETED_DIFF_WARNING = "Warning: no valid diff coordinates found in KEIYAKU_TRACE.md; showing stat-only diff.";
|
|
6
6
|
export const NOT_SHOWN_IN_DETAIL_PREFIX = "--- not shown in detail: ";
|
|
7
|
+
export const DIFF_TRUNCATED_MARKER = "...[truncated]";
|
package/build/git/diff/filter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NOT_SHOWN_IN_DETAIL_PREFIX } from "./constants.js";
|
|
2
|
-
import { isDeletedDiffSection, parseDiffPathFromHeader, parseUnifiedHunks, splitDiffByFile,
|
|
2
|
+
import { isDeletedDiffSection, parseDiffPathFromHeader, parseUnifiedHunks, splitDiffByFile, truncateDiffSectionByLineCount, } from "./parsers.js";
|
|
3
3
|
function rangesOverlap(aStart, aEnd, bStart, bEnd) {
|
|
4
4
|
return aStart <= bEnd && bStart <= aEnd;
|
|
5
5
|
}
|
|
@@ -12,21 +12,6 @@ function buildCoordinateIndex(coordinates) {
|
|
|
12
12
|
}
|
|
13
13
|
return index;
|
|
14
14
|
}
|
|
15
|
-
export function capTargetedDiffText(diffText, maxChars) {
|
|
16
|
-
if (diffText.length <= maxChars)
|
|
17
|
-
return diffText;
|
|
18
|
-
return truncateTextWithFooter(diffText, maxChars, diffText.length - maxChars);
|
|
19
|
-
}
|
|
20
|
-
function capDiffSectionByLineCount(section, maxLinesPerFile) {
|
|
21
|
-
if (maxLinesPerFile < 1)
|
|
22
|
-
return "";
|
|
23
|
-
const lines = section.split("\n");
|
|
24
|
-
if (lines.length <= maxLinesPerFile)
|
|
25
|
-
return section;
|
|
26
|
-
const truncated = lines.slice(0, maxLinesPerFile);
|
|
27
|
-
truncated.push(`... [truncated ${lines.length - maxLinesPerFile} lines for this file]`);
|
|
28
|
-
return truncated.join("\n");
|
|
29
|
-
}
|
|
30
15
|
export function filterDiffByCoordinates(rawPatch, coordinates, maxLinesPerFile = Number.POSITIVE_INFINITY) {
|
|
31
16
|
const sections = splitDiffByFile(rawPatch);
|
|
32
17
|
if (sections.length === 0)
|
|
@@ -66,11 +51,11 @@ export function filterDiffByCoordinates(rawPatch, coordinates, maxLinesPerFile =
|
|
|
66
51
|
...prelude,
|
|
67
52
|
...matchingHunks.flatMap((hunk) => [hunk.header, ...hunk.lines]),
|
|
68
53
|
].join("\n");
|
|
69
|
-
selectedSectionsByPath.set(coordinate.path,
|
|
54
|
+
selectedSectionsByPath.set(coordinate.path, truncateDiffSectionByLineCount(rendered, maxLinesPerFile));
|
|
70
55
|
}
|
|
71
56
|
const selectedSections = [
|
|
72
57
|
...selectedSectionsByPath.values(),
|
|
73
|
-
...deletedSections.map((section) =>
|
|
58
|
+
...deletedSections.map((section) => truncateDiffSectionByLineCount(section, maxLinesPerFile)),
|
|
74
59
|
];
|
|
75
60
|
if (selectedSections.length === 0)
|
|
76
61
|
return "";
|
|
@@ -1,58 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const binary = addRaw === "-" || delRaw === "-";
|
|
16
|
-
rows.push({
|
|
17
|
-
path: filePath,
|
|
18
|
-
additions: binary ? 0 : Number.parseInt(addRaw, 10) || 0,
|
|
19
|
-
deletions: binary ? 0 : Number.parseInt(delRaw, 10) || 0,
|
|
20
|
-
binary,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
return rows;
|
|
24
|
-
}
|
|
25
|
-
export function parseNameStatus(content) {
|
|
26
|
-
const map = new Map();
|
|
27
|
-
for (const line of content.split(/\r?\n/)) {
|
|
28
|
-
if (!line.trim())
|
|
29
|
-
continue;
|
|
30
|
-
const parts = line.split("\t");
|
|
31
|
-
const statusRaw = (parts[0] ?? "").trim();
|
|
32
|
-
if (!statusRaw)
|
|
33
|
-
continue;
|
|
34
|
-
if ((statusRaw.startsWith("R") || statusRaw.startsWith("C")) && parts.length >= 3) {
|
|
35
|
-
const scoreRaw = statusRaw.slice(1);
|
|
36
|
-
const score = Number.parseInt(scoreRaw, 10);
|
|
37
|
-
const oldPath = (parts[1] ?? "").trim();
|
|
38
|
-
const path = (parts[2] ?? "").trim();
|
|
39
|
-
if (!path)
|
|
40
|
-
continue;
|
|
41
|
-
const status = statusRaw[0] === "R" ? "R" : "C";
|
|
42
|
-
map.set(path, { status, score: Number.isFinite(score) ? score : 0, oldPath, path });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const path = (parts[1] ?? "").trim();
|
|
46
|
-
if (!path)
|
|
47
|
-
continue;
|
|
48
|
-
// Git can emit single-letter statuses, possibly in combinations; we keep just the first.
|
|
49
|
-
const status = statusRaw[0] ?? "";
|
|
50
|
-
if (!isSimpleNameStatus(status))
|
|
51
|
-
continue;
|
|
52
|
-
map.set(path, { status, path });
|
|
53
|
-
}
|
|
54
|
-
return map;
|
|
55
|
-
}
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { DIFF_TRUNCATED_MARKER } from "./constants.js";
|
|
3
|
+
const DIFF_GIT_HEADER_PREFIX = "diff --git ";
|
|
4
|
+
const GIT_QUOTED_PATH_ESCAPES = {
|
|
5
|
+
'"': 34,
|
|
6
|
+
"\\": 92,
|
|
7
|
+
a: 7,
|
|
8
|
+
b: 8,
|
|
9
|
+
f: 12,
|
|
10
|
+
n: 10,
|
|
11
|
+
r: 13,
|
|
12
|
+
t: 9,
|
|
13
|
+
v: 11,
|
|
14
|
+
};
|
|
56
15
|
export function splitDiffByFile(content) {
|
|
57
16
|
const sections = [];
|
|
58
17
|
let current = [];
|
|
@@ -135,10 +94,83 @@ export function isMissingBaseRevisionError(err) {
|
|
|
135
94
|
text.includes("unknown revision or path not in the working tree") ||
|
|
136
95
|
text.includes("ambiguous argument"));
|
|
137
96
|
}
|
|
97
|
+
function unescapeQuotedDiffPath(value) {
|
|
98
|
+
const bytes = [];
|
|
99
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
100
|
+
const char = value[index];
|
|
101
|
+
if (char !== "\\") {
|
|
102
|
+
bytes.push(...Buffer.from(char ?? "", "utf8"));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const escaped = value[index + 1];
|
|
106
|
+
if (escaped === undefined) {
|
|
107
|
+
bytes.push(92);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (escaped >= "0" && escaped <= "7") {
|
|
111
|
+
let octal = escaped;
|
|
112
|
+
let cursor = index + 2;
|
|
113
|
+
while (cursor < value.length && octal.length < 3) {
|
|
114
|
+
const digit = value[cursor];
|
|
115
|
+
if (digit === undefined || digit < "0" || digit > "7")
|
|
116
|
+
break;
|
|
117
|
+
octal += digit;
|
|
118
|
+
cursor += 1;
|
|
119
|
+
}
|
|
120
|
+
bytes.push(Number.parseInt(octal, 8));
|
|
121
|
+
index = cursor - 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const decoded = GIT_QUOTED_PATH_ESCAPES[escaped];
|
|
125
|
+
if (decoded !== undefined) {
|
|
126
|
+
bytes.push(decoded);
|
|
127
|
+
index += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
bytes.push(...Buffer.from(escaped, "utf8"));
|
|
131
|
+
index += 1;
|
|
132
|
+
}
|
|
133
|
+
return Buffer.from(bytes).toString("utf8");
|
|
134
|
+
}
|
|
135
|
+
function readDiffHeaderToken(line, startIndex) {
|
|
136
|
+
let index = startIndex;
|
|
137
|
+
while (index < line.length && line[index] === " ") {
|
|
138
|
+
index += 1;
|
|
139
|
+
}
|
|
140
|
+
if (index >= line.length)
|
|
141
|
+
return null;
|
|
142
|
+
if (line[index] !== '"') {
|
|
143
|
+
let endIndex = index;
|
|
144
|
+
while (endIndex < line.length && line[endIndex] !== " ") {
|
|
145
|
+
endIndex += 1;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
value: line.slice(index, endIndex),
|
|
149
|
+
nextIndex: endIndex,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
let endIndex = index + 1;
|
|
153
|
+
let escaped = false;
|
|
154
|
+
while (endIndex < line.length) {
|
|
155
|
+
const char = line[endIndex];
|
|
156
|
+
if (!escaped && char === '"') {
|
|
157
|
+
return {
|
|
158
|
+
value: unescapeQuotedDiffPath(line.slice(index + 1, endIndex)),
|
|
159
|
+
nextIndex: endIndex + 1,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
escaped = !escaped && char === "\\";
|
|
163
|
+
endIndex += 1;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
138
167
|
export function parseDiffPathFromHeader(diffGitLine) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
168
|
+
if (!diffGitLine.startsWith(DIFF_GIT_HEADER_PREFIX))
|
|
169
|
+
return null;
|
|
170
|
+
const aPart = readDiffHeaderToken(diffGitLine, DIFF_GIT_HEADER_PREFIX.length);
|
|
171
|
+
if (!aPart)
|
|
172
|
+
return null;
|
|
173
|
+
const bPart = readDiffHeaderToken(diffGitLine, aPart.nextIndex)?.value ?? "";
|
|
142
174
|
if (!bPart.startsWith("b/"))
|
|
143
175
|
return null;
|
|
144
176
|
return bPart.slice(2);
|
|
@@ -158,20 +190,76 @@ export function truncateTextWithFooter(text, maxChars, omittedChars) {
|
|
|
158
190
|
const cut = text.slice(0, maxChars - footer.length);
|
|
159
191
|
return `${cut}${footer}`;
|
|
160
192
|
}
|
|
161
|
-
|
|
193
|
+
function isChangedDiffLine(line) {
|
|
194
|
+
return (line.startsWith("+") && !line.startsWith("+++")) || (line.startsWith("-") && !line.startsWith("---"));
|
|
195
|
+
}
|
|
196
|
+
function trimUnsafeDiffBoundary(kept, remaining) {
|
|
197
|
+
if (kept.length === 0 || remaining.length === 0)
|
|
198
|
+
return kept;
|
|
199
|
+
const safe = [...kept];
|
|
200
|
+
while (safe.length > 0 && isChangedDiffLine(safe[safe.length - 1] ?? "") && isChangedDiffLine(remaining[0] ?? "")) {
|
|
201
|
+
safe.pop();
|
|
202
|
+
}
|
|
203
|
+
if ((safe[safe.length - 1] ?? "").startsWith("@@ ")) {
|
|
204
|
+
safe.pop();
|
|
205
|
+
}
|
|
206
|
+
return safe;
|
|
207
|
+
}
|
|
208
|
+
function renderTruncatedDiff(lines) {
|
|
209
|
+
if (lines.length === 0)
|
|
210
|
+
return DIFF_TRUNCATED_MARKER;
|
|
211
|
+
return `${lines.join("\n")}\n${DIFF_TRUNCATED_MARKER}`;
|
|
212
|
+
}
|
|
213
|
+
export function truncateDiffSectionByLineCount(section, maxLines) {
|
|
214
|
+
if (maxLines < 1)
|
|
215
|
+
return "";
|
|
216
|
+
const lines = section.split("\n");
|
|
217
|
+
if (lines.length <= maxLines)
|
|
218
|
+
return section;
|
|
219
|
+
const { prelude, hunks } = parseUnifiedHunks(lines);
|
|
220
|
+
if (hunks.length === 0 || prelude.length >= maxLines) {
|
|
221
|
+
return renderTruncatedDiff(trimUnsafeDiffBoundary(lines.slice(0, maxLines), lines.slice(maxLines)));
|
|
222
|
+
}
|
|
223
|
+
const kept = [...prelude];
|
|
224
|
+
let lineCount = prelude.length;
|
|
225
|
+
for (let index = 0; index < hunks.length; index += 1) {
|
|
226
|
+
const hunk = hunks[index];
|
|
227
|
+
const hunkLines = [hunk.header, ...hunk.lines];
|
|
228
|
+
if (lineCount + hunkLines.length <= maxLines) {
|
|
229
|
+
kept.push(...hunkLines);
|
|
230
|
+
lineCount += hunkLines.length;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
kept.push(...hunkLines);
|
|
234
|
+
lineCount += hunkLines.length;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
if (lineCount >= lines.length)
|
|
238
|
+
return section;
|
|
239
|
+
return renderTruncatedDiff(kept);
|
|
240
|
+
}
|
|
241
|
+
export function truncateDiffBlockToCharBudget(block, maxChars) {
|
|
162
242
|
if (maxChars <= 0)
|
|
163
243
|
return "";
|
|
164
244
|
if (block.length <= maxChars)
|
|
165
245
|
return block;
|
|
246
|
+
if (DIFF_TRUNCATED_MARKER.length >= maxChars) {
|
|
247
|
+
return DIFF_TRUNCATED_MARKER.slice(0, maxChars);
|
|
248
|
+
}
|
|
166
249
|
const lines = block.split("\n");
|
|
167
250
|
const kept = [];
|
|
168
251
|
let used = 0;
|
|
169
252
|
for (const line of lines) {
|
|
170
253
|
const delta = (kept.length > 0 ? 1 : 0) + line.length;
|
|
171
|
-
|
|
254
|
+
const reservedMarker = 1 + DIFF_TRUNCATED_MARKER.length;
|
|
255
|
+
if (used + delta + reservedMarker > maxChars)
|
|
172
256
|
break;
|
|
173
257
|
kept.push(line);
|
|
174
258
|
used += delta;
|
|
175
259
|
}
|
|
176
|
-
|
|
260
|
+
const safe = trimUnsafeDiffBoundary(kept, lines.slice(kept.length));
|
|
261
|
+
if (safe.length === 0) {
|
|
262
|
+
return DIFF_TRUNCATED_MARKER.slice(0, maxChars);
|
|
263
|
+
}
|
|
264
|
+
return renderTruncatedDiff(safe);
|
|
177
265
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createGit, wrapGitError } from "../core.js";
|
|
2
2
|
import { DIFF_EXCLUDES } from "./constants.js";
|
|
3
|
-
import { parseNumStat } from "./parsers.js";
|
|
4
3
|
export async function getDiffStats(cwd, baseBranch) {
|
|
5
4
|
const git = createGit(cwd);
|
|
6
5
|
let rawNumStat;
|
|
@@ -10,7 +9,22 @@ export async function getDiffStats(cwd, baseBranch) {
|
|
|
10
9
|
catch (err) {
|
|
11
10
|
throw wrapGitError(`diff --numstat ${baseBranch}...HEAD`, err, cwd);
|
|
12
11
|
}
|
|
13
|
-
const rows =
|
|
12
|
+
const rows = rawNumStat
|
|
13
|
+
.split(/\r?\n/)
|
|
14
|
+
.flatMap((line) => {
|
|
15
|
+
if (!line.trim())
|
|
16
|
+
return [];
|
|
17
|
+
const [addRaw, delRaw, ...pathParts] = line.split("\t");
|
|
18
|
+
const filePath = pathParts.join("\t").trim();
|
|
19
|
+
if (!filePath)
|
|
20
|
+
return [];
|
|
21
|
+
const binary = addRaw === "-" || delRaw === "-";
|
|
22
|
+
return [{
|
|
23
|
+
path: filePath,
|
|
24
|
+
additions: binary ? 0 : Number.parseInt(addRaw, 10) || 0,
|
|
25
|
+
deletions: binary ? 0 : Number.parseInt(delRaw, 10) || 0,
|
|
26
|
+
}];
|
|
27
|
+
});
|
|
14
28
|
return {
|
|
15
29
|
filesChanged: rows.length,
|
|
16
30
|
insertions: rows.reduce((sum, row) => sum + row.additions, 0),
|
package/build/git/diff/read.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { createGit, wrapGitError } from "../core.js";
|
|
2
2
|
import { DIFF_EXCLUDES } from "./constants.js";
|
|
3
|
-
import { isMissingBaseRevisionError, splitDiffByFile, totalJoinedLength,
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { isMissingBaseRevisionError, splitDiffByFile, totalJoinedLength, truncateDiffBlockToCharBudget, truncateDiffSectionByLineCount, truncateTextWithFooter, } from "./parsers.js";
|
|
4
|
+
function normalizeDiffTarget(target) {
|
|
5
|
+
return typeof target === "string" ? [target] : [...target];
|
|
6
|
+
}
|
|
7
|
+
function formatDiffTarget(target) {
|
|
8
|
+
return typeof target === "string" ? target : target.join(" ");
|
|
9
|
+
}
|
|
10
|
+
function buildPatchCommand(target, options) {
|
|
6
11
|
const args = ["diff", "--no-color", "--no-ext-diff", "--unified=3"];
|
|
7
12
|
if (options.algorithm === "histogram") {
|
|
8
13
|
args.push("--diff-algorithm=histogram");
|
|
9
14
|
}
|
|
10
|
-
args.push(
|
|
15
|
+
args.push(...normalizeDiffTarget(target), "--", ".", ...DIFF_EXCLUDES);
|
|
11
16
|
return args;
|
|
12
17
|
}
|
|
13
18
|
function renderPatchDiff(output, options) {
|
|
@@ -26,11 +31,9 @@ function renderPatchDiff(output, options) {
|
|
|
26
31
|
let omittedFiles = 0;
|
|
27
32
|
for (let i = 0; i < sections.length; i += 1) {
|
|
28
33
|
const section = sections[i];
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
const fileBlock = content.join("\n");
|
|
34
|
+
const fileBlock = maxLinesPerFile === undefined
|
|
35
|
+
? section.join("\n")
|
|
36
|
+
: truncateDiffSectionByLineCount(section.join("\n"), maxLinesPerFile);
|
|
34
37
|
if (maxChars === undefined) {
|
|
35
38
|
renderedSections.push(fileBlock);
|
|
36
39
|
continue;
|
|
@@ -44,7 +47,7 @@ function renderPatchDiff(output, options) {
|
|
|
44
47
|
if (renderedSections.length === 0) {
|
|
45
48
|
const notice = `... [omitted ${omittedFiles} file(s) to stay under ${maxChars} chars] ...`;
|
|
46
49
|
const partialBudget = Math.max(0, maxChars - totalJoinedLength(["", notice]));
|
|
47
|
-
const partial =
|
|
50
|
+
const partial = truncateDiffBlockToCharBudget(fileBlock, partialBudget);
|
|
48
51
|
if (partial.trim()) {
|
|
49
52
|
renderedSections.push(partial);
|
|
50
53
|
}
|
|
@@ -62,20 +65,29 @@ function renderPatchDiff(output, options) {
|
|
|
62
65
|
}
|
|
63
66
|
return renderedSections.join("\n\n").trim() || "No diff.";
|
|
64
67
|
}
|
|
65
|
-
export async function readDiff(cwd,
|
|
68
|
+
export async function readDiff(cwd, target, format, options = {}) {
|
|
66
69
|
const git = createGit(cwd);
|
|
70
|
+
const formattedTarget = formatDiffTarget(target);
|
|
67
71
|
try {
|
|
68
72
|
if (format === "stat") {
|
|
69
73
|
const statVariant = options.statVariant ?? "summary";
|
|
70
74
|
const args = statVariant === "numstat"
|
|
71
|
-
? ["diff", "--numstat",
|
|
72
|
-
: ["diff", "--stat=120,80", "--compact-summary",
|
|
75
|
+
? ["diff", "--numstat", ...normalizeDiffTarget(target), "--", ".", ...DIFF_EXCLUDES]
|
|
76
|
+
: ["diff", "--stat=120,80", "--compact-summary", ...normalizeDiffTarget(target), "--", ".", ...DIFF_EXCLUDES];
|
|
73
77
|
const output = await git.raw(args);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
if (statVariant !== "summary") {
|
|
79
|
+
return output.trim() || "No diff.";
|
|
80
|
+
}
|
|
81
|
+
const trimmed = output.trim();
|
|
82
|
+
if (!trimmed)
|
|
83
|
+
return "No diff.";
|
|
84
|
+
const maxChars = options.maxChars ?? Number.POSITIVE_INFINITY;
|
|
85
|
+
if (trimmed.length <= maxChars) {
|
|
86
|
+
return trimmed;
|
|
87
|
+
}
|
|
88
|
+
return truncateTextWithFooter(trimmed, maxChars, trimmed.length - maxChars);
|
|
77
89
|
}
|
|
78
|
-
const output = await git.raw(buildPatchCommand(
|
|
90
|
+
const output = await git.raw(buildPatchCommand(target, options));
|
|
79
91
|
return renderPatchDiff(output, options);
|
|
80
92
|
}
|
|
81
93
|
catch (error) {
|
|
@@ -84,9 +96,9 @@ export async function readDiff(cwd, range, format, options = {}) {
|
|
|
84
96
|
}
|
|
85
97
|
const commandLabel = format === "stat"
|
|
86
98
|
? options.statVariant === "numstat"
|
|
87
|
-
? `diff --numstat ${
|
|
88
|
-
: `diff --stat=120,80 --compact-summary ${
|
|
89
|
-
: `diff --no-color --no-ext-diff --unified=3 ${
|
|
99
|
+
? `diff --numstat ${formattedTarget}`
|
|
100
|
+
: `diff --stat=120,80 --compact-summary ${formattedTarget}`
|
|
101
|
+
: `diff --no-color --no-ext-diff --unified=3 ${formattedTarget}`;
|
|
90
102
|
throw wrapGitError(commandLabel, error, cwd);
|
|
91
103
|
}
|
|
92
104
|
}
|
package/build/git/snapshot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import * as os from "os";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { NOT_GIT_REPOSITORY_PATTERNS, createGit, errorContainsAnyPattern, wrapGitError } from "./core.js";
|
|
5
5
|
async function withTempIndex(cwd, run) {
|
|
6
6
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "keiyaku-git-index-"));
|
|
7
7
|
const tempIndexPath = path.join(tempDir, "index");
|
|
@@ -18,26 +18,8 @@ export async function createWorkspaceSnapshot(cwd) {
|
|
|
18
18
|
try {
|
|
19
19
|
return await withTempIndex(cwd, async (git) => {
|
|
20
20
|
await git.raw(["add", "-A"]);
|
|
21
|
-
const tree =
|
|
22
|
-
|
|
23
|
-
return undefined;
|
|
24
|
-
let head;
|
|
25
|
-
try {
|
|
26
|
-
head = (await git.revparse(["--verify", "HEAD"])).trim() || undefined;
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
if (!errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS)) {
|
|
30
|
-
throw wrapGitError("rev-parse --verify HEAD", error, cwd);
|
|
31
|
-
}
|
|
32
|
-
head = undefined;
|
|
33
|
-
}
|
|
34
|
-
const args = ["commit-tree", tree];
|
|
35
|
-
if (head) {
|
|
36
|
-
args.push("-p", head);
|
|
37
|
-
}
|
|
38
|
-
args.push("-m", "keiyaku ask snapshot");
|
|
39
|
-
const sha = await git.raw(args);
|
|
40
|
-
return sha.trim() || undefined;
|
|
21
|
+
const tree = await git.raw(["write-tree"]);
|
|
22
|
+
return tree.trim() || undefined;
|
|
41
23
|
});
|
|
42
24
|
}
|
|
43
25
|
catch (error) {
|
|
@@ -50,11 +32,10 @@ export async function createWorkspaceSnapshot(cwd) {
|
|
|
50
32
|
export async function getChangedFilesBetweenSnapshots(cwd, beforeSha, afterSha) {
|
|
51
33
|
const git = createGit(cwd);
|
|
52
34
|
try {
|
|
53
|
-
const output = await git.raw(["diff", "--name-status",
|
|
35
|
+
const output = await git.raw(["diff", "--name-status", beforeSha, afterSha]);
|
|
54
36
|
return output.trim();
|
|
55
37
|
}
|
|
56
38
|
catch (err) {
|
|
57
|
-
throw wrapGitError(`diff --name-status ${beforeSha}
|
|
39
|
+
throw wrapGitError(`diff --name-status ${beforeSha} ${afterSha}`, err, cwd);
|
|
58
40
|
}
|
|
59
41
|
}
|
|
60
|
-
export { withTempIndex };
|
package/build/git/worktree.js
CHANGED
|
@@ -6,6 +6,7 @@ export const DIRTY_FILE_CATEGORY = {
|
|
|
6
6
|
renamed: "renamed",
|
|
7
7
|
conflicted: "conflicted",
|
|
8
8
|
copied: "copied",
|
|
9
|
+
untracked: "untracked",
|
|
9
10
|
};
|
|
10
11
|
function normalizeGitPath(filePath) {
|
|
11
12
|
return filePath.trim().replace(/^\.\/+/, "");
|
|
@@ -22,6 +23,9 @@ function toDirtyStatusPathSets(status) {
|
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
function deriveDirtyFileCategory(file, statusPathSets) {
|
|
26
|
+
if (statusPathSets.untracked.has(file.path)) {
|
|
27
|
+
return DIRTY_FILE_CATEGORY.untracked;
|
|
28
|
+
}
|
|
25
29
|
if (statusPathSets.conflicted.has(file.path)) {
|
|
26
30
|
return DIRTY_FILE_CATEGORY.conflicted;
|
|
27
31
|
}
|
|
@@ -56,10 +60,7 @@ export async function getDirtyFiles(cwd) {
|
|
|
56
60
|
throw wrapGitError("status --porcelain", err, cwd);
|
|
57
61
|
}
|
|
58
62
|
const statusPathSets = toDirtyStatusPathSets(status);
|
|
59
|
-
return status.files
|
|
60
|
-
.filter((file) => !(file.index === "?" && file.working_dir === "?"))
|
|
61
|
-
.filter((file) => !statusPathSets.untracked.has(file.path))
|
|
62
|
-
.map((file) => ({
|
|
63
|
+
return status.files.map((file) => ({
|
|
63
64
|
path: file.path,
|
|
64
65
|
index: file.index,
|
|
65
66
|
working_dir: file.working_dir,
|
package/build/mcp/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { resolveOath } from "../protocol/oath.js";
|
|
3
|
-
import { amendToolSchema,
|
|
4
|
-
import { getAvailableNamesForPreset,
|
|
3
|
+
import { amendToolSchema, askToolSchema, summonToolSchema, driveToolSchema, petitionToolSchema, helpToolSchema, statusToolSchema, } from "../tools/schema.js";
|
|
4
|
+
import { getAvailableNamesForPreset, resolveTermPreset } from "../config/term-presets/resolver.js";
|
|
5
5
|
import { renderPreset } from "../config/render-template.js";
|
|
6
6
|
import { applyArgumentDescriptions } from "../config/apply-argument-descriptions.js";
|
|
7
7
|
import { createAmendHandler } from "../tools/amend/index.js";
|
|
@@ -13,9 +13,6 @@ import { createHelpHandler } from "../tools/help.js";
|
|
|
13
13
|
import { createStatusHandler } from "../tools/status/index.js";
|
|
14
14
|
function registerTools(server, packageVersion) {
|
|
15
15
|
const preset = resolveTermPreset();
|
|
16
|
-
const presetIdentities = listTermPresets()
|
|
17
|
-
.map((item) => `${item.id}=${item.identity}`)
|
|
18
|
-
.join(", ");
|
|
19
16
|
const availableNames = getAvailableNamesForPreset(preset).join(", ");
|
|
20
17
|
const currentOath = resolveOath();
|
|
21
18
|
const renderedPreset = renderPreset(preset, {
|
|
@@ -25,73 +22,68 @@ function registerTools(server, packageVersion) {
|
|
|
25
22
|
amend: preset.tools.amend.name,
|
|
26
23
|
close: preset.tools.close.name,
|
|
27
24
|
identity: preset.identity,
|
|
28
|
-
preset_identities: presetIdentities,
|
|
29
25
|
available_names: availableNames,
|
|
30
26
|
oath_text: `'${currentOath}'`,
|
|
31
27
|
});
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
title: statusPreset.title,
|
|
92
|
-
description: statusPreset.description,
|
|
93
|
-
inputSchema: dynamicStatusSchema,
|
|
94
|
-
}, createStatusHandler());
|
|
28
|
+
const tools = [
|
|
29
|
+
{
|
|
30
|
+
key: "start",
|
|
31
|
+
schema: applyArgumentDescriptions(summonToolSchema, {
|
|
32
|
+
...renderedPreset.tools.start.args,
|
|
33
|
+
}),
|
|
34
|
+
handler: createSummonHandler(),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "drive",
|
|
38
|
+
schema: applyArgumentDescriptions(driveToolSchema, {
|
|
39
|
+
...renderedPreset.tools.drive.args,
|
|
40
|
+
}),
|
|
41
|
+
handler: createDriveHandler(),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "ask",
|
|
45
|
+
schema: applyArgumentDescriptions(askToolSchema, {
|
|
46
|
+
...renderedPreset.tools.ask.args,
|
|
47
|
+
}),
|
|
48
|
+
handler: createAskHandler(),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: "amend",
|
|
52
|
+
schema: applyArgumentDescriptions(amendToolSchema, {
|
|
53
|
+
...renderedPreset.tools.amend.args,
|
|
54
|
+
}),
|
|
55
|
+
handler: createAmendHandler(),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: "close",
|
|
59
|
+
schema: applyArgumentDescriptions(petitionToolSchema, {
|
|
60
|
+
...renderedPreset.tools.close.args,
|
|
61
|
+
}),
|
|
62
|
+
handler: createPetitionHandler(),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "help",
|
|
66
|
+
schema: applyArgumentDescriptions(helpToolSchema, {
|
|
67
|
+
...renderedPreset.tools.help.args,
|
|
68
|
+
}),
|
|
69
|
+
handler: createHelpHandler(renderedPreset, packageVersion),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "status",
|
|
73
|
+
schema: applyArgumentDescriptions(statusToolSchema, {
|
|
74
|
+
...renderedPreset.tools.status.args,
|
|
75
|
+
}),
|
|
76
|
+
handler: createStatusHandler(),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
for (const tool of tools) {
|
|
80
|
+
const toolPreset = renderedPreset.tools[tool.key];
|
|
81
|
+
server.registerTool(toolPreset.name, {
|
|
82
|
+
title: toolPreset.title,
|
|
83
|
+
description: toolPreset.description,
|
|
84
|
+
inputSchema: tool.schema,
|
|
85
|
+
}, tool.handler);
|
|
86
|
+
}
|
|
95
87
|
}
|
|
96
88
|
export function createServer(packageVersion) {
|
|
97
89
|
const server = new McpServer({
|