@lifeaitools/rdc-skills 0.34.1 → 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.
- package/.claude-plugin/plugin.json +284 -1
- package/VALIDATOR-ARCHITECTURE.md +534 -0
- package/commands/analyze-tests.md +11 -0
- package/commands/check-clean-code.md +11 -0
- package/commands/check-packages.md +10 -0
- package/commands/compare-compliance.md +14 -0
- package/commands/full-analysis.md +50 -0
- package/commands/get-refactoring-plan.md +13 -0
- package/commands/quick-check.md +13 -0
- package/commands/recover.md +149 -0
- package/commands/review-arch.md +12 -0
- package/commands/review.md +12 -113
- package/commands/suggest-patterns.md +11 -0
- package/commands/validate-solid.md +11 -0
- package/package.json +14 -2
- package/scripts/architecture-score.mjs +157 -0
- package/scripts/clean-code-score.mjs +177 -0
- package/scripts/duplication-score.mjs +66 -0
- package/scripts/lib/architecture-scoring.mjs +695 -0
- package/scripts/lib/clean-code-scoring.mjs +258 -0
- package/scripts/lib/duplication-scoring.mjs +238 -0
- package/scripts/lib/language-plugin.mjs +82 -0
- package/scripts/lib/package-metrics.mjs +439 -0
- package/scripts/lib/pattern-scoring.mjs +351 -0
- package/scripts/lib/plugins/treesitter.mjs +1182 -0
- package/scripts/lib/plugins/typescript.mjs +672 -0
- package/scripts/lib/refactoring-scoring.mjs +307 -0
- package/scripts/lib/solid-scoring.mjs +101 -0
- package/scripts/lib/test-smell-scoring.mjs +581 -0
- package/scripts/lib/vendor/codeflow-parser/.source-commit +1 -0
- package/scripts/lib/vendor/codeflow-parser/grammars.d.ts +23 -0
- package/scripts/lib/vendor/codeflow-parser/grammars.js +57 -0
- package/scripts/lib/vendor/codeflow-parser/memberFacts.d.ts +274 -0
- package/scripts/lib/vendor/codeflow-parser/memberFacts.js +1117 -0
- package/scripts/lib/vendor/codeflow-parser/nativeParser.d.ts +115 -0
- package/scripts/lib/vendor/codeflow-parser/nativeParser.js +759 -0
- package/scripts/lib/vendor/codeflow-parser/package.json +3 -0
- package/scripts/lib/vendor/codeflow-parser/xmlParser.d.ts +77 -0
- package/scripts/lib/vendor/codeflow-parser/xmlParser.js +400 -0
- package/scripts/package-metrics-cli.mjs +112 -0
- package/scripts/pattern-score.mjs +143 -0
- package/scripts/refactoring-score.mjs +253 -0
- package/scripts/solid-score.mjs +337 -0
- package/skills/architecture-reviewer/SKILL.md +287 -0
- package/skills/clean-code-analyzer/SKILL.md +147 -0
- package/skills/package-design/SKILL.md +118 -0
- package/skills/pattern-advisor/SKILL.md +237 -0
- package/skills/pattern-refactoring-guide/SKILL.md +262 -0
- package/skills/review/SKILL.md +29 -0
- package/skills/solid-validator/SKILL.md +92 -0
- package/skills/testing-strategy/SKILL.md +132 -0
- package/tests/lib/architecture-scoring.test.mjs +335 -0
- package/tests/lib/clean-code-scoring.test.mjs +241 -0
- package/tests/lib/duplication-scoring.test.mjs +144 -0
- package/tests/lib/fixtures.mjs +58 -0
- package/tests/lib/package-metrics.test.mjs +241 -0
- package/tests/lib/pattern-scoring.test.mjs +251 -0
- package/tests/lib/refactoring-scoring.test.mjs +264 -0
- package/tests/lib/solid-scoring.test.mjs +291 -0
- 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.
|