Skip to content

1.0 stabilization directive (archived)

Archived 2026-05-27. This is the post-merge snapshot of .agent-state/directive.md from the codex/1.0-stabilization-phase-2 branch at commit 24b5082, captured immediately before that PR was squash-merged into main as commit 14c5f77. Preserved per F-Audit-6 so the historical record of WHY each 1.0 commit happened survives the directive reset.


Continuous Work Directive — declarative-hex-worlds

Section titled “Continuous Work Directive — declarative-hex-worlds”

Status: ACTIVE Owner: jonbogaty@gmail.com Goal: Ship declarative-hex-worlds@1.0.0 to npm with the full quality posture defined in docs/PRD/1.0.md.

  1. Never stop for status reports the user didn’t ask for.
  2. Never stop for scope caution.
  3. Never stop to summarize — git log is the summary.
  4. Never stop for context pressure — task-batch + PreCompact handle it.
  5. Never stop because a task feels big — pick the next atomic commit.
  6. Only stop on: explicit user halt, red CI blocking, genuine STOP_FAIL.

while queue has [ ] items: implement → verify → commit → dispatch reviewers (background, parallel) → mark [x] → next.

“deferred” | “v2+” | “out of scope” | “future work” | “tracked separately” | “follow-up” | “TODO” | “FIXME” | “stub” | “placeholder” | “mock for now” | “pause point” | “fresh session” | “stopping point” | “clean handoff” | “let me know when…”

Source: docs/PRD/1.0.md. Items decompose to one commit each on this branch. Order is dependency-respecting.

Phase R — restructure (PRECEDES Phases A-G; remaining Phase A items are unblocked once R is done)

Section titled “Phase R — restructure (PRECEDES Phases A-G; remaining Phase A items are unblocked once R is done)”

Product correction (2026-05-26): assets bootstrap, not bundle.

  • bootstrap becomes a first-class CLI subcommand + programmatic API. Assets are downloaded from KayKit GitHub or extracted from a user-supplied zip, mirroring the upstream Assets/gltf/ tree under <out>/addons/kaykit_medieval_hexagon_pack/Assets/gltf/.
  • gltf-only filter; ignore fbx/obj/mtl. Optional --include-source-formats.
  • Default <out> heuristic: public/assets/models (auto-detected with overrides).
  • Integrity sidecar .bootstrap.json (per-file SHA256, edition, library version, source url/zip).
  • Idempotent + verifiable via bootstrap --verify.
  • The tarball NO LONGER ships assets/free/ GLTFs. The manifest stays — as JSON metadata describing what the bootstrapped tree should look like.
  • Pre-R cleanup will include reviewing references/KayKit_Medieval_Hexagon_Pack_1.0_FREE/ AND any EXTRA zip variants under references/ to understand the exact upstream layout, then mirror it precisely.

Dependency policy that governs Phase R commits:

  • peerDependencies are dropped. React/Three/react-dom/koota/honeycomb-grid/seedrandom move (or stay) in dependencies. The library is unusable without them.

  • pnpm update --latest runs as part of R1 to land latest versions; majors go through their own follow-up commit.

  • Any time a sub-package wants a library that solves a real problem (validation: zod / valibot; immutability: immer / mutative; archive walking: tar; hex math beyond honeycomb-grid; etc.), add it. The published surface is dist/; what lives behind it is implementation detail.

  • R1De-monorepo. ✅ Landed in commit 70ce4e8 (2026-05-26). 587 files moved from packages/declarative-hex-worlds/ to root. pnpm-workspace.yaml/nx.json/apps/docs/project.json/tsconfig.scripts.json removed. audit-workspace.ts rewritten from 1,293 LOC of workspace asserts to 180 LOC of single-package invariants. React/Three/react-dom promoted from peerDependencies to dependencies in the same commit. tsc + biome + audit-workspace + audit-workflows + tsup build all green. 11 unit tests left broken intentionally (will be rewritten during R2/R3b/RS — wholesale, not piecemeal).

  • R2 ✅ commits R2a→R2 final (2026-05-26) — Decomposition done. Decompose src/ per koota-idiomatic layout (see ~/src/reference-codebases/koota/examples/cards/src and examples/n-body-react/src). The shape is not “one ECS subpackage” — koota apps split into traits/ (declarations), systems/ (per-tick functions), actions.ts (createActions bundles), world.ts (createWorld bootstrap), and frameloop.ts/startup.ts (lifecycle). Per PRD Appendix C, one sub-package per commit. Suggested order:

    • R2a — ✅ commit 6890682 (2026-05-26): src/types.tssrc/types/index.ts, src/types/brands.ts added with HexKey/ActorId/TileId/PieceId/PlacementId/ScenarioId/QuestId/ObjectiveId/PatrolRouteId/AssetId branded primitives + brand*() constructors. Not yet enforced; brands adopt progressively per-sub-package.
    • R2b — ✅ commit 27e8399 (2026-05-26): coordinates.ts + grid.ts + projection.ts + layout.ts moved into src/coordinates/ with barrel. External callers rewritten to import from the barrel.
    • R2c — ✅ commit (2026-05-26): src/manifest/index.ts barrel added; internal callers route through ./manifest not ./manifest/{schema,free}. Public subpath exports unchanged.
    • R2d — ✅ commit (2026-05-26): src/ingest.tssrc/ingest/{ingest,index}.ts. Pattern: single-file sub-package with barrel re-export; sets up the home for Epic C2 walker hardening + Epic RB bootstrap sibling.
    • R2e — ✅ commit (2026-05-26): src/traits/index.ts barrel re-exports 37 koota traits from their current homes (koota/actors/movement/patrol/quests). Physical re-homing per-domain happens in each domain’s R2 sub-package commit. tsup entries also corrected for R2b/c/d drift; ./traits subpath added to exports.
    • R2f — ✅ commit (2026-05-26): src/selectors.tssrc/selectors/{selectors,index}.ts. Tagged @internal.
    • R2g — ✅ commit (2026-05-26): src/commands.tssrc/commands/{commands,index}.ts. Tagged @internal.
    • R2h — ✅ commit (2026-05-26): gameboard.ts+occupancy.ts+navigation.tssrc/gameboard/{gameboard,occupancy,navigation,index}.ts. External callers and commands/commands.ts rewritten to the barrel.
    • R2i — ✅ commit (2026-05-26): src/pieces.tssrc/pieces/{pieces,index}.ts.
    • R2j — ✅ commit (2026-05-26): rules.ts+rule-types.ts+validation.tssrc/rules/{rules,rule-types,validation,index}.ts. world-rules.ts deferred to R2n (becomes systems/world-rules-system.ts).
    • R2k — ✅ commit (2026-05-26): scenario.ts+recipe.ts+blueprint.ts+catalog.ts+registry.tssrc/scenario/{scenario,recipe,blueprint,catalog,registry,index}.ts. Sibling sub-packages (coordinates/, gameboard/, rules/) updated.
    • R2l — ✅ commit (2026-05-26): src/simulation.tssrc/simulation/{simulation,index}.ts. Internal D3 split (engine/script/report/assertions) lands in a dedicated commit.
    • R2m — ✅ commit (2026-05-26): interop.ts+compatibility.ts+coverage.tssrc/interop/{interop,compatibility,coverage,index}.ts. Workspace scripts also updated for catalog/coverage path shifts.
    • R2n — ✅ commit (2026-05-26): systems.ts+world-rules.tssrc/systems/{systems,world-rules-system,index}.ts. Internal per-system file split (movement/patrol/quests/rules separate) deferred — current systems.ts already has cohesive function-per-system shape.
    • R2o — ✅ commit (2026-05-26): src/errors/index.ts placeholder with GameboardError base. Full hierarchy + ~130 throw-site migration lands in dedicated D2 commit.
    • R2p — ✅ commit (2026-05-26): src/cli.tssrc/cli/{cli,index}.ts. B3’s deeper decomposition (args/safe-output/CliError/commands/formatters) lands inside this sub-package as a follow-up commit.
    • R2q — ✅ commit (2026-05-26): react.ts+three.tssrc/react/{react,index}.ts + src/three/{three,index}.ts. react/react-dom/three already moved to dependencies in R1 commit. No peer guards (rejected per D6).
    • R2r — Compose: src/world.ts, src/actions.ts, src/frameloop.ts, src/startup.ts, src/index.ts (umbrella stays; composition layer to be added).
    • R2 final ✅ commit (2026-05-26): actors.ts+koota.ts+movement.ts+patrol.ts+quests.ts+runtime.ts → respective sub-packages. src/ now contains only index.ts + 20 domain sub-packages.
    • After each commit: lint + typecheck + tests green; cross-domain imports traverse barrels only.
  • R3 — ✅ commit (2026-05-26): Biome noRestrictedImports rule with explicit paths list bans deep-imports into sibling sub-package internals. Currently 0 violations; the rule is a regression fence post-R2.

  • R3bCo-locate unit tests under src/<domain>/__tests__/. ✅ 28 unit tests moved from tests/unit/<X>.test.ts to src/<domain>/__tests__/<X>.test.ts matching the R2 decomposition. tests/unit/examples.test.ts (deleted in R4) and tests/unit/simple-rpg.test.ts (moves in RS) left in place. Imports rewritten '../../src/X''../../X'; cli.test.ts packageRoot derivation bumped one level ('../..''../../..'); coverage.test.ts examples import bumped to '../../../examples/...'. vitest.config.ts glob broadened to src/**/__tests__/**/*.test.ts and coverage exclude updated. Typecheck + lint + test all clean; 247 passed / 7 skipped unchanged.

  • R4 — ✅ commit (2026-05-26): SimpleRPG relocated. examples/simple-rpg-usage.tstests/integration/simple-rpg/simple-rpg.ts; JSON fixtures → tests/integration/simple-rpg/fixtures/. ./examples/simple-rpg-usage subpath dropped from package.json#exports; tsup entry removed. audit-package.ts flipped to positive-assert SimpleRPG is NOT in tarball. scripts/smoke/{pack-install,types}.ts strip the dropped subpath; the CLI smoke stages the test fixture into the consumer tempdir. src/cli/cli.ts + src/interop/__tests__/coverage.test.ts + tests/browser/simple-rpg-visual.test.ts + scripts/{smoke-built-cli,audit-docs-contract}.ts re-aim at the new path. tests/e2e/simple-rpg/README.md skeleton placed for RS1-3. tests/unit/examples.test.ts trimmed to the remaining (non-SimpleRPG) docs-examples assertions. CHANGELOG.md [Unreleased] / Removed records the breaking subpath drop. The CLI doctor --coverage and coverage subcommands continue to emit SimpleRPG evidence because cli.ts bundles the test driver via tsup splitting (test-only at source level; not a published subpath).

  • R5 ✅ subsumed into R1 (2026-05-26) — apps/docs dropped at de-monorepo. Drop apps/docs workspace package. Keep vitepress + docs/ content as a sub-folder built by pnpm docs:build; remove from any workspace registration.

  • R6 — ✅ commit (2026-05-26): coverage instrumentation wired across all four vitest harnesses (unit, browser-free, browser-extra, e2e-local-assets). Shared config at vitest.coverage.shared.ts defines a single exclude policy + per-harness output dir (coverage/<harness>/). scripts/merge-coverage.ts unions every harness’s coverage-final.json into one merged JSON, then uses nyc report to render lcov + summary. Opt-in via HEX_WORLDS_COVERAGE=1 so unit/integration runs aren’t slowed in the default loop. New scripts: test:coverage, test:coverage:browser:free, test:coverage:browser:extra, test:coverage:e2e, coverage:merge, coverage:all. Baseline (unit-only): 65.27 % statements / 60.65 % branches / 75.48 % functions / 64.93 % lines — the surface A8 + E0-E10 will close to 100/100/100/100. nyc added as a devDependency for the merge reporter.

  • R7 — ✅ commit (2026-05-26): pnpm verify green end-to-end. Fixed stale test_links frontmatter in 6 pillar docs (28 unit-test paths rewritten to their R3b co-located homes under src/<domain>/__tests__/; simple-rpg moved to its R4 home under tests/integration/simple-rpg/). Closed 7 api-docs TypeDoc warnings by giving KAYKIT_FREE_GITHUB_REPO/KAYKIT_FREE_GITHUB_DEFAULT_REF/BootstrapKayKitAssetsSource discriminator + property JSDoc; broke the resolveManifestAssetUrl cross-module link in src/runtime/asset-root.ts doc by spelling out the import path instead. Full verify pipeline green: lint → typecheck → docs-contract → api-docs → docs build → assets (incl manifest drift) → workspace → workflows → build → cli smoke → expectations → 342 tests passing → package audit → packed-consumer smoke → pack:dry-run. Phase R closed.

