Skip to content

feat(snapshot): mode snapshot writes live settings to a file or a per-repo directory - #146

Merged
Vivswan merged 9 commits into
mainfrom
feat/snapshot-mode
Sep 12, 2026
Merged

Vivswan merged 9 commits into
mainfrom
feat/snapshot-mode

Conversation

@Vivswan

@Vivswan Vivswan commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner
- uses: Vivswan/github-settings-as-code@v2
  with:
    token: ${{ secrets.ADMIN_TOKEN }}
    mode: snapshot
    snapshot-file: settings.snapshot.yml
  • mode: snapshot reads a repository's live settings back through the section snapshot ports and writes them as a settings document. Nothing is written to GitHub.
  • Two forms: snapshot-file for one repository, snapshot-dir for the multi-repo targets (repos, repos-dir, repos: "*" with the same filters as apply), written as <snapshot-dir>/<owner>/<name>.yml, the repos-dir layout.
  • The document reaches only the file. The log, the summary, and the outputs carry section keys, statuses, and the notes a check run prints, routed through the same redaction channel check mode uses; a redacted private target's file is written while the public surfaces know it only as private repository #N.
  • Library: snapshotRepository(client, repo, opts) and snapshotRepositories(client, targets, opts) return the engine result plus the rendered yaml; runSnapshot, SnapshotConfig, SNAPSHOT_RESULTS, renderSnapshotYaml, and the result types are exported.
  • e2e: one round-trip scenario per section with a snapshot handler (18), each re-running the written file as a check against the same seeded state and expecting clean; plus the unsupported-and-denied, denied-fails, and dir-form multi-repo scenarios.
  • Docs: docs/operate/snapshot.md (the mode, the $NAME placeholders, backup-then-check, the round trip and its exceptions, the dir form), registered everywhere a guide is listed.
Technical details

Inputs in mode: snapshot

Input Meaning
snapshot-file One repository (the repository input, default the current one) written to this path; parent directories created. Rejected with repos or repos-dir, and when it is .github/settings.yml
snapshot-dir One <owner>/<name>.yml per repos / repos-dir target under this directory. Rejected with repository, without targets, and when it is, contains, or sits inside the repos-dir
sections, on-missing-permission, private-repos, api-version, token, the discovery filters As in apply and check
settings-file, merged-file, layering, required-sections, defaults-file, private-report, report-public-key Rejected when set to a non-default value, derived from the declarations like MERGE_REJECTED_INPUTS

Exactly one of snapshot-file and snapshot-dir is required in snapshot mode; both are rejected in apply, check, and merge.

Result semantics

result Exit When
snapshot 0 Every target read fully back and its file was written
partial 0 A section was skipped under warn or failed on its own; the file omits it and the header says why; skipped-sections lists the skipped keys
failed 1 A target failed (denial under fail, unwritable path, a section value its own schema rejects); no file for that target

The dir form publishes repos-result keyed by display name (the placeholder for redacted targets), and the worst result across targets decides.

Notes

  • Target resolution for the dir form is resolveTargets, extracted from runMulti in src/flows/multi.ts without behavior change (the defaults-file read moved ahead of it; it emitted nothing about targets before either).
  • The schema hint a snapshot writes is the moving-major URL the README uses, with a release-please marker, so release-please-config.json now lists src/flows/snapshot.ts; test/docs/readme.test.ts pins the constant to the README's hint.
  • Known limit, shared with the merged-file guard: the snapshot-dir / repos-dir and snapshot-file / .github/settings.yml collision checks compare resolved paths, not canonical ones, so a symlink alias of the same directory (/tmp vs /private/tmp) is not caught.
  • Files outside the flow itself this PR had to touch: 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), and architecture.yml (flows may import types for the result lockstep).
  • The diff includes PR refactor(arch): move the run flows to src/flows and add the public entry src/index.ts #138's files (the flows layer) until that PR lands; the base is feat/snapshot-sections.

Reviewer note (Copilot): report a finding only with a concrete input and the wrong output it produces.

@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

File size check

1 over a hard cap (fails), 5 warning(s).

File Size Tier Cap
test/scripts/check-compat-markers.test.ts:55 336 chars hard 256
.github/scripts/release-pipeline.ts:333 168 chars warn 150
.github/workflows/post-green.yml:78 19 comment lines warn 10
src/flows/snapshot.ts:129 11 comment lines warn 10
test/docs/guides.test.ts:468 155 chars warn 150
test/e2e/schema.ts:132 11 comment lines warn 10

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

5 managed file(s) skipped; repo-platform owns them.

