ITADN
swift-institute/Workspace
swift-institute/Workspace · 文件
文件最后提交记录最后更新时间
README.md

Swift Institute Workspace

The front door to the Swift Institute: the public package inventory (Institute.json), the default checkout (Selection.json) with a per-machine override that stays out of Git (Selection.local.json), machine-checked facts about that checkout (institute doctor), an isolated local development checkout for Xcode (institute sync), and local-source composition for cross-package work (institute compose / restore / verify).

CommandWhat it does
installInstall or refresh the account-local workspace command.
syncClone missing repositories and fast-forward eligible ones. Never rewrites work.
doctorReport what is measurably true about this checkout.
inventoryPrint the committed name → organization → relative-path register. Never discovers or writes.
inventory regenerateDiscover the live roster and replace Institute.json; --dry-run plans only.
dependenciesAudit direct manifest dependency origins at exact remote revisions; never writes package state.
composePoint one package's dependency at your local checkout of it, so edits are picked up.
restoreUndo a composition, returning the manifest to its declared form byte-for-byte.
verifyReport which source a dependency actually compiled from, read from resolved state.
context install|checkInstall or verify the checkout-root agent entry point and canonical skill projections.
context packetRender a bounded, read-only current-state packet for one GitHub Issue; ordinary history stays cold unless a comment URL is named explicitly.
navigation install|checkInstall or verify the pinned cclsp/SourceKit-LSP integration for this checkout.
package <action>Run SwiftPM build, test, resolution, and administration through the Swift coordinator.
package lintLint one package with the same binary, rules, and exit policy CI gates on.
lintLint the whole ecosystem. install|check manage the pinned linter and its parity with CI.
lint ledgerRender the complete residual lint census as a human report or deterministic JSON.

What Swift Institute is

Swift Institute builds a layered ecosystem of independent Swift packages. Three layers are realised:

LayerFamilyGitHub org
1Primitives — atomic, dependency-light identity and value typesswift-primitives
2Standards — models of externally defined formats, protocols, and specificationsswift-standards
3Foundations — operational capabilities composed from the lower layersswift-foundations

Dependencies flow downward; same-layer edges are permitted only when they express a genuine semantic prerequisite and the graph stays acyclic. Names above Layer 3 — components, applications — are reservations recording intent. Never read such a name as evidence that the thing exists.

Specification packages live in orgs named for the issuing authority: swift-ietf (the swift-rfc-* family), swift-iso, swift-w3c, swift-whatwg, and further authority, vendor, and jurisdiction orgs on the same pattern. The swift-*-standard family inside swift-standards models de-facto systems (Git, SwiftPM, GitHub, …) and is a different family from the authority specification packages.

Every package is one repository; there is no monorepo. This repository clones selected packages as independent checkouts materialized in the org hierarchy — one root per layer organization (swift-primitives/, swift-standards/, swift-foundations/), with packages owned by a specification-authority, vendor, or jurisdiction organization nested one level deeper under their layer root (for example swift-standards/swift-ietf/<package>) — and composes them into a single Xcode workspace. Selection.json contains only canonical owner/repository identities and decides which inventory entries participate in the default checkout. Placement and ordering derive from Institute.json alone: each selected entry's organization and layer fields decide the path, and inventory order decides synchronization and Xcode order. Tools never infer a location from a package's name or by scanning the tree, and materialized paths are regenerable state — when a repository transfers between organizations, both documents must be updated explicitly before sync can proceed.

Where facts come from

  • Inventory: Institute.json is the public roster of packages this workspace manages, intended to grow to every public, non-archived Institute package.
  • Default checkout: Selection.json is a membership list of canonical owner/repository identities. It deliberately does not repeat package metadata, paths, or ordering. It is committed policy: it decides what a fresh clone opens, for everyone.
  • Your checkout: Selection.local.json is your own delta over that policy — add and remove — and it is gitignored. It is how you change what your machine opens without editing a tracked file.
  • Checkout facts: institute doctor measures the checkout directly — identities, remotes, branches, upstreams, toolchain, and workspace references.

sync and doctor load these files and fail before repository work if the selection is missing, malformed, duplicated, or names an entry absent from the inventory. Those checks apply to the selection in effect — the committed document with your override applied — so a local file cannot buy an exemption from them, and a present-but-malformed override fails the command rather than falling back to committed policy. They never treat an invalid selection as permission to operate on the complete inventory. compose, restore, and verify resolve their explicitly named operands against the complete inventory instead; an operand does not have to remain in the default selection once it is already checked out.

Prefer running doctor over trusting any written snapshot: repository-state prose is a measurement with a timestamp, and it drifts.

institute inventory is the read-only view of the committed register. It prints each repository's name, owning organization, and inventory-derived relative materialization path; it performs no GitHub discovery and cannot write Institute.json. Roster maintenance uses the explicitly mutating institute inventory regenerate. Run it with --dry-run to learn whether the file would be replaced. Applying the regeneration requires a clean Workspace worktree and refuses before discovery otherwise; the final write is atomic and also refuses if Institute.json changes during discovery.

Audit dependency origins

workspace dependencies reads the repositories already eligible in Institute.json, fetches every root, nested, and Package@swift-* manifest from each repository's default branch, and records the exact commit examined. It enumerates direct canonical GitHub repository URLs; the report keeps repeated declaration edges separate from distinct package identities and states that transitive closure was not measured.

workspace dependencies
workspace dependencies --format json
workspace dependencies \
  --sanctioned-exception <owner/repository> \
  --sanctioned-exception <owner/repository>

The human format is a concise diagnosis. The JSON format is deterministic evidence carrying the inventory population, source references and revisions, per-manifest and per-edge provenance, ownership classifications, excluded path or registry declarations, and every unavailable, rate-limited, malformed, or otherwise unmeasured input. Sanctioned exceptions are explicit inputs from the governing policy rather than a second policy list in Workspace, and the report records them. A redirect is resolved before ownership is classified. Runtime controls drive a finding and a clean input through that same redirect, classification, report, and exit-status pipeline before the inventory is measured.

Any excluded declaration — including a path dependency, registry dependency, or malformed repository URL — makes the report incomplete and exits 2. A sanctioned exception applies only to its canonical repository identity; it cannot sanction a declaration whose identity was not measured.

The command is read-only. It does not inspect or change local package manifests, Package.resolved, the inventory, or a materialized checkout. Exit 0 means complete evidence containing only Institute-owned or supplied sanctioned-exception identities; exit 1 means a measured personal-owner or third-party identity; exit 2 means some input was not measured. Remote reads use an authenticated gh session, but require no Institute-only repository access; anything the session cannot read is reported as unavailable rather than clean.

Where open work lives

Open objectives are public GitHub issues on the relevant repositories:

gh issue list --repo swift-institute/institute-application

Get started

No Institute access is required. Everything below works from a clone of this repository alone, against public repositories, with no credentials and no internal tooling. If a step here needs anything you cannot get, that is a defect — please open an issue.

Requires the macOS, Xcode, and Swift floors declared in Institute.json, plus Git. institute doctor checks those floors on the current machine; a newer toolchain passes.

swiftly is how the Institute installs and selects Swift toolchains; install it if you do not already keep one. If you keep more than one Swift toolchain installed, TOOLCHAINS.md covers how to select one explicitly and how to determine which one actually produced a result — machine-local configuration, not committed state.

The optional navigation setup additionally requires Node 18 or newer and Bun.

Clone into a directory named swift-institute. The layout is load-bearing, not cosmetic: the materialized organization roots are placed beside this checkout, and the canonical skill roots are resolved from its grandparent. Cloning into a bare directory puts both somewhere nothing looks.

mkdir -p Institute/swift-institute && cd Institute/swift-institute
git clone https://github.com/swift-institute/institute-application.git
git clone https://github.com/swift-institute/Skills.git
cd Workspace
export PATH="$HOME/.local/bin:$PATH"
swift run --package-path Application institute install
institute sync
institute context install
open institute.xcworkspace

Institute is yours to name; swift-institute is not. That leaves you with Institute/swift-institute/institute-application alongside Institute/swift-institute/Skills, the materialized roots as further siblings, and the generated agent entry point in Institute/.

The export changes only the current shell; Workspace never edits a shell startup file. If your environment already puts $HOME/.local/bin on PATH, it changes nothing. Keep the equivalent setting in whichever environment manager owns future shells on your machine.