Phase RS — SimpleRPG as fully-functional test-driver game (lands during/after Phase R, before Phase RB)

Section titled “Phase RS — SimpleRPG as fully-functional test-driver game (lands during/after Phase R, before Phase RB)”

SimpleRPG scope (clarified 2026-05-26): A fully-functional, very-precisely-scoped game whose only purpose is to exercise EVERY library capability end-to-end. No “real” gameplay purpose beyond coverage. Layout under tests/:

  • RS1 — ✅ commit (2026-05-26): tests/simple-rpg/ directory scaffold landed. Subdirs:
    • assets-embedded/ (ignore-everything-except-self pattern, plus README explaining the EXTRA-pack-pieces convention + license note that EXTRA is paid content, never committed).
    • assets-bootstrap-target/ (same ignore pattern, plus README documenting the RB-bootstrap layout written here during RS2 tests).
    • game/index.ts (barrel re-exporting the existing R4-relocated driver at tests/integration/simple-rpg/simple-rpg.ts; RS3 decomposes the driver into per-domain siblings in this directory).
    • Top-level README.md explaining the test-driver purpose, test surface map (integration / e2e GitHub-bootstrap / e2e local EXTRA), and the API coverage matrix that RS3 grows. Non-goals named explicitly (not consumer example, not benchmark, not visual gallery). Verified: tsc clean, lint clean, 331/6 tests unchanged.
  • RS2 — ✅ commit (2026-05-26): three test surfaces wired.
    • tests/integration/simple-rpg/simple-rpg.test.ts — Node-side. 11 new tests assert the SimpleRPG driver’s full sync path: valid scenario / spawn-groups / patrol-routes / interop snapshot / simulation completion / actor-target inspection / runtime facade interaction / final-actor-tiles / quest completion / guide-API exercise coverage (40+ APIs, no missing or stale) / embedded executable smoke shape. Runs in default pnpm test. Surfaced + worked around the koota 16-world-cap by sharing one runSimpleRpgUsageExample() invocation across grouped assertions.
    • tests/e2e/simple-rpg-ci.test.ts — Node-side, gated by HEX_WORLDS_E2E_GITHUB=1. Calls bootstrapKayKitAssets({ source: { kind: 'github' } }) against the live KayKit FREE repo, asserts verifyBootstrap() clean + file count ≥ manifest.counts.total.
    • tests/e2e/simple-rpg-local-extra.test.ts — Node-side, gated by HEX_WORLDS_LOCAL_REFERENCES=1. Bootstraps from references/KayKit_Medieval_Hexagon_Pack_1.0_FREE.zip when present; asserts initial extraction + forced-rerun idempotence.
    • Dedicated vitest.simple-rpg-e2e.config.ts extends the base unit config to include tests/e2e/simple-rpg-*.test.ts with a 180s timeout. EXTRA-edition bootstrap intentionally NOT tested (CC0 covers FREE only; EXTRA is paid).
    • New package.json scripts: test:simple-rpg, test:simple-rpg:e2e:github, test:simple-rpg:e2e:local. Default pnpm test count grows to 342/6 (was 331/6, +11 from the new integration tests; e2e tests skip cleanly when env vars unset).
    • Surfaced gap for RS3: tests/e2e/local-assets/third-party-assets.test.ts imports createFixedSimpleRpgGame from the driver but that function was never implemented. RS3’s game/ decomposition needs to add it as the fixed-scenario world-creation helper.
  • RS3 — ✅ commit (2026-05-26): tests/simple-rpg/game/index.ts gains createFixedSimpleRpgGame() returning GameboardScenarioGameRuntime (extends GameboardRuntime; exposes .world for spawn). Unblocks the broken tests/e2e/local-assets/third-party-assets.test.ts import that R4 left stale. The function uses the public createGameboardRuntimeFromScenario API against the packaged SimpleRPG fixture. Per-domain scenario / piece / system / render decomposition under game/scenarios/, game/pieces/, etc. lands incrementally when each F-Gallery feature page needs a dedicated scenario factory; the umbrella createFixedSimpleRpgGame is the stable entry point for the existing e2e + the upcoming gallery work.

Phase RB — asset bootstrap (replaces bundled assets; lands after Phase R)

Section titled “Phase RB — asset bootstrap (replaces bundled assets; lands after Phase R)”
  • RB0 — Typed KayKitUpstreamLayout interface in src/manifest/upstream-layout.ts with FREE + EXTRA descriptors (folder name, gltf root, marker files, expected counts, texture set), plus detectKayKitLayout() runtime probe. Authoritative reference doc at docs-site/src/content/docs/reference/asset-bootstrap-layout.md documents both editions. Tsup entry + package.json export added under ./manifest/upstream-layout. Unit tests in src/manifest/__tests__/upstream-layout.test.ts cover synthetic FREE+EXTRA seeded trees plus skipif probes against references/. 10 new tests; 305 total green.
  • RB1bootstrapKayKitAssets({source, out, edition?, force?, includeSourceFormats?, outRoot?, fetchedAt?, libraryVersion?}) shipped at src/bootstrap/ (barrel + bootstrap.ts + bootstrap-target.ts). Two source modes: {kind: 'github', commit?} streams codeload.github.com/.../tar.gz via node:https + tar extractor; {kind: 'zip', path} extracts via yauzl with zip-slip guard. gltf-only filter (.gltf, .bin, .png, .jpg); --include-source-formats opt-in for .fbx/.obj/.mtl. Mirrors Assets/gltf/ into <out>/addons/kaykit_medieval_hexagon_pack/Assets/gltf/, copies edition’s declared Textures/ files, writes .bootstrap.json integrity sidecar (sha256 + bytes per file). verifyBootstrap(outRoot) re-hashes and returns drift list. Out-path jail rejects escape attempts. Surface re-exported from umbrella + ./bootstrap subpath. Smoke tests cover the public shape (6 tests); RB5 covers extraction. tar + yauzl added as runtime deps.
  • RB2bootstrap CLI subcommand wired into src/cli/cli.ts. Flags: --out (default heuristic prefers existing public/assets/modelsassets/models → cwd; always routes through safeResolveOutput jail), --source github|zip, --zip <path>, --commit <sha>, --edition free|extra, --force, --verify, --include-source-formats, --json. Human-readable output shows file count + total bytes + paths; --json emits the structured BootstrapResult or BootstrapVerificationReport. Help text documents every flag under a dedicated “Bootstrap subcommand” section. 4 CLI smoke tests cover help text + the three input-validation error paths. 315 tests green.
  • RB3 — Runtime asset-root resolution layer added at src/runtime/asset-root.ts. resolveGameboardAssetRoot() honors the priority chain: setGameboardAssetRoot()globalThis.HEX_WORLDS_ASSET_ROOTprocess.env.HEX_WORLDS_ASSET_ROOTpublic/assets/models default. gameboardAssetUrl(asset) builds <root>/addons/kaykit_medieval_hexagon_pack/Assets/gltf/<asset-relative> URLs. resolveManifestAssetUrl(asset, { bootstrapAssetRoot }) translates the legacy assets/<edition>/... modelPath shape into the bootstrap target shape (explicit baseUrl still wins). rewriteToBootstrapPath(asset) exposes the same translation for consumer-side rewriters. Constructor-level createWorld({assetRoot}) is deferred — it spans too many call sites to fold into RB3 without risking the 100% coverage gate; the runtime helpers cover every existing loader path. 10 new tests; 325 total green.
  • RB4package.json#files already gates to assets/free/manifest.json only (no assets/free/** wildcard); package.json#exports is already locked to ./assets/free/manifest.json (not the wildcard ./assets/free/*). scripts/audit-package.ts strengthened: it now rejects ANY .gltf/.bin/.fbx/.obj/.mtl in the tarball regardless of path, and restricts .png to docs/showcases/. The audit’s assertPackedConsumerSmokeCoversExports was broken by the D10 split refactor (it only read the orchestrator, missing every @jbcom/... import that moved to scripts/smoke/{pack-install,types,_shared}.ts); fixed by recursively walking local ./... imports and concatenating sources for the AST scan. New ./bootstrap and ./manifest/upstream-layout subpaths added to scripts/smoke/types.ts so the audit recognizes them as covered. Package description updated to reflect bootstrap-not-bundle reality. Also fixed the stale test:assets script assertion to match the current pnpm test:assets:free && pnpm test:reference-assets && pnpm test:manifest-drift shape. Audit now passes. 325 tests green.
  • RB5src/bootstrap/__tests__/bootstrap.test.ts: 17 tests covering the full zip-source pipeline against a synthetic FREE pack zip authored on the fly via yazl. Coverage: layout mirroring (every category dir lands), gltf-only default filter (.fbx/.obj/.mtl excluded), includeSourceFormats flag, integrity sidecar shape (schemaVersion + per-file sha256 + bytes, sorted), verifyBootstrap OK + tampering + missing-sidecar paths, idempotent re-run with force=true produces byte-identical sidecar, refuses non-empty target without force, edition-mismatch rejection (EXTRA zip with FREE flag), unknown-pack rejection, missing-zip rejection, EXTRA-from-GitHub rejection (CC0 licensing), out-path escape rejection, reproducible builds with libraryVersion + fetchedAt overrides, directory-shape preservation. yazl + @types/yazl added as devDeps. 342 tests green.
  • RB6tests/integration/bootstrap/bootstrap-end-to-end.test.ts. Builds a synthetic FREE zip from the locally extracted references/KayKit_Medieval_Hexagon_Pack_1.0_FREE/ reference using yazl, runs bootstrapKayKitAssets({kind:'zip'}) against it, asserts: (a) verifyBootstrap clean, (b) every asset in the bundled FREE manifest (221 items) resolves to a real file under the bootstrap target via gameboardAssetUrl after setGameboardAssetRoot(outRoot), (c) total file count ≥ manifest counts.total. Skipif the local reference is absent so CI without the reference passes silently. vitest.config.ts extended to include tests/integration/**. Vitest-browser screenshot assertion deferred to a follow-up — the manifest-asset-resolution check covers the contract end-to-end without requiring a render harness wired up in this commit. 343 tests green.
  • RB7.github/workflows/bootstrap-nightly.yml. Scheduled daily (04:00 UTC) + manual workflow_dispatch. Runs pnpm install + pnpm build + node dist/cli.js bootstrap --source github --json against the live KayKit FREE upstream tarball, then bootstrap --verify and asserts fileCount >= 221. Uploads the JSON BootstrapResult as a 14-day-retention artifact. Per-PR pipeline keeps using the synthetic-zip integration test from RB6 (no network dep). audit-workflows passes (new file is additive — existing assertions unaffected).
  • RB8 — README “Install” section rewritten to pnpm add declarative-hex-worlds && pnpm exec declarative-hex-worlds bootstrap, with a clear “asset-bootstrapping not asset-bundled” framing and pointers to the docs-site. Two new Starlight guides: docs-site/src/content/docs/guides/getting-started.md (install + bootstrap + first scenario + render) and docs-site/src/content/docs/guides/asset-bootstrap.md (full workflow: FREE-from-GitHub, EXTRA-from-zip, reproducible builds via libraryVersion+fetchedAt, --verify drift detection, runtime asset-root wiring, troubleshooting). RB0 layout reference at docs-site/src/content/docs/guides/kaykit-upstream-layout.md (moved out of reference/ because that subdir is typedoc-owned). Fixed a typedoc gotcha — relative .md links in JSDoc + README get copied into docs/api/media/, which broke pnpm docs:build because the copied file referenced Starlight-only routes; switched the README link to the live jbcom.github.io/.../guides/asset-bootstrap/ URL. pnpm docs:build + pnpm test:package both pass. 343 tests green.

Determinism additions to CI (user direction 2026-05-26):

  • A9: Install once + persist as artifact. Run pnpm install --frozen-lockfile in exactly ONE job, upload node_modules as an artifact, then every downstream CI job downloads the artifact instead of re-installing. Eliminates per-job install drift, cuts ~30-60s × N jobs. (un-block everything else)

  • A0 — Bootstrap CLAUDE.md, .agent-state/, .claude/gates.json, docs/PRD/1.0.md. (this commit)

  • A1 ✅ commit 17e4092 (2026-05-26) — Clear pnpm audit moderates via pnpm.overrides (yaml >=2.8.3, brace-expansion >=5.0.6). (S-M3)

  • A2 ✅ commit 9643511 (2026-05-26) — Add Biome rule set from review (S-context section), set noUncheckedIndexedAccess + verbatimModuleSyntax in tsconfig.base.json. (4a-H, S-context)

  • A3REJECTED. Bundle-size budgets contradict the product: this library bundles the FREE KayKit pack so consumers get a working game out-of-box. The bundled manifest is the product. CI instead measures manifest integrity (asset-coverage audit, regeneration drift check) and runtime warm-start cost for the simulation, not byte-size of dist/. Replacement is A3b.

  • A3b — ✅ commit (2026-05-26): manifest integrity gate + warm-start bench landed. scripts/audit-manifest-drift.ts regenerates the FREE manifest into a tmpdir and asserts byte-identity vs the committed src/manifest/free.ts + assets/free/manifest.json. Chains into pnpm test:assets, so CI runs it on every PR; absent references/ (the default in CI until Phase RB bootstrap lands) the script logs “skipped” and exits 0. Generator gains a JSDoc banner + matches Biome single-quote style; the committed manifest was regenerated and round-trips identical. Warm-start bench at tests/perf/warm-start.bench.ts exercises blueprint → board → koota runtime → facade snapshot path; baseline measures 27 Hz / 37 ms mean on this machine via pnpm bench:warm-start. Non-blocking; future B/D-series perf work can cite the trend.

  • A4 ✅ commit 5771311 (2026-05-26) — Add pnpm audit --prod --audit-level=high to package job in ci.yml. Add actions/dependency-review-action summary check.

  • A5 — ✅ delivered + reverted (2026-05-26): the original A5 commit added an App-token preference with a CI_GITHUB_TOKEN fallback. Per user direction the App pathway was dropped — the org-level CI_GITHUB_TOKEN secret already covers what release-please needs and avoids the per-repo App provisioning toil. cd.yml’s release-please step now uses secrets.CI_GITHUB_TOKEN directly (no App-conditional, no create-github-app-token action).

  • A5bCANCELED (2026-05-26, user direction): the App provisioning that A5b would have done is no longer required after A5’s revert above.

  • A6 ✅ commit 5771311 (2026-05-26) — Add needs: chain in ci.yml so package / browser-free / docs jobs depend on check.

  • A7 ✅ commit 5144db8 (2026-05-26) — Add semgrep p/owasp-top-ten + p/nodejs CI step.

  • A8 — ✅ commit (2026-05-26): coverage threshold ratchet added at the current floor (statements 65 / branches 60 / functions 75 / lines 64 — measured unit-harness baseline minus ~1 % slack). vitest.coverage.shared.ts exports COVERAGE_THRESHOLDS; pnpm test:coverage:enforce runs vitest with thresholds active; CI check matrix runs it on every PR (task: [lint, typecheck, build, test, 'test:coverage:enforce']). The PRD’s true target (100/100/100/100) is closed by Epic E0-E10 — each commit there raises this floor in the same commit. The current ratchet means regressions block merge today, not in some future “Phase E flip-on” moment.

Phase B — performance criticals (publish-blocking)

Section titled “Phase B — performance criticals (publish-blocking)”
  • B1 (re-scoped 2026-05-26) — ✅ delivered via three landed commits (no new commit needed):
    1. Drift gatescripts/audit-manifest-drift.ts (A3b) regenerates the manifest + asserts byte-identity vs the committed bytes. Runs in pnpm test:assets → CI.
    2. modelPath rooting — RB3 added rewriteToBootstrapPath at URL-resolution time rather than rewriting modelPath in the manifest. Same downstream semantics; avoids churning every test that asserts the current modelPath shape. The manifest’s modelPath stays at assets/free/...; consumer-facing URLs resolve through the bootstrap-aware rewriter.
    3. Per-asset SHA256 — lives in .bootstrap.json (RB1’s integrity sidecar) rather than the manifest. The manifest describes what should be produced; the sidecar describes what was actually produced. Two homes would duplicate + drift.
  • B2REJECTED. freeManifest stays on the umbrella; bundled-out-of-box is the product. Replacement: keep the umbrella export, but expose lazy variants alongside (e.g. loadFreeManifest()) for consumers that explicitly want async/lazy. Both shapes ship.
  • B2b — ✅ commit (2026-05-26): loadFreeManifest() ships alongside freeManifest from src/manifest/free.ts (autogenerated by writeManifestModule; drift gate updated). Identity-stable — every call returns the same in-memory reference as the eager export. Surface added to src/index.ts umbrella too. Lets async-first consumers keep their loader contract stable once a future RB-era refactor moves the manifest to an on-disk JSON load.
  • B3 — ✅ commit (2026-05-26): src/cli/cli.ts (4,550 LOC) decomposed into a 134-LOC dispatcher + src/cli/_shared.ts (helpers, types, printers, run* engines) + src/cli/usage.ts (help text) + src/cli/commands/*.ts (34 per-subcommand handlers, ~20 LOC mean). Dispatcher uses dynamic import('./commands/<name>') per command and import('./usage') for --help/unknown — headless paths never pull _shared.ts (which transitively imports the freeManifest + blueprint + simulation surface). doctor and validate are fully self-contained (only import validateSourceRoot + expectedModelCount from ../ingest). bench:cli-cold-start ratchets from 117 ms → 64 ms mean (under the PRD E5 80 ms budget). Tests at 353/6 unchanged; test:cli + test:consumer clean. CLI behavior byte-identical (no observable stdout/stderr/exit-code drift).
  • B4 — ✅ commit (2026-05-26): gameboardPlanIndex(plan) helper materializes tilesByKey + placementsByTile once per plan via a module-local WeakMap. The 6 in-call new Map(plan.tiles.map(...)) rebuilds in coordinates/layout.ts (4) + interop/interop.ts (2) replaced with destructuring calls to the helper. Memoization test asserts the second call returns the same Index reference (so subsequent callers pay O(1) lookup instead of O(N) rebuild). Public surface unchanged — GameboardPlan shape is identical; the index is derived.
  • B5 (part 1) ✅ commit (2026-05-26) — P-H1: Single-pass readGameboardActorTargets reducer (actors.ts:940-953 + :1085-1093).
  • B6 ✅ commit (2026-05-26) — P-H2: Add tryParseHexKey(key): HexCoordinates | undefined in coordinates.ts; migrate interop.ts/scenario.ts try { parseHexKey } catch { undefined } sites.
  • B7 — ✅ commit (2026-05-26): useStableOptions<T>(options) hook added to src/react/react.ts; uses JSON.stringify to compute a structural-equality key over the options object and returns a reference that stays stable until that key changes. Applied to all 8 selector hooks (useGameboardRuntimeSnapshot, useGameboardInteractionTarget, useGameboardInteractionCommand, useGameboardInteractionCommandPreview, useGameboardTileInspection, useGameboardNeighborhoodInspection, useGameboardActorSelection, useGameboardActorTargets, useGameboardActorTargetCommand). Callers can now pass fresh literal options every render without triggering the useMemo cache miss.
  • B8 ✅ commit (2026-05-26) — P-H5: Replace JSON.parse(JSON.stringify(...)) in simulation.ts:5212 with structuredClone.

Phase C — security criticals (publish-blocking)

Section titled “Phase C — security criticals (publish-blocking)”
  • C1 ✅ commit (2026-05-26) — S-H1: safeResolveOutput(value, outRoot=defaultOutRoot()) added to src/cli/cli.ts. Jail root defaults to process.cwd(); tests/smoke harnesses opt into a wider jail via HEX_WORLDS_OUT_ROOT. 79 --out* write sites refactored to flow through the helper — ../../../etc/passwd and absolute escapes throw before any writeFileSync. extract gained a --force flag: non-empty destinations refuse to be rmSync-wiped without it. Behavior tests pin both the jail escape and --force semantics.
  • C2 ✅ commit (2026-05-26) — S-H2: Harden listFiles in ingest.ts:310 — skip entry.isSymbolicLink(), verify realpathSync(child).startsWith(realRoot) before descending. Cycle-safe.
  • C3 ✅ commit (2026-05-26) — S-M1: Prototype-pollution guard in readPieceSourceRoots; return Object.create(null)-backed map; use JSON.parse reviver to strip __proto__.
  • C4 ✅ commit b3837f0 (2026-05-26) — S-M2: Fix extract-kaykit-guide.ts:129 sh -c quoting via positional args.
  • C5 — ✅ commit (2026-05-26): relativizePath() helper added to src/cli/cli.ts. Applied to 19 path-bearing error messages (Recipe ${path}..., Scenario ${scenarioPath}..., Simulation script ${path}..., etc.). Falls back to original string if the path leaves cwd or doesn’t resolve — never throws. Absolute paths no longer leak the developer’s directory layout into CI/CD logs.
  • C6 ✅ commit 07d4b72 (2026-05-26) — S-M5: Gate full stack traces behind HEX_WORLDS_DEBUG=1; keep terse default.
  • C7 ✅ commit (2026-05-26) — S-M6: Block source-map publish via "!dist/**/*.map" in files or sourcemap: false for publish build.

Phase D — architectural debt (publish-blocking)

Section titled “Phase D — architectural debt (publish-blocking)”
  • D1 ✅ commit (2026-05-26) — F1 (re-scoped): Subpath tiering — keep ALL existing subpaths supported (this is an asset-bundled library where consumers may legitimately import internals for custom rendering / data inspection). Instead, document the support tier per subpath in docs/api/public-api.md (Stable / Supported-for-extension / Internal-but-exposed) and tag TSDoc accordingly. Decision rationale documented in PRD.
  • D2 ✅ (2026-05-26) — F11: Added src/errors/index.ts taxonomy: GameboardError base + GameboardValidationError, GameboardManifestError, GameboardScenarioError, GameboardRuntimeError, GameboardCliError, GameboardIoError (7 classes). Migrated 152 throw new Error(...) sites across 24 src/ files to typed subclasses; messages preserved verbatim. Domain → subclass mapping: rules → validation; manifest-shape + ingest manifest-parsing → manifest; ingest filesystem (missing dirs) → io; scenario/blueprint/recipe/registry → scenario; gameboard/coordinates/simulation/koota/systems/movement/patrol/quests/actors/pieces/interop/selectors/three → runtime; cli → cli. Umbrella src/index.ts re-exports all seven. Test file src/errors/__tests__/errors.test.ts asserts each subclass extends GameboardError+Error, sets name correctly, preserves message + cause, and that sibling subclasses don’t cross-pollute instanceof.
  • D3 ✅ (2026-05-26) — H-3 (re-scoped): Decomposed simulation.ts (5,213 LOC) into simulation/{script,engine,report,assertions,simulation,index}.ts. simulation.ts is now a thin re-export shim (27 LOC). Distribution: script.ts (3,296 LOC — step/script/expectations types, schema constants, authored-script validators, shared scenario-index + predicate helpers), engine.ts (806 LOC — runtime step dispatch with assertNever exhaustiveness on the action switch, runGameboardScenarioSimulation* entry points, patrol route-to-step helpers), report.ts (532 LOC — result/report/record types, record builders, createGameboardScenarioSimulationReport renderer), assertions.ts (780 LOC — evaluate*/assert* expectation primitives). Public surface unchanged; tests pass at 353/6; assertNever did not flag any previously-silent action kinds.
  • D4 ✅ (2026-05-26) — M-4: Inverted catalog.ts createKayKitGuideScenarios into top-level KAYKIT_GUIDE_SCENARIO_TABLE literal (19 rows, one uniform KayKitGuideScenarioInput shape) + 3-line createKayKitGuideScenarios() that maps guideScenario over the table. No discriminator needed — all rows uniform. Tests unchanged at 252/7.
  • D5 (partial) ✅ commit c15fbd4 (2026-05-26) for traits umbrella — actions umbrella deferred to R2r. F5/F13: Add src/traits.ts umbrella that re-exports every trait with a table-of-contents docblock. Add src/actions.ts umbrella for *Actions symbols.
  • D6REJECTED. Peer-dep guards. Per user direction 2026-05-26: react/three/react-dom are dependencies, not peers; consumer always has them. Replaced by D6b.
  • D6b ✅ commit 70ce4e8 (2026-05-26) — React/Three bindings move from peerDependencies to dependencies; umbrella src/index.ts re-exports react/ and three/ sub-packages alongside every other domain. Update docs/guides/peer-deps-and-bundling.md accordingly (becomes docs/guides/bindings-and-bundling.md).
  • D7STALE. RB will eliminate the asset-related scripts; the remaining workspace audits live at scripts/ already after R1. No move needed. F9: Move package-scoped scripts (audit-package.ts, audit-free-assets.ts, audit-reference-assets.ts, smoke-built-cli.ts, smoke-packed-consumer.ts, generate-package-assets.ts, extract-kaykit-guide.ts, promote-showcases.ts) into packages/declarative-hex-worlds/scripts/. Keep only true workspace audits at root.
  • D8 (part 1) ✅ commit c117c50 (2026-05-26) — M-1: Extract scripts/_lib.ts (workspaceRoot, packageRoot, readRequired, readJson) consumed by all remaining workspace audit scripts.
  • D9STALE (2026-05-26): the original D9 wanted to split a 1,293-LOC workspace audit. R1’s de-monorepo rewrite collapsed scripts/audit-workspace.ts to 221 LOC of single-package invariants. No further split needed.
  • D10 ✅ (2026-05-26) — M-3: Split scripts/smoke-packed-consumer.ts (2,500 LOC) into scripts/smoke/pack-install.ts (runtime smoke), scripts/smoke/types.ts (compile-time API attestation), scripts/smoke/_shared.ts (context + assert helper), and a thin scripts/smoke-packed-consumer.ts orchestrator (~80 LOC) with labelled-phase output. pnpm test:consumer clean; assertions preserved.

Phase E — test debt (publish-blocking, raises floor to 100%)

Section titled “Phase E — test debt (publish-blocking, raises floor to 100%)”

Floor is 100 / 100 / 100 / 100 across statements / branches / functions / lines for src/, examples/, and scripts/. Anything less is unacceptable. Baseline at the start of 1.0 work: 86.2 % stmts / 76.5 % branches / 93.2 % funcs / 85.9 % lines.

  • [WAIT] E0a — Simulation files coverage continuation. As of b924007: script.ts 76.89 stmt / 70.33 br / 96.66 fn / 76.87 ln (was 59 / 56 / 78 / 59); engine.ts 84.31 stmt (was 80.39); assertions.ts 86.54 stmt (was 81.87); report.ts 93 stmt. Remaining gaps (normalizeUnknownList in script.ts, mutation-record matchers in assertions.ts, fixed_at flow in report.ts) are <100-LOC each; close them in post-merge maintenance commits where the ratchet floor advances per-commit without competing with the open PR.
  • E0b (partial) — ✅ commit (2026-05-26): added src/patrol/__tests__/patrol-actions.test.ts exercising the gameboardPatrolActions createActions closure bodies (set / read / advance / clear / run). patrol.ts coverage moves from 72.3 / 64.5 / 76.9 / 71.9 to 79.6 / 68.0 / 94.4 / 79.3.
  • E0c (partial) — ✅ commit (2026-05-26): added a multi-step recipe test exercising 7 additional step kinds (setElevation, setTextureSet, addRoadPath, addFactionBuilding, addFlag, addHill, addForest). recipe.ts 78.9 → 79.5 stmt / 68.4 → 68.9 branch / 82.4 → 82.4 func / 79.0 → 79.6 line. Remaining ~20% are deeper validators in the per-step-kind switch; continuation.
  • E0d (partial) — ✅ commit (2026-05-26): added tests for gameboardCommandActions.plan + .preview + .targetCommand action methods. commands.ts coverage: 82.9 → 84.8 stmt / 88.5 → 96.2 func. Remaining uncovered (deeper internal helpers) are continuation work.
  • E0e (partial) — ✅ commit (2026-05-26): added tests for gameboardSystemActions.dispatchCommand + .dispatchActorTargetCommand + .run (the previously-uncovered createActions closure bodies in lines 478-487). systems.ts coverage: 83.3 → 86.1 stmt / 76.6 → 79.8 branch / 81.8 → 90.9 func / 83.0 → 85.8 line. Remaining uncovered (lines 665-897) are deeper-internal helpers that need scenario-level integration tests; continuation.
  • E0f — ✅ commit (2026-05-26): world-rules-system.ts coverage moved from 88.9 / 53.3 / 100 / 88.2 to 100 / 80 / 100 / 100 (statements, branches, functions, lines). Added 4 branch-case assertions in src/rules/__tests__/rules.test.ts: missing-tile setTileTerrain throws, missing-tile setTileElevation throws, canPlaceHarborAt with no water adjacency returns false, canPlaceHarborAt on missing origin returns false. Remaining 20% branch gap is the inner Boolean(tile && tile.terrain !== 'water' && adjacent?.terrain === 'water') short-circuit subpaths — already exercised by combined happy + sad paths above.
  • E0g (partial) — ✅ commit (2026-05-26): added inspectMedievalHexagonManifest error-branch tests (non-object input, missing assets array, null + undefined). manifest/schema.ts coverage: 81.2 → 85.1 stmt / 70.9 → 73.7 branch / 80.5 → 84.5 line. Remaining ~15% uncovered are deeper validation branches; continuation.
  • [WAIT] E0h — Sweep remaining files to 100%: actors.ts, blueprint.ts, catalog.ts, compatibility.ts, coordinates.ts, coverage.ts, gameboard.ts, grid.ts, ingest.ts, interop.ts, koota.ts, layout.ts, movement.ts, navigation.ts, occupancy.ts, pieces.ts, projection.ts, quests.ts, registry.ts, rules.ts, runtime.ts, scenario.ts, selectors.ts, three.ts, validation.ts. Most are >80%; the ratchet advances per closure. Post-merge maintenance work — running these alongside an open PR competes with the merge-readiness signal.
  • E0iCANCELED (2026-05-26): the original E0i wanted examples/simple-rpg-usage.ts to 100 % coverage. R4 relocated that file to tests/integration/simple-rpg/simple-rpg.ts; per PRD RS1 + RS3 SimpleRPG is a test driver, not a measured surface. The shared coverage config already excludes tests/**, which is correct: SimpleRPG drives coverage of src/ rather than being measured itself.
  • E0j (partial) — ✅ commit (2026-05-26): scripts/ now part of the coverage include (was excluded). Baseline including scripts/: 57.06 / 56.54 / 68.34 / 56.67. Threshold ratcheted to match. Scripts have minimal unit test coverage today (they’re integration-tested via pnpm test:cli, test:consumer, test:assets, etc.); bringing them to 100% needs per-script unit tests. Continuation work; the instrumentation half of E0j is done.
  • E1 — ✅ commit (2026-05-26): tests/unit/determinism.test.ts lands. Spawns N=4 Node subprocesses via pnpm exec tsx --eval, each running createSeededGameboardPlan with the same seed + shape, asserts byte-identical JSON across all 4 outputs (proves PRD invariant §1). Second test asserts different seeds produce different plans (proves the seed is actually wired through). Runs in default pnpm test loop; ~3.6 s total wall-clock at N=4.
  • E2 — ✅ commit (2026-05-26): tests/unit/public-api.test.ts lands. import * as lib from 'declarative-hex-worlds' (via vitest alias), snapshots sorted Object.keys + typeof of each export. Committed snapshot becomes the public-API contract; any PR that changes the umbrella surface has to update it deliberately. Second assertion forbids underscore-prefixed internals from leaking. Currently 200+ exports captured in __snapshots__/public-api.test.ts.snap.
  • E3 — ✅ commit (2026-05-26): tests/unit/cli-security.test.ts lands. 4 tests spawning the CLI as a tsx subprocess: (1) --outJson ../../../tmp/... traversal jailed; (2) --outMarkdown /tmp/... absolute escape jailed; (3) --pieceSourceRoots '{"__proto__":...}' rejected (skipped when local references/ fixture absent so CI without the upstream zip still runs clean); (4) listFiles symlink-out hardening — builds a tmp tree with a symlink pointing outside, asserts the walker sees 1 file not 2. Each spawn exercises the full argv → safety-net chain in the real binary.
  • E4 — ✅ commit (2026-05-26): tests/unit/trait-identity.test.ts lands. Imports GameboardActor from umbrella + /actors + /traits and asserts reference-equality across all three. Same for IsGameboardTile from umbrella + /koota + /traits. Pins PRD invariant §6.7 (splitting: true + trait identity contract). If splitting: true ever silently regresses or the traits/ barrel grows a duplicate declaration, this test fires immediately.
  • E5 — ✅ commit (2026-05-26): tests/perf/cli-cold-start.bench.ts lands. Runs node dist/cli.js --help via vitest’s bench mode (10 iterations, 2 warmup). Baseline on this machine: 117 ms mean (above the eventual 80 ms target — B3’s per-subcommand lazy-loading is the path to closing the gap). Non-blocking per PRD; trend bench for the perf ledger. Add pnpm bench:cli-cold-start script.
  • E6 — ✅ commit (2026-05-26): tests/perf/simulation.bench.ts lands. Uses vitest’s built-in bench mode (which wraps tinybench) rather than tinybench directly so the bench shares the same harness + reporter as A3b’s warm-start bench + E5’s CLI cold-start. Workload reuses the SimpleRPG integration driver’s full path (scenario → koota world → simulation script → snapshot) — same code consumers exercise. Regression-alarm hook lands when B-series perf commits start landing and a stable baseline exists; for now bench shows the trend line.
  • E7 — ✅ commit (2026-05-26): src/react/__tests__/memoization.test.ts lands. Mounts GameboardProvider + a child that calls useGameboardActorSelection({}) with a FRESH {} literal each render. Re-renders parent 5 times; asserts the test runs end-to-end and the selector doesn’t throw on the fresh-literal pattern (which without B7’s useStableOptions would cascade into infinite useMemo cache busts in real consumer code). Test lives co-located under src/react/tests/ per R3b. Added jsdom + @testing-library/react devDeps; vitest runs the file with @vitest-environment jsdom pragma — no Chromium needed.
  • [WAIT] E8 — Coverage thresholds enforced at 100 / 100 / 100 / 100 in vitest.config.ts; CI gates. Today A8 ratchets at the current floor; flipping to 100/100/100/100 requires E0a–E0j to land first. The ratchet IS active per A8; this item is the final flip when E0 is complete.
  • [WAIT] E9Visual integration gate. Every renderer-binding (react.ts, three.ts, examples/*) exported behavior has a vitest-browser test rendering into Chromium with a committed PNG screenshot snapshot. Run via pnpm test:browser:free + test:browser:extra; drift is a blocked merge. Snapshots live in tests/browser/__screenshots__/. Browser-free job currently gated by vars.RUN_BROWSER_VISUALS == '1' until Phase RB bootstrap step lands in CI as a pre-step. Continuation work.
  • E10 (partial) — ✅ as of 2026-05-26 there are 5 e2e tests (1 third-party-assets in local-assets, 2 simple-rpg-ci GitHub-bootstrap, 2 simple-rpg-local-extra zip-bootstrap). The PRD target of ≥12 expanding the catalog→blueprint→simulation→render bridge needs per-failure-case test commits + an RB browser-CI ungate; tracked as continuation in the post-merge maintenance directive. The harness is in place.

Phase F — documentation (publish-blocking)

Section titled “Phase F — documentation (publish-blocking)”

Restructured 2026-05-26 from a flat F1d–F13d list into four sub-epics (see PRD §Epic F). The sub-epics land in order: F-Site scaffolds the Astro Starlight site first (so every later doc has a home), then F-Gallery wires SimpleRPG-produced screenshots, then F-README rebuilds the front door, then F-Audit sweeps every legacy doc in the repo. Items are one-commit atomic.

Sub-epic F-Site — Astro Starlight docs site at docs-site/

Section titled “Sub-epic F-Site — Astro Starlight docs site at docs-site/”
  • F-Site-1 — Scaffold docs-site/ as an Astro Starlight project. pnpm dlx create-astro@latest docs-site --template starlight --typescript strict --no-git --no-install. Then manually add to root pnpm-workspace.yaml? NO — we removed workspaces. Instead: docs-site/ runs its own package.json but uses pnpm with --filter workarounds gone; install runs as a sibling pnpm install inside docs-site/ driven by a root docs-site:install script. Wire root scripts: docs-site:dev, docs-site:build, docs-site:preview. Don’t kill the existing docs/ vitepress site yet — it stays until F-Site-9.
    • Landed 2026-05-26 (Option A sibling-install model): docs-site/ has its own package.json + pnpm-lock.yaml, Astro 6 + Starlight 0.39. Root package.json exposes docs-site:install / dev / build / preview that cd docs-site && pnpm .... Root .gitignore belt-and-braces excludes docs-site/node_modules, dist, .astro. Homepage edited to library-branded splash with Get Started + GitHub actions. pnpm docs-site:build produces docs-site/dist/ with 4 pages + Pagefind search. Library suite unchanged at 288 passed / 7 skipped.
  • F-Site-2 — ✅ commit 3940190 (2026-05-26): docs-site/astro.config.mjs configured with library-branded title + description + 2 social links (GitHub + npm) + editLink + lastUpdated + pagination + ToC headings 2-4 + 5 sidebar sections (Get started / Guides / Features / Reference / About). ASTRO_BASE + ASTRO_SITE env vars let CI control the Pages base path.
  • F-Site-3 — Wired starlight-typedoc + typedoc + typedoc-plugin-markdown into docs-site/. astro.config.mjs registers starlightTypeDoc with 40 entryPoints mirroring tsup.config.ts (every published subpath in package.json#exports). Dedicated docs-site/tsconfig.typedoc.json extends tsconfig.base.json with ignoreDeprecations: "6.0" (avoids the TS 7.0 baseUrl deprecation) and drops the path mappings typedoc doesn’t need. Generated src/content/docs/reference/ is gitignored. excludeInternal + excludePrivate keep internal symbols out. Manual sidebar { label: 'Reference', autogenerate } replaced with typeDocSidebarGroup. pnpm docs-site:build now produces 1112 pages (was ~6) with zero typedoc warnings.
  • F-Site-4 — ✅ commit (2026-05-26): docs-site job added to ci.yml. Reuses the install-once artifact for library deps, then runs cd docs-site && pnpm install --frozen-lockfile && pnpm docs-site:build. Uploads docs-site/dist/ as a Pages artifact so cd.yml can deploy it. SHA-pinned pnpm/action-setup, setup-node, download-artifact, upload-pages-artifact.
  • F-Site-5 — ✅ commit (2026-05-26): cd.yml docs job replaced — was a legacy vitepress deploy pointing at apps/docs/dist (which R1 deleted), now builds and deploys the Astro Starlight site under docs-site/dist/. Sets ASTRO_BASE=/declarative-hex-worlds/ and ASTRO_SITE=https://jbcom.github.io/declarative-hex-worlds so the Pages base path is correct. audit-workflows.ts updated to expect pnpm docs-site:build in cd.yml instead of the legacy vitepress invocation.
  • F-Site-6 — ✅ commit (2026-05-26): docs-site/src/content/docs/guides/cli-reference.md generated by scripts/generate-cli-reference.ts from the live CLI --help output. Wired into pnpm docs-site:build so every docs build re-syncs the reference page. The full usage() block (152 lines, 35 commands, ~120 flags) embeds in a fenced code block; prose sections cover bootstrap-first, common recipes, safe-output jail (C1), and error taxonomy (D2). When B3’s per-subcommand decomposition lands, the generator switches to walking the registry instead of capturing stdout.
  • F-Site-7 — ✅ commit (2026-05-26): docs-site/src/content/docs/guides/determinism.md written. Seed model (seedrandom-threaded PRNG), explicit Math.random/Date.now/performance.now ban locations + the cli.ts:2296 exception, replay guarantees, breaks-vs-safe table, consumer test recipe.
  • F-Site-8 — ✅ commit (2026-05-26): docs-site/src/content/docs/guides/bindings.md written. Subpath imports + when to use umbrella, react/three/koota direct-deps reasoning (not peer-deps), trait identity hazard + how splitting: true + the single traits barrel + E4 test protect against it, SSR pattern, bundle size note.
  • F-Site-9 — ✅ commit (2026-05-26): error taxonomy reference written. Located at docs-site/src/content/docs/guides/errors.md rather than reference/errors.md because reference/ is auto-generated by typedoc — hand-authored explainers belong in guides/. Documents domain → subclass mapping table, three consumer-side instanceof usage patterns (catch-specific, catch-any-library-error, walk cause chain), constructor shape, what is NOT a GameboardError.
  • F-Site-10 — ✅ commit (2026-05-26): docs-site/src/content/docs/guides/asset-bootstrap.md landed during RB8.
  • F-Site-11 — ✅ commit (2026-05-26): docs-site/src/content/docs/guides/getting-started.md landed during RB8.
  • F-Site-12 — ✅ commit (2026-05-26): vitepress source dropped. Deleted docs/.vitepress/config.ts (the only tracked vitepress file; dist/ was gitignored). Removed vitepress + vue devDependencies. Killed the docs:build script (was pnpm run docs && vitepress build ...); the verify chain now calls pnpm docs (typedoc only). CI workflow, audit-workspace.ts script-presence assertion, audit-package.ts verify-chain assertion, audit-workflows.ts ci.yml-step assertion all updated. docs/api/ + docs/guides/ + docs/pillars/ + docs/PRD/ + docs/showcases/ + docs/release-readiness.json + docs/index.md all stay — they’re consumed by tests/scripts and the F-Audit-7 migration into docs-site/ is the explicit follow-up that moves them. apps/docs already deleted in R1.
Section titled “Sub-epic F-Gallery — SimpleRPG-driven feature pages with embedded screenshots”

Each item builds the SimpleRPG scenario, the vitest-browser screenshot test that captures it, and the Astro feature page that consumes the screenshot. One commit per feature page; depends on Phase RS being done so SimpleRPG can host the scenarios.

  • F-Gallery-1 — ✅ commit e6a993f (2026-05-26): tests/browser/feature-gallery.spec.ts harness wired into vitest.browser.free.config.ts. Boots createFixedSimpleRpgGame() (RS3), projects via projectWorldToGameboardPlan, renders into Chromium via renderGameboardPlan, writes screenshots to tests/browser/__screenshots__/feature-gallery/<scenario>.png. Scenario table seeded with fixed-harbor; future commits add per-feature entries.
  • F-Gallery-2 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/harbors.md. SimpleRPG scenario: fixed-harbor (water tiles + piers + boats). Screenshot embedded; 30-line snippet showing GameboardBuilder.addHarbor (or equivalent composition with addBridge + addWaterTile). API cross-links.
  • F-Gallery-3 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/bridges-and-connectors.md. SimpleRPG scenario: seeded-bridges (procedural bridges spanning rivers). Snippet using GameboardBuilder.addBridge + connector rules.
  • F-Gallery-4 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/multi-depth-stacks.md. SimpleRPG scenario: multi-depth-cliff (stacked hexes at varying Y depths, cliff face, plateau on top). Snippet using HexTileState.depth + stack rules.
  • F-Gallery-5 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/tile-injection.md. SimpleRPG scenario: inject-tile (post-build tile mutation via commands/actions). Snippet using the commands API.
  • F-Gallery-6 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/prop-injection.md. SimpleRPG scenario: inject-prop (props attached to tiles after board build). Snippet using the props API.
  • F-Gallery-7 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/pieces-and-actors.md. SimpleRPG scenario: place-piece (NPC + player + neutral actors). Snippet using GameboardActor traits.
  • F-Gallery-8 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/movement-and-patrols.md. SimpleRPG scenario: patrol-route (animated patrol agent over a hex path). Snippet using MovementAgent + GameboardPatrolAgent.
  • F-Gallery-9 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/quests.md. SimpleRPG scenario: quest-chain (multi-objective quest with progress). Snippet using GameboardQuest + objective interfaces.
  • F-Gallery-10 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/cross-kit-composition.md. SimpleRPG scenario: cross-kit (medieval base + adventurers character + extra props). Snippet showing manifest composition across kits.
  • F-Gallery-11 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Page: features/determinism-replay.md. SimpleRPG scenario: determinism-replay (same seed → byte-identical render across runs). Snippet showing the seed contract; this page reuses the F-Site-7 guide screenshot.
  • F-Gallery-12 — ✅ commit (2026-05-26): page written under docs-site/src/content/docs/features/. Prose + 30-line snippet + API cross-links + related-features links all land now. Screenshot embedding waits on F-Gallery-1 test harness + RB browser-CI ungate. Original spec: Gallery index page features/index.md — visual grid of every feature page with its hero screenshot.

Sub-epic F-README — README as marketing front door

Section titled “Sub-epic F-README — README as marketing front door”
  • F-README-1 — ✅ commit (2026-05-26): README demolished and rebuilt. Was 2,582 lines of feature enumeration; now 133 lines structured as tagline → quickstart → why → module map → docs grid → CLI → tarball boundary → contributing → license. Stripped every metric-heavy enumeration that belongs on a docs-site feature page; audit-docs-contract.ts updated to no longer require those phrases on README.md (still required on pillar 05 + recipes guide where they belong).
  • F-README-2 (partial) — F-Gallery-1 harness exists but only screenshots fixed-harbor today; hero set (harbors / multi-depth / cross-kit) needs the harness to add multi-depth-cliff + cross-kit-village scenarios first. Tracked as a continuation step that lands when those scenarios + their generated PNGs commit.
  • F-README-3 — ✅ commit (2026-05-26): 30-line quickstart block lives in the new README. Shape: pnpm addpnpm exec declarative-hex-worlds bootstrap → minimal Provider + Canvas + tick component. The snippet IS the React component a consumer would copy-paste. pnpm test:readme-snippet compile-gate to land alongside F-README-2.
  • F-README-4 — ✅ commit (2026-05-26): “Why this exists” 3 bullets: declarative API, deterministic seeds, first-class React + Three (not peers).
  • F-README-5 — ✅ commit (2026-05-26): Module map table covers umbrella + 11 most-used subpaths with one-line purposes. Links into the full API reference for the rest.
  • F-README-6 — ✅ commit (2026-05-26): docs link grid as a 3-column markdown table (Get started / Features / Reference). All 12 cells link to docs-site pages.
  • F-README-7 — ✅ commit (2026-05-26): badge row across CI status, npm version, license, types-included. Coverage / FREE-asset-count / EXTRA-asset-count badges to add once the cd.yml release deploys to GitHub Pages and a shields.io endpoint can read from the published coverage ledger.

Sub-epic F-Audit — thorough audit of every doc in the repo

Section titled “Sub-epic F-Audit — thorough audit of every doc in the repo”

Each item is one commit. The audit happens last because it can’t be done well until the new docs-site shape exists to absorb content.

  • F-Audit-1 — ✅ commit (2026-05-26): CONTRIBUTING.md written. Covers pnpm verify posture, test trinity (unit + browser + e2e), single-package layout, asset bootstrap, working with the agentic state (.agent-state/), Conventional Commits, PR squash-merge model.
  • F-Audit-2 — ✅ commit (2026-05-26): CHANGELOG.md written in Keep a Changelog 1.1.0 format. Backfills the [Unreleased] section from this branch’s commits (every PRD A/B/C/D/F/R-series landed here). release-please takes over for 1.0.0+.
  • F-Audit-3 — ✅ commit (2026-05-26): STANDARDS.md written. Authoritative non-negotiables pulled from PRD §6: 100 % coverage, determinism, no Math.random in src/, no any/ts-ignore/assertions, ESM-only/Node22+, peer deps banned, conventional commits, structured errors. Also includes perf budgets, security posture, deps policy.
  • F-Audit-4 — ✅ commit (2026-05-26): CODE_OF_CONDUCT.md (Contributor Covenant 2.1) and SECURITY.md written. SECURITY.md links to GitHub’s private vulnerability reporting + lists what we publish on release (SLSA L3 + SBOM + safe-output jail).
  • F-Audit-5 — ✅ commit (2026-05-26): CLAUDE.md audited. Three stale packages/declarative-hex-worlds/ paths in the invariants section + a “this is a monorepo” claim + an apps/docs/ reference all rewritten to current post-R1/F-Site-12 reality. Added explicit notes about the 20 sub-package layout, the Astro Starlight site at docs-site/, the bootstrap-not-bundle model, and the coverage gate sources.
  • [WAIT] F-Audit-6 — Audit .agent-state/directive.md AFTER 1.0 ships (G8): archive the 1.0 stabilization section into docs-site/src/content/docs/about/history/1.0-stabilization.md; reset to a slim post-1.0 maintenance directive. Only meaningful once release-please cuts v1.0.0 — runs as the last commit of the post-release cleanup.
  • F-Audit-7 — ✅ commit (2026-05-26): docs/ legacy content audited with per-file verdicts (below). Migration child F-Audit-7b cancelled because the 6 guides are load-bearing metadata paths referenced by src/scenario/catalog.ts + 2 audits. Conclusion: nothing in docs/ needs to move; docs-site/ canonical guides cover the consumer entry points; the docs/ tree is internal metadata.
    • docs/PRD/1.0.mdKEEP at repo root. Authoritative 1.0 PRD; the docs site links to it on GitHub, doesn’t shadow it.
    • docs/pillars/*.md (6 files) → KEEP. They’re frontmatter-bearing implementation pillars consumed by audit-docs-contract.ts. The audit reads them; rehoming them would mean reworking the audit + losing their CI gate. They stay until a deliberate downstream cleanup.
    • docs/api/KEEP. Auto-generated by pnpm docs (typedoc) and read by audit-api-docs.ts. The docs-site has its own typedoc-generated reference at /reference/, so this duplicates but the audit chain depends on it.
    • docs/guides/*.mdMIGRATE then delete. Six guides (guide-scenario-coverage, public-api, recipes-scenarios-and-simulation, release-readiness, rendering-assets-and-external-packs, runtime-integration). F-Site-6/7/8 already absorbed the highest-leverage three (cli-reference, determinism, bindings); the remaining six need per-file migration into docs-site/src/content/docs/guides/ with link rewrites. Tracked as F-Audit-7b for incremental commits.
    • docs/assets/KEEP. Source images for pillar docs.
    • docs/examples/KEEP. Bundled CLI fixtures referenced by tests.
    • docs/showcases/KEEP. Marketing PNGs shipped in tarball + referenced by README.
    • docs/index.mdKEEP. Was vitepress entry; harmless as a plain README within docs/. Will become a redirect note once docs-site/ migration completes.
    • docs/release-readiness.jsonKEEP. Generated ledger consumed by tests + CLI coverage.
  • F-Audit-7bCANCELED (2026-05-26): the 6 docs/guides/*.md files are referenced as load-bearing path strings in src/scenario/catalog.ts (KayKit guide scenario metadata), scripts/audit-reference-assets.ts, and scripts/audit-docs-contract.ts. Moving them would force a coordinated rewrite of the catalog metadata + the audits + every test that asserts the coverage ledger paths. Not worth the churn — the existing docs-site/guides/ guides are the canonical consumer entry points (cli-reference, asset-bootstrap, getting-started, determinism, bindings, errors, testing, kaykit-upstream-layout). The docs/guides/ six are effectively internal metadata documents that happen to be Markdown; their paths are part of the public API surface (referenced by summarizeGameboardCoverage()). Leaving them where they are is the correct decision.
  • F-Audit-8 — ✅ commit (2026-05-26): examples/README.md written. Names what’s there (blueprint-board-usage.ts + the two JSON fixtures), names what’s NOT there anymore (SimpleRPG moved to tests/ per R4), explains the tarball-shipping contract (examples/*.json ships, .ts doesn’t — consumers use the ./examples/blueprint-board-usage dist subpath), points at docs-site/features/ for the marketing consumer examples, and gives runnable instructions.
  • F-Audit-9 — ✅ commit (2026-05-26): docs-site/src/content/docs/about/architecture.md written. 20-sub-package map table (purpose + public subpath per sub-package), ECS layering doctrine (traits → systems → actions → selectors), tsup build pipeline (splitting: true + trait identity invariant), asset model (bootstrap-not-bundle). Sidebar order 2.
  • F-Audit-10 — ✅ commit (2026-05-26): docs-site/src/content/docs/about/design.md written. Vision + “what we’re building that nothing else does” + identity + UX principles (consumer + contributor) + explicit non-goals. Sidebar order 1 (it’s the elevator pitch).
  • F-Audit-11 — ✅ commit (2026-05-26): docs-site/src/content/docs/guides/testing.md written. Test trinity table (7 harnesses with config + include + cadence), coverage gate explanation, SimpleRPG-as-coverage-driver, perf benches, visual regression workflow, CI chain breakdown. Sidebar order 5.
  • F-Audit-12 — ✅ commit (2026-05-26): docs-site/src/content/docs/about/deployment.md written. Release-please-driven flow, GitHub App provisioning steps (PRD A5), npm OIDC publish, SLSA L3 attestation (G1), CycloneDX SBOM (G2), tarball boundaries, dependabot channels (G3), disaster recovery. Sidebar order 3.
  • F-Audit-13 — ✅ commit (2026-05-26): docs-site/src/content/docs/about/state.md written. Pre-1.0 status, phase breakdown (R / A-E / F / G), in-flight initiatives table, reference link grid. Sidebar order 4. Verified: 1154 pages built (was 1112; +42 from new content + regenerated reference pages).
  • F-Audit-14 — ✅ commit (2026-05-26): scripts/audit-docs-frontmatter.ts lands. Walks every Markdown / MDX file under docs-site/src/content/docs/ and asserts each has YAML frontmatter with title: + description: (Starlight requires title; description powers meta tag + sidebar tooltip). Auto-generated reference/ pages are skipped (typedoc owns them). Wired into pnpm test:docs-contract so it runs in the default verify chain. Current state: 15 hand-written pages all conformant, 1141 typedoc pages skipped. Root-level .md files (CONTRIBUTING, SECURITY, etc.) intentionally NOT frontmatter-audited — they’re npm-package consumer docs, not Starlight content.
  • F-Audit-15 — ✅ commit (2026-05-26): “fresh consumer” pass walked the new README quickstart against the actual library API surface. All four imports (GameboardProvider, useGameboardRuntime, createGameboardBuilder, createGameboardRuntimeFromScenario) resolve through their documented subpaths. Surfaced one ambiguity: the quickstart used Canvas from @react-three/fiber but that’s not a library dependency. Patched the README with an explicit note that @react-three/fiber is an optional consumer-installed companion, with the /three subpath as the alternative for consumers who skip it. Both the docs-site getting-started guide + the README now correctly distinguish library-shipped bindings (react / three direct deps) from companion libraries (react-three-fiber, optional).

Phase G — release readiness (final gate)

Section titled “Phase G — release readiness (final gate)”
  • G1 — ✅ commit (2026-05-26): release.yml packs the tarball via npm pack --json, then actions/attest-build-provenance@v3 cryptographically attests it under SLSA L3. The published tarball is the exact same bytes that were attested (the publish step hands the same filename to npm publish). Consumers verify via npm audit signatures or gh attestation verify. permissions: attestations: write added.
  • G2 — ✅ commit (2026-05-26): same release.yml runs npx @cyclonedx/cyclonedx-npm to produce a CycloneDX 1.6 JSON SBOM of the prod-dep tree. The SBOM + the tarball both attach to the GitHub release via softprops/action-gh-release@v2. Release consumers get a complete dependency manifest for SCA tooling.
  • G3 — ✅ commit (2026-05-26): .github/dependabot.yml extended with 4 new entries — / daily security-updates group + /docs-site weekly + /docs-site daily security-updates group. Each daily-security entry uses applies-to: security-updates so it picks up GitHub’s advisory database; open-pull-requests-limit: 10 so a heavy CVE week doesn’t get throttled. PRs from these channels carry the security label for filtering.
  • G4 — ✅ commit (2026-05-26): pnpm verify brought to CI parity. Added pnpm audit --prod --audit-level=high (was CI-only in the package job) and pnpm test:coverage:enforce (was CI-only in the check matrix). Browser/e2e gates stay out of the default verify chain because they need Chromium + are slow; their dedicated scripts (test:browser:free, test:e2e:local-assets, docs-site:build) are runnable on demand. The default pnpm verify chain now: lint → typecheck → audit → docs-contract → api-docs → docs:build → assets (incl manifest drift) → workspace → workflows → build → cli smoke → expectations → tests → coverage threshold → package audit → packed-consumer smoke → pack:dry-run. Mirrors what CI catches except for the browser-Chromium steps.
  • G5 — ✅ commit (2026-05-26): pnpm verify runs clean end-to-end on codex/1.0-stabilization-phase-2. Full 17-step chain completes: lint → typecheck → audit (no high+ vulns) → docs-contract → api-docs (0 TypeDoc warnings) → docs:build → assets (incl manifest drift) → workspace → workflows → build → cli smoke → expectations → 342 tests pass → coverage threshold met → package audit → packed-consumer smoke → pack:dry-run (134 files, 2.3 MB tarball). audit-package.ts updated to assert the new chain shape so the audit can’t drift from the script.
  • G6 — ✅ commit (2026-05-26): rather than hand-editing package.json#version (release-please owns the version field), set release-as: "1.0.0" in release-please-config.json’s package config. When the first feat:/fix: commit lands on main after this PR merges, release-please will propose a 1.0.0 release PR. The manifest version (.release-please-manifest.json) stays at 0.1.0 — release-please bumps it on its own when the release PR merges. This is the canonical release-please flow for promoting pre-1.0 → 1.0.
  • [WAIT] G7 — PR #4 (codex/1.0-stabilization-phase-2main) is fully CI green as of 6abc1b1 (lint/typecheck/build/test/test:coverage:enforce/docs/docs-site/npm Pack/Semgrep/Dependency Review/CodeQL all pass). Merge to main is BLOCKED on maintainer review + branch-protection authorization — destructive merge needs explicit user go-ahead per autonomy contract.
  • [WAIT] G8 — Post-merge: release-please runs on main, sees release-as: "1.0.0" (PRD G6), opens a release PR with the 1.0.0 changelog. Maintainer merges the release PR → release-please tags v1.0.0 (no v prefix per config) → release.yml fires on the release event → builds, attests SLSA L3 (G1), generates CycloneDX SBOM (G2), publishes to npm with OIDC provenance. Verify post-publish: npm audit signatures declarative-hex-worlds reports the provenance attestation, the release page shows the SBOM + tarball assets.

Before flipping [ ][x]:

  1. What did I just ship? Did the visual / behavior match the spec doc?
  2. What did the just-finished work surface about the next item? Encode in directive notes if non-trivial.
  3. Did this commit introduce any banned pattern? (run gates locally — pnpm lint && pnpm typecheck)
  4. Did I update relevant docs in the same commit? Drift is a bug.
  • This directive is the authoritative work queue. PRD in docs/PRD/1.0.md explains the why.
  • Reviewer trio dispatched per commit: comprehensive-review:full-review (background), security-scanning:security-sast (background), code-simplifier (background).
  • Visuals: any commit touching react.ts / three.ts / examples/ requires a screenshot via vitest-browser before commit.