Files
ironclaw/scripts/test-check-type-duplicates.py
Henry Park 3fd143933a docs(guidance): repo-wide agent-guidance audit — fix drift, prune 21.5k lines, consolidate tests/ onto AGENTS.md convention (#7797)
* docs(guidance): repo-wide agent-guidance audit — fix drift, prune 21.5k lines, consolidate tests/ onto AGENTS.md convention

Full-layer audit of the agent-guidance system (root contracts, .claude rules/
skills/commands, family and crate AGENTS.md, CONTRACT specs, tests guidance,
docs/internal), verified reference-by-reference against HEAD.

- Fix stale/ghost references: UserSandboxProcessPort, ProductSurfaceError,
  LlmError::ContextLengthExceeded, INVERTED_PORT_IMPLEMENTORS, split channel
  traits (ChannelIngress/ChannelReply/ChannelDelivery), memory-native's
  never-implemented EmbeddingProvider seam, wrong layer/crate/module counts.
- Convert unpinned prose numbers to regeneration commands or pinning-test
  citations across root, family, and crate guidance (drift-proofing).
- tests/: rename CLAUDE.md -> AGENTS.md with CLAUDE.md symlinks (crates/
  convention), extend scripts/ci/check-guidance.py discovery to tests/,
  delete stale e2e scenario tables, dedupe tier taxonomy against
  .claude/rules/testing.md.
- Commands/skills: delete six dead v1 commands (add-tool, review-pr,
  review-crate, fix-issue, respond-pr, add-sse-event) and the v1-teaching
  architecture-video skill; convert ironclaw-reborn-skill-maintainer into
  the auto-loading rule .claude/rules/guidance-maintenance.md; fix clippy
  -D warnings and portable date in surviving commands; triggers-only
  frontmatter; add automations section to reborn-feature.
- Rules: rename gateway-events.md -> events.md; revive
  scripts/check-type-duplicates.py (glob matched zero types since the
  family reorg); index all 15 rules in root AGENTS.md for Codex parity.
- docs/internal: delete 70 superseded plans/specs/design docs (~21.5k
  lines, each re-verified unreferenced); fix misleading v1-migration
  status lines; rewrite the contracts index as a recipe; restore two docs
  that proved live-referenced.
- Trim composition CONTRACT.md route-mirror sections (invariants kept).

Verified: check-guidance.py (384 files, 0 grandfathered),
docs_publication_boundary.py, cargo test -p ironclaw_architecture_tests,
scripts/ci test-plan suite (87/87) — all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(guidance): address PR #7797 review comments

Verified each of the ~40 bot/reviewer findings against the tree; applied the
valid mechanical fixes, rebutted the rest with evidence (see PR comment).

- Test planner: add renamed tests/ guidance aliases to IGNORED_GUIDANCE_PATHS
  (reproduced the fail-closed abort on this PR's own changed-file list) and
  extend the planner test to all six guidance paths.
- check-guidance: add self-tests proving tests/-tree discovery and alias
  enforcement (48 tests, was 46); new self-test file for
  check-type-duplicates.py (4 tests).
- Portability: replace GNU-only date -d in triage commands with a python3
  one-liner (works on macOS BSD and Linux).
- Count/claim accuracy: product_contracts manager-port prose 4 -> 3 (matches
  INVERTED_PORT_IMPLEMENTORS), kernel grep -cF for literal #[test] (was regex
  char class, 2 vs 23), rg -o|wc -l for a true total in architecture.md,
  Rust-scoped LlmProvider count (catches 5 generic impls), measured
  1/73-crate dual-backend claim in pr-shepherd, executable wc -l in
  assistant guidance, AST/pytest recipes for the e2e test-count figures.
- Content: deslop co-author line no longer hardcodes an address; ship.md
  surfaces Postgres-skip counts; risk-label guidance documents the crates/**
  labeler blind spot; e2e authoring recipe leads with reborn_v2_* fixtures;
  stale CLAUDE.md line citation replaced with a stable anchor; ✎ provenance
  notes for two deleted-plan citations; unified-channel-model status text
  reconciled with an explicit ChannelDelivery-only exception note.

Gates: check-guidance (384 files, 0 grandfathered), docs boundary, planner
tests 87/87, check-guidance self-test 48/48, type-dup self-test 4/4 - green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): classify scripts/test-check-type-duplicates.py in the PR test planner

The self-test added in 77bc8f8c5 was never registered in
PR_STATIC_CONTROL_PATHS, so the planner's fail-closed unmapped-path arm
aborted 'Detect Reborn test scope' and cascaded into the whole Reborn
matrix skipping. Classified like its subject (deliberately CI-unwired
local dev tool, per the existing entry's rationale) and pinned in the
static-control planner test alongside it.

Verified: planner tests OK; planner run against this PR's full
changed-file list now returns mode=selected with the path owned by
static checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(docs): drop parity-QA path reference from tests/integration/AGENTS.md

The guidance dedup in this PR added a literal
tests/support/reborn_parity_qa reference to tests/integration guidance,
which scripts/ci/check-test-suite-boundaries.sh correctly flags: the
one-way dependency guard covers docs too, and origin/main's version of
this file carried no such reference. Fix the content, not the check -
the tier comparison is reworded to describe the RebornBinaryE2EHarness
seam difference without naming the parity/QA tree.

Verified: check-test-suite-boundaries.sh OK; check-guidance OK; the
full 'Detect Reborn test scope' job reproduced locally end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: run the type-duplicates self-test in Code Style; exercise the production report path

Address the two follow-up review findings on 77bc8f8c5/9ed30caf8:

- Wire scripts/test-check-type-duplicates.py into code_style.yml's
  Static-check self-tests step (next to test-check-guidance.py) so the
  regression test is actually enforced; update the planner's
  PR_STATIC_CONTROL_PATHS comment accordingly (classification unchanged
  - Code Style is the static lane).
- Strengthen the semantic-duplicate self-test to also drive the
  production main() report path and assert on its printed candidate
  output, instead of only re-computing similarity locally. Strictly
  stronger; the other three tests are untouched.

Verified: self-test 4/4, planner tests 87/87, workflow-contract
self-test 94/94, planner simulation over both changed paths classifies
cleanly (no unmapped-path abort).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: trigger fast-checks on type-duplicates script changes; symbol recipe for the tier reference

- code_style.yml's has_code filter covered scripts/ci/ but not the bare
  scripts/ type-duplicates pair, so a diff touching only the new
  self-test skipped the fast-checks job that runs it (the previous
  commit's 'runs unconditionally' claim was wrong - corrected the
  planner comment too). Added the two files to the filter following the
  check_no_panics precedent and pinned them as in_scope probes in the
  ws12 workflow-contracts routing test.
- tests/integration/AGENTS.md tier reference is now re-verifiable via a
  single-hit symbol recipe (rg 'struct RebornBinaryE2EHarness') instead
  of a path citation, which the test-suite boundary guard forbids from
  this subtree.

Verified: ws12 workflow contracts 94/94, planner tests 87/87,
check-test-suite-boundaries OK, check-guidance OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Henry Park <16583448+henrypark133@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 21:26:50 +00:00

153 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Self-test for `scripts/check-type-duplicates.py`.
The collector walks two glob shapes: `crates/<family>/<crate>/src` (the
post-reorg nested layout) and `crates/extensions/packages/<pkg>/src` (the
extension-package layout, which does not nest under a family directory the
same way). Neither had a regression test — a future glob/layout change could
silently narrow the scan back to a shallow `crates/*/src` layout and nothing
would fail, despite the tool's docstring claiming the reorganized workspace
is covered. These tests build a small on-disk crate tree covering both
shapes and assert `collect()` finds types from each, and that the semantic
(field-shape) duplicate match still fires across differently-named types.
`collect()` walks relative `Path('crates')`/`Path('crates/extensions/...')`,
so it is cwd-dependent; tests `chdir` into a temporary tree rather than
patching the module (the same real-tree-vs-fixture split
`scripts/ci/test-check-guidance.py` uses).
"""
from __future__ import annotations
import contextlib
import importlib.util
import io
import os
import pathlib
import sys
import tempfile
import unittest
SCRIPT = pathlib.Path(__file__).with_name("check-type-duplicates.py")
_SPEC = importlib.util.spec_from_file_location("check_type_duplicates", SCRIPT)
assert _SPEC and _SPEC.loader
DUP = importlib.util.module_from_spec(_SPEC)
sys.modules[_SPEC.name] = DUP
_SPEC.loader.exec_module(DUP)
class CollectTests(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory()
self.root = pathlib.Path(self._tmp.name)
self.addCleanup(self._tmp.cleanup)
self._cwd = os.getcwd()
os.chdir(self.root)
self.addCleanup(os.chdir, self._cwd)
def write(self, relative: str, content: str) -> None:
path = self.root / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
def test_nested_family_crate_is_discovered(self) -> None:
"""`crates/<family>/<crate>/src` — the post-reorg layout — must be
walked, not just a shallow `crates/*/src`."""
self.write(
"crates/domains/ironclaw_alpha/src/types.rs",
"pub struct Widget {\n"
" id: String,\n"
" name: String,\n"
" size: u32,\n"
"}\n",
)
types = DUP.collect(min_items=3)
self.assertEqual(len(types), 1)
crate, kind, name, items, path = types[0]
self.assertEqual(crate, "alpha")
self.assertEqual(kind, "struct")
self.assertEqual(name, "Widget")
self.assertEqual(
items,
frozenset({("id", "String"), ("name", "String"), ("size", "u32")}),
)
def test_extension_package_crate_is_discovered(self) -> None:
"""`crates/extensions/packages/<pkg>/src` does not nest under a
family directory the same way as `crates/<family>/<crate>` — a
glob written only for the family shape would miss it."""
self.write(
"crates/extensions/packages/acme/src/config.rs",
"pub struct Gadget {\n"
" id: String,\n"
" name: String,\n"
" size: u32,\n"
"}\n",
)
types = DUP.collect(min_items=3)
self.assertEqual(len(types), 1)
crate, kind, name, items, path = types[0]
self.assertEqual(crate, "acme")
self.assertEqual(kind, "struct")
self.assertEqual(name, "Gadget")
def test_semantic_duplicate_across_both_shapes_is_flagged(self) -> None:
"""The field-shape match (not name matching) is the tool's whole
point: a family crate and an extension-package crate defining
differently-named structs with the same (field, type) set must be
reported as a candidate pair.
This drives the production `main()` candidate-selection and report
path (the itertools.combinations/Jaccard loop and its printed
output), not a local reimplementation of Jaccard over `collect()`'s
return value — a regression in `main()`'s own pairing or reporting
logic would not be caught by re-deriving the score independently.
"""
self.write(
"crates/domains/ironclaw_alpha/src/types.rs",
"pub struct Widget {\n"
" id: String,\n"
" name: String,\n"
" size: u32,\n"
"}\n",
)
self.write(
"crates/extensions/packages/acme/src/config.rs",
"pub struct Gadget {\n"
" id: String,\n"
" name: String,\n"
" size: u32,\n"
"}\n",
)
types = DUP.collect(min_items=3)
self.assertEqual(len(types), 2)
a, b = types
i1, i2 = a[3], b[3]
union = len(i1 | i2)
jaccard = len(i1 & i2) / union if union else 0
self.assertEqual(jaccard, 1.0)
argv = sys.argv
sys.argv = ["check-type-duplicates.py", "--min-items", "3"]
out = io.StringIO()
try:
with contextlib.redirect_stdout(out):
DUP.main()
finally:
sys.argv = argv
report = out.getvalue()
self.assertIn("struct alpha::Widget <-> acme::Gadget", report)
self.assertIn("1 candidate pair(s) from 2 types", report)
def test_below_min_items_is_not_collected(self) -> None:
self.write(
"crates/domains/ironclaw_alpha/src/types.rs",
"pub struct Tiny {\n id: String,\n}\n",
)
self.assertEqual(DUP.collect(min_items=3), [])
if __name__ == "__main__":
unittest.main()