feat(mcp): cut GitHub API calls per write (blob inlining, read memoization, stats injection)#63
Merged
Merged
Conversation
applyPlanToGitHub created a blob per changed file (client.rest.git.createBlob) then referenced its SHA in the tree. GitHub's Trees API accepts file content inline on a tree entry, so the per-file blob POST is unnecessary: writes now carry content directly and deletions keep sha: null. A write drops from N+3 GitHub mutations to a fixed 3 (createTree + createCommit + updateRef) regardless of file count, which directly eases the mutation-rate secondary limit. buildTreeEntry becomes synchronous (no client needed). Brings GitHub to parity with the GitLab provider, which already inlines content in its commit actions. Tests updated to assert content-in-tree + no createBlob, plus a byte-fidelity round-trip (unicode/emoji/newlines). http integration path green.
new GitHubReader(client, repo, { memoize: true }) dedupes repeated (path, ref)
reads within one operation across readFile/listDirectory/fileExists, so flows
that re-read the same files (context build, validate) pay one getContent
instead of N. Opt-in and OFF by default — behavior is byte-identical without
the flag. A rejected read is evicted from the memo so a transient error is
retried, not cached forever. The provider's own reader (github/provider.ts) is
long-lived and does NOT enable memoization: a reader that outlives a write
would serve stale results; short-lived read-only consumers opt in explicitly.
buildContextChange walked listModels + readModel + countEntries over the reader (O(models·locales) round trips on a remote provider) purely to fill stats.models / stats.entries. A new optional opts.stats lets a caller that already knows those counts (e.g. Studio's index) skip the walk entirely; only readConfig remains, for the locale list and lastOperation.locale default. The emitted context.json is byte-identical to the scanned variant. Parameterless callers are unchanged. Adds the first tests for buildContextChange (byte-equal injected vs scanned, no-scan assertion, null-entries dropped).
✅ Deploy Preview for contentrain-ai ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three additive, independently-shippable optimizations that cut GitHub API calls per write — the spec Ahmet derived from the 1.6.0 dist bundle, verified against source and adjusted where the source diverged from the bundle. No breaking changes; one minor bump.
M1 — applyPlan blob inlining
feat(mcp): inline blob content in GitHub tree entriesapplyPlanToGitHubcreated a blob per changed file (createBlob) then referenced its SHA in the tree. GitHub's Trees API accepts content inline on a tree entry, so the per-file blob POST is unnecessary.contentdirectly; deletions keepsha: null.buildTreeEntrybecomes synchronous.N+3GitHub mutations to a fixed3(createTree + createCommit + updateRef) regardless of file count — directly easing the mutation-rate secondary limit that the high-call-count scenario hits.gitlab/apply-plan.ts) already inlines content in its commit actions — M1 brings GitHub to that parity.Tests: assert content-in-tree +
createBlobnever called + a byte-fidelity round-trip (unicode/emoji/newlines).http.test.tsGitHub write path stays green.M3 — GitHubReader opt-in read memoization
feat(mcp): opt-in read memoization for GitHubReadernew GitHubReader(client, repo, { memoize: true })dedupes repeated(path, ref)reads within one operation acrossreadFile/listDirectory/fileExists.github/provider.ts:42) is constructed once and long-lived, so it does not enable memoization — a reader that outlives a write would serve stale results. Only short-lived read-only consumers opt in (Studio-side; in-repo wiring is a follow-up, since remotevalidatecurrently delegates to the provider's long-lived reader).Tests: off → N getContent (unchanged); on → 1 getContent per
(path, ref), keyed by ref, per-method; rejected not cached.M2 — buildContextChange stats injection
feat(mcp): allow injected stats in buildContextChangebuildContextChange(reader, operation, source, { stats })lets a caller that already knows the model/entry counts skip theO(models·locales)reader walk; onlyreadConfigremains.buildContextChangehas no caller in this repo (Studio consumes it), so this is an additive API — it does not speed up anything local.ContextStatsintentionally omitslocales(it already comes fromreadConfig, which stays) to avoid an injected/config mismatch. AndwriteContext(the local path) is deliberately not changed — it reads local disk (cheap); only the remote reader's HTTP round-trips were worth cutting.buildContextChange.Verification
Targeted (the full blast radius of these surgical, GitHub-provider-scoped changes):
apply-plan.test.ts6/6,reader.test.ts16/16,context.test.ts4/4 (new),http.test.ts16/16.oxlint0/0,tsc --noEmitclean.The local / git-transaction / write-path suites are untouched by these changes (LocalProvider and the worktree flow are not on any of these code paths), so they were not re-run.
🤖 Generated with Claude Code