feat(selfhost): add shared-base config layer for multi-repo operators (#1959) - #3660
Conversation
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-06 02:04:04 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 7 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3660 +/- ##
=======================================
Coverage 93.29% 93.29%
=======================================
Files 314 314
Lines 32057 32061 +4
Branches 11748 11747 -1
=======================================
+ Hits 29906 29910 +4
Misses 1517 1517
Partials 634 634
🚀 New features to boost your workflow:
|
…#1959) Adds a third, lowest-priority manifest-loading layer inside the existing GITTENSORY_REPO_CONFIG_DIR: `_shared/.gittensory.yml`. A self-host operator running many repos can now write one house review policy (tone, path filters, labeling rules) once instead of copy-pasting it into every repo's private config. The repo file still overlays the global default, which now overlays the shared base, using the same deep-merge/array-replace/explicit- null-clear semantics the existing per-repo/global layers already use. No shared file mounted (the default case) is byte-identical to prior behavior; a malformed/unreadable shared file is dropped from the merge and never blocks a review.
23b43da to
244bf35
Compare
Summary
directory (
GITTENSORY_REPO_CONFIG_DIR):_shared/.gittensory.yml(.yaml/.jsonalsoaccepted, same lookup order as every other candidate).
review.tone, a baselinepath_filters/wantedPathsset, commonlabeling_rules) onceinstead of copy-pasting it into every repo's per-repo file or the global default.
(shared base → global → per-repo, ascending priority). This reuses the exact same
mergeConfigOverlaydeep-merge/array-replace/explicit-null-clear helper the existingper-repo/global layers already use — no new merge algorithm.
mergeConfigOverlayitself isuntouched.
combineConfigText(binary, 2-layer) is generalized intocombineConfigLayers(N-way fold overan ascending-priority list), preserving every existing fallback rule: 1 layer present → its raw
text unchanged; 2+ parse → deep-merged JSON; 0 parse → highest-priority present layer's raw text.
A malformed/oversized/non-mapping layer is dropped from the fold and never blocks a review.
review.shared_configfrom the issue title describes the loadingpipeline change, not a new key under
FocusManifestReviewConfig—focus-manifest.tsandfocus-manifest-loader.tsare unchanged. Confirmed via the.gittensory.yml.examplefield-exhaustiveness test (
test/unit/focus-manifest.test.ts), which needed no new entries andpasses unchanged (442 tests).
behavior — verified by re-running every pre-existing
private-config.test.tscase unchanged(all 39 pass with zero edits).
config/examples/README.md, updated directory-layout block and precedence chain (now 6 layers),updated overlay-semantics prose for N-way folding, and a new worked example (shared + global +
per-repo, all three present). Added
config/examples/shared.gittensory.ymlas a copy-pastestarter, mirroring the existing
global.gittensory.yml/repo-override.gittensory.ymlpattern.Small descriptive comment update in
docker-compose.ymlnear the existingGITTENSORY_REPO_CONFIG_DIRenv var comment._sharedwould collide with the shared-base folder name (repo segments allow_as an interiorcharacter). No functional fix; documented as an operator-side naming convention instead.
Closes #1959
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlint— not run locally; no workflow/YAML files touched by this PR, CI will confirm.npm run typecheck— clean.npm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate. — ran the affected suite directly (test/unit/private-config.test.ts,test/unit/selfhost-config-examples.test.ts,test/unit/focus-manifest-loader.test.ts,test/unit/focus-manifest.test.ts; 530 tests) with--coverage --coverage.reporter=json, then cross-referenced every changed line range insrc/selfhost/private-config.ts(fromgit diff origin/main --unified=0) againstcoverage-final.json'sstatementMap/branchMap/fnMapin a small script: zero uncovered statements, branches, or functions in any changed range. The whole file reports 100% statements/branches/functions/lines. Did not run the full unshardednpm run test:coverageacross the whole repo, per this repo's own guidance to avoid duplicating the CI gate locally for a narrowly-scoped change — CI will run it in full.npm run test:workers— not run locally; this PR touches no Workers-runtime code path (Node-onlysrc/selfhost/**), CI will confirm.npm run build:mcp— not run locally; no MCP package changes in this PR.npm run test:mcp-pack— not run locally; no MCP package changes in this PR.npm run ui:openapi:check— not applicable; no API/OpenAPI schema changes. Also rannpm run cf-typegen:checkandnpm run db:migrations:checkproactively (both clean) even though neither wrangler bindings nor the DB schema changed.npm run ui:lint— not run locally; noapps/gittensory-ui/**changes in this PR.npm run ui:typecheck— not run locally; noapps/gittensory-ui/**changes in this PR.npm run ui:build— not run locally; noapps/gittensory-ui/**changes in this PR.npm audit --audit-level=moderate— not run locally; no dependency changes in this PR (nopackage.json/lockfile edits).describe("makeLocalManifestReader — shared base layer (#1959)", ...)block: shared-only, repo-only (byte-identical regression case), shared+repo (no global), all-three-present (fold-direction correctness), array-replace-wholesale across 3 layers, explicit-null-clear across 3 layers, malformed shared file with repo+global present (fail-safe, non-blocking), oversized shared file as the only other layer (fail-safe, non-blocking), all-three-malformed fallback, and exact candidate-path ordering (.yml→.yaml→.json).Also ran (not in the template's list, but relevant to this change):
npm run selfhost:env-reference:check(clean — noenv.SOMETHINGreads added/moved) andnode scripts/check-docs-drift.mjs(clean).If any required check was skipped, explain why:
src/selfhost/private-config.ts+ tests + docs + onedocker-compose.ymlcomment line — none ofthose surfaces are touched. CI's
test:ciruns the complete gate regardless; I ran the narrower,directly-relevant local checks (typecheck, the affected test files with coverage
cross-referenced line-by-line, migrations/cf-typegen/env-reference/docs-drift checks) rather than
duplicating the whole suite locally for a change this scoped.
Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. — not applicable; no visible UI change (backend/docs only).config/examples/README.mdupdated;CHANGELOG.mdintentionally untouched.Notes
schema —
src/signals/focus-manifest.tsandsrc/signals/focus-manifest-loader.tsareunchanged, and
test/unit/focus-manifest.test.ts's.gittensory.yml.examplefield-exhaustiveness maps needed no new entries (verified by running that file's full 442-test
suite unchanged).
narrow: no changes to
focus-manifest.ts/focus-manifest-loader.ts(none needed — the loaderalready treats the private-config reader's return value as an opaque already-merged string), no
new env var (the issue explicitly asks to build on the existing
GITTENSORY_REPO_CONFIG_DIR,which this does).
private-config.tsmodule header): arepo literally named
_sharedwould be shadowed by the new shared-base folder name, sinceisSafeRepoSegmentpermits_as an interior character in a repo segment. Flagging here per thebrief's request — no functional fix proposed; an operator hosting such a repo should use a
private, documented workaround rather than relying on automatic disambiguation.