Skip to content

[Tracking] PowerShellBuild v1.0.0 roadmap #120

Description

@tablackburn

[Tracking] PowerShellBuild v1.0.0 roadmap

Goal: ship PowerShellBuild 1.0.0 — the first version with a stable public-API contract per SemVer. Treats the 0.x line as initial development per SemVer §4; 1.0.0 defines the public API per SemVer §5.

Status: planning → execution
Current version: 0.8.2
Target version: 1.0.0
Prerelease cadence: one prerelease, 1.0.0-rc1, cut in #160 and soaked ≥ 7 days before 1.0.0 (revised 2026-08-27 when Phase 3 was deferred; the earlier previewN cadence is below under Locked-in decisions). It is load-bearing rather than ceremony: several breaks this cycle are install-time — RequiredModules now demands psake 5.0.4 and Pester 6.0.0, and PlatyPS was removed from it entirely — and none of them can be observed from a branch or from this repository's CI, which builds this module rather than a consumer's. Only a real gallery install exercises them.
Branching: PRs land directly on main (allowed under 0.x); 1.0.0 cuts when scope is complete

Why 1.0.0 and not 2.0.0: there has never been a stable 1.x. Jumping to 2.0.0 would imply a prior stable 1.x existed. SemVer says go to 1.0.0.

Destination

PowerShellBuild 1.0.0 published to PSGallery. This issue is the wayfinder map for
that route: it indexes every decision already made and points at the tickets holding the
detail. The map is complete when 1.0.0 ships — not when planning ends.

Working this map

