From 7d28dc0f90b54712d84537edc54f7637da35ba17 Mon Sep 17 00:00:00 2001 From: Matt Carvin <90224411+mcarvin8@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:25:44 -0400 Subject: [PATCH 1/3] feat!: merge usage into summarizeGitDiff, peel tags in merge-base, boolean --merge-base flag - summarizeGitDiff now always returns { summary, usage }; summarizeGitDiffWithUsage is removed. - getMergeBase peels annotated tags (revParse returns the tag object oid, not the commit; tsgit's mergeBase primitive doesn't peel) so --merge-base against a tag no longer falsely reports no common ancestor. - --from-merge-base is replaced by --merge-base/-b: /--from is now always required, and --merge-base resolves it as merge-base(to, from). BREAKING CHANGE: summarizeGitDiffWithUsage is removed; summarizeGitDiff's return type changes from string to { summary, usage }. --from-merge-base is removed in favor of --from/ plus --merge-base/-b (mergeBase option replaces fromMergeBase). Co-Authored-By: Claude Sonnet 5 --- README.md | 20 +++++++++------- src/cli.ts | 10 +++----- src/cliOptions.ts | 24 +++++++------------ src/git/gitDiffOps.ts | 22 +++++++++++++++++- src/index.ts | 44 +++++++++++------------------------ test/cli.spec.ts | 15 +++++++++--- test/cliOptions.spec.ts | 32 +++++++++++-------------- test/gitDiff.async.spec.ts | 15 ++++++++++++ test/index.spec.ts | 4 ++-- test/summarizeGitDiff.spec.ts | 10 ++++---- 10 files changed, 105 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index fd07169..2e455f1 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Use smart-diff as a library, or as the `smart-diff` CLI binary that ships with t ```ts import { summarizeGitDiff } from '@mcarvin/smart-diff'; -const markdown = await summarizeGitDiff({ +const { summary, usage } = await summarizeGitDiff({ from: 'origin/main', to: 'HEAD', cwd: '/path/to/repo', // optional; default process.cwd() @@ -150,6 +150,8 @@ const markdown = await summarizeGitDiff({ model: 'claude-3-5-sonnet-latest', // optional maxDiffChars: 120_000, // optional; also see LLM_MAX_DIFF_CHARS }); +// summary: Markdown string +// usage: LlmUsageReport aggregated across every LLM call — see Token usage reporting below ``` ### Use as a CLI @@ -161,7 +163,7 @@ npx smart-diff --help ``` - `` (required) and `[to]` (default `HEAD`) can be passed positionally or via `--from`/`--to`. -- Instead of ``/`--from`, pass `--from-merge-base ` to resolve `from` as the merge base of `to` and `` — e.g. `--to develop --from-merge-base main` is the tsgit-native equivalent of `--to develop --from $(git merge-base develop main)`, with no local git binary required. Mutually exclusive with ``/`--from`. +- Pass `--merge-base` / `-b` alongside ``/`--from` to resolve it as the merge base of `to` and `from` instead of using it directly — e.g. `--to develop --from main --merge-base` is the tsgit-native equivalent of `--to develop --from $(git merge-base develop main)`, with no local git binary required. - Repeatable options (`--include`, `--exclude`, `--commit-include`, `--commit-exclude`) accept multiple flags. - The Markdown summary is printed to stdout; errors go to stderr and exit with code 1. - Run `smart-diff --help` for the full flag reference, or `smart-diff --version` for the installed version. @@ -173,7 +175,7 @@ npx smart-diff --help | Option | CLI flag | Description | |--------|----------|-------------| | `from` / `to` | `` `[to]` / `--from` / `--to` | Git refs for the range; `to` defaults to `HEAD`. | -| `fromMergeBase` | `--from-merge-base ` | Resolve `from` as the merge base of `to` and ``, in-process via tsgit — no local git binary needed. Mutually exclusive with `from`/`--from`. | +| `mergeBase` | `--merge-base`, `-b` | Resolve `from` as the merge base of `to` and `from`, in-process via tsgit — no local git binary needed. | | `cwd` / `git` | `--cwd ` | Working directory path, or inject your own `GitClient` instance (library only; see [Lower-level API](#lower-level-api)). | | `includeFolders` | `--include ` | Limit diff to these paths relative to repo root (omit for full repo minus excludes). | | `excludeFolders` | `--exclude ` | Excluded paths, applied client-side to the changed-path list (directory-prefix match), e.g. `node_modules`. | @@ -214,7 +216,7 @@ npx smart-diff --help | Option | CLI flag | Description | |--------|----------|-------------| -| use `summarizeGitDiffWithUsage` instead | `--usage` | Print the same `LlmUsageReport` as [Token usage reporting](#token-usage-reporting) to stderr, after the Markdown summary on stdout. | +| — (`summarizeGitDiff` always returns `usage`) | `--usage` | Print the same `LlmUsageReport` as [Token usage reporting](#token-usage-reporting) to stderr, after the Markdown summary on stdout. | | — | `-h`, `--help` | Show CLI help. | | — | `-v`, `--version` | Print the installed version. | @@ -253,12 +255,12 @@ This costs one extra LLM call per batch plus one reduce call, so it's slower and ### Token usage reporting -`summarizeGitDiffWithUsage` returns the same Markdown summary plus token usage aggregated across every LLM call made to produce it — one call by default, or every map-reduce batch plus the reduce call when `mapReduce` is used: +`summarizeGitDiff` returns the Markdown summary plus token usage aggregated across every LLM call made to produce it — one call by default, or every map-reduce batch plus the reduce call when `mapReduce` is used: ```ts -import { summarizeGitDiffWithUsage } from '@mcarvin/smart-diff'; +import { summarizeGitDiff } from '@mcarvin/smart-diff'; -const { summary, usage } = await summarizeGitDiffWithUsage({ from: 'origin/main' }); +const { summary, usage } = await summarizeGitDiff({ from: 'origin/main' }); // usage: { requestCount, inputTokens, outputTokens, totalTokens, cachedInputTokens } ``` @@ -272,7 +274,7 @@ If you want full control — for example, to configure retries, middlewares, or import { summarizeGitDiff } from '@mcarvin/smart-diff'; import { createAnthropic } from '@ai-sdk/anthropic'; -const md = await summarizeGitDiff({ +const { summary } = await summarizeGitDiff({ from: 'origin/main', llmModelProvider: async () => createAnthropic({ apiKey: process.env.MY_ANTHROPIC_KEY })( @@ -309,7 +311,7 @@ Only the [lower-level API](#lower-level-api) is affected: - `buildDiffShapingGitArgs` is removed — `contextLines` and `ignoreWhitespace` are now consumed directly by the diff renderer instead of being turned into `git diff` flags. - `parseDiffSummary` is removed; build `DiffFileSummary` entries from a `DiffChange` + rendered diff via `buildFileSummary`, and aggregate with `mergeFileSummariesByPath` / `summarizeFiles`. -`summarizeGitDiff`, `summarizeGitDiffWithUsage`, and every CLI flag are unchanged. +Every CLI flag is unchanged. (`summarizeGitDiff`'s return shape changed separately — see [Token usage reporting](#token-usage-reporting).) ## Used By diff --git a/src/cli.ts b/src/cli.ts index 74592b6..1a28e9f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { CliUsageError, HELP_TEXT, parseCliArgs } from "./cliOptions.js"; -import { summarizeGitDiff, summarizeGitDiffWithUsage } from "./index.js"; +import { summarizeGitDiff } from "./index.js"; function readPackageVersion(): string { const packageJsonUrl = new URL("../package.json", import.meta.url); @@ -27,15 +27,11 @@ async function main(): Promise { return; } + const { summary, usage } = await summarizeGitDiff(parsed.options); + process.stdout.write(`${summary}\n`); if (parsed.reportUsage) { - const { summary, usage } = await summarizeGitDiffWithUsage(parsed.options); - process.stdout.write(`${summary}\n`); process.stderr.write(`\n[usage] ${JSON.stringify(usage)}\n`); - return; } - - const summary = await summarizeGitDiff(parsed.options); - process.stdout.write(`${summary}\n`); } main().catch((err: unknown) => { diff --git a/src/cliOptions.ts b/src/cliOptions.ts index e1b7516..ea3d5b0 100644 --- a/src/cliOptions.ts +++ b/src/cliOptions.ts @@ -16,14 +16,14 @@ export const HELP_TEXT = `smart-diff [to] [options] Summarizes a git diff between two refs using an LLM, printed as Markdown to stdout. Arguments: - Start ref (required unless --from-merge-base is set; also settable via --from) + Start ref (required; also settable via --from) [to] End ref (default: HEAD; also settable via --to) Core: - --from-merge-base Resolve as the merge base of --to and , e.g. - --to develop --from-merge-base main (no local git binary - needed — resolved in-process via tsgit). Mutually - exclusive with /--from. + -b, --merge-base Resolve as the merge base of --to and , e.g. + --to develop --from main --merge-base is the tsgit-native + equivalent of --to develop --from $(git merge-base develop main) + (no local git binary needed — resolved in-process via tsgit). --cwd Repo working directory (default: process.cwd()) --include Only include this path (repeatable) --exclude Exclude this path (repeatable) @@ -78,7 +78,7 @@ export function parseCliArgs(argv: string[]): ParsedCli { allowPositionals: true, options: { from: { type: "string" }, - "from-merge-base": { type: "string" }, + "merge-base": { type: "boolean", short: "b" }, to: { type: "string" }, cwd: { type: "string" }, include: { type: "string", multiple: true }, @@ -108,22 +108,16 @@ export function parseCliArgs(argv: string[]): ParsedCli { if (values.version) return { kind: "version" }; const from = values.from ?? positionals[0]; - const fromMergeBase = values["from-merge-base"]; - if (from && fromMergeBase) { + if (!from) { throw new CliUsageError( - "/--from and --from-merge-base are mutually exclusive.", - ); - } - if (!from && !fromMergeBase) { - throw new CliUsageError( - "Missing required ref. Usage: smart-diff [to] [options]\nRun with --help for details.\nAlternatively, pass --from-merge-base to resolve as the merge base of --to and .", + "Missing required ref. Usage: smart-diff [to] [options]\nRun with --help for details.", ); } const to = values.to ?? positionals[1]; const options: GitDiffAiSummaryOptions = { from, - fromMergeBase, + mergeBase: values["merge-base"], to, cwd: values.cwd, includeFolders: values.include, diff --git a/src/git/gitDiffOps.ts b/src/git/gitDiffOps.ts index aff0cc8..45e04af 100644 --- a/src/git/gitDiffOps.ts +++ b/src/git/gitDiffOps.ts @@ -53,6 +53,22 @@ export async function getCommits( })); } +/** + * Peel an object id down to the commit it (transitively) points at. + * `revParse` on an annotated tag returns the tag object's own oid, not the + * commit it targets, and tsgit's `mergeBase` primitive doesn't peel — so + * feeding a tag oid straight in would report no common ancestor even for a + * perfectly valid ref. + */ +async function peelToCommit(git: GitClient, id: ObjectId): Promise { + let current = id; + for (;;) { + const object = await git.primitives.readObject(current); + if (object.type !== "tag") return current; + current = object.data.object; + } +} + /** * Resolve the best common ancestor of `to` and `other`, in-process via tsgit — * the equivalent of `git merge-base ` without a local git binary. @@ -66,7 +82,11 @@ export async function getMergeBase( git.revParse(to), git.revParse(other), ]); - const [base] = await git.primitives.mergeBase([toId, otherId]); + const [peeledTo, peeledOther] = await Promise.all([ + peelToCommit(git, toId), + peelToCommit(git, otherId), + ]); + const [base] = await git.primitives.mergeBase([peeledTo, peeledOther]); if (!base) { throw new Error(`No merge base found between '${to}' and '${other}'`); } diff --git a/src/index.ts b/src/index.ts index 3470e93..7dd97af 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ import { type GenerateSummaryInput, - generateSummary, generateSummaryWithUsage, type LlmModelProvider, type LlmProviderId, @@ -24,17 +23,14 @@ import { } from "./git/index.js"; export type GitDiffAiSummaryOptions = { + /** Start ref (older side of the range). Required. */ + from: string; /** - * Start ref (older side of the range). Required unless `fromMergeBase` is set. - */ - from?: string; - /** - * Resolve `from` as the merge base of `to` and this ref, instead of passing - * an explicit `from`. Equivalent to `--from $(git merge-base )`, - * resolved in-process via tsgit — no local git binary required. Mutually - * exclusive with `from`. + * Resolve the effective `from` as the merge base of `to` and `from`, instead + * of using `from` directly. Equivalent to `--from $(git merge-base + * )`, resolved in-process via tsgit — no local git binary required. */ - fromMergeBase?: string; + mergeBase?: boolean; /** End ref; defaults to `HEAD`. */ to?: string; /** Working directory of the git repository; defaults to `process.cwd()`. */ @@ -185,16 +181,11 @@ async function resolveFromRef( options: GitDiffAiSummaryOptions, to: string, ): Promise { - if (options.from && options.fromMergeBase) { - throw new Error( - "`from` and `fromMergeBase` are mutually exclusive — pass one or the other.", - ); - } - if (options.fromMergeBase) { - return getMergeBase(git, to, options.fromMergeBase); - } if (!options.from) { - throw new Error("Either `from` or `fromMergeBase` must be provided."); + throw new Error("`from` must be provided."); + } + if (options.mergeBase) { + return getMergeBase(git, to, options.from); } return options.from; } @@ -270,20 +261,13 @@ async function prepareSummaryInput( /** * Produce an AI-assisted Markdown summary of the git changes between `from` and `to`, - * honoring path filters, commit message include/exclude regexes, optional team label, and optional system prompt. + * honoring path filters, commit message include/exclude regexes, optional team label, and + * optional system prompt. Also returns token usage aggregated across every LLM call made + * to produce the summary (one call, or every map-reduce batch plus the reduce call). See + * {@link LlmUsageReport}. */ export async function summarizeGitDiff( options: GitDiffAiSummaryOptions, -): Promise { - return generateSummary(await prepareSummaryInput(options)); -} - -/** - * Same as `summarizeGitDiff`, but also returns token usage aggregated across - * every LLM call made to produce the summary. See {@link LlmUsageReport}. - */ -export async function summarizeGitDiffWithUsage( - options: GitDiffAiSummaryOptions, ): Promise<{ summary: string; usage: LlmUsageReport }> { return generateSummaryWithUsage(await prepareSummaryInput(options)); } diff --git a/test/cli.spec.ts b/test/cli.spec.ts index 7ed3015..8902f79 100644 --- a/test/cli.spec.ts +++ b/test/cli.spec.ts @@ -83,7 +83,16 @@ describe("cli entrypoint", () => { (mod) => { summarizeGitDiffSpy = vi .spyOn(mod, "summarizeGitDiff") - .mockResolvedValue("## Summary\n\nchanges"); + .mockResolvedValue({ + summary: "## Summary\n\nchanges", + usage: { + requestCount: 1, + inputTokens: 0, + outputTokens: 0, + totalTokens: 0, + cachedInputTokens: 0, + }, + }); }, ); @@ -94,11 +103,11 @@ describe("cli entrypoint", () => { expect(exitCode).toBeUndefined(); }); - it("uses summarizeGitDiffWithUsage and reports usage on stderr when --usage is set", async () => { + it("reports usage on stderr when --usage is set", async () => { const { stdout, stderr, exitCode } = await runCli( ["origin/main", "--usage"], (mod) => { - vi.spyOn(mod, "summarizeGitDiffWithUsage").mockResolvedValue({ + vi.spyOn(mod, "summarizeGitDiff").mockResolvedValue({ summary: "## Summary", usage: { requestCount: 1, diff --git a/test/cliOptions.spec.ts b/test/cliOptions.spec.ts index a3861a3..b0c788d 100644 --- a/test/cliOptions.spec.ts +++ b/test/cliOptions.spec.ts @@ -56,31 +56,25 @@ describe("parseCliArgs", () => { expect(parsed.options.to).toBe("flag-to"); }); - it("accepts --from-merge-base in place of ", () => { - const parsed = parseCliArgs([ - "--to", - "develop", - "--from-merge-base", - "main", - ]); + it("accepts --merge-base alongside to resolve it as a merge base", () => { + const parsed = parseCliArgs(["main", "--to", "develop", "--merge-base"]); if (parsed.kind !== "run") throw new Error("expected run"); - expect(parsed.options.from).toBeUndefined(); - expect(parsed.options.fromMergeBase).toBe("main"); + expect(parsed.options.from).toBe("main"); + expect(parsed.options.mergeBase).toBe(true); expect(parsed.options.to).toBe("develop"); }); - it("throws CliUsageError when both and --from-merge-base are passed", () => { - expect(() => - parseCliArgs(["origin/main", "--from-merge-base", "main"]), - ).toThrow(CliUsageError); - expect(() => - parseCliArgs(["origin/main", "--from-merge-base", "main"]), - ).toThrow(/mutually exclusive/); + it("accepts -b as a shorthand for --merge-base", () => { + const parsed = parseCliArgs(["main", "-b"]); + if (parsed.kind !== "run") throw new Error("expected run"); + expect(parsed.options.mergeBase).toBe(true); }); - it("throws CliUsageError when neither nor --from-merge-base are passed", () => { - expect(() => parseCliArgs(["--to", "develop"])).toThrow(CliUsageError); - expect(() => parseCliArgs(["--to", "develop"])).toThrow( + it("throws CliUsageError when is missing even if --merge-base is passed", () => { + expect(() => parseCliArgs(["--to", "develop", "--merge-base"])).toThrow( + CliUsageError, + ); + expect(() => parseCliArgs(["--to", "develop", "--merge-base"])).toThrow( /Missing required ref/, ); }); diff --git a/test/gitDiff.async.spec.ts b/test/gitDiff.async.spec.ts index 294deef..72ca81f 100644 --- a/test/gitDiff.async.spec.ts +++ b/test/gitDiff.async.spec.ts @@ -400,5 +400,20 @@ describe("gitDiffOps against a real tsgit repo", () => { expect(await getMergeBase(git, descendant, base)).toBe(base); }); + + it("peels an annotated tag to its target commit instead of reporting no common ancestor", async () => { + const base = await fx.commit("base", { "a.ts": "1\n" }); + const descendant = await fx.commit("descendant", { "a.ts": "2\n" }); + await git.config.set({ key: "user.name", value: "Test" }); + await git.config.set({ key: "user.email", value: "test@example.com" }); + await git.tag.create({ + name: "v1.0.0", + target: base, + message: "release", + }); + + expect(await getMergeBase(git, descendant, "v1.0.0")).toBe(base); + expect(await getMergeBase(git, "v1.0.0", descendant)).toBe(base); + }); }); }); diff --git a/test/index.spec.ts b/test/index.spec.ts index eceeb3a..cd5d401 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -31,7 +31,7 @@ describe("summarizeGitDiff", () => { "node_modules/pkg.js": "vendored2\n", }); - const md = await summarizeGitDiff({ + const { summary } = await summarizeGitDiff({ from: c1, to: c2, git: fx.repo, @@ -41,7 +41,7 @@ describe("summarizeGitDiff", () => { llmModelProvider: mockLlmProvider("# Infra Summary\nBody from model"), }); - expect(md).toBe("# Infra Summary\nBody from model"); + expect(summary).toBe("# Infra Summary\nBody from model"); }); it("uses per-commit diff shape when include regexes are set even if all match", async () => { diff --git a/test/summarizeGitDiff.spec.ts b/test/summarizeGitDiff.spec.ts index c8593c5..349892f 100644 --- a/test/summarizeGitDiff.spec.ts +++ b/test/summarizeGitDiff.spec.ts @@ -1,7 +1,7 @@ import type { LanguageModel } from "ai"; import * as gitDiff from "../src/git/index"; -import { summarizeGitDiff, summarizeGitDiffWithUsage } from "../src/index"; +import { summarizeGitDiff } from "../src/index"; import { makeMockModel, makeUsageMockProvider } from "./helpers/mockLlm"; import { createFixtureRepo, type FixtureRepo } from "./helpers/tsgitFixture"; @@ -27,7 +27,7 @@ describe("summarizeGitDiff integration", () => { const createSpy = vi.spyOn(gitDiff, "createGitClient"); - const md = await summarizeGitDiff({ + const { summary } = await summarizeGitDiff({ from: c1, to: c2, cwd: fx.dir, @@ -35,7 +35,7 @@ describe("summarizeGitDiff integration", () => { }); expect(createSpy).toHaveBeenCalledWith(fx.dir); - expect(md).toBe("summary"); + expect(summary).toBe("summary"); }); it("uses per-commit diff shape when filtered commits differ without regex options", async () => { @@ -61,7 +61,7 @@ describe("summarizeGitDiff integration", () => { ); }); - it("summarizeGitDiffWithUsage returns the summary alongside aggregated token usage", async () => { + it("summarizeGitDiff returns the summary alongside aggregated token usage", async () => { const c1 = await fx.commit("root", { "a.ts": "1\n" }); const c2 = await fx.commit("edit", { "a.ts": "2\n" }); @@ -69,7 +69,7 @@ describe("summarizeGitDiff integration", () => { { text: "summary", inputTokens: 42, outputTokens: 8 }, ]); - const { summary, usage } = await summarizeGitDiffWithUsage({ + const { summary, usage } = await summarizeGitDiff({ from: c1, to: c2, git: fx.repo, From 77bdb31cbca7b1a7eb0bbc258e91f216ae3e1381 Mon Sep 17 00:00:00 2001 From: Matt Carvin <90224411+mcarvin8@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:32:14 -0400 Subject: [PATCH 2/3] test: cover getMergeBase's no-common-ancestor throw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores 100% coverage on gitDiffOps.ts after the annotated-tag peeling change — the throw path was never exercised by a real test. Co-Authored-By: Claude Sonnet 5 --- test/gitDiff.async.spec.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/gitDiff.async.spec.ts b/test/gitDiff.async.spec.ts index 72ca81f..a92693a 100644 --- a/test/gitDiff.async.spec.ts +++ b/test/gitDiff.async.spec.ts @@ -415,5 +415,23 @@ describe("gitDiffOps against a real tsgit repo", () => { expect(await getMergeBase(git, descendant, "v1.0.0")).toBe(base); expect(await getMergeBase(git, "v1.0.0", descendant)).toBe(base); }); + + it("throws when the two refs share no common ancestor", async () => { + const base = await fx.commit("base", { "a.ts": "1\n" }); + const descendant = await fx.commit("descendant", { "a.ts": "2\n" }); + const noMergeBaseGit = { + revParse: git.revParse.bind(git), + primitives: { + ...git.primitives, + mergeBase: async () => [], + }, + } as unknown as GitClient; + + await expect( + getMergeBase(noMergeBaseGit, descendant, base), + ).rejects.toThrow( + `No merge base found between '${descendant}' and '${base}'`, + ); + }); }); }); From e6ff0cc3fb6077dd4ea6543e069b587de9f3e759 Mon Sep 17 00:00:00 2001 From: Matt Carvin <90224411+mcarvin8@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:33:07 -0400 Subject: [PATCH 3/3] build(deps): bump tsgit to the latest --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d7b187..a86e4cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "4.1.1", "license": "MIT", "dependencies": { - "@scolladon/tsgit": "3.2.1", + "@scolladon/tsgit": "3.4.0", "ai": "7.0.58", "diff": "9.0.0" }, @@ -3579,9 +3579,9 @@ ] }, "node_modules/@scolladon/tsgit": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@scolladon/tsgit/-/tsgit-3.2.1.tgz", - "integrity": "sha512-DskPzL5Ix1zPccIzTIa1Gr6uXPVXFmifQ8k0AD2zxmKus7fIpv3owTe9/ZLuKBOEdTTp0mEj5gSSsMf+zzenuA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@scolladon/tsgit/-/tsgit-3.4.0.tgz", + "integrity": "sha512-AeRPDWRApon1XhARTpJWyIMV6jKfxOq78VTfWiAGSU9pnTANIJTsTYny84QYJRLvpsWFUF4OTPneWyE7PAkA7w==", "license": "MIT", "engines": { "node": ">=22.22.1" diff --git a/package.json b/package.json index 9236cb8..7c7da69 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "vitest": "4.1.5" }, "dependencies": { - "@scolladon/tsgit": "3.2.1", + "@scolladon/tsgit": "3.4.0", "ai": "7.0.58", "diff": "9.0.0" },