r reckon

reckon build — CI static site generation

shipped mid ROI M effort S5

Portable static delivery is shipped: the reckon-plans wheel carries canonical assets, the repository workflow validates without publishing, and distributed project resources replace the monolithic index as the canonical write store.

§ 1 — Problem

The reckon server (v8+) serves UI assets via canonical routes: /_ui/<file> and /_shared/<file>. Generated index.html uses these absolute paths. This works perfectly with the live server but breaks for static deployment — GitHub Pages has no reckon process to serve those routes.

Additionally, sprint and milestone definitions currently require authoring index.json by hand. This is the last piece of wiring that plans do not have: plans auto-discover from HTML meta tags; sprints and milestones do not.

✓ landed 2026-07-29

§ 2 — reckon build command

Packaged all 16 UI and 4 shared assets inside the reckon-plans wheel and made reckon build fail loudly when assets are missing. Installed-wheel smoke generation produced relative paths, .nojekyll and usable static state. Full record: §2–§6 landed (commits e827e56, 948b2c5).

✓ landed 2026-07-29

§ 3 — Sprint and milestone discovery from HTML

Verified dedicated sprint/milestone HTML discovery and excluded definition pages from plan inventory; static builds preserve authored sprint, milestone, timeline and blocker state. Distributed semantic resources now own canonical writes, while the frozen legacy index is retained only for compatibility and rollback. The dissent review rejected reopening because the original index-json-fate → deprecate entirely lock is now realized. Full record: passing result.

✓ landed 2026-07-29

§ 4 — CI YAML template

Replaced the wrong-package pip workflow with uv-native generation: consumer workflows use uvx --from pinned to v0.2.0rc25, while Reckon’s own Pages workflow builds its checked-out SHA with uv run --frozen. Full record: CI evidence.

✓ landed 2026-07-29

§ 5 — reckon as a versioned package

Renamed only the distribution to reckon-plans, retaining the reckon import and console command. The verified wheel contains 35 entries, including every SPA asset; tag v0.2.0rc25 pins the consumer workflow. Publishing to PyPI remains deliberately outside this sprint. Full record: package evidence.

§ 6 — Implementation review & recommendations (2026-05-27, Opus)

6 of 6 actions landed Distribution naming, installed-wheel assets, uv CI, build tests, release tagging, and distributed project state are complete. The historical analysis below is retained as the record that motivated the package and storage corrections. Final evidence.

Reviewed the shipped code against this plan. The build command, sprint/milestone HTML discovery, and the sync --generate-ci flag all landed. But the CI install story (§4 + §5) is not deployable as written — three blockers, detailed below. The driving fix is the user's instinct: CI should pull reckon in as a uv-managed dependency, not pip install reckon.

§ 6.1 — Section-by-section status

§ItemStateEvidence / gap
§2reckon build command✅ landed (from source) cli.py build(); smoke test copies 12 _ui/ files + _shared/, writes relative-path index.html, drops .nojekyll, bakes index.json inventory. Only works from a source checkout (see §6.2-B).
§3Sprint/milestone discovery⚠️ coded, unexercised discover_plans() scans docs/sprints/*.html + docs/milestones/*.html (serve.py). No such files exist in any repo yet, so the path has never run with real input.
§3Eliminate index.json❌ contradicts code Locked decision index-json-fate = "deprecate entirely", but both sync and build still write index.json, and discover_plans() still falls back to it. Decision is not realised.
§4CI YAML template⚠️ landed but broken sync --generate-ci writes .github/workflows/reckon-pages.yml, but it uses setup-python + pip install reckon — wrong package and wrong tool (see §6.2). reckon's own repo has no committed workflow to deploy its plans.
§5Versioned PyPI package❌ blocked Name reckon is taken on PyPI by an unrelated caching library; 0 git tags, so hatch-vcs yields 0.1.devNN; no release workflow. pip install reckon==1.2.3 is impossible as written.
Tests❌ missing tests/ holds only MCP tests; no tests/test_build.py.

§ 6.2 — The three blockers

A. PyPI name collision (§4, §5). https://pypi.org/project/reckon/ resolves to "reckon: Dead simple, dynamic caching" by Sean Stewart — not this project. Any CI that runs pip install reckon installs that package, and reckon build is simply not found. The distribution must be renamed to an available name (e.g. reckon-plans) while keeping the reckon import package and reckon console-script unchanged.

B. The wheel does not ship the UI assets (§2, §4). build copies assets from Path(__file__).parent.parent / "docs" / {ui,_shared}, but the wheel ([tool.hatch.build.targets.wheel] packages = ["reckon"]) contains only reckon/*.py — verified: 11 entries, no docs/. So from an installed wheel the if ui_src.is_dir() guard is false and asset copying is silently skipped — producing a broken static site with no error. build only works today when run from a git checkout (where docs/ui/ exists beside the package). Fix: ship the assets in the wheel via hatch force-include, or move them under reckon/_assets/ and resolve with importlib.resources.

C. CI is pip-based, not uv-managed (§4) — the user's ask. Even after A+B, the workflow should use uv for reproducibility and to match how reckon is run everywhere else on this host (uv run --project ~/Code/reckon reckon …).

§ 6.3 — Recommendation: bring reckon in as a uv-managed CI dependency

Variant 1 — works today, no packaging changes (source checkout via uv). Because the wheel omits assets (blocker B), the only correct install is from source. Check out reckon as a second repo and run it through uv from that tree — pin with ref: for reproducibility:

name: Deploy plans to GitHub Pages
on: { push: { branches: [main] }, workflow_dispatch: {} }
permissions: { contents: read, pages: write, id-token: write }
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4                 # the project whose docs/ we publish
      - uses: actions/checkout@v4                 # reckon itself (ships docs/ui + docs/_shared)
        with: { repository: Simon-McIntosh/reckon, ref: v0.2.0, path: .reckon-src }
      - uses: astral-sh/setup-uv@v6
      - run: uv run --project .reckon-src reckon build "$GITHUB_WORKSPACE/docs"
      - uses: actions/upload-pages-artifact@v3
        with: { path: docs/ }
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: { name: github-pages, url: "${{ steps.deployment.outputs.page_url }}" }
    steps:
      - uses: actions/deploy-pages@v4
        id: deployment

Variant 2 — after blockers A + B are fixed (packaged + published). Once assets ship in the wheel and the dist is renamed, the install collapses to one uv line, pinned to a published version:

      - uses: astral-sh/setup-uv@v6
      - run: uvx --from "reckon-plans==0.2.0" reckon build docs/
        # or, git-pinned without a PyPI release:
        # uvx --from "git+https://github.com/Simon-McIntosh/reckon@v0.2.0" reckon build docs/

A consuming repo that already uses uv can instead declare reckon in its own pyproject.toml under [tool.uv.sources] (git + ref) and call uv run reckon build docs/ — same lockfile-pinned reproducibility, no extra checkout step.

§ 6.4 — Recommended next steps (queued as the followup below)

  1. Open a dist-name decision and rename the distribution off the squatted reckon name.
  2. Ship UI assets in the wheel (hatch force-include or reckon/_assets/ + importlib.resources); add a wheel-install smoke test.
  3. Rewrite the generated CI template to uv (Variant 1 now; Variant 2 after 1+2), and commit a copy for reckon's own Pages deploy.
  4. Add tests/test_build.py covering asset copy, relative paths, .nojekyll, inventory.
  5. Reconcile index-json-fate: realise "deprecate entirely" or reopen the decision via the dissent flow — today it is locked but unimplemented.
  6. Cut the first git tag so hatch-vcs emits a real version.

§ Decisions

CLI verb for the static-build command

Where do sprint definition HTML files live?

Once sprint discovery lands, what happens to index.json?

What delivery policy applies while reckon remains a local project?

Build and test automation are useful, but pushing docs or packages to a public service is premature.

Reckon is currently a local project. Retain build and test validation, but hold back GitHub Pages and package publishing until explicitly revisited.

What distribution name should replace the temporary reckon-plans metadata?

The installed distribution identity must match pyproject.toml, wheel metadata, README guidance, and the landed build evidence. The import package and console command remain reckon; public publication remains a separate policy decision.

The built and tested wheel, pyproject.toml, README, and landed evidence all identify the distribution as reckon-plans. Aligning the plan with that authoritative artifact requires no executable change; the Python import package and console command remain reckon.

§ Followups

Implement reckon build command + sprint/milestone HTML discovery

Design and decisions are locked in the plan. Implement: (1) cli.py reckon build command, (2) discover_plans extension for sprint/milestone HTML meta tags, (3) reckon sync --generate-ci flag for CI YAML, (4) index.json elimination once sprint discovery lands.
Project: reckon
Plan:    reckon-ci-build
Section: §2 + §3
Tier:    sonnet

Context
  reckon now serves UI assets from /_ui/ and /_shared/ routes (v8 dynamic server).
  Static deployment to GitHub Pages needs local asset copies. The plan at
  docs/reckon-ci-build.html describes the full design.

State to read
  docs/state/reckon/reckon-ci-build.json

Locked decisions to honour
  None yet — open decisions in the plan must be surfaced first.

Open decisions to surface
  command-name, sprint-discovery-dir, index-json-fate

Done-when
  1. reckon build <docs_path> command in cli.py copies _ui/ + _shared/ and writes
     index.html with relative paths
  2. discover_plans() scans docs/sprints/*.html for sprint meta tags
  3. reckon sync --generate-ci writes .github/workflows/reckon-pages.yml
  4. Tests added for build command
  5. Followup written + this followup resolved

Landed: reckon build command (cli.py) copies _ui/ + _shared/ assets, writes relative-path index.html, drops .nojekyll, writes plan inventory to index.json. reckon sync --generate-ci writes .github/workflows/reckon-pages.yml. discover_plans() extended to scan docs/sprints/*.html and docs/milestones/*.html for sprint/milestone meta tags. Committed 5c9a71c. Tests and q1/q2 resolution remain.

Add tests for reckon build command + resolve q1 (state.js copy)

build command and sprint/milestone discovery are implemented. Remaining: (1) tests/test_build.py covering reckon build output correctness, (2) resolve q1 — confirm whether reckon build should copy state.js to _shared/ alongside the CSS (check what standalone plan pages need), (3) resolve q2 — already resolved by implementation: docs/sprints/ + docs/milestones/.
Project: reckon
Plan:    reckon-ci-build
Tier:    sonnet

Context
  reckon build command landed in commit 5c9a71c. It copies _ui/ + _shared/ assets,
  writes a relative-path index.html, and writes the plan inventory to index.json.
  reckon sync --generate-ci writes .github/workflows/reckon-pages.yml.
  Sprint/milestone HTML discovery (docs/sprints/*.html, docs/milestones/*.html) is live.

State to read
  docs/state/reckon/reckon-ci-build.json

Locked decisions to honour
  sprint-discovery-dir → docs/sprints/ + docs/milestones/ (implemented)

Open decisions to surface
  q1: should reckon build also copy state.js to _shared/ for legacy standalone pages?

Done-when
  1. tests/test_build.py covers: asset copy correctness, index.html relative paths,
     .nojekyll presence, index.json inventory written
  2. q1 resolved (check standalone plan page needs)
  3. impl updated to 1.0, status → shipped if tests pass
  4. This followup resolved with outcome

Build coverage landed in e827e56: 11 focused tests cover assets, relative paths, .nojekyll, state preservation, wheel contents and installed-wheel generation; state.js ships in the wheel.

Make reckon CI-installable via uv: rename PyPI dist, ship UI assets in wheel, switch CI to uv

Review on 2026-05-27 surfaced three blockers to the §4/§5 CI story: the PyPI name `reckon` belongs to an unrelated caching package; the wheel excludes docs/ui + docs/_shared so `reckon build` only works from a source checkout; and the generated CI uses pip, not uv. Fix packaging + naming and make CI install reckon as a uv-managed dependency (see §6 recommendations).
Project: reckon
Plan:    reckon-ci-build (http://localhost:8765/reckon/reckon-ci-build.html)
Section: §4 + §5 (+ §2 packaging)
Tier:    sonnet

Context
  2026-05-27 review found the §4/§5 CI install story is broken three ways:
  (1) PyPI `reckon` is an unrelated package (Sean Stewart's caching lib), so
  `pip install reckon` fetches the wrong code; (2) the wheel ships only
  reckon/*.py — docs/ui + docs/_shared are excluded, so `reckon build` from an
  installed wheel copies nothing (works only from a source checkout); (3) the
  generated CI uses pip, not uv. Fix packaging + naming, then make CI pull
  reckon in as a uv-managed dependency.

State to read
  GET /plan/reckon/reckon-ci-build   (decisions, followups, status, version)
  reckon/cli.py (build + sync --generate-ci), pyproject.toml,
  reckon/serve.py discover_plans()

Locked decisions to honour
  command-name          -> build
  sprint-discovery-dir  -> docs/sprints/ + docs/milestones/

Open decisions to surface (do not resolve)
  dist-name        : `reckon` is taken on PyPI -> reckon-plans? pyreckon? reckon-agile?
  asset-packaging  : hatch force-include docs/ into wheel vs move assets under
                     reckon/_assets/ + importlib.resources lookup
  ci-install-mode  : uvx --from git+URL@ref reckon build  vs  second actions/checkout
                     of reckon + `uv run --project` (only this works until assets ship)

Constraints
  MIT licence; keep the `reckon` import package + `reckon` console script names
  unchanged even if the distribution is renamed; hatch-vcs needs >=1 git tag for
  a real version (currently 0.1.devNN, 0 tags).

Done-when
  1. `reckon build` works from an installed wheel (assets resolved via
     importlib.resources or hatch force-include) — verified by
     `uv run --isolated --with <wheel> reckon build <tmp-docs>`.
  2. Distribution renamed to an available PyPI name; README + plan §5 updated.
  3. CI YAML (generated template AND a committed copy for reckon's own docs)
     installs reckon via uv (astral-sh/setup-uv + uvx/uv run), pinned to a
     ref or version — no bare `pip install reckon`.
  4. tests/test_build.py covers asset copy, relative index.html paths,
     .nojekyll, and index.json inventory.
  5. index-json-fate reconciled: code still writes/reads index.json, so either
     realise "deprecate entirely" or reopen the decision via the dissent flow.
  6. followup written into plan + this followup marked resolved.

Packaging and CI landed in e827e56 and 948b2c5: distribution renamed to reckon-plans; 35-entry wheel ships 16 UI + 4 shared assets; uv-native workflows and installed-wheel smoke pass. Project-index dissent remains open separately.

Review the project-index deprecation decision

The locked choice `deprecate entirely` predates the current canonical format. Current evidence shows `docs/state//index.json` is the sole project-level store for sprints, milestones, timeline and blockers; deleting it would contradict the repository contract and break S5 state. Reopen the decision and choose whether to retain the project index while keeping plan inventory live-discovered.
Project: reckon
Plan:    reckon-ci-build (http://localhost:8765/reckon/reckon-ci-build.html)
Section: §3 — project-index contract
Capability: orchestrator-level

Context
  Static build packaging and uv CI can land independently. The locked choice to deprecate index.json now conflicts with the canonical project-state format used by the active sprint.

State to read
  GET /plan/reckon/reckon-ci-build
  docs/state/reckon/index.json
  PLAN-FORMAT.md and AGENTS.md index.json contract

Locked decisions to honour
  index-json-fate → deprecate entirely (under review; do not overwrite silently)
  command-name → build
  sprint-discovery-dir → docs/sprints/

Open decisions to surface (do not resolve)
  Whether the locked index-json-fate choice should be reopened and replaced by retaining project-only state while inventory remains live-discovered.

Constraints
  Preserve the audit trail through /reckon-edit --reopen. Do not delete active sprint, milestone, timeline or blocker state.

Done-when
  1. The lead accepts or rejects reopening with rationale
  2. Any accepted choice is recorded through the dissent workflow
  3. this followup is resolved and the next implementation followup is written

Superseded by f-ci-index-dissent-20260729b because the literal project placeholder in this body was parsed as HTML. The locked decision remains under review.

Review the project-index deprecation decision

The locked choice deprecate entirely predates the current canonical format. Current evidence shows docs/state/{project}/index.json is the sole project-level store for sprints, milestones, timeline and blockers; deleting it would contradict the repository contract. Reopen the decision and choose whether to retain project-only state while inventory stays live-discovered.

Project: reckon
Plan:    reckon-ci-build (http://localhost:8765/reckon/reckon-ci-build.html)
Section: §3 — project-index contract
Capability: orchestrator-level

Context
  Installed-wheel assets, build tests and uv CI are landed and verified. The sole remaining blocker is the locked choice to deprecate index.json, which now conflicts with the canonical project-state format.

State to read
  GET /plan/reckon/reckon-ci-build
  docs/state/reckon/index.json
  PLAN-FORMAT.md and AGENTS.md project-index contract

Locked decisions to honour
  index-json-fate → deprecate entirely (under review; do not overwrite silently)
  command-name → build
  sprint-discovery-dir → docs/sprints/

Open decisions to surface (do not resolve)
  Whether to reopen index-json-fate and retain project-only sprint, milestone, timeline and blocker state while inventory remains live-discovered.

Constraints
  Preserve the audit trail through /reckon-edit --reopen. Do not delete active project state. Packaging and CI commits are already landed; do not relitigate them.

Done-when
  1. the lead accepts or rejects reopening with rationale
  2. any accepted choice is recorded through the dissent workflow
  3. this followup is resolved and the next implementation followup is written

Reopening was rejected after the distributed-state implementation changed the evidence. The original deprecate entirely lock is now realizable: independently versioned sprint, blocker, milestone, timeline, and identity-only project resources are canonical; the frozen legacy index remains only a compatibility and rollback artifact until fleet migration. The original lock and this dissent outcome preserve the complete audit chain.

Reconcile the installed distribution identity

The locked reckon choice conflicts with the installed package contract already shipped in pyproject.toml, the README, the built wheel, and the landed evidence. Preserve the old lock, reopen it through the dissent flow, and select the authoritative reckon-plans distribution while retaining the reckon import and console command.

Project: reckon
Plan:    reckon-ci-build (http://localhost:8765/reckon/plans/reckon-ci-build)
Section: §5 — installed distribution identity
Capability: orchestrator
Requirements: deep reasoning, extended context, autonomous tools, strict verification, elevated risk

Context
  The installed wheel and package metadata already use reckon-plans, while a later plan lock records reckon.
  Reconcile that contradiction through the dissent flow without erasing the old decision record.

State to read  (CODE / FILES / DATA — the builder already injects the plan URL)
  pyproject.toml, README.md, docs/archive/reckon-ci-build-s2-s6-landed.html
  git show e827e56 --stat and the current wheel/build test evidence

Scope locks / constraints  (non-decision)
  Keep the Python import package and console command named reckon.
  Automatic publication remains out of scope; preserve the validation-only repository workflow.

Done-when
  1. the old lock is archived, reopened, and relocked to the authoritative installed distribution
  2. package, README, plan and evidence identities agree
  3. followup written + this followup marked resolved

Accepted. The prior reckon lock is preserved in docs/archive/reckon-ci-build-distribution-name-locked.html. The decision was reopened and relocked to reckon-plans, matching pyproject.toml, README guidance, wheel metadata, and landed build evidence; the import package and console command remain reckon.

Close portable build delivery

The installed wheel, validation-only repository workflow, distributed project state, and package identity now agree. This record closes the plan without queuing additional build work; later readable-response and fleet-migration work remains owned by its separate plans.

Project: reckon
Plan:    reckon-ci-build (http://localhost:8765/reckon/plans/reckon-ci-build)
Section: closure verification
Capability: general
Requirements: standard reasoning and context, guided tools, strict verification, low risk

Context
  Portable static delivery, installed-wheel assets, validation-only local CI, and distributed project state are shipped.
  This prompt exists as the terminal verification record; subsequent work is tracked by separate plans.

State to read  (CODE / FILES / DATA — the builder already injects the plan URL)
  pyproject.toml, .github/workflows/reckon-pages.yml, tests/test_build.py
  docs/evidence/archive/distributed-sprint-state-storage-landed.html
  docs/evidence/archive/reckon-ci-build-closure.html

Scope locks / constraints  (non-decision)
  Do not add automatic publication. Keep the import package and console command named reckon.
  Treat the legacy index as a frozen compatibility and rollback artifact, never as a write target.

Done-when
  1. plan, package, CI and distributed-state evidence remain mutually consistent
  2. validation and audits remain green
  3. followup written + this followup marked resolved

Done — no followup. Portable build delivery is closed; subsequent sprint work remains in its own plans.

§ Open questions

Should reckon build copy state.js to _shared/ (needed for legacy standalone pages) or only the CSS?

Yes — verified 2026-05-27. reckon build copies the whole docs/_shared/ dir (foundation.css, dashboard.css, state.js) plus all 12 docs/ui/ files into _shared/ and _ui/. state.js is included, so legacy standalone pages render. (Note: this works only from a source checkout — the wheel does not ship these assets; see new followup.)

What directory convention for sprint/milestone definition HTML files? docs/sprints/, docs/_meta/, or alongside plans in docs/?

docs/sprints/*.html + docs/milestones/*.html — implemented in discover_plans() (serve.py). Matches locked decision sprint-discovery-dir. No example files exist yet, so the path is untested in practice.