mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-03 08:06:01 +08:00
* docs(reborn): final crate/module refactor design Blueprint for decomposing the reborn stack by redistributing mass inside crates rather than adding or removing crates. Supersedes the abandoned 6-crate composition extraction (reversed on fan-in evidence). - 76-crate refactor map with per-crate verdicts (KEEP / INTERNAL / FOLD / MERGE / JIT), driven by fan-in x size analysis - composition god-crate (153k) dissected into 10 internal modules with an 11-PR leaf-first sequence, from a six-agent coupling analysis - subagent-loop eval harness: machine gates G1-G6, structural gates S1-S5, judge rubric, plus a pub-use API-freeze baseline - pre-move characterization test plan (T1-T8) for the leaked-block extractions - final target architecture (L0-L6 layer map), RebornServicesApi domain-port split, seam registry, and a roadmap overlay mapping each planned feature to its landing zones and reduced footprint Docs only; no code changes. Executed later via the 11-PR loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(reborn): make the target module count consistent (≤12) The headline said ~11 while the S1 gate and §6.1 table said ≤15 — a subagent runs S1 literally, so the gate could pass at 15 while the 11-module architecture is missed. Align all three to ≤12: root + the 10 domain modules of §3/§6.2 plus the test_support top-level mod. Reported by CodeRabbit on PR #5529. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(reborn): harden eval gates from review (G3 postgres, S4 cfg-capture) - G3: add `postgres` to the composition test feature set so the eval catches postgres-backend breakage, not just libsql. - S4: capture #[cfg(...)] attributes above each `pub use` in the freeze snapshot (regenerated: 170 -> 200 lines, 30 gate attrs that were previously invisible), so a silently dropped/changed feature gate on an export is caught. - Note that --test-threads=1 is a flake discriminator, not the fix; the durable fix is lock_env(). Addresses Gemini review on PR #5529. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(reborn): name the exact bundled_skills frozen artifacts Replace vague "embedded JSON filenames" with the concrete, verified contract from build.rs + src/bundled_skills.rs: embedded_reborn_skill_summaries.json, embedded_reborn_skill_bundles.json, the marker file .ironclaw-reborn-bundled.json, and the owner string ironclaw_reborn_composition_bundled_skill. Addresses CodeRabbit; note its suggested embedded_catalog.json / embedded_skills.json do not exist in this crate (verified by grep). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(reborn): add type-placement rule + semantic dedup detector + judged backlog Companion to the crate-design work: governs type location/multiplicity and the mirror-pipeline change-one-thing-edit-N-DTOs cost. .claude/rules/type-placement.md (placement ladder + mirror resolution order: pass-through->import, additive->serde(flatten), subtractive/redaction->keep manual; pub use only at a mandated contract facade). scripts/check-type-duplicates.py (field/variant-signature scan, not name grep). docs/plans/2026-07-02-type-dedup-backlog.md (all 178 pairs judged: 18 TRUE-DUP clustered with owners, 14 borderline lockstep mirrors, do-not-fix list). Docs/rule/script only; no code behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(reborn): trait audit — abstraction-must-earn-its-keep rule + judged inventory Measured all 352 traits in crates/: 62% have 2+ prod impls; 31 cross-crate DIP seams, 30 test-seamed, 48 dyn-injection singles all judged keep; only 8 (2.3%) are ceremony/dead (agent-verified by reading defs/impls/call sites). Adds the trait clause to type-placement.md (justified by polymorphism, inversion, test seam, or dyn injection — else inline) and the removable-trait table + explicit-keep list to the backlog doc. Also documents the mechanical-audit pitfall: naive impl-grep misses blanket/generic/macro impls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
92 lines
3.3 KiB
Python
Executable File
92 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Detect cross-crate SEMANTIC duplicate-type candidates by field/variant signature.
|
|
|
|
Name matching misses the real failure mode (same DTO job, different names), so
|
|
this compares (field-name, field-type) sets for structs and variant sets for
|
|
enums across crates and reports pairs above a similarity threshold.
|
|
|
|
Output is CANDIDATES, not verdicts: a match is either a true duplicate (unify
|
|
into its owner), a justified mirror (independent wire/domain evolution), or a
|
|
coincidental shape. Judge each pair by reading the definitions before acting.
|
|
See .claude/rules/type-placement.md; judged backlog (2026-07):
|
|
docs/plans/2026-07-02-type-dedup-backlog.md.
|
|
|
|
Usage: python3 scripts/check-type-duplicates.py [--jaccard 0.6] [--min-items 3]
|
|
"""
|
|
import argparse
|
|
import itertools
|
|
import re
|
|
from pathlib import Path
|
|
|
|
DEF_RE = re.compile(r'^pub (struct|enum) ([A-Za-z0-9_]+)(?:<[^>]*>)?\s*\{', re.M)
|
|
FIELD_RE = re.compile(r'(?:pub(?:\([^)]*\))?\s+)?([a-z_][a-z0-9_]*)\s*:\s*([^,\n]+)')
|
|
VARIANT_RE = re.compile(r'^\s{4}([A-Z][A-Za-z0-9_]*)', re.M)
|
|
KEYWORDS = {'where', 'if', 'let', 'match'}
|
|
|
|
|
|
def body_of(text, start):
|
|
i = text.index('{', start)
|
|
depth, j = 0, i
|
|
while j < len(text):
|
|
if text[j] == '{':
|
|
depth += 1
|
|
elif text[j] == '}':
|
|
depth -= 1
|
|
if depth == 0:
|
|
break
|
|
j += 1
|
|
return text[i + 1:j]
|
|
|
|
|
|
def collect(min_items):
|
|
types = []
|
|
for src in Path('crates').glob('*/src'):
|
|
crate = src.parent.name.removeprefix('ironclaw_')
|
|
for f in src.rglob('*.rs'):
|
|
try:
|
|
text = f.read_text(errors='ignore')
|
|
except OSError:
|
|
continue
|
|
for m in DEF_RE.finditer(text):
|
|
kind, name = m.group(1), m.group(2)
|
|
body = body_of(text, m.start())
|
|
if kind == 'struct':
|
|
items = frozenset(
|
|
(fm.group(1), re.sub(r'\s+', '', fm.group(2)).rstrip(','))
|
|
for fm in FIELD_RE.finditer(body)
|
|
if fm.group(1) not in KEYWORDS)
|
|
else:
|
|
items = frozenset((vm.group(1), '') for vm in VARIANT_RE.finditer(body))
|
|
if len(items) >= min_items:
|
|
types.append((crate, kind, name, items, f))
|
|
return types
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('--jaccard', type=float, default=0.6)
|
|
ap.add_argument('--min-items', type=int, default=3)
|
|
args = ap.parse_args()
|
|
|
|
types = collect(args.min_items)
|
|
found = 0
|
|
for a, b in itertools.combinations(types, 2):
|
|
if a[0] == b[0] or a[1] != b[1]:
|
|
continue
|
|
i1, i2 = a[3], b[3]
|
|
union = len(i1 | i2)
|
|
jac = len(i1 & i2) / union if union else 0
|
|
n1 = frozenset(x[0] for x in i1)
|
|
n2 = frozenset(x[0] for x in i2)
|
|
njac = len(n1 & n2) / len(n1 | n2) if (n1 | n2) else 0
|
|
if jac >= args.jaccard or (njac >= 0.75 and min(len(n1), len(n2)) >= 4):
|
|
found += 1
|
|
print(f"jac={jac:.2f} njac={njac:.2f} {a[1]:6} "
|
|
f"{a[0]}::{a[2]} <-> {b[0]}::{b[2]}")
|
|
print(f"\n{found} candidate pair(s) from {len(types)} types "
|
|
f"(>= {args.min_items} items). Judge before acting.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|