@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,262 @@
1
+ ---
2
+ name: pattern-refactoring-guide
3
+ description: >-
4
+ Usage `rdc:pattern-refactoring-guide <path>` — turns a pattern-advisor
5
+ recommendation (or a solid-validator/architecture-reviewer finding) into a
6
+ concrete before/after refactor plan. Produces a plan, does not apply it.
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
+ # pattern-refactoring-guide — Concrete Refactor Plans
14
+
15
+ ## Input contract
16
+
17
+ Takes a finding from one of two sources — an upstream surface (a
18
+ `pattern-advisor` recommendation, a `solid-validator` low-score criterion, an
19
+ `architecture-reviewer` boundary violation), OR this skill's own mechanical
20
+ detection layer, `rdc-refactoring-score` (`scripts/refactoring-score.mjs`) —
21
+ and produces a step-ordered plan a build agent can execute. It does not
22
+ invent new findings beyond what a cited detector reported; it does not apply
23
+ the refactor itself.
24
+
25
+ ## Procedure
26
+
27
+ 1. **Find or receive the finding.** Two paths, either is a valid start:
28
+ - **Upstream finding already exists** — an emitted `pattern-advisor` /
29
+ `solid-validator` / `architecture-reviewer` result. Skip to step 2.
30
+ - **No finding yet — detect candidates directly.** Run
31
+ `node scripts/refactoring-score.mjs <path> --format json` (installed
32
+ bin: `rdc-refactoring-score`). It is a real, deterministic, AST-based
33
+ scanner — NOT this skill reasoning about the code — covering nine
34
+ refactoring types: `extract-method`, `extract-class`,
35
+ `introduce-parameter-object`, `replace-magic-number`,
36
+ `consolidate-duplicate-code`, `decompose-conditional`,
37
+ `strategy-transform`, `factory-transform`, `null-object-transform`.
38
+ Detection logic: `scripts/lib/refactoring-scoring.mjs`. Every finding
39
+ carries a real file:line and a mechanical effort signal (see step 5).
40
+ A "zero findings" result on a real target is only trustworthy once the
41
+ tool's own positive control passed (it runs one automatically and
42
+ reports `effortScope.positiveControlOk` in JSON output / the "Effort
43
+ scan" line in text output — per
44
+ `.claude/rules/prove-absence-positive-control.md`, treat a failed
45
+ control as "unmeasured", never as "clean").
46
+ Two thresholds here are DELIBERATELY DIFFERENT from
47
+ `clean-code-analyzer`'s mechanical rules even though they read the same
48
+ underlying fact: `extract-method` fires at >25 statements (this tool)
49
+ vs. clean-code's F1 at >20 statements; `introduce-parameter-object`
50
+ fires at >4 params (this tool) vs. clean-code's F2 at >3 params. Both
51
+ numbers are architecture-toolkit's own real thresholds for their
52
+ respective domains (`refactoring-analyzer.ts:49,171`) — cite the
53
+ difference, do not silently merge the two tools' output.
54
+
55
+ 2. **Restate the finding** exactly as reported by the upstream skill or by
56
+ `rdc-refactoring-score` — file:line, the score/violation, the evidence.
57
+
58
+ 3. **Write the BEFORE** — the actual current code (a real excerpt, not a
59
+ paraphrase).
60
+
61
+ 4. **Write the AFTER** — the concrete target shape, real code, not a
62
+ description of code.
63
+
64
+ 5. **Order the steps** so each one leaves the codebase in a working state:
65
+ - Extract the new shape ALONGSIDE the old one first (new class/module,
66
+ unused by anything yet).
67
+ - Migrate ONE call site, verify.
68
+ - Migrate remaining call sites incrementally, verifying after each.
69
+ - Delete the old shape only after zero call sites remain — verify with
70
+ the same call-graph-with-positive-control method `clean-code-analyzer`
71
+ uses for dead code, don't assume.
72
+
73
+ 6. **Estimate effort — low/medium/high — using named criteria, not a gut
74
+ call.** Adapted from the `estimatedEffort` field actually shipped in
75
+ [OnSightTeam/architecture-toolkit](https://github.com/OnSightTeam/architecture-toolkit)'s
76
+ `src/agents/pattern-refactoring-guide/tools/*.ts`, extended with the
77
+ criterion its own examples expose a gap in (see below). When the finding
78
+ came from `rdc-refactoring-score` (step 1), don't re-derive this table by
79
+ hand — the tool already computed the mechanical half of it: it re-walks
80
+ the SAME cross-file reference-graph mechanism `clean-code-scoring.mjs`'s
81
+ G9 dead-export check uses (`plugin.referenceSitesOf`, built on
82
+ `findReferencesAsNodes`, gated behind its own positive control) and
83
+ reports each unit's real call-site count and whether any reference
84
+ crosses a `packages/*`/`apps/*` boundary. The one criterion below it
85
+ CANNOT compute — a cross-cutting invariant that isn't locally checkable —
86
+ is always a human judgment call; the tool flags `invariantCheckRequired:
87
+ true` as a reminder wherever boundary-crossing or >15 call sites already
88
+ pushed it to High, but never claims to have evaluated the invariant
89
+ itself.
90
+
91
+ | Effort | Criteria (any ONE qualifies) | toolkit precedent |
92
+ |---|---|---|
93
+ | **Low** | Single file, ≤3 call sites, no package-boundary crossing, mechanical (rename, extract-constant, extract-condition-to-named-method). | `introduce_parameter_object` (`refactoring-analyzer.ts:179`), `replace_magic_number` (`:234`), `decompose_conditional` (`code-smell-refactoring-guide.ts:121`) — all single-function, in-place edits. |
94
+ | **Medium** | 4-15 call sites within the SAME package, OR a new abstraction introduced but consumed only inside the current package/module. | `extract_method` (`refactoring-analyzer.ts:57`) — "may need to pass many parameters" but stays in one file. `consolidate_duplicate_code` (`code-smell-refactoring-guide.ts:59`), Strategy/Factory/Null-Object pattern transforms (`pattern-transformation-guide.ts:52,110,168`) — new types, but all call sites are local. |
95
+ | **High** | **Crosses a package boundary** (the extraction moves logic into or out of a different `packages/*`/`@regen/*` workspace), OR >15 call sites, OR — the criterion the toolkit's own field values don't cover — **the target carries a cross-cutting invariant that isn't locally checkable** (event ordering, transactional/append-only integrity, freeze-after-mutation semantics): call-site count alone UNDER-counts effort here, because migrating the invariant correctly matters more than how many call sites exist. | `extract_class` (`refactoring-analyzer.ts:116`) is the toolkit's own high-effort case — ">15 methods... requires careful dependency management" is the package-boundary risk stated in words even though the field only tracks method count. |
96
+
97
+ **Call-site count is not sufficient on its own** — see the worked example
98
+ below, where the real count (8) sits in the Medium range by call-site
99
+ count alone, yet the target is still HIGH effort because of the
100
+ package-boundary and invariant clauses.
101
+
102
+ 7. **Name the test that must go from red to green** (or the golden-capture
103
+ delta that must appear) at each step — route through `testing-strategy`
104
+ for the right level/shape if the finding doesn't already specify one.
105
+
106
+ 8. **Every step names what proves it succeeded — no exceptions, including
107
+ intermediate steps.** This is stricter than the toolkit's own shipped
108
+ examples: `refactoring-analyzer.ts`'s own `extract_method` plan has a
109
+ `validation` field on steps 1, 3, 4 (`:69`, `:79`, `:84`) but step 2 — "extract
110
+ each section into a separate method" (`:72-75`) — carries only a `code`
111
+ field and NO validation, i.e. the toolkit's own reference plan has a step
112
+ whose success is unstated. That gap is exactly what this rule closes: a
113
+ step that only shows the code to write, with no stated proof it worked,
114
+ is incomplete here even if the toolkit's own precedent shipped that way.
115
+ Every step gets one of: a specific test name, a `tsc`/lint exit code, a
116
+ call-site grep returning zero, or an explicit "no assertion possible,
117
+ inspect manually" — never a bare code sample standing in for proof.
118
+
119
+ 9. **Report:**
120
+ ```
121
+ ## Refactor Plan — <finding source>: <file:line>
122
+ ### Before
123
+ ### After
124
+ ### Effort: low/medium/high — <which criterion triggered it>
125
+ ### Steps (each leaves the tree working; each names its own proof)
126
+ ### Verification per step
127
+ ```
128
+
129
+ ## Rules
130
+
131
+ - Never skip straight from BEFORE to AFTER in one step for anything touching
132
+ more than one call site — the incremental-migration order is the point;
133
+ a plan that says "rewrite it" is not a plan.
134
+ - A plan with no verification step per stage is incomplete — and "per
135
+ stage" means literally every step, not just the final one; a step with a
136
+ code sample and no proof is a rewrite instruction wearing a plan's
137
+ clothing.
138
+ - Never report effort from call-site count alone — always check the
139
+ package-boundary and cross-cutting-invariant criteria too; a low
140
+ call-site count on an invariant-bearing target is still HIGH.
141
+ - This skill does not execute the plan — hand off to `rdc:build`/`rdc:fixit`.
142
+
143
+ ## Worked Example — extracting `rdc-harness`'s `Harness` god-object
144
+
145
+ Chained input: the `architecture-reviewer` worked example (see that skill's
146
+ own SKILL.md) found `Harness` in
147
+ [`C:/Dev/rdc-harness/packages/core/src/index.mjs`](file:///C:/Dev/rdc-harness/packages/core/src/index.mjs)
148
+ reimplementing transaction/delivery/deploy/orchestration logic inline instead
149
+ of delegating to the sibling packages that already exist for each. `rdc-
150
+ refactoring-score` independently detects the SAME target via step 1's own
151
+ mechanical path — a real run (`node scripts/refactoring-score.mjs
152
+ C:/Dev/rdc-harness/packages/core/src/index.mjs`) reports:
153
+
154
+ ```
155
+ [extract-class] [high] Harness — 24 methods (over 15) — violates Single
156
+ Responsibility, candidate for Extract Class
157
+ ```
158
+
159
+ 24 methods, not the loose "+ 9 more methods" this example previously said —
160
+ the real count once `memberEntries()` visits constructors, getters/setters,
161
+ and arrow-property methods too, not just `cls.getMethods()`. This is that
162
+ finding turned into a plan.
163
+
164
+ ```
165
+ ## Refactor Plan — architecture-reviewer: packages/core/src/index.mjs:49-394 (Harness)
166
+
167
+ ### Before
168
+ class Harness {
169
+ deploy({ handleId }) {
170
+ const { handle, target, snapshot } = this.#bind(handleId, 'deploy');
171
+ const approved = Object.values(snapshot.decisions).some(...);
172
+ if (!approved) throw new RefusedError(...);
173
+ const to = join(this.#root, 'artifacts', 'production', ...);
174
+ this.#adapters.writeArtifact({ from: src.path, to }); // inline delivery
175
+ this.#emit({ type: 'deployed', ... });
176
+ }
177
+ // + 23 more methods, same shape: inline logic that belongs to a sibling package
178
+ }
179
+
180
+ ### After
181
+ class Harness {
182
+ constructor({ root, clock, adapters, deployPort } = {}) {
183
+ ...
184
+ this.#deployPort = deployPort ?? new DefaultDeployPort({ root, adapters });
185
+ }
186
+ deploy({ handleId }) {
187
+ const { handle, target, snapshot } = this.#bind(handleId, 'deploy');
188
+ const receipt = this.#deployPort.deploy({ handle, target, snapshot });
189
+ this.#emit({ type: 'deployed', ...receipt });
190
+ }
191
+ }
192
+ // packages/deploy/src/default-deploy-port.mjs — owns the artifact-path/approval logic
193
+
194
+ ### Effort: HIGH — package-boundary crossing and the invariant clause, NOT
195
+ call-site count. `rdc-refactoring-score`'s real reference-graph scan
196
+ (`plugin.referenceSitesOf('packages/core/src/index.mjs', 'Harness', ...)`,
197
+ positive control passed) reports **8 cross-file references**, all within
198
+ `packages/core` itself: `index.mjs:75`'s own internal factory call
199
+ (`new Harness(...)`), plus 7 in `packages/core/test/site-html-lifecycle.test.mjs`
200
+ — the `import { Harness, ... }` statement (line 20), 4 `new Harness(...)`
201
+ construction sites (lines 40/55/70/94), and 2 static-method calls,
202
+ `Harness.resume(...)` (line 142) and `Harness.replay(...)` (line 296). This
203
+ is a REAL demonstration of why the real reference-graph walk
204
+ (`findReferencesAsNodes`) is used here instead of a text grep: a naive
205
+ `grep -rn "new Harness(" --include="*.mjs"` finds only 5 of these 8 —
206
+ the import statement and both static-method calls are invisible to that
207
+ grep, and a `new Harness(...)` regex would silently undercount call sites on
208
+ any class whose API includes static factory/replay methods. By call-site
209
+ count alone (8, same package) criterion 2 says **Medium** — this example
210
+ previously speculated a call-site count of zero and a resulting "criterion 2
211
+ would say LOW", which the tool's real output does not bear out; correcting
212
+ that here matters because it is exactly the trap this rule exists to prevent
213
+ (see "Call-site count is not sufficient on its own" above). What actually drives
214
+ this to HIGH is criterion 3: the refactor's real target is package-boundary
215
+ crossing (the extraction moves deploy/delivery/transaction/orchestration
216
+ logic OUT of `packages/core` and INTO `packages/deploy`, `packages/delivery`,
217
+ etc. — those packages are not yet Harness's callers, but they become its
218
+ collaborators after the extract), plus the invariant clause — every method
219
+ must keep emitting to the same append-only event log with the same monotonic
220
+ `#seq`, and the file's own comments (`index.mjs:103-110`) document a prior
221
+ real bug where a spread copy silently defeated a freeze invariant. That
222
+ invariant is exactly the case a mechanical call-site count cannot see, which
223
+ is why `rdc-refactoring-score` reports `invariantCheckRequired: true`
224
+ whenever boundary-crossing or a >15-call-site result already pushes it to
225
+ High, but leaves the actual invariant judgment to this step.
226
+
227
+ ### Steps (each leaves the tree working; each names its own proof)
228
+ 1. Define `DeployPort`/`DeliveryPort`/`TransactionPort`/`OrchestrationPort`
229
+ interfaces (method signatures only, in `packages/deploy`, `packages/delivery`,
230
+ `packages/transaction`, `packages/orchestration` respectively) — unused by
231
+ `Harness` yet.
232
+ Validation: `npx tsc --noEmit` (or the repo's JS-equivalent lint) passes
233
+ with the new files added; zero import changes in `index.mjs` yet, so the
234
+ existing test suite is still 100% green with zero deltas.
235
+ 2. Implement `DefaultDeployPort` in `packages/deploy`, moving `deploy()`'s
236
+ artifact-path + approval-check logic verbatim out of `Harness` into it.
237
+ Validation: a new unit test in `packages/deploy/test/` exercises
238
+ `DefaultDeployPort.deploy()` directly with a fixture snapshot/handle and
239
+ asserts the same receipt shape `Harness.deploy()` used to return.
240
+ 3. Wire `Harness`'s constructor to accept an injected `deployPort` (default:
241
+ `new DefaultDeployPort(...)`), and change `deploy()` to call
242
+ `this.#deployPort.deploy(...)` instead of inline logic.
243
+ Validation: `packages/core/test/site-html-lifecycle.test.mjs` (the
244
+ existing suite) passes unmodified — same assertions, same call signature,
245
+ only the internal implementation moved.
246
+ 4. Repeat steps 2-3 for `shipDev`→`DeliveryPort`, `requestProduction`/
247
+ `recordDecision`→`TransactionPort`, `createRun`/`createRepository`/
248
+ `createTarget`→`OrchestrationPort`, one method-group at a time.
249
+ Validation per group: same pattern as step 3 — the existing lifecycle
250
+ test suite passes unmodified after each group's swap, never after all
251
+ four at once.
252
+ 5. Once all four ports are wired, grep `index.mjs` for `node:fs` and
253
+ `node:crypto` imports used OUTSIDE the four new ports.
254
+ Validation: the grep returns zero matches outside `#emit`/`#load` (the
255
+ event-log read/write, which is intentionally NOT extracted — it's the
256
+ spine, not a layering violation per this skill's own header) — this is
257
+ the positive-control check that no inline I/O was missed.
258
+ ## Verdict: HIGH effort, 5 steps, each with a named proof; 8 call sites, all
259
+ WITHIN `packages/core` (zero from any OTHER package today) means the
260
+ migration is low-RISK to sequence but not low-EFFORT — the two are different
261
+ axes and this plan's Effort line says so explicitly.
262
+ ```
@@ -106,6 +106,35 @@ description: "Usage `rdc:review [--unattended]` — Post-build quality gate: tsc
106
106
 
107
107
  Under `RDC_TEST=1`: echo `[RDC_TEST] skipping code-review dispatch` and continue.
108
108
 
109
+ 8c. **Form/fit/function gate — solid-validator + architecture-reviewer:**
110
+
111
+ ⛔ **No CLEAN verdict without this pass either.** Step 8b catches logic,
112
+ security, and convention drift. This step catches a different failure
113
+ shape entirely: code that is logically correct and convention-clean but
114
+ architecturally wrong — the `Harness` case (a use-case orchestrator that
115
+ reimplements what sibling packages exist to do, scoring fine on every
116
+ generic metric while failing the one check that names the actual rule).
117
+
118
+ ```bash
119
+ rdc-solid-score <modified-package-path> --diff origin/main --config <repo>/.solid-score.yml --format json
120
+ ```
121
+ Read `regressions` (SOLID score dropped vs `origin/main`) and
122
+ `boundaryViolations` (the `satisfied: false` subset of `boundaryFindings`
123
+ — named Clean Architecture rule failures) from the
124
+ output. Either non-empty is a FORM/FIT failure — dispatch
125
+ `architecture-reviewer` (skill: "architecture-reviewer") for the judgment
126
+ half if a boundary finding needs a suggested fix, not just a named
127
+ violation.
128
+
129
+ **Severity gate:**
130
+ - Any regression, or any boundary violation → verdict cannot be CLEAN.
131
+ - No `.solid-score.yml` present in the target repo → note it and skip,
132
+ do not fabricate a config; a repo with no boundary rules configured has
133
+ nothing wrong to report, but say so explicitly rather than silently
134
+ passing.
135
+
136
+ Under `RDC_TEST=1`: echo `[RDC_TEST] skipping form/fit/function gate` and continue.
137
+
109
138
  9. **Verification gate — dispatch the verify agent:**
110
139
  After any fixes land, run the verify gate on every touched package. See `guides/agents/verify.md`.
111
140
  Apply `guides/engineering-behavior.md` while reviewing: flag unnecessary abstraction, drive-by refactors, missing assumptions, hidden uncertainty, out-of-scope edits, and prose-only verification.
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: solid-validator
3
+ description: >-
4
+ Usage `rdc:solid-validator <path> [--diff <ref>] [--config <file>]` — the
5
+ FORM corner of the form/fit/function model. Deterministic AST scoring of
6
+ all five SOLID letters (weighted sum, confidence-annotated) plus a separate
7
+ Clean Architecture boundary check. Real numbers, not LLM judgment — see
8
+ `scripts/solid-score.mjs` for the mechanism.
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
+ # solid-validator — Deterministic SOLID + Clean Architecture Scoring
16
+
17
+ ## Why this one is mechanical, not a dispatched agent
18
+
19
+ Three other skills in this suite (`architecture-reviewer`,
20
+ `clean-code-analyzer`, `package-design`) dispatch `pr-review-toolkit:code-reviewer`
21
+ for judgment; `pattern-advisor` and `pattern-refactoring-guide` produce
22
+ recommendations/plans without a dispatch, and `testing-strategy` recommends
23
+ a level/shape rather than scoring anything. This one does not dispatch a
24
+ judgment agent at all, on purpose: a ratchet gate that blocks a merge
25
+ on regression needs a NUMBER two runs can be diffed against, and an LLM
26
+ judgment call is not deterministic enough for that job. `git` is the baseline
27
+ — `--diff <ref>` scores each unit twice (at `<ref>` and in the working tree)
28
+ and gates on the delta, so nothing here persists a baseline file that can
29
+ drift out of sync with reality.
30
+
31
+ Dogfooded: `rdc-harness`'s `Harness` god-object scores 68.5/100 on the
32
+ weighted SOLID sum (SRP=40, three disconnected components across 21 members
33
+ — real cohesion signal) while the boundary check independently fails it on
34
+ all 6 of its declared ports. Two checks, proven necessary together — DIP's
35
+ generic concrete-instantiation-ratio metric alone did NOT catch the
36
+ violation (a class with almost no dependencies of any kind scores fine on
37
+ it), which is why the boundary rule exists as a separate, named check rather
38
+ than folded into DIP's score.
39
+
40
+ ## Arguments
41
+
42
+ - `rdc:solid-validator <path>` — full score, current working tree
43
+ - `rdc:solid-validator <path> --diff <ref>` — regression gate against `<ref>`
44
+ - `rdc:solid-validator <path> --config <file>` — weights/thresholds/boundaries
45
+
46
+ ## Procedure
47
+
48
+ 1. **Run the scorer:**
49
+ ```bash
50
+ rdc-solid-score <path> --diff <ref> --config <repo>/.solid-score.yml --format json
51
+ ```
52
+ (installed globally via `npm link`/publish from this package — see
53
+ `package.json`'s `bin.rdc-solid-score`; falls back to
54
+ `node <rdc-skills-install-path>/scripts/solid-score.mjs` if the bin isn't
55
+ on PATH.)
56
+
57
+ 2. **Read `results`** — per-unit SRP/OCP/LSP/ISP/DIP scores with confidence
58
+ (`high`/`low-medium`/`low` — OCP and LSP are heuristic by nature; report
59
+ the confidence, never hide it).
60
+
61
+ 3. **Read `regressions`** (only present with `--diff`) — any unit whose
62
+ score dropped more than `diff.maxDecrease` (default 0, i.e. no regression
63
+ tolerated) versus the base ref, or any NEW unit below `diff.newUnitMin`.
64
+
65
+ 4. **Read `boundaryFindings`** — named Clean Architecture dependency-rule
66
+ violations from the repo's configured `boundaries` list. Treat these as
67
+ equally load-bearing as a regression — they catch a different failure
68
+ shape (see the `Harness` case above).
69
+
70
+ 5. **Read `unresolvedLanguages`** — files no registered plugin could parse.
71
+ Report them explicitly; do not silently treat them as passing. (Day-1
72
+ plugin: TypeScript/JavaScript via `ts-morph`. A Python plugin implementing
73
+ the same `lib/language-plugin.mjs` contract extends coverage without
74
+ touching this skill or the scoring core.)
75
+
76
+ 6. **Report:**
77
+ ```
78
+ ## SOLID + Clean Architecture Score
79
+ | Unit | SRP | OCP | LSP | ISP | DIP | Total |
80
+ ### Regressions (vs <ref>)
81
+ ### Boundary violations
82
+ ### Unresolved languages (no plugin — not silently passed)
83
+ ## Verdict: CLEAN / HAS ISSUES
84
+ ```
85
+
86
+ ## Rules
87
+
88
+ - Never treat an `unresolvedLanguages` entry as a pass — it is unmeasured,
89
+ not clean.
90
+ - A boundary violation is a hard block regardless of the weighted score —
91
+ do not let a high SOLID total offset a missing required port import.
92
+ - Confidence is reported data, never a reason to omit OCP/LSP scores.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: testing-strategy
3
+ description: >-
4
+ Usage `rdc:testing-strategy <path>` — recommends the right TEST LEVEL and
5
+ SHAPE for a surface (unit/integration/live tier, assertion vs golden-capture),
6
+ not a specific test to write. The FUNCTION corner of the form/fit/function
7
+ model — solid-validator covers FORM, architecture-reviewer covers FIT, this
8
+ covers whether a surface's behavior is actually provable, and how.
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
+ # testing-strategy — Test Level & Shape Recommendation
16
+
17
+ ## The distinction this exists to enforce
18
+
19
+ A test can be green and prove nothing — this session's own history: a
20
+ proof-ledger claimed 144/144 while grading each row against evidence weaker
21
+ than the row demanded, because the tests were written to make a NAMED CLAIM
22
+ go green rather than to capture real behavior. This skill's job is to say,
23
+ BEFORE a test is written, what level and shape it needs to be honest —
24
+ catching that failure at design time instead of at a review months later.
25
+
26
+ ## Level — pick the lowest that can still prove the claim
27
+
28
+ | Level | Proves | Fixture allowed? |
29
+ |---|---|---|
30
+ | **Unit** | pure function, one input/output contract | yes — the function IS the boundary |
31
+ | **Integration** | real adapters composed across a package boundary | disposable repo/worktree/registry fixture, never a stub standing in for the boundary itself |
32
+ | **Live** | a real external outcome (real git, real process, real HTTP) through the authoritative source | no — a fixture at this level is a downgrade, not a shortcut |
33
+
34
+ A claim that says "delivers," "deploys," "persists," or "ships" needs live
35
+ tier. A claim about a pure calculation needs unit tier and nothing more —
36
+ forcing live tier on a pure function is not rigor, it's noise that hides the
37
+ real live-tier gaps under a pile of slow, brittle tests.
38
+
39
+ ## Shape — assertion vs golden-capture
40
+
41
+ Ask: **can the author state, in one sentence, exactly what "correct" means
42
+ before running the code?**
43
+
44
+ - Yes → assertion-based test. Write the expectation, then the test.
45
+ - No (the correct shape is "whatever the real system currently does, until a
46
+ human reviews a change") → golden-capture. Run the seam, record the FULL
47
+ observable result (return value, thrown error, side-effect journal), diff
48
+ future runs against it. A delta is a question for a human, never an
49
+ auto-pass or auto-fail.
50
+
51
+ Golden-capture is not a lesser form of testing — it exists specifically
52
+ because assertion-writing failed on complex orchestration surfaces this same
53
+ session: the author cannot accidentally assert something weaker than the
54
+ truth when they are not choosing what to assert.
55
+
56
+ ## Procedure
57
+
58
+ 1. **Identify the surface's SEAM** — its real, production-called entry point.
59
+ If nothing in production calls it, that is not a testing question, it's an
60
+ `architecture-reviewer` finding (dead code / no production caller) —
61
+ route it there first. A test cannot prove a surface has no reason to exist.
62
+
63
+ 2. **Classify the claim** the surface makes (calculation / composition /
64
+ external effect) and pick the LOWEST level from the table above that can
65
+ prove it. State the level explicitly in the checklist row — not "tested",
66
+ but "unit: pure function" / "integration: composed across guard+delivery
67
+ against a disposable worktree" / "live: real git commit against a
68
+ disposable repo".
69
+
70
+ 3. **Pick assertion vs golden-capture** using the one-sentence test above.
71
+
72
+ 4. **For a claim naming an outcome word** (delivers/deploys/persists/ships/
73
+ writes) — require a positive control: the SAME test run against a target
74
+ KNOWN to succeed, before trusting a negative result from it. A test that
75
+ never proves it CAN detect failure proves nothing when it passes.
76
+
77
+ 5. **Report:**
78
+ ```
79
+ ## Testing Strategy
80
+ | Surface | Claim | Level | Shape | Positive control needed? |
81
+ ```
82
+
83
+ ## Rules
84
+
85
+ - Never recommend live tier for a pure function, or unit tier for an
86
+ external-effect claim — matching level to claim is the whole point.
87
+ - A recommendation with no seam identified is incomplete — say so, don't
88
+ guess a seam that doesn't exist in production.
89
+ - This skill recommends; it does not write the test. Pair with the actual
90
+ build task.
91
+
92
+ ## Mechanical test-smell scoring — a separate, lower layer
93
+
94
+ Everything above answers "what SHAPE should this test be" before it's
95
+ written. `scripts/lib/test-smell-scoring.mjs` answers a different question
96
+ once a test exists: does its own construction show a known smell? Same
97
+ distinction as `solid-validator`/`architecture-reviewer` — this is FORM
98
+ applied to test code, not FIT or FUNCTION, and it runs mechanically over
99
+ existing `.test.mjs`/`.test.ts`/`*.spec.*` files, not as a pre-write
100
+ recommendation.
101
+
102
+ Language-independent in shape (pure functions over text/AST facts, same
103
+ discipline as `language-plugin.mjs` — no ts-morph import in the scoring file
104
+ itself). Rule IDs and several thresholds are reused from
105
+ [OnSightTeam/architecture-toolkit](https://github.com/OnSightTeam/architecture-toolkit)
106
+ (MIT) — see the file header for exact file:line citations and what was
107
+ adapted vs. reused verbatim.
108
+
109
+ | Rule | Detects | Threshold |
110
+ |---|---|---|
111
+ | T1 Insufficient Tests | fewer test() blocks than exported functions/methods in the paired source file | `testCount < exportedUnitCount` (source count via the `NormalizedUnit` plugin contract, not a regex) |
112
+ | T2 Ignored Tests | `test.skip`/`it.skip`/`xit`/`xdescribe` | any occurrence |
113
+ | T5 Exhaustive Testing | too many assertions in one test() block | >10 per block |
114
+ | T6 Long Tests | oversized test() block body | >30 lines |
115
+ | T7 Slow Tests | literal `setTimeout`/`setInterval`/`sleep`/`delay` calls INSIDE a test's own body | any occurrence in-block |
116
+ | T8 Fragile Tests | `Date.now()`, bare `new Date()`, `Math.random()`, `process.env.*` referenced directly inside a test body | any occurrence in-block |
117
+ | T9 Duplicated Setup | near-identical `beforeEach`/`beforeAll` bodies ACROSS test files | ≥0.75 Jaccard similarity over normalized 3-token shingles (literals collapsed, identifiers not — see file header) |
118
+ | FIRST-Independent | a `let` declared outside test() and mutated inside 2+ separate test() blocks | requires both the declaration site AND ≥2 distinct mutation sites, not presence alone |
119
+
120
+ T3 (Test Per Class) and T4 (Untested Method) are deliberately not
121
+ implemented — see the file header's "Skipped" section for why faking them
122
+ would have been a weak check invented to fill a slot. Fast/Repeatable/
123
+ SelfValidating/Timely are likewise skipped as either duplicating T7/T8 or
124
+ not being smell checks at all.
125
+
126
+ Dogfooded against `rdc-harness`'s 47 real `*.test.mjs` files (2026-08-20):
127
+ 71 findings (T5/T6/T8), plus T1 flagged `packages/core` (28 exported
128
+ members, 16 tests — the same `Harness` god-object `solid-score.mjs` already
129
+ flags on SOLID grounds) and three other packages. T2/T7(in-block)/T9/
130
+ Independent had zero real hits in that corpus — confirmed as true negatives
131
+ via grep positive-control before trusting the absence, and each rule was
132
+ separately proven to fire against a synthetic fixture exercising it.