@gaunt-sloth/review 2.0.0-beta.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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), so the script exits non-zero exactly when `gth review` would. Those
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
- const config = await initConfig({
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
- const contentArg = args[0];
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
- const diffWithReqs = requirements
66
- ? `Requirements:\n${requirements}\nDiff:\n${content}`
67
- : content;
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('pr-review', preambleText, diffWithReqs, config, 'pr', undefined, {
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
@@ -1,4 +1,5 @@
1
1
  export * from '@gaunt-sloth/core/config.js';
2
+ export * from '#src/utils/diffPaths.js';
2
3
  export * from '#src/modules/reviewModule.js';
3
4
  export * from '#src/commands/commandUtils.js';
4
5
  export * from '#src/tools/ghReadFileTool.js';
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;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"}
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
- displayTermination(runner.getTerminationReason());
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":"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"}
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 [refRange]` — review the working tree (or a
4
- * ref range) without GitHub and without piping a diff through stdin.
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
- * @param _ config (unused in this source)
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`. When omitted, diffs the working tree against the index (plain `git diff`).
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 content).
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(_: ProviderConfig | null, refRange: string | undefined): Promise<string | null>;
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` can emit multi-megabyte output for large changes; the node default (1 MiB)
4
- * would truncate-and-fail such runs.
5
- */
6
- const MAX_DIFF_BUFFER = 32 * 1024 * 1024;
7
- /**
8
- * Gets a local diff via `git --no-pager diff [refRange]` — review the working tree (or a
9
- * ref range) without GitHub and without piping a diff through stdin.
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
- * @param _ config (unused in this source)
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`. When omitted, diffs the working tree against the index (plain `git diff`).
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 content).
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(_, refRange) {
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 the ref range is valid.`);
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":"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"}
1
+ {"version":3,"file":"gitDiffSource.js","sourceRoot":"","sources":["../../src/sources/gitDiffSource.ts"],"names":[],"mappings":"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"}
@@ -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.0-beta.9",
3
+ "version": "2.0.0",
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.9",
38
- "@langchain/langgraph": "^1.4.13",
39
- "langchain": "^1.5.10",
40
- "zod": "^4.5.4",
41
- "@gaunt-sloth/core": "2.0.0-beta.9"
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.0"
42
42
  },
43
43
  "files": [
44
44
  "./dist/*",
45
45
  "cli.js"
46
46
  ],
47
47
  "publishConfig": {
48
- "tag": "beta"
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
  }