@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,147 @@
1
+ ---
2
+ name: clean-code-analyzer
3
+ description: >-
4
+ Usage `rdc:clean-code-analyzer <path> [--project-root <dir>]` — naming,
5
+ dead code, function-size, and error-handling smells outside SOLID's scope
6
+ (SOLID governs class/module shape; this governs whether the code inside is
7
+ readable and honest about what it does). N1/N2/N4/N7/F1/F2/E1/E2/G9 are
8
+ mechanical AST checks, not LLM judgment — see `scripts/clean-code-score.mjs`.
9
+ ---
10
+
11
+ > **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
12
+ > Checklist-only output. No tool-call narration. No raw MCP/JSON/log dumps.
13
+ > One checklist upfront, updated in place, shown again at end with a 1-line verdict.
14
+
15
+ # clean-code-analyzer — Naming, Dead Code, Function Size, Error Handling
16
+
17
+ ## Scope — what this covers that solid-validator doesn't
18
+
19
+ `solid-validator` scores the SHAPE of a class/module (cohesion, coupling,
20
+ inheritance contracts). This skill covers what's INSIDE that shape: does a
21
+ name lie about what it does, is there code nothing calls, is a function too
22
+ big to hold in one head, is an error silently swallowed. Two different
23
+ failure classes; a class can score perfectly on SOLID and still be unreadable.
24
+
25
+ ## Why 8 of these rules are mechanical, not a dispatched agent
26
+
27
+ Same reasoning as `solid-validator`: a rule that names a real AST fact
28
+ (a declared binding's name and length, a numeric literal's declaration
29
+ context, a statement count, a parameter count, an empty catch block, an
30
+ unreachable constant-conditional branch, a cross-file reference count) does
31
+ not need LLM judgment to detect — it needs the same NormalizedUnit contract
32
+ `lib/language-plugin.mjs` already defines, extended with the facts these
33
+ rules read (`statementCount`, `declaredNames`, `magicNumbers`, `emptyCatches`,
34
+ `deadConditionals` — see that file's JSDoc — plus the OPTIONAL
35
+ `deadExportsOf(filePath, projectFilePaths)` plugin method for G9's
36
+ cross-file half). All eight live in `scripts/lib/clean-code-scoring.mjs` as
37
+ pure functions over a `NormalizedUnit`, exactly like `solid-scoring.mjs`.
38
+
39
+ Detection logic (thresholds, patterns) is ported/adapted from
40
+ architecture-toolkit's real implementation —
41
+ github.com/OnSightTeam/architecture-toolkit (MIT),
42
+ `src/agents/clean-code-analyzer/tools/{naming,function,code-smell}-validator.ts`
43
+ — with per-rule citations in `clean-code-scoring.mjs`'s own comments. Their
44
+ checks are whole-file text regexes with an occurrence-count threshold (e.g.
45
+ "flag single-letter names only if more than 3 appear in the file", to
46
+ suppress the regex's own false-positive rate); ours reads the real AST per
47
+ declared binding, so context is known directly and every genuine occurrence
48
+ is its own finding — no threshold needed. F1 (>20 statements) and F2 (>3
49
+ params) independently corroborate architecture-toolkit's own real thresholds
50
+ at `function-validator.ts:52` and `:82`.
51
+
52
+ Dogfooded live against this repo's own `scripts/` tree (75 files): N1 fired
53
+ 271 times, N4 223 times, F1 53 times, E1 31 times, F2 10 times, G9 5 times
54
+ (3 confirmed by independent repo-wide grep: `registeredPlugins`,
55
+ `packageMetrics`, `cleanupStaleWorktrees` — genuinely zero callers). N7 fired
56
+ 0 times on real code (no `*Manager`/`*Handler`/`*Util` names in this repo) —
57
+ confirmed against a constructed fixture instead. N2's low-confidence label is
58
+ earned: it also produced one real false positive (`pm2`, a product name,
59
+ matches the numeric-suffix heuristic) alongside real hits (`data`, `temp`,
60
+ `val`) — report it as a heuristic finding, never a certainty.
61
+
62
+ ## Procedure
63
+
64
+ 1. **Run the mechanical scorer:**
65
+ ```bash
66
+ rdc-clean-code-score <path> [--project-root <dir>] [--no-dead-exports] --format json
67
+ ```
68
+ (installed globally via `npm link`/publish — see `package.json`'s
69
+ `bin.rdc-clean-code-score`; falls back to
70
+ `node <rdc-skills-install-path>/scripts/clean-code-score.mjs`.)
71
+
72
+ 2. **Read `results`** — per-unit findings for N1 (cryptic names), N2 (heuristic
73
+ meaningless-distinction names, low confidence), N4 (magic numbers), N7
74
+ (generic class/function names), F1 (>20 statements), F2 (>3 params), E1
75
+ (empty catch blocks), and G9 (dead code — BOTH halves: unreachable
76
+ constant-conditional branches, always measured, AND unused exports via a
77
+ real cross-file `findReferencesAsNodes()` reference-graph walk, measured
78
+ only when `deadExportsScope.positiveControlOk` is true).
79
+
80
+ 3. **Read `deadExportsScope`** before trusting ANY G9 unused-export finding.
81
+ `positiveControlOk: false` means the cross-file reference scan itself
82
+ failed a known-used-symbol control — G9's export-usage findings are
83
+ withheld entirely in that case (the unreachable-conditional half still
84
+ ran). An unverified "zero callers" is a guess, not a finding — see
85
+ `.claude/rules/prove-absence-positive-control.md`.
86
+
87
+ 4. **Naming-honesty judgment pass — this stays a dispatched agent, on
88
+ purpose.** N1/N2/N7 catch SHAPE (a name too short, too generic, or
89
+ suspiciously paired) — none of them can tell whether a name LIES about
90
+ behavior (a function named for its happy path that also has a side
91
+ effect, a boolean named affirmatively that is usually false, a variable
92
+ whose name predates a refactor and no longer matches its contents). That
93
+ needs reading intent against implementation, which is judgment:
94
+ ```
95
+ Agent({
96
+ subagent_type: "pr-review-toolkit:code-reviewer",
97
+ description: "clean-code naming-honesty pass",
98
+ prompt: "Review `git diff <ref>...HEAD` for names that misdescribe what
99
+ the code does — not too short or too generic (a mechanical
100
+ scorer already caught that), but SEMANTICALLY WRONG: a
101
+ happy-path name hiding a side effect, an affirmative boolean
102
+ that's usually false, a name that predates a refactor. High-
103
+ confidence findings only. Return CLEAN_CODE_NAMING_COMPLETE
104
+ with { findings: [{file:line, name, issue, suggested_name}] }."
105
+ })
106
+ ```
107
+
108
+ 5. **Report:**
109
+ ```
110
+ ## Clean Code Analysis
111
+ ### Mechanical findings (N1/N2/N4/N7/F1/F2/E1/G9) — rdc-clean-code-score
112
+ ### Dead-export scan status (positive control OK / withheld — reason)
113
+ ### Naming-honesty findings (dispatched judgment)
114
+ ### Not implemented (see below)
115
+ ## Verdict: CLEAN / HAS ISSUES
116
+ ```
117
+
118
+ ## Not implemented — named, not faked
119
+
120
+ These stayed OUT of the mechanical catalog because a cheap regex/AST version
121
+ would report a confident number for evidence that's actually a judgment call,
122
+ which is worse than not measuring it. Route these through a dispatched
123
+ `pr-review-toolkit:code-reviewer` pass reading the real diff, same shape as
124
+ step 4, or accept they are genuinely unmeasured this round:
125
+
126
+ | ID | Why it stays judgment |
127
+ |----|------------------------|
128
+ | N3 | Unpronounceability is a phonetic/readability judgment, not an AST fact — a consonant-run regex flags real acronyms and abbreviations as often as bad names. |
129
+ | N5, N6 | Member-prefix (`m_`/`_`) and interface-`I`-prefix conventions are STYLE-GUIDE-dependent, not universal Clean Code violations — some house styles mandate them. Flagging them needs the repo's own convention as ground truth, which isn't in `NormalizedUnit`. |
130
+ | C1–C5 | Comment quality/staleness needs comparing prose against code behavior over time — semantic, not structural. |
131
+ | G5 | Duplication detection worth trusting needs real similarity (token/AST-diff) across the whole codebase, not a per-file line-repeat count — a per-unit, per-file scorer is the wrong shape for a cross-file structural-clone problem. |
132
+ | G14 | "Feature Envy" (a member using another object's data more than its own) needs cross-class field-access comparison this scorer doesn't do — SRP's connected-component analysis in `solid-scoring.mjs` is the adjacent real check, not a substitute. |
133
+ | G16, G28 | Nested-ternary / complex-boolean "obscures intent" is a readability judgment about a specific reader's tolerance, not a fixed threshold — a mechanical AST-depth count would either flag idiomatic short expressions or miss genuinely tangled ones depending on where the line is drawn. |
134
+
135
+ ## Rules
136
+
137
+ - Dead-export (G9) claims MUST cite the positive control that proved the scan
138
+ itself works — `deadExportsScope.positiveControlOk` — an unverified "zero
139
+ callers" claim is not a finding, it's a guess with a command attached.
140
+ - N2 findings are heuristic and low-confidence BY DESIGN — report them as
141
+ "worth a look," never as certain violations. A real false positive
142
+ (`pm2` flagged as a "numeric-suffix" name) was found during dogfooding and
143
+ is why this label is load-bearing, not decorative.
144
+ - Do not flag a name as wrong without a suggested replacement — "confusing"
145
+ alone is not actionable.
146
+ - Never claim a Not-Implemented rule (N3/N5/N6/C1-C5/G5/G14/G16/G28) was
147
+ checked — it wasn't, on purpose.
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: package-design
3
+ description: >-
4
+ Usage `rdc:package-design <path>` — module boundary and export-surface
5
+ review: does this package expose the right things, hide the right things,
6
+ and sit at the right size.
7
+ ---
8
+
9
+ > **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
10
+ > Checklist-only output. No tool-call narration. No raw MCP/JSON/log dumps.
11
+ > One checklist upfront, updated in place, shown again at end with a 1-line verdict.
12
+
13
+ # package-design — Module Boundary & Export-Surface Review
14
+
15
+ ## Procedure
16
+
17
+ 1. **Read the package's barrel** (`src/index.ts`/`.mjs`) and list every
18
+ export. For each: is it used by anything OUTSIDE the package? An export
19
+ used only internally is an encapsulation leak — it should not be public.
20
+ Use the same call-graph approach as `clean-code-analyzer`'s dead-code
21
+ check, with the same positive-control requirement.
22
+
23
+ 2. **Check for a missing barrel** — internal files imported directly by
24
+ other packages (`import { x } from '../../foo/src/internal/thing.mjs'`
25
+ instead of `from '../../foo/src'`) is a boundary violation even when
26
+ nothing is technically broken; it means the package has no real public
27
+ contract, just whatever consumers happened to reach into.
28
+
29
+ 3. **Size check** — a package with one export and a package with sixty are
30
+ both worth asking about. Too small: does this need to be its own package,
31
+ or is it one file that belongs inside a consumer? Too large: does it
32
+ actually have one responsibility, or has "utils"/"shared"/"core" become
33
+ several unrelated things sharing a directory (the same LCOM-style
34
+ cohesion question `solid-validator`'s SRP score asks, applied at the
35
+ package level instead of the class level).
36
+
37
+ 4. **Dependency direction — mechanical, real numbers.** Run
38
+ `node scripts/package-metrics-cli.mjs <packagesRoot>` (root containing
39
+ `packages/*`, or `--dirs <d1,d2,...>` for an explicit set). This is a
40
+ REAL package-dependency graph, not a `package.json` `dependencies` read —
41
+ many real monorepos (rdc-harness among them) declare zero
42
+ `dependencies` and wire packages together entirely through relative
43
+ `../../pkg/src/...` imports, which `package.json` alone can't see. It
44
+ resolves every `import`/`export ... from`/`require()`/dynamic `import()`
45
+ across every sibling package (implementation: `scripts/lib/package-metrics.mjs`,
46
+ independent of the ts-morph `language-plugin.mjs` used by SRP/OCP/etc —
47
+ plain-text/regex parsing, so it works on any language whose imports look
48
+ like ES/CJS syntax) and reports, per package:
49
+
50
+ | Metric | Meaning | Formula |
51
+ |---|---|---|
52
+ | `ca` | how many OTHER packages import from this one | count of distinct importing packages |
53
+ | `ce` | how many OTHER packages this one imports from | count of distinct imported packages |
54
+ | `instability` (I) | Ce/(Ca+Ce); `null` if Ca+Ce=0 (isolated, no coupling data — not 0) | Martin's I |
55
+ | `abstractness` (A) | exported `interface`/`type` ÷ exported total (`class`/`function`/`const`/…); `null` unless the package contains at least one real `.ts`/`.tsx` file | Martin's A |
56
+ | `distanceFromMainSequence` (D) | how far off Martin's main sequence; `null` if either I or A is null | `\|A + I − 1\|` |
57
+ | `cycles` | REAL cycle paths through this package (e.g. `a -> b -> c -> a`), not just "a cycle exists" | ADP — Acyclic Dependencies Principle |
58
+ | `zone` | `main-sequence` (D≤0.5) / `zone-of-pain` (I<0.5, A<0.5) / `zone-of-uselessness` (I>0.5, A>0.5) / `off-main-sequence` / `unmeasurable` | |
59
+
60
+ **Honest limits, not fabricated numbers:** `abstractness` is `null` for a
61
+ plain `.mjs`/`.js` package — there is no type system to measure, and
62
+ reporting a fake 0 would silently claim "fully concrete" for a package
63
+ this tool has no basis to judge. Measurability is decided by file
64
+ EXTENSION (does the package contain a real `.ts`/`.tsx` file), never by
65
+ whether `interface`/`type` keywords happen to appear — a `.ts` package
66
+ with zero interfaces is a real, legitimate A=0, not "unmeasurable" (this
67
+ was a real bug, caught by a synthetic fixture during dogfooding, fixed
68
+ before ship). Declaration counting excludes test files — a test fixture
69
+ that embeds source-as-a-STRING (e.g. a template literal holding
70
+ `` `export interface Page {...}` `` as test input) is indistinguishable
71
+ from a real declaration to a regex scanner; `ca`/`ce` still count
72
+ test-file imports, since those are real coupling regardless.
73
+
74
+ Dogfooded against `rdc-harness/packages/*` (21 packages, zero `.ts`
75
+ files, zero `package.json` `dependencies` entries — everything wired via
76
+ relative imports): every `ca`/`ce` number was hand-verified against a
77
+ manual `grep -rn "from '\.\./\.\./"` cross-check across the whole tree,
78
+ with one instructive miss — a dynamic `await import('../../delivery/src/...')`
79
+ inside `adoption/test/isolation-and-adoption.test.mjs` that the naive
80
+ `grep ... from` positive control doesn't catch (no `from` keyword on a
81
+ dynamic import) but this tool correctly does. Zero ADP cycles found,
82
+ confirmed by hand against the full 22-package edge list. `abstractness`
83
+ was `null` for all 22 — correct, since the fleet has no TypeScript.
84
+
85
+ 5. **Dispatch judgment for what's not mechanical:**
86
+ ```
87
+ Agent({
88
+ subagent_type: "pr-review-toolkit:code-reviewer",
89
+ description: "package-design judgment pass",
90
+ prompt: "Review the package at <path>: does its actual responsibility
91
+ match its name and its README/CLAUDE.md description? Would a
92
+ new contributor guess what belongs here correctly? Return
93
+ PACKAGE_DESIGN_COMPLETE with
94
+ { findings: [{severity, issue, suggestion}] }."
95
+ })
96
+ ```
97
+
98
+ 6. **Report:**
99
+ ```
100
+ ## Package Design Review
101
+ | Export | Used externally? | Should be public? |
102
+ ### Boundary leaks (direct internal imports from outside)
103
+ ### Size/cohesion note
104
+ ### Dependency-direction — Ca/Ce/I/A/D/zone table (from package-metrics-cli.mjs), cycles called out by name
105
+ ## Verdict: CLEAN / HAS ISSUES
106
+ ```
107
+
108
+ ## Rules
109
+
110
+ - An export-usage claim needs the same positive control as dead-code
111
+ detection — prove the scan works before trusting a zero result.
112
+ - Do not recommend splitting or merging a package without naming the exact
113
+ target shape — "this is too big" alone is not actionable.
114
+ - Ca/Ce/I/A/D/zone/cycles are MECHANICAL (step 4) — never eyeball or
115
+ estimate these from reading `package.json`/imports; run
116
+ `package-metrics-cli.mjs` and quote its numbers. A `zone-of-pain` or
117
+ `zone-of-uselessness` verdict, or any reported `cycles` entry, needs the
118
+ actual tool output in the report, not a paraphrase.
@@ -0,0 +1,237 @@
1
+ ---
2
+ name: pattern-advisor
3
+ description: >-
4
+ Usage `rdc:pattern-advisor <path>` — suggests an applicable design pattern
5
+ for a given code shape. Factory Method/Builder/Singleton/Decorator/
6
+ Adapter/Facade/Strategy/Observer/Command/Template Method are mechanical
7
+ AST checks, not LLM judgment — see `scripts/pattern-score.mjs`. Advisory
8
+ only; never rewrites code itself.
9
+ ---
10
+
11
+ > **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
12
+ > Checklist-only output. No tool-call narration. No raw MCP/JSON/log dumps.
13
+ > One checklist upfront, updated in place, shown again at end with a 1-line verdict.
14
+
15
+ # pattern-advisor — Design Pattern Suggestions
16
+
17
+ ## The failure this guards against
18
+
19
+ A pattern applied because it's a pattern, not because the code needs it, is
20
+ the same kind of theater as a test written to make a claim go green. This
21
+ skill's first output for most inputs should be "no pattern needed" — that is
22
+ a valid, common, correct answer, not a non-answer.
23
+
24
+ ## Why 9 of these are mechanical, not a dispatched agent
25
+
26
+ Same reasoning as `solid-validator` and `clean-code-analyzer`: a rule that
27
+ names a real, AST-visible structural shape (a switch statement's
28
+ discriminant and case bodies, a constructor's parameter count, an
29
+ if-block's calls, a call chain's depth, a static property's name) does not
30
+ need LLM judgment to detect. All 9 live in `scripts/lib/pattern-scoring.mjs`
31
+ as pure functions over a `NormalizedUnit` (same `lib/language-plugin.mjs`
32
+ contract every other mechanical scorer in this repo shares), fed by facts
33
+ computed once in `lib/plugins/typescript.mjs`.
34
+
35
+ Detection heuristics (thresholds, word lists, structural shapes) are ported
36
+ from architecture-toolkit's REAL implementation —
37
+ [OnSightTeam/architecture-toolkit](https://github.com/OnSightTeam/architecture-toolkit)
38
+ (MIT), `src/agents/pattern-advisor/tools/{creational,structural,behavioral}-
39
+ pattern-analyzer.ts` — fetched from raw.githubusercontent.com and read in
40
+ full, per-detector citations in `pattern-scoring.mjs`'s own comments. Their
41
+ own checks are whole-file text regexes with zero scoping to which
42
+ switch/if/call the signal actually came from (e.g. a type-switch's `new`
43
+ check matches if "new" appears anywhere before the switch's closing brace,
44
+ even in an unrelated statement three lines later). Ours walks the real AST
45
+ per switch-case / if-block / call-expression, so every finding is
46
+ attributable to the real member and — for the three patterns whose toolkit
47
+ signal is inherently node-scoped (Factory Method's switch+new, Strategy's
48
+ switch+behavior-call, Decorator's conditional-feature-call) — the real line
49
+ it was found on.
50
+
51
+ Confidence and priority numbers are the LITERAL values architecture-
52
+ toolkit's own analyzers return, hard-coded per detector (not computed). The
53
+ calibration table below (unchanged from before this scorer existed) was
54
+ checked against every one of them: **no discrepancy found.**
55
+
56
+ Dogfooded live (2026-08-20):
57
+ - **This repo's own `scripts/` tree** (36 JS/TS files scanned, 8 non-JS
58
+ skipped and reported, not silently passed): Decorator fired 20 times,
59
+ Adapter 4, Facade 1, Observer 1, Template Method 1 — 12 files carried a
60
+ finding. **Factory Method, Builder, Singleton, Strategy, and Command all
61
+ scored ZERO on real code.** Per
62
+ `.claude/rules/prove-absence-positive-control.md`, a zero is not reported
63
+ as a finding until the scanner is proven to work: a constructed positive-
64
+ control fixture (a type-switch factory, a 6-param constructor, a
65
+ `getInstance()` singleton, a calculate/tax switch, and an undo/redo/
66
+ queue/execute command-history class) scored 2/1/1/1/1 findings
67
+ respectively across those same 5 detectors — the zero on real code is a
68
+ genuine absence, not a broken scanner.
69
+ - **`rdc-harness/packages`** (a different, larger real corpus, 87 JS/TS
70
+ files scanned, 17 carried a finding): Factory Method fired once — a real Redux-style event reducer
71
+ switching on `type` and constructing via `new` (`core/src/events.mjs`,
72
+ `reduce()`) — Decorator once, Adapter 13 times (this package genuinely has
73
+ an `adapters.mjs` module), Facade twice, including a 19-call chain in
74
+ `transaction/src/index.mjs`'s `SaveTransaction##drive`.
75
+ - **ATF-compatibility**: `--format json` run twice on each of the two
76
+ corpora above produced byte-identical output both times (`diff` empty) —
77
+ no timestamps, file paths relative to the scanned root, and every
78
+ results/finding array explicitly sorted.
79
+
80
+ ## Confidence calibration
81
+
82
+ Adapted from the confidence values actually shipped in architecture-
83
+ toolkit's `src/agents/pattern-advisor/tools/*.ts` — every detector there
84
+ returns a confidence in exactly the 70-90 band, never higher, never lower —
85
+ and now hard-coded verbatim in `pattern-scoring.mjs`:
86
+
87
+ | Pattern | Confidence | Priority | Toolkit citation |
88
+ |---|---|---|---|
89
+ | Factory Method (switch-on-type + `new`) | 90% | high | `creational-pattern-analyzer.ts:43-53` |
90
+ | Factory Method (scattered `new`, >5 total >3 unique) | 75% | medium | `creational-pattern-analyzer.ts:68-83` |
91
+ | Builder (constructor >4 params) | 85% | high | `creational-pattern-analyzer.ts:98-108` |
92
+ | Singleton (`private static instance` \| `getInstance()`) | 70% | medium | `creational-pattern-analyzer.ts:128-138` |
93
+ | Strategy (switch + calculate/process/execute/validate/format) | 90% | high | `behavioral-pattern-analyzer.ts:44-54` |
94
+ | Command (undo/redo/history/queue/execute, >4) | 80% | high | `behavioral-pattern-analyzer.ts:107-119` |
95
+ | Observer (notify/update/inform/broadcast, >3) | 75% | medium | `behavioral-pattern-analyzer.ts:74-86` |
96
+ | Adapter (convert/transform/adapt) | 80% | medium | `structural-pattern-analyzer.ts:74-84` |
97
+ | Decorator (conditional wrap/add/extend/enhance) | 75% | medium | `structural-pattern-analyzer.ts:43-53` |
98
+ | Facade (>5 `a.b.c(...)` calls) | 70% | medium | `structural-pattern-analyzer.ts:104-114` |
99
+ | Template Method (>2 members call initialize/process/cleanup) | 70% | medium | `behavioral-pattern-analyzer.ts:139-150` |
100
+
101
+ Never report a confidence outside 70-90% for a *heuristic* pattern match —
102
+ below 70 the finding isn't worth surfacing; above 90 claims a certainty
103
+ static analysis of a live codebase cannot honestly produce.
104
+
105
+ ## Procedure
106
+
107
+ 1. **Run the mechanical scorer:**
108
+ ```bash
109
+ rdc-pattern-score <path> --format json
110
+ ```
111
+ (installed globally via `npm link`/publish — see `package.json`'s
112
+ `bin.rdc-pattern-score`; falls back to
113
+ `node <rdc-skills-install-path>/scripts/pattern-score.mjs`.)
114
+
115
+ 2. **Read `results`** — per-file, per-unit, per-pattern findings. Each
116
+ finding carries `location` (unit#member[:line] where a line is known),
117
+ `problem` (the shape observed, named first — never lead with the
118
+ recommendation), `solution`, `reasoning`, `confidence`, `priority`,
119
+ `alternatives`, `tradeoffs: {pros, cons}`, and `source` (the exact
120
+ toolkit file:line it was ported from).
121
+
122
+ 3. **A file/unit with zero findings across all 9 patterns is "no pattern
123
+ needed" — report it as plainly as a positive recommendation.** It
124
+ carries no confidence/priority scoring (there is nothing being
125
+ recommended to score). This is the expected, common, correct answer for
126
+ most code — the dogfooding numbers above show 5 of 9 detectors scoring
127
+ zero on this repo's own real source.
128
+
129
+ 4. **Domain-fit judgment pass — this stays a dispatched agent, on
130
+ purpose.** The mechanical scorer can prove a shape exists (a type-switch
131
+ constructing via `new`, a 6-parameter constructor) but cannot judge
132
+ whether the recommended pattern is actually the RIGHT fit for THIS
133
+ domain, or merely structurally similar to one that would be — e.g. a
134
+ 6-param constructor on a one-off internal test fixture that's called
135
+ exactly once is not a real Builder candidate even though it trips the
136
+ mechanical threshold; a type-switch in a state-machine reducer may be
137
+ the CORRECT idiom for that domain (Redux-shaped code), not a Factory
138
+ Method smell. That needs reading intent and call-site context against
139
+ structure, which is judgment:
140
+ ```
141
+ Agent({
142
+ subagent_type: "pr-review-toolkit:code-reviewer",
143
+ description: "pattern-advisor domain-fit pass",
144
+ prompt: "Given these mechanical pattern-advisor findings (paste the
145
+ JSON), judge for EACH finding whether the recommended pattern
146
+ is actually the right fit for this code's domain and call-site
147
+ context, or whether the shape is structurally similar but the
148
+ recommendation doesn't actually help here (a one-off fixture,
149
+ a legitimate domain idiom like a reducer's type-switch, a
150
+ constructor called from exactly one call site). Return
151
+ PATTERN_ADVISOR_DOMAIN_FIT_COMPLETE with
152
+ { findings: [{location, pattern, verdict: apply|skip, reason}] }."
153
+ })
154
+ ```
155
+
156
+ 5. **Every recommendation carries trade-offs — pros AND cons, never pros
157
+ alone** — the scorer's own `tradeoffs` field already supplies both; do
158
+ not drop the cons when reporting. **At least one alternative pattern is
159
+ named where the toolkit source names one** (`alternatives` field) — do
160
+ not invent a generic "or don't" alternative where the source gives none.
161
+
162
+ 6. **Report:**
163
+ ```
164
+ ## Pattern Advice
165
+ | File:Line | Shape observed | Recommendation | Confidence | Priority | Why now |
166
+ ### Trade-offs & alternative (per recommendation)
167
+ ### Domain-fit verdicts (dispatched judgment, step 4)
168
+ ### No pattern needed (files/units with zero mechanical findings)
169
+ ```
170
+
171
+ ## Rules
172
+
173
+ - Never recommend a pattern without naming the shape it responds to
174
+ (`problem` field) — the mechanical scorer already does this; do not strip
175
+ it when reporting.
176
+ - Never recommend a pattern without its confidence score, priority, both
177
+ pros AND cons, and its named alternative(s) if the toolkit source has any.
178
+ - "No pattern needed" is a first-class, expected verdict — report it as
179
+ plainly as a positive recommendation.
180
+ - A mechanical finding is a structural fact, not a final verdict — run the
181
+ domain-fit judgment pass (step 4) before telling Dave to actually apply a
182
+ recommendation; do not present a raw mechanical hit as settled advice.
183
+ - Do not write the refactor here — hand off to
184
+ `pattern-refactoring-guide` for the concrete before/after.
185
+ - A zero-finding pattern on a real scan is not reported as "clean" without
186
+ having proven the detector fires on a positive-control fixture first (see
187
+ the dogfooding section above) — same discipline as
188
+ `.claude/rules/prove-absence-positive-control.md`.
189
+
190
+ ## Not implemented — named, not faked
191
+
192
+ Everything the toolkit's own 9 detectors check is now mechanical (see
193
+ above). What's left is genuinely NOT a shape a regex or an AST walk can
194
+ settle:
195
+
196
+ | Question | Why it stays judgment |
197
+ |----------|------------------------|
198
+ | Is the recommended pattern actually the right fit for this domain, or just structurally similar? | Needs reading intent against call-site context — a type-switch in a reducer may be the correct domain idiom, not a smell; a 6-param constructor called once is not a real Builder candidate even though it trips the mechanical threshold. Routed through the domain-fit judgment pass (step 4). |
199
+ | Would applying this pattern actually improve the code, given its growth trajectory? | "Small, stable branch count with no growth signal" is a trend judgment a single-snapshot AST scan cannot make — the mechanical scorer reports the shape at THIS commit; whether it is worth refactoring is a maintainer call. |
200
+
201
+ ## Worked Example — this repo's own dogfood run
202
+
203
+ Real target, scanned in full during this scorer's build:
204
+ [`C:/Dev/rdc-skills/scripts/lib/plugins/typescript.mjs`](file:///C:/Dev/rdc-skills/scripts/lib/plugins/typescript.mjs)
205
+ — 4 real mechanical findings, real `rdc-pattern-score` output:
206
+
207
+ ```
208
+ ## Pattern Advice — scripts/lib/plugins/typescript.mjs
209
+ | File:Line | Shape observed | Recommendation | Confidence | Priority | Why now |
210
+ | unitsFromSourceFile:435 | if-block calls a function whose name matches
211
+ wrap/add/extend/enhance | Decorator | 75% | medium | Single structural
212
+ signal, no growth trend evidence in a snapshot scan — flagged, not settled. |
213
+ | deadExportsOf:552 | same shape, different member | Decorator | 75% | medium | ditto |
214
+ | referenceSitesOf:590 | same shape, different member | Decorator | 75% | medium | ditto |
215
+ | (unit-wide) | 3 members each call an initialize/process/cleanup-named
216
+ function | Template Method | 70% | medium | Lowest band — pure keyword
217
+ frequency, no shared-"how" evidence beyond the name match. |
218
+
219
+ ### Domain-fit verdict (dispatched judgment)
220
+ All 3 Decorator findings: SKIP — each if-block is a real conditional
221
+ branch (class-vs-module dispatch, a missing-file guard, a found/not-found
222
+ check), not feature decoration; the wrap/add/extend/enhance substring match
223
+ is a false positive on ordinary control flow in all three cases, not a
224
+ Decorator candidate.
225
+ Template Method finding: SKIP — the three "process"-adjacent calls are
226
+ three distinct, unrelated cross-file reference-graph walks, not a shared
227
+ algorithm skeleton with varying steps; there is no "how" worth abstracting.
228
+
229
+ ## Verdict: 4 mechanical findings, all 4 correctly downgraded to SKIP by
230
+ the domain-fit pass — a worked example of why step 4 exists: the mechanical
231
+ scorer's job is to surface the shape, not to be the final word.
232
+ ```
233
+
234
+ This is the calibration this skill exists to enforce: a real structural hit
235
+ is not the same as a real recommendation. The mechanical scorer's honest job
236
+ is surfacing candidates at the stated confidence; the domain-fit pass is
237
+ what turns a candidate into advice.