Choose that parent directory as a long-lived one, never a reclaimed-on-reboot location such as /tmp. It becomes the home of every Institute repository you will work in — the checkout root and the materialized organization roots beside it are durable working state, not scratch output. Selection.local.json is unrecoverable if lost: it is gitignored and exists in no remote by construction. Application/.build, the Xcode workspace and scheme, and a clean materialized repository are merely expensive to rebuild — regenerable from sync and a fresh resolve — but any uncommitted or unpushed work inside a materialized repository is not, because package work happens in those checkouts, not in this one.

The committed Selection.json decides that first synchronization. It selects the whole public roster, so a fresh clone materializes every package in Institute.json. To open fewer, remove them in Selection.local.json below.

To add packages to your own checkout, do not edit Selection.json. Write Selection.local.json beside it — the file is gitignored, so it never appears in git status and can never be committed by a git add .:

{
  "version": 1,
  "add": ["swift-primitives/swift-affine-primitives"],
  "remove": []
}

Identities are the exact owner/repository spelling from Institute.json; order has no effect. add and remove are both required — write [] for the one you are not using. Then re-run sync.

It is a delta, not a replacement list, so a package added to the committed policy later still reaches your machine. It also fails closed rather than doing something approximate: adding a package the committed selection already has, removing one it does not, naming the same package in both lists, removing everything, or naming a package absent from Institute.json each stop the command and say which file is wrong. Delete the file to go back to the default checkout.

Selection.json itself is committed policy — the public default checkout. Edit it only when you intend to change what everyone's fresh clone opens, and expect that change to be reviewed as policy.

Both sync and doctor lead with which selection is in effect, so a package that is not where you expect it names its own cause:

selection: Selection.json — 5 selected; Selection.local.json — 1 added, 1 removed; 5 in effect
  Selection.local.json withholds: swift-foundations/swift-http-body

institute.xcworkspace is generated, not committedsync writes it from the selection in effect, which is why institute sync must run before open. A fresh clone has no workspace file until it does; institute doctor reports it missing and names the command that writes it. Change the selection and re-run sync rather than editing the workspace in Xcode, because the next sync rewrites whatever you edited.

The bootstrap swift run is slow the first time, and it is silent while it works. Before institute install can print anything, SwiftPM resolves and compiles the command-line application and its whole dependency graph. Two costs stack up, and both are silent:

  • Resolution. SwiftPM fetches the full transitive dependency graph before compiling anything. On a first run with a cold package cache this is network-bound, so how long it takes depends on your connection more than your machine.
  • Compilation. The graph is large, so compilation can take several minutes after fetching; the duration and step count depend on the checkout, toolchain, and machine.

The earliest minutes print nothing at all while SwiftPM evaluates manifests, and the rest print nothing either: no progress bar, no percentage, nothing until the build finishes and institute install reports its destinations. Silence there is expected, not a hang.

That first swift run is the unavoidable self-hosting bootstrap. Its only job is to install a durable copy at $HOME/.local/share/swift-institute/institute/bin/institute and expose it through the relative link $HOME/.local/bin/institute. The copy lives outside SwiftPM's generated build state, so cleaning Application does not break the command. From the next line onward, workspace is the one canonical command surface, and all SwiftPM work goes through institute package.

The installer owns only a receipt-marked installation directory and the exact link it creates. It refreshes those on a later run, but refuses before changing anything if $HOME/.local/bin/institute is an unmanaged executable or link, or if its installation directory exists without the receipt. It also refuses to report success when $HOME/.local/bin is not on PATH, because a binary the shell cannot discover does not close the bootstrap gap.

Install the shared agent entry point:

institute context install

This validates every canonical skill before projecting it into your account's ~/.claude/skills, links the account-wide ~/.agents/skills to the same projection for Codex and ChatGPT, writes the generated root AGENTS.md, and links root CLAUDE.md to it. It fails closed on any path it does not own. The projection is account-wide so both clients load the same skills whichever root in the hierarchy a session starts from.

Skills come from canonical roots resolved beside this checkout, all optional because the hierarchy is:

RootPublicHolds
swift-institute/Skillsyes — clone itthe Institute's shared skills
swift-institute/Internal/Skillsnointernal-only skills
swift-institute/Engagement/Skillsnoengagement-only skills
rule-institute/SkillsnoRule Institute skills

