You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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/halpi2pyproject.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.fici.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
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.
Target repos: this plan spans four repositories. Each implementation unit
names its own target; file paths are relative to that unit's target repo.
halos-org/docs-toolshalos-org/shared-workflowshalos-org/docs.halos.fihatlabs/halpi2Overview
Six documentation checkers live only in
scripts/ofhatlabs/halpi2. They moveinto a public Python package under
halos-org, pinned as a git dependency byconsuming 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
halpi2startsusing 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.pystatesthe 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.
mainitself is clean — 180 of 180 current across all ninelocales — so the gate has no backlog to overcome (see origin:
_local/brainstorms/2026-08-12-shared-docs-tools-requirements.md).Requirements Trace
halos-org; no second copy inhatlabs.check, typography check, built-site anchor check.
pyproject.toml, souv syncgives CI and a laptopidentical code.
stale,missing,unstamped,orphaned, repo-wide.every configured locale, and no locale carries a page the default lacks.
halos-org/shared-workflowsgains a reusable translation workflow.hatlabs/shared-workflowsis not modified.hatlabs/halpi2drops itsscripts/, pins the package, runs the gate red.translate-pageskill calls entry points instead of script paths.halos-org/docs.halos.fipins the package and gains anchor validation.hatlabs/halpi2changes until PR#47 merges.
docs.halos.fiwiring proceed immediately.Scope Boundaries
docs.halos.fi.hatlabs/shared-workflows.HALSER-docs,halmet-docs,sh-rpi-docs,sh-esp32-docs,sh-wg-docsarenot wired.
debian/packaging indocs-tools— it ships as a Python dependency, nota
.deb, so the repoVERSIONand APT machinery in the workspace AGENTS.mddo not apply to it.
Context & Research
Relevant Code and Patterns
Source scripts, all currently in
hatlabs/halpi2:scripts/translation_status.py--diff,--only-pagesdocs/,mkdocs.ymlscripts/stamp_translation.pytranslated_fromfrontmatterdocs/scripts/map_anchors.pydocs/scripts/check_glossary.pysolutions/translation(CLI default)scripts/check_typography.pydocs/<lang>scripts/check_anchors.pycontainer-packaging-toolsin this workspace —src/layout, setuptools backend,[project.scripts]console entry points,pyproject.tomlwith ruff and pytest config,rundispatcher,lefthook.yml,tests/withconftest.pyandfixtures/.shared-workflows/.github/workflows/pr-checks.yml—workflow_callwith typed inputs, a header comment stating callerrequirements, and a matching entry under
examples/.@main.hatlabs/halpi2pyproject.tomldeclares onlymkdocs-materialandmkdocs-static-i18n;pyyamlis used by the scripts but reaches themtransitively through MkDocs.
halos-org/docs.halos.fici.ymlalready runsuv syncandmkdocs 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
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).
halos-org:halos-orgmust stand alone;hatlabsmay depend on it, never the reverse.
docs.halos.ficonsumes the package directly, not the reusable workflow:translation_status.pyraisesSystemExit("mkdocs.yml has no i18n plugin configuration")when no i18n plugin is configured, so a translation workflowis unusable there. Its
ci.ymlgains two lines instead. This means thereusable workflow needs no "translations off" input.
halpi2's current workflow deliberately runs the anchor check after the PRcomment, 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.
emits a comment body;
ghfinds, creates or patches the comment. This keepsGitHub API concerns out of the package and makes the body unit-testable.
formats stay as they are, so the
translate-pageskill and existing habitsneed only the command prefix changed.
pyproject.tomlplus avX.Y.Zgit tag: noVERSIONfile,no
debian/changelog, no APT dispatch. The workspace version-bump policygoverns
.deb-producing repos; this repo produces none.Open Questions
Resolved During Planning
halos-org/shared-workflows, referenced byhatlabs/halpi2at@main,matching every other caller in this workspace.
workflow_callworkflow checks out the caller's repo, so
uv syncthere resolves theconsumer's own MkDocs version and the build and anchor check belong in the
same job as the translation gate.
@v1.0.0) for the package dependency,@mainfor theworkflow reference.
--only-pages: it stays as a local convenience flag. Thecomment no longer uses it, because the comment must match the repo-wide gate.
Deferred to Implementation
names fixed in Unit 1.
decide when porting, once the argument parser shape is in front of you.
check-glossaryandcheck-typographyare worth running in CI at all,or stay author-invoked. They are not part of the gate either way.
comment post.
High-Level Technical Design
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"| HJob shape inside the reusable workflow, ordered so the explanation always
publishes before the failure lands:
Implementation Units
Phase 1 — the package (unblocked)
halos-org/docs-tools#139 — Unit 1: Scaffoldhalos-org/docs-toolsPhase 2 — CI glue (unblocked)
docs.halos.fito the anchor checker #146 — Unit 8: Wiredocs.halos.fito the anchor checkerPhase 3 —
halpi2(blocked until hatlabs/halpi2 PR #47 merges)halpi2and turn the gate redSystem-Wide Impact
translate-pageskill, the per-language glossariesunder
solutions/translation/,halpi2'sdeploy.yml, anddocs.halos.fi'sci.yml. Only the skill and the two CI files change; the glossaries are readby the package but stay repository content.
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.
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.
moment a second repo pins them. Renaming one later is a breaking change for
every consumer and for the
translate-pageskill.wiring. Units 8 and 9 carry that proof as observed CI runs.
repo-root convention and every existing CLI flag stay as they are. A reader of
translation_status.pytoday will recognise the package version.Risks & Dependencies
translate-pageskill keeps pointing at deletedscripts/pathsuv syncneeds to reach GitHub to resolve the git dependencyDocumentation / Operational Notes
docs-toolsneeds a README covering the six commands, the pin line consumerscopy, and the locale-addition consequence from Unit 5.
shared-workflows/README.mdgains an entry for the new workflow, matching howthe other four are listed.
halpi2'sCLAUDE.mdcurrently documents the scripts by path; Unit 9 updatesit.
run the status check locally, translate the reported pages with the
translate-pageskill, stamp, push. Worth stating once inhalpi2'sCLAUDE.md.Sources & References
_local/brainstorms/2026-08-12-shared-docs-tools-requirements.mdcontainer-packaging-tools/pyproject.tomlshared-workflows/.github/workflows/pr-checks.yml