@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.
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,274 @@
1
+ /**
2
+ * memberFacts.ts — per-member structural facts for the validation engine.
3
+ *
4
+ * WHY THIS MODULE EXISTS
5
+ * ----------------------
6
+ * `nativeParser.ts` extracted calls only under
7
+ * `exported && declNode.type === 'function_declaration'`. Class methods —
8
+ * which are most of what SOLID / Clean Code / design-pattern / refactoring
9
+ * scoring actually reads — produced nothing at all: no calls, and in
10
+ * nativeParser not even a symbol. A scorer fed that surface cannot tell an
11
+ * empty class from an unparsed one.
12
+ *
13
+ * This module walks EVERY callable body (free function, class method,
14
+ * constructor, accessor, arrow/lambda, nested helper) and reports the
15
+ * structural facts a scorer needs, per member and per unit.
16
+ *
17
+ * CONSUMER SPLIT — deliberate, not an oversight
18
+ * ---------------------------------------------
19
+ * CodeFlow's graph wants the coarse surface (symbols / calls / imports) and is
20
+ * unchanged by this file. The validation engine wants this. Both are served
21
+ * from ONE parse of ONE tree, so the two views can never disagree about what
22
+ * the source said — which is the failure mode that produced this work in the
23
+ * first place (two independent parsers, same bug in both).
24
+ *
25
+ * NON-FUNCTIONAL CONTRACT
26
+ * -----------------------
27
+ * - **Deterministic.** Identical bytes for identical input, across calls and
28
+ * across processes. Every collection is emitted in a defined order (source
29
+ * order, or lexicographic where the set has no natural order). No
30
+ * timestamps. No absolute paths — `path` is echoed exactly as the caller
31
+ * supplied it and is never resolved against the filesystem.
32
+ * - **No accumulating per-process state.** Every exported function here is
33
+ * pure over (node, profile) and allocates its own working set. The real
34
+ * defect behind this requirement was ts-morph's shared `Project` silently
35
+ * returning wrong reference counts once ~113 files accumulated in one
36
+ * process; nothing in this module caches across calls, so that class of
37
+ * drift cannot occur.
38
+ * - **In-process, no network.** No service, no port, no grammar download.
39
+ *
40
+ * GRAMMAR PORTABILITY
41
+ * -------------------
42
+ * Every tree-sitter grammar spells the same construct differently — `call` vs
43
+ * `call_expression` vs `invocation_expression` is the bug that cost python and
44
+ * csharp their entire CALLS surface. So node types live in one table
45
+ * (`LANGUAGE_PROFILES`) rather than being written inline at each test site. A
46
+ * new language is a table row, and a language with no row degrades to the
47
+ * C-like defaults with `profile_complete: false` on the result rather than
48
+ * silently reporting zeros that look like clean code.
49
+ */
50
+ /** A name bound inside a member: a parameter or a local declaration. */
51
+ export interface DeclaredName {
52
+ name: string;
53
+ line: number;
54
+ kind: 'param' | 'const' | 'let' | 'var' | 'local';
55
+ }
56
+ /** A numeric literal that is not 0, 1 or -1 and is not a named constant. */
57
+ export interface MagicNumber {
58
+ /** Literal source text, sign included when written as a unary minus. */
59
+ value: string;
60
+ line: number;
61
+ }
62
+ /**
63
+ * One `switch` in a member body, described by the two shapes that matter to a
64
+ * refactoring scorer.
65
+ *
66
+ * `behaviorDispatch` — cases select behaviour (call/return per case). The
67
+ * classic replace-conditional-with-polymorphism candidate.
68
+ *
69
+ * `typeConstruction` — cases construct types (`new X()` per case). A factory
70
+ * in disguise; a different refactoring with a different risk profile.
71
+ *
72
+ * They are not mutually exclusive and a switch can be neither.
73
+ */
74
+ export interface SwitchFact {
75
+ line: number;
76
+ /** Normalized discriminant source text, grouping parens removed. */
77
+ discriminant: string;
78
+ /**
79
+ * Non-default arms. `default` is reported separately by `hasDefault` rather
80
+ * than folded in here, because "3 cases" and "2 cases plus a fallback" are
81
+ * different refactoring situations and a single total cannot say which.
82
+ */
83
+ caseCount: number;
84
+ hasDefault: boolean;
85
+ behaviorDispatch: boolean;
86
+ typeConstruction: boolean;
87
+ }
88
+ /**
89
+ * How a member relates to the same-named member on its base class.
90
+ *
91
+ * Resolution is BATCH-SCOPED: the base class must be declared in one of the
92
+ * files handed to the same `parse()` call. When it is not, the drift fields
93
+ * are `null` — meaning "not determinable here", never `false`. A scorer must
94
+ * be able to tell an override that matches from an override we could not
95
+ * check, and collapsing those two into `false` is how a clean report gets
96
+ * manufactured from missing data.
97
+ */
98
+ export interface OverrideShape {
99
+ callsSuper: boolean;
100
+ baseClass: string | null;
101
+ baseParamCount: number | null;
102
+ /** own paramCount − base paramCount; null when the base is unresolved. */
103
+ paramCountDrift: number | null;
104
+ baseReturnType: string | null;
105
+ /** true when both return types are known and differ. */
106
+ returnTypeDrift: boolean | null;
107
+ }
108
+ /** Every structural fact for one callable body. */
109
+ export interface ParsedMember {
110
+ name: string;
111
+ /** Enclosing class/unit name; null at module scope. */
112
+ owner: string | null;
113
+ kind: 'function' | 'method' | 'constructor' | 'getter' | 'setter' | 'arrow';
114
+ exported: boolean;
115
+ isStatic: boolean;
116
+ start_line: number;
117
+ end_line: number;
118
+ return_type: string | null;
119
+ paramCount: number;
120
+ /** Parameters first (source order), then locals (source order). */
121
+ declaredNames: DeclaredName[];
122
+ /** Flattened statement-kind descendant count. */
123
+ statementCount: number;
124
+ /** Normalized text of every statement longer than 10 chars, source order. */
125
+ statementTexts: string[];
126
+ /** `this.x` / `self.x` names, deduped, lexicographic. */
127
+ fieldAccess: string[];
128
+ /** Bare trailing call names with the receiver fully stripped, source order. */
129
+ calleeNames: string[];
130
+ /** Calls shaped `a.b.c(...)` — receiver depth >= 2. */
131
+ deepChainCallCount: number;
132
+ /** Every `new X()` target name, unfiltered, source order. */
133
+ constructorNewCallTargets: string[];
134
+ /** switch cases + instanceof/typeof checks + if/else-if chain lengths. */
135
+ branchHits: number;
136
+ switchStatements: SwitchFact[];
137
+ /** `if` conditions whose source text is >= 50 chars, source order. */
138
+ complexConditionals: string[];
139
+ /** `if` conditions with a top-level null comparison. */
140
+ nullChecks: number;
141
+ magicNumbers: MagicNumber[];
142
+ /** Catch blocks containing zero statements. */
143
+ emptyCatches: number;
144
+ /** `if (true)` / `if (false)` / `while (false)`. */
145
+ deadConditionals: number;
146
+ override: OverrideShape;
147
+ }
148
+ /** Facts about a class (or the file's module scope). */
149
+ export interface ParsedUnit {
150
+ name: string;
151
+ kind: 'class' | 'module';
152
+ exported: boolean;
153
+ start_line: number;
154
+ end_line: number;
155
+ baseClass: string | null;
156
+ hasBaseClass: boolean;
157
+ /** `new X()` where X is a class declared in THIS file. */
158
+ concreteInstantiations: number;
159
+ /**
160
+ * Distinct external names this unit leans on: construction targets, call
161
+ * receivers other than this/self, and callees not declared inside the unit.
162
+ */
163
+ totalDependencies: number;
164
+ /** Static field/property names, lexicographic. */
165
+ staticPropertyNames: string[];
166
+ /** A `getInstance` / `get_instance` / `Instance` member — singleton tell. */
167
+ hasGetInstanceMethod: boolean;
168
+ /** Member names owned by this unit, source order. */
169
+ memberNames: string[];
170
+ }
171
+ /**
172
+ * Where one exported symbol is referenced, across the parse batch.
173
+ *
174
+ * Batch-scoped by construction, and that is the honest scope: `parse()` is
175
+ * given a set of files and can only speak about those. `files` excludes the
176
+ * declaring file and `count` excludes the declaration site itself, so a symbol
177
+ * used nowhere else reads `count: 0, files: []` — the dead-export signal.
178
+ */
179
+ export interface SymbolReference {
180
+ name: string;
181
+ count: number;
182
+ files: string[];
183
+ }
184
+ /** Node-type vocabulary for one grammar. */
185
+ export interface LanguageProfile {
186
+ call: string[];
187
+ newExpr: string[];
188
+ /** Field naming the constructed type on a `newExpr` node. */
189
+ newTypeField: string[];
190
+ func: string[];
191
+ method: string[];
192
+ constructorDecl: string[];
193
+ klass: string[];
194
+ lambda: string[];
195
+ /** Receiver spellings that mean "my own instance". */
196
+ selfWords: string[];
197
+ /** Node type for an explicit self/this keyword, if the grammar has one. */
198
+ selfNodes: string[];
199
+ memberAccess: string[];
200
+ memberObjectField: string;
201
+ memberPropertyField: string;
202
+ ifStmt: string[];
203
+ switchStmt: string[];
204
+ switchCase: string[];
205
+ switchDefault: string[];
206
+ catchClause: string[];
207
+ whileStmt: string[];
208
+ numberLit: string[];
209
+ trueLit: string[];
210
+ falseLit: string[];
211
+ nullLit: string[];
212
+ /** Extra node types counted as statements beyond the `_statement` suffix. */
213
+ extraStatements: string[];
214
+ /** Node types that hold a body of statements. */
215
+ blocks: string[];
216
+ paramsField: string[];
217
+ bodyField: string[];
218
+ nameField: string[];
219
+ conditionField: string[];
220
+ /** Node type for a class's static-modifier-bearing field declaration. */
221
+ fieldDecl: string[];
222
+ /** Operators that mean equality for null-check detection. */
223
+ eqOperators: string[];
224
+ }
225
+ /** True when this language has a hand-written profile (not the fallback). */
226
+ export declare function hasLanguageProfile(language: string): boolean;
227
+ export declare function profileFor(language: string): LanguageProfile;
228
+ /** Collapse all whitespace runs to single spaces and trim. */
229
+ export declare function normalizeText(text: string): string;
230
+ /**
231
+ * Extract every callable body in a file, with its owning unit.
232
+ *
233
+ * `onBody` receives each member's identity and its body node as the walk finds
234
+ * it. That callback is how the coarse CALLS surface is driven: one traversal
235
+ * decides what a member IS, and both the fact surface and the call surface
236
+ * read that same decision. The alternative — a second walk with its own idea
237
+ * of which bodies count — is exactly the arrangement that let `call` vs
238
+ * `call_expression` diverge unnoticed across two parsers.
239
+ */
240
+ export declare function extractMembers(rootNode: any, language: string, onBody?: (member: ParsedMember, bodyNode: any) => void): {
241
+ members: ParsedMember[];
242
+ units: ParsedUnit[];
243
+ };
244
+ /**
245
+ * Fill in override drift for members whose base class was parsed in the same
246
+ * batch. Mutates in place; members whose base is not in the batch keep their
247
+ * `null` drift fields, which is the "not determinable" signal, not "no drift".
248
+ */
249
+ export declare function resolveOverrideShapes(perFile: Array<{
250
+ members: ParsedMember[];
251
+ units: ParsedUnit[];
252
+ }>): void;
253
+ /**
254
+ * Count references to each file's exported symbols across the rest of the
255
+ * batch — the equivalent of ts-morph `findReferencesAsNodes()`, minus the
256
+ * shared-`Project` state that made its counts drift after ~113 files.
257
+ *
258
+ * Identifier occurrences are collected per file ONCE, then counted. That is
259
+ * O(files + names), not O(files x names), so a large batch does not degrade.
260
+ */
261
+ export declare function buildReferenceGraph(files: Array<{
262
+ path: string;
263
+ exportedNames: string[];
264
+ identifierCounts: Map<string, number>;
265
+ }>): Map<string, SymbolReference[]>;
266
+ /**
267
+ * Every identifier occurrence in a tree, counted by name.
268
+ *
269
+ * Declaration-site occurrences are excluded by the caller subtracting the
270
+ * declaring file, which is why this stays a dumb frequency map: a
271
+ * declaration-aware version would need scope resolution the grammar does not
272
+ * give us, and a half-correct one would be worse than an honest count.
273
+ */
274
+ export declare function countIdentifiers(rootNode: any): Map<string, number>;