swift-institute/Skills is the one every contributor can clone, and the quickstart above clones it. The command reports how many skills it projected and from which roots, and fails rather than reporting success when it resolves no root at all — an install that projected nothing is not an install, and used to print the same line as one that worked.

Install code navigation

Workspace owns the reproducible integration boundary between cclsp and Xcode's SourceKit-LSP:

institute navigation install
institute navigation check

install clones the public sourcekit-lsp-adapter line at the exact revision compiled into Workspace, installs dependencies from cclsp's frozen Bun lockfile, builds its Node executable, and writes two generated files beneath the physical organization hierarchy:

  • .workspace/navigation/cclsp.json — one SourceKit-LSP server for the Workspace Application and each currently materialized Institute.json repository;
  • .workspace/navigation/mcp-server.json — the command, arguments, and environment an MCP client registers.

The command prints the descriptor path. Client applications own their own registration format, so Workspace does not rewrite a user's global client configuration. The descriptor is the canonical value to translate into that format.

SourceKit-LSP is launched through the generated institute navigation serve invocation. That typed boundary removes TOOLCHAINS, resolves sourcekit-lsp through xcrun, and refuses a binary outside the Xcode selected by xcode-select. cclsp remains a distinct third-party TypeScript tool: it is not a Swift package, is not listed in Institute.json, and is never resolved from a personal fork or a fixed machine path.

The current generated configuration is deliberately per-package. A single deduplicated Institute-wide index requires a larger IndexStore merge and stabilizing acceptance probe; that exact Full-Swift remainder is tracked in issue #25. Workspace does not claim that per-package navigation is equivalent to cross-package index coverage.

Any earlier ad hoc merged-index pipeline and any prebuilt index bundle are retired, unsupported, and not prerequisites for navigation. A clean machine installs from the public cclsp revision and generates its own configuration through the Workspace commands above; it does not copy old index artifacts.

Lint

Run lint install once as part of setting up. Until you do, doctor reports a linter warning — swift-linter is not installed — on every run, so a fresh checkout never comes up clean. The warning is honest rather than cosmetic: without the binaries there is no lint verdict to have, and Workspace reports the absence instead of counting an unlinted ecosystem as a clean one. It does not fail your checkout; warnings still exit 0.

Workspace runs the same swift-linter CI gates on: the same binaries from the same rolling ci-binaries release, verified against that release's SHA256SUMS, invoked as swift-linter <package-root> --exit-policy strict with the prebuilt standard runner provisioned on the environment. Workspace sets that environment variable itself; a developer's shell profile is never written, which is what makes the setup identical for everyone.

institute lint install     # fetch, verify, record the build
institute lint check       # is it the build CI consumes?
institute lint             # the whole ecosystem
institute lint --changed   # only packages with local work
institute lint ledger      # complete residual compliance ledger
institute lint ledger --format json
institute package lint     # one package, from inside it

package lint takes no arguments: standing anywhere inside a package it finds the package root and the installed binaries by walking up, reads no inventory, and enumerates no organization. The whole-ecosystem sweep enumerates from Institute.json and lints packages concurrently. Both modes go through one implementation, so a package's verdict cannot depend on which one asked for it.

lint ledger is the read-only machine evidence entry point for the complete inventory. Its human and JSON forms come from the same typed report. Every Institute.json repository appears exactly once with its canonical identity, owning organization, layer, measured or UNMEASURED state and reason, typed prerequisite cause when one blocks measurement, exact unsuppressed error count, advisory findings grouped by rule, and a known or explicitly unknown verification coordinate. JSON object keys, package rows, findings, and remediation batches are deterministically ordered. A validated inventory containing zero repositories is incomplete and exits 2; it can never render a compliant ledger.

Terminal advisory decisions and qualifying exact-head GitHub Actions runs are explicit inputs; Workspace does not infer them from Issue prose, comments, or past CI state:

institute lint ledger --format json \
  --disposition 'PLAT-ARCH-022=remediation@swift-foundations/swift-linter#20' \
  --verification 'swift-primitives/swift-bytes@<40-hex-sha>=https://github.com/swift-primitives/swift-bytes/actions/runs/<id>'

The verification grammar is owner/repository@<40-hex-sha>=https://github.com/owner/repository/actions/runs/<id>. Disposition states are promotion, retention, change, removal, addition, relocation, and remediation. Batches combine only the same rule and exact supplied owner Issue; missing dispositions remain explicitly unresolved.

The ledger requests swift-linter's existing SARIF result schema and validates the result count against the always-on summary. It never parses the human diagnostic format or reclassifies findings. Until configured-package and prebuilt-runner dispatch propagate that structured-output request, affected rows fail closed as UNMEASURED and name swift-linter issue #20. The typed sarif prerequisite field owns that blocked state; human reason prose is rendered alongside it but is never parsed to derive machine output. Exit 0 means a complete compliant ledger, 1 means complete evidence with error-severity findings, and 2 means incomplete evidence or unresolved advisory disposition.

Every package is linted, whether or not it carries a Lint.swift. A package with one is dispatched exactly as CI dispatches it. A package without one cannot go through the dispatcher at all — with no consumer manifest to classify, the dispatcher falls through to a zero-rules configuration and exits clean having loaded nothing — so it is handed straight to the prebuilt standard runner with an explicit bundle selection on SWIFT_LINTER_BUNDLE and the exit policy on SWIFT_LINTER_EXIT_POLICY, which is the same terminal the dispatched executable reads.

The default bundle is the one the package's own layer already uses: primitives for the primitives layer, standards for the standards layer, institute for everything above them. That is not a second standard — it is byte-for-byte what the package's configured peers activate, so writing the Lint.swift its layer's convention calls for changes nothing about the verdict. Picking each layer's own bundle rather than one global choice is what stops a primitives-layer package from being measured against a weaker set than its peers, which would reward staying unconfigured. A package that sits under no layer root has no peers to inherit from and is reported UNMEASURED rather than linted against a guess.

This one path has no CI counterpart: CI's activation signal is the presence of Lint.swift, so for these packages CI runs nothing. The default-bundle run is Workspace's own measurement. Nothing here changes what the gating CI legs require.

A lint run cannot report clean without having measured something. The engine ships rule-pack-agnostic: without a reachable configuration zero rules fire, and three invocations of the dispatcher exit zero having printed nothing at all — a directory holding Swift source but no Lint.swift, a file path rather than a package root, and an empty directory. Exit status attests that a process ran, never that it was configured. Every run is adjudicated against the engine's always-on summary line, and a missing summary, zero active rules, or zero files linted reports UNMEASURED — never clean, per package inside the sweep as well as on its own. A sweep that enumerates the inventory and materializes nothing fails rather than reporting an empty ecosystem clean. Exit status follows doctor: 0 measured and passing, 1 measured with error-severity findings, 2 something could not be measured.

A file path is resolved to its enclosing package, which is linted whole and whose diagnostics are then narrowed to that file — passing a file to the engine is one of the silent-zero invocations and is unreachable through this capability.

lint check compares the installed build's composite digest against the one CI consumes. Because ci-binaries is a rolling tag, that establishes you are running what CI would install right now, not what CI ran on any past run. The macOS asset publishes on a slower cadence than Linux, so a transient divergence is expected rather than a defect. A lint run itself never contacts the network.

Build and test packages

The bootstrapped executable owns SwiftPM concurrency, job count, and build state:

institute package build --package-path Application
institute package test --package-path Application --fresh
institute package resolve --package-path Application

Builds are serialized through a machine-wide advisory lock and compile with three jobs. --fresh is available for build and test evidence: it uses a unique scratch directory beside the package and removes it before returning. Additional SwiftPM arguments use repeated --argument options, for example --argument=--filter --argument=Unit; coordinator-owned path, state, and job options cannot be overridden. The coordinator never edits Package.resolved.

sync prints its complete plan before changing repositories. It clones missing repositories into the org hierarchy described above and only fast-forwards an existing repository when it is clean, currently on main, tracks origin/main, and has no local commits. It never resets, cleans, stashes, rebases, switches a feature branch, or overwrites a repository.

Preview the plan without changing files or Git metadata:

institute sync --dry-run

Where packages materialize

The org hierarchy materializes beside the physical checkout. Workspace resolves the checkout through symlinks first, then uses exactly its parent as the organization directory; invoking the tool through a symlink does not redirect that hierarchy. For a clone at X/Workspace:

X/
├── Workspace/              this repository: Application/, Institute.json, Selection.json,
│                            your ignored Selection.local.json if you have one, and the
│                            generated, untracked institute.xcworkspace
├── swift-primitives/       ┐
├── swift-standards/        ├ materialization roots: independent repositories,
└── swift-foundations/      ┘ none part of this repository

Each package under those roots is an independent repository with its own history, remote, CI, and license. Committing their contents to this repository is always wrong — work on a package inside its own checkout and open the pull request on its own repository.

The roots sit beside the clone rather than inside it so the checkout stays a plain repository and the hierarchy reads as the organization itself. sync creates repositories only beneath those inventory-derived roots. Clone and update validation may use collision-resistant temporary siblings in the same organization directory, and the generated institute.xcworkspace remains inside the Workspace checkout. Materialized paths are regenerable state — if a repository moves between organizations, its inventory entry changes and sync materializes the new location, so nothing durable should reference one of these paths as though it were stable.

Before inspecting or writing a materialized path, Workspace rejects . and .. traversal, symbolic links and non-directories in existing path prefixes, and any prefix that resolves outside the physical organization directory. These checks assume a stable local filesystem namespace: they are repeated safety snapshots, not a descriptor-relative guarantee against another process replacing a directory concurrently.

Peer institutes

A peer institute is a sibling ecosystem — the Rule Institute is the first — whose checkout root sits beside this hierarchy root under the same entry directory, carrying the peer's name. For a clone at X/Workspace the hierarchy is X/, and a peer named rule-institute roots at the entry sibling X/../rule-institute/:

entry/
├── swift-institute/          the hierarchy above: Workspace/ and the materialization roots
└── rule-institute/           a peer institute's root — not part of this hierarchy
    ├── .github/              the peer's control plane, carrying its inventory file
    └── swift-nl-wetgever/    the peer's organization directories

The committed Peers.json beside Institute.json registers which peers Workspace can resolve and where each peer's own inventory file lives relative to its root. The peer inventory ({"version", "ecosystem", "repositories"}, records {"name", "url", "organization"}Institute.json's record shape minus layer) stays inside the peer's tree, so the peer owns its package records and this repository never carries them. A peer repository materializes at <peer root>/<organization>/<name>, or directly at <peer root>/<name> when its organization is the peer's eponymous one — the same inventory-derived discipline as above, with no layer level: locations come from the declaration, never from walking a tree.

Adoption is opt-in per checkout: a machine without the peer root has simply not opted in, which doctor's peer-checkout check reports as a fact, never a finding. A peer root that exists without a usable inventory is the state the mechanism exists to end — its packages would only be locatable by tree inference — so that warns, and a broken declaration is an error. institute inventory prints each registered peer's register after the swift-institute one. sync does not clone peer repositories; materializing a peer tree is the peer's own concern.

Reading doctor

doctor reports what is measurably true about your checkout right now — never a written snapshot:

institute doctor

A healthy contributor run reports one line per check, then a summary that repeats every measured population, then a verdict:

<check>: ok (population n)
…
inventory-currency: not run (institute-internal)
N checks: … ok, 1 not run (institute-internal); measured populations: …
doctor: passed — N check(s) measured, 1 not run (institute-internal), N warning(s).

Deliberately a shape rather than a transcript. Read the verdict line and the populations your own run prints — a pasted sample is a claim about a version of this repository you are probably not on.

The four results

Every check ends in exactly one of four states, and they are deliberately never printed the same way:

ResultMeaning
ok (population n)The check ran over n subjects and found nothing wrong.
warning findings / error findingsThe check ran and lists what it found.
unmeasured — <reason>The check could not establish what it needed to measure. This is not a pass.
not run (institute-internal)The check is out of scope for a contributor run. This is not a failure.

Exit status follows: 0 when everything was measured and no errors were found (warnings still exit 0), 1 when a check measured an error, and 2 if anything was unmeasured. An unmeasured check outranks an error precisely because it is worse: a failure to measure hides an unknown number of both. A run containing one is never described as passing — "we could not look" is a different answer from "we looked and it was fine", and conflating them is how a broken check masquerades as a green one.

inventory-currency needs an authenticated GitHub client that the contributor path does not carry, so it reports not run. That is the expected result and it does not fail your checkout. If a step ever demands credentials or a repository you cannot read, that is a defect worth reporting.

A maintainer with an authenticated gh can ask for it explicitly:

institute doctor --institute

That discovers the live Institute organizations and compares the result with Institute.json in both directions, naming every repository that is on one side and not the other. It is opt-in rather than automatic on purpose: doctor is otherwise credential-free and offline, and it must not become a different, slower, network-bound command on the machines that happen to have gh logged in. Nothing about the contributor invocation above changes. Drift is caught without anyone remembering the flag by the roster-currency workflow, which runs the same command nightly.

Why a population is printed

The population is the check's evidence that it actually measured something. A check that silently evaluated zero subjects would print exactly the same reassuring ok as one that examined all of them, so the count is printed to make the difference visible: materialization: ok (population n) says repositories were inspected, not that inspection was skipped.

ok (population 0) therefore means something specific: the check ran, its controls fired correctly, and there were genuinely zero subjects in existence to measure. Above, resolved-pins: ok (population 0) means no materialized repository has a Package.resolved yet, so there are no pins to compare against their branch tips. For repository-subject checks, an empty population measured against a non-empty selection is reported unmeasured, never ok — that case is a failure to measure, and it is reported as one. The institute-only inventory-currency check is the exception: it compares the complete inventory with live discovery and reports their union as its measured population.

Each check also carries a known-positive and a known-negative control that run through the same evaluation path as the real subjects. If the control that must fire does not, the check aborts as unmeasured rather than reporting a green it did not earn.

Materialization states

For each selected repository, doctor distinguishes the active sibling location from the superseded location inside the Workspace checkout:

On-disk stateResult
Git repository only at the sibling locationCanonical and ok.
Git repository only inside the Workspace checkoutLegacy and an error.
Git repositories at both locationsAn error; the sibling is active and the legacy checkout is left untouched.
No Git repository at either locationAbsent and an error.
A location cannot be formed or safely inspectedInvalid and an error.

Workspace never migrates or deletes a legacy checkout. Only the active sibling repository enters the downstream working-state, resolved-pin, and manifest-identity checks; a legacy-only tree never satisfies them.

Severities

Dirty worktrees, untracked files, detached HEADs, feature branches, and stale resolved pins are warnings — they may hold your unpushed work, so they are reported and left alone. Identity, remote, upstream, divergence, toolchain, missing-package, and workspace-reference problems are errors.

Working across packages locally

Every package depends on its siblings by URL, so an edit to a dependency normally has to be pushed before the package consuming it can see the change. That is the wrong loop for work that spans two repositories at once.

compose closes it: it rewrites one .package(url:) clause in the consumer's manifest to a .package(path:) clause pointing at the dependency's own checkout in this workspace, so builds compile the source you are editing. restore puts the manifest back. verify reports which source actually compiled, so you never have to trust your own memory of which state you left things in.

Both packages must be named in Institute.json and checked out. If one is not already checked out because it is outside the committed default, add its canonical identity to the add list in your Selection.local.json, then run sync before composing it — not to Selection.json, which is committed policy rather than your own checkout.

The loop, end to end

Say you are changing swift-color-standard and want swift-color to compile against your local copy.

1. Compose. Point the consumer at your local checkout:

institute compose \
  --consumer swift-color --dependency swift-color-standard
Composed swift-color → swift-color-standard (local development source).
  manifest: <checkout-parent>/swift-foundations/swift-color/Package.swift
  now: .package(path: "<checkout-parent>/swift-standards/swift-color-standard")
  was: .package(url: "https://github.com/swift-standards/swift-color-standard.git", branch: "main")

  ⚠️  This manifest now carries a machine-local absolute path.
      Do NOT commit or push it — it resolves only on this machine.

The written path is deliberately absolute. That means the composed manifest is worthless on any other machine — which is the point: if it escapes, it fails loudly at resolution instead of quietly resolving to some other copy of the package. Treat a composed manifest as an uncommittable local state.

2. Edit and build. Change swift-color-standard in its own checkout and build swift-color normally. It now compiles your local source.

3. Verify — which source actually compiled:

institute verify \
  --consumer swift-color --dependency swift-color-standard

This reads SwiftPM's own resolved state; it never infers the answer from the manifest. It also compares that against the composition ledger and warns if the two disagree — which means the last resolve predates your current composition, and you need to re-resolve before believing anything.

4. Restore before you commit or push:

institute restore \
  --consumer swift-color --dependency swift-color-standard

The declared .package(url:) clause comes back byte-for-byte — the original text is stored when composing and replayed verbatim, not regenerated, so nothing about the manifest's formatting drifts. Your work in the dependency's checkout, including unpushed commits, is never touched: restore only edits the consumer's manifest.

What restore's structural check does and does not guarantee

restore finishes by evaluating the restored manifest in an isolated temporary directory, and tells you so:

  Structural check (resolve-free): the restored manifest evaluates, swift-color-standard
  is declared by URL again, and no local path leaked. This does NOT resolve
  dependencies and is NOT a remote-reproducibility guarantee.

Read that limit literally. The check does confirm three things: the restored manifest still evaluates, the dependency is declared by URL again rather than by path, and no machine-local path survived.

It does not resolve anything. It does not contact a remote, does not confirm the declared URL exists, does not confirm the branch still has the commit you built against, and does not prove your colleague or CI can build what you just restored. A green structural check means "the composition was removed cleanly" — not "this builds from its canonical sources."

Confirming that last part is a step this tool deliberately leaves to you: run a full resolve and build afterwards. If the dependency's local commits were never pushed, your restored consumer will resolve to a remote that does not have them, and only a real resolve will tell you.

Limits

One composition per consumer/dependency pair at a time — compose again without restoring and it refuses rather than stacking edits. If the composed clause has been hand-edited out of the manifest, restore refuses to guess and says so. Both packages must be workspace repositories; arbitrary local packages, multi-root setups, and Xcode-side composition are out of scope.

Questions

Issues are the channel — for questions as much as for defects:

gh issue list --repo swift-institute/institute-application

There is no private support path and no internal-only documentation: this README is the whole contributor surface. A step that does not work as described here, or that turns out to need access you do not have, is a defect — please open an issue rather than working around it.

Contributing

Contributions come through the same path this README describes — there is no second, internal one. Pick up an issue, work in the package's own repository at its org-layout checkout, and open a pull request there; each package is an independent repository with its own history and CI.

Before opening a pull request, run doctor and make sure the package builds and tests from its own repository. doctor reports which of its checks apply to your setup; checks that need Institute access report that they did not run rather than failing your checkout.

From a clone to a pull request

The steps above get you a working checkout; these are the ones that get a change out of it. They are listed because nothing else here covered them, and a contributor who is set up but cannot land a change is not set up.

Set a Git identity. A fresh account has none, and the commit fails rather than warns:

git config --global user.name "Your Name"
git config --global user.email "you@example.com"

Expect to push somewhere other than where you cloned. The quickstart clones over HTTPS and sync gives every materialized package an HTTPS remote, so git push on an unauthenticated HTTPS remote fails with could not read Username. That is the expected result of a credential-free setup, not a broken checkout — read access needs nothing, write access needs an account.

If you have no write access to the package — which is the normal case — fork it and push there. Run this inside the package's own repository, not inside this one:

cd ../swift-foundations/<package>        # the package you changed
gh repo fork --remote --remote-name fork
git switch -c <branch>
git commit -am "<message>"
git push -u fork <branch>
gh pr create --repo <org>/<package>

If you do have write access, push a branch to origin directly and open the pull request the same way. Maintainers push over SSH: an HTTPS remote is additionally rejected when a change touches workflow files, so switch that remote to git@github.com:<org>/<package>.git rather than working around the failure.

Open the pull request against the package's repository, never this one. Each package is an independent repository with its own history and CI, and sync will fast-forward your checkout of it once the change lands.

Scope

The committed selection is the full public roster, so a fresh clone materializes every package in Institute.json. The inventory and the selection line that sync and doctor print are the authorities; this document does not duplicate their changing counts.

The Xcode workspace uses only relative sibling-layout references (../swift-foundations/swift-color, …); non-selected transitive dependencies still resolve from their canonical remote URLs.

License

Licensed under the terms in LICENSE.md.