Route steps are sub-issues of this issue (and of #105 for the PlatyPS chain), wired
with GitHub's native dependency edges, so the tracker itself shows what is takeable.

Read the frontier without a script by opening this issue: blocked children render with a
blocked marker, and the rest are the frontier.


Locked-in decisions

Decision Choice
Version 1.0.0 (not 2.0.0)
Tracking This issue + v1.0.0 milestone + GitHub Project board
AIM deployment Phase 0 (first PR)
Branching PRs to main + 1.0.0-previewN prereleases
Breaking changes Hard cut + migration guide
Migration guide docs/migration-v0.8-to-v1.0.md; per-PR entries; AI-assisted prompt at top
Auto-migration tool None
Deprecation cycle None (skip 0.9.0)
psake 5.x In scope for 1.0.0
Minimum PS version Windows PowerShell 5.1; PowerShell 7.4+ for PS 7 (decided 2026-07-21; PlatyPS 1.x validated on 5.1, Pester 6 floor drives the 7.4+)
Consumer Pester minimum Keep ≥ 5.x (decided 2026-07-22; RequiredModules stays at 5.6.1 — Test-PSBuildPester supports Pester 5 and 6, verified by the #137 integration matrix)
Preview numbering Decoupled from phase numbers (decided 2026-07-22). No preview is cut after Phase 1 — it shipped only two small changes (Test-PSBuildPester fixes #102 + the manifest floor #141), nothing risky to soak. The first preview is preview1, cut after Phase 2, and it carries the Phase 1 shipped changes along with it.
Migration guide scope Breaking changes plus behavioral changes that can require action on upgrade (decided 2026-07-25). The guide previously said "breaking changes only", which excluded fixes like #143 that can turn a passing consumer build red. Preamble widened to match.
Destination 1.0.0 published to PSGallery (decided 2026-08-19). The route is done when the release ships, not when the planning does.
Route tracking This issue is the map (decided 2026-08-19). Route steps are sub-issues wired with native dependency edges rather than checkboxes, so the frontier is visible in the tracker and claimable by parallel sessions.
psake 5.x In scope, clean re-spike (decided 2026-08-19) — #155. The extras that sank #117 (task caching, LLM Pester output, external PesterConfiguration file, Format-PSBuildResult) are 1.1.0 features, not part of a dependency bump.
psake 5.x outcome MIGRATE (#155, resolved 2026-08-19). Consumer-facing psakeFile.ps1 needs zero changes under 5.0.4 — no task renames, no $PSBPreference change — so the abort criterion did not fire. All four breaking changes in psake's own v4→v5 guide were checked and none apply. Full suite 428/0 under 5.0.4, matching the 4.9.1 baseline. psake 5.x does differ behaviorally — Invoke-psake returns a PsakeBuildResult where v4 returned nothing, and Set-BuildEnvironment in a Pester BeforeAll fails the container — but neither is triggered by upgrading PowerShellBuild, so neither is documented here. Migration filed as #161.
BuildHelpers break escape Found by the #155 spike, undocumented upstream (2026-08-19). Set-BuildEnvironment in a Pester BeforeAll leaks an escaping break from Get-BuildVariable's switch blocks; psake 4.9.x absorbs it, psake 5.x does not, and Pester fails the whole container. Guarding the call on $env:BHProjectName fixes it. Affects any consumer whose Pester tests call Set-BuildEnvironment.
psake abort criterion Fixed in advance, before the spike runs (decided 2026-08-19). Migrate if the consumer-facing PowerShellBuild/psakeFile.ps1 needs only mechanical changes; cut psake 5.x to 1.1.0 and ship 1.0.0 on 4.9.0 if task names or the $PSBPreference contract must change. Set ahead of the findings so the call is made on evidence rather than sunk cost.
Phase 2 ordering PlatyPS and psake chains run in parallel (decided 2026-08-19), gated by a single preview1 cut. They touch disjoint files, so serializing them buys nothing and halves the frontier.
FailBuildOnSeverityLevel #144 option 3, extended to cover #147 (decided 2026-08-19): fold ParseError into the Error threshold and add 'Any' wired to the catch-all branch. The ParseError half is a behavior change and needs a migration-guide entry. Corrected 2026-08-20: this row originally also said the gate should fail on analyzer-level errors "so a crashed rule no longer reads as 'no findings'". That premise was wrong — #147 measured that a crashed rule does not cost findings (100 cold runs, never fewer than the known-good count). #163 therefore implements #147's preferred fix, a retry on RULE_ERROR that leaves failure semantics untouched, rather than a hard failure that would have cost some consumers a red build to help others. Implemented in #163.
#83 design Grilling ticket first (#156, decided 2026-08-19). Precedence between a supplied PesterConfiguration and $PSBPreference.Test.* is public API and must settle before implementation. The former pointer to "joshooaj's design in #80" was stale — #80 is a different, closed request.
Phase 4 milestone discrepancy Resolved 2026-08-19 — milestone stripped, then partly reversed 2026-08-28. #94 and #95 stay out (both closed). #98 and #103 are back in v1.0.0 and block #160: they are the only two public functions with no coverage of their own, and the compile-mode test that appeared to cover Build-PSBuildModule asserts on file text only — it passes over a module that exports nothing (#201). Verifying code that already ships is a different question from adding features; see the note under Phase 4.
Changelog and guide scope Internal changes get neither (decided 2026-08-20, applied to #162). CHANGELOG.md records user-facing changes only, and the migration guide covers changes triggered by upgrading PowerShellBuild. A build-toolchain pin in requirements.psd1 is neither, even when the upgraded dependency behaves differently — that belongs in the dependency's own migration guide. Sharpens the existing "Migration guide scope" row: the test is who is affected by this upgrade, not how interesting the change is.
Scope additions #124 and #138 pulled in (decided 2026-08-19). Both touch the public surface freezing at 1.0.0: #124 adds a PlatyPS setting to functions Phase 2 already rewrites, #138 is a correctness bug in a public function's output.
PlatyPS 1.x updatable help Survives, and now ships (resolved 2026-08-24, delivered 2026-08-26 in #176). New-HelpCabinetFile maps onto the old call one-for-one — CabinetFilesFolder / MarkdownModuleFile / OutputFolder against CabFilesFolder / LandingPagePath / OutputFolder. The port was small; the three defects in #169 were the work, because the function had never succeeded in 0.8.x either. One contract change fell out of it: a cabinet now requires HelpInfoUri in the consumer's manifest, because without one New-HelpCabinetFile writes the cabinet and skips the HelpInfo.xml that Update-Help resolves it through — output that looks complete and cannot be used. The function refuses rather than half-producing.
PlatyPS module coexistence Never both, at any point (corrected twice, settled 2026-08-24). Command names do not collide, but each module ships its own YamlDotNet.dll with a different assembly identity (0.0.0.0 unsigned vs 15.0.0.0 signed) loaded via NestedModules, so importing the second fails in either order on PowerShell 7; only a separate process escapes it. Verified by adding both to a requirements.psd1 and running the bootstrap, which fails. The first correction concluded "install both, never import both" and had #149 add the module to an install-only requirements file. That was still wrong: with an atomic migration (next row) there is no window in which both are needed, so the dependency swap belongs in the migration commit and nothing installs 1.x ahead of it. #149 keeps only its test half; #153 folded into the migration and is closed.
PlatyPS chain shape Two pull requests to main, split at the Markdown/MAML seam (decided 2026-08-24). 2b/2c/2d cannot land as three independent merges: a build after #150 alone would need New-MarkdownCommandHelp and New-ExternalHelp in one psake session, which the loader forbids, and #150 also changes the on-disk markdown schema that an unmigrated Build-PSBuildMAMLHelp would be handed. Rejected: one combined pull request (800+ lines, past the size where review stays careful) and a stacked integration branch (intermediate sub-pull-requests would carry red CI, discarding the gating that made stacking attractive). main requires linear history, so every option squashes to one commit anyway — the choice was review ergonomics only. PR 1 migrates Build-PSBuildMarkdown and Build-PSBuildMAMLHelp together, the coupled pair, and carries the dependency swap. PR 2 migrates Build-PSBuildUpdatableHelp and fixes #169. Both green; main stays shippable throughout.
PlatyPS PR 1 intermediate Build-PSBuildUpdatableHelp is knowingly left dangling between the two pull requests (decided 2026-08-24). After PR 1 it still calls New-ExternalHelpCab, from a module no longer installed. This costs nothing observable: the function has never worked (#169) and its tests are already skipped. Its psake PreCondition is deliberately left checking Get-Module platyPS, which is then false, so GenerateUpdatableHelp skips with a warning instead of crashing — better than today, where it throws on parameter binding. PR 1 must state this rather than leave it to be discovered.
Docs task coverage Partly wrong as originally recorded — corrected 2026-08-26. This row claimed no test source exercised the three help functions. That was false. tests/build.tests.ps1 builds tests/TestModule through -FromModule PowerShellBuild, whose Build task depends on BuildHelp, so GenerateMarkdown and GenerateMAML had been running end to end all along, and Has MAML help XML was pinning the output layout in two contexts. That existing coverage is what settled the layout contract the migration preserves — had it been noticed earlier, the flatten-versus-nest question would have answered itself. What was true: the functions had no coverage of their own, with specific options, and GenerateUpdatableHelp was genuinely unobserved because it is not in the default Build chain. #149 added the unit coverage; #152 added the task-level coverage.

Migration guide

Lives at docs/migration-v0.8-to-v1.0.md. Every breaking-change PR must add an entry
using the standard structure (What changed / Why / Detection / Migration / Notes). Enforced
through instructions/git-workflow.instructions.md (AIM) plus reviewer catch — not a PR
template checkbox, which Phase 1 explicitly skipped. The top-of-file section includes a
canonical AI prompt users can paste into their agent to migrate their build.ps1
automatically; #159 tests that prompt before release.


Phase 0 — Foundation

  • Deploy AIM to the repo (feat: deploy AIM (AI Agent Instruction Modules) #122, merged 2026-05-18)
    • Add AGENTS.md, aim.config.json, instructions/
    • Migrate CLAUDE.md content → instructions/repository-specific.instructions.md
    • Modules to include: agent-workflow, shorthand, git-workflow, testing, powershell, markdown, releases, github-cli, readme, contributing, update, repository-specific
    • Fix stale version reference (CLAUDE.md says 0.7.3; actual is 0.8.0)

Phase 1 — Conventions & guardrails ✅ complete (2026-07-22)

No preview cut after Phase 1. Phase 1 was conventions/guardrails: the only consumer-facing changes since v0.8.2 are the Test-PSBuildPester bug fixes (#102) and the manifest version floor (#141) — nothing risky enough to warrant a prerelease soak. These ride along in preview1 (cut after Phase 2). See the Preview numbering decision above.

Phase 2 — Breaking dependency upgrades

Both chains below are open in parallel — they touch disjoint files — and both gate the
same preview cut.

PlatyPS migration (#105)

Three pull requests total, not six. 2a lands first and alone; 2b and 2c land together; 2d
lands with #169. See the PlatyPS chain shape decision above. The tickets stay separate because
they are separate units of work, not separate merges.

  1. PlatyPS 2a: baseline the help building functions #149 — 2a baseline the three Build-PSBuild*Help functions against current platyPS
    0.14.2 behavior, so the migration has a red-before-green target. Test-only; carries no
    dependency change. Open as test: Baseline the help building functions #170, all CI legs green.
  2. PlatyPS 2b: migrate Build-PSBuildMarkdown to New-MarkdownCommandHelp #150 + PlatyPS 2c: migrate Build-PSBuildMAMLHelp to Export-MamlCommandHelp #151 — 2b/2c, one pull request. Migrate Build-PSBuildMarkdown and
    Build-PSBuildMAMLHelp together (closes Tests: Build-PSBuildMarkdown #99 and Tests: Build-PSBuildMAMLHelp #100); they are coupled by the markdown
    schema and cannot be split. Carries the dependency swap — requirements.psd1, the
    RequiredModules manifest entry, the Markdown and MAML PreConditions, and the
    migration-guide entry for the breaking RequiredModules change.
  3. PlatyPS 2d: migrate Build-PSBuildUpdatableHelp to the 1.x cab pipeline #152 — 2d, second pull request. Migrate Build-PSBuildUpdatableHelp (closes Tests: Build-PSBuildUpdatableHelp #101) and
    fix Build-PSBuildUpdatableHelp cannot succeed: missing landing page, undefined $moduleOutDir, unbound Module #169, unskipping its tests and updating its PreCondition. The riskiest link:
    Windows-only, makecab.exe-dependent, and non-functional today — a port plus three
    fixes, not a port.
  4. PlatyPS 2e: document the docs/ schema conversion for consumers #154 — 2e convert committed docs/ markdown to the 1.x schema → document the
    conversion for consumers
    . Re-scoped 2026-08-24: git ls-files docs returns only
    docs/migration-v0.8-to-v1.0.md, so there is nothing here to convert. Now blocks Release: finalize the migration guide, test the AI prompt, write the 1.0.0 changelog #159
    rather than the chain.
  5. PlatyPS 2f: remove the old platyPS 0.14.2 dependency #153 — 2f remove the old platyPS 0.14.2 dependency — closed 2026-08-24, folded
    into 2b.
    Removing the old dependency is not a step after the migration; it is the
    migration's dependency swap.

psake 5.x bump

Gate

Phase 3 — API improvements — deferred to 1.1.0 (2026-08-27)

PesterConfiguration support is additive: $PSBPreference.Test.Configuration defaults
to unset, so shipping it in 1.1.0 breaks nobody who upgrades to 1.0.0 first. The precedence
design does not get harder either — the surface it must interact with, $PSBPreference.Test.*,
has been frozen since 0.8.2 regardless of the next version number. What it was doing was
holding a finished break surface (PlatyPS 1.x, psake 5.0.4, Pester 6.0.0, the 5.1 floor)
unreleased behind a design conversation that had not started. 1.0.0 means stable and
supported, not feature complete. Milestone removed from both; see #83 for the full reasoning.

Phase 4 — Test infrastructure

In v1.0.0:

Related test-infra work that landed during the cycle: #128/#133 (fail the build on Pester
block/container failures, not just failed tests), #140 (code-coverage tracking in the
Pester task), #143 (severity gate actually fails the build; also closed #125).

Tracked but not blocking 1.0.0 — see "Out of scope" below.

Phase 5 — Release


Not yet specified

In scope, but not yet sharp enough to ticket. Graduates as the frontier advances.

Out of scope

Ruled beyond this destination. These do not graduate; they return only if the destination
is redrawn.


Definition of done


AI-assisted-development notes

  • Keep PRs small (≤ ~400 lines diff where feasible) so they fit in agent context
  • Each PR description should reference this tracking issue and the relevant phase
  • The AIM instructions/ files are the canonical guide for any agent working in this repo — agents must read agent-workflow.instructions.md first
  • Conventional commit prefixes (feat:, fix:, docs:, chore:, BREAKING CHANGE:) to keep history machine-parseable

Activity

  1. added this to the v1.0.0 milestone on May 6, 2026
  2. pinned this issue on May 6, 2026
  3. tablackburn commented on May 16, 2026

    @tablackburn
    ContributorAuthor

    Phase 0 PR opened (draft): #122

    Status of Phase 0 checklist (boxes to tick in the issue body once #122 merges):

    • Add AGENTS.md, aim.config.json, instructions/
    • Migrate CLAUDE.md content → instructions/repository-specific.instructions.md
    • Modules included: agent-workflow, shorthand, git-workflow, testing, powershell, markdown, releases, github-cli (plus readme, contributing, update, repository-specific — flagged in the PR description; happy to drop any if the 8-module list here was intentional)
    • Fix stale version reference (CLAUDE.md said 0.7.3; actual is 0.8.0; public function count corrected 9 → 12)

    After this lands, Phase 1 (Conventions & guardrails) begins.

  4. tablackburn commented on May 18, 2026

    @tablackburn
    ContributorAuthor

    Phase 0 — complete ✅

    #122 merged 2026-05-18 (squash commit 08b191a). All Phase 0 checkboxes above ticked.

    What shipped:

    • AGENTS.md, aim.config.json, and 12 instruction modules under instructions/ (per the original Phase 0 list, plus readme, contributing, update, and repository-specific)
    • CLAUDE.md migration → instructions/repository-specific.instructions.md (repo-specific content only; generic content dropped to avoid duplication with standard AIM modules)
    • CLAUDE.md retained as a one-line @AGENTS.md import so Claude Code auto-loads AIM context on every session
    • AIM template synced to 0.8.14 (the deployment caught two bugs in the upstream templates which were fixed upstream as tablackburn/ai-agent-instruction-modules#24 and re-synced into feat: deploy AIM (AI Agent Instruction Modules) #122 before merge)
    • Stale references in the migrated content corrected (version 0.7.3 → 0.8.0; public function count 9 → 12 after the signing functions)

    No module code, dependency, or CI workflow changes — docs/config only.


    Phase 1 — kickoff plan

    Working order I'm proposing:

    GitHub admin (UI-only, needs maintainer):

    • Create v1.0.0 milestone; link this issue
    • Create the GitHub Project board (Now / Next / Later / Done)
    • Confirm branch protection on main (PR + 1 review + CI green) before cutting 1.0.0-preview.1

    PRs, smallest first:

    1. .github/PULL_REQUEST_TEMPLATE.md with a "breaking change → migration-guide entry" checkbox (unblocks every future PR carrying that contract)
    2. docs/migration/v0.8-to-v1.0.md skeleton (header, AI-prompt placeholder, per-PR entries section, format guide) — needs to exist before any Phase 2 breaking-change PR
    3. Minimum-PowerShell-version investigation — outputs both a written decision recorded in the Locked-in decisions table above and a follow-up PR fixing PowerShellVersion = '3.0' in PowerShellBuild.psd1 and aligning the CI matrix. Gated by Microsoft.PowerShell.PlatyPS 1.x and psake 5.x requirements, so timing it ahead of Phase 2 is deliberate

    1.0.0-preview.1 to PSGallery after all Phase 1 items are done.

  5. tablackburn commented on May 19, 2026

    @tablackburn
    ContributorAuthor

    Project board + v1.1.0 milestone changes

    Project board created: https://github.com/orgs/psake/projects/2 ("PowerShellBuild v1.0.0"). Status: Todo / In Progress / Done (GitHub defaults — no custom fields). Seeded with the 7 v1.0.0 milestone issues plus #122 (set to Done).

    ⚠️ Visibility note: the project was created private (default for org projects via API). Making it public requires org admin and has to be flipped in the UI: https://github.com/orgs/psake/projects/2/settings.

    v1.1.0 milestone dissolved. Its 4 issues (#17 / #98 / #102 / #103) moved to no-milestone and joined the existing "not blocking 1.0.0" pile alongside #94/#95/#96.

    Reasoning (per discussion above): tests aren't user-facing and don't influence SemVer. Milestoning test backfill under v1.1.0 implied "v1.1.0 ships when these are done," which is misleading — none of these would trigger a release on their own. They'll ship with whichever release is next cut for API reasons. The v1.1.0 milestone will be re-created when an actual feature motivates that release; any tests merged in the meantime will ride along automatically.

    Phase 1 status:

    • Create v1.0.0 milestone, link this issue
    • Create GitHub Project board
    • Add PR template
    • Initialize docs/migration/v0.8-to-v1.0.md skeleton
    • Investigate minimum PowerShell version
    • Audit PowerShellVersion in PowerShellBuild.psd1
    • Confirm branch protection on main (admin-only — UI work)
    • Cut 1.0.0-preview.1
  6. HeyItsGilbert commented on Jul 17, 2026

    @HeyItsGilbert
    Member

    Pester 6 requires at a minimum 5.1. PSDepend also moved to min of 5.1. I think it's reasonable we move to that.

  7. tablackburn commented on Jul 21, 2026

    @tablackburn
    ContributorAuthor

    @HeyItsGilbert Agreed — locking in Windows PowerShell 5.1 as the v1.0.0 minimum. Evidence gathered since your comment:

    Microsoft.PowerShell.PlatyPS 1.x is confirmed compatible with 5.1 (this was the open gate in the roadmap's investigation item):

    • The shipped assembly targets .NET Framework 4.7.2, the manifest is tagged PSEdition_Desktop + PSEdition_Core, and it declares no PowerShellVersion floor
    • The platyPS README states support for Windows PowerShell 5.1+ and PowerShell 7+
    • Validated end-to-end on Windows PowerShell 5.1.26100 (Desktop): Save-Module 1.0.2 → Import-Module → New-MarkdownCommandHelp → Import-MarkdownCommandHelp | Export-MamlCommandHelp produced valid, parseable MAML — i.e., the exact pipelines Build-PSBuildMarkdown and Build-PSBuildMAMLHelp will use after the Phase 2 migration

    One wrinkle to record alongside the 5.1 decision: Pester 6 (which the repo suite adopted in #132) supports Windows PowerShell 5.1 and PowerShell 7.4+, but not pwsh 7.0–7.3. So the v1.0.0 support statement and CI matrix should read "Windows PowerShell 5.1, or PowerShell 7.4+" rather than a plain "5.1+".

    Consumer-facing Pester minimum (RequiredModules, currently 5.6.1): proposing we keep it at ≥ 5.x rather than force 6.0 on consumers — Test-PSBuildPester supports both majors, and the #137 integration matrix verifies that contract against 5.9.0 and 6.0.0. Shout if you'd rather require 6.0 for v1.0.0.

    Updating the issue body to record the decision and catch it up with what's merged.


    Generated by Claude Code

  8. 12 remaining items

  9. added
    wayfinder:mapWayfinder map: the canonical route artifact for an effort
    on Aug 20, 2026
  10. tablackburn commented on Aug 20, 2026

    @tablackburn
    ContributorAuthor

    Route charted — this issue is now the wayfinder map

    Charting session, 2026-08-19. This issue keeps its role as the single source of truth; what
    changed is that the route is now queryable rather than prose. Nothing was resolved this
    session — charting only.

    What changed

    Structure. Added a Destination, a "Working this map" section, "Not yet specified" (in-scope
    fog), and "Out of scope". Nine decisions from this session were appended to the locked-in table.
    The 12 pre-existing HTML entities in the body (>, ', &) were fixed — several
    blockquotes had been rendering as literal > text.

    Route steps are issues now, not checkboxes. Twelve created. A checkbox cannot be claimed,
    blocked, or assigned, so the board could not show what was takeable:

    PlatyPS chain #149 (2a) → #150 (2b) → #151 (2c) → #152 (2d) → #153 (2f); #154 (2e) branches off 2a
    psake #155 (clean spike)
    Design #156 (PesterConfiguration precedence, blocks #83)
    Gates #157 (preview1), #158 (preview2)
    Release #159 (guide + changelog), #160 (bump, rc1, soak, publish)

    Wired with native dependency edges — 19 blocking edges and a full sub-issue hierarchy, so
    GitHub renders the frontier itself. #99/#100/#101 were reparented under 2b/2c/2d (they close
    with those PRs, written against the new API so tests are not written twice); #147 was folded
    under #144 as the same decision.

    Corrections found while charting

    Frontier — takeable now, in any order

    Claim by assigning yourself before starting, so parallel sessions skip it.

    Biggest open risks

    1. PlatyPS 2d: migrate Build-PSBuildUpdatableHelp to the 1.x cab pipeline #152 (updatable help) — Windows-only, makecab.exe-dependent, no test coverage today,
      and the least-used of the three functions. Most likely place for breakage to reach 1.0.0
      undetected. If the 1.x cab story is materially different, that is a scope decision for this
      map, not something to work around inside the PR.
    2. psake 5.x spike: assess breakage under psake 5.0.4 (clean bump, no extras) #155 (psake) — one prior attempt ([1.0.0-alpha1] Upgrade to psake 5.0.0 with task caching and LLM output #117) did not land. The abort criterion was agreed
      before the spike so the call gets made on evidence rather than sunk cost.
  11. tablackburn commented on Aug 24, 2026

    @tablackburn
    ContributorAuthor

    Execution plan for the remaining route (2026-08-24)

    Phase 2 is now the whole schedule. This comment records the sequence, what actually
    gates what, and four recon findings that change the plan.

    Remaining route tickets

    Thirteen. The board shows more, but #94/#95/#98/#103 are out of scope, #99/#100/#101 are
    folded into 2b/2c/2d, and #105/#120 are umbrellas.

    Lane Tickets
    PlatyPS chain (Phase 2) #149 → #150 → #151 → #152 → #153, plus #154
    Gate #157 (1.0.0-preview1)
    Phase 3 #156 → #83, #124, #158 (1.0.0-preview2)
    Release (Phase 5) #159 → #160
    Loose #166 (decision), #167 (deferred upstream)

    The critical path is nine links, and it is the PlatyPS chain

    149 → 150 → 151 → 152 → 153 → 157 → 158 → 159 → 160
    

    Everything else has slack:

    So the release date is a function of how fast the five PlatyPS pull requests land, and no
    amount of parallelism changes that. What can change it is the one lane whose blocker is
    calendar availability rather than code — see Lane B.

    Recon findings

    1. Updatable help survives PlatyPS 1.x. Closing the map's largest open unknown.
    Microsoft.PowerShell.PlatyPS 1.0.3 (published 2026-07-22) exports New-HelpCabinetFile.
    The "Not yet specified" entry asking whether #152 would surface a scope decision about
    Build-PSBuildUpdatableHelp is resolved: it is a port, not a question. #152 remains the
    riskiest link in the chain — Windows-only, makecab.exe-dependent — but the risk is
    execution, not scope.

    2. Zero command-name collisions, so #149 is safe as specified. The two modules share no
    exported command name:

    Exported commands
    platyPS 0.14.2 New-MarkdownHelp, Get-MarkdownMetadata, New-ExternalHelp, New-YamlHelp, Get-HelpPreview, New-ExternalHelpCab, Update-MarkdownHelp, Update-MarkdownHelpModule, New-MarkdownAboutHelp, Merge-MarkdownHelp
    Microsoft.PowerShell.PlatyPS 1.0.3 New-MarkdownCommandHelp, Update-MarkdownCommandHelp, Export-MamlCommandHelp, New-HelpCabinetFile, Show-HelpPreview, Import-MarkdownCommandHelp, New-CommandHelp, Update-CommandHelp, Test-MarkdownCommandHelp, Compare-CommandHelp, and the *-Yaml*/*ModuleFile set

    Side-by-side install carries no shadowing risk, which keeps #149 a genuinely small pull
    request.

    3. #154 has nothing to convert. git ls-files docs returns exactly one path:
    docs/migration-v0.8-to-v1.0.md. This repository has never committed command markdown —
    docs/ is generated output, and .gitignore plus history confirm it. #154 as written
    ("convert committed docs/ markdown to the 1.x schema") is a no-op against this
    repository. It should be re-scoped to the consumer-facing question the map already lists
    under "Not yet specified" — what a consumer with committed docs must do — or closed. It
    does not shorten the critical path either way, but it should stop blocking #153 on empty
    work.

    4. The docs tasks have no test coverage at all, and this repository does not dogfood
    them.
    psakeFile.ps1 at the repository root runs Init → Clean → Build → Analyze →
    Pester → Publish. It never invokes GenerateMarkdown, GenerateMAML, or
    GenerateUpdatableHelp. No test source exercises them either; the only matches in tests/
    are comment-based-help assertions and stale artifacts under tests/out/. All three
    functions about to be rewritten are therefore uncovered, and CI cannot report a regression
    in any of them.

    This is why #99/#100/#101 were folded into 2b/2c/2d, and it means each of those pull
    requests is "write the first test this function has ever had, then migrate it" — the test
    is the hard half.

    Change to #149's scope: add a docs smoke test that runs the three tasks against
    tests/fixtures/PSBuildTestFixture on the current 0.14.2 code, before any function is
    touched. That gives 2b, 2c, and 2d a red-before-green baseline instead of each inventing
    its own safety net, and it is the highest-leverage work in the phase.

    Sequence

    Lane A — critical path, strictly serial, one pull request per session

    1. PlatyPS 2a: baseline the help building functions #149 — pin Microsoft.PowerShell.PlatyPS 1.0.3 alongside platyPS = '0.14.2'; add
      the docs smoke test. No functional change.
    2. PlatyPS 2b: migrate Build-PSBuildMarkdown to New-MarkdownCommandHelp #150 — Build-PSBuildMarkdown → New-MarkdownCommandHelp (closes Tests: Build-PSBuildMarkdown #99)
    3. PlatyPS 2c: migrate Build-PSBuildMAMLHelp to Export-MamlCommandHelp #151 — Build-PSBuildMAMLHelp → Export-MamlCommandHelp (closes Tests: Build-PSBuildMAMLHelp #100)
    4. PlatyPS 2d: migrate Build-PSBuildUpdatableHelp to the 1.x cab pipeline #152 — Build-PSBuildUpdatableHelp → New-HelpCabinetFile (closes Tests: Build-PSBuildUpdatableHelp #101)
    5. PlatyPS 2f: remove the old platyPS 0.14.2 dependency #153 — remove platyPS 0.14.2
    6. Release gate: cut 1.0.0-preview1 after Phase 2 #157 — cut 1.0.0-preview1

    Lane B — open now, runs in parallel

    Lane C — parallel, after #149

    Then #158 → #159 → #160.

    Wall clock

    Nine sessions of pull-request work, plus the mandatory seven-day release-candidate soak and
    the preview soaks. Four to six weeks with steady attention. The soaks are irreducible.

    Loose ends

  12. tablackburn commented on Aug 24, 2026

    @tablackburn
    ContributorAuthor

    Correction to the execution plan: the two PlatyPS modules cannot coexist in one session

    Finding 2 in the plan comment above
    said side-by-side install "carries no shadowing risk." The command-name half of that is
    correct and still stands — zero collisions. The conclusion drawn from it was wrong, because
    the conflict is not at the command layer. It is at the assembly layer.

    Both modules ship their own YamlDotNet.dll, with different assembly identities:

    Module YamlDotNet.dll identity
    platyPS 0.14.2 Version=0.0.0.0, PublicKeyToken=null (unsigned, net45)
    Microsoft.PowerShell.PlatyPS 1.0.3 Version=15.0.0.0, PublicKeyToken=ec19458f3c15af5e (signed, net47)

    Both load it through NestedModules, so it lands in the default load context at import
    time. Verified directly on PowerShell 7.6.5, both orders, fresh child process each:

    === pwsh: platyPS then Microsoft.PowerShell.PlatyPS
    OK  import 1: platyPS
    ERR import 2 (Microsoft.PowerShell.PlatyPS): Could not load file or assembly 'YamlDotNet,
        Version=15.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e'.
        Assembly with same name is already loaded
    
    === pwsh: Microsoft.PowerShell.PlatyPS then platyPS
    OK  import 1: Microsoft.PowerShell.PlatyPS
    ERR import 2 (platyPS): Could not load file or assembly 'YamlDotNet, Version=0.0.0.0,
        Culture=neutral, PublicKeyToken=null'. Assembly with same name is already loaded

    The failure is symmetric and hard. A separate runspace in the same process does not help;
    only a separate process does. On Windows PowerShell 5.1 one order happens to work — new
    module first, then platyPS — because the .NET Framework loader lets the unsigned
    0.0.0.0 reference bind to the already-loaded signed 15.0.0.0. That is luck, it does not
    generalize to PowerShell 7, and it is not a strategy.

    Consequence 1 — #149 must not add the module to requirements.psd1

    build.ps1:44 bootstraps that file with Invoke-PSDepend -Install -Import. Adding
    Microsoft.PowerShell.PlatyPS there would import both modules into the bootstrap session
    and fail on every PowerShell 7 leg.

    This repository already has the pattern for exactly this shape of problem, added for the
    Pester majors in #137: requirements.pester-matrix.psd1, installed by a second
    Invoke-PSDepend call without -Import, with the reason in a header comment. #149
    follows that precedent rather than inventing a new one. Its scope is unchanged otherwise;
    it stays a small pull request.

    Consequence 2 — 2b, 2c, and 2d cannot land as independently working states

    This is the part that affects the route, not just one ticket.

    The chain as mapped assumes each link leaves a working build. It does not. After #150 alone,
    a single psake session would need New-MarkdownCommandHelp from the new module and
    New-ExternalHelp from the old one, in one process — which the loader forbids. And this is
    not only an assembly problem: #150 also changes the on-disk markdown schema, so the
    unmigrated Build-PSBuildMAMLHelp would be handed input it cannot parse even if both
    modules could load. Two independent reasons, same conclusion.

    GenerateUpdatableHelp does not escape it either — it depends on BuildHelp, so it runs in
    the same session as the other two.

    The three function migrations are inherently atomic. The map should say so.

    What I would do about it

    Two options, and this is a route decision rather than a ticket decision:

    Option A — one pull request. Collapse #150, #151, and #152 into a single migration.
    Shortens the critical path from nine links to seven. Costs: one large pull request carrying
    three rewrites plus the three first-ever test suites for those functions, well past the
    ~400-line guidance this map sets for agent-context reasons.

    Option B — stacked branches, atomic merge. Keep #150, #151, and #152 as three small
    reviewable pull requests, but target them at an integration branch
    (feature/platyps-1x) rather than main. The integration branch merges to main once
    green, so main never sees a mixed state. Costs: CI has to run on the integration branch,
    and the branch lives for the length of the chain.

    I lean to Option B. It keeps the review units small, which is the reason the chain was
    split in the first place, and it keeps main shippable — the property Option A preserves
    only by making one pull request too big to review well.

    There is a third, weaker option worth naming so it can be rejected explicitly: land the
    three on main and accept that main's docs tasks are broken for the duration. Nothing
    ships between #149 and #157, and this repository does not run its own docs tasks
    (finding 4), so the breakage would be invisible in CI. I do not recommend it — "invisible
    in CI" is the problem, not the mitigation.

  13. tablackburn commented on Aug 24, 2026

    @tablackburn
    ContributorAuthor

    Phase 2 reshaped: the chain is shorter than it was mapped

    Following the coexistence correction
    to its conclusion. That comment stopped one step short: it established that the two modules
    cannot be imported together and that the migrations are atomic, then still had #149 install 1.x
    ahead of the migration. Those two things do not fit together.

    With an atomic migration there is no window in which both modules are needed. The dependency
    lives in three places — requirements.psd1, the RequiredModules manifest entry, and six
    Get-Module platyPS -ListAvailable PreConditions across psakeFile.ps1 and IB.tasks.ps1 —
    and all of them flip in one commit. Installing 1.x before that commit buys nothing.

    Changes

    Ticket Change
    #149 Re-scoped to baseline the help building functions. Test-only, no dependency change. Open as #170, all CI legs green.
    #150 Now carries the dependency swap and the RequiredModules migration-guide entry, since CI needs 1.x from 2b onward.
    #152 Absorbs #169. It is a port plus three fixes, because Build-PSBuildUpdatableHelp has never worked.
    #153 Closed, folded into 2b. Removing the old dependency is the migration's dependency swap, not a step after it.
    #154 Re-scoped earlier today to consumer guidance; now blocks #159 rather than the chain.
    #157 Blocker moved from #153 to #152.

    Critical path

    149 → 150 → 151 → 152 → 157 → 158 → 159 → 160
    

    Eight links, down from nine. Under the combined-pull-request option it is seven.

    Still open, and it is yours

    The route call between one combined pull request and stacked branches on an integration
    branch
    is unchanged and still needed before 2b starts. It now also decides where the
    dependency swap sits: the first commit on the integration branch under the stacked option, or
    just part of the single pull request under the combined one. Options and recommendation are in
    the correction comment linked above; I lean stacked.

    Note on #169

    Build-PSBuildUpdatableHelp cannot succeed as wired — the landing page it needs is never
    generated, $moduleOutDir is undefined, and -Module is never passed. Found by writing the
    #149 baseline, which is the argument for having written it first. The map called #152 "untested
    today"; it is worse than untested.

  14. tablackburn commented on Aug 24, 2026

    @tablackburn
    ContributorAuthor

    Route call: two pull requests, split at the Markdown/MAML seam

    Decided 2026-08-24. Recorded as the PlatyPS chain shape row above, with the intermediate state as its own row.

    PR 1 — #150 + #151 together, plus the dependency swap and the breaking RequiredModules migration-guide entry.
    PR 2 — #152, plus the #169 fixes.

    Rejected: one combined pull request (800+ lines, past the size where review stays careful), and a stacked integration branch — its intermediate sub-pull-requests would carry red CI, which discards the gating that made stacking attractive. Since main requires linear history every option squashes to one commit regardless, so the choice was review ergonomics only.

    The cost of this shape is one knowingly dangling function between the two pull requests: Build-PSBuildUpdatableHelp still calls New-ExternalHelpCab from a module no longer installed. It costs nothing observable — the function has never worked (#169) and its tests are already skipped — and its PreCondition is left checking platyPS so the task skips with a warning rather than crashing. PR 1 states this rather than leaving it to be found.

    Phase 2 is now three pull requests total: #170 (open, green), PR 1, PR 2.

  15. tablackburn commented on Aug 26, 2026

    @tablackburn
    ContributorAuthor

    Phase 2 complete (2026-08-26)

    The PlatyPS chain is closed. main is green at 9177803.

    #149 baseline coverage for the three help functions — #170
    #150 + #151 Build-PSBuildMarkdown and Build-PSBuildMAMLHelp to PlatyPS 1.x — #173
    #152 Build-PSBuildUpdatableHelp to New-HelpCabinetFile, closing #101 and #169 — #176
    #153 folded into the dependency swap, as decided
    #154 re-scoped to consumer guidance; the only PlatyPS ticket still open

    Six mapped pull requests became three. #157 (1.0.0-preview1) has no open blockers.

    Two corrections to this map

    Both rows above are edited in place; recording them here so the change is visible rather than silent.

    The Docs task coverage row was wrong. It said no test source exercised the three help
    functions. tests/build.tests.ps1 builds tests/TestModule through -FromModule PowerShellBuild,
    whose Build task depends on BuildHelp — so GenerateMarkdown and GenerateMAML had been
    running end to end all along, with Has MAML help XML pinning the output layout in two contexts.
    I repeated that claim in #149's re-scope and in two pull request descriptions before a test proved
    it false. It mattered: that existing coverage is exactly what settled whether to accept PlatyPS
    1.x's extra <ModuleName> directory or flatten it back. Noticing earlier would have made the
    decision instead of my having to reason it out.

    The PlatyPS 1.x updatable help row was a prediction and is now a fact. New-HelpCabinetFile
    shipped in #176.

    What the migration turned out to be

    Worth recording, because none of it was visible when the chain was mapped:

    • The two PlatyPS modules cannot coexist in a session. Each ships its own YamlDotNet with a
      different assembly identity. Putting Microsoft.PowerShell.PlatyPS in RequiredModules failed
      394 tests locally — the suite went 461 → 67 — because that forces the load into every session
      importing PowerShellBuild, including one already holding platyPS 0.14.2, which is every
      consumer mid-upgrade. It is now an optional dependency, matching how the docs tasks have always
      behaved. alt3/Docusaurus.Powershell does declare it in RequiredModules, and that is what
      broke maester365/maester#1955.
    • Merely naming a command from the old module autoloads it, which is why
      Build-PSBuildUpdatableHelp had to be stubbed in feat!: Migrate markdown and MAML help to Microsoft.PowerShell.PlatyPS 1.x #173 rather than left alone until PlatyPS 2d: migrate Build-PSBuildUpdatableHelp to the 1.x cab pipeline #152.
    • PlatyPS 1.x nests output under <ModuleName> with no opt-out
      (platyPS#835), and renames MAML to
      -Help.xml. Both are flattened back so consumers' docs trees, site configuration, and
      .ExternalHelp directives keep working — the file name is pinned through front matter rather
      than renamed after export, which is the control point upstream actually uses.
    • A module landing page in a MAML export batch aborts the entire export and writes nothing.
      Filed as PowerShell/platyPS#862.
    • Update-MarkdownCommandHelp fails on rerun when its own .bak exists. Filed as
      PowerShell/platyPS#863.

    Open, not blocking

  16. tablackburn commented on Aug 28, 2026

    @tablackburn
    ContributorAuthor

    Scope change 2026-08-27 — Phase 3 deferred to 1.1.0, one prerelease instead of three

    Decision: #83 (PesterConfiguration object support) and its design ticket #156 move to 1.1.0. Milestone removed from both.

    Reasoning. $PSBPreference.Test.Configuration is additive and defaults to unset, so shipping it in 1.1.0 breaks nobody who upgrades to 1.0.0 first. The precedence design does not get harder after 1.0.0 either — the surface it must interact with, $PSBPreference.Test.*, has been frozen since 0.8.2 regardless of the next version number.

    What it was doing was holding a finished break surface — the PlatyPS 1.x migration, the psake 5.0.4 floor, the Pester 6.0.0 floor, the PowerShell 5.1 manifest floor, and a run of fixes — unreleased since 0.8.2, behind a design conversation that had not started. 1.0.0 means stable and supported, not feature complete.

    Consequences, all applied:

    Issue Change
    #83, #156 Deferred to 1.1.0, milestone removed
    #157 (preview1) Closed superseded — with the break surface complete, preview1 and rc1 would soak identically
    #158 (preview2) Closed superseded — Phase 3 is empty, nothing to soak
    #159 Unblocked. Its preview2 gate is satisfied by scope rather than by a release
    #160 Unchanged; its rc1 is now the cycle's only prerelease

    The prerelease is not ceremony. Several breaks this cycle are install-time and observable no other way: RequiredModules now demands psake 5.0.4 and Pester 6.0.0, so a consumer on psake 4.x cannot import the module at all; and PlatyPS was removed from RequiredModules, so Install-Module PowerShellBuild no longer brings it and anyone building help will find it missing. Neither reproduces from a branch, and neither is reachable by this repository's CI, which builds this module rather than a consumer's. One real gallery install exercises both.

    The critical path is now two issues:

    #159  finalize the guide, test the AI prompt, write the changelog
      |
    #160  bump to 1.0.0, cut rc1, soak >= 7 days, publish 1.0.0
    

    @joshooaj — #83 is yours and I have asked there for your pushback if you disagree with the deferral.

  17. tablackburn commented on Aug 28, 2026

    @tablackburn
    ContributorAuthor

    Scope change 2026-08-28 (second) — #98 and #103 restored to 1.0.0

    Reverses part of the 2026-08-19 "Phase 4 milestone discrepancy" resolution. #94 and #95 stay out (both closed). #98 and #103 are back in v1.0.0 and now block #160.

    The argument

    1.0.0 is the release that claims a stable, supported public API. Two of twelve public functions have no coverage of their own — and the one that looks covered is covered by assertions that cannot fail when it is broken.

    tests/build.tests.ps1 has a compile-mode context with eight assertions. Every one asserts on file text: that the PSM1 contains the header, the footer, the per-script header and footer. None imports the built module. And tests/TestModule's source .psm1 is a single line — # I'm some code in the src PSM1 — so it carries no dot-sourcing loader and structurally cannot exhibit #201, where compile mode produces a module that exports nothing.

    Publish-PSBuildModule is worse: no test references anywhere, 0 of 21 instructions covered, and it is the function that pushes to a package repository.

    The inductive case is strong. Every function that received real tests this cycle produced defects — #169 (updatable help had never worked, three of them), the Get-PSBuildCertificate mocks that never reached the module so tests passed against broken code, #197 (22 tests silently skipped on Windows PowerShell 5.1), and #201, found by accident rather than by the suite. Assuming the two unexamined functions are clean is not a position the evidence supports.

    Not a contradiction of this morning's deferral

    #83 moved to 1.1.0 and #98/#103 moved into 1.0.0 on the same day, and both were right:

    Consequences, applied

    Issue Change
    #98, #103 Milestone v1.0.0; both added as blockers of #160
    #83, #156 Milestone v1.1.0 (was unmilestoned after this morning's deferral, which left the board unable to distinguish them from release work)
    #160 Now blocked_by #159, #98, #103

    The v1.1.0 milestone was reopened rather than created; it already existed, closed and empty.

    Critical path

    #159  guide + AI prompt + changelog        (PR #200 open, green)
    #98   Build-PSBuildModule coverage         + fix the compile-mode blind spot
    #103  Publish-PSBuildModule coverage
      |
    #160  bump to 1.0.0, cut rc1, soak >= 7 days, publish
    

    #98 and #103 are independent of each other and of #159, so all three can run in parallel.

  18. tablackburn commented on Sep 4, 2026

    @tablackburn
    ContributorAuthor

    Decision, 2026-09-04: the 1.0.0-rc1 prerelease and its seven-day soak are dropped. 1.0.0 will be published directly.

    The rc was recorded above as load-bearing rather than ceremony, because several breaks this cycle are install-time and this repository's CI builds the module without ever installing it. That reasoning was sound, and it was nearly overlooked in making this decision. It is answered two other ways rather than dismissed:

    1. An unsatisfiable dependency cannot ship. Measured today: Publish-Module validates every RequiredModules entry against the destination repository and refuses with UnableToResolveModuleDependency when one cannot be resolved. The same code path runs against the PowerShell Gallery. A 1.0.0 whose RequiredModules cannot be satisfied fails at publish rather than reaching a consumer.
    2. The remaining install-time mechanics get a test. CI: Install the built module from a local repository to verify install-time dependency behaviour #229 publishes the built module into a local file-share repository and Save-Modules it in an isolated process on both PowerShell 7 and Windows PowerShell 5.1. Prototyped today: all three dependencies resolve at the declared versions, the generated nuspec carries no PlatyPS, the module imports and exports, and a manifest requiring psake 99.0.0 fails cleanly — so the test can fail. CI: Install the built module from a local repository to verify install-time dependency behaviour #229 is a sub-issue of this map and blocks Release: bump to 1.0.0 and publish to PSGallery #160.

    What the rc uniquely offered and nothing local replaces: resolution against the live dependency graph, gallery ingestion, real network transport on 5.1, and consumers running real pipelines. The judgement is that prerelease build tooling receives essentially no consumer exposure in practice — of the consumers surveyed this cycle, none track prereleases — so the soak would have measured silence. The migration guide's AI prompt was instead exercised against four real public consumer builds (#227), which is more validation of the upgrade path than a gallery prerelease would have produced.

    Consequences applied: the definition of done above is amended; #159 is re-gated on the merge of #224 through #228 rather than on a preview; #160 is retitled and rescoped, with a pre-flight checklist that includes a workflow_dispatch dry run of publish.yaml — the release will be the first run of actions/checkout@v7, and the manual trigger is the recovery path if the release-event run fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestwayfinder:mapWayfinder map: the canonical route artifact for an effort

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions