Skip to content

feat: Shared docs tooling package with a blocking translation gate #138

Description

@mairas

Target repos: this plan spans four repositories. Each implementation unit
names its own target; file paths are relative to that unit's target repo.

Repo Role
halos-org/docs-tools new, public — the package
halos-org/shared-workflows gains one reusable workflow
halos-org/docs.halos.fi first consumer, anchors only
hatlabs/halpi2 full consumer, blocked on PR #47

Overview

Six documentation checkers live only in scripts/ of hatlabs/halpi2. They move
into a public Python package under halos-org, pinned as a git dependency by
consuming repos so that a local run and a CI run execute the same code. The
translation status checker gains a mode that exits non-zero, and halpi2 starts
using it, so an English edit can no longer merge while its nine translations
remain behind.

Problem Frame

A translation records the git blob hash of the English page it was written
against. An English edit changes that hash, so every translation of that page
reports as stale on its own — but only reports. translation_status.py states
the limit in its own docstring: "Reports; never blocks." An English-only edit
merges green and nine locales go quietly wrong on the published site.

PR #47 is the live instance: 12
English pages rewritten, no translations updated, 108 stale page-translations on
its branch, CI green. main itself is clean — 180 of 180 current across all nine
locales — so the gate has no backlog to overcome (see origin:
_local/brainstorms/2026-08-12-shared-docs-tools-requirements.md).

Requirements Trace

  • R1. Single public package repo under halos-org; no second copy in hatlabs.
  • R2. Six entry points: translation status, stamping, anchor mapping, glossary
    check, typography check, built-site anchor check.
  • R3. Consumers pin it in pyproject.toml, so uv sync gives CI and a laptop
    identical code.
  • R4. Each consumer pins its own version; upgrades are deliberate.
  • R5. Glossaries and language rules stay repo content.
  • R6. Translation status gains a non-zero-exit mode, and CI uses it.
  • R7. Four states fail: stale, missing, unstamped, orphaned, repo-wide.
  • R8. Invariant: every default-locale page has a stamped, current counterpart in
    every configured locale, and no locale carries a page the default lacks.
  • R9. Adding a locale requires its complete page set in the same PR.
  • R10. The PR comment reports everything that fails the gate; size cap retained.
  • R11. Comment construction lives in the package, not workflow shell script.
  • R12. halos-org/shared-workflows gains a reusable translation workflow.
  • R13. hatlabs/shared-workflows is not modified.
  • R14. hatlabs/halpi2 drops its scripts/, pins the package, runs the gate red.
  • R15. The translate-page skill calls entry points instead of script paths.
  • R16. halos-org/docs.halos.fi pins the package and gains anchor validation.
  • R17. No file in hatlabs/halpi2 changes until PR
    #47 merges.
  • R18. The package and the docs.halos.fi wiring proceed immediately.

Scope Boundaries

  • No i18n for docs.halos.fi.
  • No modification to hatlabs/shared-workflows.
  • No change to the staleness mechanism — the frontmatter blob-hash stamp stays.
  • No PyPI publication; git tags are the distribution mechanism.
  • HALSER-docs, halmet-docs, sh-rpi-docs, sh-esp32-docs, sh-wg-docs are
    not wired.
  • No baseline or ratchet file.
  • No debian/ packaging in docs-tools — it ships as a Python dependency, not
    a .deb, so the repo VERSION and APT machinery in the workspace AGENTS.md
    do not apply to it.

Context & Research

Relevant Code and Patterns

Source scripts, all currently in hatlabs/halpi2:

