@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.
- 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,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern-advisor scoring — pure functions over a `NormalizedUnit` (see
|
|
3
|
+
* language-plugin.mjs), same discipline as solid-scoring.mjs and
|
|
4
|
+
* clean-code-scoring.mjs: no ts-morph, no language-specific parser. Every
|
|
5
|
+
* fact these detectors read was computed once, in `lib/plugins/typescript.mjs`,
|
|
6
|
+
* from the real AST.
|
|
7
|
+
*
|
|
8
|
+
* Detection heuristics (thresholds, word lists, structural shapes) are
|
|
9
|
+
* ported from architecture-toolkit's REAL implementation —
|
|
10
|
+
* github.com/OnSightTeam/architecture-toolkit (MIT), specifically
|
|
11
|
+
* `src/agents/pattern-advisor/tools/{creational,structural,behavioral}-
|
|
12
|
+
* pattern-analyzer.ts` — raw source fetched from
|
|
13
|
+
* raw.githubusercontent.com/OnSightTeam/architecture-toolkit/main/... and
|
|
14
|
+
* read in full this task (2026-08-20), reuse explicitly approved by the
|
|
15
|
+
* operator for this scorer family. Their checks are whole-file text regexes
|
|
16
|
+
* with zero scoping to which switch/if/call the signal actually came from
|
|
17
|
+
* (e.g. `/switch\s*\([^)]*type[^)]*\)\s*{[^}]*new\s+/i` matches if "new"
|
|
18
|
+
* appears ANYWHERE after a type-switch's opening brace, even three
|
|
19
|
+
* unrelated statements later). Ours walks the real AST per switch-case /
|
|
20
|
+
* if-block / call-expression via facts computed in typescript.mjs
|
|
21
|
+
* (switchStatements[].hasTypeCreation, switchBehaviorCallLine,
|
|
22
|
+
* constructorNewCallTargets, conditionalFeatureCallLine, deepChainCallCount,
|
|
23
|
+
* calleeNames, staticPropertyNames, hasGetInstanceMethod) — every finding is
|
|
24
|
+
* attributable to the real member/unit and (where the toolkit's own signal
|
|
25
|
+
* is node-scoped) the real line it was found in.
|
|
26
|
+
*
|
|
27
|
+
* Confidence and priority numbers below are HARD-CODED, not computed — they
|
|
28
|
+
* are the LITERAL values architecture-toolkit's own analyzers return (see
|
|
29
|
+
* per-detector citation comment). skills/pattern-advisor/SKILL.md's existing
|
|
30
|
+
* "70-90% confidence calibration table" (written before this scorer existed,
|
|
31
|
+
* itself already citing the same toolkit source lines) was checked against
|
|
32
|
+
* every number below: NO discrepancy found — Factory Method 90/75, Builder
|
|
33
|
+
* 85, Singleton 70, Decorator 75, Adapter 80, Facade 70, Strategy 90,
|
|
34
|
+
* Observer 75, Command 80, Template Method 70 all match exactly.
|
|
35
|
+
*
|
|
36
|
+
* One deliberate narrowing from the source: Adapter's original regex is
|
|
37
|
+
* `/convert|transform|adapt|wrap.*interface/i` (structural-pattern-
|
|
38
|
+
* analyzer.ts:74) — the `wrap.*interface` alternative is dropped here
|
|
39
|
+
* because it is a `.*`-spanning whole-file text match with no AST-scoped
|
|
40
|
+
* equivalent that wouldn't just re-derive Decorator's own "wrap" signal
|
|
41
|
+
* under a different name; `convert|transform|adapt` (the unambiguous three
|
|
42
|
+
* words) is kept in full.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
function loc(unit, memberName, line) {
|
|
46
|
+
if (!memberName) return unit.name;
|
|
47
|
+
return line != null ? `${unit.name}#${memberName}:${line}` : `${unit.name}#${memberName}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Creational ───────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
// Factory Method — TWO independent toolkit signals, each its own finding:
|
|
53
|
+
// (a) switch-on-type constructs via `new` (creational-pattern-analyzer.ts:
|
|
54
|
+
// 43-64) — reuses the EXISTING `switchStatements[].hasTypeCreation`
|
|
55
|
+
// fact (added for refactoring-scoring.mjs's factory-transform; the
|
|
56
|
+
// identical regex shape appears independently at both
|
|
57
|
+
// creational-pattern-analyzer.ts:43 and pattern-transformation-guide.ts:
|
|
58
|
+
// 100 in the real toolkit source, so this is a genuine, not coincidental, reuse).
|
|
59
|
+
// (b) >5 total `new` calls with >3 unique constructor names scattered in
|
|
60
|
+
// one member (creational-pattern-analyzer.ts:67-84).
|
|
61
|
+
export function detectFactoryMethod(unit) {
|
|
62
|
+
const findings = [];
|
|
63
|
+
for (const m of unit.members) {
|
|
64
|
+
for (const sw of m.switchStatements ?? []) {
|
|
65
|
+
if (sw.hasTypeCreation) {
|
|
66
|
+
findings.push({
|
|
67
|
+
location: loc(unit, m.name, sw.line), line: sw.line,
|
|
68
|
+
problem: 'switch statement on a "type" discriminant constructs objects via `new` within the switch',
|
|
69
|
+
solution: 'Use Factory Method pattern to encapsulate object creation logic',
|
|
70
|
+
reasoning: 'Factory Method eliminates switch statements and follows Open/Closed Principle',
|
|
71
|
+
confidence: 90, priority: 'high',
|
|
72
|
+
alternatives: ['Abstract Factory (if multiple product families)', 'Strategy (if behavior varies)'],
|
|
73
|
+
tradeoffs: { pros: ['Open/Closed compliant', 'Easy to add new types', 'Single Responsibility'], cons: ['More classes', 'Slight complexity increase'] },
|
|
74
|
+
source: 'creational-pattern-analyzer.ts:39-65',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const targets = m.constructorNewCallTargets ?? [];
|
|
79
|
+
const unique = new Set(targets);
|
|
80
|
+
if (targets.length > 5 && unique.size > 3) {
|
|
81
|
+
findings.push({
|
|
82
|
+
location: loc(unit, m.name), line: null,
|
|
83
|
+
problem: `${targets.length} object instantiation(s) (${unique.size} different type(s)) scattered in one member`,
|
|
84
|
+
solution: 'Centralize object creation in Factory Method',
|
|
85
|
+
reasoning: 'Reduces coupling and makes code more maintainable',
|
|
86
|
+
confidence: 75, priority: 'medium',
|
|
87
|
+
alternatives: ['Abstract Factory (if multiple product families)'],
|
|
88
|
+
tradeoffs: { pros: ['Centralized creation', 'Reduced coupling'], cons: ['More classes', 'Indirection'] },
|
|
89
|
+
source: 'creational-pattern-analyzer.ts:67-86',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { pattern: 'Factory Method', findings };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Builder — constructor with >4 parameters (telescoping constructor).
|
|
97
|
+
// creational-pattern-analyzer.ts:92-119. `paramCount` is already part of the
|
|
98
|
+
// base NormalizedMember contract; no new fact needed.
|
|
99
|
+
export function detectBuilder(unit) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
for (const m of unit.members) {
|
|
102
|
+
if (m.name === 'constructor' && m.paramCount > 4) {
|
|
103
|
+
findings.push({
|
|
104
|
+
location: loc(unit, m.name), line: null,
|
|
105
|
+
problem: `constructor has ${m.paramCount} parameters (telescoping constructor anti-pattern)`,
|
|
106
|
+
solution: 'Use Builder pattern for step-by-step object construction',
|
|
107
|
+
reasoning: 'Builder provides fluent interface and handles optional parameters elegantly',
|
|
108
|
+
confidence: 85, priority: 'high',
|
|
109
|
+
alternatives: ['Named-options object (if construction has no ordering constraints — do not reach for Builder when a plain object literal does the job)'],
|
|
110
|
+
tradeoffs: { pros: ['Clear object construction', 'Handles optional parameters', 'Immutable objects'], cons: ['More code', 'Additional builder class needed'] },
|
|
111
|
+
source: 'creational-pattern-analyzer.ts:89-122',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { pattern: 'Builder', findings };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Singleton — `private static instance` field OR a `getInstance()` method/
|
|
119
|
+
// call-site. creational-pattern-analyzer.ts:124-146
|
|
120
|
+
// (`/private\s+static\s+instance|getInstance\s*\(\)/i` — an OR, not an AND).
|
|
121
|
+
export function detectSingleton(unit) {
|
|
122
|
+
const staticInstanceField = (unit.staticPropertyNames ?? []).some((n) => /instance/i.test(n));
|
|
123
|
+
const getInstanceCallSite = unit.members.some((m) => (m.calleeNames ?? []).includes('getInstance'));
|
|
124
|
+
if (!unit.hasGetInstanceMethod && !staticInstanceField && !getInstanceCallSite) {
|
|
125
|
+
return { pattern: 'Singleton', findings: [] };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
pattern: 'Singleton',
|
|
129
|
+
findings: [{
|
|
130
|
+
location: unit.name, line: null,
|
|
131
|
+
problem: 'Singleton pattern detected (often overused anti-pattern)',
|
|
132
|
+
solution: 'Consider dependency injection instead of Singleton',
|
|
133
|
+
reasoning: 'Singletons create hidden dependencies and make testing difficult',
|
|
134
|
+
confidence: 70, priority: 'medium',
|
|
135
|
+
alternatives: ['Dependency Injection', 'Monostate pattern'],
|
|
136
|
+
tradeoffs: { pros: ['Global access', 'Single instance guaranteed'], cons: ['Hidden dependencies', 'Hard to test', 'Violates SRP', 'Global state'] },
|
|
137
|
+
source: 'creational-pattern-analyzer.ts:124-146',
|
|
138
|
+
}],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Structural ───────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
// Decorator — conditional logic adds features via a wrap/add/extend/enhance
|
|
145
|
+
// call. structural-pattern-analyzer.ts:39-68.
|
|
146
|
+
export function detectDecorator(unit) {
|
|
147
|
+
const findings = [];
|
|
148
|
+
for (const m of unit.members) {
|
|
149
|
+
if (m.conditionalFeatureCallLine != null) {
|
|
150
|
+
findings.push({
|
|
151
|
+
location: loc(unit, m.name, m.conditionalFeatureCallLine), line: m.conditionalFeatureCallLine,
|
|
152
|
+
problem: 'conditional logic adds a feature/responsibility dynamically (an if-block calls a wrap/add/extend/enhance-named function)',
|
|
153
|
+
solution: 'Use Decorator pattern to add responsibilities without inheritance',
|
|
154
|
+
reasoning: 'Decorator provides flexible alternative to subclassing for extending functionality',
|
|
155
|
+
confidence: 75, priority: 'medium',
|
|
156
|
+
alternatives: ['Chain of Responsibility'],
|
|
157
|
+
tradeoffs: { pros: ['Flexible composition', 'Open/Closed compliant', 'Single Responsibility'], cons: ['Many small objects', 'Complexity in configuration'] },
|
|
158
|
+
source: 'structural-pattern-analyzer.ts:39-68',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { pattern: 'Decorator', findings };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Adapter — a member name or a call it makes names an interface conversion.
|
|
166
|
+
// structural-pattern-analyzer.ts:70-98 (see file header for the deliberate
|
|
167
|
+
// `wrap.*interface` narrowing).
|
|
168
|
+
const ADAPTER_RE = /(convert|transform|adapt)/i;
|
|
169
|
+
export function detectAdapter(unit) {
|
|
170
|
+
const findings = [];
|
|
171
|
+
for (const m of unit.members) {
|
|
172
|
+
const nameMatch = ADAPTER_RE.test(m.name);
|
|
173
|
+
const callMatch = (m.calleeNames ?? []).some((n) => ADAPTER_RE.test(n));
|
|
174
|
+
if (nameMatch || callMatch) {
|
|
175
|
+
findings.push({
|
|
176
|
+
location: loc(unit, m.name), line: null,
|
|
177
|
+
problem: nameMatch
|
|
178
|
+
? `member name '${m.name}' names an interface conversion (convert/transform/adapt)`
|
|
179
|
+
: 'member calls a function named for an interface conversion (convert/transform/adapt)',
|
|
180
|
+
solution: 'Use Adapter pattern to make interfaces compatible',
|
|
181
|
+
reasoning: 'Adapter allows collaboration between classes with incompatible interfaces',
|
|
182
|
+
confidence: 80, priority: 'medium',
|
|
183
|
+
alternatives: [],
|
|
184
|
+
tradeoffs: { pros: ['Reuses existing code', 'Single Responsibility', 'Open/Closed'], cons: ['Additional class', 'Potential performance overhead'] },
|
|
185
|
+
source: 'structural-pattern-analyzer.ts:70-98',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { pattern: 'Adapter', findings };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Facade — >5 calls shaped `a.b.c(...)` in one member.
|
|
193
|
+
// structural-pattern-analyzer.ts:100-128.
|
|
194
|
+
export function detectFacade(unit) {
|
|
195
|
+
const findings = [];
|
|
196
|
+
for (const m of unit.members) {
|
|
197
|
+
if ((m.deepChainCallCount ?? 0) > 5) {
|
|
198
|
+
findings.push({
|
|
199
|
+
location: loc(unit, m.name), line: null,
|
|
200
|
+
problem: `${m.deepChainCallCount} call(s) shaped 'a.b.c(...)' — complex interactions with multiple subsystem objects`,
|
|
201
|
+
solution: 'Use Facade pattern to provide a simplified interface to the complex subsystem',
|
|
202
|
+
reasoning: 'Facade reduces coupling and provides an easier-to-use interface',
|
|
203
|
+
confidence: 70, priority: 'medium',
|
|
204
|
+
alternatives: [],
|
|
205
|
+
tradeoffs: { pros: ['Simplified interface', 'Loose coupling', 'Easier to use'], cons: ['May hide useful subsystem features', 'Additional layer'] },
|
|
206
|
+
source: 'structural-pattern-analyzer.ts:100-128',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { pattern: 'Facade', findings };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Behavioral ───────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
// Strategy — a switch statement selects behavior via a calculate/process/
|
|
216
|
+
// execute/validate/format-named call. behavioral-pattern-analyzer.ts:40-68.
|
|
217
|
+
export function detectStrategy(unit) {
|
|
218
|
+
const findings = [];
|
|
219
|
+
for (const m of unit.members) {
|
|
220
|
+
if (m.switchBehaviorCallLine != null) {
|
|
221
|
+
findings.push({
|
|
222
|
+
location: loc(unit, m.name, m.switchBehaviorCallLine), line: m.switchBehaviorCallLine,
|
|
223
|
+
problem: 'switch statement selects a different algorithm/behavior at runtime (case body calls a calculate/process/execute/validate/format-named function)',
|
|
224
|
+
solution: 'Use Strategy pattern to encapsulate interchangeable algorithms',
|
|
225
|
+
reasoning: 'Strategy allows algorithm selection at runtime while maintaining Open/Closed Principle',
|
|
226
|
+
confidence: 90, priority: 'high',
|
|
227
|
+
alternatives: [],
|
|
228
|
+
tradeoffs: { pros: ['Open/Closed compliant', 'Runtime algorithm selection', 'Testable strategies'], cons: ['More classes', 'Client must know strategies'] },
|
|
229
|
+
source: 'behavioral-pattern-analyzer.ts:40-68',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { pattern: 'Strategy', findings };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Observer — >3 calls (aggregated across the whole unit, matching the
|
|
237
|
+
// toolkit's own whole-file count) named notify/update/inform/broadcast.
|
|
238
|
+
// behavioral-pattern-analyzer.ts:70-101.
|
|
239
|
+
const OBSERVER_RE = /^(notify|update|inform|broadcast)/i;
|
|
240
|
+
export function detectObserver(unit) {
|
|
241
|
+
const matches = unit.members.flatMap((m) => m.calleeNames ?? []).filter((n) => OBSERVER_RE.test(n));
|
|
242
|
+
if (matches.length <= 3) return { pattern: 'Observer', findings: [] };
|
|
243
|
+
return {
|
|
244
|
+
pattern: 'Observer',
|
|
245
|
+
findings: [{
|
|
246
|
+
location: unit.name, line: null,
|
|
247
|
+
problem: `${matches.length} manual notification call(s) (notify/update/inform/broadcast) suggest tight coupling between objects`,
|
|
248
|
+
solution: 'Use Observer pattern for loose coupling and automatic notifications',
|
|
249
|
+
reasoning: 'Observer decouples objects and allows dynamic subscription',
|
|
250
|
+
confidence: 75, priority: 'medium',
|
|
251
|
+
alternatives: ['Event Bus', 'Pub/Sub'],
|
|
252
|
+
tradeoffs: { pros: ['Loose coupling', 'Dynamic subscription', 'Broadcast capability'], cons: ['Potential memory leaks', 'Unexpected updates', 'Update order unclear'] },
|
|
253
|
+
source: 'behavioral-pattern-analyzer.ts:70-101',
|
|
254
|
+
}],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Command — >4 occurrences of undo/redo/history/queue/execute as EXACT
|
|
259
|
+
// identifier/call names (matches the source regex's `\b(...)\b` word-
|
|
260
|
+
// boundary behavior: "executeCommand" does not match, a bare "execute" does)
|
|
261
|
+
// across the unit's own name, member names, callee names, declared
|
|
262
|
+
// local-binding names, AND `this.x` field references (`fieldAccess`,
|
|
263
|
+
// already on the base contract) — the toolkit's own regex is a blind
|
|
264
|
+
// whole-file text match, so a bare property reference like `this.queue`
|
|
265
|
+
// is exactly as real an occurrence to it as a call or a declaration; a
|
|
266
|
+
// scan that skipped fieldAccess under-counted a real fixture case
|
|
267
|
+
// (`this.queue`/`this.history` used only as property reads, never called
|
|
268
|
+
// or declared) below the >4 threshold during dogfooding. behavioral-
|
|
269
|
+
// pattern-analyzer.ts:103-133.
|
|
270
|
+
const COMMAND_RE = /^(undo|redo|history|queue|execute)$/i;
|
|
271
|
+
export function detectCommand(unit) {
|
|
272
|
+
let count = COMMAND_RE.test(unit.name) ? 1 : 0;
|
|
273
|
+
for (const m of unit.members) {
|
|
274
|
+
if (COMMAND_RE.test(m.name)) count++;
|
|
275
|
+
count += (m.calleeNames ?? []).filter((n) => COMMAND_RE.test(n)).length;
|
|
276
|
+
count += (m.declaredNames ?? []).filter((d) => COMMAND_RE.test(d.name)).length;
|
|
277
|
+
count += (m.fieldAccess ?? []).filter((n) => COMMAND_RE.test(n)).length;
|
|
278
|
+
}
|
|
279
|
+
if (count <= 4) return { pattern: 'Command', findings: [] };
|
|
280
|
+
return {
|
|
281
|
+
pattern: 'Command',
|
|
282
|
+
findings: [{
|
|
283
|
+
location: unit.name, line: null,
|
|
284
|
+
problem: `${count} occurrence(s) of undo/redo/history/queue/execute across names and calls suggest a need for undo/redo, queuing, or operation logging`,
|
|
285
|
+
solution: 'Use Command pattern to encapsulate requests as objects',
|
|
286
|
+
reasoning: 'Command enables undo/redo, queuing, and logging of operations',
|
|
287
|
+
confidence: 80, priority: 'high',
|
|
288
|
+
alternatives: [],
|
|
289
|
+
tradeoffs: { pros: ['Undo/redo support', 'Macro commands', 'Queuing operations', 'Logging'], cons: ['Many small classes', 'Increased complexity'] },
|
|
290
|
+
source: 'behavioral-pattern-analyzer.ts:103-133',
|
|
291
|
+
}],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Template Method — >2 members that each call an initialize/process/
|
|
296
|
+
// cleanup-named function (similar algorithm structure, varying details).
|
|
297
|
+
// behavioral-pattern-analyzer.ts:135-165.
|
|
298
|
+
const TEMPLATE_RE = /(initialize|process|cleanup)/i;
|
|
299
|
+
export function detectTemplateMethod(unit) {
|
|
300
|
+
const matchingMembers = unit.members.filter((m) => (m.calleeNames ?? []).some((n) => TEMPLATE_RE.test(n)));
|
|
301
|
+
if (matchingMembers.length <= 2) return { pattern: 'Template Method', findings: [] };
|
|
302
|
+
return {
|
|
303
|
+
pattern: 'Template Method',
|
|
304
|
+
findings: [{
|
|
305
|
+
location: unit.name, line: null,
|
|
306
|
+
problem: `${matchingMembers.length} member(s) call initialize/process/cleanup-named functions — similar algorithm structure repeated with varying details`,
|
|
307
|
+
solution: 'Use Template Method to define the algorithm skeleton with customizable steps',
|
|
308
|
+
reasoning: 'Template Method eliminates duplication while allowing customization',
|
|
309
|
+
confidence: 70, priority: 'medium',
|
|
310
|
+
alternatives: ['Straight composition (if there is no shared "how" worth abstracting)'],
|
|
311
|
+
tradeoffs: { pros: ['Reuses common code', 'Controls algorithm structure', 'Easy to extend'], cons: ['Inheritance-based', 'Less flexible than Strategy'] },
|
|
312
|
+
source: 'behavioral-pattern-analyzer.ts:135-165',
|
|
313
|
+
}],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const PATTERN_NAMES = [
|
|
318
|
+
'Factory Method', 'Builder', 'Singleton',
|
|
319
|
+
'Decorator', 'Adapter', 'Facade',
|
|
320
|
+
'Strategy', 'Observer', 'Command', 'Template Method',
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const DETECTORS = [
|
|
324
|
+
detectFactoryMethod, detectBuilder, detectSingleton,
|
|
325
|
+
detectDecorator, detectAdapter, detectFacade,
|
|
326
|
+
detectStrategy, detectObserver, detectCommand, detectTemplateMethod,
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @param {import('./language-plugin.mjs').NormalizedUnit} unit
|
|
331
|
+
*
|
|
332
|
+
* Runs all 9 detectors and returns their findings, each pattern's own
|
|
333
|
+
* findings array sorted by (line ?? last, then location) so ATF's
|
|
334
|
+
* byte-identical-JSON requirement holds regardless of any incidental AST
|
|
335
|
+
* traversal-order variance.
|
|
336
|
+
*/
|
|
337
|
+
export function patternScore(unit) {
|
|
338
|
+
const patterns = {};
|
|
339
|
+
let totalFindings = 0;
|
|
340
|
+
for (const detector of DETECTORS) {
|
|
341
|
+
const { pattern, findings } = detector(unit);
|
|
342
|
+
findings.sort((a, b) => {
|
|
343
|
+
const la = a.line ?? Number.MAX_SAFE_INTEGER;
|
|
344
|
+
const lb = b.line ?? Number.MAX_SAFE_INTEGER;
|
|
345
|
+
return la - lb || a.location.localeCompare(b.location);
|
|
346
|
+
});
|
|
347
|
+
patterns[pattern] = { findings };
|
|
348
|
+
totalFindings += findings.length;
|
|
349
|
+
}
|
|
350
|
+
return { unit: unit.name, kind: unit.kind, patterns, totalFindings };
|
|
351
|
+
}
|