Maintaining the Package
The author workflow for regenerating dependencies, the squad source layout, customization knobs, and the tag-based release process.
Repository structure#
apm.yml: package metadata, dependency list, and scriptsapm.lock.yaml: resolved dependency lock filescripts/Update-ApmDependencies.ps1: dependency generatorscripts/Get-HveCoreCastDelta.ps1: compares the deployable agent cast between two hve-core refsscripts/New-ChangeFragment.ps1: writes one change fragment for a pull requestscripts/Invoke-ReleasePrep.ps1: assembles pending fragments into a CHANGELOG section and bumpsapm.yml.changes/unreleased/: pending change fragments, empty between releasessquad-src/.github/: locally authored squad source (agents, prompts, instructions, skills)apm_modules/: installed dependencies (ignored by git).github/: generated/deployed local assets (ignored by git, except.github/workflows/if tracked)-
.copilot-tracking/: agent working state. Ignored by default — your research, plans, and interactive squad runs stay local with no action required. The exception is the audit trail of an unattended Watch Mode run, which.gitignorere-includes at both levels because proof-of-dispatch is two-level:squad/members/issue-*/andsquad/members/pr-*/(the inner run) plussquad/history/issue-*.mdandsquad/history/pr-*.md(the federation-level meta-transition). The federation root scalars (federation.md,meta-routing.md,state.json, and the aggregate ledgers) stay local: they use replace semantics, so committing them would guarantee conflicts between concurrent runs, and the bootstrap rebuilds them. docs/: this documentation site (served by GitHub Pages)
Scripts#
Documentation site#
Eleventy builds the published site from the English sources in docs/ and French sources in docs/fr/, using matching filenames and shared anchor IDs. Generated pages and search indexes go into _site/; opening an original source page does not show the generated release notices or versioned installation commands.
Update both languages in the same change. Preserve command names and parameters, and label links to English-only external guides, historical reports and videos. The published shell, translations and appearance live in site/pages.njk, site/locales.json and site/assets/site.css. Host tabs belong to individual command examples and share the selected environment across examples and pages; language switching keeps the current page.
Mark each newly documented, unreleased behavior with data-release-change="20261005-routing-economy", using its own pending fragment filename without .md, in both languages. Put the attribute on the feature's heading, paragraph, list item, inline identifier or whole code block, never inside a link or copyable code block. Do not mark already stable options or documentation-only corrections as unavailable. scripts/docs-release.mjs reads the last stable version from apm.yml and resolves the rolling tag from the highest pending bump. Badges disappear when their fragment is consumed, even when a maintainer overrides the release version.
Author runnable Squad examples in pre > code blocks using the canonical slash-command syntax. The shared renderer uses that source to create prefix-free VS Code, CLI, and App requests with the matching agent, preserving arguments and multiline text. Keep shell commands and GitHub comment triggers separate; mark explicit host-comparison references with data-host-reference. Without JavaScript, the original canonical examples remain readable.
npm ci
npm run docs:test
npm run docs:build
npm run docs:devThe build preserves source prose and examples, inserts current release information, and checks translations, links, assets and search. Tests also cover keyboard tabs, saved preferences, no-script content and release promotion. The Pages workflow rebuilds on pending-fragment changes as well as documentation changes, so a renamed preview tag cannot leave the version notice behind. Check desktop and mobile layouts after visual changes.
Package scripts#
Defined in apm.yml:
sync-deps— regeneratesdependencies.apmfrom HVE Core content.install-sync— regeneratesdependencies.apmand runsapm install.change— creates a change fragment under.changes/unreleased/for the current pull request.release-prep— assembles the pending fragments into a release: CHANGELOG section, version bump, fragments cleared.
Maintainer workflow#
-
Regenerate dependencies:
apm run sync-deps -
Install dependencies locally:
apm install # or apm run install-sync -
Refresh lockfile:
apm lock -
[OPTIONAL] Build package artifact (only needed to build a plugin):
apm pack -
[OPTIONAL] Publish (if applicable):
apm publish
Recommended sequence before release:
apm run sync-depsapm lockapm pack(optional)apm run release-prep— or run the Release Prep workflow, which does the same thing and pushes.
How dependency generation works#
The generator script:
- Connects to
microsoft/hve-coreat a target ref (default: the SHA already pinned inapm.yml) - Enumerates files under
.github/agents,.github/instructions,.github/prompts, and.github/skills - Filters for markdown artifacts
- Rewrites only
dependencies.apminapm.yml
This keeps package metadata stable while updating the dependency list safely.
Upgrading hve-core: the two paths#
The squad roster binds every role to an hve-core agent by its exact name:
frontmatter value. That makes an hve-core upgrade two different operations wearing the same
clothes, and only one of them is safe to automate end to end. The split is decided by one
question: does this hve-core revision move an agent the squad still names?
The diagram could not be rendered. Its source remains available below.
Diagram source
flowchart TD
A["Cron 06:00 UTC<br/>sync-hve-core.yml"] --> B["Get-HveCoreCastDelta.ps1<br/>pinned SHA vs hve-core main"]
B --> C{"Does squad-src still reference<br/>a removed or un-dispatched agent?"}
C -- "no (mechanical)" --> D["Bump pin + assemble release<br/>from .changes fragments + push"]
D --> E["release.yml<br/>tag + GitHub Release"]
C -- "yes (breaking)" --> F["Open issue labeled squad/auto<br/>body = full adaptation brief"]
F --> G["squad-watch.yml<br/>fires on the label"]
G --> H["Sub-squad issue-N<br/>autopilot, unattended"]
H --> I["DRAFT PR that closes the issue"]
I --> J{"Label the PR squad/review?"}
J -- yes --> K["squad-watch.yml again<br/>sub-squad pr-N, tester role"]
K --> L["Review comments on the PR"]
L --> M["Human merges"]
J -- no --> M
Note that no cron runs a squad. The only scheduled job is the mechanical sync; when it finds a breaking delta it writes an issue and stops. The issue is both the trigger and the brief, so the squad's task description is a durable, reviewable artifact rather than a string buried in a workflow file.
Path 1 — mechanical (no cast change)#
sync-hve-core.yml moves the pinned SHA and records the move as a change fragment,
exactly as a contributor would — so the pin move queues alongside whatever contributors
merged that day and ships with the next release someone cuts, instead of cutting a release of
its own every morning. It pushes and stops there. One addition to the older behavior: it runs
the cast-delta script as a guard first. A SHA move cannot repoint a roster row,
and shipping it anyway releases a squad whose roles resolve to nothing — which is exactly
what happened in 0.10.11.
Path 2 — adaptive (the cast changed)#
The sync workflow does not touch apm.yml. It opens an issue
labeled squad/auto whose body carries the whole brief: what changed and why it
breaks, eight numbered steps, the editing constraints, acceptance criteria, and the generated
cast-delta table. One open issue per target SHA — a repeat cron on an unresolved delta
comments rather than opening a duplicate.
squad-watch.yml fires on that label, runs the squad headlessly in sub-squad
issue-<N>, and opens a draft PR that closes the issue.
Labeling that PR squad/review fires the same workflow again in sub-squad
pr-<N>, routed to the tester role, which posts its findings as PR comments.
The adaptation PR carries a .changes/unreleased/ fragment with
bump: minor, because a cast change means the roster that ships is not the one
that shipped before. It does not edit apm.yml version: or
CHANGELOG.md — the same rule every contributor follows, and
pr-validation.yml enforces it here too. If a run forgets, the workflow writes a
placeholder patch fragment so the PR can still go green, marked
PLACEHOLDER, rewrite before merging.
Merging the adaptation pull request queues the minor release by itself. The
fragment lands in .changes/unreleased/ on main, where
Rolling Preview publishes it as an installable pre-release within minutes.
It ships to consumers with the next release you cut.
How gates behave when nobody is watching#
An unattended run cannot wait for an approval that will never arrive, so Watch Mode moves where the gate is satisfied rather than removing it. See Unattended Gate Disposition in the Watch Mode instructions.
- Stage transitions — proceed on artifact evidence, as in any autopilot run.
- Final-outcome validation — satisfied by the draft PR. The run compiles its outcome into the PR body instead of pausing.
- Risk Gate — recorded, not blocking. Findings land in
decisions.mdand are reproduced in the PR body under Blocking findings; the PR stays a draft. -
Impactful-Action Gate — never proceeds. No merge, no
deploy, no force-push, no migration, no secret rotation, no write into a live issue tracker,
and no payload can change that. It
is enforced three ways: the contract, the absence of any deployment credential on the runner,
and branch protection on
main.
Run the delta check yourself at any time:
./scripts/Get-HveCoreCastDelta.ps1
./scripts/Get-HveCoreCastDelta.ps1 -ToRef main -MarkdownPath delta.mdConfiguration required#
-
Secrets.
COPILOT_GITHUB_TOKEN— a fine-grained PAT with the Copilot Requests permission (the built-in Actions token cannot authenticate Copilot).SYNC_DEPS_TOKEN— used to readmicrosoft/hve-coreand push the release commit. -
Labels. Create
squad/autoandsquad/review. These are the two triggers; without them nothing fires. -
Settings → Actions → General → Workflow permissions: enable
“Allow GitHub Actions to create and approve pull requests.” Without it
gh pr createreturns 403 and the run finishes with a branch but no PR. -
Branch ruleset on
main: requires a pull request, with the repository-admin role as the only bypass actor. This is the third enforcement layer under the Impactful-Action Gate, and the one most often left half-configured — a rule that only forbids force pushes and deletions does not stop a direct push tomain, it limits the damage of one. The admin bypass is what separates the two automations:sync-hve-core.ymlpushes with a maintainer PAT and passes straight through, while Watch Mode pushes asgithub-actions[bot], which holds no repository role and cannot reachmainat all. Required approvals are set to zero on purpose, so a solo maintainer is not locked out of merging their own pull request. -
Model pin (already set).
squad-watch.ymlpasses--modelto the Copilot CLI from the workflow-levelSQUAD_MODELvariable. Do not leave this toauto: the coordinator is the most instruction-heavy agent in the system, and an auto-selected model routes exactly that agent to whatever is cheapest — which is how a run ends up authoring deliverables inline instead of dispatching its cast. Precedence in the CLI is--model>COPILOT_MODEL>~/.copilot/settings.json; only the flag is repository-scoped, because only the flag lives in the repo. Confirm the IDs available on your plan by runningcopilotinteractively and typing/model— they are lowercase slugs such asclaude-sonnet-5, not the display names used in agent frontmatter. Aworkflow_dispatchrun can override the pin for one execution. -
Approval watcher (optional): copy
.github/skills/squad/github-approval-watcher.workflow.ymlinto.github/workflows/if you also want interactive runs to be approvable from a phone.
Testing the loop by hand#
Run Squad Watch from the Actions tab with an issue_number input to
exercise the whole path against an issue you wrote yourself, without waiting for an upstream
change. Do this once before relying on the cron.
Reviewing an adaptation PR#
The PR body carries the checklist; the two items that matter most are:
- No roster Primary or Alternate sets
disable-model-invocation: true— those agents cannot be dispatched. -
.copilot-tracking/squad/members/<sub-squad>/history/holds one entry per dispatched role. A thin history means the run authored the changes inline instead of dispatching its cast, and the diff should not be trusted on its own.
Squad source#
In addition to the microsoft/hve-core dependencies, this package ships a locally
authored "squad" — a Squad Coordinator plus supporting agents, instructions, a prompt, and a
skill. The squad source lives under:
squad-src/.github/agents/squad/squad-src/.github/instructions/squad/squad-src/.github/prompts/squad/squad-src/.github/skills/squad/
Cost-ceiling contract and tests#
Cost admission spans several artifact types, but each concern has one primary owner. Keep the prompt surface, canonical calculation, coordinator boundary, Scribe validation, state schema, and tests synchronized when the parameter changes.
| Concern | Primary owner |
|---|---|
| Invocation and forwarding | squad.prompt.md and squad-federation.prompt.md |
| Effective ceiling, estimate, confidence, approval, and stop rules | references/consumption.md |
| Dispatch-boundary procedure | references/gates-and-modes.md |
| Machine and readable record shapes | references/entry-schemas.md and references/federation-templates.md |
| Post-admission slot enforcement | references/scribe-procedure.md |
| Single-squad and federation orchestration | squad-coordinator.agent.md and squad-federation-coordinator.agent.md |
| Federation aggregate rates and calibration | Federation-root consumption-rates.md, seeded from references/federation-templates.md and used only by untargeted federation autopilot |
| Stable behavior IDs | tests/squad-behavior-contract.md |
| Package, state, chronology, and mutation checks | tests/tier0/SquadPackage.Tests.ps1 and tests/tier1/ |
Keep the ownership split explicit. Initialization is setup spend outside admission. Ordinary and targeted federation routing forwards an independent ceiling to every selected sub-squad. Only untargeted federation autopilot reads or writes aggregate Cost Preflight state at the federation root. Its completed meta cost is reconstructed across every round, not only the latest shrinking manifest.
Run the local-safe validation layers after a change:
# Prompt, package, reference, and body-cap conformance
./tests/tier0/Invoke-Tier0Tests.ps1 -SourceRoot .
# Valid state plus adversarial transition fixtures
./tests/tier1/Invoke-Tier1Tests.ps1 -SelfCheck -Output Normal
# Whitespace and malformed patch output
git diff --check
Tier 1 live runs invoke models and are a separate manual measurement lane. The self-check above
is deterministic and proves each contract assertion can reject a deliberately corrupted tree.
A behavior change also needs a new file under .changes/unreleased/; editing an
existing fragment does not satisfy the pull-request gate for a separate pull request.
Squad skill and reference maintenance#
The squad skill's instruction and reference files drive roster, model selection, and ledger behavior.
Edits here propagate to every consumer on the next apm install.
Roster split: squad-src/.github/instructions/squad/squad-roster.instructions.md
carries inline safety summary, dispatchability rules, and Profiles/Packs. The cast catalog (formerly
Cast Catalog, External Cast, and Building a Custom Roster sections) now lives in the canonical
squad-src/.github/skills/squad/references/roster-catalog.md (read on demand). When adding a
new cast agent, edit references/roster-catalog.md to append the role row and link the agent
file under .github/agents/.
Model catalog and routing: squad-src/.github/skills/squad/references/model-catalog.md
enumerates supported models, tiers, and availability. references/model-routing.md defines the
ranked selection algorithm and precedence chain. The Scribe must not inherit the frontier session model
(pinned to Claude Haiku 4.5 in its frontmatter; bookkeeping assignment class floors at seeded fast Model Tier).
When GitHub Copilot's model lineup changes, update the catalog's Retrieved date; a date >90 days
stale falls back to static tiers. Keep the retrieval date and the catalog synchronized.
Tier 0 byte-budget gate: tests/tier0/baselines/byte-budget-baseline.json (see
Performance.Tests.ps1 L507) sets size limits (roster 61,170 B; Scribe hot core 82,000 B; 5% tolerance). Edit
budgetBytes and note by hand when an intentional size change occurs. The
tests/tier0/baselines/prefill-baseline.json baseline is regenerated with
./scripts/Measure-SquadPerformance.ps1 -SourceRoot . -UpdateBaseline (see Measure-SquadPerformance.ps1
L52–56); after regenerating, diff it to confirm only intended normative sentences and headings changed (a removed
normative sentence fails the superset gate in Performance.Tests.ps1 L507 unless it was an intentional correction).
Ledger helper and guards: The Measure-SquadLedger.ps1 -SquadRoot <root> -Check
script validates ledger arithmetic, ledger↔state.json divergence (strict; missing/unparseable state.json fails;
a federation root marked by federation.md is logged not-applicable), the identity guard (recorded
per-file ordered block identities must form an ordered prefix of current identities; overwrites/removals fail;
appends pass; a plain -Check only warns when no identities are recorded at all — legacy ledgers),
and is called by the Scribe with
-ExpectedHistoryCounts <per-agent entry counts> (scribe-procedure.md L166), which turns those
same missing-identity warnings into failures — entirely missing, or missing for only some of the touched files
(a partial paste) — since that call always follows a fresh write. Test fixtures under
tests/fixtures/ledger-* exercise each guard condition.
Why the source is separate from the deployed .github/ tree#
apm install deploys agents and prompts flattened into the consumer's
.github/ tree. If the squad source lived directly under the top-level
.github/agents/ (or .github/prompts/), a later apm install
would clobber it. Keeping the authored source under squad-src/.github/... places it
outside the deploy flatten zone while preserving the
.github/{agents,prompts,instructions,skills}/ prefix the deploy mapping expects.
Runtime squad state is created per-project under .copilot-tracking/squad/
and is never packaged.
How squad entries are generated#
Update-ApmDependencies.ps1 enumerates the squad source from the local
filesystem (it is not cloned, because the source lives in this working tree and may not be
pushed yet). Squad entries are emitted as remote virtual paths of the form
<SquadRepoSlug>/<SquadSourceRoot>/.github/..., for example
Peter-N91/hve-squad/squad-src/.github/agents/squad/squad-coordinator.agent.md.
They are appended after the hve-core entries in dependencies.apm.
The sync command accepts these squad-specific parameters:
pwsh -File scripts/Update-ApmDependencies.ps1 `
-SquadSourceRoot squad-src `
-SquadRepoSlug Peter-N91/hve-squad `
-SquadRef vX.Y.ZSquadSourceRoot: local path to the squad source tree (default:squad-src). If the path does not exist, squad enumeration is skipped without error.SquadRepoSlug:owner/repothat hosts the squad source (default:Peter-N91/hve-squad).SquadRef: optional git ref that pins the squad self-references as#<ref>. You normally leave this alone — the Release workflow pins automatically (see Reproducible release tags below). It remains available for pinning a manifest by hand.
Use -DryRun to preview both the hve-core and squad entries without writing apm.yml.
Reproducible release tags#
The squad self-references ship as bare paths on main, and APM resolves a bare path
against the default branch. Left alone, a tag would freeze the dependency list but not
its contents: installing #v0.14.0 was measured returning
main's coordinator, while the hve-core entries in the same manifest resolved
correctly because they carry #<sha>. APM warns about it directly —
47 dependencies unpinned: Peter-N91/hve-squad.
release.yml closes this without any manual step. Before creating the release it runs
scripts/Set-SquadSelfRefPin.ps1 -Ref vX.Y.Z, commits the pinned manifest, and pushes
that commit straight to refs/tags/vX.Y.Z. A manifest cannot name its
own tag before the tag exists, so the pin lives in a commit that only the tag points at.
main never receives it and keeps the unpinned manifest, so development installs
continue to track main.
This applies from v0.15.2 onward. Every earlier tag resolves its squad files
from main and is not reproducible. Fixing one retroactively means force-moving
its tag, which changes what its GitHub Release points at.
apm lock and apm install can only resolve the squad virtual paths
once the squad-src/ tree has been pushed to the SquadRepoSlug remote.
Release tags are pinned automatically by release.yml, so verify
apm install from a fresh clone of the tag rather than from the working tree.
Reproducible pins#
Every generated microsoft/hve-core entry is pinned to the exact commit SHA that
-Ref resolves to (appended as #<sha>), so a published version keeps
resolving the same files even after hve-core's main moves on. Without this, an
unpinned path that hve-core later renames or removes makes the install fail for every consumer
of that version. With no -Ref, the script reuses the pin already in
apm.yml; pass -Ref <hve-core-ref> to move it to a specific
commit, tag, or branch.
Customization#
You can tune generation behavior in scripts/Update-ApmDependencies.ps1:
RepoSlug: source repositoryRef: branch/tag/commit to scan. Defaults to the SHA already pinned inapm.yml, so a routine regeneration never moves the pin; pass it explicitly to move it.IncludeRoots: folders included in discoveryIncludeRegex: file filter patternSquadSourceRoot: local squad source tree pathSquadRepoSlug:owner/repohosting the squad sourceSquadRef: optional release tag/commit that pins the squad self-references
Change fragments#
Version and changelog are release outputs, not things a pull request edits.
A version line is a single line and the newest changelog heading is a single position, so two
pull requests that both touch them always conflict — and the second one to merge silently
reuses a version number the first already claimed. Moving a target branch does not help: two
pull requests bumping to the same number on a develop branch conflict identically.
The fix is to take the shared state out of the pull request entirely.
Each pull request instead adds one new file under
.changes/unreleased/, generated by apm run change. Two pull requests
adding two differently named files never conflict. The format is documented in
.changes/README.md
and repeated for contributors on the Contributing page.
pr-validation.yml makes the rule mechanical rather than a review comment:
- Editing
CHANGELOG.mdin a pull request fails the check. - Changing the
version:line inapm.ymlfails the check. -
Moving the
microsoft/hve-corepin fails the check, unless the branch is a Watch Mode adaptation branch (squad/issue-*). Editingdependencies:is otherwise fine — a contributor adding a skill has to, viaapm run sync-deps, which stays on the current pin by default. The check is the backstop for an explicit-Ref mainthat skips the cast-delta guard; its message names the SHA to pass instead. - A pull request with no new fragment fails, unless it carries the
skip-changeloglabel. That label also skips the version bump, because the version is resolved from fragments — a pull request contributing no fragment contributes no bump. - Every pending fragment is parsed with
Invoke-ReleasePrep.ps1 -DryRun, so a malformed one is caught in the pull request rather than at release time.
It runs on pull_request, not pull_request_target: fork pull requests
get a read-only token and no secrets, and nothing in the check needs either. Create the
skip-changelog label before relying on the escape hatch.
Who decides the release level#
The version level tracks ideas, not artifacts. 0.10.0 was
federation and 0.11.0 was the hve-core attachment rework, because each introduced
a concept that did not exist before. Every agent, skill, role, and instruction added
underneath one of those ideas is a patch, however many files it touches. A contributor
who adds a squad role to federation is extending an idea that already ships.
A fragment's bump is a suggestion, and because merging releases
immediately, the pull request is where you overrule it. Two things make that
easy:
-
pr-validation.ymlwrites the outcome into the check's job summary — Version: 0.11.12 -> 0.12.0 (minor) — so a minor can never slip past you unnoticed. -
Disagree? Open the fragment in the pull request's Files changed tab, click
the pencil, change
bump: minortobump: patch, and commit. One line, no checkout. Fork pull requests allow this by default (Allow edits by maintainers).
The release-time overrides still exist for the batch you assemble yourself, and for anything
that reached main without a release:
pwsh -File scripts/Invoke-ReleasePrep.ps1 -Bump minor -DryRun # this batch adds up to a new idea
pwsh -File scripts/Invoke-ReleasePrep.ps1 -Version 1.0.0 # exact number, overrides everything
The Release Prep workflow exposes the same choice as a bump
dropdown, defaulting to fragments. The script prints “Bump override:
fragments resolved to X, releasing as Y” whenever you overrule them, so the decision
is visible in the run log.
Release process#
Releases use a tag-based flow on top of the default branch (main). The tag is the
release artifact consumers install with apm install "Peter-N91/hve-squad#vX.Y.Z".
You do not choose the version number by hand — the highest bump among the
pending fragments selects it.
Releases are cut by hand. Merging a pull request that carries a fragment
pushes it to .changes/unreleased/ on main and releases nothing.
Nothing releases on a timer either. When the pending batch is ready, you dispatch
Release Prep, which ships it as one version: it resolves the level, prepends
the CHANGELOG section with the consumer-install block and the link reference, writes
version: into apm.yml, deletes the fragments it consumed, commits
chore(release): X.Y.Z, dispatches release.yml to cut the tag and the
GitHub Release, and retires the pre-release.
That makes releasing a judgement about readiness rather than a date. A quick fix ships as soon as you have verified it in the preview; a larger change sits in preview until it is ready, and anything merged in the meantime rides along with it.
Between releases, main is installable. Every merge triggers
Rolling Preview, which renders the pending fragments into a single GitHub
pre-release tagged with the version they resolve to (see the current version and install command) and force-moves
that tag to the new head. It only reads: no fragment is consumed, no version is bumped, and
CHANGELOG.md is untouched. Exactly one pre-release is open at a time, so when a
minor fragment lands the old one is deleted and the new name takes over.
A pull request with no fragment (the skip-changelog label) contributes no bump.
Tick dry run when you dispatch to see the resolved version and the assembled section
without committing anything. Use the bump and version inputs to
overrule the level the fragments resolved to or to pin an exact number.
Do not keep a long-lived release branch. Cut a short-lived
hotfix/vX.Y.Z branch off an existing tag only when main holds work
that must not ship yet and a fix cannot wait for it — a release ships a commit, so a tag
cut from main carries that pending work out with the fix.
git switch --detach v0.14.0
git switch -c hotfix/v0.14.1
# apply the fix, then record it
apm run change # Type: Fixed, Bump: patch
git push -u origin hotfix/v0.14.1
Dispatch Release Prep with ref set to hotfix/v0.14.1.
It consumes only that branch's fragments, bumps apm.yml there, and cuts the tag
from that branch; release.yml receives the same ref and tags the commit it checked
out. The rolling pre-release is deliberately not retired, because the batch it
describes is still pending on main.
Merge the branch back afterwards so the fix survives the next release, and label that pull
request release-merge-back. That label is the only exemption to the
CHANGELOG.md and version: guards in pr-validation.yml,
and it exists precisely because Release Prep — not a human — wrote both. If a
higher version has already shipped, the release workflow compares versions and passes
--latest=false, so the hotfix does not take the Latest badge from
the release that supersedes it.
Release Prep can also be run directly:
apm run release-prep # resolve version, assemble, write
pwsh -File scripts/Invoke-ReleasePrep.ps1 -DryRun # preview only
pwsh -File scripts/Invoke-ReleasePrep.ps1 -Bump minor # overrule the fragments
pwsh -File scripts/Invoke-ReleasePrep.ps1 -Version 1.0.0 # force an exact version
Before a release that also re-pins hve-core, regenerate the list first:
pwsh -File scripts/Update-ApmDependencies.ps1 -Ref main pins every hve-core entry to
main's commit SHA. Refresh the lockfile with apm lock. The squad
self-references need no action — release.yml pins them into the tagged commit.
Avoid a long-lived release branch. Only create release/vX.Y.Z from an
existing tag when you need to patch an older version after newer work has landed on
main.
Known incidents from pipelining testing#
During the development and testing of Scribe hand-off pipelining (autopilot mode), five audit-trail integrity incidents were observed and detected. These incidents informed the design of per-write verification barriers and corrective controls. They are recorded here for maintainers working with squad state and Scribe dispatches:
- Incident 3 (turn 15): The run-start Scribe dispatch cut
history/Squad Scribe.mdfrom 48 blocks to 8; a bareMeasure-SquadLedger.ps1 -Checkpassed because the ledger had been rewritten to match. The original content was unrecoverable because.copilot-tracking/is git-ignored. - Incident 4 (turn 18): The Scribe hand-wrote
consumption.mdin an invalid shape with an empty Attribution Agent cell that causedMeasure-SquadLedger -Checkto throw; the coordinator's pre-write snapshot comparison detected it and the file was rewritten from the helper's output. Separately, a triple heading append in a history file received an append-only correction entry. - Incident 5 (turn 21): Existing bytes in the first 4,937 bytes of
history/System Architecture Reviewer.mdwere modified by the Scribe.Measure-SquadLedger -Check -ExpectedHistoryCountspassed despite the prefix change; only the coordinator's out-of-band SHA-256 prefix check detected it. The original bytes were unrecoverable, and this incident led to the adoption of tool-level content-prefix baseline checks (-EmitBaseline/-BaselinePath). - Incident 6 (turn 23): The Scribe overwrote
plans/2026-09-28-scribe-pipelining-plan-amendment-2.md(99,355 bytes to 8,659 bytes) because its payload did not enumerate an explicit allowed-write set. The owning role (Squad Lead) re-authored the plan; the tool now enforces a protected-artifact scope via content-baseline comparison. - Incident 7 (turn 25): The Scribe appended six heading lines per council history file (instead of one), issued void/re-append corrections, embedded entries inside heading lines, and included newline-split values. A human decision adopted pre-rendered Scribe payloads (coordinator authorship, Scribe append-only) for the rest of that run.
Each incident was detected and corrected with append-only history entries or by re-dispatching the owning role. The original bytes for incidents 3 and 5 could not be recovered. The corrective controls are now integrated into the Scribe pipelining verification contract: Measure-SquadLedger.ps1 -Check -ExpectedHistoryCounts -BaselinePath (not bare -Check), content-prefix preservation and checking via baseline snapshots, and role re-dispatch on failed verification.