Script Behaviour Repo-root assumptions
scripts/translation_status.py five states, markdown/text report, --diff, --only-pages docs/, mkdocs.yml
scripts/stamp_translation.py writes translated_from frontmatter docs/
scripts/map_anchors.py rewrites English anchor fragments to translated slugs docs/
scripts/check_glossary.py prescribed terms actually used solutions/translation (CLI default)
scripts/check_typography.py quote pairing and unit spacing per language docs/<lang>
scripts/check_anchors.py built-site anchor resolution, exits 1 on breakage site dir argument
  • Package pattern to mirror: container-packaging-tools in this workspace —
    src/ layout, setuptools backend, [project.scripts] console entry points,
    pyproject.toml with ruff and pytest config, run dispatcher, lefthook.yml,
    tests/ with conftest.py and fixtures/.
  • Reusable workflow pattern: shared-workflows/.github/workflows/pr-checks.yml
    workflow_call with typed inputs, a header comment stating caller
    requirements, and a matching entry under examples/.
  • Every caller in this workspace pins shared workflows at @main.
  • hatlabs/halpi2 pyproject.toml declares only mkdocs-material and
    mkdocs-static-i18n; pyyaml is used by the scripts but reaches them
    transitively through MkDocs.
  • halos-org/docs.halos.fi ci.yml already runs uv sync and
    mkdocs build --strict, so anchor validation is an added step, not a new job.

External References

None gathered. The work is a package extraction and a CI wiring against patterns
that already exist in these repositories.

Key Technical Decisions

  • Package, not a copied script or a checked-out workflow: the checkers are
    run by hand at least as often as by CI. Only a package makes the local run and
    the gate the same code (see origin).
  • One copy, hosted in halos-org: halos-org must stand alone; hatlabs
    may depend on it, never the reverse.
  • docs.halos.fi consumes the package directly, not the reusable workflow:
    translation_status.py raises SystemExit("mkdocs.yml has no i18n plugin configuration") when no i18n plugin is configured, so a translation workflow
    is unusable there. Its ci.yml gains two lines instead. This means the
    reusable workflow needs no "translations off" input.
  • The gate runs last, or its exit status is deferred to the end of the job:
    halpi2's current workflow deliberately runs the anchor check after the PR
    comment, so the report publishes even when the run fails. A gate that aborts
    the job on step three would suppress the comment that explains it. The failing
    status must not prevent the comment from posting.
  • Comment construction in the package, posting in the workflow: the package
    emits a comment body; gh finds, creates or patches the comment. This keeps
    GitHub API concerns out of the package and makes the body unit-testable.
  • CLI compatibility is preserved on port: flag names, defaults and output
    formats stay as they are, so the translate-page skill and existing habits
    need only the command prefix changed.
  • Versioning by pyproject.toml plus a vX.Y.Z git tag: no VERSION file,
    no debian/changelog, no APT dispatch. The workspace version-bump policy
    governs .deb-producing repos; this repo produces none.

Open Questions

Resolved During Planning

  • Where the CI glue lives: a reusable workflow in
    halos-org/shared-workflows, referenced by hatlabs/halpi2 at @main,
    matching every other caller in this workspace.
  • Whether the reusable workflow builds the site: yes. A workflow_call
    workflow checks out the caller's repo, so uv sync there resolves the
    consumer's own MkDocs version and the build and anchor check belong in the
    same job as the translation gate.
  • Pin style: git tag (@v1.0.0) for the package dependency, @main for the
    workflow reference.
  • What happens to --only-pages: it stays as a local convenience flag. The
    comment no longer uses it, because the comment must match the repo-wide gate.

Deferred to Implementation

  • Exact module and function names inside the package, beyond the six entry point
    names fixed in Unit 1.
  • Whether the check mode is a flag on the existing entry point or a subcommand —
    decide when porting, once the argument parser shape is in front of you.
  • Whether check-glossary and check-typography are worth running in CI at all,
    or stay author-invoked. They are not part of the gate either way.
  • Whether fork pull requests need special handling beyond tolerating a failed
    comment post.

High-Level Technical Design

This illustrates the intended approach and is directional guidance for review,
not implementation specification. The implementing agent should treat it as
context, not code to reproduce.

Dependency and consumption graph:

