@aslomon/effectum 0.2.0 → 0.3.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.
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Intelligent Setup Recommender — rules-based recommendation engine v1.
3
+ *
4
+ * Derives a complete Claude Code setup from:
5
+ * stack + appType + description + autonomyLevel + language
6
+ *
7
+ * Returns: commands, hooks, skills, mcps, subagents, agentTeams flag.
8
+ */
9
+ "use strict";
10
+
11
+ const { APP_TYPE_TAGS } = require("./app-types");
12
+ const { SUBAGENT_SPECS, STACK_SUBAGENTS } = require("./specializations");
13
+ const { MCP_SERVERS } = require("./constants");
14
+
15
+ // ─── Keyword → Tag mapping ─────────────────────────────────────────────────
16
+
17
+ /**
18
+ * Description keywords mapped to intent tags.
19
+ * @type {Record<string, string[]>}
20
+ */
21
+ const KEYWORD_TAG_MAP = {
22
+ dashboard: ["dashboard", "frontend-heavy", "data-visualization"],
23
+ crm: ["crm", "internal-tool", "auth-needed", "db-needed", "multi-user"],
24
+ admin: ["internal-tool", "auth-needed", "db-needed", "multi-user"],
25
+ auth: ["auth-needed"],
26
+ login: ["auth-needed"],
27
+ signup: ["auth-needed"],
28
+ register: ["auth-needed"],
29
+ api: ["api-first"],
30
+ rest: ["api-first"],
31
+ graphql: ["api-first", "graphql"],
32
+ database: ["db-needed"],
33
+ postgres: ["db-needed", "postgres"],
34
+ supabase: ["supabase", "db-needed"],
35
+ "e-commerce": ["e-commerce", "payments", "auth-needed", "db-needed"],
36
+ ecommerce: ["e-commerce", "payments", "auth-needed", "db-needed"],
37
+ shop: ["e-commerce", "payments", "auth-needed", "db-needed"],
38
+ payment: ["payments", "auth-needed"],
39
+ stripe: ["payments", "auth-needed"],
40
+ chat: ["realtime", "websocket", "multi-user"],
41
+ realtime: ["realtime", "websocket"],
42
+ websocket: ["realtime", "websocket"],
43
+ ai: ["ai-agent"],
44
+ agent: ["ai-agent"],
45
+ llm: ["ai-agent"],
46
+ openai: ["ai-agent"],
47
+ claude: ["ai-agent"],
48
+ bot: ["ai-agent", "automation"],
49
+ automation: ["automation", "background-jobs"],
50
+ workflow: ["automation", "background-jobs"],
51
+ cron: ["automation", "background-jobs"],
52
+ blog: ["content", "seo", "frontend-heavy"],
53
+ cms: ["content", "auth-needed", "db-needed"],
54
+ portfolio: ["content", "seo", "frontend-heavy"],
55
+ landing: ["content", "seo", "frontend-heavy"],
56
+ saas: ["multi-user", "auth-needed", "db-needed", "payments", "multi-tenant"],
57
+ "multi-tenant": ["multi-tenant", "multi-user", "auth-needed"],
58
+ tenant: ["multi-tenant", "multi-user"],
59
+ analytics: ["analytics", "data-visualization", "db-needed"],
60
+ monitoring: ["analytics", "data-visualization", "realtime"],
61
+ docker: ["devops", "automation"],
62
+ kubernetes: ["devops", "automation"],
63
+ ci: ["devops", "automation"],
64
+ deploy: ["devops"],
65
+ test: ["testing-heavy"],
66
+ testing: ["testing-heavy"],
67
+ e2e: ["testing-heavy", "e2e"],
68
+ mobile: ["native-ui", "frontend-heavy"],
69
+ ios: ["native-ui", "swift"],
70
+ android: ["native-ui"],
71
+ react: ["react", "frontend-heavy"],
72
+ next: ["nextjs", "frontend-heavy"],
73
+ nextjs: ["nextjs", "frontend-heavy"],
74
+ python: ["python"],
75
+ fastapi: ["python", "api-first"],
76
+ django: ["python", "frontend-heavy", "db-needed"],
77
+ swift: ["swift", "native-ui"],
78
+ electron: ["desktop", "frontend-heavy"],
79
+ tauri: ["desktop", "frontend-heavy"],
80
+ cli: ["terminal-ui", "scripting"],
81
+ scraper: ["automation", "data-pipeline"],
82
+ crawler: ["automation", "data-pipeline"],
83
+ pipeline: ["data-pipeline", "automation"],
84
+ etl: ["data-pipeline", "automation"],
85
+ ml: ["data-pipeline", "compute-heavy"],
86
+ "machine learning": ["data-pipeline", "compute-heavy"],
87
+ model: ["data-pipeline", "compute-heavy"],
88
+ library: ["api-design", "testing-heavy", "docs-needed"],
89
+ sdk: ["api-design", "testing-heavy", "docs-needed"],
90
+ plugin: ["api-design", "testing-heavy"],
91
+ mcp: ["mcp", "ai-agent"],
92
+ security: ["auth-needed", "security"],
93
+ upload: ["storage", "file-handling"],
94
+ image: ["storage", "file-handling"],
95
+ file: ["storage", "file-handling"],
96
+ notification: ["realtime", "multi-user"],
97
+ email: ["automation", "multi-user"],
98
+ };
99
+
100
+ // ─── Stack → Tag mapping ────────────────────────────────────────────────────
101
+
102
+ /** @type {Record<string, string[]>} */
103
+ const STACK_TAGS = {
104
+ "nextjs-supabase": [
105
+ "nextjs",
106
+ "react",
107
+ "supabase",
108
+ "typescript",
109
+ "frontend-heavy",
110
+ "db-needed",
111
+ "postgres",
112
+ ],
113
+ "python-fastapi": ["python", "api-first", "backend-heavy"],
114
+ "swift-ios": ["swift", "native-ui", "frontend-heavy"],
115
+ generic: [],
116
+ };
117
+
118
+ // ─── Tag extraction ─────────────────────────────────────────────────────────
119
+
120
+ /**
121
+ * Extract intent tags from all inputs.
122
+ * @param {{ appType: string, stack: string, description: string }} input
123
+ * @returns {string[]}
124
+ */
125
+ function extractTags({ appType, stack, description }) {
126
+ const tags = new Set();
127
+
128
+ // App-type tags
129
+ const appTags = APP_TYPE_TAGS[appType] || [];
130
+ appTags.forEach((t) => tags.add(t));
131
+
132
+ // Stack tags
133
+ const stackTags = STACK_TAGS[stack] || [];
134
+ stackTags.forEach((t) => tags.add(t));
135
+
136
+ // Description keyword matching
137
+ const lower = (description || "").toLowerCase();
138
+ for (const [keyword, keyTags] of Object.entries(KEYWORD_TAG_MAP)) {
139
+ if (lower.includes(keyword)) {
140
+ keyTags.forEach((t) => tags.add(t));
141
+ }
142
+ }
143
+
144
+ return [...tags];
145
+ }
146
+
147
+ // ─── Recommendation rules ───────────────────────────────────────────────────
148
+
149
+ /**
150
+ * All available optional commands with the tags that trigger them.
151
+ * @type {Array<{ key: string, label: string, tags: string[], always?: boolean }>}
152
+ */
153
+ const COMMAND_RULES = [
154
+ { key: "plan", label: "/plan", tags: [], always: true },
155
+ { key: "tdd", label: "/tdd", tags: [], always: true },
156
+ { key: "verify", label: "/verify", tags: [], always: true },
157
+ { key: "code-review", label: "/code-review", tags: [], always: true },
158
+ { key: "build-fix", label: "/build-fix", tags: [], always: true },
159
+ { key: "refactor-clean", label: "/refactor-clean", tags: [], always: true },
160
+ { key: "e2e", label: "/e2e", tags: [], always: true },
161
+ { key: "ralph-loop", label: "/ralph-loop", tags: [], always: true },
162
+ { key: "checkpoint", label: "/checkpoint", tags: [], always: true },
163
+ { key: "update-docs", label: "/update-docs", tags: ["docs-needed"] },
164
+ { key: "test-coverage", label: "/test-coverage", tags: ["testing-heavy"] },
165
+ {
166
+ key: "simplify",
167
+ label: "/simplify",
168
+ tags: ["backend-heavy", "frontend-heavy"],
169
+ },
170
+ ];
171
+
172
+ /**
173
+ * Optional hooks (beyond foundation) with trigger tags.
174
+ * @type {Array<{ key: string, label: string, tags: string[], always?: boolean }>}
175
+ */
176
+ const HOOK_RULES = [
177
+ { key: "commit-gate", label: "Commit Message Gate", tags: [], always: true },
178
+ {
179
+ key: "changelog-update",
180
+ label: "CHANGELOG Auto-Update",
181
+ tags: [],
182
+ always: true,
183
+ },
184
+ {
185
+ key: "completion-verifier",
186
+ label: "Completion Verifier",
187
+ tags: [],
188
+ always: true,
189
+ },
190
+ {
191
+ key: "subagent-verifier",
192
+ label: "Subagent Quality Gate",
193
+ tags: [],
194
+ always: true,
195
+ },
196
+ {
197
+ key: "desktop-notifications",
198
+ label: "Desktop Notifications",
199
+ tags: [],
200
+ always: true,
201
+ },
202
+ ];
203
+
204
+ /**
205
+ * Skills with trigger tags and mandatory-per-stack rules.
206
+ * @type {Array<{ key: string, label: string, tags: string[], mandatoryForStacks?: string[] }>}
207
+ */
208
+ const SKILL_RULES = [
209
+ {
210
+ key: "frontend-design",
211
+ label: "Frontend Design",
212
+ tags: ["frontend-heavy", "ui-design"],
213
+ mandatoryForStacks: ["nextjs-supabase"],
214
+ },
215
+ {
216
+ key: "security-check",
217
+ label: "Security Check",
218
+ tags: ["auth-needed", "multi-user", "payments", "security"],
219
+ },
220
+ {
221
+ key: "webapp-testing",
222
+ label: "Web App Testing",
223
+ tags: ["frontend-heavy", "e2e"],
224
+ mandatoryForStacks: ["nextjs-supabase"],
225
+ },
226
+ {
227
+ key: "doc-coauthoring",
228
+ label: "Doc Co-Authoring",
229
+ tags: ["docs-needed"],
230
+ },
231
+ {
232
+ key: "claude-api",
233
+ label: "Claude API",
234
+ tags: ["ai-agent"],
235
+ },
236
+ {
237
+ key: "mcp-builder",
238
+ label: "MCP Builder",
239
+ tags: ["mcp", "ai-agent"],
240
+ },
241
+ ];
242
+
243
+ /**
244
+ * MCP server trigger rules.
245
+ * @type {Array<{ key: string, tags: string[], always?: boolean }>}
246
+ */
247
+ const MCP_RULES = [
248
+ { key: "context7", tags: [], always: true },
249
+ { key: "playwright", tags: [], always: true },
250
+ { key: "sequential-thinking", tags: [], always: true },
251
+ { key: "filesystem", tags: ["file-handling", "storage"] },
252
+ ];
253
+
254
+ // ─── Core recommendation functions ──────────────────────────────────────────
255
+
256
+ /**
257
+ * Recommend commands based on tags.
258
+ * @param {string[]} tags
259
+ * @returns {string[]}
260
+ */
261
+ function recommendCommands(tags) {
262
+ const tagSet = new Set(tags);
263
+ return COMMAND_RULES.filter(
264
+ (r) => r.always || r.tags.some((t) => tagSet.has(t)),
265
+ ).map((r) => r.key);
266
+ }
267
+
268
+ /**
269
+ * Recommend optional hooks based on tags.
270
+ * @param {string[]} tags
271
+ * @returns {string[]}
272
+ */
273
+ function recommendHooks(tags) {
274
+ const tagSet = new Set(tags);
275
+ return HOOK_RULES.filter(
276
+ (r) => r.always || r.tags.some((t) => tagSet.has(t)),
277
+ ).map((r) => r.key);
278
+ }
279
+
280
+ /**
281
+ * Recommend skills based on tags and stack.
282
+ * @param {string[]} tags
283
+ * @param {string} stack
284
+ * @returns {string[]}
285
+ */
286
+ function recommendSkills(tags, stack) {
287
+ const tagSet = new Set(tags);
288
+ return SKILL_RULES.filter(
289
+ (r) =>
290
+ r.tags.some((t) => tagSet.has(t)) ||
291
+ (r.mandatoryForStacks && r.mandatoryForStacks.includes(stack)),
292
+ ).map((r) => r.key);
293
+ }
294
+
295
+ /**
296
+ * Recommend MCP servers based on tags.
297
+ * @param {string[]} tags
298
+ * @returns {string[]}
299
+ */
300
+ function recommendMcps(tags) {
301
+ const tagSet = new Set(tags);
302
+ return MCP_RULES.filter((r) => r.always || r.tags.some((t) => tagSet.has(t)))
303
+ .map((r) => r.key)
304
+ .filter((key) => MCP_SERVERS.some((s) => s.key === key));
305
+ }
306
+
307
+ /**
308
+ * Recommend subagent specializations based on tags and stack.
309
+ * @param {string[]} tags
310
+ * @param {string} stack
311
+ * @returns {string[]}
312
+ */
313
+ function recommendSubagents(tags, stack) {
314
+ const tagSet = new Set(tags);
315
+ const result = new Set();
316
+
317
+ // Stack base recommendations
318
+ const stackBase = STACK_SUBAGENTS[stack] || STACK_SUBAGENTS.generic;
319
+ stackBase.forEach((s) => result.add(s));
320
+
321
+ // Tag-based additions
322
+ for (const spec of SUBAGENT_SPECS) {
323
+ if (spec.tags.some((t) => tagSet.has(t))) {
324
+ result.add(spec.key);
325
+ }
326
+ }
327
+
328
+ return [...result];
329
+ }
330
+
331
+ // ─── Main recommendation entry point ────────────────────────────────────────
332
+
333
+ /**
334
+ * Generate a complete recommended setup from user inputs.
335
+ *
336
+ * @param {{ stack: string, appType: string, description: string, autonomyLevel: string, language: string }} input
337
+ * @returns {{ commands: string[], hooks: string[], skills: string[], mcps: string[], subagents: string[], agentTeams: boolean, tags: string[] }}
338
+ */
339
+ function recommend({ stack, appType, description, autonomyLevel, language }) {
340
+ const tags = extractTags({ appType, stack, description });
341
+
342
+ return {
343
+ commands: recommendCommands(tags),
344
+ hooks: recommendHooks(tags),
345
+ skills: recommendSkills(tags, stack),
346
+ mcps: recommendMcps(tags),
347
+ subagents: recommendSubagents(tags, stack),
348
+ agentTeams: false,
349
+ tags,
350
+ };
351
+ }
352
+
353
+ // ─── Catalog accessors (for manual/customize mode) ──────────────────────────
354
+
355
+ /** Get all available commands for manual selection. */
356
+ function getAllCommands() {
357
+ return COMMAND_RULES.map((r) => ({ key: r.key, label: r.label }));
358
+ }
359
+
360
+ /** Get all available optional hooks for manual selection. */
361
+ function getAllHooks() {
362
+ return HOOK_RULES.map((r) => ({ key: r.key, label: r.label }));
363
+ }
364
+
365
+ /** Get all available skills for manual selection. */
366
+ function getAllSkills() {
367
+ return SKILL_RULES.map((r) => ({ key: r.key, label: r.label }));
368
+ }
369
+
370
+ /** Get all available MCP servers for manual selection. */
371
+ function getAllMcps() {
372
+ return MCP_SERVERS.map((s) => ({ key: s.key, label: s.label, desc: s.desc }));
373
+ }
374
+
375
+ /** Get all available subagent specializations for manual selection. */
376
+ function getAllSubagents() {
377
+ return SUBAGENT_SPECS.map((s) => ({ key: s.key, label: s.label }));
378
+ }
379
+
380
+ module.exports = {
381
+ recommend,
382
+ extractTags,
383
+ getAllCommands,
384
+ getAllHooks,
385
+ getAllSkills,
386
+ getAllMcps,
387
+ getAllSubagents,
388
+ COMMAND_RULES,
389
+ HOOK_RULES,
390
+ SKILL_RULES,
391
+ MCP_RULES,
392
+ };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Subagent specialization definitions and stack/app-type mappings.
3
+ * Drives the recommendation engine's subagent suggestions.
4
+ */
5
+ "use strict";
6
+
7
+ /**
8
+ * All available subagent specializations with the tags they match.
9
+ * @type {Array<{ key: string, label: string, tags: string[] }>}
10
+ */
11
+ const SUBAGENT_SPECS = [
12
+ {
13
+ key: "frontend-developer",
14
+ label: "Frontend Developer",
15
+ tags: ["frontend-heavy", "ui-design"],
16
+ },
17
+ {
18
+ key: "backend-developer",
19
+ label: "Backend Developer",
20
+ tags: ["backend-heavy", "api-first", "db-needed"],
21
+ },
22
+ {
23
+ key: "fullstack-developer",
24
+ label: "Fullstack Developer",
25
+ tags: ["frontend-heavy", "db-needed"],
26
+ },
27
+ {
28
+ key: "react-specialist",
29
+ label: "React Specialist",
30
+ tags: ["nextjs", "react"],
31
+ },
32
+ {
33
+ key: "nextjs-developer",
34
+ label: "Next.js Developer",
35
+ tags: ["nextjs"],
36
+ },
37
+ {
38
+ key: "postgres-pro",
39
+ label: "PostgreSQL Pro",
40
+ tags: ["db-needed", "supabase", "postgres"],
41
+ },
42
+ {
43
+ key: "security-engineer",
44
+ label: "Security Engineer",
45
+ tags: ["auth-needed", "multi-user", "payments"],
46
+ },
47
+ {
48
+ key: "test-automator",
49
+ label: "Test Automator",
50
+ tags: ["testing-heavy"],
51
+ },
52
+ {
53
+ key: "api-designer",
54
+ label: "API Designer",
55
+ tags: ["api-first", "api-design"],
56
+ },
57
+ {
58
+ key: "ui-designer",
59
+ label: "UI Designer",
60
+ tags: ["native-ui", "ui-design"],
61
+ },
62
+ {
63
+ key: "debugger",
64
+ label: "Debugger",
65
+ tags: ["data-pipeline", "compute-heavy"],
66
+ },
67
+ {
68
+ key: "performance-engineer",
69
+ label: "Performance Engineer",
70
+ tags: ["compute-heavy", "data-pipeline"],
71
+ },
72
+ {
73
+ key: "devops-engineer",
74
+ label: "DevOps Engineer",
75
+ tags: ["automation", "background-jobs", "devops"],
76
+ },
77
+ {
78
+ key: "docker-expert",
79
+ label: "Docker Expert",
80
+ tags: ["automation", "devops"],
81
+ },
82
+ {
83
+ key: "typescript-pro",
84
+ label: "TypeScript Pro",
85
+ tags: ["nextjs", "typescript"],
86
+ },
87
+ {
88
+ key: "mcp-developer",
89
+ label: "MCP Developer",
90
+ tags: ["ai-agent", "mcp"],
91
+ },
92
+ {
93
+ key: "code-reviewer",
94
+ label: "Code Reviewer",
95
+ tags: ["testing-heavy", "docs-needed"],
96
+ },
97
+ ];
98
+
99
+ /**
100
+ * Stack-specific base subagent recommendations.
101
+ * These are always included for the given stack, regardless of tags.
102
+ * @type {Record<string, string[]>}
103
+ */
104
+ const STACK_SUBAGENTS = {
105
+ "nextjs-supabase": [
106
+ "frontend-developer",
107
+ "backend-developer",
108
+ "postgres-pro",
109
+ "security-engineer",
110
+ "test-automator",
111
+ ],
112
+ "python-fastapi": [
113
+ "backend-developer",
114
+ "debugger",
115
+ "security-engineer",
116
+ "test-automator",
117
+ "api-designer",
118
+ ],
119
+ "swift-ios": ["ui-designer", "test-automator"],
120
+ generic: ["debugger", "test-automator"],
121
+ };
122
+
123
+ module.exports = { SUBAGENT_SPECS, STACK_SUBAGENTS };
@@ -7,7 +7,8 @@
7
7
 
8
8
  const fs = require("fs");
9
9
  const path = require("path");
10
- const { FORMATTER_MAP, LANGUAGE_INSTRUCTIONS } = require("./constants");
10
+ const { FORMATTER_MAP } = require("./constants");
11
+ const { LANGUAGE_INSTRUCTIONS } = require("./languages");
11
12
 
12
13
  /**
13
14
  * Build a substitution map from user config and parsed stack sections.