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).
| Command | What it does |
|---|---|
install | Install or refresh the account-local workspace command. |
sync | Clone missing repositories and fast-forward eligible ones. Never rewrites work. |
doctor | Report what is measurably true about this checkout. |
inventory | Print the committed name → organization → relative-path register. Never discovers or writes. |
inventory regenerate | Discover the live roster and replace Institute.json; --dry-run plans only. |
dependencies | Audit direct manifest dependency origins at exact remote revisions; never writes package state. |
compose | Point one package's dependency at your local checkout of it, so edits are picked up. |
restore | Undo a composition, returning the manifest to its declared form byte-for-byte. |
verify | Report which source a dependency actually compiled from, read from resolved state. |
context install|check | Install or verify the checkout-root agent entry point and canonical skill projections. |
context packet | Render a bounded, read-only current-state packet for one GitHub Issue; ordinary history stays cold unless a comment URL is named explicitly. |
navigation install|check | Install 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 lint | Lint one package with the same binary, rules, and exit policy CI gates on. |
lint | Lint the whole ecosystem. install|check manage the pinned linter and its parity with CI. |
lint ledger | Render 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:
| Layer | Family | GitHub org |
|---|---|---|
| 1 | Primitives — atomic, dependency-light identity and value types | swift-primitives |
| 2 | Standards — models of externally defined formats, protocols, and specifications | swift-standards |
| 3 | Foundations — operational capabilities composed from the lower layers | swift-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/repositoryidentities. 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.jsonis your own delta over that policy —addandremove— and it is gitignored. It is how you change what your machine opens without editing a tracked file. - Checkout facts:
institute doctormeasures 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 committed — sync 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:
| Root | Public | Holds |
|---|---|---|
swift-institute/Skills | yes — clone it | the Institute's shared skills |
swift-institute/Internal/Skills | no | internal-only skills |
swift-institute/Engagement/Skills | no | engagement-only skills |
rule-institute/Skills | no | Rule 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 materializedInstitute.jsonrepository;.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:
| Result | Meaning |
|---|---|
ok (population n) | The check ran over n subjects and found nothing wrong. |
warning findings / error findings | The 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 state | Result |
|---|---|
| Git repository only at the sibling location | Canonical and ok. |
| Git repository only inside the Workspace checkout | Legacy and an error. |
| Git repositories at both locations | An error; the sibling is active and the legacy checkout is left untouched. |
| No Git repository at either location | Absent and an error. |
| A location cannot be formed or safely inspected | Invalid 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.