@lifeaitools/rdc-skills 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude-plugin/plugin.json +284 -1
  2. package/VALIDATOR-ARCHITECTURE.md +534 -0
  3. package/commands/analyze-tests.md +11 -0
  4. package/commands/check-clean-code.md +11 -0
  5. package/commands/check-packages.md +10 -0
  6. package/commands/compare-compliance.md +14 -0
  7. package/commands/full-analysis.md +50 -0
  8. package/commands/get-refactoring-plan.md +13 -0
  9. package/commands/quick-check.md +13 -0
  10. package/commands/recover.md +149 -0
  11. package/commands/review-arch.md +12 -0
  12. package/commands/review.md +12 -113
  13. package/commands/suggest-patterns.md +11 -0
  14. package/commands/validate-solid.md +11 -0
  15. package/package.json +14 -2
  16. package/scripts/architecture-score.mjs +157 -0
  17. package/scripts/clean-code-score.mjs +177 -0
  18. package/scripts/duplication-score.mjs +66 -0
  19. package/scripts/lib/architecture-scoring.mjs +695 -0
  20. package/scripts/lib/clean-code-scoring.mjs +258 -0
  21. package/scripts/lib/duplication-scoring.mjs +238 -0
  22. package/scripts/lib/language-plugin.mjs +82 -0
  23. package/scripts/lib/package-metrics.mjs +439 -0
  24. package/scripts/lib/pattern-scoring.mjs +351 -0
  25. package/scripts/lib/plugins/treesitter.mjs +1182 -0
  26. package/scripts/lib/plugins/typescript.mjs +672 -0
  27. package/scripts/lib/refactoring-scoring.mjs +307 -0
  28. package/scripts/lib/solid-scoring.mjs +101 -0
  29. package/scripts/lib/test-smell-scoring.mjs +581 -0
  30. package/scripts/lib/vendor/codeflow-parser/.source-commit +1 -0
  31. package/scripts/lib/vendor/codeflow-parser/grammars.d.ts +23 -0
  32. package/scripts/lib/vendor/codeflow-parser/grammars.js +57 -0
  33. package/scripts/lib/vendor/codeflow-parser/memberFacts.d.ts +274 -0
  34. package/scripts/lib/vendor/codeflow-parser/memberFacts.js +1117 -0
  35. package/scripts/lib/vendor/codeflow-parser/nativeParser.d.ts +115 -0
  36. package/scripts/lib/vendor/codeflow-parser/nativeParser.js +759 -0
  37. package/scripts/lib/vendor/codeflow-parser/package.json +3 -0
  38. package/scripts/lib/vendor/codeflow-parser/xmlParser.d.ts +77 -0
  39. package/scripts/lib/vendor/codeflow-parser/xmlParser.js +400 -0
  40. package/scripts/package-metrics-cli.mjs +112 -0
  41. package/scripts/pattern-score.mjs +143 -0
  42. package/scripts/refactoring-score.mjs +253 -0
  43. package/scripts/solid-score.mjs +337 -0
  44. package/skills/architecture-reviewer/SKILL.md +287 -0
  45. package/skills/clean-code-analyzer/SKILL.md +147 -0
  46. package/skills/package-design/SKILL.md +118 -0
  47. package/skills/pattern-advisor/SKILL.md +237 -0
  48. package/skills/pattern-refactoring-guide/SKILL.md +262 -0
  49. package/skills/review/SKILL.md +29 -0
  50. package/skills/solid-validator/SKILL.md +92 -0
  51. package/skills/testing-strategy/SKILL.md +132 -0
  52. package/tests/lib/architecture-scoring.test.mjs +335 -0
  53. package/tests/lib/clean-code-scoring.test.mjs +241 -0
  54. package/tests/lib/duplication-scoring.test.mjs +144 -0
  55. package/tests/lib/fixtures.mjs +58 -0
  56. package/tests/lib/package-metrics.test.mjs +241 -0
  57. package/tests/lib/pattern-scoring.test.mjs +251 -0
  58. package/tests/lib/refactoring-scoring.test.mjs +264 -0
  59. package/tests/lib/solid-scoring.test.mjs +291 -0
  60. package/tests/lib/test-smell-scoring.test.mjs +281 -0
@@ -0,0 +1,534 @@
1
+ # Validator — Architecture
2
+
3
+ ## Third-party attribution
4
+
5
+ | Package | License (verified) | What we took from it |
6
+ |---|---|---|
7
+ | [OnSightTeam/architecture-toolkit](https://github.com/OnSightTeam/architecture-toolkit) | MIT (verified via its `package.json` `license` field + repo README `## License` section — the repo's root `LICENSE` file itself was not independently fetched this session) | Real detection logic, thresholds, and rule IDs for Steps 1–7 below, ported/adapted from its actual TypeScript source under `src/agents/*/tools/*.ts` (not reimplemented from a description) |
8
+ | [ts-morph](https://github.com/dsherret/ts-morph) `24.0.0` | MIT (verified: `node_modules/ts-morph/package.json`) | The AST layer for the TypeScript/JavaScript `LanguagePlugin` (`scripts/lib/plugins/typescript.mjs`) — still the backend for Clean Code, Patterns, and Refactoring this pass; see "AST parser — CLOSED" below for the SOLID swap to tree-sitter |
9
+ | [web-tree-sitter](https://www.npmjs.com/package/web-tree-sitter) `0.24.7` | MIT (verified: `node_modules/web-tree-sitter/package.json`) | The tree-sitter WASM runtime bindings for `scripts/lib/plugins/treesitter.mjs` — SOLID's default AST layer as of this pass |
10
+ | [tree-sitter-wasms](https://www.npmjs.com/package/tree-sitter-wasms) `0.1.13` | **Unlicense** (verified: `node_modules/tree-sitter-wasms/package.json` — public domain, NOT MIT) | Pre-built WASM grammar assets (TypeScript, TSX, JavaScript) consumed by `treesitter.mjs` |
11
+ | [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) `1.30.0` | MIT (verified: `node_modules/@modelcontextprotocol/sdk/package.json`) | MCP server transport, unrelated to the scoring logic itself |
12
+ | [zod](https://github.com/colinhacks/zod) `3.25.76` | MIT (verified) | Input validation elsewhere in this repo's skill tooling |
13
+ | [express](https://github.com/expressjs/express) `5.2.1` | MIT (verified) | HTTP transport for `rdc-skills-mcp`, unrelated to the scoring logic |
14
+ | [yaml](https://github.com/eemeli/yaml) `2.9.0` | **ISC** (verified — NOT MIT, corrected here) | YAML parsing elsewhere in this repo's skill tooling |
15
+
16
+ **Explicitly evaluated but NOT used** (their algorithms/prior-art informed design decisions below; no code or dependency taken):
17
+ [jscpd](https://github.com/kucherenko/jscpd), PMD [CPD](https://pmd.github.io/pmd/pmd_userdocs_cpd.html), [ArchUnitTS](https://github.com/LukasNiessen/ArchUnitTS), [dependency-cruiser](https://github.com/sverweij/dependency-cruiser).
18
+
19
+ ## What it is
20
+
21
+ A deterministic code-analysis suite covering SOLID, Clean Code, Clean
22
+ Architecture, package design, testing strategy, design patterns,
23
+ refactoring opportunities, and duplicate-code detection. Eight CLIs, zero
24
+ LLM calls inside any of them. Seven are ported from
25
+ [OnSightTeam/architecture-toolkit](https://github.com/OnSightTeam/architecture-toolkit)
26
+ (MIT) — their own project structure is 7 top-level agent folders under
27
+ `src/agents/`, and their own `.claude/skills/*.md` are thin wrappers around
28
+ `node dist/cli.js --agents=<name>`, no LLM in their loop either. This suite
29
+ matches that shape: one folder of pure functions per domain, one CLI per
30
+ domain, real detection logic ported from their real TypeScript source (not
31
+ reimplemented from a description of it), each independently dogfooded
32
+ against real code before being called done.
33
+
34
+ Four judgment calls remain genuinely un-mechanizable and stay routed to an
35
+ LLM reviewer (`pr-review-toolkit:code-reviewer`, or a Codex peer via
36
+ `rdc:co-develop`) — listed at the bottom of this document, not hidden inside
37
+ the tool tables.
38
+
39
+ ## Shared contract
40
+
41
+ Four tools (SOLID, Clean Code, Patterns, Refactoring) read from one shape:
42
+ `NormalizedUnit` / `NormalizedMember`, defined in
43
+ [`scripts/lib/language-plugin.mjs`](scripts/lib/language-plugin.mjs). Two
44
+ plugins produce that shape for TypeScript/JavaScript today:
45
+ [`scripts/lib/plugins/typescript.mjs`](scripts/lib/plugins/typescript.mjs)
46
+ (ts-morph — the ONE file in this repo allowed to import it) and
47
+ [`scripts/lib/plugins/treesitter.mjs`](scripts/lib/plugins/treesitter.mjs)
48
+ (the fleet's own tree-sitter parser, ported from CodeFlow's
49
+ `nativeParser.ts` — see "AST parser — CLOSED" below). SOLID defaults to the
50
+ tree-sitter plugin (`--parser` selects either); Clean Code, Patterns, and
51
+ Refactoring still default to ts-morph this pass. Every scoring file
52
+ downstream of either plugin is pure functions over the shared shape; none of
53
+ them touch an AST directly. This is what "language independent" means in
54
+ practice: adding a Python or Go plugin that emits the same `NormalizedUnit`
55
+ shape makes all four of those tools work on that language with zero changes
56
+ to the scoring logic itself.
57
+
58
+ Three tools (Package Design, Architecture, Duplicate Code) are deliberately
59
+ NOT built on this contract — they operate on file paths, import statements,
60
+ or raw token streams, which a plain text/regex scan answers as correctly as
61
+ an AST would, so they carry no `ts-morph` dependency at all and work on any
62
+ language whose imports look like ES/CJS syntax (Package Design, Architecture)
63
+ or whose source can be tokenized at all (Duplicate Code — see Step 8).
64
+
65
+ All 8 CLIs support `--format json`, are proven deterministic (identical
66
+ output across two runs on the same unchanged input — no timestamps, no
67
+ absolute paths, sorted finding arrays), and are registered as global bins in
68
+ `package.json`.
69
+
70
+ ---
71
+
72
+ ## Step 1 — SOLID
73
+
74
+ | | |
75
+ |---|---|
76
+ | **Call** | `rdc-solid-score <path> [--diff <ref>] [--config <file>]` |
77
+ | **Input** | file or directory path; optional git ref to score a diff; optional boundary-check config |
78
+ | **Script** | [`scripts/lib/solid-scoring.mjs`](scripts/lib/solid-scoring.mjs), CLI in [`scripts/solid-score.mjs`](scripts/solid-score.mjs) |
79
+ | **Algorithm** | SRP: union-find over shared `this.field`/method-to-method access, connected-component count. OCP: switch/instanceof/type-check density per member. LSP: override signature, super-call presence, return-type drift vs. base class. ISP: public-member count + average param count. DIP: ratio of concrete `new X()` instantiation vs. injected/abstract dependency. Plus a separate Clean Architecture boundary check (`{orchestrator, requiredPorts}` config-driven). |
80
+ | **Output** | JSON: per-unit `{srp, ocp, lsp, isp, dip}` scores 0–100 each with a confidence label (`high`/`medium`/`low`/`none`), a weighted `total` renormalized over only the measured criteria (weights: srp .20 / ocp .15 / lsp .15 / isp .20 / dip .30), plus boundary violations if configured. |
81
+ | **Source** | Original build, not ported — this repo's own design, informed by standard SOLID literature (Martin, *Agile Principles, Patterns, and Practices*). `confidence: 'none'` exists because a class with real constructor-injected deps and arrow methods was scoring a false 100 before the AST-completeness fix (2026-08-20) — see the file header. |
82
+
83
+ ---
84
+
85
+ ## Step 2 — Clean Code
86
+
87
+ | | |
88
+ |---|---|
89
+ | **Call** | `rdc-clean-code-score <path> [--project-root <dir>] [--no-dead-exports]` |
90
+ | **Input** | file or directory path; optional project root for cross-file resolution |
91
+ | **Script** | [`scripts/lib/clean-code-scoring.mjs`](scripts/lib/clean-code-scoring.mjs), CLI in [`scripts/clean-code-score.mjs`](scripts/clean-code-score.mjs) |
92
+ | **Algorithm** | N1/N2/N4/N7: per-declared-name checks (cryptic, noise-word, magic-number-outside-const, generic-suffix) walking the real AST, not a whole-file regex. F1/F2: statement count (>20) / param count (>3) per member. E1: empty catch-block scan. G9a: unreachable constant-conditional branch. G9b: cross-file unused-export scan via `findReferencesAsNodes()`, gated behind a positive-control check. |
93
+ | **Output** | JSON: findings list, each `{rule, file, line, message, confidence}`; G9 findings additionally carry `deadExportsScope.positiveControlOk` — G9b's export-usage findings are withheld entirely if the control fails. |
94
+ | **Source** | `src/agents/clean-code-analyzer/tools/{naming,function,code-smell}-validator.ts`, github.com/OnSightTeam/architecture-toolkit (MIT). Their checks are whole-file text regexes with an occurrence-count threshold to suppress false positives; this port reads the real AST per declared binding instead, so every occurrence is its own finding with no threshold needed. **Not ported**: N3, N5, N6, C1–C5, G14, G16, G28 — named with reasons in `skills/clean-code-analyzer/SKILL.md`. G5 (Duplicate Code) is real but lives in Step 8 below, not here — it's a cross-file, whole-corpus check, the wrong shape for this file's per-unit contract. |
95
+
96
+ ---
97
+
98
+ ## Step 3 — Package Design
99
+
100
+ | | |
101
+ |---|---|
102
+ | **Call** | `rdc-package-metrics <packagesRoot>` or `--dirs <d1,d2,...>` |
103
+ | **Input** | directory containing `packages/*`, or an explicit list of package dirs |
104
+ | **Script** | [`scripts/lib/package-metrics.mjs`](scripts/lib/package-metrics.mjs), CLI in [`scripts/package-metrics-cli.mjs`](scripts/package-metrics-cli.mjs) |
105
+ | **Algorithm** | Robert C. Martin's package-coupling metrics. Resolves every `import`/`export...from`/`require()`/dynamic `import()` across sibling packages via plain `node:fs` + regex (no AST — deliberately independent of the ts-morph plugin). Ca = distinct importing packages, Ce = distinct imported packages, Instability I = Ce/(Ca+Ce), Abstractness A = exported interface/type ÷ exported total (`.ts` files only), Distance D = \|A+I-1\|, real graph cycle-walk for ADP violations. |
106
+ | **Output** | JSON: per-package `{ca, ce, instability, abstractness, distanceFromMainSequence, zone}` (`zone` ∈ main-sequence / zone-of-pain / zone-of-uselessness / off-main-sequence / unmeasurable), plus named cycle paths (`a → b → c → a`), not just "a cycle exists." |
107
+ | **Source** | Formulas checked against `src/agents/package-design/tools/{stability-metrics-calculator,package-coupling-analyzer}.ts`, github.com/OnSightTeam/architecture-toolkit (MIT) — confirmed independently rather than copied (both short enough to derive from Martin's own published formulas and cross-check). The cycle-detection graph walk is this repo's own implementation, later reused by Step 5. |
108
+
109
+ ---
110
+
111
+ ## Step 4 — Testing Strategy
112
+
113
+ | | |
114
+ |---|---|
115
+ | **Call** | `rdc-test-smell-score <test-path> [--repo-root <dir>]` |
116
+ | **Input** | test file or directory (`.test.mjs`/`.test.ts`/`*.spec.*`) |
117
+ | **Script** | [`scripts/lib/test-smell-scoring.mjs`](scripts/lib/test-smell-scoring.mjs) (also its own CLI — has a runnable main-guard block) |
118
+ | **Algorithm** | T1: test-block count vs. exported-unit count of the paired source file (via the same `NormalizedUnit` contract, not a regex). T2: `.skip`/`xit`/`xdescribe` scan. T5/T6: assertions-per-block (>10) / lines-per-block (>30), via a brace-balanced block extractor (not a non-greedy regex, which breaks on nested `{}`). T7/T8: literal timer/`Date.now`/`Math.random`/`process.env` calls inside a test body. T9: Jaccard similarity (≥0.75) over normalized 3-token shingles of `beforeEach`/`beforeAll` bodies, across files. FIRST-Independent: a `let` declared outside test() and mutated in ≥2 separate test() blocks. |
119
+ | **Output** | JSON: findings list per rule with file:line and description; T9 findings span multiple files by design. |
120
+ | **Source** | `src/agents/testing-strategy/tools/test-quality-validator.ts`, github.com/OnSightTeam/architecture-toolkit (MIT), lines 47–240 (T1:47-65, T2:67-85, T5:127-145, T6:147-171, T7:173-190, T8:192-219, T9:221-240). The brace-balanced extractor replacing their non-greedy regex is this repo's own fix. **Not ported**: T3 (Test Per Class), T4 (Untested Method), and Fast/Repeatable/SelfValidating/Timely (FIRST's other 4 letters) — named with reasons in `skills/testing-strategy/SKILL.md`. |
121
+
122
+ ---
123
+
124
+ ## Step 5 — Architecture (Clean Architecture boundaries)
125
+
126
+ | | |
127
+ |---|---|
128
+ | **Call** | `rdc-architecture-score <path> [--config <file>]` |
129
+ | **Input** | directory path; optional layer-classification config (glob → layer name mapping) |
130
+ | **Script** | [`scripts/lib/architecture-scoring.mjs`](scripts/lib/architecture-scoring.mjs), CLI in [`scripts/architecture-score.mjs`](scripts/architecture-score.mjs) |
131
+ | **Algorithm** | Self-contained, no AST — file-path + import-target analysis, same shape as Step 3. Classifies each file into a Clean Architecture layer (Entities/UseCases/Adapters/Frameworks) by configurable path glob, then checks: dependency-direction (inner layer imports outer), framework-coupling (concrete framework import inside Entities/UseCases), missing-abstraction (5 subtypes — a UseCase file importing a concrete DB/HTTP client instead of a port/repository interface), circular-layer-dependency (reuses Step 3's `findCycles` graph walk, applied to layers instead of packages). |
132
+ | **Output** | JSON: findings by violation type, each citing the file(s) and layer(s) involved; low-confidence heuristic findings (mixed-concerns, UI-mixing, mixed-layer-imports) are labeled as such, not reported as certain. |
133
+ | **Source** | `src/agents/architecture-reviewer/tools/{dependency-rule-validator (213 lines), boundary-analysis-validator (169 lines), layer-separation-validator (145 lines)}.ts`, github.com/OnSightTeam/architecture-toolkit (MIT), fetched and read in full 2026-08-20. Their own circular-dependency check (a `"../../.."`-depth proxy) was deliberately NOT ported — replaced with a real graph cycle walk, which is strictly stronger. A real bug was caught by this tool's own positive-control fixture before ship: a `**`-glob-to-regex translation mismatched top-level paths with no parent directory segment — fixed same session. |
134
+
135
+ ---
136
+
137
+ ## Step 6 — Pattern Advisor
138
+
139
+ | | |
140
+ |---|---|
141
+ | **Call** | `rdc-pattern-score <path>` |
142
+ | **Input** | file or directory path |
143
+ | **Script** | [`scripts/lib/pattern-scoring.mjs`](scripts/lib/pattern-scoring.mjs), CLI in [`scripts/pattern-score.mjs`](scripts/pattern-score.mjs) |
144
+ | **Algorithm** | 10 structural detectors over `NormalizedUnit`/`NormalizedMember` facts (`switchStatements[].hasTypeCreation`, `switchBehaviorCallLine`, `constructorNewCallTargets`, `conditionalFeatureCallLine`, `deepChainCallCount`, `calleeNames`, `hasGetInstanceMethod`, field-access for Command): Factory Method (switch/if-else creating types via `new`), Builder (>4-param constructor), Singleton (`getInstance()` present), Decorator (conditional feature-wrapping call), Adapter (interface-conversion call pattern), Facade (deep call-chain into a subsystem), Strategy (switch/if-else selecting behavior), Observer (manual notify/listener pattern), Command (undo/redo/queue call OR field-access), Template Method (base method calling overridable hooks). |
145
+ | **Output** | JSON: per-pattern findings, each with confidence and the specific structural signal matched, attributed to a real member/unit and line. |
146
+ | **Source** | `src/agents/pattern-advisor/tools/{creational,structural,behavioral}-pattern-analyzer.ts`, github.com/OnSightTeam/architecture-toolkit (MIT), fetched and read in full 2026-08-20. Their checks are whole-file text regexes with zero scoping to which switch/if/call the signal actually came from (their own Factory regex matches if `new` appears anywhere after a type-switch's opening brace, even statements later) — this port walks the real AST per switch-case/if-block/call-expression, so every finding is real-line-attributable. A real undercount was found and fixed during build: Command's original port only scanned call names, missing field-access-only usage (`this.queue`/`this.history` read but never called). |
147
+
148
+ ---
149
+
150
+ ## Step 7 — Refactoring Guide
151
+
152
+ | | |
153
+ |---|---|
154
+ | **Call** | `rdc-refactoring-score <path> [--project-root <dir>] [--no-effort]` |
155
+ | **Input** | file or directory path; optional project root for cross-file call-site resolution |
156
+ | **Script** | [`scripts/lib/refactoring-scoring.mjs`](scripts/lib/refactoring-scoring.mjs), CLI in [`scripts/refactoring-score.mjs`](scripts/refactoring-score.mjs) |
157
+ | **Algorithm** | 9 rules over `NormalizedUnit`/`NormalizedMember` facts: extract-method (statementCount > 25), extract-class (method count > 15), introduce-parameter-object (params > 4), replace-magic-number (reuses Step 2's N4 signal, reframed as a fix recommendation), consolidate-duplicate-code (repeated statement text > 3× across > 3 patterns), decompose-conditional (> 2 conditions, each ≥ 50 chars), strategy/factory/null-object-transform candidates. `estimateEffort()` reuses the SAME cross-file `findReferencesAsNodes()` reference-graph walk as Step 2's G9 dead-export check to get a real call-site count, not a guess. |
158
+ | **Output** | JSON: findings with `{type, file, line, effort: low\|medium\|high\|null}` — `effort` is `null` for module-level (non-class) findings rather than a fabricated guess, since call-site resolution currently only covers exported class symbols. |
159
+ | **Source** | `src/agents/pattern-refactoring-guide/tools/{refactoring-analyzer, code-smell-refactoring-guide, pattern-transformation-guide}.ts`, github.com/OnSightTeam/architecture-toolkit (MIT), fetched and read in full 2026-08-20; two stale line-citations in the prior skill doc were corrected against the real source during this build (`pattern-transformation-guide.ts:109→110`, `:169→168`). **Deliberately not merged** with Step 2's thresholds even though both measure similar facts: extract-method here fires at statementCount > 25 (the toolkit's real refactoring-domain number, `refactoring-analyzer.ts:49`) vs. Clean Code's F1 at > 20 — same underlying fact, two different real thresholds for two different domains, kept separate on purpose. |
160
+
161
+ ---
162
+
163
+ ## Step 8 — Duplicate Code (G5)
164
+
165
+ | | |
166
+ |---|---|
167
+ | **Call** | `rdc-duplication-score <path> [--min-tokens <n>] [--format text\|json]` |
168
+ | **Input** | file or directory path; `--min-tokens` default 50 (matches PMD CPD's default token threshold) |
169
+ | **Script** | [`scripts/lib/duplication-scoring.mjs`](scripts/lib/duplication-scoring.mjs), CLI in [`scripts/duplication-score.mjs`](scripts/duplication-score.mjs) |
170
+ | **Algorithm** | Token-shingle Rabin-Karp rolling-hash matching — the same technique jscpd and PMD's CPD both use. Tokenizes each file (comments stripped by extension, string literals kept as real content since a repeated literal is real duplication), computes a rolling hash over every `minTokens`-length window in O(n), buckets windows by hash, then **merges consecutive matching window offsets into maximal contiguous blocks** — the merge step is the part that matters: without it, a single real 60-token duplicate reports as ~40 overlapping findings (one per window slide), which is exactly the bug this tool's own first draft shipped with and caught via its own positive-control test before this doc was written. |
171
+ | **Output** | JSON: `{duplicates: [{tokenCount, occurrences: [{file, startLine, endLine}, ...]}], filesScanned, minTokens}` — each `duplicates[]` entry is one real contiguous duplicate block, not one per window slide. |
172
+ | **Source** | Algorithm choice informed by [jscpd](https://github.com/kucherenko/jscpd) and PMD's [CPD](https://pmd.github.io/pmd/pmd_userdocs_cpd.html) (both real, mature, Rabin-Karp-based — see Research Bibliography below) — **no code taken from either**; this is an independent implementation of the published algorithm, not a port. **Verified against the real toolkit source** (`src/agents/clean-code-analyzer/tools/code-smell-validator.ts`, `checkDuplication()`): their G5 is a same-file repeated-line-text counter (flags a line if it appears >3 times in ONE file) — no cross-file matching, no minimum block length, a one-line coincidental repeat counts the same as a real duplicated block. This implementation is strictly stronger: real cross-file structural matching, a minimum contiguous-block length (not per-line), and reports the actual matched block range on both sides, not just a per-file count. |
173
+
174
+ ---
175
+
176
+ ## What's still judgment, not mechanical
177
+
178
+ Four questions no AST/regex fact can answer, still routed to an LLM
179
+ reviewer (`pr-review-toolkit:code-reviewer` via `Agent()`, or a Codex peer
180
+ via `rdc:co-develop`) — named here so nothing is silently hidden inside a
181
+ tool's own "done" claim:
182
+
183
+ | Skill | Judgment call | Why it can't be mechanical |
184
+ |---|---|---|
185
+ | `architecture-reviewer` | Is this abstraction boundary architecturally *right* for the domain, not just shaped right | Shape-correctness (Step 5) is a fact; domain-fit is a design opinion |
186
+ | `pattern-advisor` | Is the detected pattern actually the correct fit here, not just structurally similar | A switch-selecting-behavior IS a Strategy signal (Step 6) whether or not Strategy is the right call for this problem |
187
+ | `clean-code-analyzer` | Does a name lie about what the code does (semantic, not shape) | N1/N2/N7 (Step 2) catch shape; a name that's the right LENGTH and SPECIFICITY but describes the wrong behavior needs reading intent against implementation |
188
+ | `package-design` | Does a package's actual responsibility match its name/README | Ca/Ce/cohesion (Step 3) are structural; "does this package do what it claims" needs reading intent |
189
+
190
+ ## Decisions closed this pass (not left open)
191
+
192
+ - **AST parser (SOLID) — closed.** Swapped `solid-score.mjs` from ts-morph
193
+ to the fleet's own tree-sitter parser (`scripts/lib/plugins/treesitter.mjs`,
194
+ ported from CodeFlow's `nativeParser.ts`), default `--parser tree-sitter`.
195
+ Exact parity on `rdc-harness`'s `Harness` (68.5/100, both backends, every
196
+ criterion). One real bug found and fixed (concise-arrow-body blindness);
197
+ one real pre-existing ts-morph defect found and root-caused (shared-project
198
+ degradation at 100+ files — not a tree-sitter issue). Clean Code, Patterns,
199
+ and Refactoring stay on ts-morph this pass — see "AST parser — CLOSED"
200
+ below for full detail.
201
+ - **G5 duplication detection** — closed. Built Step 8 above, real
202
+ Rabin-Karp implementation, positive-control-verified, one real bug (window
203
+ merge) caught and fixed before ship.
204
+ - **Cycle-detection algorithm (Steps 3 and 5) — evaluated, decision: KEEP
205
+ the hand-rolled graph walk, do not adopt ArchUnitTS or dependency-cruiser.**
206
+ Reasoning: (1) our walk is already proven correct — dogfooded against
207
+ `rdc-harness` and this repo's own tree, zero cycles found, independently
208
+ confirmed by hand-verification, not just trusted; (2) it's reused twice
209
+ (Steps 3 and 5) with proven determinism, satisfying the ATF golden-capture
210
+ requirement; (3) both external tools are designed as standalone CLI/CI
211
+ linters with their own config/output format, not as an importable pure
212
+ function returning JSON into another tool's pipeline — adopting either
213
+ would mean wrapping a subprocess or forking their internals, a bigger
214
+ footprint than the ~40-line graph walk already in `package-metrics.mjs`
215
+ for a problem with no identified functional gap. Revisit only if a real
216
+ case surfaces that the hand-rolled walk gets wrong.
217
+ - **Unit tests for all 8 scoring libraries** — closed. 243 tests under
218
+ [`tests/lib/`](tests/lib/), Node's built-in `node:test` (no new
219
+ dependency), one file per library plus a shared `fixtures.mjs`. Every
220
+ rule/detector/threshold has a violation fixture; every numeric threshold
221
+ (F1 >20, F2 >3, Builder >4 params, T5 >10, T6 >30, etc.) is tested at and
222
+ just past the boundary. Re-run independently (`node --test
223
+ tests/lib/*.test.mjs`), not just trusted from the build report: **243
224
+ pass, 0 fail.** No bugs found in the libraries themselves. One real design
225
+ finding, not a bug: `package-metrics.mjs`'s `zone` classifier has an
226
+ `'off-main-sequence'` branch that is mathematically unreachable — since
227
+ `instability`/`abstractness` are both bounded `[0,1]`, `distance > 0.5`
228
+ can only happen when both values fall under 0.5 (zone-of-pain) or both
229
+ over 0.5 (zone-of-uselessness); no input reaches the mixed quadrant with
230
+ distance > 0.5. Left as-is (harmless dead branch, not incorrect output),
231
+ documented in the test file with the proof rather than silently removed.
232
+
233
+ ## AST parser — CLOSED (SOLID moved to tree-sitter; 3 tools stay on ts-morph)
234
+
235
+ **Previously disclosed here as an open finding, now closed by direct operator
236
+ instruction (Dave, 2026-08-20):** the AST layer (`scripts/lib/plugins/
237
+ typescript.mjs`) used `ts-morph` (TypeScript/JavaScript only) without first
238
+ checking for existing fleet infrastructure. The monorepo already owns
239
+ [`@regen/codeflow-parser`](https://github.com/LIFEAI/regen-root/tree/e018e119dd22c2b75c9cae243a79230b495c53c7/packages/codeflow-parser)
240
+ ([`nativeParser.ts`](https://github.com/LIFEAI/regen-root/blob/e018e119dd22c2b75c9cae243a79230b495c53c7/packages/codeflow-parser/src/nativeParser.ts))
241
+ — a genuinely multi-language, standalone, in-process tree-sitter parser
242
+ (TypeScript, JavaScript, Python, C, C++, C#), confirmed callable in-process
243
+ as a library (`this.parser.parse(files)`, CodeFlow's own ingestion pipeline)
244
+ with the PM2 `server.ts` wrapper as an optional separate deployment this
245
+ validator does not depend on.
246
+
247
+ **Built:** [`scripts/lib/plugins/treesitter.mjs`](scripts/lib/plugins/treesitter.mjs)
248
+ — a new `LanguagePlugin` implementing the full `NormalizedUnit`/
249
+ `NormalizedMember` contract for TypeScript, TSX, and JavaScript on
250
+ `web-tree-sitter` + `tree-sitter-wasms` directly (no dependency on
251
+ `regen-root` at runtime — the port is textual, done once). Foundation ported
252
+ from `nativeParser.ts`'s `extractTsJsSymbols`/`extractCallsFromBody`/
253
+ `extractTsJsImports` (same node-type vocabulary, same top-level-declaration
254
+ walk shape), then extended with every fact `NormalizedUnit`/
255
+ `NormalizedMember` requires that `nativeParser.ts` does not compute
256
+ (statement counts, magic numbers, empty catches, dead conditionals,
257
+ switch-statement shapes, null checks, complex conditionals, callee names,
258
+ constructor `new` targets, deep-chain call counts, `getInstance` detection,
259
+ static property names, LSP override comparison against a resolved base
260
+ class, and cross-file `deadExportsOf`/`referenceSitesOf` via a real
261
+ identifier-text walk over every cached parsed file — tree-sitter has no
262
+ `findReferencesAsNodes()` language service). Every tree-sitter node type and
263
+ field name used (`public_field_definition`, `method_definition`'s
264
+ `parameters`/`body`/`return_type` fields, `if_statement`'s `condition`/
265
+ `consequence`/`alternative`, `else_clause` wrapping an `else if` as a nested
266
+ `if_statement`, `super()` vs `super.method()`'s different function-field
267
+ shapes, parenless single-param arrows exposing a bare `parameter` field, TS
268
+ `enum_assignment`) was verified empirically by parsing representative
269
+ TypeScript with the installed grammar and inspecting the resulting CST —
270
+ not assumed from memory of the grammar.
271
+
272
+ `scripts/solid-score.mjs` now takes `--parser tree-sitter|ts-morph`,
273
+ **defaulting to `tree-sitter`** per the operator's instruction to actually
274
+ use the new plugin, not just build it unused; `ts-morph` stays available as
275
+ an escape hatch/regression-comparison lever.
276
+
277
+ **Parity — real numbers, via the actual CLI, both flags, same target
278
+ (`rdc-harness`'s `Harness` god-object, `packages/core/src/index.mjs`):**
279
+
280
+ ```
281
+ === tree-sitter (default) ===
282
+ Harness (class) total=68.5
283
+ SRP: 40 [high] 3 connected component(s) across 24 member(s)
284
+ OCP: 100 [low] 0 branch/type-check hit(s) across 24 member(s)
285
+ LSP: 100 [low-medium] no base class
286
+ ISP: 73 [medium-high] 18 public member(s), avg 0.8 param(s)
287
+ DIP: 53 [high] 15 concrete instantiation(s) of 32 total dependenc(y/ies)
288
+
289
+ === ts-morph (--parser ts-morph) ===
290
+ Harness (class) total=68.5 <- byte-identical, all 5 criteria, all details
291
+ ```
292
+ Not just in-range: **exactly** `68.5/100` under both backends, every
293
+ per-criterion score and detail string identical. `RefusedError` (the file's
294
+ other class) also scored identically (100/100, both backends). Confirmed
295
+ with a member-level diff across every `NormalizedMember` field
296
+ (`paramCount`, `branchHits`, `statementCount`, `isPublic`, `fieldAccess`,
297
+ `calls`) before wiring the CLI — zero diffs.
298
+
299
+ **A real bug found and fixed during broader dogfooding (not on the Harness
300
+ target — found by scoring 130 files across `rdc-skills` + `rdc-harness`):**
301
+ a concise-body arrow class/module property — `model = () => new
302
+ PhaseModel({...})` (`rdc-harness/packages/phases/test/phase-model.test.mjs:21`)
303
+ — has its ENTIRE body AS the `new_expression`/`call_expression`/
304
+ `member_expression` node itself (an arrow function's concise body is the
305
+ expression directly, not a `statement_block` wrapping it). The self-
306
+ exclusive tree walk (`getDescendantsOfKind`-equivalent, visits children only)
307
+ only tested the body's CHILDREN, never the body node's own type — so
308
+ `new PhaseModel` itself was invisible to `constructorNewCallTargetsOf`/
309
+ `callsOf`/`fieldsOf`/`branchHitsOf`/`calleeNamesOf`/`deepChainCallCountOf`
310
+ for every concise-arrow member. Fixed by making the walk self-inclusive
311
+ (`walkSelfAndDescendants`) — verified safe everywhere else in the file
312
+ (every other target node type this plugin searches for can never
313
+ structurally BE the root node passed in) and confirmed fixed by re-running
314
+ the same 130-file dogfood pass.
315
+
316
+ **A second, larger divergence found and root-caused during the same
317
+ dogfooding — and it is NOT a tree-sitter defect:** scoring all 130 files in
318
+ one process, 9 files showed DIP-score gaps up to 24.3 points, `ts-morph`
319
+ consistently reporting FEWER concrete instantiations than tree-sitter (e.g.
320
+ `phase-model.test.mjs`: ts-morph 0, tree-sitter 7). Root-caused by hand: a
321
+ **fresh, independent ts-morph project** parsing that same file finds **7**
322
+ `new PhaseModel(...)` nodes — matching tree-sitter exactly — and `typescript.mjs`'s
323
+ own `sharedProject()`, warmed up with only the 2 files that actually matter
324
+ (the defining file + the test file), ALSO correctly returns **7**. Only the
325
+ `sharedProject()` instance that had accumulated 113+ prior files in one
326
+ long-running process returns 0. This is a **pre-existing correctness defect
327
+ in ts-morph's incremental shared-`Project` pattern at scale**, not something
328
+ this build introduced — and it is evidence FOR the swap, not against it:
329
+ tree-sitter's plugin holds no incremental language-service state to go
330
+ stale, so it does not reproduce this failure mode at all. `solid-score.mjs`'s
331
+ real invocation pattern (one target directory/file per process) rarely hits
332
+ the file count where ts-morph's degradation appears, but a long-running
333
+ multi-package sweep (exactly what dogfooding this pass did) will.
334
+
335
+ **Determinism** — same requirement as every other tool in this doc: ran
336
+ `solid-score.mjs` twice against the same target, `--format json`, both a
337
+ single-file target and a whole-directory target, diffed the output.
338
+ **Byte-identical both times**, both scopes.
339
+
340
+ **UPDATE (2026-08-20/21, same night) — `treesitter.mjs` now consumes
341
+ `nativeParser.ts`'s own `members[]`/`units[]` extraction directly, instead of
342
+ re-implementing it:** the parity numbers and bug writeups directly above
343
+ describe the FIRST build of `treesitter.mjs`, at a point where
344
+ `nativeParser.ts` only extracted `symbols`/`interfaces`/`calls`/`imports` at a
345
+ coarse, top-level-only granularity — it never walked class method bodies at
346
+ all, which is why this plugin had to independently re-derive every per-member
347
+ fact from scratch. Minutes after that first build shipped, `nativeParser.ts`
348
+ gained a real `members[]`/`units[]`/`references[]` surface (a new
349
+ `src/memberFacts.ts`, commit `1e4e4012b` on `regen-root`'s `develop`) that
350
+ walks EVERY callable body — not just top-level functions — and computes
351
+ almost exactly the same per-member/per-unit facts this plugin was
352
+ duplicating. This plugin was rewritten the same night to consume that surface
353
+ directly rather than continue re-deriving it.
354
+
355
+ **What changed, mechanically:** the compiled `nativeParser.js`/`grammars.js`/
356
+ `memberFacts.js`/`xmlParser.js` (plus `.d.ts`) were re-vendored into
357
+ `scripts/lib/vendor/codeflow-parser/` from `regen-root`'s freshly-built
358
+ `dist/`, and `.source-commit` bumped to `a354d5be2d1db865faea92c1013eb2a62981e271`
359
+ (the `x-claude-sv` worktree HEAD at vendor time). `treesitter.mjs` now imports
360
+ `extractMembers` from the vendored `memberFacts.js` and calls it directly and
361
+ SYNCHRONOUSLY against its own tree-sitter parse of each file — not through
362
+ `createNativeParser().parse()`'s `async` service wrapper, which cannot be
363
+ called from `extractUnits()` (the `LanguagePlugin` contract in
364
+ `../language-plugin.mjs` requires that method to stay synchronous, and
365
+ `parse()` is `async` end-to-end because it also fronts an XML branch and a
366
+ batch override/reference-resolution pass this plugin doesn't use).
367
+ `extractMembers` itself has no `await` in it anywhere — a plain, pure,
368
+ deterministic function of `(rootNode, language)` per its own header contract
369
+ — so calling it directly is the correct fix for the sync/async mismatch, not
370
+ a workaround.
371
+
372
+ **Field mapping — `NormalizedMember`/`NormalizedUnit` (this plugin's
373
+ contract) ← `ParsedMember`/`ParsedUnit` (the vendored extractor's):**
374
+
375
+ | NormalizedMember/Unit field | Source | Note |
376
+ |---|---|---|
377
+ | `paramCount` | `ParsedMember.paramCount` | direct |
378
+ | `fieldAccess` | `ParsedMember.fieldAccess` | direct — now deduped + lexicographically sorted (was unsorted, with duplicate occurrences, before) |
379
+ | `branchHits` | `ParsedMember.branchHits` | direct — semantics broadened: native counts a `switch`'s `default` arm as a branch and counts an if/else-if chain by its full arm count, where this plugin's own prior local walk counted only chain LINKS and never counted `default`. Real, verified difference — see parity re-run below |
380
+ | `statementCount` | `ParsedMember.statementCount` | direct — boundary differs: native's walk stops at a NESTED callable (a closure passed to `.map()` is its own member), where this plugin's prior local walk was self-inclusive across ALL nesting depths including nested closures. Deliberate design in `memberFacts.ts` ("a closure...is its own member...folding its statements into the enclosing method would inflate every complexity signal") |
381
+ | `declaredNames` | `ParsedMember.declaredNames`, filtered | destructured-pattern entries (`{ handle, target, snapshot }` as ONE combined name — verified empirically) are dropped; `kind` field stripped to match this plugin's existing shape |
382
+ | `magicNumbers` | `ParsedMember.magicNumbers`, mapped | `value` coerced `string→number` (native emits `value` as source text, e.g. `"-5"`); native's 0/1/-1 exclusion is a STRING comparison (`"1.0"` would NOT be excluded) where the prior local version excluded by NUMBER comparison — a real, disclosed edge-case difference, not hit in the Harness fixture |
383
+ | `constructorNewCallTargets` | `ParsedMember.constructorNewCallTargets` | direct |
384
+ | `deepChainCallCount` | `ParsedMember.deepChainCallCount` | direct |
385
+ | `calleeNames` | `ParsedMember.calleeNames` | direct |
386
+ | `concreteInstantiations`, `totalDependencies` (unit, class only) | `ParsedUnit.concreteInstantiations`/`.totalDependencies` | direct — `totalDependencies` uses a FUNDAMENTALLY DIFFERENT formula than this plugin's prior local one (native: distinct non-self call receivers + `new`-targets, minus own member names; prior local: concrete instantiations + import-specifier count + constructor-injected-typed-param count) — a real, measured difference, see parity re-run |
387
+ | `staticPropertyNames`, `hasGetInstanceMethod`, `hasBaseClass` (unit, class only) | `ParsedUnit.*` | direct — `hasGetInstanceMethod`/`hasBaseClass` both broaden slightly (more `getInstance`-family names; more heritage-clause node types recognized) |
388
+ | `calls` | **stays local** | this plugin's contract keeps a `this.`-stripped-only, receiver-otherwise-preserved form (`obj.method()` → `"obj.method"`) for `solid-scoring.mjs`'s SRP same-component test; native's `calleeNames` strips EVERY receiver, which is right for pattern-scoring.mjs's keyword scans but wrong for SRP's sibling-call detection — would have created spurious cross-member unions |
389
+ | `isPublic` | **stays local** | native's `ParsedMember.exported` is the OWNING CLASS's export flag propagated to every member — it has no `private`/`protected`/`#`-prefix accessibility signal at all |
390
+ | `override` | **stays local** | native's `resolveOverrideShapes` is BATCH-scoped across one `parse()` call over every file at once; this plugin's `extractUnits` is called per-file, incrementally — re-running a whole-project batch parse on every single-file call would be a real perf regression, so cross-file base-method resolution keeps using this plugin's existing `fileCache`-backed lookup, unchanged |
391
+ | `emptyCatches`, `deadConditionals`, `nullChecks` | **stay local** | native reports these as a bare COUNT (`number`), not an array — `clean-code-scoring.mjs`'s E1/G9 findings and `refactoring-scoring.mjs`'s null-object-transform read `.line` (and, for `deadConditionals`, `.kind`) per occurrence, which a count cannot supply |
392
+ | `statementTexts`, `complexConditionals` | **stay local** | native reports these as `string[]` (text only, no `line`) — `refactoring-scoring.mjs`'s consolidate-duplicate-code and decompose-conditional findings need `.line` (and, for `complexConditionals`, `.length`) to build a locatable finding |
393
+ | `switchStatements[].hasBehaviorCall/.hasTypeCreation`, `switchBehaviorCallLine`, `conditionalFeatureCallLine` | **stay local** | native's `SwitchFact.behaviorDispatch`/`.typeConstruction` use a DIFFERENT, broader test (any call-or-return in a case; any `new` inside a type-named discriminant switch) than this repo's specific architecture-toolkit word lists (calculate/process/validate/format for clean-code and refactoring; calculate/process/execute/validate/format for pattern-advisor's Strategy; wrap/add/extend/enhance for Decorator) — reusing native's flags would silently change which findings fire |
394
+ | `deadExportsOf`/`referenceSitesOf` | **unchanged, fully local** | cross-file identifier-text walk, as before this pass; SOLID never calls either, so this is orthogonal to the parity numbers below |
395
+
396
+ **Correlation.** `extractMembers` returns a FLAT `members[]` including every
397
+ nested closure as its own entry with `owner: null` (a callback is genuinely
398
+ its own member with its own facts, per `memberFacts.ts`'s own design). This
399
+ plugin still needs to decide what counts as a "member" under its OWN contract
400
+ (a class's own methods/arrow-fields, or a file's own top-level declarations —
401
+ never an inner closure folded into one of those), so it keeps its existing
402
+ identity walk (`memberEntriesOf` for a class body; the top-level declaration
403
+ scan for a module) and correlates each locally-identified entry to the
404
+ matching native entry by `` `${owner ?? ''}::${name}::${startLine}` `` (the
405
+ callable node's own start line — verified to match exactly between the two
406
+ walks for method/constructor/arrow-field/top-level-function/top-level-arrow
407
+ shapes). A correlation miss falls back to this plugin's ORIGINAL, fully local
408
+ computation for that one member — unchanged from before this pass — so a miss
409
+ degrades to old-but-correct, never to a dropped or wrong fact.
410
+
411
+ **Re-verified parity, same target (`rdc-harness`'s `Harness`,
412
+ `packages/core/src/index.mjs`), both flags, real CLI output:**
413
+
414
+ ```
415
+ === tree-sitter (post-native-consumption) ===
416
+ Harness (class) total=66.9
417
+ SRP: 40 [high] 3 connected component(s) across 24 member(s)
418
+ OCP: 83 [low] 16 branch/type-check hit(s) across 24 member(s), density 0.67
419
+ LSP: 100 [low-medium] no base class
420
+ ISP: 73 [medium-high] 18 public member(s), avg 0.8 param(s)
421
+ DIP: 56 [high] 15 concrete instantiation(s) of 34 total dependenc(y/ies)
422
+
423
+ === ts-morph (--parser ts-morph, unchanged) ===
424
+ Harness (class) total=68.5
425
+ OCP: 100 [low] 0 branch/type-check hit(s)
426
+ DIP: 53 [high] 15 concrete instantiation(s) of 32 total dependenc(y/ies)
427
+ (SRP/LSP/ISP unchanged, byte-identical to tree-sitter)
428
+ ```
429
+
430
+ **66.9, not 68.5 — a real, explained difference, not a regression:**
431
+ `concreteInstantiations: 15` matches exactly (both backends, unchanged —
432
+ confirms the underlying `new PhaseModel(...)`-class detection is stable). The
433
+ two criteria that moved are exactly the two fields the mapping table above
434
+ flags as using DIFFERENT NATIVE FORMULAS, not local re-derivations:
435
+ - **OCP (83 vs 100, ts-morph's own 0):** `branchHits` rose from ts-morph's 0
436
+ to tree-sitter's 16 because `memberFacts.ts` counts a `switch`'s `default`
437
+ arm as a branch and counts a full if/else-if chain by arm count — both
438
+ MORE COMPLETE than either prior implementation. This is the "real, positive
439
+ finding" case the task called out: the native parser counts branches the
440
+ duplicate logic undercounted (ts-morph's own branchHits equivalent reports
441
+ 0 for this same class — a pre-existing gap in the untouched ts-morph path,
442
+ not introduced here).
443
+ - **DIP (56 vs 53):** `totalDependencies` is 34 (native) vs 32 (ts-morph) —
444
+ a 2-dependency gap from two genuinely different counting methodologies
445
+ (native: distinct call-receivers + `new`-targets; ts-morph/prior-local:
446
+ import-specifier count + constructor-injected-typed-param count), not a
447
+ bug in either.
448
+ `RefusedError` (the file's other class) still scores 100/100 identically.
449
+
450
+ **Determinism** — ran `solid-score.mjs --parser tree-sitter` twice against
451
+ the same target, `--format json`, diffed the output: **byte-identical.**
452
+
453
+ **Regression suite** — `node --test tests/lib/*.test.mjs`: **243/243**, no
454
+ change from before this pass (none of the 243 tests exercise the Harness
455
+ fixture's exact score, so the OCP/DIP formula changes above did not trip any
456
+ existing assertion; they are captured here as a disclosed finding instead).
457
+
458
+ **How much duplicate CST-walking code was actually removable — the honest
459
+ number is small, not large:** `treesitter.mjs` grew from 964 to 1,182 lines
460
+ (+218), not shrank. Nothing was DELETED from the local extractor block —
461
+ every local function (`fieldsOf`, `callsOf`, `branchHitsOf`,
462
+ `declaredNamesOf`, `magicNumbersOf`, `statementCountOf`,
463
+ `constructorNewCallTargetsOf`, `deepChainCallCountOf`, `calleeNamesOf`,
464
+ `concreteDependencyCounts`, plus all eleven residual-fact extractors) is
465
+ still present, because it is still needed as the correlation-miss fallback
466
+ path for the nine member fields and three unit fields now primarily sourced
467
+ from native output, in addition to being unconditionally needed for the
468
+ eleven fields that never had a native equivalent to begin with (`calls`,
469
+ `isPublic`, `override`, and the eight clean-code/refactoring/pattern-advisor
470
+ facts requiring per-item line/regex detail the native surface doesn't carry).
471
+ What changed is which VALUES are used at runtime, not which code exists: in
472
+ the common case (correlation succeeds — the normal case for real class/module
473
+ members), 9 of 19 `NormalizedMember` fields and 5 of 9 `NormalizedUnit`
474
+ fields now come from the vendored extractor instead of this plugin's own
475
+ walk, with the local computation demoted to a safety-net fallback rather
476
+ than deleted. The remaining ~10 member facts genuinely cannot be sourced
477
+ from `memberFacts.ts`'s current output shape (missing per-item line numbers,
478
+ different regex/keyword semantics, or a batch-scoping mismatch with this
479
+ plugin's incremental per-file API) and stay local, unconditionally, by
480
+ design — not by oversight.
481
+
482
+ **Scoped explicitly out of this pass:**
483
+ - **Only `solid-score.mjs` was swapped.** `clean-code-score.mjs`,
484
+ `pattern-score.mjs`, and `refactoring-score.mjs` still default to
485
+ `typescript.mjs`/ts-morph. Swapping SOLID first, with the new plugin
486
+ built, dogfooded, and proven, de-risks the other three — a larger
487
+ regression-proof job the operator can direct next.
488
+ - **Python is NOT implemented in `treesitter.mjs`.** `nativeParser.ts` has
489
+ real, working Python extraction logic (`extractPythonSymbols`) that could
490
+ be ported in a follow-up pass, but no scoring CLI in this repo has ever
491
+ targeted Python — shipping an unproven, un-dogfooded third language in
492
+ the same pass as the TS/JS swap is scope creep, not scope discipline.
493
+ C/C++/C# are out of scope entirely for the same reason.
494
+ - **A minor, disclosed `.d.ts`-only gap:** two ambient-declaration-only
495
+ files (`scripts/lib/vendor/codeflow-parser/{grammars,nativeParser}.d.ts`)
496
+ score 1 unit under ts-morph (which recognizes `declare function ...;` as a
497
+ `FunctionDeclaration` via its higher-level API) and 0 under tree-sitter
498
+ (whose grammar wraps an ambient signature as `ambient_declaration`, a
499
+ different node type than plain `function_declaration`, which this
500
+ plugin's top-level walk does not yet match). Both sides of this gap are
501
+ bodyless type declarations with zero SOLID-relevant content either way
502
+ (no statements, no branches, no fields) — disclosed, not chased further
503
+ this pass.
504
+
505
+ **Real dependency versions installed and verified this pass** (added to
506
+ `rdc-skills`' own `package.json` `dependencies` — not the codeflow-parser
507
+ package in the separate `regen-root` monorepo, which this plugin does not
508
+ import from at runtime):
509
+
510
+ | Package | Version installed | License (verified) |
511
+ |---|---|---|
512
+ | [web-tree-sitter](https://www.npmjs.com/package/web-tree-sitter) | `0.24.7` (matches the version confirmed live in `regen-root`'s own `codeflow-parser`; npm `latest` is `0.26.12` — pinned to the version already proven working with this ported logic rather than chasing latest) | MIT (verified: `node_modules/web-tree-sitter/package.json`) |
513
+ | [tree-sitter-wasms](https://www.npmjs.com/package/tree-sitter-wasms) | `0.1.13` (matches npm `latest`) | **Unlicense** (verified: `node_modules/tree-sitter-wasms/package.json` — public domain, NOT MIT) |
514
+
515
+ ## Research bibliography
516
+
517
+ Real URLs fetched/searched this session — for the next person picking this
518
+ up, not "I researched online":
519
+
520
+ **Source ported from:**
521
+ - [github.com/OnSightTeam/architecture-toolkit](https://github.com/OnSightTeam/architecture-toolkit) — root repo, `.claude/skills/`, `src/agents/` tree
522
+ - `src/agents/clean-code-analyzer/tools/{naming,function,code-smell}-validator.ts`
523
+ - `src/agents/package-design/tools/{stability-metrics-calculator,package-coupling-analyzer}.ts`
524
+ - `src/agents/testing-strategy/tools/test-quality-validator.ts`
525
+ - `src/agents/architecture-reviewer/tools/{dependency-rule-validator,boundary-analysis-validator,layer-separation-validator}.ts`
526
+ - `src/agents/pattern-advisor/tools/{creational,structural,behavioral}-pattern-analyzer.ts`
527
+ - `src/agents/pattern-refactoring-guide/tools/{refactoring-analyzer,code-smell-refactoring-guide,pattern-transformation-guide}.ts`
528
+ (all fetched via `raw.githubusercontent.com/OnSightTeam/architecture-toolkit/main/<path>`)
529
+
530
+ **Web searches run (query → what it surfaced):**
531
+ - "static analysis tools SOLID principles violation detection without LLM AST-based" → [cycode.com](https://cycode.com/blog/static-code-analysis/), [Sorald (arXiv 2103.12033)](https://arxiv.org/pdf/2103.12033), [AVATAR (arXiv 1812.07270)](https://arxiv.org/pdf/1812.07270), [Mining Fix Patterns for FindBugs (arXiv 1712.03201)](https://arxiv.org/pdf/1712.03201), [datadoghq.com](https://www.datadoghq.com/knowledge-center/static-analysis/), [blog.codacy.com](https://blog.codacy.com/static-code-analysis), [oligo.security](https://www.oligo.security/academy/static-code-analysis)
532
+ - "ArchUnit dependency-cruiser layered architecture boundary enforcement rules" → [archunit.org/userguide](https://www.archunit.org/userguide/html/000_Index.html), [ArchUnitTS](https://github.com/LukasNiessen/ArchUnitTS), [ArchUnitPython](https://github.com/LukasNiessen/ArchUnitPython), [Loiane Groner: Architecture Testing for Java with ArchUnit](https://loiane.com/2026/07/architecture-testing-java-archunit/), [thearchitectsnotebook.substack.com](https://thearchitectsnotebook.substack.com/p/ep-122-the-modular-monolith-part)
533
+ - "design pattern detection static analysis algorithm academic Factory Strategy Observer AST" → [Design pattern detection approaches: a systematic review (Springer, 10.1007/s10462-020-09834-5)](https://link.springer.com/article/10.1007/s10462-020-09834-5), [MARPLE (ScienceDirect S0020025510005955)](https://www.sciencedirect.com/science/article/abs/pii/S0020025510005955), [Identification and Assessment of Software Design Pattern Violations (arXiv 1906.01419)](https://arxiv.org/pdf/1906.01419), [Automatic Design Pattern Detection (Brown CS)](https://vis.cs.brown.edu/docs/pdf/Heuzeroth-2003-ADP.pdf)
534
+ - "jscpd PMD CPD code duplication detection algorithm Rabin-Karp token-based" → [jscpd](https://github.com/kucherenko/jscpd), [PMD CPD](https://pmd.github.io/pmd/pmd_userdocs_cpd.html), [aarongoldenthal.com: GitLab Code Quality with PMD CPD](https://aarongoldenthal.com/posts/gitlab-code-quality-duplication-analysis-with-pmd-cpd/), [dev.to duplicate-checker roundup](https://dev.to/rahulxsingh/13-best-duplicate-code-checker-tools-in-2026-1cnk)
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: analyze-tests
3
+ description: >-
4
+ Usage `rdc:analyze-tests <path>` — recommends test level (unit/
5
+ integration/live) and shape (assertion/golden-capture) for a surface
6
+ (FUNCTION corner). See skills/testing-strategy.
7
+ ---
8
+
9
+ # analyze-tests
10
+
11
+ Use Skill tool with skill: "testing-strategy", passing the path as args.
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: check-clean-code
3
+ description: >-
4
+ Usage `rdc:check-clean-code <path> [--diff <ref>]` — naming, dead code,
5
+ complexity smells. See skills/clean-code-analyzer.
6
+ ---
7
+
8
+ # check-clean-code
9
+
10
+ Use Skill tool with skill: "clean-code-analyzer", passing the path and any
11
+ `--diff` flag as args.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: check-packages
3
+ description: >-
4
+ Usage `rdc:check-packages <path>` — module boundary and export-surface
5
+ review. See skills/package-design.
6
+ ---
7
+
8
+ # check-packages
9
+
10
+ Use Skill tool with skill: "package-design", passing the path as args.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: compare-compliance
3
+ description: >-
4
+ Usage `rdc:compare-compliance <path> --diff <ref>` — the SOLID/Clean
5
+ Architecture regression gate: did this change make compliance worse than
6
+ `<ref>`? Wraps solid-validator's git-diff mode.
7
+ ---
8
+
9
+ # compare-compliance
10
+
11
+ Use Skill tool with skill: "solid-validator", passing the path and a
12
+ required `--diff <ref>` as args. Read `regressions` and `boundaryViolations`
13
+ from the output — either non-empty means the change made compliance worse
14
+ than the base ref, not merely "still imperfect."