Metadata-Version: 2.5
Name: h2h-dev-tools
Version: 0.2.1
Summary: Shared development tools and reproducible repository snapshots for H2H projects
Project-URL: Homepage, https://github.com/Kuan-Lun/h2h-dev-tools
Project-URL: Source, https://github.com/Kuan-Lun/h2h-dev-tools
Project-URL: Tracker, https://github.com/Kuan-Lun/h2h-dev-tools/issues
Author: Kuan-Lun Wang
License-Expression: GPL-3.0-only
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.14
Requires-Dist: packaging>=26.0
Provides-Extra: dev
Requires-Dist: build>=1.5.0; extra == 'dev'
Requires-Dist: hatchling>=1.32.0; extra == 'dev'
Requires-Dist: mypy>=2.3.1; extra == 'dev'
Requires-Dist: pytest>=9.1.1; extra == 'dev'
Requires-Dist: ruff>=0.16.4; extra == 'dev'
Description-Content-Type: text/markdown

# h2h-dev-tools

Shared development tools for H2H repositories. The Python package owns common
implementations; each consumer commits a verified source snapshot so its hooks
run offline against the code present in that repository's Git tree.

## Install and synchronize

Use Python 3.14 or newer. Install the package in a development environment:

```bash
python -m pip install h2h-dev-tools
```

For an unpublished candidate, install an explicitly supplied wheel instead:

```bash
python -m pip install /absolute/path/to/h2h_dev_tools-0.2.0-py3-none-any.whl
```

Declare the components the consumer actually uses in its `pyproject.toml`:

```toml
[tool.h2h-dev-tools]
components = ["change-scope", "dependencies", "documentation", "git-flow"]
```

Then generate or update the snapshot:

```bash
h2h-dev-tools sync --repository /absolute/path/to/consumer
h2h-dev-tools verify --repository /absolute/path/to/consumer
```

Selection is explicit. The generator includes each selected component's complete
dependency closure and removes previously verified files that are no longer
selected. Verification rejects stale selection, modified files and unknown files.
Python modules and shell resources share the same hash-bound manifest.

| Component | Responsibility |
| --- | --- |
| `atomic-files` | Atomic byte publication with explicit replace or no-replace semantics; callers own JSON and directory policy |
| `change-scope`, `documentation` | Exact Git candidate classification and documentation checks |
| `dependencies`, `dependency-audit` | Declared dependencies, registry candidates and reviewed audit receipts |
| `version-policy` | Task-level release impact, version and audit validation |
| `primary-branch`, `git-flow`, `hooks`, `environment` | Local branch selection, safe merge lifecycle, hook installation and environment rebuild |
| `publish-version` | Version transition across a complete push |
| `distribution-check` | Installed requirements, RECORD and import-origin validation |
| `offline-wheel` | Temporary candidate-wheel environment with development dependencies and a local smoke callback |
| `process` | Owned process trees, deadlines, signal deferral and Windows Job start gates |
| `formal` | Pinned TLC download and offline host/Docker execution |

Schema-1 snapshots from 0.1 use a fixed payload and are not accepted by normal
schema-2 verification or sync. After declaring components, convert once:

```bash
h2h-dev-tools migrate-v1 --repository /absolute/path/to/consumer
```

The converter verifies the complete old manifest, file set and hashes before
updating generated files. It preserves unrelated files and rejects edited or
incomplete snapshots. This conversion changes development tools only; application
databases, archives and runtime environments do not need conversion.

Review the resulting consumer diff, run that repository's checks and commit the
snapshot update on its task branch. Sync is an explicit source update; it does
not commit, merge, push or publish. Consumers do not need this installed package
to run their committed snapshots, and do not add it to runtime dependencies.

The consumer snapshot lives in `scripts/_shared/h2h_dev_tools/`. Its tracked
`manifest.json` identifies the toolkit version and source payload hashes; these
are payload identities, not the wheel archive hash. Generated snapshots must
never be edited manually. A consumer checks its local snapshot without network
or an installed copy of the tool:

```bash
python scripts/_shared/h2h_dev_tools/bundle_verify.py --repository .
```

The manifest detects changed or incomplete source files; it is not a signature
or proof of the publisher's identity. Review the package source and consumer diff
before adopting an update.

Sync writes individual files before replacing the manifest. An interrupted update
can therefore leave a bundle that verification and later sync deliberately reject.
Inspect and preserve any pending edits first. If this consumer already has a
committed bundle, restore only that generated directory from the intended known
good commit, then run sync again; do not restore the whole repository or overwrite
unrelated changes. For example, after confirming `HEAD` contains the bundle you
intend to recover:

```bash
git diff -- scripts/_shared/h2h_dev_tools
git diff --cached -- scripts/_shared/h2h_dev_tools
git restore --source=HEAD --worktree -- scripts/_shared/h2h_dev_tools
h2h-dev-tools sync --repository .
```

The restore command changes only working files; after sync, review and stage the
complete updated bundle using the consumer's normal workflow.

For an interrupted first sync with no committed bundle, preserve the incomplete
directory outside `scripts/_shared/` after inspecting its contents, then sync into
the vacant destination. The tool never treats unknown files as disposable.

Thin repository entry points supply their own root and policies. Core schema
checks, adapter tests, pytest selectors, timeout budgets, formal targets and
deployment acceptance remain owned by their original repositories. Shell bootstrap
resources run before a `.venv` exists; they do not import the Python package.

Version paths are declared in `[tool.h2h.version]`; dependency inventory comes
from the project manifests. Shared version evaluation considers only commits that
actually change release surface. Hook installation refuses to replace an existing
third-party hook directory. Rebuild validates explicit source overrides before
replacing the environment. Merge checks dirty worktrees, in-progress Git operations
and stable refs; it runs the local candidate review/gate and aborts on failure.
Already-integrated tasks use checked cleanup without another merge or gate.

The low-level process launch API is silent and accepts file-backed stdout/stderr.
Its caller must defer signals until it owns the returned process. `run_command`
provides that lifecycle, reserves cleanup time, rejects surviving descendants and
verifies the final absolute deadline. Local callers select their budgets and
signals. POSIX children that deliberately detach and Docker-daemon-owned resources
are outside the owned-tree guarantee. Windows behavior also runs in the dedicated
Windows workflow; a POSIX test run does not substitute for it.

The `offline-wheel` component owns temporary build/install directories for
consumer wheel smoke checks. A consumer supplies its own validation script:

```bash
.venv/bin/python scripts/_shared/h2h_dev_tools/offline_wheel.py \
  --repository . --smoke-script scripts/check-installed-wheel.py
```

Arguments following `--` are forwarded unchanged to that script. The helper
builds fresh distributions with the repository's development Python and build
backend, requires exactly one wheel, then installs only that wheel in a temporary
venv with `uv` in offline mode. It explicitly adds the development site for the
callback's dependencies and runs the callback with `python -I` from the temporary
directory. The callback owns package import/metadata checks, CLI checks and domain
oracles; arguments naming repository files must therefore be absolute paths.
Success and ordinary failure both remove the temporary environment. A surrounding
runner still owns the aggregate deadline and process-tree termination policy.
This checks the candidate artifact with development dependencies; it does **not**
prove clean dependency resolution or compatibility of an index-selected cohort.
The toolkit's own isolated resolver smoke and hbrowser's target-directory smoke
have different contracts and do not use this component.

## Develop this repository

```bash
scripts/rebuild-env.sh
scripts/install-git-hooks.sh
scripts/check-fast.sh
scripts/check-full.sh
```

The expected primary branch is `master`. Create a dedicated task branch before
changing tracked files, then finish with `scripts/git-flow-merge.sh`. The merge
hook validates the real staged candidate and its version impact before running
the applicable documentation or full gate. `scripts/format.sh` applies formatters;
the check commands are read-only. Ruff and strict mypy use `pyproject.toml`.

The full gate builds fresh distributions, then installs the sdist-derived wheel
in a new environment with normal, offline pip resolution. Only the installed
`packaging` distribution is copied into that environment as dependency evidence;
no editable project or development-site fallback is used. The smoke verifies
package and metadata locations, runtime requirements, both CLI entry points, and
real snapshot export plus standalone verification.
This is an offline distribution check, not a registry-backed clean-resolution test.

## Releases

`pyproject.toml` is the sole project-version source. The initial version is
`0.1.0`; subsequent compatible releases increment the third component, and
breaking changes increment the second component while it is below 1.0.
The shipped development tools and snapshot format are release surface even
though consumers use them only for development.

Before committing a release version, review dependency candidates and generate
the version-bound audit evidence:

```bash
.venv/bin/python scripts/audit-dependencies.py \
  --review-note "Release notes and compatibility validation results"
```

GitHub's `publish.yml` workflow uses PyPI Trusted Publishing with environment
`pypi`. Normal default-branch pushes publish only when the version increases over
the whole push. Creating the branch does not publish. The first release can use
an explicitly authorized workflow dispatch with the exact project version; it
still requires the default branch and builds and verifies distributions first.
The PyPI publisher must match owner `Kuan-Lun`, repository `h2h-dev-tools`, workflow
`publish.yml` and environment `pypi`. Configuring this workflow does not execute a
release or validate the remote publisher configuration.

Licensed under GPL-3.0-only; see [LICENSE](LICENSE).
