Skip to content

Migrate pysignals 0.3.0 POC into humansays 0.1.0#4

Merged
rhawk117 merged 41 commits into
developfrom
feat/proof-of-concept
Jul 26, 2026
Merged

Migrate pysignals 0.3.0 POC into humansays 0.1.0#4
rhawk117 merged 41 commits into
developfrom
feat/proof-of-concept

Conversation

@rhawk117

Copy link
Copy Markdown
Owner

Summary

Behavior-preserving migration of the pysignals 0.3.0 proof-of-concept into src/humansays/, per .claude/plans/migrate-the-proof-of-concept-in-cozy-beacon.md.

  • Flat layout → src/humansays/, split into config/, analysis/, findings/, reporting/ (only analysis/ imports ast/tokenize, enforced by import-linter contracts).
  • Zero runtime dependencies: pydantic/pydantic-settings removed, config models are frozen dataclasses, rich moved to an optional terminal extra with a plain-ANSI fallback honoring NO_COLOR/FORCE_COLOR/TERM=dumb.
  • PY### → HS### rename; PY010/PY011/PY020 (and the tokenize pass backing PY010) deleted; schema_version reset to 1; console script → humansays.cli:main.
  • --config <nonexistent> now exits 4.
  • Acceptance criterion: tests/golden/test_parity.py recomputes findings/scores from a frozen pysignals 0.3.0 oracle (PY ids) against live humansays output (HS ids) for both corpus groups — exact match.
  • Self-scan gate (tests/golden/test_self_scan.py) is an exact-match baseline: any new weighted finding or any baselined finding that disappears fails the test. Two categories are baselined as required-design tradeoffs, not defects: HS002 (boolean-modes) on ansi.py's keyword-only color-toggle params (a known argument-kind blind spot in HS002 itself), and HS021 (lazy-import) on the single _load_rich() accessor backing the optional rich extra.
  • 57 tests, 34 subtests, all passing; lint-imports (3 contracts), ruff, uv sync all clean.

CI note

The Documentation workflow's automatic triggers are disabled in this PR (.github/workflows/github-pages.yml now only runs via workflow_dispatch) because docs/ is mid-restructuring on this branch and mkdocs build --strict currently fails. The workflow file itself is left in place as a repository artifact, not deleted.

rhawk117 added 30 commits July 25, 2026 21:10
… tighten ast node types, fix pre-existing ruff gaps

- ty was silently running with zero config because the file needed to be
  named ty.toml, not .ty.toml; this is why the exclude for the vendored
  poc-parity corpus and the one-time oracle generator never took effect,
  and why missing-override-decorator/etc were never actually enforced.
- Excluded tests/golden/poc-parity/corpus and _generate.py from ty (vendored
  third-party fixtures and an oracle generator only importable inside the
  ephemeral POC env).
- Disabled missing-override-decorator: typing.override needs Python 3.12+
  and this project supports 3.11+ with zero runtime dependencies, so the
  decorator isn't available everywhere the rule would require it.
- Added FunctionNode = ast.FunctionDef | ast.AsyncFunctionDef and used it to
  replace overly-generic ast.AST parameter types in cpython_ast.py/rules.py
  wherever the body actually only ever receives a function node; similarly
  narrowed assigned_names/decorator_names/_record_lazy_import in
  syntax.py/cpython_ast.py to the node types their callers actually pass.
  _record_call_target now narrows node.func to ast.Attribute itself instead
  of relying on the caller's isinstance check, which ty can't see across
  a function boundary.
- config/loading.py: typed _build/build_settings against DataclassInstance
  and Mapping instead of bare type/dict so dataclasses.fields() and the
  recursive call type-check; added runtime asserts at the one genuinely
  dynamic recursion point (dispatch driven by THRESHOLDS_SPEC/SETTINGS_SPEC)
  with a narrow ty ignore where the assert isn't expressible statically.
- render.py: added assert rich is not None in _render_rich, which is only
  ever called after emit() has confirmed _load_rich() is non-None.
- test_text_snapshot.py: fixed _scan_result's return annotation from
  tuple[ScanResult, object] to tuple[ScanResult, Score].
- Fixed two pre-existing ruff I001 import-order violations (test_config_models.py,
  test_findings_models.py) and one pre-existing ruff format gap (catalog.py)
  that were already failing CI before this investigation, unrelated to the
  ty work.
…B101)

.bandit.yaml intentionally runs B101 (assert-used) against src/ and asks for
narrowly justified fixes rather than a skip list. The asserts added in the
previous commit to satisfy ty's type narrowing were flagged; replaced with
equivalent isinstance-checked raises, which ty narrows on the same as an
assert and which aren't stripped under python -O.
…ures)

- ruff format was rewriting an embedded Python code fence inside
  docs/phases/02-fact-model/PHASE.md. docs/ is mid-restructuring in a
  separate workstream and is explicitly not to be touched here, so exclude
  it from ruff's scope instead of reformatting the file.
- ty check failed in CI (but not locally) on 'Cannot resolve imported module
  pytest' in tests/*.py: the Code Linter job's bootstrap only installs the
  lint dependency group, not test, so pytest isn't on that job's Python path.
  Excluded all of tests/ from ty -- the test matrix job already runs these
  files directly, which is a stronger signal than static type-checking them
  without their real dependencies installed.
scripts/smoke_test_package.py (pre-existing repo CI tooling, from before this
migration) verifies both --help and --version against any console-script
entry point after installing the built wheel. argparse gives --help for free
but --version needs to be added explicitly; the POC's build_parser never had
one either since it was never exercised by this smoke test. Uses
importlib.metadata so the printed version always matches the installed
distribution rather than a hardcoded string.
@rhawk117 rhawk117 self-assigned this Jul 26, 2026
rhawk117 added 9 commits July 26, 2026 00:42
Sections C and D write to tests/tooling, the release workflow, README and
the smoke scripts. Forward scope only; the guard is not run against this
PR's migration history.
The guard was untracked and therefore never linted. S603/S607 are expected
for a tool whose entire job is shelling out to git.
Seven violation cases from scope-guard.md, the permitted isolated-widening
companion, and a glob unit test covering the bash-era defect where * crossed
a path separator.
The table listed seven violation cases and omitted the permitted isolated
widening the guard implements. Also corrects the invocation, which named a
bash script that never existed in this repository, and records the
non-retroactivity exception in the reconciliation file.
Reserves the PyPI name. The test asserts pyproject, installed distribution
metadata and --version output all agree.
The README is the pyproject readme field and becomes the PyPI long
description for 0.1.0a1. It was still titled 'askance', the pre-rename name.
Adds the rule-id instability notice the phase requires.
The wrapper tested only the wheel, and the Python smoke only --version and
--help. Adds the sdist, a fixture scan with JSON validation, and a self-scan
that asserts the package resolved outside the source tree.
Replaces the version-diff detect job, which could never succeed
(release_info.py raises on every valid version string), with a tag/version
match. Uncomments the publish job, scopes id-token: write to it alone and
contents: write to the release job alone, and corrects the environment URL,
which pointed at another project.
rhawk117 added a commit that referenced this pull request Jul 26, 2026
Squash of feat/proof-of-concept (PR #4) into develop. Includes the full
proof-of-concept migration plus Phase 1 Sections A, C and D: the phase-1
inventory, a working scope guard with an 8-case test suite, and the
0.1.0a1 prerelease (versioning, README, artifact smoke, tag-triggered
release.yml with split id-token/contents permission scopes).
check_scope.py is never executed directly — every caller (docs, tests,
the CI workflow) invokes it via 'uv run python scripts/check_scope.py' or
[sys.executable, ...]. The shebang triggered EXE001 on a real Linux CI
runner (file is 644, non-executable) but not locally, because ruff's
EXE001/EXE002 checks are unconditionally skipped inside WSL
(astral-sh/ruff#10084) regardless of the actual filesystem's exec-bit
support. Matches scripts/smoke_test_package.py and
scripts/release_info.py, neither of which carries a shebang.
@rhawk117
rhawk117 changed the base branch from main to develop July 26, 2026 06:45
@rhawk117
rhawk117 merged commit 9622059 into develop Jul 26, 2026
9 checks passed
@rhawk117
rhawk117 deleted the feat/proof-of-concept branch July 26, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant