Repository navigation
import command
Issue #11 adds a read-only import command and an --into refresh flow. The command brings an existing repository into a schemaVersion: 1 configuration; refresh updates only the configuration's declared managed surface while preserving local intent and source formatting.
| Type | Issue |
|---|---|
| Parent | Issue #11 |
| State fetching and reverse mapping | Issue #18 |
| Format-preserving refresh | Issue #19 |
| CLI and convergence | Issue #20 |
Tri-state repository.description
|
Issue #22 |
Manually transcribing repository settings is error-prone, while a refresh must not silently take ownership of configuration sections the user did not declare. The existing schema already represents repository settings, Actions, security, labels, milestones, rulesets, and agent variables, but the reverse mapping and the safe file update path are missing.
Reviewing the specification against the code base found three things the original plan assumed and the current implementation cannot provide: eight repository reads that do not exist, a YAML editing guarantee the current parser cannot make, and a set of planner comparisons that report an update without ever reading the live value. This page records the corrected contract.
import cannot be written against the current GitHub client surface.
| Read | Current state |
|---|---|
| Repository environments | Create/update only. The response is a wrapped {total_count, environments} object. |
| Actions selected-actions allowlist | Write-only. Required for actions.selectedActions. |
| Actions default workflow permissions | Write-only. Required for defaultWorkflowPermissions and canApprovePullRequestWorkflows. |
| Automated security fixes | Write-only. Required for security.dependabotSecurityUpdates. |
| Private vulnerability reporting | Write-only. |
| CodeQL default setup | Write-only. |
security_and_analysis on the repository |
Not decoded today. Required for security.secretScanning and security.pushProtection. |
| Ruleset detail | List only. The rulesets list endpoint returns identity, target, and enforcement — not rules, conditions, or bypass actors — so each ruleset needs an individual fetch. This is also why the planner can currently compare rulesets by name alone. |
The pagination helper decodes a bare JSON array, so it cannot page a wrapped list. Variables, secrets, and custom repository roles therefore do not page at all today and stop at GitHub's default page size. A wrapped-list variant is part of the work, and the page ceiling has to be reconsidered.
The importer uses a small read-only client interface, complete pagination, and a reverse mapping into the existing configuration type. Server-generated ruleset IDs and timestamps are omitted.
Bypass actors are reversed by actor type. Integration becomes app, actor ID 1 becomes orgAdmin, and RepositoryRole becomes the symbolic role for the fixed IDs 2, 4, and 5 (maintain, write, admin) and customRole: id:<number> for any other ID, because both share that actor type. Teams, apps, and custom roles use the resolved id:<number> selector; role never does. An empty bypass actor list is written explicitly, since omitting it clears the actors on the next apply. Rule parameters are emitted with sorted keys and integral numbers so output is byte-stable.
Secret values and source environment names are unavailable from GitHub; secret names are reported as YAML comments or JSON diagnostics. Local intent is never inferred: seed sources and the seed overwrite list, commands, agent file paths, agent secrets, repository.create, and repository.allowVisibilityChange are decisions no repository state implies, and a refresh preserves all of them.
--into refreshes only already-declared paths and top-level sections. Within a declared collection, entries that exist on GitHub are updated and entries missing from the file are added in name order.
A declared entry that does not exist on GitHub is kept and reported as not yet applied — it is never deleted. A configuration entry is a desired state that may simply not have been applied yet, and deleting it would turn drift into acceptance without the user deciding to accept it. This also keeps the refresh consistent with the additive rule that led this extension to reject authoritative reconciliation in the first place. Undeclared sections stay undeclared, an empty declared collection stays declared, and retained entry order with attached comments is preserved.
A security feature whose live state cannot be read keeps its existing declaration and is reported, rather than being written as false.
YAML refresh parses the file into an AST whose tokens carry a byte offset, and replaces only the spans that changed. Comments, key order, indentation, quote style, and line endings outside those spans survive untouched.
The extension's current YAML parser cannot provide this. It exposes only a node's start line and column — no span end, no byte offset — and it normalizes CRLF to LF, which makes preserving the original line endings impossible on that path. The rewrite path therefore uses a second YAML parser, while strict loading and validation stay on the existing one. The two must agree on what a valid document is; anything loading rejects, the rewrite path must not accept.
JSON is reformatted as JSON and keeps its input format. Differences are shown before confirmation, and a no-op, failed validation, refusal, concurrent edit, or save failure leaves the original unchanged. Pre-save validation deliberately excludes the filesystem checks that seed paths normally receive, so a refresh does not fail because a seed directory is absent on the machine running it.
"An empty plan" means no create or update changes and no blocked changes, excluding two classes that cannot converge by construction: manual entries for seed, commands, and agent file paths, and secret updates, whose values GitHub never returns. Import output contains no secrets, so the exclusion only affects refresh verification.
Reaching that state also requires work on the planner. Actions workflow permissions, selected actions, environments, every security feature, ruleset contents, and ruleset bypass actors are currently reported as an update without reading the live value, and the matching apply paths write unconditionally for the same reason. repository.description is compared without distinguishing an absent key from an empty one, so a document that does not manage the description reports a change forever; it becomes tri-state, matching homepage.
repository.visibility is required by the schema, so import always emits it, while allowVisibilityChange is local intent import cannot supply. Applying imported output to a different repository can therefore report a blocked visibility change; the round-trip criterion applies to the repository the document was imported from.
- Add the importer, the missing reads, wrapped-list pagination, reverse mapping, diagnostics, and ruleset normalization (Issue #18).
- Add the source-preserving YAML and JSON refresh engine with managed-surface detection and local-intent preservation (Issue #19).
- Wire CLI parsing and output, and complete the planner and apply comparisons (Issue #20).
- Make
repository.descriptiontri-state (Issue #22).
Test YAML and JSON output by reloading it through the configuration loader, covering all supported sections, pagination past one page and past the page ceiling, wrapped responses, open and closed milestones, every bypass actor type, secret diagnostics, and API failures. Test refresh with comments, non-alphabetical keys, both line endings, quoted and multiline values, flow syntax, collection additions and updates, declared entries absent from GitHub, and empty collections, asserting unchanged bytes outside the edited spans. Test the CLI contract: a positional repository combined with -R, flags on either side of the positional argument, a format value outside yaml|json, non-interactive input without --yes, and the usage text. Confirm that import makes no GitHub write request and that the resulting plan is empty under the definition above.
This plan covers Issue #11 only. plan --ci, apply --force-secrets, and the exit-code plumbing that plan --ci needs are tracked separately. The decisions recorded here are also written to docs/adr.md and docs/adr_ja.md in the repository.