@gaunt-sloth/review 2.0.0-beta.9 → 2.0.1
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/README.md +3 -1
- package/cli.js +36 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/reviewRateMiddleware.js +32 -0
- package/dist/middleware/reviewRateMiddleware.js.map +1 -1
- package/dist/modules/reviewFailureReport.d.ts +28 -0
- package/dist/modules/reviewFailureReport.js +125 -0
- package/dist/modules/reviewFailureReport.js.map +1 -0
- package/dist/modules/reviewHeading.js +2 -1
- package/dist/modules/reviewHeading.js.map +1 -1
- package/dist/modules/reviewModule.d.ts +13 -0
- package/dist/modules/reviewModule.js +107 -2
- package/dist/modules/reviewModule.js.map +1 -1
- package/dist/sources/gitDiffSource.d.ts +27 -6
- package/dist/sources/gitDiffSource.js +87 -44
- package/dist/sources/gitDiffSource.js.map +1 -1
- package/dist/utils/diffPaths.d.ts +66 -0
- package/dist/utils/diffPaths.js +178 -0
- package/dist/utils/diffPaths.js.map +1 -0
- package/dist/utils/git.d.ts +43 -0
- package/dist/utils/git.js +103 -0
- package/dist/utils/git.js.map +1 -0
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -73,7 +73,9 @@ by default, so anything posting this output identifies its reviewer without addi
|
|
|
73
73
|
own; `output.header: "none"` on the config you pass to `review()` is the one setting that removes
|
|
74
74
|
it, for a caller piping the review into a template of their own. With rating enabled, `review()`
|
|
75
75
|
sets `process.exitCode = 1` when the rating comes back below `passThreshold` (or when the model
|
|
76
|
-
fails to produce a rating)
|
|
76
|
+
fails to produce a rating); a run that fails inside the agent — a provider error, a context
|
|
77
|
+
overflow — sets it whatever the rating settings say, because there is no verdict to configure. So
|
|
78
|
+
the script exits non-zero exactly when `gth review` would. Those
|
|
77
79
|
two — the attributed text on stdout and the exit code — are the whole embed contract.
|
|
78
80
|
Configuration (provider, prompts, rating thresholds) is the standard Gaunt Sloth config, see
|
|
79
81
|
[the configuration guide](https://github.com/pukeko-robotics/gaunt-sloth/blob/main/docs/configuration/index.md).
|
package/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ setEntryPoint(import.meta.url);
|
|
|
24
24
|
|
|
25
25
|
import { initConfig } from '@gaunt-sloth/core/config.js';
|
|
26
26
|
import { review } from '#src/modules/reviewModule.js';
|
|
27
|
+
import { writeReviewFailureReport } from '#src/modules/reviewFailureReport.js';
|
|
27
28
|
import { displayError } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
28
29
|
import {
|
|
29
30
|
getContentFromSource,
|
|
@@ -32,14 +33,31 @@ import {
|
|
|
32
33
|
resolvePrIdFromArg,
|
|
33
34
|
} from '#src/commands/commandUtils.js';
|
|
34
35
|
|
|
36
|
+
/** The source label this bin passes to `review()`, and therefore names its report file with. */
|
|
37
|
+
const REVIEW_SOURCE = 'pr-review';
|
|
38
|
+
|
|
35
39
|
async function main() {
|
|
40
|
+
// REL-20 — the run is in two phases with two catches, because only the first one owes a report.
|
|
41
|
+
//
|
|
42
|
+
// Everything up to `review()` happens before the report file exists, so a failure there is the
|
|
43
|
+
// one that leaves a workflow reading `review.md` with nothing to read; it writes the report
|
|
44
|
+
// itself. Once `review()` is running the file is already open and captures its own failures, so
|
|
45
|
+
// the second catch stays as it was — writing a report over that one would replace a review that
|
|
46
|
+
// got part-way with a bare error.
|
|
47
|
+
//
|
|
48
|
+
// `gth pr` / `gth review` keep the same promise through the same helper. A second copy of the
|
|
49
|
+
// report-writing here is how the two entry points would drift apart.
|
|
50
|
+
let config;
|
|
51
|
+
let diffWithReqs;
|
|
52
|
+
let contentArg;
|
|
53
|
+
let preambleText;
|
|
36
54
|
try {
|
|
37
|
-
|
|
55
|
+
config = await initConfig({
|
|
38
56
|
identityProfile: process.env.GSLOTH_IDENTITY_PROFILE,
|
|
39
57
|
});
|
|
40
58
|
|
|
41
59
|
// First arg is content (e.g. PR number), rest are requirements
|
|
42
|
-
|
|
60
|
+
contentArg = args[0];
|
|
43
61
|
const requirementArgs = args.slice(1);
|
|
44
62
|
|
|
45
63
|
// Get content (e.g. PR diff)
|
|
@@ -57,18 +75,26 @@ async function main() {
|
|
|
57
75
|
}
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
// Build the review preamble (backstory + guidelines + review instructions + optional
|
|
61
|
-
// system prompt), the same composition `gth review` / `gth pr` use.
|
|
62
|
-
const preambleText = getReviewPreamble(config);
|
|
63
|
-
|
|
64
78
|
// Combine requirements and content for the review
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
diffWithReqs = requirements ? `Requirements:\n${requirements}\nDiff:\n${content}` : content;
|
|
80
|
+
|
|
81
|
+
// Build the review preamble (backstory + guidelines + review instructions + optional
|
|
82
|
+
// system prompt), the same composition `gth review` / `gth pr` use. In this phase because it
|
|
83
|
+
// reads files off disk and can fail, and a failure there is still a run that never reached the
|
|
84
|
+
// agent.
|
|
85
|
+
preambleText = getReviewPreamble(config);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
displayError(error instanceof Error ? error.message : String(error));
|
|
88
|
+
// `config` is undefined when `initConfig` itself threw; the helper knows there is no path to
|
|
89
|
+
// write to in that case and does nothing.
|
|
90
|
+
writeReviewFailureReport(config, REVIEW_SOURCE, 'pr', error);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
68
93
|
|
|
94
|
+
try {
|
|
69
95
|
// Pass the PR id on so GitHub-only tools address the PR explicitly instead of trying to
|
|
70
96
|
// resolve it from the checked-out branch, which a CI runner's detached HEAD cannot provide.
|
|
71
|
-
await review(
|
|
97
|
+
await review(REVIEW_SOURCE, preambleText, diffWithReqs, config, 'pr', undefined, {
|
|
72
98
|
prId: resolvePrIdFromArg(contentArg),
|
|
73
99
|
});
|
|
74
100
|
} catch (error) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export * from '@gaunt-sloth/core/config.js';
|
|
2
|
+
// CFG-70 — the diff path extractor lives here rather than in core (it is diff-specific), so unlike
|
|
3
|
+
// the selector it does not arrive through the core config barrel above and needs its own line.
|
|
4
|
+
export * from '#src/utils/diffPaths.js';
|
|
2
5
|
export * from '#src/modules/reviewModule.js';
|
|
3
6
|
export * from '#src/commands/commandUtils.js';
|
|
4
7
|
export * from '#src/tools/ghReadFileTool.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,mGAAmG;AACnG,+FAA+F;AAC/F,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC"}
|
|
@@ -46,6 +46,38 @@ const REVIEW_RATE_TOOL_NAME = 'gth_review_rate';
|
|
|
46
46
|
* Override per command with `commands.<review|pr>.rating.timeoutMs`.
|
|
47
47
|
*/
|
|
48
48
|
export const DEFAULT_REVIEW_RATE_TIMEOUT_MS = 120_000;
|
|
49
|
+
// [[EXT-183]] — the review GENERATION has no budget of its own. That is a decision deferred on a
|
|
50
|
+
// named measurement, not an oversight, and the asymmetry with the cap above is deliberate.
|
|
51
|
+
//
|
|
52
|
+
// The budget above bounds the rating call only. The generation that runs before it is bounded by
|
|
53
|
+
// nothing: its runnable config carries a recursion limit and a thread id, the agent adds only
|
|
54
|
+
// `streamFromInput`'s Esc `AbortController`, and gth sets no call timeout on any provider. Nor is a
|
|
55
|
+
// runaway there rare — across the recorded gate windows the runaways split about evenly between the
|
|
56
|
+
// two calls, and in the most recent one all three of them were generation.
|
|
57
|
+
//
|
|
58
|
+
// WHY NO NUMBER WAS CHOSEN. The seam exists: `wrapModelCall` wraps each model call individually, so
|
|
59
|
+
// a budget could bound ONE call without capping a legitimate many-turn review, and a value in the
|
|
60
|
+
// 120-240 s band would sit below the integration suite's 300 s per-test ceiling while clearing every
|
|
61
|
+
// healthy call measured here by a wide margin. What is missing is the measurement that would set it.
|
|
62
|
+
// Every healthy sample is a sub-kilobyte fixture reviewed by a local `gemma4:12b` at ~31.5 tokens a
|
|
63
|
+
// second, and none is the case a default has to survive: one call, a real diff, a remote provider,
|
|
64
|
+
// thinking enabled. Wall clock does not carry across providers — the same number is a few thousand
|
|
65
|
+
// tokens locally and something else entirely on a fast model with a large thinking budget — and
|
|
66
|
+
// GS2-105 could pick 120 s only because it had a healthy baseline for the call it was bounding.
|
|
67
|
+
// Truncating a real review is the failure a review tool can least afford, so the number waits for
|
|
68
|
+
// its measurement: legitimate single-call durations for `review` and `pr` across the providers
|
|
69
|
+
// people actually run, at real diff sizes.
|
|
70
|
+
//
|
|
71
|
+
// WHY THE GAP IS TOLERABLE MEANWHILE. What makes an unbounded RATING call the failure GS2-105 exists
|
|
72
|
+
// to prevent is that it renders NOTHING while it runs — a finished review followed by an unexplained
|
|
73
|
+
// pause. The generation is the opposite: it streams token by token (`streamOutput` defaults true),
|
|
74
|
+
// so a user watching it can see what it is doing and judge when it has gone wrong, and on a TTY
|
|
75
|
+
// `streamFromInput` has Esc armed to stop it.
|
|
76
|
+
//
|
|
77
|
+
// Two cases that reasoning does not cover, neither of them claimed harmless: `streamOutput: false`,
|
|
78
|
+
// whose non-streaming `invoke` path arms no Esc at all and so IS the silent shape described above;
|
|
79
|
+
// and any caller whose stdin is not a TTY — CI, a piped diff, the batch pipeline — where Esc is off
|
|
80
|
+
// by construction.
|
|
49
81
|
export function createReviewRateMiddleware(settings, gthConfig) {
|
|
50
82
|
const normalizedConfig = normalizeRatingConfig(settings);
|
|
51
83
|
const rateTool = tool((input) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewRateMiddleware.js","sourceRoot":"","sources":["../../src/middleware/reviewRateMiddleware.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reviewRateMiddleware.js","sourceRoot":"","sources":["../../src/middleware/reviewRateMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { GthConfig } from '@gaunt-sloth/core/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one line that tells a reader of the report why there is no verdict under the heading.
|
|
4
|
+
*
|
|
5
|
+
* Exported so the specs assert on the same string production writes, rather than on a copy of it
|
|
6
|
+
* that can drift out of agreement without anything going red.
|
|
7
|
+
*/
|
|
8
|
+
export declare const REVIEW_DID_NOT_RUN = "This review did not run. The error that stopped it follows; there is no verdict below it.";
|
|
9
|
+
/**
|
|
10
|
+
* Write the promised report for a run that ended before the agent was reached.
|
|
11
|
+
*
|
|
12
|
+
* Call it from every exit that fails a `review`/`pr` run before `review()` is invoked, passing the
|
|
13
|
+
* SAME source label that call would have used (`PR-42`, `PR-discovery`, `REVIEW`, `pr-review`) so
|
|
14
|
+
* the file lands where the successful run would have put it — the label picks the generated file
|
|
15
|
+
* name when `writeOutputToFile` is `true`.
|
|
16
|
+
*
|
|
17
|
+
* A no-op when the config asks for no file, so the default stdout-only run is untouched. It never
|
|
18
|
+
* throws: it runs inside a catch, and a report that cannot be written must not replace the error
|
|
19
|
+
* the caller is already reporting.
|
|
20
|
+
*
|
|
21
|
+
* @param config - The resolved config, or `undefined` when the run failed before config loaded
|
|
22
|
+
* (nothing can be written then — the path is a config field).
|
|
23
|
+
* @param source - The source label the successful run would have passed to `review()`.
|
|
24
|
+
* @param command - `review` or `pr`; names the command in the heading.
|
|
25
|
+
* @param error - The failure, as an `Error` or as text already rendered for the terminal.
|
|
26
|
+
* @returns The path written, or `null` when no report was asked for or the write failed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeReviewFailureReport(config: GthConfig | undefined, source: string, command: 'pr' | 'review', error: unknown): string | null;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REL-20 — the report a review still owes its caller when the run ends before the agent.
|
|
3
|
+
*
|
|
4
|
+
* `writeOutputToFile` is a promise about a path: a workflow reads the file back and posts it. That
|
|
5
|
+
* promise was kept only by runs that reached inference, because the file is created inside
|
|
6
|
+
* `review()` and every content-fetch failure returns before it. A PR over GitHub's 300-file diff
|
|
7
|
+
* limit therefore ended as `ENOENT: no such file or directory, open 'review.md'` in the *next* CI
|
|
8
|
+
* step — a crash about a missing file, several steps away from the HTTP 406 that caused it.
|
|
9
|
+
*
|
|
10
|
+
* So a failed run writes the report too: the same heading a successful review opens with, one line
|
|
11
|
+
* saying the review did not run, and the error exactly as the source produced it. The exit code
|
|
12
|
+
* does not change — the run still failed — but what the workflow posts now names the failure
|
|
13
|
+
* instead of dying on its absence.
|
|
14
|
+
*
|
|
15
|
+
* ## Why a shared helper rather than opening the report before the fetch
|
|
16
|
+
*
|
|
17
|
+
* Both were open (the file name is known before the fetch), and opening early was measured and
|
|
18
|
+
* rejected. The report file is a capture of console output, so opening it before the fetch puts
|
|
19
|
+
* everything the fetch prints into every SUCCESSFUL review: `fileSource` alone emits
|
|
20
|
+
* `Reading file …` through `display`, and the requirement sources, the discovery agent and the
|
|
21
|
+
* scoped-prompt warnings all write through the same channel. That is a visible change to the
|
|
22
|
+
* document every existing user already reads, paid on every run, to fix the runs that fail.
|
|
23
|
+
*
|
|
24
|
+
* A helper called from each failing exit costs one line per exit and cannot touch the success path
|
|
25
|
+
* at all — the success path never calls it.
|
|
26
|
+
*
|
|
27
|
+
* ## Why it writes synchronously, and why it truncates
|
|
28
|
+
*
|
|
29
|
+
* The session log is a `WriteStream`, and `stopSessionLogging()` ends it asynchronously. One of the
|
|
30
|
+
* two callers here — the `gaunt-sloth-review` bin — follows its failure with `process.exit(1)`,
|
|
31
|
+
* which tears the process down without waiting for a pending flush, so a report written through the
|
|
32
|
+
* stream could simply not exist. A single `writeFileSync` has completed by the time it returns.
|
|
33
|
+
*
|
|
34
|
+
* It truncates where the session log appends. Appending would leave CI posting the previous run's
|
|
35
|
+
* verdict with a failure notice stapled underneath it, which reads as a review that happened.
|
|
36
|
+
*
|
|
37
|
+
* @module
|
|
38
|
+
*/
|
|
39
|
+
import { writeFileSync } from 'node:fs';
|
|
40
|
+
import { displayDebug, displayError } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
41
|
+
import { reviewHeadingBlock } from '#src/modules/reviewHeading.js';
|
|
42
|
+
import { getCommandOutputFilePath } from '#src/utils/fileUtils.js';
|
|
43
|
+
/**
|
|
44
|
+
* The one line that tells a reader of the report why there is no verdict under the heading.
|
|
45
|
+
*
|
|
46
|
+
* Exported so the specs assert on the same string production writes, rather than on a copy of it
|
|
47
|
+
* that can drift out of agreement without anything going red.
|
|
48
|
+
*/
|
|
49
|
+
export const REVIEW_DID_NOT_RUN = 'This review did not run. The error that stopped it follows; there is no verdict below it.';
|
|
50
|
+
/**
|
|
51
|
+
* Write the promised report for a run that ended before the agent was reached.
|
|
52
|
+
*
|
|
53
|
+
* Call it from every exit that fails a `review`/`pr` run before `review()` is invoked, passing the
|
|
54
|
+
* SAME source label that call would have used (`PR-42`, `PR-discovery`, `REVIEW`, `pr-review`) so
|
|
55
|
+
* the file lands where the successful run would have put it — the label picks the generated file
|
|
56
|
+
* name when `writeOutputToFile` is `true`.
|
|
57
|
+
*
|
|
58
|
+
* A no-op when the config asks for no file, so the default stdout-only run is untouched. It never
|
|
59
|
+
* throws: it runs inside a catch, and a report that cannot be written must not replace the error
|
|
60
|
+
* the caller is already reporting.
|
|
61
|
+
*
|
|
62
|
+
* @param config - The resolved config, or `undefined` when the run failed before config loaded
|
|
63
|
+
* (nothing can be written then — the path is a config field).
|
|
64
|
+
* @param source - The source label the successful run would have passed to `review()`.
|
|
65
|
+
* @param command - `review` or `pr`; names the command in the heading.
|
|
66
|
+
* @param error - The failure, as an `Error` or as text already rendered for the terminal.
|
|
67
|
+
* @returns The path written, or `null` when no report was asked for or the write failed.
|
|
68
|
+
*/
|
|
69
|
+
export function writeReviewFailureReport(config, source, command, error) {
|
|
70
|
+
if (!config) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const filePath = getCommandOutputFilePath(config, source);
|
|
74
|
+
if (!filePath) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// The same gate the successful report is under: `review()` opens the file with
|
|
78
|
+
// `initSessionLogging(filePath, config.streamSessionInferenceLog)`, which writes nothing at all
|
|
79
|
+
// when that flag is off. A failure report appearing for a user whose successful runs write none
|
|
80
|
+
// would be a second, contradictory answer to "does this config produce a file".
|
|
81
|
+
if (!config.streamSessionInferenceLog) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const body = `${REVIEW_DID_NOT_RUN}\n\n${errorText(error)}\n`;
|
|
85
|
+
// GS2-93 — `output.header: 'none'` silences the heading here exactly as it does in `review()`. A
|
|
86
|
+
// caller who turned it off to get a byte-clean stream did not ask for a clean stream only on the
|
|
87
|
+
// days the review succeeds.
|
|
88
|
+
//
|
|
89
|
+
// The trailing blank line matches what the success path produces: `display()` appends a newline
|
|
90
|
+
// to a heading block that already ends in one, so a report opens with the heading, a blank line,
|
|
91
|
+
// and then its content.
|
|
92
|
+
const report = config.output?.header === 'none'
|
|
93
|
+
? body
|
|
94
|
+
: `${reviewHeadingBlock(command, config.modelDisplayName, config.modelProviderType)}\n${body}`;
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(filePath, stripAnsi(report), { encoding: 'utf8' });
|
|
97
|
+
return filePath;
|
|
98
|
+
}
|
|
99
|
+
catch (writeError) {
|
|
100
|
+
displayDebug(writeError instanceof Error ? writeError : String(writeError));
|
|
101
|
+
displayError(`Failed to write review to file: ${filePath}`);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The failure as the report should carry it: an `Error`'s message, or text a caller has already
|
|
107
|
+
* rendered. Nothing is reformatted — the 406 a large PR produces already names the 300-file limit
|
|
108
|
+
* and both ways around it, and a summary of it would lose exactly that.
|
|
109
|
+
*/
|
|
110
|
+
function errorText(error) {
|
|
111
|
+
return error instanceof Error ? error.message : String(error);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The colour codes out, as the session log does to every line it writes.
|
|
115
|
+
*
|
|
116
|
+
* The report is read as a document — attached to a ticket, posted as a pull request comment — so a
|
|
117
|
+
* successful one has never contained an escape sequence, and this path must not be the one that
|
|
118
|
+
* starts. The text here is not ours: an error from a content source carries the child process's
|
|
119
|
+
* stderr, and a `gh` that decided it was talking to a terminal colours that. Same treatment on both
|
|
120
|
+
* paths is the only way the two cannot diverge, since only one of them is ever looked at.
|
|
121
|
+
*/
|
|
122
|
+
function stripAnsi(text) {
|
|
123
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=reviewFailureReport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewFailureReport.js","sourceRoot":"","sources":["../../src/modules/reviewFailureReport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,2FAA2F,CAAC;AAE9F;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAA6B,EAC7B,MAAc,EACd,OAAwB,EACxB,KAAc;IAEd,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+EAA+E;IAC/E,gGAAgG;IAChG,gGAAgG;IAChG,gFAAgF;IAChF,IAAI,CAAC,MAAM,CAAC,yBAAyB,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,kBAAkB,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;IAE9D,iGAAiG;IACjG,iGAAiG;IACjG,4BAA4B;IAC5B,EAAE;IACF,gGAAgG;IAChG,iGAAiG;IACjG,wBAAwB;IACxB,MAAM,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM;QAC9B,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,GAAG,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,iBAAiB,CAAC,KAAK,IAAI,EAAE,CAAC;IAEnG,IAAI,CAAC;QACH,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACjE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,UAAU,EAAE,CAAC;QACpB,YAAY,CAAC,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QAC5E,YAAY,CAAC,mCAAmC,QAAQ,EAAE,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @module reviewHeading
|
|
3
2
|
* REL-12 — the line every `gth review` / `gth pr` run opens with, so a reader of the output knows
|
|
4
3
|
* whose review it is and what produced it.
|
|
5
4
|
*
|
|
@@ -32,6 +31,8 @@
|
|
|
32
31
|
* site in `reviewModule.ts`, not here — this module builds the line and does not decide whether it
|
|
33
32
|
* is shown), because a caller piping a review into their own template needs a byte-clean stream.
|
|
34
33
|
* That rung is opt-in precisely so the reasoning above survives for everyone who does not set it.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
35
36
|
*/
|
|
36
37
|
import { modelProviderLabel } from '@gaunt-sloth/core/core/modelLabel.js';
|
|
37
38
|
import { runHeaderLine } from '@gaunt-sloth/core/core/runHeader.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewHeading.js","sourceRoot":"","sources":["../../src/modules/reviewHeading.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"reviewHeading.js","sourceRoot":"","sources":["../../src/modules/reviewHeading.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAwB,EACxB,KAAc,EACd,QAAiB;IAEjB,MAAM,KAAK,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9E,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;AAC9C,CAAC"}
|
|
@@ -4,6 +4,19 @@ import type { AgentResolvers } from '@gaunt-sloth/core/core/types.js';
|
|
|
4
4
|
export interface ReviewContext {
|
|
5
5
|
/** PR number under review; undefined in `gth pr` discovery mode (current branch's PR). */
|
|
6
6
|
prId?: string;
|
|
7
|
+
/**
|
|
8
|
+
* CFG-70 — the paths this run's diff touches, for selecting `prompts.paths` entries.
|
|
9
|
+
*
|
|
10
|
+
* **Extracted from the content-source output alone**, never from the whole message: `review()`
|
|
11
|
+
* is handed requirements + the diff + `--file` contents + stdin + `--message` joined together,
|
|
12
|
+
* and a requirements document that quotes a diff would otherwise inject paths no change went
|
|
13
|
+
* near — attaching one module's guidelines to another module's review, silently.
|
|
14
|
+
*
|
|
15
|
+
* An embedder populates it itself with `extractChangedPathsFromDiff`, exported from this
|
|
16
|
+
* package's barrel for exactly that. An empty array and an absent one are treated alike: both
|
|
17
|
+
* mean no path was found, and both are worth saying out loud when entries are configured.
|
|
18
|
+
*/
|
|
19
|
+
changedPaths?: string[];
|
|
7
20
|
}
|
|
8
21
|
/**
|
|
9
22
|
* Run a review of `diff` and print the verdict.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isGhReadFileToolEnabled } from '@gaunt-sloth/core/config.js';
|
|
1
|
+
import { isGhReadFileToolEnabled, selectScopedPrompts } from '@gaunt-sloth/core/config.js';
|
|
2
|
+
import { measureScopedPrompts, SCOPED_PROMPT_BUDGET_BYTES, } from '@gaunt-sloth/core/utils/llmUtils.js';
|
|
2
3
|
import { defaultStatusCallback, display, displayDebug, displayError, displayInfo, displaySuccess, displayWarning, flushSessionLog, initSessionLogging, stopSessionLogging, } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
3
4
|
import { reviewHeadingBlock } from '#src/modules/reviewHeading.js';
|
|
4
5
|
import { getCommandOutputFilePath } from '#src/utils/fileUtils.js';
|
|
@@ -11,6 +12,7 @@ import { deleteArtifact, getArtifact } from '@gaunt-sloth/core/state/artifactSto
|
|
|
11
12
|
import { setExitCode, stdout } from '@gaunt-sloth/core/utils/systemUtils.js';
|
|
12
13
|
import { ApprovalStopError, approvalStopRows } from '@gaunt-sloth/core/core/shell/approvalStop.js';
|
|
13
14
|
import { displayTermination } from '@gaunt-sloth/core/core/terminationNotice.js';
|
|
15
|
+
import { displayOutstandingWork } from '@gaunt-sloth/core/core/outstandingWork.js';
|
|
14
16
|
import { get as getGhReadFileTool, GTH_GH_READ_FILE_TOOL_NAME } from '#src/tools/ghReadFileTool.js';
|
|
15
17
|
/**
|
|
16
18
|
* Run a review of `diff` and print the verdict.
|
|
@@ -68,8 +70,28 @@ _preamble, diff, config, command = 'review', resolvers, reviewContext) {
|
|
|
68
70
|
// deliberately reverses REL-12 for a user who asks for it: a caller piping a review into their
|
|
69
71
|
// own template or diffing captured stdout needs a byte-clean stream, and nobody loses
|
|
70
72
|
// attribution without setting this key.
|
|
73
|
+
// CFG-70 — select the path-scoped prompt entries this diff activates, BEFORE the agent below
|
|
74
|
+
// composes its system prompt from the same config object. Mutating the resolved config inside
|
|
75
|
+
// `review()` is the established shape here: `maybeAddGhReadFileTool` above does it too.
|
|
76
|
+
//
|
|
77
|
+
// Placed after `initSessionLogging` so its diagnostics reach the report file as well as the
|
|
78
|
+
// terminal, and before the heading guard so the report line can join the heading inside it.
|
|
79
|
+
const scopedPromptsReport = applyScopedPrompts(config, reviewContext?.changedPaths);
|
|
71
80
|
if (config.output?.header !== 'none') {
|
|
72
81
|
display(reviewHeadingBlock(command, config.modelDisplayName, config.modelProviderType));
|
|
82
|
+
// CFG-70 — inside the GS2-93 guard, with the heading, because it is review-document
|
|
83
|
+
// provenance of the same kind: which module guidelines this verdict was formed under. A
|
|
84
|
+
// caller who silenced the header to diff captured stdout byte-for-byte would otherwise find
|
|
85
|
+
// a line they did not ask for at the top of their stream.
|
|
86
|
+
//
|
|
87
|
+
// The WARNINGS `applyScopedPrompts` emits are deliberately NOT in here. They are diagnostics
|
|
88
|
+
// about the run's configuration, not content of the review, and the one situation that most
|
|
89
|
+
// needs them — a misspelled glob matching nothing — is indistinguishable from a working run
|
|
90
|
+
// without them. `output.header: 'none'` asks for a clean document, not for silence about
|
|
91
|
+
// config that did not do what it says.
|
|
92
|
+
if (scopedPromptsReport) {
|
|
93
|
+
display(scopedPromptsReport);
|
|
94
|
+
}
|
|
73
95
|
}
|
|
74
96
|
const rateConfig = config.commands?.[command]?.rating;
|
|
75
97
|
if (rateConfig && rateConfig.enabled !== false) {
|
|
@@ -103,6 +125,19 @@ _preamble, diff, config, command = 'review', resolvers, reviewContext) {
|
|
|
103
125
|
await runner.processMessages(messages);
|
|
104
126
|
}
|
|
105
127
|
catch (error) {
|
|
128
|
+
// REL-24 — the run failed, so the PROCESS says it failed. Reporting a failure is not the
|
|
129
|
+
// same as signalling one: `_review-shared.yml` keys its "Check review step result" step on
|
|
130
|
+
// the review step's OUTCOME, so a review that printed a provider error and exited 0 posted
|
|
131
|
+
// what reads as a verdict on a build CI called green.
|
|
132
|
+
//
|
|
133
|
+
// Deliberately NOT gated on `commands.<cmd>.rating.errorOnReviewFail`. That flag governs a
|
|
134
|
+
// VERDICT — exit 1 when the review scores below its threshold — and a run that never
|
|
135
|
+
// reached a verdict has no score to configure. The rating path below already draws the same
|
|
136
|
+
// line: its missing-artifact branch sets the code without consulting the flag, because a
|
|
137
|
+
// rating that did not happen is a malfunction rather than a low score.
|
|
138
|
+
//
|
|
139
|
+
// Set before the branches, so an approvals stop and an ordinary agent error signal alike.
|
|
140
|
+
setExitCode(1);
|
|
106
141
|
displayDebug(error instanceof Error ? error : String(error));
|
|
107
142
|
// [[TUI-C71]] — `review` and `pr` wire no tool-approval callback, so an escalation here is
|
|
108
143
|
// always the §6.2 error and an `attack` verdict is always the halt: the untrusted text this
|
|
@@ -132,7 +167,12 @@ _preamble, diff, config, command = 'review', resolvers, reviewContext) {
|
|
|
132
167
|
// snapshots at cleanup: the agent is gone by here, and its own sites are the innermost ones.
|
|
133
168
|
// A run that produced a review says nothing — an ordinary completion is not news.
|
|
134
169
|
try {
|
|
135
|
-
|
|
170
|
+
const reason = runner.getTerminationReason();
|
|
171
|
+
displayTermination(reason);
|
|
172
|
+
// [[EXT-158]] — and whether the run finished its own checklist. `review` and `pr` are
|
|
173
|
+
// long multi-step runs with no prompt to come back to, so a review that stopped halfway
|
|
174
|
+
// through its plan and wrote a partial report is exactly the silent ending this fills.
|
|
175
|
+
displayOutstandingWork(runner.getOutstandingWork(), reason);
|
|
136
176
|
}
|
|
137
177
|
catch {
|
|
138
178
|
/* fail-soft: explaining a run must never be what breaks it */
|
|
@@ -164,6 +204,71 @@ _preamble, diff, config, command = 'review', resolvers, reviewContext) {
|
|
|
164
204
|
progressIndicator?.stop();
|
|
165
205
|
}
|
|
166
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* CFG-70 — a count and the noun it governs, agreeing. Every number below is routinely 1: a
|
|
209
|
+
* one-file diff, a project with a single `prompts.paths` entry. "1 changed files" in a line the
|
|
210
|
+
* review document carries reads as a bug in the tool, which is the wrong thing for a line whose
|
|
211
|
+
* whole job is to be believed about what the review ran on.
|
|
212
|
+
*/
|
|
213
|
+
function countOf(count, singular, plural = `${singular}s`) {
|
|
214
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
215
|
+
}
|
|
216
|
+
const ENTRY_NOUN = ['configured prompts.paths entry', 'configured prompts.paths entries'];
|
|
217
|
+
/**
|
|
218
|
+
* CFG-70 — match this run's changed paths against `prompts.paths`, set the runtime field the
|
|
219
|
+
* prompt-reading layer reads, and say what happened.
|
|
220
|
+
*
|
|
221
|
+
* @returns the one-line report for the review document when at least one entry matched, else
|
|
222
|
+
* `undefined`. Warnings are emitted here directly; the report line is returned so the caller can
|
|
223
|
+
* place it inside the `output.header` guard, which the warnings are not subject to.
|
|
224
|
+
*
|
|
225
|
+
* A misspelled glob (`packages/vue-ui/*` where `/**` was meant), or `--content-source text`,
|
|
226
|
+
* produces zero matches and a review that runs on the root guidelines alone — **indistinguishable
|
|
227
|
+
* from a working run** unless the run says so. Hence three outcomes rather than one:
|
|
228
|
+
*
|
|
229
|
+
* - at least one entry matched → the report line;
|
|
230
|
+
* - paths found, no entry matched → a warning naming how many paths were seen, because the number
|
|
231
|
+
* is what separates "my globs are wrong" from "the diff really is outside every module";
|
|
232
|
+
* - no paths at all → a warning that the content source is not a unified diff, which is the actual
|
|
233
|
+
* cause when `--content-source text` or a file review reaches here.
|
|
234
|
+
*
|
|
235
|
+
* Nothing is said at all when no entries are configured: that is every run of every project not
|
|
236
|
+
* using the feature.
|
|
237
|
+
*/
|
|
238
|
+
function applyScopedPrompts(config, changedPaths) {
|
|
239
|
+
const entries = config.prompts?.paths;
|
|
240
|
+
if (!entries?.length) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const paths = changedPaths ?? [];
|
|
244
|
+
if (paths.length === 0) {
|
|
245
|
+
displayWarning(`No "diff --git" header was found in the content under review, so the ` +
|
|
246
|
+
`${countOf(entries.length, ...ENTRY_NOUN)} could not be selected from. Path-scoped ` +
|
|
247
|
+
`prompts need a unified diff — check the content source for this run.`);
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
const selected = selectScopedPrompts(paths, entries);
|
|
251
|
+
if (selected.length === 0) {
|
|
252
|
+
displayWarning(`${countOf(entries.length, ...ENTRY_NOUN)} matched none of the ` +
|
|
253
|
+
`${countOf(paths.length, 'changed path')} in this diff, so no module prompts were ` +
|
|
254
|
+
`attached. Check the match globs against the paths the diff actually touches.`);
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
config.scopedPrompts = selected;
|
|
258
|
+
// The budget check, once per run and from here: this is the only site that knows the final
|
|
259
|
+
// selection, and a "have I warned yet" module flag would misbehave across the several agent
|
|
260
|
+
// inits a single process performs. Warn, never truncate — a guideline cut mid-sentence is worse
|
|
261
|
+
// than a long prompt, because the model then follows a rule whose exception was removed.
|
|
262
|
+
const budget = measureScopedPrompts(selected, config);
|
|
263
|
+
if (budget.overBudget) {
|
|
264
|
+
displayWarning(`Path-scoped prompts add ${budget.totalBytes} bytes to this run's prompt ` +
|
|
265
|
+
`(${budget.entryNames.join(', ')}), over the ${SCOPED_PROMPT_BUDGET_BYTES}-byte guide. ` +
|
|
266
|
+
`Nothing was truncated; consider narrowing the match globs or shortening those files.`);
|
|
267
|
+
}
|
|
268
|
+
return (`Scoped prompts: ${selected.map((entry) => entry.name).join(', ')} ` +
|
|
269
|
+
`(${selected.length} of ${countOf(entries.length, 'entry', 'entries')}, ` +
|
|
270
|
+
`${countOf(paths.length, 'changed file')})`);
|
|
271
|
+
}
|
|
167
272
|
/**
|
|
168
273
|
* REL-2: conditionally inject the optional `gh api` file-read tool into the review agent's tools.
|
|
169
274
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewModule.js","sourceRoot":"","sources":["../../src/modules/reviewModule.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"reviewModule.js","sourceRoot":"","sources":["../../src/modules/reviewModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import type { ProviderConfig } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Gets a local diff via `git --no-pager diff
|
|
4
|
-
*
|
|
3
|
+
* Gets a local diff via `git --no-pager diff` — review the working tree, a ref range, or a
|
|
4
|
+
* branch against its merge base, without GitHub and without piping a diff through stdin.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Which diff, in order:
|
|
7
|
+
*
|
|
8
|
+
* 1. An explicit `refRange` always wins and keeps its plain meaning, `git diff <refRange>`,
|
|
9
|
+
* whatever `mergeBase` says. It is the more specific request: the user typed it for this run,
|
|
10
|
+
* while `mergeBase` is a standing default from config.
|
|
11
|
+
* 2. Otherwise, with `mergeBase` set, the working tree against the merge base of that ref and
|
|
12
|
+
* `HEAD` — what the branch's pull request would show, plus uncommitted edits to tracked files.
|
|
13
|
+
* The base is resolved to a sha by {@link resolveMergeBase} (see there for why a sha, and why
|
|
14
|
+
* an unresolvable base fails instead of falling back), and the diff is
|
|
15
|
+
* `git diff <sha> --`: the `--` stops git reading the sha as a path when a file of that name
|
|
16
|
+
* exists. Untracked files are not in it, as in any `git diff`.
|
|
17
|
+
* 3. Otherwise the working tree against the index (plain `git diff`).
|
|
18
|
+
*
|
|
19
|
+
* `mergeBase` is validated here rather than in core's config schema, which keeps every
|
|
20
|
+
* content source's block opaque (`z.record(z.string(), z.unknown())`) and leaves each source to
|
|
21
|
+
* own its keys. It is validated on every call, even when an explicit `refRange` means it will not
|
|
22
|
+
* be used: a malformed value is a config error that should surface on the first run, not on the
|
|
23
|
+
* first run that happens to omit the id.
|
|
24
|
+
*
|
|
25
|
+
* @param config `contentSourceConfig.git`, if any. Recognised key: `mergeBase` (string); any
|
|
26
|
+
* other key is ignored with a warning, so a typo such as `mergebase` is reported.
|
|
7
27
|
* @param refRange optional revision selection passed to `git diff`, e.g. `origin/main...HEAD`
|
|
8
|
-
* or `HEAD~3`.
|
|
28
|
+
* or `HEAD~3`.
|
|
9
29
|
* @returns the diff content; throws with a clear message outside a git repository, on a bad
|
|
10
|
-
* ref, or when the diff is empty (an empty review would otherwise run against no
|
|
30
|
+
* ref or merge base, or when the diff is empty (an empty review would otherwise run against no
|
|
31
|
+
* content).
|
|
11
32
|
*/
|
|
12
|
-
export declare function get(
|
|
33
|
+
export declare function get(config: ProviderConfig | null, refRange: string | undefined): Promise<string | null>;
|
|
@@ -1,28 +1,103 @@
|
|
|
1
1
|
import { ProgressIndicator } from '@gaunt-sloth/core/utils/ProgressIndicator.js';
|
|
2
|
+
import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
3
|
+
import { MERGE_BASE_SETTING, resolveMergeBase, runGit } from '#src/utils/git.js';
|
|
4
|
+
/** The keys this source reads from `contentSourceConfig.git`. */
|
|
5
|
+
const KNOWN_GIT_CONFIG_KEYS = ['mergeBase'];
|
|
2
6
|
/**
|
|
3
|
-
* `git diff`
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Gets a local diff via `git --no-pager diff` — review the working tree, a ref range, or a
|
|
8
|
+
* branch against its merge base, without GitHub and without piping a diff through stdin.
|
|
9
|
+
*
|
|
10
|
+
* Which diff, in order:
|
|
11
|
+
*
|
|
12
|
+
* 1. An explicit `refRange` always wins and keeps its plain meaning, `git diff <refRange>`,
|
|
13
|
+
* whatever `mergeBase` says. It is the more specific request: the user typed it for this run,
|
|
14
|
+
* while `mergeBase` is a standing default from config.
|
|
15
|
+
* 2. Otherwise, with `mergeBase` set, the working tree against the merge base of that ref and
|
|
16
|
+
* `HEAD` — what the branch's pull request would show, plus uncommitted edits to tracked files.
|
|
17
|
+
* The base is resolved to a sha by {@link resolveMergeBase} (see there for why a sha, and why
|
|
18
|
+
* an unresolvable base fails instead of falling back), and the diff is
|
|
19
|
+
* `git diff <sha> --`: the `--` stops git reading the sha as a path when a file of that name
|
|
20
|
+
* exists. Untracked files are not in it, as in any `git diff`.
|
|
21
|
+
* 3. Otherwise the working tree against the index (plain `git diff`).
|
|
10
22
|
*
|
|
11
|
-
*
|
|
23
|
+
* `mergeBase` is validated here rather than in core's config schema, which keeps every
|
|
24
|
+
* content source's block opaque (`z.record(z.string(), z.unknown())`) and leaves each source to
|
|
25
|
+
* own its keys. It is validated on every call, even when an explicit `refRange` means it will not
|
|
26
|
+
* be used: a malformed value is a config error that should surface on the first run, not on the
|
|
27
|
+
* first run that happens to omit the id.
|
|
28
|
+
*
|
|
29
|
+
* @param config `contentSourceConfig.git`, if any. Recognised key: `mergeBase` (string); any
|
|
30
|
+
* other key is ignored with a warning, so a typo such as `mergebase` is reported.
|
|
12
31
|
* @param refRange optional revision selection passed to `git diff`, e.g. `origin/main...HEAD`
|
|
13
|
-
* or `HEAD~3`.
|
|
32
|
+
* or `HEAD~3`.
|
|
14
33
|
* @returns the diff content; throws with a clear message outside a git repository, on a bad
|
|
15
|
-
* ref, or when the diff is empty (an empty review would otherwise run against no
|
|
34
|
+
* ref or merge base, or when the diff is empty (an empty review would otherwise run against no
|
|
35
|
+
* content).
|
|
16
36
|
*/
|
|
17
|
-
export async function get(
|
|
37
|
+
export async function get(config, refRange) {
|
|
18
38
|
// Args go to execFile (no shell), so shell metacharacters are inert; still reject
|
|
19
39
|
// option-shaped input so an id can never become a git flag (e.g. `--output=<file>`).
|
|
20
40
|
if (refRange && refRange.startsWith('-')) {
|
|
21
41
|
throw new Error(`Invalid git diff argument "${refRange}"; expected a ref or ref range (e.g. "origin/main...HEAD"), not an option.`);
|
|
22
42
|
}
|
|
43
|
+
const mergeBase = readMergeBase(config);
|
|
44
|
+
if (!refRange && mergeBase !== undefined) {
|
|
45
|
+
return getMergeBaseDiff(mergeBase);
|
|
46
|
+
}
|
|
23
47
|
const label = refRange ? `for "${refRange}"` : 'for the working tree';
|
|
24
48
|
const gitArgs = ['--no-pager', 'diff', ...(refRange ? [refRange] : [])];
|
|
25
49
|
const progress = new ProgressIndicator(`Getting local git diff ${label}`);
|
|
50
|
+
return runDiff(progress, label, gitArgs, 'the ref range is valid');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read `mergeBase` from the git source's config block. `undefined` when unset; throws, naming
|
|
54
|
+
* the setting, for anything that is set but is not a non-empty string (and for a block that is
|
|
55
|
+
* not an object), so a mistyped value is reported instead of silently ignored.
|
|
56
|
+
*
|
|
57
|
+
* Any other key in the block gets a warning, not an error. A mistyped key such as `mergebase`
|
|
58
|
+
* would otherwise review the plain working-tree diff with nothing said. It is not rejected
|
|
59
|
+
* because a project config is shared by people on different gth versions: a key a later release
|
|
60
|
+
* adds must not break an older one's reviews. That matches the top-level rule in core's config
|
|
61
|
+
* schema, where unknown keys warn and only known deprecated names fail.
|
|
62
|
+
*/
|
|
63
|
+
function readMergeBase(config) {
|
|
64
|
+
if (config === null || config === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
if (typeof config !== 'object' || Array.isArray(config)) {
|
|
68
|
+
throw new Error(`Invalid contentSourceConfig.git; expected an object such as { "mergeBase": "origin/main" }.`);
|
|
69
|
+
}
|
|
70
|
+
const unknownKeys = Object.keys(config).filter((key) => !KNOWN_GIT_CONFIG_KEYS.includes(key));
|
|
71
|
+
if (unknownKeys.length > 0) {
|
|
72
|
+
const names = unknownKeys.map((key) => `"${key}"`).join(', ');
|
|
73
|
+
const noun = unknownKeys.length === 1 ? 'key' : 'keys';
|
|
74
|
+
const verb = unknownKeys.length === 1 ? 'is' : 'are';
|
|
75
|
+
displayWarning(`contentSourceConfig.git: unknown ${noun} ${names} ${verb} ignored; known keys: ${KNOWN_GIT_CONFIG_KEYS.join(', ')}.`);
|
|
76
|
+
}
|
|
77
|
+
const value = config.mergeBase;
|
|
78
|
+
if (value === undefined) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
82
|
+
throw new Error(`Invalid ${MERGE_BASE_SETTING} ${JSON.stringify(value) ?? String(value)}; expected a non-empty string naming a ref, e.g. "origin/main".`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
/** The working tree against the merge base of `mergeBase` and `HEAD`. */
|
|
87
|
+
async function getMergeBaseDiff(mergeBase) {
|
|
88
|
+
const target = `the merge base of "${mergeBase}" and HEAD`;
|
|
89
|
+
const progress = new ProgressIndicator(`Getting local git diff against ${target}`);
|
|
90
|
+
let sha;
|
|
91
|
+
try {
|
|
92
|
+
sha = await resolveMergeBase(mergeBase);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
progress.stop();
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return runDiff(progress, `against ${target} (${sha})`, ['--no-pager', 'diff', sha, '--'], `${MERGE_BASE_SETTING} names a ref in this repository`);
|
|
99
|
+
}
|
|
100
|
+
async function runDiff(progress, label, gitArgs, hint) {
|
|
26
101
|
try {
|
|
27
102
|
const diffContent = await runGit(gitArgs);
|
|
28
103
|
progress.stop();
|
|
@@ -37,39 +112,7 @@ export async function get(_, refRange) {
|
|
|
37
112
|
if (reason.startsWith('No changes found')) {
|
|
38
113
|
throw new Error(reason);
|
|
39
114
|
}
|
|
40
|
-
throw new Error(`Failed to get git diff ${label}: ${reason}\nConsider checking that you are inside a git repository and
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Run git with an args array (execFile, no shell). Rejects on a non-zero exit or spawn
|
|
45
|
-
* failure with git's stderr as the reason; benign stderr chatter on a zero exit is ignored
|
|
46
|
-
* (unlike systemUtils.execAsync, which rejects on any stderr output).
|
|
47
|
-
*/
|
|
48
|
-
async function runGit(args) {
|
|
49
|
-
const { execFile } = await import('node:child_process');
|
|
50
|
-
return new Promise((resolve, reject) => {
|
|
51
|
-
execFile('git', args, { maxBuffer: MAX_DIFF_BUFFER }, (error, stdout, stderr) => {
|
|
52
|
-
if (error) {
|
|
53
|
-
reject(new Error(extractGitError(stderr ?? '', error.message)));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
resolve(stdout);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Reduce git's stderr to the one meaningful line. Outside a repository `git diff` appends
|
|
62
|
-
* its entire `--no-index` usage screen after the warning line; that wall of text is noise
|
|
63
|
-
* in a CLI error. Prefer the `fatal:` line when present, else the first non-empty line.
|
|
64
|
-
*/
|
|
65
|
-
function extractGitError(stderr, fallback) {
|
|
66
|
-
const lines = stderr
|
|
67
|
-
.split('\n')
|
|
68
|
-
.map((line) => line.trim())
|
|
69
|
-
.filter(Boolean);
|
|
70
|
-
if (lines.length === 0) {
|
|
71
|
-
return fallback;
|
|
115
|
+
throw new Error(`Failed to get git diff ${label}: ${reason}\nConsider checking that you are inside a git repository and ${hint}.`);
|
|
72
116
|
}
|
|
73
|
-
return lines.find((line) => line.startsWith('fatal:')) ?? lines[0];
|
|
74
117
|
}
|
|
75
118
|
//# sourceMappingURL=gitDiffSource.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitDiffSource.js","sourceRoot":"","sources":["../../src/sources/gitDiffSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"gitDiffSource.js","sourceRoot":"","sources":["../../src/sources/gitDiffSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEjF,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,CAAC,WAAW,CAAU,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,MAA6B,EAC7B,QAA4B;IAE5B,kFAAkF;IAClF,qFAAqF;IACrF,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,8BAA8B,QAAQ,4EAA4E,CACnH,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAExC,IAAI,CAAC,QAAQ,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC;IACtE,MAAM,OAAO,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAExE,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;IAC1E,OAAO,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,MAAyC;IAC9D,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAC5C,CAAC,GAAG,EAAE,EAAE,CAAC,CAAE,qBAA2C,CAAC,QAAQ,CAAC,GAAG,CAAC,CACrE,CAAC;IACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QACvD,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;QACrD,cAAc,CACZ,oCAAoC,IAAI,IAAI,KAAK,IAAI,IAAI,yBAAyB,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACtH,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CACb,WAAW,kBAAkB,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,iEAAiE,CACzI,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,yEAAyE;AACzE,KAAK,UAAU,gBAAgB,CAAC,SAAiB;IAC/C,MAAM,MAAM,GAAG,sBAAsB,SAAS,YAAY,CAAC;IAC3D,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;IACnF,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,OAAO,CACZ,QAAQ,EACR,WAAW,MAAM,KAAK,GAAG,GAAG,EAC5B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,EACjC,GAAG,kBAAkB,iCAAiC,CACvD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,QAA2B,EAC3B,KAAa,EACb,OAAiB,EACjB,IAAY;IAEZ,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1C,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEhB,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,sBAAsB,CAAC,CAAC;QAC/E,CAAC;QAED,OAAO,kBAAkB,KAAK,OAAO,WAAW,EAAE,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,IAAI,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,0BAA0B,KAAK,KAAK,MAAM,gEAAgE,IAAI,GAAG,CAClH,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* CFG-70 — **the paths a unified diff touches**, read from its `diff --git` header lines.
|
|
4
|
+
*
|
|
5
|
+
* It lives in the review package rather than in core because it is diff-specific: nothing else in
|
|
6
|
+
* the product has a unified diff to read, and a "generic path extractor" in core would invite a
|
|
7
|
+
* second, differently-wrong caller.
|
|
8
|
+
*
|
|
9
|
+
* ## Only the `diff --git` line, deliberately
|
|
10
|
+
*
|
|
11
|
+
* The `---` and `+++` lines are NOT read. On an addition the `---` line says `/dev/null`, and on a
|
|
12
|
+
* deletion the `+++` line does; a parser that read them would either emit `/dev/null` as a changed
|
|
13
|
+
* path or need a special case to suppress it. The `diff --git` line names both real paths on every
|
|
14
|
+
* kind of change, additions and deletions included, so reading it alone makes `/dev/null` an
|
|
15
|
+
* absence rather than a case.
|
|
16
|
+
*
|
|
17
|
+
* A **rename** yields two different paths on that one line, and **both count**: the guidelines for
|
|
18
|
+
* the module a file left are as relevant to reviewing the move as the guidelines for the module it
|
|
19
|
+
* arrived in.
|
|
20
|
+
*
|
|
21
|
+
* ## Quoting — measured against real git output, not reasoned out
|
|
22
|
+
*
|
|
23
|
+
* Git quotes a path in the header when it contains a byte outside printable ASCII (under the
|
|
24
|
+
* default `core.quotePath=true`), a double quote, a backslash, or a control character; it then
|
|
25
|
+
* writes it as a C-style quoted string with octal escapes for the individual UTF-8 **bytes**, so
|
|
26
|
+
* decoding has to reassemble bytes before decoding text. **When either side needs quoting, git
|
|
27
|
+
* quotes both**, so a header either opens with a quote or contains none.
|
|
28
|
+
*
|
|
29
|
+
* ## The space case, and what this heuristic cannot do
|
|
30
|
+
*
|
|
31
|
+
* A space does **not** trigger quoting: git writes `diff --git a/pkg/with space.ts b/pkg/with
|
|
32
|
+
* space.ts` unquoted, and the separator between the two paths is itself a space. The two are
|
|
33
|
+
* genuinely indistinguishable in the general case, so this is a heuristic and is documented as one:
|
|
34
|
+
*
|
|
35
|
+
* - **Preferred rule — equal halves.** Among the candidate split points (every occurrence of a
|
|
36
|
+
* space followed by `b/`), one where the `a/` side and the `b/` side are the same path is taken.
|
|
37
|
+
* That resolves *every* non-rename header, however many spaces the path has, including the
|
|
38
|
+
* adversarial `a/pkg/a b/c.ts b/pkg/a b/c.ts` where a directory is literally named `a b`.
|
|
39
|
+
* - **Fallback — the last candidate.** When no split yields equal halves the header is a rename
|
|
40
|
+
* whose two paths differ, and nothing in the line can decide it. The last candidate is taken.
|
|
41
|
+
*
|
|
42
|
+
* **What that cannot resolve:** a rename whose *destination* path contains a literal ` b/` — say
|
|
43
|
+
* `old.ts` renamed into a directory named `x b` — splits in the wrong place and yields two paths
|
|
44
|
+
* that are not the user's. Choosing the first candidate instead would merely move the failure onto
|
|
45
|
+
* the source path; the information to decide is not in the line. It is left as a known limit
|
|
46
|
+
* rather than papered over, and the shape is vanishingly rare: it needs a rename, a space, AND a
|
|
47
|
+
* directory whose name ends in a space-delimited `b`.
|
|
48
|
+
*
|
|
49
|
+
* A header that does not fit the `a/… b/…` shape at all (git's `--no-prefix`, or
|
|
50
|
+
* `diff.mnemonicPrefix`'s `i/`…`w/`) is **skipped**, not guessed at. A fabricated path is worse
|
|
51
|
+
* than a missing one: it can pull in a scoped entry that has nothing to do with the change, and
|
|
52
|
+
* nothing downstream can tell it from a real one.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Extract the repository-relative POSIX paths a unified diff touches, in the order the diff names
|
|
56
|
+
* them and with duplicates removed.
|
|
57
|
+
*
|
|
58
|
+
* De-duplication matters because the overwhelmingly common header names the same path twice (`a/x
|
|
59
|
+
* b/x`), and a caller reporting how many files a review saw would otherwise double every count.
|
|
60
|
+
* A rename's two distinct paths both survive it.
|
|
61
|
+
*
|
|
62
|
+
* Content that is not a unified diff — a requirements document, `--content-source text` output —
|
|
63
|
+
* simply yields an empty list. There is no error here: deciding what an empty result means belongs
|
|
64
|
+
* to the caller, which is the only place that knows whether a diff was expected.
|
|
65
|
+
*/
|
|
66
|
+
export declare function extractChangedPathsFromDiff(diff: string): string[];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* CFG-70 — **the paths a unified diff touches**, read from its `diff --git` header lines.
|
|
4
|
+
*
|
|
5
|
+
* It lives in the review package rather than in core because it is diff-specific: nothing else in
|
|
6
|
+
* the product has a unified diff to read, and a "generic path extractor" in core would invite a
|
|
7
|
+
* second, differently-wrong caller.
|
|
8
|
+
*
|
|
9
|
+
* ## Only the `diff --git` line, deliberately
|
|
10
|
+
*
|
|
11
|
+
* The `---` and `+++` lines are NOT read. On an addition the `---` line says `/dev/null`, and on a
|
|
12
|
+
* deletion the `+++` line does; a parser that read them would either emit `/dev/null` as a changed
|
|
13
|
+
* path or need a special case to suppress it. The `diff --git` line names both real paths on every
|
|
14
|
+
* kind of change, additions and deletions included, so reading it alone makes `/dev/null` an
|
|
15
|
+
* absence rather than a case.
|
|
16
|
+
*
|
|
17
|
+
* A **rename** yields two different paths on that one line, and **both count**: the guidelines for
|
|
18
|
+
* the module a file left are as relevant to reviewing the move as the guidelines for the module it
|
|
19
|
+
* arrived in.
|
|
20
|
+
*
|
|
21
|
+
* ## Quoting — measured against real git output, not reasoned out
|
|
22
|
+
*
|
|
23
|
+
* Git quotes a path in the header when it contains a byte outside printable ASCII (under the
|
|
24
|
+
* default `core.quotePath=true`), a double quote, a backslash, or a control character; it then
|
|
25
|
+
* writes it as a C-style quoted string with octal escapes for the individual UTF-8 **bytes**, so
|
|
26
|
+
* decoding has to reassemble bytes before decoding text. **When either side needs quoting, git
|
|
27
|
+
* quotes both**, so a header either opens with a quote or contains none.
|
|
28
|
+
*
|
|
29
|
+
* ## The space case, and what this heuristic cannot do
|
|
30
|
+
*
|
|
31
|
+
* A space does **not** trigger quoting: git writes `diff --git a/pkg/with space.ts b/pkg/with
|
|
32
|
+
* space.ts` unquoted, and the separator between the two paths is itself a space. The two are
|
|
33
|
+
* genuinely indistinguishable in the general case, so this is a heuristic and is documented as one:
|
|
34
|
+
*
|
|
35
|
+
* - **Preferred rule — equal halves.** Among the candidate split points (every occurrence of a
|
|
36
|
+
* space followed by `b/`), one where the `a/` side and the `b/` side are the same path is taken.
|
|
37
|
+
* That resolves *every* non-rename header, however many spaces the path has, including the
|
|
38
|
+
* adversarial `a/pkg/a b/c.ts b/pkg/a b/c.ts` where a directory is literally named `a b`.
|
|
39
|
+
* - **Fallback — the last candidate.** When no split yields equal halves the header is a rename
|
|
40
|
+
* whose two paths differ, and nothing in the line can decide it. The last candidate is taken.
|
|
41
|
+
*
|
|
42
|
+
* **What that cannot resolve:** a rename whose *destination* path contains a literal ` b/` — say
|
|
43
|
+
* `old.ts` renamed into a directory named `x b` — splits in the wrong place and yields two paths
|
|
44
|
+
* that are not the user's. Choosing the first candidate instead would merely move the failure onto
|
|
45
|
+
* the source path; the information to decide is not in the line. It is left as a known limit
|
|
46
|
+
* rather than papered over, and the shape is vanishingly rare: it needs a rename, a space, AND a
|
|
47
|
+
* directory whose name ends in a space-delimited `b`.
|
|
48
|
+
*
|
|
49
|
+
* A header that does not fit the `a/… b/…` shape at all (git's `--no-prefix`, or
|
|
50
|
+
* `diff.mnemonicPrefix`'s `i/`…`w/`) is **skipped**, not guessed at. A fabricated path is worse
|
|
51
|
+
* than a missing one: it can pull in a scoped entry that has nothing to do with the change, and
|
|
52
|
+
* nothing downstream can tell it from a real one.
|
|
53
|
+
*/
|
|
54
|
+
const DIFF_GIT_PREFIX = 'diff --git ';
|
|
55
|
+
/** git's C-style single-character escapes, as `unquote_c_style` reads them. */
|
|
56
|
+
const C_ESCAPES = {
|
|
57
|
+
a: 0x07,
|
|
58
|
+
b: 0x08,
|
|
59
|
+
t: 0x09,
|
|
60
|
+
n: 0x0a,
|
|
61
|
+
v: 0x0b,
|
|
62
|
+
f: 0x0c,
|
|
63
|
+
r: 0x0d,
|
|
64
|
+
'"': 0x22,
|
|
65
|
+
'\\': 0x5c,
|
|
66
|
+
};
|
|
67
|
+
const utf8Decoder = new TextDecoder('utf-8');
|
|
68
|
+
const utf8Encoder = new TextEncoder();
|
|
69
|
+
/**
|
|
70
|
+
* Read one C-style quoted token starting at the opening quote, returning the decoded path and the
|
|
71
|
+
* index just past the closing quote — or `undefined` when the quote is never closed.
|
|
72
|
+
*
|
|
73
|
+
* Octal escapes are collected as raw bytes and decoded as UTF-8 at the end, because a single
|
|
74
|
+
* non-ASCII character arrives as several `\NNN` escapes that are only meaningful together.
|
|
75
|
+
*/
|
|
76
|
+
function readQuotedToken(line, start) {
|
|
77
|
+
const bytes = [];
|
|
78
|
+
let index = start + 1;
|
|
79
|
+
while (index < line.length) {
|
|
80
|
+
const character = line[index];
|
|
81
|
+
if (character === '"') {
|
|
82
|
+
return { value: utf8Decoder.decode(Uint8Array.from(bytes)), end: index + 1 };
|
|
83
|
+
}
|
|
84
|
+
if (character === '\\') {
|
|
85
|
+
const escaped = line[index + 1];
|
|
86
|
+
if (escaped === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
if (escaped >= '0' && escaped <= '7') {
|
|
89
|
+
let digits = '';
|
|
90
|
+
let cursor = index + 1;
|
|
91
|
+
while (digits.length < 3 && line[cursor] >= '0' && line[cursor] <= '7') {
|
|
92
|
+
digits += line[cursor];
|
|
93
|
+
cursor++;
|
|
94
|
+
}
|
|
95
|
+
bytes.push(parseInt(digits, 8));
|
|
96
|
+
index = cursor;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const single = C_ESCAPES[escaped];
|
|
100
|
+
if (single === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
bytes.push(single);
|
|
103
|
+
index += 2;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
for (const byte of utf8Encoder.encode(character))
|
|
107
|
+
bytes.push(byte);
|
|
108
|
+
index++;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Split the unquoted `a/… b/…` remainder into its two prefixed halves — see the module docblock.
|
|
114
|
+
*
|
|
115
|
+
* The equal-halves rule is deterministic rather than merely first-match: equal strings have equal
|
|
116
|
+
* length, so a candidate whose halves are equal can only be the exact midpoint, and a string has
|
|
117
|
+
* one midpoint. At most one candidate can therefore satisfy it, and which one is found first never
|
|
118
|
+
* matters. The last candidate is the fallback only for a genuine rename, where no split makes the
|
|
119
|
+
* halves equal.
|
|
120
|
+
*/
|
|
121
|
+
function splitUnquotedPair(remainder) {
|
|
122
|
+
const candidates = [];
|
|
123
|
+
for (let index = remainder.indexOf(' b/'); index >= 0; index = remainder.indexOf(' b/', index + 1))
|
|
124
|
+
candidates.push(index);
|
|
125
|
+
if (candidates.length === 0)
|
|
126
|
+
return undefined;
|
|
127
|
+
const halves = candidates.map((index) => [remainder.slice(0, index), remainder.slice(index + 1)]);
|
|
128
|
+
return (halves.find(([left, right]) => left.slice(2) === right.slice(2)) ?? halves[halves.length - 1]);
|
|
129
|
+
}
|
|
130
|
+
/** Strip the `a/` or `b/` header prefix, or report that the token never had one. */
|
|
131
|
+
function stripPrefix(token, prefix) {
|
|
132
|
+
return token.startsWith(prefix) ? token.slice(prefix.length) : undefined;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Extract the repository-relative POSIX paths a unified diff touches, in the order the diff names
|
|
136
|
+
* them and with duplicates removed.
|
|
137
|
+
*
|
|
138
|
+
* De-duplication matters because the overwhelmingly common header names the same path twice (`a/x
|
|
139
|
+
* b/x`), and a caller reporting how many files a review saw would otherwise double every count.
|
|
140
|
+
* A rename's two distinct paths both survive it.
|
|
141
|
+
*
|
|
142
|
+
* Content that is not a unified diff — a requirements document, `--content-source text` output —
|
|
143
|
+
* simply yields an empty list. There is no error here: deciding what an empty result means belongs
|
|
144
|
+
* to the caller, which is the only place that knows whether a diff was expected.
|
|
145
|
+
*/
|
|
146
|
+
export function extractChangedPathsFromDiff(diff) {
|
|
147
|
+
const paths = new Set();
|
|
148
|
+
for (const rawLine of diff.split(/\r?\n/)) {
|
|
149
|
+
if (!rawLine.startsWith(DIFF_GIT_PREFIX))
|
|
150
|
+
continue;
|
|
151
|
+
const remainder = rawLine.slice(DIFF_GIT_PREFIX.length);
|
|
152
|
+
let left;
|
|
153
|
+
let right;
|
|
154
|
+
if (remainder.startsWith('"')) {
|
|
155
|
+
const first = readQuotedToken(remainder, 0);
|
|
156
|
+
if (!first || remainder[first.end] !== ' ' || remainder[first.end + 1] !== '"')
|
|
157
|
+
continue;
|
|
158
|
+
const second = readQuotedToken(remainder, first.end + 1);
|
|
159
|
+
if (!second)
|
|
160
|
+
continue;
|
|
161
|
+
left = stripPrefix(first.value, 'a/');
|
|
162
|
+
right = stripPrefix(second.value, 'b/');
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const pair = splitUnquotedPair(remainder);
|
|
166
|
+
if (!pair)
|
|
167
|
+
continue;
|
|
168
|
+
left = stripPrefix(pair[0], 'a/');
|
|
169
|
+
right = stripPrefix(pair[1], 'b/');
|
|
170
|
+
}
|
|
171
|
+
if (left === undefined || right === undefined)
|
|
172
|
+
continue;
|
|
173
|
+
paths.add(left);
|
|
174
|
+
paths.add(right);
|
|
175
|
+
}
|
|
176
|
+
return [...paths];
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=diffPaths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diffPaths.js","sourceRoot":"","sources":["../../src/utils/diffPaths.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** The config key that names the base a branch is reviewed against. */
|
|
2
|
+
export declare const MERGE_BASE_SETTING = "contentSourceConfig.git.mergeBase";
|
|
3
|
+
/**
|
|
4
|
+
* A git invocation that failed. `exitCode` is git's own exit status when git ran and exited
|
|
5
|
+
* non-zero, and `undefined` when it could not be spawned at all; callers use it to tell apart
|
|
6
|
+
* failures that print nothing (`git merge-base` exits 1 silently when there is no common
|
|
7
|
+
* ancestor) from the ones that print a `fatal:` line.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GitCommandError extends Error {
|
|
10
|
+
readonly exitCode: number | undefined;
|
|
11
|
+
constructor(message: string, exitCode: number | undefined);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Run git with an args array (execFile, no shell) in the current working directory. Rejects
|
|
15
|
+
* with a {@link GitCommandError} on a non-zero exit or spawn failure, with git's stderr reduced
|
|
16
|
+
* to its meaningful line as the message; benign stderr chatter on a zero exit is ignored (unlike
|
|
17
|
+
* systemUtils.execAsync, which rejects on any stderr output).
|
|
18
|
+
*/
|
|
19
|
+
export declare function runGit(args: string[]): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the merge base of `base` and `HEAD` to a commit sha, with `git merge-base <base> HEAD`.
|
|
22
|
+
*
|
|
23
|
+
* This is the ONE place the merge base is computed, and it is shared on purpose: the git content
|
|
24
|
+
* source diffs against it, and a later reader that lists the branch's commits (as requirements
|
|
25
|
+
* evidence) must compute its range from this same function. Two resolvers are how a reviewed
|
|
26
|
+
* diff and the evidence beside it would come to describe different branches.
|
|
27
|
+
*
|
|
28
|
+
* Why a sha rather than `git diff --merge-base <base>`: that flag needs git 2.30+, and it would
|
|
29
|
+
* put a flag on git's command line that came from the caller's side of the seam. Resolving first
|
|
30
|
+
* works on any git, and what reaches `git diff` afterwards is a validated object name, never
|
|
31
|
+
* something option-shaped.
|
|
32
|
+
*
|
|
33
|
+
* An unresolvable base throws; it never falls back to some other diff. A fallback (the plain
|
|
34
|
+
* working-tree diff, say) would silently review LESS than the user configured, which is worse
|
|
35
|
+
* than stopping. The messages name `origin` (by default the config key) so the user knows which
|
|
36
|
+
* setting to fix.
|
|
37
|
+
*
|
|
38
|
+
* @param base the configured ref, e.g. `origin/main`; rejected if it starts with `-`, so a value
|
|
39
|
+
* from config can never become a git flag.
|
|
40
|
+
* @param origin what to call the value in error messages.
|
|
41
|
+
* @returns the full merge-base commit sha.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveMergeBase(base: string, origin?: string): Promise<string>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `git diff` can emit multi-megabyte output for large changes; the node default (1 MiB)
|
|
3
|
+
* would truncate-and-fail such runs.
|
|
4
|
+
*/
|
|
5
|
+
const MAX_GIT_BUFFER = 32 * 1024 * 1024;
|
|
6
|
+
/** The config key that names the base a branch is reviewed against. */
|
|
7
|
+
export const MERGE_BASE_SETTING = 'contentSourceConfig.git.mergeBase';
|
|
8
|
+
/**
|
|
9
|
+
* A git invocation that failed. `exitCode` is git's own exit status when git ran and exited
|
|
10
|
+
* non-zero, and `undefined` when it could not be spawned at all; callers use it to tell apart
|
|
11
|
+
* failures that print nothing (`git merge-base` exits 1 silently when there is no common
|
|
12
|
+
* ancestor) from the ones that print a `fatal:` line.
|
|
13
|
+
*/
|
|
14
|
+
export class GitCommandError extends Error {
|
|
15
|
+
exitCode;
|
|
16
|
+
constructor(message, exitCode) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'GitCommandError';
|
|
19
|
+
this.exitCode = exitCode;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Run git with an args array (execFile, no shell) in the current working directory. Rejects
|
|
24
|
+
* with a {@link GitCommandError} on a non-zero exit or spawn failure, with git's stderr reduced
|
|
25
|
+
* to its meaningful line as the message; benign stderr chatter on a zero exit is ignored (unlike
|
|
26
|
+
* systemUtils.execAsync, which rejects on any stderr output).
|
|
27
|
+
*/
|
|
28
|
+
export async function runGit(args) {
|
|
29
|
+
const { execFile } = await import('node:child_process');
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
execFile('git', args, { maxBuffer: MAX_GIT_BUFFER }, (error, stdout, stderr) => {
|
|
32
|
+
if (error) {
|
|
33
|
+
const code = error.code;
|
|
34
|
+
reject(new GitCommandError(extractGitError(stderr ?? '', error.message), typeof code === 'number' ? code : undefined));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
resolve(stdout);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reduce git's stderr to the one meaningful line. Outside a repository `git diff` appends
|
|
43
|
+
* its entire `--no-index` usage screen after the warning line; that wall of text is noise
|
|
44
|
+
* in a CLI error. Prefer the `fatal:` line when present, else the first non-empty line.
|
|
45
|
+
*/
|
|
46
|
+
function extractGitError(stderr, fallback) {
|
|
47
|
+
const lines = stderr
|
|
48
|
+
.split('\n')
|
|
49
|
+
.map((line) => line.trim())
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
if (lines.length === 0) {
|
|
52
|
+
return fallback;
|
|
53
|
+
}
|
|
54
|
+
return lines.find((line) => line.startsWith('fatal:')) ?? lines[0];
|
|
55
|
+
}
|
|
56
|
+
/** A full object name as `git merge-base` prints it: SHA-1 (40) or SHA-256 (64) hex. */
|
|
57
|
+
const OBJECT_NAME = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the merge base of `base` and `HEAD` to a commit sha, with `git merge-base <base> HEAD`.
|
|
60
|
+
*
|
|
61
|
+
* This is the ONE place the merge base is computed, and it is shared on purpose: the git content
|
|
62
|
+
* source diffs against it, and a later reader that lists the branch's commits (as requirements
|
|
63
|
+
* evidence) must compute its range from this same function. Two resolvers are how a reviewed
|
|
64
|
+
* diff and the evidence beside it would come to describe different branches.
|
|
65
|
+
*
|
|
66
|
+
* Why a sha rather than `git diff --merge-base <base>`: that flag needs git 2.30+, and it would
|
|
67
|
+
* put a flag on git's command line that came from the caller's side of the seam. Resolving first
|
|
68
|
+
* works on any git, and what reaches `git diff` afterwards is a validated object name, never
|
|
69
|
+
* something option-shaped.
|
|
70
|
+
*
|
|
71
|
+
* An unresolvable base throws; it never falls back to some other diff. A fallback (the plain
|
|
72
|
+
* working-tree diff, say) would silently review LESS than the user configured, which is worse
|
|
73
|
+
* than stopping. The messages name `origin` (by default the config key) so the user knows which
|
|
74
|
+
* setting to fix.
|
|
75
|
+
*
|
|
76
|
+
* @param base the configured ref, e.g. `origin/main`; rejected if it starts with `-`, so a value
|
|
77
|
+
* from config can never become a git flag.
|
|
78
|
+
* @param origin what to call the value in error messages.
|
|
79
|
+
* @returns the full merge-base commit sha.
|
|
80
|
+
*/
|
|
81
|
+
export async function resolveMergeBase(base, origin = MERGE_BASE_SETTING) {
|
|
82
|
+
if (base.startsWith('-')) {
|
|
83
|
+
throw new Error(`Invalid ${origin} "${base}"; expected a ref (e.g. "origin/main"), not an option.`);
|
|
84
|
+
}
|
|
85
|
+
let output;
|
|
86
|
+
try {
|
|
87
|
+
output = await runGit(['merge-base', base, 'HEAD']);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// `git merge-base` exits 1 and prints nothing when the two commits share no history.
|
|
91
|
+
if (error instanceof GitCommandError && error.exitCode === 1) {
|
|
92
|
+
throw new Error(`${origin} "${base}" has no common ancestor with HEAD, so there is no merge base to review against.`);
|
|
93
|
+
}
|
|
94
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
95
|
+
throw new Error(`${origin} "${base}" could not be resolved to a merge base: ${reason}`);
|
|
96
|
+
}
|
|
97
|
+
const sha = output.trim();
|
|
98
|
+
if (!OBJECT_NAME.test(sha)) {
|
|
99
|
+
throw new Error(`${origin} "${base}" could not be resolved to a merge base: git merge-base printed "${sha}".`);
|
|
100
|
+
}
|
|
101
|
+
return sha;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaunt-sloth/review",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Review functionality for Gaunt Sloth",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Andrew Kondratev",
|
|
@@ -34,20 +34,21 @@
|
|
|
34
34
|
"#src/*.js": "./dist/*.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@langchain/core": "^1.2.
|
|
38
|
-
"@langchain/langgraph": "^1.4.
|
|
39
|
-
"langchain": "^1.5.
|
|
40
|
-
"zod": "^4.5
|
|
41
|
-
"@gaunt-sloth/core": "2.0.
|
|
37
|
+
"@langchain/core": "^1.2.12",
|
|
38
|
+
"@langchain/langgraph": "^1.4.16",
|
|
39
|
+
"langchain": "^1.5.11",
|
|
40
|
+
"zod": "^4.6.5",
|
|
41
|
+
"@gaunt-sloth/core": "2.0.1"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"./dist/*",
|
|
45
45
|
"cli.js"
|
|
46
46
|
],
|
|
47
47
|
"publishConfig": {
|
|
48
|
-
"tag": "
|
|
48
|
+
"tag": "latest"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
|
-
"build": "tsc"
|
|
51
|
+
"build": "tsc",
|
|
52
|
+
"value-surface:generate": "node scripts/generate-value-surface.mjs"
|
|
52
53
|
}
|
|
53
54
|
}
|