Repository navigation
feat(snapshot): mode snapshot writes live settings to a file or a per-repo directory - #146
Conversation
9a6d86b to
15b7cc5
Compare
b08ad63 to
a34b6da
Compare
File size check1 over a hard cap (fails), 5 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 5 managed file(s) skipped; repo-platform owns them. |
0ac317b to
2a92e5b
Compare
7ccf945 to
59243ec
Compare
59243ec to
8cbd122
Compare
d8284c2 to
c2332ba
Compare
8cbd122 to
3d5fa53
Compare
fd5c230 to
da2af61
Compare
3d5fa53 to
e2a0bce
Compare
e2a0bce to
22ef2bd
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Critical file-write safety and moderate result-semantics issues remain, along with documentation and test consistency nits.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds read-only mode: snapshot to export live repository settings as YAML for single or multiple repositories, with redaction, validation, library APIs, and round-trip coverage.
Changes:
- Adds snapshot parsing, execution, rendering, results, and file/directory outputs.
- Adds public APIs, action inputs/outputs, and multi-repository support.
- Adds section fixtures, unit tests, e2e scenarios, and documentation updates.
File summaries
| File | Reviewed change |
|---|---|
test/schema-corpus.test.ts |
Adds snapshot documents; count conflicts with the PR description (nit, 3 votes). |
test/problem.test.ts |
Covers snapshot input errors. |
test/library/public-surface.test.ts |
Pins exported snapshot APIs and result types. |
test/flows/snapshot.test.ts |
Tests snapshot execution, failures, and redaction. |
test/flows/library.test.ts |
Covers library snapshot helpers and YAML rendering. |
test/flows/inputs.test.ts |
Tests snapshot input validation. |
test/e2e/schema.ts |
Defines snapshot scenario expectations. |
test/e2e/scenarios/snapshot-unsupported-and-denied.yml |
Covers unsupported and denied reads. |
test/e2e/scenarios/snapshot-dir-multi-repo.yml |
Covers directory output for multiple repositories. |
test/e2e/scenarios/snapshot-denied-fails.yml |
Covers denial failure behavior. |
test/e2e/runner.ts |
Runs snapshot files and round-trip checks. |
test/e2e/runner.test.ts |
Tests e2e runner helpers. |
test/e2e/run.ts |
Selects snapshot scenarios. |
test/docs/readme.test.ts |
Checks schema hint and README consistency. |
test/docs/guides.test.ts |
Checks guide registration and consistency. |
test/action/run.test.ts |
Tests action snapshot dispatch. |
test/action/action-yml.test.ts |
Validates snapshot action metadata. |
src/sections/workflows/scenarios/workflows-snapshot-roundtrip.yml |
Adds workflows round-trip coverage. |
src/sections/webhooks/scenarios/webhooks-snapshot-roundtrip.yml |
Adds webhooks round-trip coverage. |
src/sections/secret_scanning_custom_patterns/scenarios/secret_scanning_custom_patterns-snapshot-roundtrip.yml |
Adds secret-pattern round-trip coverage. |
src/sections/pages/scenarios/pages-snapshot-roundtrip.yml |
Adds Pages round-trip coverage. |
src/sections/milestones/scenarios/milestones-snapshot-roundtrip.yml |
Adds milestones round-trip coverage. |
src/sections/labels/scenarios/labels-snapshot-roundtrip.yml |
Adds labels round-trip coverage. |
src/sections/interaction_limits/scenarios/interaction_limits-snapshot-roundtrip.yml |
Adds interaction-limits round-trip coverage. |
src/sections/deploy_keys/scenarios/deploy_keys-snapshot-roundtrip.yml |
Adds deploy-keys round-trip coverage. |
src/sections/dependabot_secrets/scenarios/dependabot_secrets-snapshot-roundtrip.yml |
Adds Dependabot-secrets round-trip coverage. |
src/sections/custom_properties/scenarios/custom_properties-snapshot-roundtrip.yml |
Adds custom-properties round-trip coverage. |
src/sections/codespaces_secrets/scenarios/codespaces_secrets-snapshot-roundtrip.yml |
Adds Codespaces-secrets round-trip coverage. |
src/sections/code_scanning_default_setup/scenarios/code_scanning_default_setup-snapshot-roundtrip.yml |
Adds code-scanning round-trip coverage. |
src/sections/code_quality_setup/scenarios/code_quality_setup-snapshot-roundtrip.yml |
Adds code-quality round-trip coverage. |
src/sections/autolinks/scenarios/autolinks-snapshot-roundtrip.yml |
Adds autolinks round-trip coverage. |
src/sections/agents_variables/scenarios/agents_variables-snapshot-roundtrip.yml |
Adds agent-variable round-trip coverage. |
src/sections/agents_secrets/scenarios/agents_secrets-snapshot-roundtrip.yml |
Adds agent-secret round-trip coverage. |
src/sections/actions_variables/scenarios/actions_variables-snapshot-roundtrip.yml |
Adds Actions-variable round-trip coverage. |
src/sections/actions_secrets/scenarios/actions_secrets-snapshot-roundtrip.yml |
Adds Actions-secret round-trip coverage. |
src/problem.ts |
Adds snapshot validation and failure messages. |
src/index.ts |
Exports snapshot APIs, configs, results, and rendering. |
src/flows/summary.ts |
Reports snapshot statuses, notes, and outputs. |
src/flows/snapshot.ts |
Orchestrates snapshots; unsupported-only selections can return snapshot (moderate, 1 vote), and direct writes can destroy existing files on failure (critical, 1 vote). |
src/flows/single.ts |
Integrates single-repository flow behavior. |
src/flows/multi.ts |
Shares multi-repository target resolution. |
src/flows/library.ts |
Provides snapshot library helpers. |
src/flows/inputs.ts |
Parses snapshot inputs and rejects invalid combinations. |
src/action/run.ts |
Dispatches snapshot mode. |
src/action/io.ts |
Documents snapshot action inputs and outputs. |
release-please-config.json |
Registers snapshot files for release metadata. |
README.md |
Adds snapshot quick-start and schema guidance. |
docs/upgrading/README.md |
Adds version guidance; feature is labeled v3 instead of v2 (nit, 1 vote). |
docs/reference/inputs.md |
Documents snapshot inputs. |
docs/README.md |
Registers the snapshot guide. |
docs/operate/snapshot.md |
Documents snapshot usage; unsupported-only results and ambiguous 404 behavior need qualification (two nits, 1 vote each). |
docs/operate/layering.md |
Updates rejected-input documentation. |
action.yml |
Adds snapshot inputs and outputs. |
Review details
Suppressed comments (4)
docs/operate/snapshot.md:180
result: snapshotis also returned when a selected section is unsupported, so this row overstates what was read back. Withsections: check_suite_preferences, the engine writes an empty document, records anunsupportedoutcome, and exits 0 withsnapshot, rather than reading every selected section. Mention unsupported sections in this meaning or classify that result as partial.
| `snapshot` | 0 | Every target read fully back and its file was written |
docs/operate/snapshot.md:171
on-missing-permission: faildoes not always fail a denied section, so this row promises behavior the flow cannot provide. For example, when the Pages GET returns the fine-grained token's 404,pagesSection.snapshottreats it as an absent site; the run writes a file withoutpagesand returnssnapshot. Qualify this row with the ambiguous-404 exception.
| `on-missing-permission` | `fail` (default) fails the run on a section the token cannot read, and no file is written for that target; `warn` skips the section with a warning and writes the rest |
docs/upgrading/README.md:32
- Snapshot mode is introduced on the moving
@v2release line, but this row saysSince v3. The README and the schema URL both pin the feature tov2, so the upgrading guide places the feature in the wrong major. Change theSincevalue tov2.
| v3 | `mode: snapshot` ([guide](../operate/snapshot.md)) | `result` may read `snapshot` in that mode, beside `partial` and `failed`; a step that treats every unknown `result` as a failure should list it |
src/flows/snapshot.ts:210
- Selecting only
check_suite_preferencesreturnsresult: snapshotand exit 0 even though its outcome isunsupportedand no live value enters the file. The result contract sayssnapshotmeans every target was read fully back, so consumers cannot distinguish a complete snapshot from one made entirely of omissions. Count unsupported outcomes aspartial, or revise the result contract and its documentation to exclude them.
channel: TargetChannel;
timestamp: string;
}): Promise<SnapshotTargetResult> {
const { api, repo, cfg, path, channel } = ctx;
- Files reviewed: 53/53 changed files
- Comments generated: 2
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
A critical multi-repository snapshot bug and a moderate incomplete-result bug remain, alongside documentation updates.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
docs/upgrading/README.md:32
- This new upgrade row says snapshot mode was added in v3, but the feature is documented and pinned throughout this release as v2 (
README.md:12,docs/operate/snapshot.md:25, andsrc/flows/snapshot.ts:68). A v2 user following the upgrade guide will be told to expect the feature only in a nonexistent later major.
| v3 | `mode: snapshot` ([guide](../operate/snapshot.md)) | `result` may read `snapshot` in that mode, beside `partial` and `failed`; a step that treats every unknown `result` as a failure should list it |
src/action/io.ts:19
- The new output contract is not reflected in the reference outputs table. A
mode: snapshotrun withsnapshot-dir: snapshotsemitsrepos-result, butdocs/reference/inputs.mdstill says this output is multi-repo-only, so users following the reference will not know where the per-target results are available. Update the generated reference entry to include the snapshot-dir form.
"Multi-repo mode (and the snapshot-dir form of mode: snapshot) only: JSON map of owner/name " +
"to {result, source, skippedSections}. A redacted private target is keyed by its " +
'"private repository #N" placeholder instead of its slug. Empty in single-repo mode.',
src/flows/snapshot.ts:221
- When
sectionscontains only an unsupported section,snapshotRepositoryreturnssnapshotand this flow preserves that result, even though the file omits the selected live setting. For example,sections: check_suite_preferenceswrites an incomplete document and publishesresult: snapshot, so callers can mistake it for a complete snapshot. Propagatepartialfor unsupported outcomes (including the library result), rather than treating this notice as a full snapshot.
const unsupported = result.outcomes.filter((o) => o.status === "unsupported").map((o) => o.key);
if (unsupported.length > 0) {
channel.io.annotate(
"notice",
`not snapshotted: ${unsupported.join(", ")} - snapshot does not read these sections back, so the file omits them (the header says why); declare them by hand if they should be managed`,
);
src/index.ts:121
- The newly exported snapshot library API is missing from
docs/reference/library.md. A consumer can importsnapshotRepository,snapshotRepositories,renderSnapshotYaml, andSnapshotConfigfrom this entry, but the library reference documents no snapshot group or example, so the documented API is incomplete. Add the snapshot operations and their result/YAML semantics to the library reference.
export {
concludeSnapshot,
type FinishedSnapshot,
runSnapshot,
SNAPSHOT_RESULTS,
SNAPSHOT_SCHEMA_URL,
type SnapshotConfig,
type SnapshotRunResult,
type SnapshotTargetView,
- Files reviewed: 55/55 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
d2d1ba4 to
cc6e5f4
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Atomic write handling, missing CLI coverage, and documented count/version/result inconsistencies remain unresolved.
Review details
Suppressed comments (5)
docs/operate/snapshot.md:180
- With
sections: repository,labels,repositoryis unsupported, so this flow writes onlylabelsand still returnsresult: snapshot(the engine test covers this case). That meanssnapshotdoes not guarantee that every selected section was read back; the result description is false for this input. Describe the result in terms of supported sections and say unsupported ones are omitted and explained in the header.
| `snapshot` | 0 | Every target read fully back and its file was written |
docs/upgrading/README.md:32
- The upgrade guide places
mode: snapshotin v3 even though this PR adds it to the@v2action shown in the PR and pins the snapshot schema to v2. A user runninguses: Vivswan/github-settings-as-code@v2can therefore incorrectly conclude that snapshot is unavailable on their current major. Change theSincevalue tov2.
| v3 | `mode: snapshot` ([guide](../operate/snapshot.md)) | `result` may read `snapshot` in that mode, beside `partial` and `failed`; a step that treats every unknown `result` as a failure should list it |
src/flows/snapshot.ts:235
- If an existing snapshot file is present and this write fails after opening it (for example because the filesystem runs out of space),
writeFileSynctruncates the old file before returning the error, leaving a partial or empty snapshot even though the run reports that no file was written. Write to a temporary sibling and rename it only after the complete document has been written so a failed target preserves its previous snapshot.
mkdirSync(dirname(path), { recursive: true });
writeFileSync(
path,
renderSnapshotYaml(result, { schemaUrl: SNAPSHOT_SCHEMA_URL, timestamp: ctx.timestamp }),
);
test/cli/inputs.test.ts:347
- The CLI coverage table still has no
snapshotcase, so its "every RunConfig arm" assertion cannot exercise this newly added arm.cases()contains nomode: snapshotentry and line 297 still expects onlymerge,multi, andsingle;gsac snapshot --token ghp --snapshot-file snap.ymlcould therefore regress without this test failing. Add a snapshot case and includesnapshotin the expected kinds.
test/schema-corpus.test.ts:69 - The PR description says the schema corpus count moves to 297, but this change pins the test to 299. Please reconcile the description with the final scenario corpus so the documented verification result is not stale.
- Files reviewed: 60/60 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
cc6e5f4 to
0c9709a
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The critical snapshot-file permission regression must be fixed, and the upgrade guide’s v2/v3 mismatch should be corrected.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
docs/upgrading/README.md:32
- This labels
mode: snapshotas a v3 addition even though this release and the documented moving major are v2 (docs/upgrading/README.md:13and the examples use@v2). Readers following the upgrade guide will look for a nonexistent v3 upgrade. Change theSincevalue tov2.
| v3 | `mode: snapshot` ([guide](../operate/snapshot.md)) | `result` may read `snapshot` in that mode, beside `partial` and `failed`; a step that treats every unknown `result` as a failure should list it |
- Files reviewed: 61/61 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
750f870 to
d59ad10
Compare
There was a problem hiding this comment.
🟡 Changes recommended
A moderate corpus-count mismatch (3 votes) and three documentation nits (1 vote each) remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
docs/reference/inputs.md:44
- The new snapshot output description leaves the next
repos-resultrow saying it is only for multi-repo mode. Amode: snapshotrun withsnapshot-diralso emits this JSON output, so the reference page is wrong for that concrete form; update the row to include the snapshot-dir form.
- `result`: <!-- BEGIN GENERATED: outputs-list (bun run build:docs; derived from REPO_RESULTS in src/engine/orchestrate.ts) -->`applied` / `partial` / `clean` / `drift` / `failed`; worst-of across targets in multi-repo mode, where `skipped` can also appear; `merged` in mode: merge; `snapshot` / `partial` / `failed` in mode: snapshot<!-- END GENERATED: outputs-list -->. The [snapshot guide](../operate/snapshot.md) has the snapshot values' exit-code table.
docs/start/cli.md:36
- The CLI overview still omits snapshot mode. The page now lists
snapshotas a command, but its opening sentence says the package runs only checks, applies, and merges, so users reading the overview are told an incomplete set of supported commands. Include snapshot in that sentence.
| `snapshot` | Write a repository's live settings as a settings file, or one file per multi-repo target | yes |
docs/upgrading/README.md:32
- The release guide puts snapshot mode in v3 even though this PR advertises and implements it on the moving
@v2line. The guide's own rule says a minor can add a mode, while the examples, schema URL, and package version are all v2; a user following this row would wait for a major that is not needed. Mark the addition as v2 (or consistently move every snapshot reference to v3).
| v3 | `mode: snapshot` ([guide](../operate/snapshot.md)) | `result` may read `snapshot` in that mode, beside `partial` and `failed`; a step that treats every unknown `result` as a failure should list it |
- Files reviewed: 61/61 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
d59ad10 to
d5a9e5d
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Correct the all-targets-failed summary and record snapshot mode under v2 in the upgrade guide.
Review details
Suppressed comments (2)
docs/upgrading/README.md:32
- This row says
mode: snapshotwas added in v3, but this PR adds it to the current v2 release line (@v2, package version 2.0.0, and the snapshot schema URL all use v2). A user following the upgrade guide can therefore look for the feature in the wrong major. Record the addition under v2.
| v3 | `mode: snapshot` ([guide](../operate/snapshot.md)) | `result` may read `snapshot` in that mode, beside `partial` and `failed`; a step that treats every unknown `result` as a failure should list it |
src/flows/summary.ts:122
- The directory summary claims snapshots were written even when every target failed. For example,
snapshot-dir: outwith one target denied underon-missing-permission: failcreates no file, but this line still saysSnapshots written under out.. Report a neutral destination or make this text conditional on at least one view having afile.
`Snapshots written under ${markdownCell(snapshotDir)}.`,
- Files reviewed: 61/61 changed files
- Comments generated: 0 new
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
…-repo directory mode: snapshot reads the target repositories' live settings back through the section snapshot ports and writes each as a settings document: one repository to snapshot-file, or every multi-repo target (resolved exactly as apply resolves them, discovery filters included) to <snapshot-dir>/<owner>/<name>.yml, the repos-dir layout. The document reaches only the file; annotations, the summary, and the outputs carry section keys, statuses, and notes through the same redaction channel check mode uses, so a private target's file lands on disk while the public surfaces know it as its placeholder. The result output reads snapshot, partial, or failed. The flow reuses the multi-repo target resolution, extracted from runMulti as resolveTargets, and the single-repo channel opener. The library gains snapshotRepository and snapshotRepositories, returning the engine result plus the rendered file. The inputs gain snapshot-file and snapshot-dir (exactly one required in snapshot mode, both rejected elsewhere) and an allowlist that rejects every apply-, check-, or merge-time input, mirroring the merge. The e2e harness gains mode: snapshot, the snapshot pins, and a round-trip re-run that checks the written file clean against the same seeded state; every section with a snapshot handler has one round-trip scenario, plus the unsupported-and-denied, denied-fails, and dir-form multi-repo scenarios. The snapshot guide documents the mode, the $NAME placeholders, the round trip and its exceptions, and the directory form. The schema hint a snapshot writes names the moving major tag like the README, so release-please-config.json lists src/flows/snapshot.ts; the layering guide's rejected-inputs row and the schema-corpus pin follow the new inputs and scenarios.
…t line cap The fleet's check-file-size gate now fails a line over 256 characters. Every literal this change introduced is split at clause boundaries; the strings are byte-identical (the exact-match tests pin them). The over-long lines the gate flags elsewhere predate this change.
…ario set The check run that proves a written snapshot round-trips built its inputs from an allowlist of three, so a target-selecting input added to the scenario schema later would have been dropped and the check would have run against the wrong repository. The inputs are now derived by exclusion: everything carries over except the mode and the two snapshot destinations check mode rejects. A unit test pins the derivation.
…as a pattern built from the schema URL The flow and library tests built a RegExp from SNAPSHOT_SCHEMA_URL (dots escaped by hand), which CodeQL reads as a hostname pattern that can match more hosts than intended. The header is now split into lines and the schema pin compared whole; a look-alike host with one dot replaced is pinned as rejected. The unwritable-path assertion drops its path-derived RegExp the same way.
…on, generated outputs list, artifact note The snapshot rejection's fix names the apply, check, or merge step an input belongs to (merged-file and layering are refused by apply and check too). The action.yml result-output test asserts the enumerated values as an exact set, so a value dropped from the enumeration while still named in prose fails. The outputs-list generated region on the inputs reference now carries the snapshot results through renderOutputsList, replacing the hand-kept list beside it. The snapshot guide's fleet example says the uploaded directory holds redacted targets' full documents in the clear and points at the redaction guide's artifact-visibility note.
…shared parser Main's CLI (#152) is total over the parsed modes: its dispatcher and per-mode flag split are typed over RunConfig and MODES, so the snapshot arm needs a home. The dispatcher concludes a snapshot config the way the action does, inputsForMode derives each mode's flags from the exported per-mode input lists (MERGE_ONLY_INPUTS and SNAPSHOT_ONLY_INPUTS now exported), and the program gains the snapshot subcommand with its guide row.
… place A write that failed partway (disk full, an interrupted run) truncated a previously valid snapshot at the destination while the run reported failed, so the documented backup was lost. The document now goes to a sibling <path>.tmp and is renamed over the destination, which is atomic for a regular file; the staging file is removed when the write fails, and a cleanup that fails itself does not replace the write's error. Both destination forms write through the one helper. Tests fail the staging write and read the previous snapshot back intact, and fail the rename under the dir form to see the staging file gone and the other target written.
…and pin the mode subcommands A stale symlink at <path>.tmp (out/s.yml.tmp -> s.yml) was written through and then installed as the destination, so the run printed "snapshot written" while the destination became a self-pointing link and both the old and the new content were gone. The staging path is unlinked before the write (a directory there still fails loudly) and created exclusively, and a test runs the link case. The Windows note on the helper now says what Node's rename is there, a single replace call, rather than claiming atomicity. Beside it: the staging tests assert the full outputs record and lines array like the rest of the file; the CLI's mode subcommands gain the lockstep type that fails when a Mode has no subcommand; the snapshot fleet-input set in the CLI test spreads FILTER_INPUTS instead of hand-listing them; and the README's snapshot pointer moves from step 3's check-mode bullet to step 2's settings-file line, where starting from a snapshot belongs.
d5a9e5d to
4f617d8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
One moderate summary defect and two nit fixes remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
src/flows/inputs.ts:103
- The mode description falsely says snapshot rejects every input used by apply or check. For example,
sections: labels,on-missing-permission: warn, andprivate-repos: redactare valid snapshot inputs, so the generated action help tells users to remove inputs that the parser accepts. Say that inputs specific to the other modes are rejected instead.
"the file, and every input that controls an apply, a check, or a merge is rejected). check " +
- Files reviewed: 61/61 changed files
- Comments generated: 2
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
…d of claiming all of them With every target failed (one target, its read denied under the fail policy), the step summary opened with "Snapshots written under <dir>." while the table showed no file and the result read failed. The heading now follows the count of views that carry a file: no snapshot written, N of M written, or the unchanged line when every target wrote one. Tests pin the all-failed and the mixed summary.
There was a problem hiding this comment.
🟡 Changes recommended
Resolve the critical case-insensitive path collision and moderate newline-separated section-filtering issues.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
test/e2e/run.ts:41
- A newline-separated
sectionsallowlist is treated as one key, so section-filtered e2e runs can skip valid scenarios. For example, withinputs.sections: "labels\nwebhooks"and--sections webhooks, this loop storeslabels\nwebhooksinstead ofwebhooks, and the scenario is omitted. Split on the same comma/newline separators as the input parser.
- Files reviewed: 61/61 changed files
- Comments generated: 1
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
mode: snapshotreads a repository's live settings back through the section snapshot ports and writes them as a settings document. Nothing is written to GitHub.snapshot-filefor one repository,snapshot-dirfor the multi-repo targets (repos,repos-dir,repos: "*"with the same filters as apply), written as<snapshot-dir>/<owner>/<name>.yml, therepos-dirlayout.private repository #N.snapshotRepository(client, repo, opts)andsnapshotRepositories(client, targets, opts)return the engine result plus the renderedyaml;runSnapshot,SnapshotConfig,SNAPSHOT_RESULTS,renderSnapshotYaml, and the result types are exported.clean; plus the unsupported-and-denied, denied-fails, and dir-form multi-repo scenarios.docs/operate/snapshot.md(the mode, the$NAMEplaceholders, backup-then-check, the round trip and its exceptions, the dir form), registered everywhere a guide is listed.Technical details
Inputs in
mode: snapshotsnapshot-filerepositoryinput, default the current one) written to this path; parent directories created. Rejected withreposorrepos-dir, and when it is.github/settings.ymlsnapshot-dir<owner>/<name>.ymlperrepos/repos-dirtarget under this directory. Rejected withrepository, without targets, and when it is, contains, or sits inside therepos-dirsections,on-missing-permission,private-repos,api-version,token, the discovery filterssettings-file,merged-file,layering,required-sections,defaults-file,private-report,report-public-keyMERGE_REJECTED_INPUTSExactly one of
snapshot-fileandsnapshot-diris required in snapshot mode; both are rejected in apply, check, and merge.Result semantics
resultsnapshotpartialwarnor failed on its own; the file omits it and the header says why;skipped-sectionslists the skipped keysfailedfail, unwritable path, a section value its own schema rejects); no file for that targetThe dir form publishes
repos-resultkeyed by display name (the placeholder for redacted targets), and the worst result across targets decides.Notes
resolveTargets, extracted fromrunMultiinsrc/flows/multi.tswithout behavior change (the defaults-file read moved ahead of it; it emitted nothing about targets before either).release-please-config.jsonnow listssrc/flows/snapshot.ts;test/docs/readme.test.tspins the constant to the README's hint.merged-fileguard: thesnapshot-dir/repos-dirandsnapshot-file/.github/settings.ymlcollision checks compare resolved paths, not canonical ones, so a symlink alias of the same directory (/tmpvs/private/tmp) is not caught.release-please-config.json(the marker file list),docs/operate/layering.md(its rejected-inputs row now names the two snapshot inputs),test/schema-corpus.test.ts(the pinned snapshot documents join the corpus; the count moves to 305: main's 262 plus this PR's 43 snapshot documents), andarchitecture.yml(flowsmay importtypesfor the result lockstep).feat/snapshot-sections.Reviewer note (Copilot): report a finding only with a concrete input and the wrong output it produces.