graph TB
    P["halos-org/docs-tools<br/>six console entry points"]
    W["halos-org/shared-workflows<br/>translation-status.yml (workflow_call)"]
    D["halos-org/docs.halos.fi<br/>ci.yml + check-anchors"]
    H["hatlabs/halpi2<br/>9 locales, gate red"]

    P -->|"pyproject git pin"| D
    P -->|"pyproject git pin"| H
    P -.->|"entry points invoked"| W
    W -->|"uses: @main"| H
Loading

Job shape inside the reusable workflow, ordered so the explanation always
publishes before the failure lands:

checkout (fetch-depth: 0)   # stamped blobs must be reachable
setup-uv, uv sync           # consumer's own deps + docs-tools
translation status report   -> job summary        (never fails)
build comment body          -> gh create/patch    (tolerated if it fails)
mkdocs build --strict
check-anchors site          -> fails on broken anchors
translation gate            -> fails on stale/missing/unstamped/orphaned

Implementation Units

Phase 1 — the package (unblocked)

Phase 2 — CI glue (unblocked)

Phase 3 — halpi2 (blocked until hatlabs/halpi2 PR #47 merges)

System-Wide Impact

  • Interaction graph: the translate-page skill, the per-language glossaries
    under solutions/translation/, halpi2's deploy.yml, and docs.halos.fi's
    ci.yml. Only the skill and the two CI files change; the glossaries are read
    by the package but stay repository content.
  • Error propagation: the gate's non-zero exit must reach the job status
    without preventing the comment step from running. This is the single crossing
    point where a mistake makes the feature worse than the status quo — a red
    check with no explanation.
  • State lifecycle risks: a stamp updated without real translation work
    reports green and makes staleness invisible. The stamping tool's docstring
    already says so; the gate cannot detect it, and no unit in this plan changes
    that.
  • API surface parity: the six command names become a public interface the
    moment a second repo pins them. Renaming one later is a breaking change for
    every consumer and for the translate-page skill.
  • Integration coverage: the package's unit tests cannot prove the workflow
    wiring. Units 8 and 9 carry that proof as observed CI runs.
  • Unchanged invariants: the stamp format, the five state names, the
    repo-root convention and every existing CLI flag stay as they are. A reader of
    translation_status.py today will recognise the package version.

Risks & Dependencies

Risk Mitigation
PR #47 merges English-only, creating a 108-entry backlog Unit 9 checks the repository is clean before flipping. If it is not, drain it first; the gate flip is already a separate change
The gate aborts the job before the comment posts, leaving a red check with no explanation Step order in Unit 7 puts the gate last; verified explicitly in that unit
A fork pull request cannot post a comment and fails the build for it The comment step tolerates failure; only the gate decides the job status
The translate-page skill keeps pointing at deleted scripts/ paths Same unit deletes the scripts and updates the skill
Entry point names churn after a second repo pins them Names are fixed in Unit 1 and treated as an interface from that point
uv sync needs to reach GitHub to resolve the git dependency Both repos are public; no token needed
Two repos pin different versions and behave differently Accepted. Independent pins are the point (R4); the alternative is every consumer upgrading on someone else's schedule

Documentation / Operational Notes

  • docs-tools needs a README covering the six commands, the pin line consumers
    copy, and the locale-addition consequence from Unit 5.
  • shared-workflows/README.md gains an entry for the new workflow, matching how
    the other four are listed.
  • halpi2's CLAUDE.md currently documents the scripts by path; Unit 9 updates
    it.
  • After the gate is live, the practical author workflow becomes: edit English,
    run the status check locally, translate the reported pages with the
    translate-page skill, stamp, push. Worth stating once in halpi2's
    CLAUDE.md.

Sources & References

  • Origin document: _local/brainstorms/2026-08-12-shared-docs-tools-requirements.md
  • Blocking dependency: hatlabs/halpi2 PR #47
  • Prior art for the comment size cap: hatlabs/halpi2 PR #48
  • Package pattern: container-packaging-tools/pyproject.toml
  • Workflow pattern: shared-workflows/.github/workflows/pr-checks.yml

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions