1.0 stabilization directive (archived)
Archived 2026-05-27. This is the post-merge snapshot of
.agent-state/directive.mdfrom thecodex/1.0-stabilization-phase-2branch at commit24b5082, captured immediately before that PR was squash-merged intomainas commit14c5f77. 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.
What CONTINUOUS means
Section titled “What CONTINUOUS means”- Never stop for status reports the user didn’t ask for.
- Never stop for scope caution.
- Never stop to summarize — git log is the summary.
- Never stop for context pressure — task-batch + PreCompact handle it.
- Never stop because a task feels big — pick the next atomic commit.
- Only stop on: explicit user halt, red CI blocking, genuine STOP_FAIL.
Operating loop
Section titled “Operating loop”while queue has [ ] items: implement → verify → commit → dispatch reviewers (background, parallel) → mark [x] → next.
Forbidden phrases
Section titled “Forbidden phrases”“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…”
Active queue — 1.0 stabilization
Section titled “Active queue — 1.0 stabilization”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.
bootstrapbecomes a first-class CLI subcommand + programmatic API. Assets are downloaded from KayKit GitHub or extracted from a user-supplied zip, mirroring the upstreamAssets/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 underreferences/to understand the exact upstream layout, then mirror it precisely.
Dependency policy that governs Phase R commits:
-
peerDependenciesare dropped. React/Three/react-dom/koota/honeycomb-grid/seedrandom move (or stay) independencies. The library is unusable without them. -
pnpm update --latestruns 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. -
R1 — De-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/srcandexamples/n-body-react/src). The shape is not “one ECS subpackage” — koota apps split intotraits/(declarations),systems/(per-tick functions),actions.ts(createActions bundles),world.ts(createWorld bootstrap), andframeloop.ts/startup.ts(lifecycle). Per PRD Appendix C, one sub-package per commit. Suggested order:- R2a — ✅ commit 6890682 (2026-05-26):
src/types.ts→src/types/index.ts,src/types/brands.tsadded 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.tsmoved intosrc/coordinates/with barrel. External callers rewritten to import from the barrel. - R2c — ✅ commit (2026-05-26):
src/manifest/index.tsbarrel added; internal callers route through./manifestnot./manifest/{schema,free}. Public subpath exports unchanged. - R2d — ✅ commit (2026-05-26):
src/ingest.ts→src/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.tsbarrel 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;./traitssubpath added to exports. - R2f — ✅ commit (2026-05-26):
src/selectors.ts→src/selectors/{selectors,index}.ts. Tagged@internal. - R2g — ✅ commit (2026-05-26):
src/commands.ts→src/commands/{commands,index}.ts. Tagged@internal. - R2h — ✅ commit (2026-05-26):
gameboard.ts+occupancy.ts+navigation.ts→src/gameboard/{gameboard,occupancy,navigation,index}.ts. External callers and commands/commands.ts rewritten to the barrel. - R2i — ✅ commit (2026-05-26):
src/pieces.ts→src/pieces/{pieces,index}.ts. - R2j — ✅ commit (2026-05-26):
rules.ts+rule-types.ts+validation.ts→src/rules/{rules,rule-types,validation,index}.ts.world-rules.tsdeferred to R2n (becomessystems/world-rules-system.ts). - R2k — ✅ commit (2026-05-26):
scenario.ts+recipe.ts+blueprint.ts+catalog.ts+registry.ts→src/scenario/{scenario,recipe,blueprint,catalog,registry,index}.ts. Sibling sub-packages (coordinates/, gameboard/, rules/) updated. - R2l — ✅ commit (2026-05-26):
src/simulation.ts→src/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.ts→src/interop/{interop,compatibility,coverage,index}.ts. Workspace scripts also updated for catalog/coverage path shifts. - R2n — ✅ commit (2026-05-26):
systems.ts+world-rules.ts→src/systems/{systems,world-rules-system,index}.ts. Internal per-system file split (movement/patrol/quests/rules separate) deferred — currentsystems.tsalready has cohesive function-per-system shape. - R2o — ✅ commit (2026-05-26):
src/errors/index.tsplaceholder withGameboardErrorbase. Full hierarchy + ~130 throw-site migration lands in dedicated D2 commit. - R2p — ✅ commit (2026-05-26):
src/cli.ts→src/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.ts→src/react/{react,index}.ts+src/three/{three,index}.ts. react/react-dom/three already moved todependenciesin 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 onlyindex.ts+ 20 domain sub-packages. - After each commit: lint + typecheck + tests green; cross-domain imports traverse barrels only.
- R2a — ✅ commit 6890682 (2026-05-26):
-
R3 — ✅ commit (2026-05-26): Biome
noRestrictedImportsrule with explicit paths list bans deep-imports into sibling sub-package internals. Currently 0 violations; the rule is a regression fence post-R2. -
R3b — Co-locate unit tests under
src/<domain>/__tests__/. ✅ 28 unit tests moved fromtests/unit/<X>.test.tstosrc/<domain>/__tests__/<X>.test.tsmatching the R2 decomposition.tests/unit/examples.test.ts(deleted in R4) andtests/unit/simple-rpg.test.ts(moves in RS) left in place. Imports rewritten'../../src/X'→'../../X';cli.test.tspackageRootderivation bumped one level ('../..'→'../../..');coverage.test.tsexamples import bumped to'../../../examples/...'.vitest.config.tsglob broadened tosrc/**/__tests__/**/*.test.tsand coverage exclude updated. Typecheck + lint + test all clean; 247 passed / 7 skipped unchanged. -
R4 — ✅ commit (2026-05-26): SimpleRPG relocated.
examples/simple-rpg-usage.ts→tests/integration/simple-rpg/simple-rpg.ts; JSON fixtures →tests/integration/simple-rpg/fixtures/../examples/simple-rpg-usagesubpath dropped frompackage.json#exports; tsup entry removed.audit-package.tsflipped to positive-assert SimpleRPG is NOT in tarball.scripts/smoke/{pack-install,types}.tsstrip 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}.tsre-aim at the new path.tests/e2e/simple-rpg/README.mdskeleton placed for RS1-3.tests/unit/examples.test.tstrimmed to the remaining (non-SimpleRPG) docs-examples assertions.CHANGELOG.md [Unreleased] / Removedrecords the breaking subpath drop. The CLIdoctor --coverageandcoveragesubcommands 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/docsworkspace package. Keep vitepress +docs/content as a sub-folder built bypnpm 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.tsdefines a single exclude policy + per-harness output dir (coverage/<harness>/).scripts/merge-coverage.tsunions every harness’scoverage-final.jsoninto one merged JSON, then usesnyc reportto render lcov + summary. Opt-in viaHEX_WORLDS_COVERAGE=1so 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 verifygreen end-to-end. Fixed staletest_linksfrontmatter in 6 pillar docs (28 unit-test paths rewritten to their R3b co-located homes undersrc/<domain>/__tests__/; simple-rpg moved to its R4 home undertests/integration/simple-rpg/). Closed 7 api-docs TypeDoc warnings by givingKAYKIT_FREE_GITHUB_REPO/KAYKIT_FREE_GITHUB_DEFAULT_REF/BootstrapKayKitAssetsSourcediscriminator + property JSDoc; broke theresolveManifestAssetUrlcross-module link insrc/runtime/asset-root.tsdoc 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 attests/integration/simple-rpg/simple-rpg.ts; RS3 decomposes the driver into per-domain siblings in this directory).- Top-level
README.mdexplaining 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 defaultpnpm test. Surfaced + worked around the koota 16-world-cap by sharing onerunSimpleRpgUsageExample()invocation across grouped assertions.tests/e2e/simple-rpg-ci.test.ts— Node-side, gated byHEX_WORLDS_E2E_GITHUB=1. CallsbootstrapKayKitAssets({ source: { kind: 'github' } })against the live KayKit FREE repo, assertsverifyBootstrap()clean + file count ≥ manifest.counts.total.tests/e2e/simple-rpg-local-extra.test.ts— Node-side, gated byHEX_WORLDS_LOCAL_REFERENCES=1. Bootstraps fromreferences/KayKit_Medieval_Hexagon_Pack_1.0_FREE.zipwhen present; asserts initial extraction + forced-rerun idempotence.- Dedicated
vitest.simple-rpg-e2e.config.tsextends the base unit config to includetests/e2e/simple-rpg-*.test.tswith 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. Defaultpnpm testcount 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.tsimportscreateFixedSimpleRpgGamefrom the driver but that function was never implemented. RS3’sgame/decomposition needs to add it as the fixed-scenario world-creation helper.
- RS3 — ✅ commit (2026-05-26):
tests/simple-rpg/game/index.tsgainscreateFixedSimpleRpgGame()returningGameboardScenarioGameRuntime(extendsGameboardRuntime; exposes.worldfor spawn). Unblocks the brokentests/e2e/local-assets/third-party-assets.test.tsimport that R4 left stale. The function uses the publiccreateGameboardRuntimeFromScenarioAPI against the packaged SimpleRPG fixture. Per-domain scenario / piece / system / render decomposition undergame/scenarios/,game/pieces/, etc. lands incrementally when each F-Gallery feature page needs a dedicated scenario factory; the umbrellacreateFixedSimpleRpgGameis 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
KayKitUpstreamLayoutinterface insrc/manifest/upstream-layout.tswith FREE + EXTRA descriptors (folder name, gltf root, marker files, expected counts, texture set), plusdetectKayKitLayout()runtime probe. Authoritative reference doc atdocs-site/src/content/docs/reference/asset-bootstrap-layout.mddocuments both editions. Tsup entry + package.json export added under./manifest/upstream-layout. Unit tests insrc/manifest/__tests__/upstream-layout.test.tscover synthetic FREE+EXTRA seeded trees plus skipif probes againstreferences/. 10 new tests; 305 total green. - RB1 —
bootstrapKayKitAssets({source, out, edition?, force?, includeSourceFormats?, outRoot?, fetchedAt?, libraryVersion?})shipped atsrc/bootstrap/(barrel +bootstrap.ts+bootstrap-target.ts). Two source modes:{kind: 'github', commit?}streamscodeload.github.com/.../tar.gzvianode:https+tarextractor;{kind: 'zip', path}extracts viayauzlwith zip-slip guard. gltf-only filter (.gltf,.bin,.png,.jpg);--include-source-formatsopt-in for.fbx/.obj/.mtl. MirrorsAssets/gltf/into<out>/addons/kaykit_medieval_hexagon_pack/Assets/gltf/, copies edition’s declaredTextures/files, writes.bootstrap.jsonintegrity sidecar (sha256 + bytes per file).verifyBootstrap(outRoot)re-hashes and returns drift list. Out-path jail rejects escape attempts. Surface re-exported from umbrella +./bootstrapsubpath. Smoke tests cover the public shape (6 tests); RB5 covers extraction. tar + yauzl added as runtime deps. - RB2 —
bootstrapCLI subcommand wired intosrc/cli/cli.ts. Flags:--out(default heuristic prefers existingpublic/assets/models→assets/models→ cwd; always routes throughsafeResolveOutputjail),--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;--jsonemits the structuredBootstrapResultorBootstrapVerificationReport. 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_ROOT→process.env.HEX_WORLDS_ASSET_ROOT→public/assets/modelsdefault.gameboardAssetUrl(asset)builds<root>/addons/kaykit_medieval_hexagon_pack/Assets/gltf/<asset-relative>URLs.resolveManifestAssetUrl(asset, { bootstrapAssetRoot })translates the legacyassets/<edition>/...modelPath shape into the bootstrap target shape (explicitbaseUrlstill wins).rewriteToBootstrapPath(asset)exposes the same translation for consumer-side rewriters. Constructor-levelcreateWorld({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. - RB4 —
package.json#filesalready gates toassets/free/manifest.jsononly (noassets/free/**wildcard);package.json#exportsis already locked to./assets/free/manifest.json(not the wildcard./assets/free/*).scripts/audit-package.tsstrengthened: it now rejects ANY.gltf/.bin/.fbx/.obj/.mtlin the tarball regardless of path, and restricts.pngtodocs/showcases/. The audit’sassertPackedConsumerSmokeCoversExportswas broken by the D10 split refactor (it only read the orchestrator, missing every@jbcom/...import that moved toscripts/smoke/{pack-install,types,_shared}.ts); fixed by recursively walking local./...imports and concatenating sources for the AST scan. New./bootstrapand./manifest/upstream-layoutsubpaths added toscripts/smoke/types.tsso the audit recognizes them as covered. Package description updated to reflect bootstrap-not-bundle reality. Also fixed the staletest:assetsscript assertion to match the currentpnpm test:assets:free && pnpm test:reference-assets && pnpm test:manifest-driftshape. Audit now passes. 325 tests green. - RB5 —
src/bootstrap/__tests__/bootstrap.test.ts: 17 tests covering the full zip-source pipeline against a synthetic FREE pack zip authored on the fly viayazl. Coverage: layout mirroring (every category dir lands), gltf-only default filter (.fbx/.obj/.mtl excluded),includeSourceFormatsflag, integrity sidecar shape (schemaVersion + per-file sha256 + bytes, sorted),verifyBootstrapOK + tampering + missing-sidecar paths, idempotent re-run withforce=trueproduces byte-identical sidecar, refuses non-empty target withoutforce, 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 withlibraryVersion+fetchedAtoverrides, directory-shape preservation.yazl+@types/yazladded as devDeps. 342 tests green. - RB6 —
tests/integration/bootstrap/bootstrap-end-to-end.test.ts. Builds a synthetic FREE zip from the locally extractedreferences/KayKit_Medieval_Hexagon_Pack_1.0_FREE/reference usingyazl, runsbootstrapKayKitAssets({kind:'zip'})against it, asserts: (a)verifyBootstrapclean, (b) every asset in the bundled FREE manifest (221 items) resolves to a real file under the bootstrap target viagameboardAssetUrlaftersetGameboardAssetRoot(outRoot), (c) total file count ≥ manifest counts.total. Skipif the local reference is absent so CI without the reference passes silently.vitest.config.tsextended to includetests/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) + manualworkflow_dispatch. Runspnpm install+pnpm build+node dist/cli.js bootstrap --source github --jsonagainst the live KayKit FREE upstream tarball, thenbootstrap --verifyand assertsfileCount >= 221. Uploads the JSONBootstrapResultas 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) anddocs-site/src/content/docs/guides/asset-bootstrap.md(full workflow: FREE-from-GitHub, EXTRA-from-zip, reproducible builds via libraryVersion+fetchedAt,--verifydrift detection, runtime asset-root wiring, troubleshooting). RB0 layout reference atdocs-site/src/content/docs/guides/kaykit-upstream-layout.md(moved out ofreference/because that subdir is typedoc-owned). Fixed a typedoc gotcha — relative.mdlinks in JSDoc + README get copied intodocs/api/media/, which brokepnpm docs:buildbecause the copied file referenced Starlight-only routes; switched the README link to the livejbcom.github.io/.../guides/asset-bootstrap/URL.pnpm docs:build+pnpm test:packageboth pass. 343 tests green.
Phase A — foundation gates
Section titled “Phase A — foundation gates”Determinism additions to CI (user direction 2026-05-26):
-
A9: Install once + persist as artifact. Run
pnpm install --frozen-lockfilein exactly ONE job, uploadnode_modulesas 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 auditmoderates viapnpm.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+verbatimModuleSyntaxintsconfig.base.json. (4a-H, S-context) -
A3— REJECTED. 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 ofdist/. Replacement isA3b. -
A3b — ✅ commit (2026-05-26): manifest integrity gate + warm-start bench landed.
scripts/audit-manifest-drift.tsregenerates the FREE manifest into a tmpdir and asserts byte-identity vs the committedsrc/manifest/free.ts+assets/free/manifest.json. Chains intopnpm test:assets, so CI runs it on every PR; absentreferences/(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 attests/perf/warm-start.bench.tsexercises blueprint → board → koota runtime → facade snapshot path; baseline measures 27 Hz / 37 ms mean on this machine viapnpm 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=highto package job inci.yml. Addactions/dependency-review-actionsummary 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_TOKENsecret already covers what release-please needs and avoids the per-repo App provisioning toil.cd.yml’s release-please step now usessecrets.CI_GITHUB_TOKENdirectly (no App-conditional, nocreate-github-app-tokenaction). -
A5b— CANCELED (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 inci.ymlsopackage/browser-free/docsjobs depend oncheck. -
A7 ✅ commit 5144db8 (2026-05-26) — Add semgrep
p/owasp-top-ten+p/nodejsCI 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.tsexportsCOVERAGE_THRESHOLDS;pnpm test:coverage:enforceruns vitest with thresholds active; CIcheckmatrix 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):
- Drift gate —
scripts/audit-manifest-drift.ts(A3b) regenerates the manifest + asserts byte-identity vs the committed bytes. Runs inpnpm test:assets→ CI. - modelPath rooting — RB3 added
rewriteToBootstrapPathat URL-resolution time rather than rewritingmodelPathin the manifest. Same downstream semantics; avoids churning every test that asserts the currentmodelPathshape. The manifest’smodelPathstays atassets/free/...; consumer-facing URLs resolve through the bootstrap-aware rewriter. - 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.
- Drift gate —
-
B2— REJECTED.freeManifeststays 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 alongsidefreeManifestfromsrc/manifest/free.ts(autogenerated bywriteManifestModule; drift gate updated). Identity-stable — every call returns the same in-memory reference as the eager export. Surface added tosrc/index.tsumbrella 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 dynamicimport('./commands/<name>')per command andimport('./usage')for--help/unknown — headless paths never pull_shared.ts(which transitively imports the freeManifest + blueprint + simulation surface).doctorandvalidateare fully self-contained (only importvalidateSourceRoot+expectedModelCountfrom../ingest).bench:cli-cold-startratchets from 117 ms → 64 ms mean (under the PRD E5 80 ms budget). Tests at 353/6 unchanged;test:cli+test:consumerclean. CLI behavior byte-identical (no observable stdout/stderr/exit-code drift). - B4 — ✅ commit (2026-05-26):
gameboardPlanIndex(plan)helper materializestilesByKey+placementsByTileonce per plan via a module-local WeakMap. The 6 in-callnew Map(plan.tiles.map(...))rebuilds incoordinates/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 —GameboardPlanshape is identical; the index is derived. - B5 (part 1) ✅ commit (2026-05-26) — P-H1: Single-pass
readGameboardActorTargetsreducer (actors.ts:940-953+:1085-1093). - B6 ✅ commit (2026-05-26) — P-H2: Add
tryParseHexKey(key): HexCoordinates | undefinedincoordinates.ts; migrateinterop.ts/scenario.tstry { parseHexKey } catch { undefined }sites. - B7 — ✅ commit (2026-05-26):
useStableOptions<T>(options)hook added tosrc/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(...))insimulation.ts:5212withstructuredClone.
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 tosrc/cli/cli.ts. Jail root defaults toprocess.cwd(); tests/smoke harnesses opt into a wider jail viaHEX_WORLDS_OUT_ROOT. 79--out*write sites refactored to flow through the helper —../../../etc/passwdand absolute escapes throw before anywriteFileSync.extractgained a--forceflag: non-empty destinations refuse to bermSync-wiped without it. Behavior tests pin both the jail escape and--forcesemantics. - C2 ✅ commit (2026-05-26) — S-H2: Harden
listFilesiningest.ts:310— skipentry.isSymbolicLink(), verifyrealpathSync(child).startsWith(realRoot)before descending. Cycle-safe. - C3 ✅ commit (2026-05-26) — S-M1: Prototype-pollution guard in
readPieceSourceRoots; returnObject.create(null)-backed map; useJSON.parsereviver to strip__proto__. - C4 ✅ commit b3837f0 (2026-05-26) — S-M2: Fix
extract-kaykit-guide.ts:129sh -cquoting via positional args. - C5 — ✅ commit (2026-05-26):
relativizePath()helper added tosrc/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"infilesorsourcemap: falsefor 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.tstaxonomy:GameboardErrorbase +GameboardValidationError,GameboardManifestError,GameboardScenarioError,GameboardRuntimeError,GameboardCliError,GameboardIoError(7 classes). Migrated 152throw 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. Umbrellasrc/index.tsre-exports all seven. Test filesrc/errors/__tests__/errors.test.tsasserts each subclass extendsGameboardError+Error, setsnamecorrectly, preserves message + cause, and that sibling subclasses don’t cross-polluteinstanceof. - D3 ✅ (2026-05-26) — H-3 (re-scoped): Decomposed
simulation.ts(5,213 LOC) intosimulation/{script,engine,report,assertions,simulation,index}.ts.simulation.tsis 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 withassertNeverexhaustiveness on the action switch,runGameboardScenarioSimulation*entry points, patrol route-to-step helpers),report.ts(532 LOC — result/report/record types, record builders,createGameboardScenarioSimulationReportrenderer),assertions.ts(780 LOC —evaluate*/assert*expectation primitives). Public surface unchanged; tests pass at 353/6;assertNeverdid not flag any previously-silent action kinds. - D4 ✅ (2026-05-26) — M-4: Inverted
catalog.ts createKayKitGuideScenariosinto top-levelKAYKIT_GUIDE_SCENARIO_TABLEliteral (19 rows, one uniformKayKitGuideScenarioInputshape) + 3-linecreateKayKitGuideScenarios()that mapsguideScenarioover 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.tsumbrella that re-exports every trait with a table-of-contents docblock. Addsrc/actions.tsumbrella for*Actionssymbols. -
D6— REJECTED. 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
peerDependenciestodependencies; umbrellasrc/index.tsre-exportsreact/andthree/sub-packages alongside every other domain. Updatedocs/guides/peer-deps-and-bundling.mdaccordingly (becomesdocs/guides/bindings-and-bundling.md). -
D7— STALE. RB will eliminate the asset-related scripts; the remaining workspace audits live atscripts/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) intopackages/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. -
D9— STALE (2026-05-26): the original D9 wanted to split a 1,293-LOC workspace audit. R1’s de-monorepo rewrite collapsedscripts/audit-workspace.tsto 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) intoscripts/smoke/pack-install.ts(runtime smoke),scripts/smoke/types.ts(compile-time API attestation),scripts/smoke/_shared.ts(context + assert helper), and a thinscripts/smoke-packed-consumer.tsorchestrator (~80 LOC) with labelled-phase output.pnpm test:consumerclean; 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.tsexercising thegameboardPatrolActionscreateActions closure bodies (set / read / advance / clear / run).patrol.tscoverage 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.ts78.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+.targetCommandaction methods.commands.tscoverage: 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-uncoveredcreateActionsclosure bodies in lines 478-487).systems.tscoverage: 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.tscoverage moved from 88.9 / 53.3 / 100 / 88.2 to 100 / 80 / 100 / 100 (statements, branches, functions, lines). Added 4 branch-case assertions insrc/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 innerBoolean(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
inspectMedievalHexagonManifesterror-branch tests (non-object input, missing assets array, null + undefined).manifest/schema.tscoverage: 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. -
E0i— CANCELED (2026-05-26): the original E0i wantedexamples/simple-rpg-usage.tsto 100 % coverage. R4 relocated that file totests/integration/simple-rpg/simple-rpg.ts; per PRD RS1 + RS3 SimpleRPG is a test driver, not a measured surface. The shared coverage config already excludestests/**, which is correct: SimpleRPG drives coverage ofsrc/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 viapnpm 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.tslands. Spawns N=4 Node subprocesses viapnpm exec tsx --eval, each runningcreateSeededGameboardPlanwith 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 defaultpnpm testloop; ~3.6 s total wall-clock at N=4. - E2 — ✅ commit (2026-05-26):
tests/unit/public-api.test.tslands.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.tslands. 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)listFilessymlink-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.tslands. ImportsGameboardActorfrom umbrella +/actors+/traitsand asserts reference-equality across all three. Same forIsGameboardTilefrom umbrella +/koota+/traits. Pins PRD invariant §6.7 (splitting: true+ trait identity contract). Ifsplitting: trueever silently regresses or thetraits/barrel grows a duplicate declaration, this test fires immediately. - E5 — ✅ commit (2026-05-26):
tests/perf/cli-cold-start.bench.tslands. Runsnode dist/cli.js --helpvia 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. Addpnpm bench:cli-cold-startscript. - E6 — ✅ commit (2026-05-26):
tests/perf/simulation.bench.tslands. 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.tslands. MountsGameboardProvider+ a child that callsuseGameboardActorSelection({})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’suseStableOptionswould cascade into infinite useMemo cache busts in real consumer code). Test lives co-located under src/react/tests/ per R3b. Addedjsdom+@testing-library/reactdevDeps; vitest runs the file with@vitest-environment jsdompragma — 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] E9 — Visual 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 viapnpm test:browser:free+test:browser:extra; drift is a blocked merge. Snapshots live intests/browser/__screenshots__/. Browser-free job currently gated byvars.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 rootpnpm-workspace.yaml? NO — we removed workspaces. Instead:docs-site/runs its ownpackage.jsonbut uses pnpm with--filterworkarounds gone; install runs as a siblingpnpm installinsidedocs-site/driven by a rootdocs-site:installscript. Wire root scripts:docs-site:dev,docs-site:build,docs-site:preview. Don’t kill the existingdocs/vitepress site yet — it stays until F-Site-9.- Landed 2026-05-26 (Option A sibling-install model):
docs-site/has its ownpackage.json+pnpm-lock.yaml, Astro 6 + Starlight 0.39. Rootpackage.jsonexposesdocs-site:install/dev/build/previewthatcd docs-site && pnpm .... Root.gitignorebelt-and-braces excludesdocs-site/node_modules,dist,.astro. Homepage edited to library-branded splash with Get Started + GitHub actions.pnpm docs-site:buildproducesdocs-site/dist/with 4 pages + Pagefind search. Library suite unchanged at 288 passed / 7 skipped.
- Landed 2026-05-26 (Option A sibling-install model):
- F-Site-2 — ✅ commit 3940190 (2026-05-26):
docs-site/astro.config.mjsconfigured 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_SITEenv vars let CI control the Pages base path. - F-Site-3 — Wired
starlight-typedoc+typedoc+typedoc-plugin-markdownintodocs-site/.astro.config.mjsregistersstarlightTypeDocwith 40 entryPoints mirroringtsup.config.ts(every published subpath inpackage.json#exports). Dedicateddocs-site/tsconfig.typedoc.jsonextendstsconfig.base.jsonwithignoreDeprecations: "6.0"(avoids the TS 7.0baseUrldeprecation) and drops the path mappings typedoc doesn’t need. Generatedsrc/content/docs/reference/is gitignored.excludeInternal+excludePrivatekeep internal symbols out. Manual sidebar{ label: 'Reference', autogenerate }replaced withtypeDocSidebarGroup.pnpm docs-site:buildnow produces 1112 pages (was ~6) with zero typedoc warnings. - F-Site-4 — ✅ commit (2026-05-26):
docs-sitejob added toci.yml. Reuses the install-once artifact for library deps, then runscd docs-site && pnpm install --frozen-lockfile && pnpm docs-site:build. Uploadsdocs-site/dist/as a Pages artifact so cd.yml can deploy it. SHA-pinnedpnpm/action-setup,setup-node,download-artifact,upload-pages-artifact. - F-Site-5 — ✅ commit (2026-05-26):
cd.ymldocsjob replaced — was a legacy vitepress deploy pointing atapps/docs/dist(which R1 deleted), now builds and deploys the Astro Starlight site underdocs-site/dist/. SetsASTRO_BASE=/declarative-hex-worlds/andASTRO_SITE=https://jbcom.github.io/declarative-hex-worldsso the Pages base path is correct.audit-workflows.tsupdated to expectpnpm docs-site:buildin cd.yml instead of the legacy vitepress invocation. - F-Site-6 — ✅ commit (2026-05-26):
docs-site/src/content/docs/guides/cli-reference.mdgenerated byscripts/generate-cli-reference.tsfrom the live CLI--helpoutput. Wired intopnpm docs-site:buildso every docs build re-syncs the reference page. The fullusage()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.mdwritten. 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.mdwritten. Subpath imports + when to use umbrella, react/three/koota direct-deps reasoning (not peer-deps), trait identity hazard + howsplitting: 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.mdrather thanreference/errors.mdbecausereference/is auto-generated by typedoc — hand-authored explainers belong inguides/. Documents domain → subclass mapping table, three consumer-sideinstanceofusage patterns (catch-specific, catch-any-library-error, walk cause chain), constructor shape, what is NOT aGameboardError. - F-Site-10 — ✅ commit (2026-05-26):
docs-site/src/content/docs/guides/asset-bootstrap.mdlanded during RB8. - F-Site-11 — ✅ commit (2026-05-26):
docs-site/src/content/docs/guides/getting-started.mdlanded 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). Removedvitepress+vuedevDependencies. Killed thedocs:buildscript (waspnpm run docs && vitepress build ...); theverifychain now callspnpm docs(typedoc only). CI workflow,audit-workspace.tsscript-presence assertion,audit-package.tsverify-chain assertion,audit-workflows.tsci.yml-step assertion all updated.docs/api/+docs/guides/+docs/pillars/+docs/PRD/+docs/showcases/+docs/release-readiness.json+docs/index.mdall 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/docsalready deleted in R1.
Sub-epic F-Gallery — SimpleRPG-driven feature pages with embedded screenshots
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.tsharness wired intovitest.browser.free.config.ts. BootscreateFixedSimpleRpgGame()(RS3), projects viaprojectWorldToGameboardPlan, renders into Chromium viarenderGameboardPlan, writes screenshots totests/browser/__screenshots__/feature-gallery/<scenario>.png. Scenario table seeded withfixed-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 showingGameboardBuilder.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 usingGameboardBuilder.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 usingHexTileState.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 usingGameboardActortraits. - 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 usingMovementAgent+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 usingGameboardQuest+ 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.tsupdated 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-harbortoday; hero set (harbors / multi-depth / cross-kit) needs the harness to addmulti-depth-cliff+cross-kit-villagescenarios 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 add→pnpm exec declarative-hex-worlds bootstrap→ minimal Provider + Canvas + tick component. The snippet IS the React component a consumer would copy-paste.pnpm test:readme-snippetcompile-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.mdwritten. Coverspnpm verifyposture, 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.mdwritten 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.mdwritten. 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) andSECURITY.mdwritten. 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.mdaudited. Three stalepackages/declarative-hex-worlds/paths in the invariants section + a “this is a monorepo” claim + anapps/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.mdAFTER 1.0 ships (G8): archive the 1.0 stabilization section intodocs-site/src/content/docs/about/history/1.0-stabilization.md; reset to a slim post-1.0 maintenance directive. Only meaningful oncerelease-pleasecuts 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.md→ KEEP 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 byaudit-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 bypnpm docs(typedoc) and read byaudit-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/*.md→ MIGRATE 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.md→ KEEP. Was vitepress entry; harmless as a plain README within docs/. Will become a redirect note once docs-site/ migration completes.docs/release-readiness.json→ KEEP. Generated ledger consumed by tests + CLI coverage.
-
F-Audit-7b— CANCELED (2026-05-26): the 6docs/guides/*.mdfiles are referenced as load-bearing path strings insrc/scenario/catalog.ts(KayKit guide scenario metadata),scripts/audit-reference-assets.ts, andscripts/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). Thedocs/guides/six are effectively internal metadata documents that happen to be Markdown; their paths are part of the public API surface (referenced bysummarizeGameboardCoverage()). Leaving them where they are is the correct decision. - F-Audit-8 — ✅ commit (2026-05-26):
examples/README.mdwritten. 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/*.jsonships, .ts doesn’t — consumers use the./examples/blueprint-board-usagedist 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.mdwritten. 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.mdwritten. 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.mdwritten. 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.mdwritten. 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.mdwritten. 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.tslands. Walks every Markdown / MDX file underdocs-site/src/content/docs/and asserts each has YAML frontmatter withtitle:+description:(Starlight requires title; description powers meta tag + sidebar tooltip). Auto-generatedreference/pages are skipped (typedoc owns them). Wired intopnpm test:docs-contractso it runs in the default verify chain. Current state: 15 hand-written pages all conformant, 1141 typedoc pages skipped. Root-level.mdfiles (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 usedCanvasfrom@react-three/fiberbut that’s not a library dependency. Patched the README with an explicit note that@react-three/fiberis an optional consumer-installed companion, with the/threesubpath 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.ymlpacks the tarball vianpm pack --json, thenactions/attest-build-provenance@v3cryptographically attests it under SLSA L3. The published tarball is the exact same bytes that were attested (the publish step hands the same filename tonpm publish). Consumers verify vianpm audit signaturesorgh attestation verify.permissions: attestations: writeadded. - G2 — ✅ commit (2026-05-26): same
release.ymlrunsnpx @cyclonedx/cyclonedx-npmto produce a CycloneDX 1.6 JSON SBOM of the prod-dep tree. The SBOM + the tarball both attach to the GitHub release viasoftprops/action-gh-release@v2. Release consumers get a complete dependency manifest for SCA tooling. - G3 — ✅ commit (2026-05-26):
.github/dependabot.ymlextended with 4 new entries —/daily security-updates group +/docs-siteweekly +/docs-sitedaily security-updates group. Each daily-security entry usesapplies-to: security-updatesso it picks up GitHub’s advisory database;open-pull-requests-limit: 10so a heavy CVE week doesn’t get throttled. PRs from these channels carry thesecuritylabel for filtering. - G4 — ✅ commit (2026-05-26):
pnpm verifybrought to CI parity. Addedpnpm audit --prod --audit-level=high(was CI-only in the package job) andpnpm 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 defaultpnpm verifychain 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 verifyruns clean end-to-end oncodex/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.tsupdated 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), setrelease-as: "1.0.0"inrelease-please-config.json’s package config. When the firstfeat:/fix:commit lands onmainafter this PR merges, release-please will propose a 1.0.0 release PR. The manifest version (.release-please-manifest.json) stays at0.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-2→main) 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, seesrelease-as: "1.0.0"(PRD G6), opens a release PR with the 1.0.0 changelog. Maintainer merges the release PR → release-please tagsv1.0.0(novprefix per config) →release.ymlfires 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-worldsreports the provenance attestation, the release page shows the SBOM + tarball assets.
Self-assessment after each commit
Section titled “Self-assessment after each commit”Before flipping [ ] → [x]:
- What did I just ship? Did the visual / behavior match the spec doc?
- What did the just-finished work surface about the next item? Encode in directive notes if non-trivial.
- Did this commit introduce any banned pattern? (run gates locally —
pnpm lint && pnpm typecheck) - 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.mdexplains 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.