npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@clossys/launcher

v0.5.1

Published

Foundry workspace scaffolder: create, resume, or adopt a GitHub repository as the account hub. Executable tooling, not a role.

Readme

Foundry Launcher

Foundry Launcher (@clossys/launcher) is executable tooling that makes one GitHub repository the account workspace hub. It is not a role and does not claim adoption, grounding, or closure.

The hub inventories where Foundry packages are installed and coordinates engagement. It is not a product application and does not receive a dump of the catalogue.

Layout: the clossys/ folder

Every apply writes to one visible clossys/ folder in the target repository — never a hidden dot-folder for anything a person might want to see. A generated README.md at the root of clossys/ is an index of which clossys/<role>/ folders are active and what each holds; it is rewritten on every run, never hand-edited. clossys/.state/ holds machine files only — the hub marker, the inventory, and the skills manifest (below) — visible so it is easy to find, but still not a place to edit by hand. Other roles' folders (clossys/strategist/, clossys/writer/, and so on) are written by their own packages, not by launcher.

A hub created before this layout existed kept its marker and inventory under a hidden .clossys/. Resume detects that automatically and migrates both files to clossys/.state/, removing the old directory, and reports the move in the health report. If somehow both a .clossys/ and a clossys/.state/ hub state exist at once, launcher refuses rather than guessing which one is current — remove one and resume again.

Conversation contract

Every composed skill carries one shared conversation contract: lead with a plain-language status, always state a recommendation, ask exactly one question with the recommended option labelled first, and say what happens next. Launcher packs this contract at build time and injects it into each composed skill in place of that skill's own "how we work together" and "one question at a time" sections, at the same position — so an installed or catalogue skill's own wording never has to drift from it.

Skills manifest and freshness

Every apply writes a skills manifest recording each composed skill's source (installed or catalogue), version, and a content digest. The health report states how many composed skills are out of date against the live @clossys/launcher version (catalogue-sourced skills are only ever as fresh as the launcher release that packed them) and how many were retired this run. Retirement means: a skill this directory's own manifest previously listed is no longer composed (its source disappeared), so launcher removes its composed output and host discovery links — and only that. It never touches a skill it did not itself write.

The recorded digest is how launcher tells whether it still owns a composed skill. Before rewriting or retiring one, it compares the file on disk (.agents/skills/clossys-<package>/SKILL.md, and any real-directory copy at a host discovery path) with the digest it recorded when it last wrote that file:

| On disk | Rewrite (skill still composed) | Retire (skill no longer composed) | | --- | --- | --- | | Matches the recorded digest | Rewritten | Removed, with its discovery links | | Edited since launcher wrote it | Left as is and reported | Left as is, with its discovery links, and reported | | Missing | Recreated | Retirement completes (discovery links removed) | | No recorded digest (first run, or an older install) | Adopted if it already equals what launcher would write; otherwise left as is and reported | Not touched: launcher only retires a skill its manifest records |

A SKILL.md that exists but cannot be read (a permissions error, or a directory in its place) is treated like an edited one: left as is and reported. A retiring skill directory that holds files other than SKILL.md is also left as is and reported; a macOS .DS_Store file is ignored for this check. Each skill left as is appears in the health report as a skill preserved line naming the file or directory that failed the check, and in the report JSON under skillComposition.preserved; it marks the report degraded, and it is reported again on every run until resolved. Launcher recreates a missing composed skill because .agents/skills is launcher-generated output, so recreating it loses nothing a client wrote. That is also how to take launcher's version of a skill you edited: move your copy aside, delete the directory the skill preserved line names (usually .agents/skills/clossys-<package>/), and run launcher again. To keep your edit instead, leave the file as it is.

Health report and staleness

After create, resume, or appoint — and on every resume — the command prints a read-only health report. It scans all four dependency buckets (dependencies, devDependencies, optionalDependencies, peerDependencies) for the two hub engine pins, @clossys/advisor and @clossys/integrator (an advisor pin and an integrator pin line), and for extra @clossys/* names. When an engine's live registry version is known, each of its pins is graded against it: a pin older than live is a stale pin finding, named by package, and marks the report degraded. The report is also degraded when either engine is missing, dual-pinned, or present in any bucket other than devDependencies, when a composed skill in the hub was left as is because a client edited it (the skill preserved lines above), when the hub's stored inventory fails its contract, and when the hub's lockfile does not resolve the engine pins yet (an install needed (engine-pins-changed-install-needed) line; see "Engine pins and the lockfile" below). Per-package skill sources missing from the catalogue are noted but do not by themselves mark degraded. Each inventoried repository other than the hub gets a sibling line (and an entry in skillComposition.siblings) saying what the run found for it: a checkout beside the hub, one not cloned yet, another account's repository, the Foundry supplier tree, a folder that is not a git checkout, a checkout git refuses to read (dubious ownership), or a checkout whose git origin does not match. The line names that repository by its position in the stored inventory's repositories array (repositories[<i>] in the stored inventory), never by its id (#1179), because the whole health report is also JSON-dumped into the apply message's health: line. For a checkout beside the hub or one not cloned yet, the line says a hub run writes nothing there, and that once the repository is staffed in an approved plan, @clossys-advisor and the voices of the roles staffed there arrive with that plan's setup pull request. A sibling line never marks the report degraded, and a sibling's working tree, output an earlier release wrote into it, or its old pins do not change the hub run's result. Exit stays 0 on resume (the report is advisory); adopt prints the same report and an unparseable pin-versus-live comparison is noted as indeterminate rather than stale. checkInventoryEntries() additionally validates hub inventory ids read-only, marking ids whose repository no longer resolves (skipped with a note when gh is unavailable).

Engine pins and the lockfile

For engine pins, resume and appoint change only the hub's package.json, never its lockfile, and install nothing. A run that changes an engine pin says exactly what it changed (engine pins changed in package.json: @clossys/advisor 0.2.6 -> 0.5.0; @clossys/integrator added at 0.8.2, and health.enginePins in the JSON) and gives one next step: run the hub's package manager install (npm install, pnpm install, yarn install or bun install, by the lockfile present), then commit package.json together with its lockfile. Until then a frozen install (npm ci, pnpm install --frozen-lockfile, yarn install --immutable) refuses the hub when a pin's version changed or an engine was added; when a pin only moved between dependency buckets, or a range became the exact version already locked, whether it refuses depends on the package manager (pnpm's frozen install does), so such a change is reported the same way. While a lockfile is present that does not resolve the pins yet, the report is degraded with an engine-pins-changed-install-needed finding (health.installNeeded): an npm lockfile (npm-shrinkwrap.json, which npm prefers when both exist, else package-lock.json) is read on every run, and each engine pinned to a plain version must resolve to that same version in it, compared as versions (a v0.6.0 pin matches a locked 0.6.0); any other lockfile is not read, so it counts as unresolved for the engines the run changed. Resume and appoint only raise a pin: one older than live, or not a plain version, becomes the live version, and one newer than live is kept. Resume rewrites package.json as 2-space-indented JSON with a final LF, only when a pin changes, and never changes its name.

Install

The get-started command is the package name:

npx @clossys/launcher

npx caches the resolved version, so a plain npx @clossys/launcher can keep running an old one. Because a catalogue-sourced skill is only ever as fresh as the launcher release that packed it (see "Skills manifest and freshness" above), run:

npx @clossys/launcher@latest

when the health report says a skill is out of date, or whenever you want to be sure you are on the current release.

Public npm reads are credentialless. Packages publish to https://registry.npmjs.org. Do not add a token or private registry mapping for @clossys. Pin an exact version once you depend on the library API:

npm install --save-dev --save-exact @clossys/[email protected]

Talking to the team

First contact is npx @clossys/launcher (empty directory or the checkout you appoint as the hub). After apply, the launcher composes the @clossys-<package> voices into the hub (.agents/skills/clossys-<package>/ plus host discovery links). A launcher run writes no skills, skills manifest, discovery links, AGENTS.md or CLAUDE.md into an inventoried product repository, and changes nothing in its checkout beside the hub (--clone-missing, below, only clones a missing one). Once a product repository is staffed in an approved plan, it receives those files with that plan's setup pull request; an inventoried repository that is not staffed does not receive them. Composition is per checkout, not machine-wide, and is not a catalogue dump into package.json.

Voices are how you talk in a coding agent; they are not engagement engines. The @clossys/advisor npm package is the engine that grades evidence; @clossys-advisor in chat is its hiring and compatibility voice. Use @clossys-advisor and @clossys-<package> in the hub, where the whole team is composed. A product repository staffed in an approved plan gets @clossys-advisor and the voices of the roles staffed there, once that plan's setup pull request has merged; a role not staffed there is expected to be absent. Each voice can talk even when that npm package is not pinned in that repo. When composing into the hub, the launcher reads each skill body from the hub's installed @clossys/<package>/skill/SKILL.md when present, then from the packed catalogue or a sibling monorepo source. Launcher health notes missing catalogue sources but apply continues.

Run npx @clossys/launcher again from the hub for a health report and to refresh the voices composed in the hub. By default it does not gh repo clone missing inventory entries -- that is not how you talk to the team. launcher --clone-missing is the one explicit, approved exception (#1179): on resume only, it clones every inventoried repository not yet sitting beside the hub, using cloneMissingInventoryRepositories(), and only those -- an id skipped for any other reason (wrong account, the Foundry supplier tree, a mismatched git origin) is left exactly as skipped, never attempted. Cloning is not composing: a repository cloned this way receives its team only once it is staffed in an approved plan, like any other.

How to run it

There is no --org flag. Owner is inferred from authenticated gh and from the current git remote. If more than one GitHub owner is visible, the command asks which one should hold the hub. Non-interactive runs may set CLOSSYS_OWNER.

GitHub-only. A GitLab, self-hosted, or other remote is a refusal, not a silent fallback.

| Current directory | What happens | | --- | --- | | Empty | Creates {owner}/workspace from the in-package skeleton (package name @owner/workspace, pinning live @clossys/advisor and @clossys/integrator exactly in devDependencies), or, when that repository already exists, clones it and classifies its hub marker like a local run: a current marker resumes; a legacy .clossys/ marker alone is migrated to clossys/.state/; both markers are refused before Launcher writes into the clone; with no marker, the clone is appointed as the hub (refused when its working tree has uncommitted changes; existing README.md, AGENTS.md and CLAUDE.md are kept, and a skeleton package.json is written only when the clone has none) and the apply message says "appointed". That appoint needs no inventory, but refuses when the clone carries an inventory that fails its contract or a package.json that is not a JSON object; every such refusal leaves the clone unchanged. | | Already a hub (generated marker; packed template skeleton/clossys/.state/workspace.json) | Resumes. No new repository. --repositories writes the repositories the founder chose again into clossys/.state/inventory.json before skills are composed (see "Choosing the hub's repositories" below). --inventory here is refused, and the refusal points at choosing the repositories again on Advisor's repository card and running launcher --repositories, instead of at hand-editing the file. A legacy .clossys/ hub state is migrated automatically; see "Layout" above. For each hub engine whose live version the registry returned, pins that version exactly in devDependencies of the hub's existing package.json: a frozen older pin is bumped, a pin newer than live is kept, a missing Integrator pin is added, and a pin in another bucket is moved; other @clossys/* entries are left as they are, and the file is rewritten only when a pin changes. The report then names each change and the install to run next (see "Engine pins and the lockfile" above). | | Any other GitHub repository you control | Appoints it as the account hub. Keeps existing product files. Refuses when the working tree has uncommitted changes (git status --porcelain non-empty) — the refusal names the offending remote host when the origin is not on github.com. Refuses when CLOSSYS_OWNER names a different account than the repository's github.com origin owner. Writes the hub marker. Pins live @clossys/advisor and @clossys/integrator, each exactly, in devDependencies, relocating any pin left in another bucket, raising an older one (a pin newer than live is kept), and leaving other @clossys/* entries as they are; the report names each change and the install to run next. An existing package.json name is kept; a {owner}/workspace checkout whose manifest has no name is named @owner/workspace. Refuses, before writing anything, an existing package.json that is not a JSON object (unparseable, an array, or a primitive). Always reads the public Advisor and Integrator versions (needed to pin live, and to pin and grade health on resume); refuses as indeterminate when the registry returns either one unreadable. Refuses if the generated hub inventory is missing or empty (packed template skeleton/clossys/.state/inventory.json; that generated path does not ship), and the refusal points the founder at choosing the hub's repositories on Advisor's repository card and passing them to --repositories, which writes the inventory (see "Choosing the hub's repositories" below). --inventory <path> still supplies a populated document instead — and, when the on-disk inventory is already populated and --inventory is also supplied, merges the two by repository identity (on-disk order first, new repositories appended, the first occurrence of a repository kept, and every kept entry kept whole, its packages included). Does not rewrite the lockfile or dump the catalogue. Prints a read-only health report. |

It does not have to be a brand-new exclusive repository, and it does not have to already match a Foundry layout. Informal "workspace-looking" trees are appointed by writing the hub contract, not assumed to be hubs already. Product applications may be appointed: they become the hub and remain the product. Sister repositories still pin their own roles. Appoint does not install packages.

Do not run this inside the Foundry supplier tree.

Open the resulting folder in your coding agent. Talk in ordinary sentences. Advisor stays read-only until you approve a next action. A yes in chat is permission for that one step only; it is not a lasting grant and it does not write git unless a file is saved later. The same command resumes later.

Choosing the hub's repositories

A founder never writes the inventory by hand (#1179). Advisor's repository card (@clossys/advisor's repositoryChoiceCard(), or its advisor-repository-card bin before the hub exists) offers the repositories the agent listed from GitHub for the founder's sign-in; the founder chooses; and the agent passes the chosen ids to Launcher:

launcher --repositories example-owner/example-app,example-owner/example-site

--repositories takes one argument of comma-separated repository ids (an id never contains a comma). It works when appointing a repository as the hub and on an existing hub checkout; in an empty directory it is refused, because there is no hub to write into yet. Launcher writes the chosen ids to clossys/.state/inventory.json (a generated hub path, not shipped in this package) -- the one inventory location it reads on every later run -- and, on resume, writes it before the health report is built, so the same run lists the repositories just chosen as sibling lines.

  • The ids, and the document built from them, are checked against docs/contracts/repository-inventory.json (in the public repository; that exact path does not ship in this package, but this package's build packs and ships its own copy of the contract) through the same shared contract checker every read of the inventory uses, and the document is checked again by that reader before it is written. What Launcher writes is what Launcher reads back.
  • A malformed choice -- an empty id, an id that is not a bare name or owner/name, or two ids naming the same repository -- is refused by position (repositories[1].id ...), never echoed, and nothing is written.
  • Launcher decides "is this the same repository" one way everywhere: a bare id names a repository of the hub's own owner, and letter case is ignored, so app, App and <owner>/app are one repository. Owners are compared the same way. A stored inventory, or a choice, that lists one repository twice this way is refused, naming the two positions; it is never merged. The hub itself is recognised by its origin's owner/name (or, without a github.com origin, the repository its marker records), never by its folder path, so an inventory that names the hub in another letter case never lists it as its own sibling.
  • An inventory that already lists exactly the chosen repositories, in any order or letter case, or with a bare id for the hub's own owner, is left as it is.
  • An inventory that lists a different set is never merged into or overwritten silently: the run is refused, stating how many repositories each side has and which positions would be added and removed -- --repositories[<i>] for an added id's position in the --repositories argument, repositories[<i>] in the stored inventory for a removed id's position in the file -- never the ids themselves, because both the stored inventory file and --repositories are input an agent may relay verbatim, and a repository id is exactly the kind of short string a hostile inventory entry could shape as prompt-injection text. Run again with --replace-inventory to approve the replacement; a repository that stays keeps its existing entry, packages included. An agent acting on the refusal looks each reported position up in its own copy of the stored inventory file or its own --repositories argument to learn which repository it names, and tells the founder that name -- never the position string itself, and never text read back out of the inventory file or the argument without that lookup. That replacement run's own success line reports the same removed positions again, distinctly labeled repositories[<i>] in the replaced inventory: by the time that line prints, clossys/.state/inventory.json is already the new file, so reusing "in the stored inventory" there would point a reader at the wrong document. The positions still index into the file as it stood before this run -- the same one the refusal step already named -- so an agent that already looked a position up there does not need to look it up again. The same position-only rule, and the same "in the stored inventory" wording, applies to every other message this command prints that names a repository from the file currently on disk -- each sibling (...) line and launcher --clone-missing's output. (Since #1511 the skill roster written health-report line names only the hub's own id, never a stored-inventory position, so it is no longer on this list.)
  • An inventory that fails its contract is likewise replaced only with --replace-inventory.
  • --repositories and --inventory each supply the whole inventory, so they are refused together, and --replace-inventory without --repositories is refused.

CLI

launcher
launcher --repositories example-owner/example-app,example-owner/example-site
launcher --repositories example-owner/example-app --replace-inventory
launcher --inventory path/to/inventory.json
launcher --clone-missing
launcher --help
launcher-check --help
launcher-check --input observation.json
launcher-doctor
launcher-apply-plan --plan plan.json --brief brief.json --repo ./product-checkout
launcher-apply-plan plan
launcher-apply-plan materialize --repo ./site-checkout
launcher-apply-plan verify --repo ./site-checkout
launcher-apply-plan status --repo ./site-checkout
launcher-apply-plan body --repo "<id>" --task-record 12
launcher-apply-plan snapshot --request package-request.json

Exit codes preserve the ternary:

| Exit | State | Meaning | | --- | --- | --- | | 0 | satisfied | Created, resumed, or appointed the hub. The message includes a read-only health report. | | 1 | violated | Known refusal: not GitHub, not empty, missing appoint inventory, a malformed --repositories choice or one that differs from the stored inventory without --replace-inventory, the supplier tree, uncommitted changes in the appoint tree, or a CLOSSYS_OWNER that disagrees with the origin owner. | | 2 | indeterminate | Missing gh, an Advisor or Integrator registry version that could not be read when creating or appointing, or an owner that could not be inferred. |

launcher-apply-plan has its own exit codes, described in Applying an approved plan and Taking the registry snapshot.

launcher-check grades a captured observation JSON through planWorkspace and does not create a hub. Same ternary: 0 is a create/resume/adopt plan, 1 is a known refusal, 2 could not run or could not decide. Appoint grades as a plan only when the observation already records a populated inventory; --inventory is a live CLI flag, not a check-cli input.

API

| Export | Description | | --- | --- | | planWorkspace() | Decides create, resume, or adopt from a cwd observation. Optional PlanWorkspaceOptions: { repositories, replaceInventory } (--repositories / --replace-inventory; appoint or resume) or { inventoryPath } (--inventory; appoint only) are the ways to appoint without a populated on-disk inventory. A plan carrying repositories holds the resulting ChosenInventory: the exact document to write, or unchanged. An appoint plan that merges --inventory into a populated stored inventory carries mergedInventoryRepositories, the merged entries, each kept whole, and mergedInventoryDocument, the exact merged document those entries render to, which apply writes. | | applyWorkspacePlan() | Copies the in-package skeleton or hub marker through a host port and returns a WorkspaceApplyResult with health. Composes the skill voices (with the shared conversation contract injected) on the hub only, writes nothing into an inventoried checkout beside it, and reports each inventoried repository other than the hub under skillComposition.siblings; refreshes stale hub guidance and the generated clossys/ README on every path, including resume; migrates a legacy .clossys/ hub state automatically. Optional { skillCatalogueRoot, launcherPackageRoot, contractPath, liveLauncherVersion } selects where skill and contract bodies are read and grades skill-manifest staleness. | | observeWorkspace() | Reads gh, git remotes, cwd, inventory classification, hub-state migration status, and the public Advisor and Integrator versions (npm view <package> version). | | readInventoryRepositories() | Reads repository ids from an inventory file, as bytes, routed through validateInventoryDocument() (a missing file reads as no ids; anything present but invalid throws, naming the offending field by position -- never silently accepted or silently emptied). An optional fourth argument, the hub's owner, makes a bare id and <owner>/<id> one repository. | | readLiveLauncherVersion() | Reads the public @clossys/launcher registry version, used only to grade catalogue-sourced skill staleness. | | launcherPackageRootFromModule() | Resolves this package's root from import.meta.url so apply can find the packed skill catalogue and contract. | | parseGitHubRemote() | Parses a github.com remote and rejects any other host. | | isHubDocument() | Type guard for the generated hub marker (packed template: skeleton/clossys/.state/workspace.json). | | inspectInventory() | Classifies an inventory document -- pass the file's bytes, and the hub's owner when known -- as missing, empty, populated, or invalid (malformed or schema-mismatched -- never silently folded into empty; see validateInventoryDocument()). | | validateInventoryDocument() | Strictly validates an inventory document -- a file's exact bytes, or text built in memory -- against docs/contracts/repository-inventory.json (in the public repository; that exact path does not ship in this package, but this package's build packs and ships its own copy of the contract; schemaVersion: 1, a repositories array of { id, packages? } entries -- id a bare repository name or owner/name in the same format Launcher's own sibling/clone resolution requires, case-insensitively unique; packages, when present, shaped exactly as @clossys/integrator's InventoryPackageEntry, no other key). It is read as strict JSON (a syntax error is reported by position only; a key repeated in any object, bytes that are not valid UTF-8, a leading byte order mark, or a lone surrogate, escaped or raw, is refused) and checked by the same shared contract checker, packed from @clossys/advisor, that checks the plan and the brief. With InventoryReadOptions { hubOwner }, a bare id and <hubOwner>/<id> are also one repository, and a document listing both is refused. Returns { valid: true, ids } or { valid: false, reason }, where reason names every field at fault by position and never quotes a value. Every read and write of an inventory document -- --inventory, --repositories, the on-disk clossys/.state/inventory.json (a hub path, not shipped in this package) on every run, and readInventoryRepositories() -- passes the file's exact bytes, read with WorkspaceHost.readBytes(), never text decoded first, so invalid bytes cannot be silently replaced before the check; an inventory Launcher copies (--inventory, or a legacy .clossys/ inventory it migrates) is written back byte for byte; a document that merely resembles an inventory (for example a governance record whose entries also carry role, visibility, status, notes) is refused, never adopted or silently read as though it validated (#1334). | | reportHubHealth() | Read-only pin, inventory, migration, and skills-manifest report: advisorPin and integratorPin, each engine graded against its own live version (the live Integrator version is the last, optional argument). Does not install or uninstall. | | formatHubHealth() | Human lines plus a health: JSON line for the same report. | | hasAdvisorPin() | True when a manifest already pins Advisor in any dependency bucket. | | checkInventoryEntries() | Read-only inventory id validation through gh repo view (batched; skips with a note when gh is unavailable). | | DEFAULT_REPOSITORY_NAME | Default hub repository name (workspace): the repository an empty-directory run creates, or clones when it already exists. | | CLOSSYS_DIR_REL | Relative path of the one visible per-repository Clossys folder (clossys). | | STATE_DIR_REL | Relative path of the machine-state folder (clossys/.state). | | WORKSPACE_MARKER_REL | Relative path of the hub marker. | | WORKSPACE_INVENTORY_REL | Relative path of the hub inventory. | | CLOSSYS_README_REL | Relative path of the generated index README at the root of clossys/. | | LEGACY_STATE_DIR_REL / LEGACY_WORKSPACE_MARKER_REL / LEGACY_WORKSPACE_INVENTORY_REL | Pre-#1171 .clossys/ paths, kept only so resume can detect and migrate them. | | CommandResult / ChosenInventory / CwdObservation / DependencyBucket / EngineInstallFinding / EnginePinChange / HubDocument / HubEnginePin / HubHealthReport / HubMigrationState / InventoryObservation / InventoryValidationEntry / InventoryValidationReport / InventoryReadOptions / PinFinding / PinGrade / PlanWorkspaceOptions / SkillManifestDocument / SkillManifestEntry / SkillsManifestSummary / ApplyWorkspaceOptions / WorkspaceApplyResult / WorkspaceDecision / WorkspaceHost / WorkspaceObservation / WorkspacePlan / WorkspaceRefusal / WorkspaceState | Typed host, observation, plan, health, and outcome contracts. A WorkspaceHost reads and writes text and, for inventory files, exact bytes (readBytes() / writeBytes()). | | cloneMissingInventoryRepositories() | Explicit, approved action (#1179): clones every inventoried repository of the hub's account that is not cloned beside the hub, and only those; a checkout already beside the hub is left out of the outcomes, and every other inventoried repository is reported as skipped-other-reason, never attempted. Returns a CloneMissingOutcome[]. | | runDoctorChecks() | Read-only prerequisite checks in fix-in-this-order sequence: git, gh, signed in, Node.js, npm, then the advisory coding-agent step. Returns a DoctorReport. | | renderDoctorReport() | Renders a DoctorReport one step at a time, the way launcher-doctor prints it. | | checkCloudSessionBootstrap() | Read-only: the three product-repository-layout.json cloud-session-bootstrap checks against a directory. Returns a CloudBootstrapReport. | | reportInventoryDrift() | Compares a declared external inventory against the launcher-written one; reports external-only, launcher-only, and agreeing repositories as a count plus each one's position, never its id (externalInventory[<i>] into the declared external document, repositories[<j>] into the hub's own stored inventory) -- both are document content, and this whole report is JSON-dumped into the apply message's health: line on every resume of a hub that declares externalInventory. Both files are read as bytes by the strict reader. An optional fifth argument, the hub's owner, compares ids as every other Launcher comparison does (a bare id is that owner's; case is ignored). The hub's own inventory is read with validateInventoryDocument(): a missing one lists nothing, and one that is present but invalid makes the report indeterminate, never a comparison against an empty list. Returns an InventoryDriftReport. | | detectLinkedHosts() | Read-only: which of claude-code, cursor, codex can currently discover skills in a directory. | | serializeHostRecord() / parseHostRecord() | Round-trip clossys/.state/hosts.json (HOSTS_REL). | | parsePreferences() | Reads clossys/preferences.json's budget stance; defaults to "balanced" on absence or malformed input. | | readHostModelProfile() | Reads a packed model-profiles/<host>.json; returns undefined, never throws, on a missing or malformed file. | | resolveModelForTier() | Resolves a tier and budget preference to one model name for a host, reporting belowFloor rather than silently substituting a weaker tier's model. | | validateAdvisorPlan() / validateEngagementBrief() | Validation of clossys/advisor/plan.json and clossys/brief.json (with its context snapshot) against the shared plan and brief contracts Advisor also validates against, including the contracts' code rules (R1-R11 for a plan, B1-B2 for a brief). Unknown fields are refused; the reason names every field at fault. | | approvedSubject() | What an approval binds: the subjectDigest of the plan's latest decision (by timestamp) when that decision has chosen === "approved", else null. null when the approval has no subjectDigest, when decisions at the latest time disagree or name different subjects, when any decision time does not parse, or when the plan does not validate. Anything that applies a plan must use this; the one stated exception is the legacy brief-only path (applyEngagementBrief() and launcher-apply-plan), which predates the binding and uses isPlanApproved(). | | isPlanApproved() | True only when a plan's most recent decision (by timestamp) has chosen === "approved". False when decisions at that latest time disagree, or when any decision time does not parse. It binds no bytes: it ignores subjectDigest, so it is also true for an approval that names no change. | | applyEngagementBrief() | Writes clossys/brief.json into a repository directory once the plan validates and is approved and the brief validates; refuses and writes nothing otherwise. Reports the plan's canonical digest. | | planDigest() / canonicalJson() / canonicalDigest() / PLAN_DIGEST_EXCLUDED_FIELDS | The canonical plan digest: sha256: over the RFC 8785 canonical JSON of the plan without asOf and decisions. Identical to Advisor's for every plan. canonicalDigest() is the shared step: sha256: over the canonical JSON of any value, which the plan, change-set and bundle digests all use. | | planApplyBundle() | The pure apply planner: from a validated plan, the hub brief, observations of each staffed repository's default branch (including the exact bytes of its installed-state ledger and its composed-skill manifest), the change sets the hub holds, the composed skill text, the producer version and the hub's Advisor and Integrator pins, computes one change set per staffed repository (a setup set for a repository in the setup phase, an apply set otherwise) and returns a report-mode bundle. Trusts a repository's ledger only through held change sets, or skips the repository with the trust rule as its reason (ledger-unreadable, identity, renamed, ledger-chain, ledger-foreign-row); computes each owned path and key by compare-and-swap against the trusted ledger (add, keep, update, or a refusal: unowned-existing, client-edited, deleted), reported under V8; composes the Advisor voice with the staffed roles' voices; skips a repository, indeterminate and outside the bundle digest, when a setup set cannot be computed safely (package-manager-unsupported, starter-pin-absent, starter-pin-unsupported, release-age-text-absent, starter-request-invalid), when an apply set would change the Starter pin its request names (starter-request-stale), one whose Controller profile needs root entries added when the observation omits the profile text (root-entry-edit-unbuilt), one whose owned package the lockfile no longer resolves to the recorded version and integrity (integrity-mismatch, violated), one whose ledger holds only some of a setup template's files (template-rows-partial), and one whose lockfile or ledger path has a case variant among the observed files (case-variant-path). Two or more observed files at the same path, compared case-insensitively, have no single base digest between them, so that path is never kept, updated or adopted. Refuses, by throwing before computing anything, a staffed role that is not a lowercase id token (role-not-an-id) and a planItem that is not its repository id, a colon and its package name (plan-item-not-derived). Reads no file, network, process or clock; the same inputs give the same bytes. Throws, naming positions and never values, on inputs it cannot plan from. | | AGENTS_GUIDE_PATH / AGENTS_GUIDE_TEXT / CLOSSYS_SKILL_PATTERNS / verifyAgentsGuide() | The Launcher-owned guide, the AGENTS.md file inside clossys/: its path, its one constant text, the three clossys-* skill path patterns the text carves out, and a check that is true only for exactly those bytes. The text holds no plan text, repository name or client detail, and no marker sits inside it. | | trustInstalledLedger() / reconcileWholeFile() | Whether a repository's ledger bytes may be trusted: exactly canonical and valid (ledger-unreadable), for the observed node id (identity) and id, compared exactly -- a difference in letter case alone is still refused (renamed) -- every generation a held, valid change set whose digest recomputes and that agrees with its history entry (ledger-chain), and every row a write of the set it names (ledger-foreign-row); a refusal carries the rule only. reconcileWholeFile() is the compare-and-swap table for one whole file, with the generation-0 adoption pass allowed only in a setup set; clossys/.state/skills.json is adopted through that pass only when its base already holds the exact bytes the set would write, never merely because a skills manifest was read. Pure. Types: LedgerTrust, LedgerTrustRule, WholeFileState, WholeFileOutcome. | | projectEngagementBrief() / serializeEngagementBrief() / PUBLIC_PROBLEM_PLACEHOLDER | One repository's brief: the hub brief with staffedHere set to that repository's roles in plan order, and problem replaced by the brief contract's fixed placeholder unless the repository is private, members in the brief contract's order at every depth; and the exact bytes written for it (two-space JSON and a final newline). | | changeSetDigest() / changeSetDigestSubject() / CHANGE_SET_DIGEST_EXCLUDED_FIELDS / DERIVED_FILE_DIGEST_FIELDS | The change-set digest: canonicalDigest() of the change set without changeSetDigest, branch, bundle, pullRequest, inverse, tooling and texts, with each derived file reduced to path, mode, derived, item and invariants. | | bundleDigest() | The bundle digest an approval binds: canonicalDigest() of the plan digest and, sorted by id, the id and change-set digest of each repository that has a change set. Nothing else. | | validateRepositoryChangeSet() / validateApplyBundle() | Validation of a change set and a bundle against the shared change-set and bundle contracts, including their code rules (C1-C16 for a change set: references, allow-list and case-insensitive path rules, the ledger, the digest, only the ledger and lockfile derived, canonical order, each item's writes matching it -- discovery links, the skills manifest, the pointer files and the setup templates included -- a pin-starter in devDependencies and at most once, phase, a complete setup set, the release-age exemption's surface and scope, the root entries a Controller profile needs, no skill written through a symbolic link, every refusal at a path or key its item binds, every whole file changed only as its act's write kind allows (none deletes), and each planItem derived from the repository id and package name; A1-A7 for a bundle: unique ids, the digest, verdicts that are the worst of their checks, the authorization-mismatch and authorization-absent checks, no state or binding in a report bundle, and in a planned bundle a state only where all nine checks passed and a binding exactly where V3 passed). Unknown fields are refused; no reason echoes a value. | | wouldViolateRootEntries() / isRootEntryName() | Whether the root names some paths introduce (each path's first segment) would fail a Controller repository profile's closed root vocabulary: satisfied when the profile has no vocabulary Controller checks (schema version 1 or 2, or an empty rootEntries) or declares every name allowed or required; violated, with the undeclared and the prohibited names, sorted; indeterminate (root-vocabulary-unknown) for anything Controller could not read as a root vocabulary. Reads only schemaVersion and rootEntries, by the rules Controller's README states; pure. isRootEntryName() is Controller's rule for one direct-child name. | | validateInstalledLedger() / readInstalledLedger() / ledgerSuccession() / serializeInstalledLedger() / renderInstalledLedger() | The installed-state ledger (clossys/.state/installed.json): validation against the shared ledger contract and its code rules L1-L10 (history, each generation's approval binding, rows naming history, owned paths and link modes, keys matching packages, no act twice, canonical order, root entries in one Controller profile among fixed names, id-token roles in skill paths, and derived planItems); the contract's succession rules for a pull request's head ledger against its base's, each given as its exact bytes, a Uint8Array (both must be exactly canonical; then unchanged, or one next generation keeping the base's history, and an admitted generation installing exactly what the setup deferred and changing nothing else, except that an apply set may add the one guide row, for the AGENTS.md file inside clossys/, with the guide's digest), reporting whether a next generation was proved admitted or only claims an approval (approval-claimed); the exact bytes of a valid ledger; a strict read of a ledger from its bytes, also a Uint8Array (null unless it is a Uint8Array, valid and exactly canonical -- decoded with the same strict, BOM- and invalid-UTF-8-refusing reader the plan and brief contracts use, never a caller's own decode); and the bytes of the next generation a change set writes over the previous ledger, under the contract's RENDER section (each deferred row takes its identity from the plan's package acts, LedgerPackageIdentity), refusing a set computed from another generation or for another repository, and an apply set that would adopt a file. A valid ledger is well formed, not trusted: see trustInstalledLedger(). | | storeChangeSet() / storeApplyBundle() / readStoredChangeSet() / readStoredApplyBundle() / CHANGE_SET_STORE_REL / BUNDLE_STORE_REL | The hub's content-addressed stores under clossys/.state/apply/change-sets/ and clossys/.state/apply/bundles/, one file per digest named by its 64 hex digits: a write validates first and does nothing when the file already holds the same bytes; a change set is append-only and different bytes under its name are refused, while a bundle's digest excludes its authorization, clock and verdicts, so storing a bundle under a digest that already names a file atomically replaces that one file with the newest computation (superseded computations are not recorded); a read is strict, validated, and returns the document only when its recomputed digest matches its name, else null. A digest argument that is not sha256: and 64 lowercase hex digits is refused before any path is built. A stored file's recomputed digest proves its integrity, not its provenance: anyone who can write the hub directory can add a set that verifies. Every store directory segment down to change-sets/ or bundles/ must be a real directory; a symbolic link anywhere in that chain is refused rather than followed, and a filesystem error other than a missing directory or file is rethrown naming only the operation and its error code, never a path. | | CloneMissingOutcome / DoctorCheckHost / DoctorReport / DoctorStepId / DoctorStepResult / CloudBootstrapCheck / CloudBootstrapReport / ExternalInventoryDeclaration / InventoryDriftReport / DiscoveredHost / HostRecord / BudgetPreference / HostModelProfile / HostTierMapping / ModelResolution / PreferencesDocument / ReasoningTier / SupportedHost / AdvisorPlan / ApplyBriefResult / BlockerKind / EngagementBrief / EngagementBriefRole / EngagementContext / EngagementContextField / EngagementContextFieldId / GoalDirection / PlanBlocker / PlanDecision / PlanKit / PlanPackageAct / PlanStaffing / ValidationResult / PlanApplyBundleInputs / PlanApplyBundleResult / RepositoryObservation / SkippedRepositoryObservation / BundleDigestEntry / ApplyBundle / ApplyBundleRepository / ApplyCheck / ApplyCheckId / ChangeSetDeferral / ChangeSetItem / ChangeSetPhase / ChangeSetRefusal / CheckVerdict / ContentDigest / DependencyPlacement / DerivedFileChange / FileChange / KeyChange / LedgerInvariant / LockfileName / PackageInvariant / PackageManagerKind / PinnedPackage / RefusalReason / ReleaseAgeSurfaceKind / RepositoryChangeSet / RepositoryVisibility / WholeFileChange / ApprovalBinding / DiscoveryRoot / ExemptionSurfaceKind / WriteRecordSource / InstalledLedger / LedgerSuccession / LedgerViolation / RepositoryProfileObservation / RootEntryDeclaration / RootEntriesVerdict | Typed contracts for the sections above. |

Doctor

launcher-doctor (installed alongside @clossys/launcher) is read-only and never writes anything (#1220). It checks the prerequisites a client needs before the hub even exists -- git, the GitHub command-line tool, whether you are signed in, Node.js, and npm -- and reports the first thing that is missing, in plain language, with the one next action to take. Run it again after fixing that one thing; it always reports the next thing, never a dump of everything at once. A missing coding agent is reported but never blocks the verdict: runDoctorChecks() marks it advisory, since Launcher cannot detect every host and it is a one-time choice, not a step to fix in sequence. renderDoctorReport() renders the report the way the CLI prints it.

Product repositories

docs/contracts/product-repository-layout.json (this repository's own contract; it does not ship in the published package) extends the account hub's clossys/ layout to a product repository: apps/*, workspace wiring so @clossys/* packages install as exact pinned versions, AGENTS.md / CLAUDE.md pointers, and the CI Starter proof (#1215). A cloud agent session (browser plus GitHub, no local setup) can pick up a product repository too -- checkCloudSessionBootstrap() verifies exactly what that session needs: a resolvable package.json plus package-lock.json pair, an AGENTS.md that mentions clossys/, and a hub marker at the same relative path as the packed template skeleton/clossys/.state/workspace.json. It never runs npm ci itself and never mutates anything; it only reports which of the three is missing. A launcher run in the hub writes nothing into a product repository, so until the repository is staffed in an approved plan and that plan's setup pull request merges, an unsatisfied agents-pointer check is the expected state; its note says so.

Setup templates

renderSetupTemplate() and the renderers beside it are pure functions that return the exact bytes of the files a setup change writes. Nothing writes those bytes yet; a later step plans them into a change set. The renderers behind it are exported too: renderStarterRequest(), renderAdoptionDecisionWorkflow(), renderProductCiWorkflow(), renderAdoptionEvidenceWorkflow(), renderSnapshotCollector(), renderPathScopeWorkflow() and renderPathScopeScript(), the standalone script the path-scope workflow embeds. A TemplateResult is either { ok: true, files }, a list of TemplateFile entries (path and bytes), or { ok: false, refusal } with a TemplateRefusal; the package manager is a SetupPackageManager and the Starter pin a StarterPinInput.

There are four acts, each with a fixed file list that renderSetupTemplate() returns in this order:

  • add-caller-workflow takes { packageManager } and returns .github/workflows/clossys-adoption-evidence.yml, .github/workflows/clossys-adoption-decision.yml and .github/scripts/clossys-collect-adoption-snapshot.mjs.
  • write-starter-request takes a StarterRequestInput and returns .starter/request.json.
  • add-ci-template takes no input and returns .github/workflows/clossys-ci.yml.
  • add-path-scope-job takes no input and returns .github/workflows/clossys-path-scope.yml.

The request is in the admission phase and names the Starter as both its own engine and its target, and it names both evidence paths, the assessment file and the target-input file. It carries no advisor and no hub. The Starter pin must be an exact version in >=0.2.0 <0.4.0 (STARTER_PIN_RANGE); any other pin is refused as starter-pin-unsupported, and a package manager other than npm or pnpm, Yarn included, is refused as package-manager-unsupported. A refusal names a position such as starter.version and never quotes the value it refused.

The decision workflow starts only on workflow_run completion of the evidence workflow, and its job carries no condition, so it starts for every conclusion. It checks out the protected pull request base, runs one fixed frozen install (npm ci --ignore-scripts or pnpm install --frozen-lockfile --ignore-scripts), and runs the installed Starter's admit command over a sparse checkout of the workflow_run head that holds only /clossys/.state/installed.json. The trusted decision job never reads or trusts the snapshot artifact the evidence workflow uploads, because a pull request controls that workflow. The collector script is still written because the contract's file set for the caller workflows names it.

The path-scope job applies to pull requests whose head branch starts with clossys/apply-, and fails when a changed path is outside the paths Clossys may own (OWNED_PATH_PATTERNS) or, apart from the ledger, package.json and the lockfiles, is not named by the pull request's own ledger. It runs in the pull request's own context, so it catches an agent's mistakes, not a hostile author; the admission job runs from the protected base.

A change to any of these workflows, or to .starter/request.json, is proved only by the first pull request after it merges, because the decision runs from the base: a one-merge lag.

Inventory: adopting an existing source

When an account already keeps a repository inventory in its own control plane, launcher reads it as the source of truth instead of writing a second, diverging one (#1216). Declare it by hand-editing the hub marker (the packed template skeleton/clossys/.state/workspace.json; the generated path does not ship) to add an externalInventory: { path, shape } field (a "foundry" or "custom" shape). Every launcher run (create, resume, or appoint) then calls reportInventoryDrift() automatically and prints the result in hub health output when the marker declares one: three sets, all three even when one is empty -- ids only in the external source, ids only in launcher's own inventory, and ids both agree on. A "custom" shape is reported indeterminate rather than guessed at -- launcher has no mapping for a non-foundry inventory shape yet. It never merges the two silently; writing the reconciled set is left as a separate, explicit apply step for a follow-up.

Hosts

Every launcher run (create, resume, or appoint) records which coding-agent hosts a directory could already discover skills through, before that run composes skills and stamps every host's discovery path (#1180): claude-code and cursor are detected by their own discovery symlink (.claude/skills, .cursor/skills); codex is detected by the presence of .agents/skills itself, since Codex reads repository skills from that path directly and needs no separate discovery link (verified against developers.openai.com/codex/skills, 2026-09-22). The snapshot is written to clossys/.state/hosts.json (HOSTS_REL, via serializeHostRecord() / parseHostRecord()) for the hub, the one checkout a launcher run composes skills into -- so a consumer such as Advisor's next-action phrasing can name the client's actual tool instead of guessing.

Model guidance

Packages declare what a step demands -- a reasoning tier (light / standard / deep) -- never a model name (#1219). Launcher ships the tier-to-model mapping per host in model-profiles/<host>.json, dated and re-verified against that host's real current models; readHostModelProfile() reads one. parsePreferences() reads clossys/preferences.json's budget stance (cost-conscious / balanced / max-quality, defaulting to balanced when absent or malformed) -- Advisor asks the question and writes the file; this package only resolves against it. resolveModelForTier() combines a profile, a tier, and a preference into one ModelResolution, and reports belowFloor rather than silently substituting a weaker model when a caller-supplied hard floor tier cannot be met.

Applying an approved plan

launcher-apply-plan --plan <plan.json> --brief <brief.json> --repo <dir> writes clossys/brief.json into a staffed repository once the plan is approved (#1178). validateAdvisorPlan() and validateEngagementBrief() check both files against the shared contracts in this repository's docs/contracts/ -- advisor-plan.json, engagement-brief.json, and engagement-context.json for the brief's context snapshot (#1475). This package's build packs those files, with a copy of the one contract checker @clossys/advisor uses, so Launcher and Advisor accept exactly the same plans and briefs while Launcher keeps no runtime dependency on Advisor. Every object is closed: a field the contracts do not declare is refused, and a known context value must be one of that field's fixed choice ids, because the brief is committed in every staffed repository. A string or key containing a lone surrogate is refused, so every plan that validates has a digest. Every plan time must be a real calendar date or date-time in ISO 8601 form, a date-time with Z or a ±hh:mm offset, checked field by field (so 2026-02-30 or T24:30 is refused). A brief's problem, role, why and metric must contain a non-whitespace character, and an item of inputsFrom, outputsTo, sequence or deliverables must not be empty. A refusal names each declared field at fault and never echoes its value, and never names a key the contracts do not declare: such a field is reported at the object that holds it, by its 1-based position there (plan.mandate has a field the contract does not declare (key 4 of this object), and unknown fields are refused), counted in the order the file wrote the keys when launcher-apply-plan reads it; a value a caller passes to a validator directly is counted in JavaScript's own key order, which lists array-index keys such as "7" first. launcher-apply-plan reads both files as strict JSON: bytes that are not valid UTF-8, a leading byte order mark, or an object that repeats a key at any depth exit 2, with a repeated key reported by its position in its object and, below the top level, that object's position, never by name, and a syntax error by position only, never quoting the file's text, so the value validated is exactly the one a reader of the file sees. A position is a 0-based index into the decoded text in UTF-16 code units (JavaScript's string index): the byte offset for ASCII text, with a character outside the Basic Multilingual Plane counting as two. A plan may say which roles work in which repository (staffing, by repository inventory id), which kits were recommended (kits), which exact package acts are authorized (packages, each one exact version and one sha512- integrity value) and where those versions were resolved from (resolution) (#1178). Once the schema passes, the contract's code rules run: no repository is staffed twice (ids compare case-insensitively), every staffed role is one of the mandate's roles and every mandate role is staffed somewhere unless it is a hub-only role, every package act names a staffed repository spelled exactly the same, no planItem repeats, no package appears twice in one repository, resolution is present exactly when packages is, no kit id repeats, no role repeats within one staffing entry, no role is named twice in mandate.roles, a repository has at most one pin-starter act, always placed in devDependencies, no hub-only role is staffed, and every planItem is exactly its act's repository, a colon and its name, in the same letter case (R12). The hub-only roles, Advisor and Integrator, are read from the plan contract's definitions.hubOnlyRoles, the same data Advisor reads. The rules read only a document's own fields, as the schema does, so an inherited one is ignored. A brief's optional staffedHere must name only the brief's own roles, each once. This package implements those rules separately from Advisor, and both are tested against the same corpus, docs/contracts/advisor-plan-rules.fixture.json (in the public repository, not shipped in this package). approvedSubject() says what an approval binds: the subjectDigest of the plan's most recent decision (by timestamp, not array position) when it is "approved". It validates the plan itself first and returns null for one that does not validate. An approval with no subjectDigest binds nothing, and so does no decision at all, a decision time that does not parse, or a tie at the latest instant between decisions that disagree or name different subjects. It says what was approved, not that it matches: a caller must recompute the digest of the change it holds and compare. isPlanApproved() reads the same most recent decision and is true when it is "approved", with the same rules for ties and unreadable times, but it binds no bytes: it ignores subjectDigest, so it is also true for an approval that names no change. Anything that applies a plan must use approvedSubject() instead. applyEngagementBrief() and launcher-apply-plan are the brief-only path, which predates that binding and is kept as it was, not extended: they require isPlanApproved() and accept an approval with or without a subjectDigest, checking no binding. applyEngagementBrief() refuses, and writes nothing, unless all three checks pass and the plan's digest is computed; only then does it write the brief byte-identically -- it never re-authors its prose -- and reports planDigest() of the plan it applied, which the CLI prints as plan digest sha256:.... That digest is defined once, in docs/contracts/advisor-plan-digest.md (in the public repository, not shipped in this package); this package and Advisor each implement it and are tested against the same fixture corpus. On this brief-only path, this package does not compute the brief's content -- it writes the brief it is given, which @clossys/advisor's toEngagementBrief() builds, and applies no per-repository projection -- and it does not decide whether a plan should be approved (that is Advisor's job); it only validates the two shapes and writes the one file. The apply planner below is different: it projects each repository's brief from the hub brief itself.

Launcher's packed skill carries the agent procedure that puts a stored change set into a staffed repository, under "Apply an approved plan" (#1762). The agent verifies, files the task-record issue, prints the pull request body with launcher-apply-plan body, commits and pushes only the set's clossys/apply- branch, opens the pull request, reads launcher-apply-plan status, and reports. Launcher never pushes, opens a pull request, files an issue or merges, and the procedure forbids merging and enabling auto-merge. check-package-skills pins each of those rules in the packed skill text.

Release-age exemption

editReleaseAgeExemption() computes the edit that lists the publishing scope's <scope>/* entry as exempt from a package manager's release-age delay (#1178). It is pure: it does no I/O. It takes a surface (pnpm-workspace or yarnrc), the surface file's text or null, and, for pnpm, the .npmrc text or null. It returns edited with the exact new text, unchanged when the scope entry is already listed, or a refusal (ReleaseAgeEdit, with ReleaseAgeEditInput and ReleaseAgeEditRefusalReason).

The entry goes under minimumReleaseAgeExclude (pnpm, single-quoted) or npmPreapprovedPackages (Yarn, double-quoted). A missing file becomes the key alone, a top-level block sequence of scalars gets one new entry after its last item, and a file without the key gets the key appended. Other bytes, including comments and the final newline, are kept. The function reads only the shapes it recognises: release-age-surface-unparseable covers a flow sequence, an anchor, an alias, a tag, a comment inside the list, several documents, a tab, a carriage return or byte order mark, a repeated key, and a value that is not a block sequence of scalars.

For pnpm the .npmrc is read under a fixed grammar and refused otherwise. Every line must be blank, a comment (first non-space character # or ;), or a plain key=value assignment, optionally spaced around the =, whose base key is only ASCII letters, digits and @ : _ . / -. An unrelated base key may have one immediate empty [] suffix. An .npmrc containing any line outside those shapes (a quoted key, an indexed, repeated, spaced or malformed bracket suffix, a comment or escape inside the key, a tab, a section header, a key with no =) is refused as release-age-surface-unparseable, because npm's ini reader could read such a line as the exclusion setting. A base key that is userconfig, globalconfig or prefix (in any case, with - and _ ignored), including its array form, is refused the same way, because it points npm at another config file or prefix directory whose own exclusion list the editor cannot read. A base key that is minimum-release-age-exclude in any case, with - and _ ignored (so the camel-case spelling too), including its array form, is refused as release-age-surface-conflict. Refusing is the default: an unrelated .npmrc line the grammar does not list also refuses the whole file, and the caller resolves the file by hand.

verifyReleaseAgeExemption() takes the surface, the text before, the text after, and, for pnpm, the .npmrc text. It reports a ReleaseAgeVerdict, { verified: true, value } (its input is a ReleaseAgeVerifyInput), only when the two texts differ by that one added entry, read again with the same rules; value is the <scope>/* string the installed-state ledger's entries row holds. It returns { verified: false } for an unchanged file (before equal to after) and for any .npmrc that is a conflict or outside the grammar above, so a wiring unit must not verify after an unchanged or refused result. It says nothing about whether a given pnpm or Yarn version honours the key; that is proved separately with pinned tools.

Computing each repository's change

planApplyBundle() computes, for each repository a plan staffs, the change set one pull request would make there, and a bundle that holds them (#1178). It is pure: it takes the plan, the hub brief (clossys/advisor/brief.json), what the caller observed on each repository's default branch (including the exact bytes of its installed-state ledger and its composed-skill manifest), the change sets the hub holds, the composed skill text for each role and for Advisor, this package's version and the hub's Advisor and Integrator pins, and it reads nothing itself. The shapes are the shared contracts docs/contracts/repository-change-set.json and apply-bundle.json, packed into this package, and every set and the bundle are validated against them, code rules included, before they are returned. The digests are defined in docs/contracts/apply-change-set-digest.md, with a corpus computed independently of this package (both in the public repository, not shipped in this package).

  • Each set describes the repository's brief, projected from the hub brief with staffedHere set to its roles and, unless the repository is private, the br