Comment thread src/flows/snapshot.ts Fixed
@Vivswan
Vivswan force-pushed the feat/snapshot-sections branch from 0ac317b to 2a92e5b Compare September 12, 2026 01:49
@Vivswan
Vivswan force-pushed the feat/snapshot-mode branch 2 times, most recently from 7ccf945 to 59243ec Compare September 12, 2026 02:46
@Vivswan Vivswan closed this Sep 12, 2026
@Vivswan Vivswan reopened this Sep 12, 2026
@Vivswan
Vivswan force-pushed the feat/snapshot-sections branch from d8284c2 to c2332ba Compare September 12, 2026 03:37
@Vivswan
Vivswan force-pushed the feat/snapshot-sections branch 2 times, most recently from fd5c230 to da2af61 Compare September 12, 2026 05:43
Base automatically changed from feat/snapshot-sections to main September 12, 2026 06:03
Copilot AI balanced review requested due to automatic review settings September 12, 2026 06:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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: snapshot is also returned when a selected section is unsupported, so this row overstates what was read back. With sections: check_suite_preferences, the engine writes an empty document, records an unsupported outcome, and exits 0 with snapshot, 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: fail does 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.snapshot treats it as an absent site; the run writes a file without pages and returns snapshot. 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 @v2 release line, but this row says Since v3. The README and the schema URL both pin the feature to v2, so the upgrading guide places the feature in the wrong major. Change the Since value to 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/snapshot.ts:210

  • Selecting only check_suite_preferences returns result: snapshot and exit 0 even though its outcome is unsupported and no live value enters the file. The result contract says snapshot means every target was read fully back, so consumers cannot distinguish a complete snapshot from one made entirely of omissions. Count unsupported outcomes as partial, 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.

Comment thread src/flows/snapshot.ts Outdated
Comment thread test/schema-corpus.test.ts Outdated
Copilot AI review requested due to automatic review settings September 12, 2026 07:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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, and src/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: snapshot run with snapshot-dir: snapshots emits repos-result, but docs/reference/inputs.md still 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 sections contains only an unsupported section, snapshotRepository returns snapshot and this flow preserves that result, even though the file omits the selected live setting. For example, sections: check_suite_preferences writes an incomplete document and publishes result: snapshot, so callers can mistake it for a complete snapshot. Propagate partial for 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 import snapshotRepository, snapshotRepositories, renderSnapshotYaml, and SnapshotConfig from 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.

Comment thread src/flows/snapshot.ts
Copilot AI review requested due to automatic review settings September 12, 2026 07:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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, repository is unsupported, so this flow writes only labels and still returns result: snapshot (the engine test covers this case). That means snapshot does 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: snapshot in v3 even though this PR adds it to the @v2 action shown in the PR and pins the snapshot schema to v2. A user running uses: Vivswan/github-settings-as-code@v2 can therefore incorrectly conclude that snapshot is unavailable on their current major. Change the Since value to 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/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), writeFileSync truncates 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 snapshot case, so its "every RunConfig arm" assertion cannot exercise this newly added arm. cases() contains no mode: snapshot entry and line 297 still expects only merge, multi, and single; gsac snapshot --token ghp --snapshot-file snap.yml could therefore regress without this test failing. Add a snapshot case and include snapshot in 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.

Copilot AI review requested due to automatic review settings September 12, 2026 07:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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: snapshot as a v3 addition even though this release and the documented moving major are v2 (docs/upgrading/README.md:13 and the examples use @v2). Readers following the upgrade guide will look for a nonexistent v3 upgrade. Change the Since value to 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 |
  • 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.

Comment thread src/flows/snapshot.ts
Copilot AI review requested due to automatic review settings September 12, 2026 09:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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-result row saying it is only for multi-repo mode. A mode: snapshot run with snapshot-dir also 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 snapshot as 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 @v2 line. 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.

Comment thread test/schema-corpus.test.ts Outdated
Copilot AI review requested due to automatic review settings September 12, 2026 10:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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: snapshot was 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: out with one target denied under on-missing-permission: fail creates no file, but this line still says Snapshots written under out.. Report a neutral destination or make this text conditional on at least one view having a file.
    `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.
Copilot AI review requested due to automatic review settings September 12, 2026 11:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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, and private-repos: redact are 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.

Comment thread src/flows/summary.ts Outdated
Comment thread docs/upgrading/README.md
…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.
Copilot AI review requested due to automatic review settings September 12, 2026 11:46
@Vivswan
Vivswan marked this pull request as ready for review September 12, 2026 11:56
@Vivswan
Vivswan merged commit 9233a14 into main Sep 12, 2026
39 checks passed
@Vivswan
Vivswan deleted the feat/snapshot-mode branch September 12, 2026 11:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 sections allowlist is treated as one key, so section-filtered e2e runs can skip valid scenarios. For example, with inputs.sections: "labels\nwebhooks" and --sections webhooks, this loop stores labels\nwebhooks instead of webhooks, 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.

Comment thread src/flows/snapshot.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants