openprompt-lang 0.3.0 → 0.4.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 (90) hide show
  1. package/README.md +483 -32
  2. package/bin/cli.js +232 -41
  3. package/bin/create.js +135 -0
  4. package/bin/lint.js +20 -21
  5. package/docs/COMMANDS.md +200 -127
  6. package/docs/COMMITS/INDEX.md +1 -0
  7. package/docs/PROMPT_AI_CONTEXT.md +99 -0
  8. package/docs/langs/dotnet.md +36 -0
  9. package/docs/langs/java-spring.md +45 -0
  10. package/docs/langs/python-fastapi.md +35 -0
  11. package/docs/langs/unity.md +30 -0
  12. package/docs/langs/vue-nuxt.md +36 -0
  13. package/package.json +31 -3
  14. package/scaffolds/.cursorrules +6 -0
  15. package/scaffolds/AGENTS.md +27 -0
  16. package/scaffolds/Dockerfile +11 -0
  17. package/scaffolds/capacitor.config.ts +17 -0
  18. package/scaffolds/netlify.toml +8 -0
  19. package/scaffolds/prompt-lang.json +15 -0
  20. package/scaffolds/railway.json +12 -0
  21. package/scaffolds/tailwind.config.js +8 -0
  22. package/scaffolds/tauri.conf.json +26 -0
  23. package/schemas/language-module.json +116 -0
  24. package/schemas/prompt-lang.json +38 -3
  25. package/schemas/structures.json +145 -0
  26. package/src/ai/prompt-builder.js +184 -0
  27. package/src/ai/providers.js +247 -0
  28. package/src/annotations/registry.js +39 -0
  29. package/src/annotations/tags.json +24 -0
  30. package/src/commands/ai-gen.js +161 -0
  31. package/src/commands/component.js +242 -212
  32. package/src/commands/context.js +184 -109
  33. package/src/commands/extract.js +242 -0
  34. package/src/commands/figma.js +15 -15
  35. package/src/commands/init.js +197 -93
  36. package/src/commands/integrate.js +406 -0
  37. package/src/commands/lang.js +148 -0
  38. package/src/commands/qa-gen.js +139 -0
  39. package/src/commands/scaffold.js +127 -0
  40. package/src/commands/suggest.js +24 -14
  41. package/src/commands/teach.js +110 -0
  42. package/src/commands/validate.js +143 -83
  43. package/src/commands/wizard.js +456 -0
  44. package/src/generators/figma-prompt.js +20 -12
  45. package/src/language-service/plugin.cjs +94 -0
  46. package/src/language-service/plugin.d.ts +6 -0
  47. package/src/mcp-server.js +605 -0
  48. package/src/templates/langs/react/INDEX.json +262 -0
  49. package/src/templates/langs/react/MODULE.json +166 -0
  50. package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
  51. package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
  52. package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
  53. package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
  54. package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
  55. package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
  56. package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
  57. package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
  58. package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
  59. package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
  60. package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
  61. package/src/templates/langs/vue/INDEX.json +246 -0
  62. package/src/templates/langs/vue/MODULE.json +105 -0
  63. package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
  64. package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
  65. package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
  66. package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
  67. package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
  68. package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
  69. package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
  70. package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
  71. package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
  72. package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
  73. package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
  74. package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
  75. package/src/templates/scripts/log-actividad.sh +32 -0
  76. package/src/templates/scripts/log-commit.sh +35 -0
  77. package/src/templates/scripts/log-error.sh +45 -0
  78. package/src/templates/scripts/validate.sh +23 -0
  79. package/src/ts-transformer/index.cjs +86 -0
  80. package/src/utils/ai.js +35 -53
  81. package/src/utils/annotations.js +260 -214
  82. package/src/utils/config.js +61 -13
  83. package/src/utils/error-learner.js +203 -0
  84. package/src/utils/file-utils.js +119 -0
  85. package/src/utils/language-loader.js +167 -0
  86. package/src/utils/template-utils.js +45 -0
  87. package/src/vite-plugin/index.js +54 -0
  88. package/vscode-extension/package.json +23 -2
  89. package/vscode-extension/snippets/promptlang.json +1 -3
  90. package/vscode-extension/syntaxes/annotations-code.tmGrammar.json +15 -0
@@ -0,0 +1,605 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, readdirSync, existsSync, statSync } from "fs"
4
+ import { join, extname } from "path"
5
+ import { lintFile } from "./utils/annotations.js"
6
+ import { loadConfig } from "./utils/config.js"
7
+ import {
8
+ searchTemplates,
9
+ getTemplate,
10
+ getLanguage,
11
+ getLanguageIndex,
12
+ } from "./utils/language-loader.js"
13
+ import { scanForLearnErrors, generateTests, writeTestFile } from "./utils/error-learner.js"
14
+ import { scaffoldFolders } from "./commands/scaffold.js"
15
+ import { extractLesson, extractTags } from "./utils/template-utils.js"
16
+ import { findAllFiles, countFiles } from "./utils/file-utils.js"
17
+
18
+ const SERVER_INFO = {
19
+ name: "openPrompt-Lang",
20
+ version: "0.3.0",
21
+ }
22
+
23
+ const TOOLS = [
24
+ {
25
+ name: "validate",
26
+ description:
27
+ "[PRE-COMMIT CHECK] Scan project for annotation errors. Use BEFORE commits or when troubleshooting annotation warnings. Returns file-by-file errors and warnings. If errors found, use lint_file on specific files to debug.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ filePath: { type: "string", description: "Path to file to validate" },
32
+ dir: { type: "string", description: "Directory to scan for files (default: cwd)" },
33
+ },
34
+ },
35
+ },
36
+ {
37
+ name: "lint_file",
38
+ description:
39
+ "[FILE LINTING] Debug a single file's annotations in detail. Returns parsed annotations, errors, and warnings. More detailed than validate for individual files. Use strict mode for thorough checking.",
40
+ inputSchema: {
41
+ type: "object",
42
+ properties: {
43
+ filePath: { type: "string", description: "Path to the source file" },
44
+ strict: { type: "boolean", description: "Enable strict mode (warnings become errors)" },
45
+ },
46
+ required: ["filePath"],
47
+ },
48
+ },
49
+ {
50
+ name: "parse_code",
51
+ description:
52
+ "[QUICK PARSE] Validate annotation syntax inline without reading from disk. Use during code generation or editing to verify annotation syntax before writing to file.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ code: { type: "string", description: "Source code containing @annotations" },
57
+ },
58
+ required: ["code"],
59
+ },
60
+ },
61
+ {
62
+ name: "context",
63
+ description:
64
+ "[CONTEXT EXTRACTION] Extract project structure with config summary and annotated file list. Use when you need a bird's-eye view of the project or before generating documentation.",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {
68
+ dir: { type: "string", description: "Project directory (default: cwd)" },
69
+ scope: { type: "string", description: "Filter by @scope module name" },
70
+ },
71
+ },
72
+ },
73
+ {
74
+ name: "search_templates",
75
+ description:
76
+ "[TEMPLATE DISCOVERY] Find relevant templates before using teach_template. Searches by keyword (button, auth, fetch, form). Returns matching templates with descriptions and metadata.",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ query: { type: "string", description: "Search query" },
81
+ lang: { type: "string", description: "Language module (react, vue)" },
82
+ },
83
+ required: ["query"],
84
+ },
85
+ },
86
+ {
87
+ name: "generate_tests",
88
+ description:
89
+ "[TEST GENERATION] Generate Vitest test cases from @learn-error annotations. AFTER documenting a bug with @learn-error, run this to create regression tests. Optionally write to file with output parameter.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ sourceDir: { type: "string", description: "Source directory to scan (default: cwd)" },
94
+ lang: { type: "string", description: "Language module (react, vue)" },
95
+ output: { type: "string", description: "Output path for generated test file (optional)" },
96
+ },
97
+ },
98
+ },
99
+ {
100
+ name: "teach_template",
101
+ description:
102
+ "[LEARN PATTERNS] Show structured lesson and code from a template. Use when implementing a known pattern (button, auth, form, fetch). Takes templateId like 'button-shadcn' or 'hook-useAuth'. Returns lesson, best practices, fix history, and optionally the code. Run search_templates first to find relevant templates.",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ templateId: {
107
+ type: "string",
108
+ description: "Template ID to teach (e.g. button-shadcn, hook-useAuth)",
109
+ },
110
+ lang: { type: "string", description: "Language module (react, vue)" },
111
+ showCode: { type: "boolean", description: "Include full template code in output" },
112
+ },
113
+ required: ["templateId"],
114
+ },
115
+ },
116
+ {
117
+ name: "analyze_project",
118
+ description:
119
+ "[PROJECT AUDIT] First tool to run when starting work on an unknown project. Returns config summary, file counts by type, and annotation stats. Use to understand project structure before making changes.",
120
+ inputSchema: {
121
+ type: "object",
122
+ properties: {
123
+ dir: { type: "string", description: "Project directory (default: cwd)" },
124
+ },
125
+ },
126
+ },
127
+ {
128
+ name: "scaffold_project",
129
+ description:
130
+ "[PROJECT SETUP] Create project folder structure and scaffold files. Use when initializing a new project or adding a module. Takes framework id (react, vue). Creates folders, .gitignore, prompt-lang.json, AGENTS.md.",
131
+ inputSchema: {
132
+ type: "object",
133
+ properties: {
134
+ framework: { type: "string", description: "Framework/language id (react, vue)" },
135
+ name: { type: "string", description: "Project subdirectory name (optional)" },
136
+ force: { type: "boolean", description: "Overwrite existing files" },
137
+ },
138
+ required: ["framework"],
139
+ },
140
+ },
141
+ ]
142
+
143
+ function sendMessage(msg) {
144
+ const data = JSON.stringify(msg)
145
+ process.stdout.write(data + "\n")
146
+ }
147
+
148
+ function sendError(id, code, message) {
149
+ sendMessage({ jsonrpc: "2.0", id, error: { code, message } })
150
+ }
151
+
152
+ function sendResult(id, result) {
153
+ sendMessage({ jsonrpc: "2.0", id, result })
154
+ }
155
+
156
+ function formatResult(type, summary, details, actions) {
157
+ const parts = [`## Result: ${type}`, "", summary]
158
+ if (details && details.length) {
159
+ parts.push("", "### Details", ...details.map((d) => `- ${d}`))
160
+ }
161
+ if (actions && actions.length) {
162
+ parts.push("", "### Recommended Actions", ...actions.map((a) => `- ${a}`))
163
+ }
164
+ return parts.join("\n")
165
+ }
166
+
167
+ function toolResult(text, isError = false) {
168
+ return { content: [{ type: "text", text }], isError }
169
+ }
170
+
171
+ async function handleToolCall(name, args) {
172
+ switch (name) {
173
+ case "validate": {
174
+ const dir = args?.dir || args?.filePath || process.cwd()
175
+ const target = join(process.cwd(), dir)
176
+ if (!existsSync(target)) return toolResult(`Path not found: ${target}`, true)
177
+
178
+ // If target is a directory, scan for annotated files
179
+ if (statSync(target).isDirectory()) {
180
+ const allFiles = []
181
+ function walk(d) {
182
+ let entries
183
+ try {
184
+ entries = readdirSync(d, { withFileTypes: true })
185
+ } catch {
186
+ return
187
+ }
188
+ for (const e of entries) {
189
+ if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "dist") continue
190
+ const p = join(d, e.name)
191
+ if (e.isDirectory()) walk(p)
192
+ else if (e.isFile() && /\.(ts|tsx|js|jsx)$/.test(e.name)) allFiles.push(p)
193
+ }
194
+ }
195
+ walk(target)
196
+ if (allFiles.length === 0)
197
+ return toolResult(
198
+ formatResult(
199
+ "info",
200
+ "No source files found in directory.",
201
+ [`Scanned: ${target}`],
202
+ ["Run scaffold_project to initialize project structure."]
203
+ )
204
+ )
205
+ let totalAnn = 0,
206
+ totalErr = 0,
207
+ totalWarn = 0
208
+ const details = []
209
+ for (const f of allFiles) {
210
+ const code = readFileSync(f, "utf-8")
211
+ const result = lintFile(code)
212
+ totalAnn += result.annotations.length
213
+ totalErr += result.errors.length
214
+ totalWarn += result.warnings.length
215
+ if (result.errors.length || result.warnings.length) {
216
+ const rel = f.replace(target + "/", "")
217
+ for (const e of result.errors) details.push(`❌ ${rel}: ${e}`)
218
+ for (const w of result.warnings) details.push(`⚠ ${rel}: ${w}`)
219
+ }
220
+ }
221
+ const status = totalErr > 0 ? "error" : totalWarn > 0 ? "warning" : "success"
222
+ const actions = []
223
+ if (totalErr > 0) actions.push("Fix errors listed above, then run validate again")
224
+ if (totalWarn > 0) actions.push("Review warnings — some may indicate potential issues")
225
+ if (totalErr === 0 && totalWarn === 0)
226
+ actions.push("Project annotations are valid. Ready for commit.")
227
+ return toolResult(
228
+ formatResult(
229
+ status,
230
+ `Scanned ${allFiles.length} files: ${totalAnn} annotations, ${totalErr} errors, ${totalWarn} warnings`,
231
+ details.length ? details : [`✅ All annotations valid across ${allFiles.length} files`],
232
+ actions
233
+ )
234
+ )
235
+ }
236
+
237
+ const result = lintFile(readFileSync(target, "utf-8"))
238
+ const status =
239
+ result.errors.length > 0 ? "error" : result.warnings.length > 0 ? "warning" : "success"
240
+ const actions = []
241
+ if (result.errors.length > 0) actions.push("Fix errors, then re-run validate on this file")
242
+ if (result.warnings.length > 0) actions.push("Review warnings before committing")
243
+ if (result.errors.length === 0 && result.warnings.length === 0)
244
+ actions.push("File is valid. No issues found.")
245
+ return toolResult(
246
+ formatResult(
247
+ status,
248
+ `File: ${args?.filePath || target}: ${result.annotations.length} annotations, ${result.errors.length} errors, ${result.warnings.length} warnings`,
249
+ [...result.errors.map((e) => `❌ ${e}`), ...result.warnings.map((w) => `⚠ ${w}`)],
250
+ actions
251
+ )
252
+ )
253
+ }
254
+
255
+ case "lint_file": {
256
+ const filePath = join(process.cwd(), args.filePath)
257
+ if (!existsSync(filePath))
258
+ return toolResult(
259
+ formatResult(
260
+ "error",
261
+ "File not found",
262
+ [`Path: ${filePath}`],
263
+ ["Verify the file path and try again"]
264
+ ),
265
+ true
266
+ )
267
+ const code = readFileSync(filePath, "utf-8")
268
+ const result = lintFile(code, args.strict)
269
+ const status =
270
+ result.errors.length > 0 ? "error" : result.warnings.length > 0 ? "warning" : "success"
271
+ return toolResult(
272
+ formatResult(
273
+ status,
274
+ `${args.filePath}: ${result.annotations.length} annotations`,
275
+ [
276
+ ...result.annotations.map((a) => `@${a.name}(${a.raw})`),
277
+ ...result.errors.map((e) => `❌ ${e}`),
278
+ ...result.warnings.map((w) => `⚠ ${w}`),
279
+ ],
280
+ result.errors.length
281
+ ? ["Fix errors and re-run lint_file"]
282
+ : result.warnings.length
283
+ ? ["Review warnings"]
284
+ : ["File is valid"]
285
+ )
286
+ )
287
+ }
288
+
289
+ case "parse_code": {
290
+ if (!args?.code)
291
+ return toolResult(
292
+ formatResult(
293
+ "error",
294
+ "No code provided",
295
+ [],
296
+ ["Pass a 'code' parameter with annotation source text"]
297
+ ),
298
+ true
299
+ )
300
+ const result = lintFile(args.code)
301
+ const status =
302
+ result.errors.length > 0 ? "error" : result.warnings.length > 0 ? "warning" : "success"
303
+ return toolResult(
304
+ formatResult(
305
+ status,
306
+ `${result.annotations.length} annotations found`,
307
+ [
308
+ ...result.annotations.map((a) => `@${a.name}(${a.raw})`),
309
+ ...result.errors.map((e) => `❌ ${e}`),
310
+ ...result.warnings.map((w) => `⚠ ${w}`),
311
+ ],
312
+ result.errors.length ? ["Fix annotation syntax"] : ["Annotation syntax is valid"]
313
+ )
314
+ )
315
+ }
316
+
317
+ case "context": {
318
+ const dir = args?.dir || process.cwd()
319
+ const cwd = join(process.cwd(), dir)
320
+ let config = null
321
+ try {
322
+ config = loadConfig(cwd)
323
+ } catch {}
324
+ const details = [`Directory: ${dir}`, `Config: ${config ? "found" : "none"}`]
325
+ if (config) {
326
+ details.push(
327
+ `Name: ${config.name || "unnamed"}`,
328
+ `Language: ${config.lenguaje || "unknown"}`,
329
+ `Stack: ${(config.stack?.base || []).join(", ")}`
330
+ )
331
+ }
332
+ const actions = config
333
+ ? ["Run validate to check annotation health"]
334
+ : ["Run init or scaffold_project to create project config"]
335
+ return toolResult(formatResult("success", "Project context extracted", details, actions))
336
+ }
337
+
338
+ case "search_templates": {
339
+ const lang = args?.lang || "react"
340
+ const query = args?.query || ""
341
+ const results = searchTemplates(query, { lang })
342
+ if (!results.length)
343
+ return toolResult(
344
+ formatResult(
345
+ "info",
346
+ `No templates match "${query}" in ${lang}`,
347
+ [],
348
+ ["Try a different query or check available templates with lang list"]
349
+ )
350
+ )
351
+ return toolResult(
352
+ formatResult(
353
+ "success",
354
+ `${results.length} templates found for "${query}" in ${lang}`,
355
+ results.map(
356
+ (t, i) => `${i + 1}. ${t.id}: ${t.description || "no description"} — ${t.category}`
357
+ ),
358
+ [
359
+ "Run teach_template with a template ID to learn more",
360
+ "Use showCode=true in teach_template to see the implementation",
361
+ ]
362
+ )
363
+ )
364
+ }
365
+
366
+ case "generate_tests": {
367
+ const sourceDir = args?.sourceDir || process.cwd()
368
+ const lang = args?.lang || "react"
369
+ const dir = join(process.cwd(), sourceDir)
370
+ if (!existsSync(dir))
371
+ return toolResult(formatResult("error", "Directory not found", [`Path: ${dir}`], []), true)
372
+ const errors = scanForLearnErrors(dir, lang)
373
+ if (!errors.length)
374
+ return toolResult(
375
+ formatResult(
376
+ "info",
377
+ "No @learn-error annotations found",
378
+ [`Scanned: ${dir}`],
379
+ ["After fixing a bug, add @learn-error to the file and re-run generate_tests"]
380
+ )
381
+ )
382
+ const tests = generateTests(errors, lang)
383
+ const details = [`Learned errors found: ${errors.length}`, `Tests generated: ${tests.length}`]
384
+ if (args?.output) {
385
+ const outPath = join(process.cwd(), args.output)
386
+ writeTestFile(errors, outPath, lang)
387
+ details.push(`Written to: ${args.output}`)
388
+ }
389
+ const actions = [
390
+ args?.output
391
+ ? `Run: npx vitest run ${args.output}`
392
+ : "Pass 'output' parameter to write test file to disk",
393
+ ]
394
+ return toolResult(
395
+ formatResult(
396
+ "success",
397
+ `Generated ${tests.length} tests from ${errors.length} learned errors`,
398
+ [...details, "", ...tests],
399
+ actions
400
+ )
401
+ )
402
+ }
403
+
404
+ case "teach_template": {
405
+ const lang = args?.lang || "react"
406
+ const template = getTemplate(args.templateId, lang)
407
+ if (!template)
408
+ return toolResult(
409
+ formatResult(
410
+ "error",
411
+ `Template "${args.templateId}" not found in ${lang}`,
412
+ [],
413
+ [`Run search_templates with keywords to find available templates in ${lang}`]
414
+ ),
415
+ true
416
+ )
417
+ const langObj = getLanguage(lang)
418
+ const meta = template.metadata
419
+ const details = [
420
+ `${meta.name}: ${meta.description || ""}`,
421
+ `Category: ${meta.category}`,
422
+ `Purposes: ${meta.purposes?.join(", ") || "general"}`,
423
+ ]
424
+ if (meta.variants?.length) details.push(`Variants: ${meta.variants.join(", ")}`)
425
+ if (meta.sizes?.length) details.push(`Sizes: ${meta.sizes.join(", ")}`)
426
+ details.push(
427
+ `Dark mode: ${meta.darkMode ? "yes" : "no"}`,
428
+ `Responsive: ${meta.responsive ? "yes" : "no"}`,
429
+ `Deps: ${meta.dependencies?.join(", ") || "none"}`
430
+ )
431
+ if (meta.hasTeachMe) {
432
+ const lesson = extractLesson(template.content)
433
+ if (lesson) details.push("", "--- LESSON ---", lesson)
434
+ }
435
+ const goodP = extractTags(template.content, "@goodPractice")
436
+ const badP = extractTags(template.content, "@badPractice")
437
+ if (goodP.length) details.push("", "--- GOOD PRACTICES ---", ...goodP.map((g) => `✅ ${g}`))
438
+ if (badP.length) details.push("", "--- BAD PRACTICES ---", ...badP.map((b) => `❌ ${b}`))
439
+ if (args?.showCode) {
440
+ const clean = template.content
441
+ .split("\n")
442
+ .filter((l) => !l.trim().startsWith("// @"))
443
+ .join("\n")
444
+ details.push("", "--- CODE ---", clean)
445
+ }
446
+ const actions = [
447
+ "Use this pattern in your project",
448
+ `Run scaffold_project to set up the project structure`,
449
+ ]
450
+ return toolResult(
451
+ formatResult("success", `${meta.name} — ${langObj?.name || lang}`, details, actions)
452
+ )
453
+ }
454
+
455
+ case "analyze_project": {
456
+ const dir = args?.dir || process.cwd()
457
+ const cwd = join(process.cwd(), dir)
458
+ if (!existsSync(cwd))
459
+ return toolResult(formatResult("error", "Directory not found", [`Path: ${cwd}`], []), true)
460
+ let config = null
461
+ try {
462
+ config = loadConfig(cwd)
463
+ } catch {}
464
+ const counts = countFiles(cwd)
465
+ const allFiles = findAllFiles(cwd)
466
+ const annResults = []
467
+ for (const f of allFiles) {
468
+ try {
469
+ const code = readFileSync(f, "utf-8")
470
+ const r = lintFile(code)
471
+ if (r.annotations.length || r.errors.length || r.warnings.length) {
472
+ annResults.push({ file: f.replace(cwd + "/", ""), ...r })
473
+ }
474
+ } catch {}
475
+ }
476
+ const details = [
477
+ `Config: ${config ? "found" : "none"}`,
478
+ `Name: ${config?.name || "unnamed"}`,
479
+ `Language: ${config?.lenguaje || "unknown"}`,
480
+ `Stack: ${(config?.stack?.base || []).join(", ")}`,
481
+ "",
482
+ `Total files: ${counts.total}`,
483
+ `.ts: ${counts.ts} | .tsx: ${counts.tsx} | .js: ${counts.js} | .jsx: ${counts.jsx} | .css: ${counts.css}`,
484
+ "",
485
+ `Files with annotations: ${annResults.length}`,
486
+ ...annResults.map(
487
+ (r) =>
488
+ `${r.file}: ${r.annotations.length} annotations, ${r.errors.length} errors, ${r.warnings.length} warnings`
489
+ ),
490
+ ]
491
+ const actions = config
492
+ ? [
493
+ "Run validate to check annotation consistency",
494
+ "Run search_templates to find relevant patterns",
495
+ ]
496
+ : ["Run init to set up project configuration"]
497
+ return toolResult(formatResult("success", `Project analysis: ${dir}`, details, actions))
498
+ }
499
+
500
+ case "scaffold_project": {
501
+ const output = await scaffoldFolders(args.framework, {
502
+ name: args?.name,
503
+ force: args?.force,
504
+ })
505
+ if (!output)
506
+ return toolResult(
507
+ formatResult(
508
+ "error",
509
+ `Failed to scaffold "${args.framework}"`,
510
+ [],
511
+ ["Verify the framework id is valid (react, vue)"]
512
+ ),
513
+ true
514
+ )
515
+ const actions = [
516
+ "Run validate to verify structure",
517
+ "Run search_templates to find relevant patterns for your first component",
518
+ ]
519
+ return toolResult(
520
+ formatResult(
521
+ "success",
522
+ `Scaffold "${args.framework}" created`,
523
+ [`Location: ${output}`],
524
+ actions
525
+ )
526
+ )
527
+ }
528
+
529
+ default:
530
+ return toolResult(`Unknown tool: ${name}`, true)
531
+ }
532
+ }
533
+
534
+ export function startServer() {
535
+ let buffer = ""
536
+
537
+ process.stdin.setEncoding("utf-8")
538
+ process.stdin.on("data", async (chunk) => {
539
+ buffer += chunk
540
+ const lines = buffer.split("\n")
541
+ buffer = lines.pop() || ""
542
+
543
+ for (const line of lines) {
544
+ const trimmed = line.trim()
545
+ if (!trimmed) continue
546
+
547
+ let msg
548
+ try {
549
+ msg = JSON.parse(trimmed)
550
+ } catch {
551
+ continue
552
+ }
553
+
554
+ if (msg.jsonrpc !== "2.0") continue
555
+
556
+ const { id, method, params } = msg
557
+
558
+ switch (method) {
559
+ case "initialize": {
560
+ sendResult(id, {
561
+ protocolVersion: "1.0",
562
+ capabilities: { tools: {} },
563
+ serverInfo: SERVER_INFO,
564
+ })
565
+ break
566
+ }
567
+
568
+ case "tools/list": {
569
+ sendResult(id, { tools: TOOLS })
570
+ break
571
+ }
572
+
573
+ case "tools/call": {
574
+ try {
575
+ const result = await handleToolCall(params.name, params.arguments)
576
+ sendResult(id, result)
577
+ } catch (err) {
578
+ sendError(id, -1, err.message)
579
+ }
580
+ break
581
+ }
582
+
583
+ case "shutdown": {
584
+ sendResult(id, null)
585
+ process.exitCode = 0
586
+ process.stdout.once("drain", () => process.exit(0))
587
+ // Force exit after 1s even if drain never fires
588
+ setTimeout(() => process.exit(0), 1000).unref()
589
+ break
590
+ }
591
+
592
+ default:
593
+ sendError(id, -32601, `Method not found: ${method}`)
594
+ }
595
+ }
596
+ })
597
+
598
+ process.stdin.on("end", () => process.exit(0))
599
+ }
600
+
601
+ // Auto-start when executed directly, not when imported
602
+ const isDirectRun =
603
+ process.argv[1] &&
604
+ (process.argv[1].endsWith("mcp-server.js") || process.argv[1].endsWith("mcp-server"))
605
+ if (isDirectRun) startServer()