@3lineas/grooveskills 0.0.0-stage → 1.0.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/README.md CHANGED
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # GrooveSkills 🚀
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Multi-Agent AI Skills & Architectural Standards Installer**
4
+ > Empower your AI coding pairs (Antigravity, Claude Code, Cursor, Gemini) with your team's real-world architectural habits, clean code patterns, and non-negotiables.
5
+
6
+ ---
7
+
8
+ ## 🌟 Features
9
+
10
+ - **⚡ Zero Configuration (`npx @3lineas/grooveskills`)**: Run inside any project root to inspect the environment and synchronize AI skills instantly.
11
+ - **🎯 Smart Stack Detection**: Automatically inspects `package.json` dependencies and pre-selects matching skills (e.g. Hono, Valibot, React, Next.js).
12
+ - **🤖 Multi-Agent Compatibility**:
13
+ - **Antigravity**: `.agents/skills/<skill-name>/`
14
+ - **Claude Code**: `.claude/skills/<skill-name>/`
15
+ - **Cursor IDE**: `.cursor/rules/<skill-name>.mdc` (auto-adapted to Cursor rules format)
16
+ - **Gemini Code Assist**: `.gemini/skills/<skill-name>/`
17
+ - **✨ Interactive & Friendly CLI**: Clean terminal experience built with `@clack/prompts` and courteous, persuasive feedback.
18
+ - **🛡️ Battle-tested Architectural Standards**:
19
+ - **Universal Best Practices**: Multi-language clean code (TypeScript, PHP, Dart, Rust, etc.), early returns, zero barrel files, strict typing, <200 lines per file.
20
+ - **Hono API Architecture**: Independent routes by domain, isolated Actions layer, database queries exclusively inside Actions, strict Valibot validation, and Cloudflare/Hono RPC.
21
+
22
+ ---
23
+
24
+ ## 🚀 Usage
25
+
26
+ ### 1. Interactive Mode (Recommended)
27
+
28
+ Run directly in the root of any project:
29
+
30
+ ```bash
31
+ npx @3lineas/grooveskills
32
+ ```
33
+
34
+ GrooveSkills will automatically:
35
+ 1. Detect any active AI agent folders (`.agents`, `.claude`, `.cursor`, `.gemini`).
36
+ 2. Scan your project dependencies (`package.json`) and pre-select the recommended skills.
37
+ 3. Guide you through an interactive multi-select menu.
38
+ 4. Install and configure the skills for all chosen AI agents.
39
+
40
+ ---
41
+
42
+ ### 2. Direct / Automated Mode (CI / Scripting)
43
+
44
+ Install specific skills for targeted agents without interactive prompts:
45
+
46
+ ```bash
47
+ # Install specific skills for selected agents
48
+ npx @3lineas/grooveskills --skills best-practices hono --agents antigravity claude
49
+
50
+ # Automatically confirm overwrite of existing skills
51
+ npx @3lineas/grooveskills --skills best-practices hono --agents antigravity claude cursor gemini --yes
52
+ ```
53
+
54
+ #### CLI Options
55
+
56
+ | Flag | Description | Example |
57
+ | :--- | :--- | :--- |
58
+ | `-s, --skills` | Space-separated list of skill IDs to install | `--skills best-practices hono` |
59
+ | `-a, --agents` | Target AI agents (`antigravity`, `claude`, `cursor`, `gemini`) | `--agents antigravity cursor` |
60
+ | `-y, --yes` | Automatically overwrite existing skills | `--yes` |
61
+ | `-h, --help` | Display command help and options | `--help` |
62
+ | `-V, --version` | Display the installed version of GrooveSkills | `--version` |
63
+
64
+ ---
65
+
66
+ ## 📦 Available Skills
67
+
68
+ | Skill ID | Name | Description | Suggested When |
69
+ | :--- | :--- | :--- | :--- |
70
+ | `best-practices` | **Universal Best Practices** | Multi-language clean code, early returns, no barrel files, strict typing, <200 lines. | **Always suggested by default** (Any language/file) |
71
+ | `hono` | **Hono Architecture (Valibot)** | Enterprise Hono standard: Valibot validation, Actions layer, RPC bindings, AppError. | Detected `hono`, `@hono/valibot-validator`, etc. |
72
+
73
+ ---
74
+
75
+ ## 📄 License
76
+
77
+ MIT © [3-Lineas](https://github.com/3-Lineas)
@@ -0,0 +1,376 @@
1
+ // src/domains/detector/detect-agents.ts
2
+ import { readdirSync } from "fs";
3
+ function detectAgents(targetPath) {
4
+ const detected = [];
5
+ try {
6
+ const entries = new Set(readdirSync(targetPath));
7
+ if (entries.has(".agents")) {
8
+ detected.push("antigravity");
9
+ }
10
+ if (entries.has(".claude")) {
11
+ detected.push("claude");
12
+ }
13
+ if (entries.has(".cursor")) {
14
+ detected.push("cursor");
15
+ }
16
+ if (entries.has(".gemini")) {
17
+ detected.push("gemini");
18
+ }
19
+ } catch {
20
+ }
21
+ return detected;
22
+ }
23
+
24
+ // src/domains/detector/detect-dependencies.ts
25
+ import { readFileSync, existsSync } from "fs";
26
+ import { join } from "path";
27
+ function detectDependencies(targetPath) {
28
+ const packageJsonPath = join(targetPath, "package.json");
29
+ if (!existsSync(packageJsonPath)) {
30
+ return { dependencies: [], exists: false };
31
+ }
32
+ try {
33
+ const raw = readFileSync(packageJsonPath, "utf-8");
34
+ const parsed = JSON.parse(raw);
35
+ const direct = Object.keys(parsed.dependencies ?? {});
36
+ const dev = Object.keys(parsed.devDependencies ?? {});
37
+ const all = Array.from(/* @__PURE__ */ new Set([...direct, ...dev]));
38
+ return { dependencies: all, exists: true };
39
+ } catch {
40
+ return { dependencies: [], exists: true };
41
+ }
42
+ }
43
+
44
+ // src/domains/registry/skill-catalog.ts
45
+ var SKILL_CATALOG = [
46
+ {
47
+ id: "best-practices",
48
+ name: "Universal Best Practices",
49
+ description: "Multi-language clean code standards, early returns, no barrel files, strict typing, <200 lines",
50
+ dependencies: [],
51
+ directoryName: "best-practices"
52
+ },
53
+ {
54
+ id: "hono",
55
+ name: "Hono Architecture (Valibot)",
56
+ description: "Enterprise Hono API standard with Valibot validation, isolated Actions layer, and RPC bindings",
57
+ dependencies: ["hono", "@hono/valibot-validator", "@hono/standard-validator"],
58
+ directoryName: "hono"
59
+ }
60
+ ];
61
+
62
+ // src/domains/detector/project-detector.ts
63
+ function detectProjectContext(targetPath) {
64
+ const detectedAgents = detectAgents(targetPath);
65
+ const { dependencies, exists } = detectDependencies(targetPath);
66
+ const suggestedSkillIds = ["best-practices"];
67
+ for (const skill of SKILL_CATALOG) {
68
+ if (skill.dependencies.length === 0) {
69
+ continue;
70
+ }
71
+ const hasMatch = skill.dependencies.some((dep) => dependencies.includes(dep));
72
+ if (hasMatch && !suggestedSkillIds.includes(skill.id)) {
73
+ suggestedSkillIds.push(skill.id);
74
+ }
75
+ }
76
+ return {
77
+ targetPath,
78
+ detectedDependencies: dependencies,
79
+ suggestedSkillIds,
80
+ detectedAgents,
81
+ packageJsonExists: exists
82
+ };
83
+ }
84
+
85
+ // src/domains/ui/ui-orchestrator.ts
86
+ import * as p4 from "@clack/prompts";
87
+ import pc2 from "picocolors";
88
+
89
+ // src/domains/ui/banners.ts
90
+ import pc from "picocolors";
91
+ function renderBanner() {
92
+ const title = pc.bold(pc.white(" GROOVE SKILLS "));
93
+ const badge = pc.bgMagenta(pc.black(title));
94
+ const subtitle = pc.dim("Multi-Agent AI Skills & Architectural Standards");
95
+ return `${badge} ${subtitle}
96
+ `;
97
+ }
98
+
99
+ // src/domains/ui/prompt-skills.ts
100
+ import * as p from "@clack/prompts";
101
+ async function promptSkills(suggestedSkillIds) {
102
+ const options = SKILL_CATALOG.map((skill) => {
103
+ const isSuggested = suggestedSkillIds.includes(skill.id);
104
+ return {
105
+ value: skill.id,
106
+ label: skill.name,
107
+ hint: isSuggested ? "Suggested based on your project stack" : skill.description
108
+ };
109
+ });
110
+ const selected = await p.multiselect({
111
+ message: "Which skills would you like to install in this project?",
112
+ options,
113
+ initialValues: suggestedSkillIds,
114
+ required: true
115
+ });
116
+ return selected;
117
+ }
118
+
119
+ // src/domains/ui/prompt-agents.ts
120
+ import * as p2 from "@clack/prompts";
121
+ async function promptAgents(detectedAgents) {
122
+ const options = [
123
+ { value: "antigravity", label: "Antigravity", hint: ".agents/skills (Google Agentic IDE)" },
124
+ { value: "claude", label: "Claude Code", hint: ".claude/skills (Anthropic Claude Code)" },
125
+ { value: "cursor", label: "Cursor", hint: ".cursor/rules/*.mdc (Cursor IDE Rules)" },
126
+ { value: "gemini", label: "Gemini Code Assist", hint: ".gemini/skills (Google Gemini)" }
127
+ ];
128
+ const initialValues = detectedAgents.length > 0 ? detectedAgents : ["antigravity"];
129
+ const selected = await p2.multiselect({
130
+ message: "Which AI agents should be configured?",
131
+ options,
132
+ initialValues,
133
+ required: true
134
+ });
135
+ return selected;
136
+ }
137
+
138
+ // src/domains/ui/prompt-confirm-overwrite.ts
139
+ import * as p3 from "@clack/prompts";
140
+ async function promptConfirmOverwrite() {
141
+ return await p3.confirm({
142
+ message: "Some skills already exist in this project. Would you like to overwrite them with the latest version?",
143
+ initialValue: true
144
+ });
145
+ }
146
+
147
+ // src/domains/installer/verify-skill-exists.ts
148
+ import { existsSync as existsSync2 } from "fs";
149
+ import { join as join2 } from "path";
150
+ var AGENT_DESTINATION_MAP = {
151
+ antigravity: (dir) => join2(".agents", "skills", dir),
152
+ claude: (dir) => join2(".claude", "skills", dir),
153
+ cursor: (dir) => join2(".cursor", "rules", `${dir}.mdc`),
154
+ gemini: (dir) => join2(".gemini", "skills", dir)
155
+ };
156
+ function verifySkillExists(targetPath, skillDirectoryName, agents) {
157
+ for (const agent of agents) {
158
+ const relativePathResolver = AGENT_DESTINATION_MAP[agent];
159
+ const destination = join2(targetPath, relativePathResolver(skillDirectoryName));
160
+ if (existsSync2(destination)) {
161
+ return true;
162
+ }
163
+ }
164
+ return false;
165
+ }
166
+
167
+ // src/domains/installer/install-orchestrator.ts
168
+ import { join as join8 } from "path";
169
+
170
+ // src/domains/adapters/antigravity.adapter.ts
171
+ import { cpSync, mkdirSync, existsSync as existsSync3 } from "fs";
172
+ import { join as join3 } from "path";
173
+ var AntigravityAdapter = class {
174
+ id = "antigravity";
175
+ name = "Antigravity";
176
+ targetRelativeDir = ".agents/skills";
177
+ async installSkill(targetPath, skill, skillSourceDir) {
178
+ const destination = join3(targetPath, this.targetRelativeDir, skill.directoryName);
179
+ if (!existsSync3(destination)) {
180
+ mkdirSync(destination, { recursive: true });
181
+ }
182
+ cpSync(skillSourceDir, destination, { recursive: true });
183
+ return [destination];
184
+ }
185
+ };
186
+
187
+ // src/domains/adapters/claude.adapter.ts
188
+ import { cpSync as cpSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
189
+ import { join as join4 } from "path";
190
+ var ClaudeAdapter = class {
191
+ id = "claude";
192
+ name = "Claude Code";
193
+ targetRelativeDir = ".claude/skills";
194
+ async installSkill(targetPath, skill, skillSourceDir) {
195
+ const destination = join4(targetPath, this.targetRelativeDir, skill.directoryName);
196
+ if (!existsSync4(destination)) {
197
+ mkdirSync2(destination, { recursive: true });
198
+ }
199
+ cpSync2(skillSourceDir, destination, { recursive: true });
200
+ return [destination];
201
+ }
202
+ };
203
+
204
+ // src/domains/adapters/cursor.adapter.ts
205
+ import { writeFileSync, mkdirSync as mkdirSync3, existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
206
+ import { join as join5 } from "path";
207
+ var CursorAdapter = class {
208
+ id = "cursor";
209
+ name = "Cursor";
210
+ targetRelativeDir = ".cursor/rules";
211
+ async installSkill(targetPath, skill, skillSourceDir) {
212
+ const rulesDir = join5(targetPath, this.targetRelativeDir);
213
+ if (!existsSync5(rulesDir)) {
214
+ mkdirSync3(rulesDir, { recursive: true });
215
+ }
216
+ const skillMdPath = join5(skillSourceDir, "SKILL.md");
217
+ let content = "";
218
+ if (existsSync5(skillMdPath)) {
219
+ content = readFileSync2(skillMdPath, "utf-8");
220
+ }
221
+ const mdcHeader = `---
222
+ description: ${skill.description}
223
+ globs: ["**/*"]
224
+ alwaysApply: false
225
+ ---
226
+
227
+ `;
228
+ const cleanedBody = content.replace(/^---[\s\S]*?---\s*/, "");
229
+ const targetFile = join5(rulesDir, `${skill.id}.mdc`);
230
+ writeFileSync(targetFile, `${mdcHeader}${cleanedBody}`, "utf-8");
231
+ return [targetFile];
232
+ }
233
+ };
234
+
235
+ // src/domains/adapters/gemini.adapter.ts
236
+ import { cpSync as cpSync3, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
237
+ import { join as join6 } from "path";
238
+ var GeminiAdapter = class {
239
+ id = "gemini";
240
+ name = "Gemini Code Assist";
241
+ targetRelativeDir = ".gemini/skills";
242
+ async installSkill(targetPath, skill, skillSourceDir) {
243
+ const destination = join6(targetPath, this.targetRelativeDir, skill.directoryName);
244
+ if (!existsSync6(destination)) {
245
+ mkdirSync4(destination, { recursive: true });
246
+ }
247
+ cpSync3(skillSourceDir, destination, { recursive: true });
248
+ return [destination];
249
+ }
250
+ };
251
+
252
+ // src/domains/installer/resolve-skills-source.ts
253
+ import { fileURLToPath } from "url";
254
+ import { dirname, resolve, join as join7 } from "path";
255
+ import { existsSync as existsSync7 } from "fs";
256
+ function resolveSkillsSourceRoot() {
257
+ const currentFilePath = fileURLToPath(import.meta.url);
258
+ const currentDir = dirname(currentFilePath);
259
+ const candidate1 = resolve(currentDir, "../../../skills");
260
+ if (existsSync7(candidate1)) {
261
+ return candidate1;
262
+ }
263
+ const candidate2 = resolve(currentDir, "../../skills");
264
+ if (existsSync7(candidate2)) {
265
+ return candidate2;
266
+ }
267
+ const candidate3 = resolve(process.cwd(), "skills");
268
+ if (existsSync7(candidate3)) {
269
+ return candidate3;
270
+ }
271
+ return join7(process.cwd(), "skills");
272
+ }
273
+
274
+ // src/domains/installer/install-orchestrator.ts
275
+ var ADAPTER_REGISTRY = {
276
+ antigravity: new AntigravityAdapter(),
277
+ claude: new ClaudeAdapter(),
278
+ cursor: new CursorAdapter(),
279
+ gemini: new GeminiAdapter()
280
+ };
281
+ async function installSkills(options) {
282
+ const sourceRoot = resolveSkillsSourceRoot();
283
+ const createdFiles = [];
284
+ for (const skillId of options.selectedSkillIds) {
285
+ const skill = SKILL_CATALOG.find((item) => item.id === skillId);
286
+ if (!skill) {
287
+ continue;
288
+ }
289
+ const skillSourceDir = join8(sourceRoot, skill.directoryName);
290
+ for (const agentId of options.selectedAgents) {
291
+ const adapter = ADAPTER_REGISTRY[agentId];
292
+ if (!adapter) {
293
+ continue;
294
+ }
295
+ const files = await adapter.installSkill(options.targetPath, skill, skillSourceDir);
296
+ createdFiles.push(...files);
297
+ }
298
+ }
299
+ return {
300
+ installedSkills: options.selectedSkillIds,
301
+ configuredAgents: options.selectedAgents,
302
+ createdFiles
303
+ };
304
+ }
305
+
306
+ // src/domains/ui/ui-orchestrator.ts
307
+ async function runInteractiveUi(context) {
308
+ console.log(renderBanner());
309
+ p4.intro(pc2.magenta("Welcome to GrooveSkills! Let's tailor your AI agents with clean architectural standards."));
310
+ if (context.detectedDependencies.length > 0) {
311
+ p4.note(
312
+ `Detected dependencies: ${context.detectedDependencies.map((d) => pc2.bold(d)).join(", ")}
313
+ Active AI folders: ${context.detectedAgents.map((a) => pc2.green(a)).join(", ") || "None (will be created)"}`,
314
+ "Project Environment"
315
+ );
316
+ }
317
+ const selectedSkills = await promptSkills(context.suggestedSkillIds);
318
+ if (p4.isCancel(selectedSkills)) {
319
+ p4.cancel("Installation cancelled. Have a wonderful day!");
320
+ process.exit(0);
321
+ }
322
+ const selectedAgents = await promptAgents(context.detectedAgents);
323
+ if (p4.isCancel(selectedAgents)) {
324
+ p4.cancel("Installation cancelled. Have a wonderful day!");
325
+ process.exit(0);
326
+ }
327
+ let forceOverwrite = true;
328
+ const anyExists = selectedSkills.some((skillId) => {
329
+ const meta = SKILL_CATALOG.find((s2) => s2.id === skillId);
330
+ return meta ? verifySkillExists(context.targetPath, meta.directoryName, selectedAgents) : false;
331
+ });
332
+ if (anyExists) {
333
+ const confirm2 = await promptConfirmOverwrite();
334
+ if (p4.isCancel(confirm2) || !confirm2) {
335
+ p4.cancel("Installation stopped to protect existing skills.");
336
+ process.exit(0);
337
+ }
338
+ forceOverwrite = Boolean(confirm2);
339
+ }
340
+ const s = p4.spinner();
341
+ s.start("Installing and adapting skills across your configured AI agents...");
342
+ const result = await installSkills({
343
+ targetPath: context.targetPath,
344
+ selectedSkillIds: selectedSkills,
345
+ selectedAgents,
346
+ forceOverwrite
347
+ });
348
+ s.stop(pc2.green("All skills have been successfully installed and configured!"));
349
+ p4.note(
350
+ `Configured skills: ${result.installedSkills.map((s2) => pc2.bold(s2)).join(", ")}
351
+ Target agents: ${result.configuredAgents.map((a) => pc2.cyan(a)).join(", ")}`,
352
+ "Installation Summary"
353
+ );
354
+ p4.outro(pc2.green("Your AI programming pair is now empowered with your team's architectural habits! \u{1F680}"));
355
+ }
356
+
357
+ // src/main.ts
358
+ async function runGrooveSkills(options = {}) {
359
+ const targetPath = process.cwd();
360
+ const context = detectProjectContext(targetPath);
361
+ if (options.skills && options.skills.length > 0 && options.agents && options.agents.length > 0) {
362
+ await installSkills({
363
+ targetPath,
364
+ selectedSkillIds: options.skills,
365
+ selectedAgents: options.agents,
366
+ forceOverwrite: Boolean(options.yes)
367
+ });
368
+ return;
369
+ }
370
+ await runInteractiveUi(context);
371
+ }
372
+
373
+ export {
374
+ runGrooveSkills
375
+ };
376
+ //# sourceMappingURL=chunk-75476IDS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/domains/detector/detect-agents.ts","../src/domains/detector/detect-dependencies.ts","../src/domains/registry/skill-catalog.ts","../src/domains/detector/project-detector.ts","../src/domains/ui/ui-orchestrator.ts","../src/domains/ui/banners.ts","../src/domains/ui/prompt-skills.ts","../src/domains/ui/prompt-agents.ts","../src/domains/ui/prompt-confirm-overwrite.ts","../src/domains/installer/verify-skill-exists.ts","../src/domains/installer/install-orchestrator.ts","../src/domains/adapters/antigravity.adapter.ts","../src/domains/adapters/claude.adapter.ts","../src/domains/adapters/cursor.adapter.ts","../src/domains/adapters/gemini.adapter.ts","../src/domains/installer/resolve-skills-source.ts","../src/main.ts"],"sourcesContent":["import { readdirSync } from \"node:fs\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport function detectAgents(targetPath: string): AgentId[] {\n const detected: AgentId[] = [];\n\n try {\n const entries = new Set(readdirSync(targetPath));\n\n if (entries.has(\".agents\")) {\n detected.push(\"antigravity\");\n }\n if (entries.has(\".claude\")) {\n detected.push(\"claude\");\n }\n if (entries.has(\".cursor\")) {\n detected.push(\"cursor\");\n }\n if (entries.has(\".gemini\")) {\n detected.push(\"gemini\");\n }\n } catch {\n // If directory cannot be read, return empty list gracefully\n }\n\n return detected;\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface PackageJsonDetection {\n dependencies: string[];\n exists: boolean;\n}\n\nexport function detectDependencies(targetPath: string): PackageJsonDetection {\n const packageJsonPath = join(targetPath, \"package.json\");\n\n if (!existsSync(packageJsonPath)) {\n return { dependencies: [], exists: false };\n }\n\n try {\n const raw = readFileSync(packageJsonPath, \"utf-8\");\n const parsed = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const direct = Object.keys(parsed.dependencies ?? {});\n const dev = Object.keys(parsed.devDependencies ?? {});\n const all = Array.from(new Set([...direct, ...dev]));\n\n return { dependencies: all, exists: true };\n } catch {\n return { dependencies: [], exists: true };\n }\n}\n","import type { SkillMetadata } from \"@/types/skill.types\";\n\nexport const SKILL_CATALOG: SkillMetadata[] = [\n {\n id: \"best-practices\",\n name: \"Universal Best Practices\",\n description: \"Multi-language clean code standards, early returns, no barrel files, strict typing, <200 lines\",\n dependencies: [],\n directoryName: \"best-practices\",\n },\n {\n id: \"hono\",\n name: \"Hono Architecture (Valibot)\",\n description: \"Enterprise Hono API standard with Valibot validation, isolated Actions layer, and RPC bindings\",\n dependencies: [\"hono\", \"@hono/valibot-validator\", \"@hono/standard-validator\"],\n directoryName: \"hono\",\n },\n];\n","import { detectAgents } from \"@/domains/detector/detect-agents\";\nimport { detectDependencies } from \"@/domains/detector/detect-dependencies\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\nimport type { ProjectContext } from \"@/types/context.types\";\n\nexport function detectProjectContext(targetPath: string): ProjectContext {\n const detectedAgents = detectAgents(targetPath);\n const { dependencies, exists } = detectDependencies(targetPath);\n\n const suggestedSkillIds: string[] = [\"best-practices\"];\n\n for (const skill of SKILL_CATALOG) {\n if (skill.dependencies.length === 0) {\n continue;\n }\n const hasMatch = skill.dependencies.some((dep) => dependencies.includes(dep));\n if (hasMatch && !suggestedSkillIds.includes(skill.id)) {\n suggestedSkillIds.push(skill.id);\n }\n }\n\n return {\n targetPath,\n detectedDependencies: dependencies,\n suggestedSkillIds,\n detectedAgents,\n packageJsonExists: exists,\n };\n}\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { renderBanner } from \"@/domains/ui/banners\";\nimport { promptSkills } from \"@/domains/ui/prompt-skills\";\nimport { promptAgents } from \"@/domains/ui/prompt-agents\";\nimport { promptConfirmOverwrite } from \"@/domains/ui/prompt-confirm-overwrite\";\nimport { verifySkillExists } from \"@/domains/installer/verify-skill-exists\";\nimport { installSkills } from \"@/domains/installer/install-orchestrator\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\nimport type { ProjectContext } from \"@/types/context.types\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport async function runInteractiveUi(context: ProjectContext): Promise<void> {\n console.log(renderBanner());\n p.intro(pc.magenta(\"Welcome to GrooveSkills! Let's tailor your AI agents with clean architectural standards.\"));\n\n if (context.detectedDependencies.length > 0) {\n p.note(\n `Detected dependencies: ${context.detectedDependencies.map((d) => pc.bold(d)).join(\", \")}\\n` +\n `Active AI folders: ${context.detectedAgents.map((a) => pc.green(a)).join(\", \") || \"None (will be created)\"}`,\n \"Project Environment\"\n );\n }\n\n const selectedSkills = await promptSkills(context.suggestedSkillIds);\n if (p.isCancel(selectedSkills)) {\n p.cancel(\"Installation cancelled. Have a wonderful day!\");\n process.exit(0);\n }\n\n const selectedAgents = await promptAgents(context.detectedAgents);\n if (p.isCancel(selectedAgents)) {\n p.cancel(\"Installation cancelled. Have a wonderful day!\");\n process.exit(0);\n }\n\n let forceOverwrite = true;\n const anyExists = (selectedSkills as string[]).some((skillId) => {\n const meta = SKILL_CATALOG.find((s) => s.id === skillId);\n return meta ? verifySkillExists(context.targetPath, meta.directoryName, selectedAgents as AgentId[]) : false;\n });\n\n if (anyExists) {\n const confirm = await promptConfirmOverwrite();\n if (p.isCancel(confirm) || !confirm) {\n p.cancel(\"Installation stopped to protect existing skills.\");\n process.exit(0);\n }\n forceOverwrite = Boolean(confirm);\n }\n\n const s = p.spinner();\n s.start(\"Installing and adapting skills across your configured AI agents...\");\n\n const result = await installSkills({\n targetPath: context.targetPath,\n selectedSkillIds: selectedSkills as string[],\n selectedAgents: selectedAgents as AgentId[],\n forceOverwrite,\n });\n\n s.stop(pc.green(\"All skills have been successfully installed and configured!\"));\n\n p.note(\n `Configured skills: ${result.installedSkills.map((s) => pc.bold(s)).join(\", \")}\\n` +\n `Target agents: ${result.configuredAgents.map((a) => pc.cyan(a)).join(\", \")}`,\n \"Installation Summary\"\n );\n\n p.outro(pc.green(\"Your AI programming pair is now empowered with your team's architectural habits! 🚀\"));\n}\n","import pc from \"picocolors\";\n\nexport function renderBanner(): string {\n const title = pc.bold(pc.white(\" GROOVE SKILLS \"));\n const badge = pc.bgMagenta(pc.black(title));\n const subtitle = pc.dim(\"Multi-Agent AI Skills & Architectural Standards\");\n\n return `${badge} ${subtitle}\\n`;\n}\n","import * as p from \"@clack/prompts\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\n\nexport async function promptSkills(suggestedSkillIds: string[]): Promise<string[] | symbol> {\n const options = SKILL_CATALOG.map((skill) => {\n const isSuggested = suggestedSkillIds.includes(skill.id);\n return {\n value: skill.id,\n label: skill.name,\n hint: isSuggested ? \"Suggested based on your project stack\" : skill.description,\n };\n });\n\n const selected = await p.multiselect({\n message: \"Which skills would you like to install in this project?\",\n options,\n initialValues: suggestedSkillIds,\n required: true,\n });\n\n return selected;\n}\n","import * as p from \"@clack/prompts\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport async function promptAgents(detectedAgents: AgentId[]): Promise<AgentId[] | symbol> {\n const options: Array<{ value: AgentId; label: string; hint: string }> = [\n { value: \"antigravity\", label: \"Antigravity\", hint: \".agents/skills (Google Agentic IDE)\" },\n { value: \"claude\", label: \"Claude Code\", hint: \".claude/skills (Anthropic Claude Code)\" },\n { value: \"cursor\", label: \"Cursor\", hint: \".cursor/rules/*.mdc (Cursor IDE Rules)\" },\n { value: \"gemini\", label: \"Gemini Code Assist\", hint: \".gemini/skills (Google Gemini)\" },\n ];\n\n const initialValues: AgentId[] = detectedAgents.length > 0 ? detectedAgents : [\"antigravity\"];\n\n const selected = await p.multiselect({\n message: \"Which AI agents should be configured?\",\n options,\n initialValues,\n required: true,\n });\n\n return selected as AgentId[] | symbol;\n}\n","import * as p from \"@clack/prompts\";\n\nexport async function promptConfirmOverwrite(): Promise<boolean | symbol> {\n return await p.confirm({\n message: \"Some skills already exist in this project. Would you like to overwrite them with the latest version?\",\n initialValue: true,\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nconst AGENT_DESTINATION_MAP: Record<AgentId, (dir: string) => string> = {\n antigravity: (dir) => join(\".agents\", \"skills\", dir),\n claude: (dir) => join(\".claude\", \"skills\", dir),\n cursor: (dir) => join(\".cursor\", \"rules\", `${dir}.mdc`),\n gemini: (dir) => join(\".gemini\", \"skills\", dir),\n};\n\nexport function verifySkillExists(\n targetPath: string,\n skillDirectoryName: string,\n agents: AgentId[]\n): boolean {\n for (const agent of agents) {\n const relativePathResolver = AGENT_DESTINATION_MAP[agent];\n const destination = join(targetPath, relativePathResolver(skillDirectoryName));\n if (existsSync(destination)) {\n return true;\n }\n }\n\n return false;\n}\n","import { join } from \"node:path\";\nimport { AntigravityAdapter } from \"@/domains/adapters/antigravity.adapter\";\nimport { ClaudeAdapter } from \"@/domains/adapters/claude.adapter\";\nimport { CursorAdapter } from \"@/domains/adapters/cursor.adapter\";\nimport { GeminiAdapter } from \"@/domains/adapters/gemini.adapter\";\nimport { resolveSkillsSourceRoot } from \"@/domains/installer/resolve-skills-source\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { InstallOptions, InstallResult } from \"@/types/installer.types\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nconst ADAPTER_REGISTRY: Record<AgentId, AgentAdapter> = {\n antigravity: new AntigravityAdapter(),\n claude: new ClaudeAdapter(),\n cursor: new CursorAdapter(),\n gemini: new GeminiAdapter(),\n};\n\nexport async function installSkills(options: InstallOptions): Promise<InstallResult> {\n const sourceRoot = resolveSkillsSourceRoot();\n const createdFiles: string[] = [];\n\n for (const skillId of options.selectedSkillIds) {\n const skill = SKILL_CATALOG.find((item) => item.id === skillId);\n if (!skill) {\n continue;\n }\n\n const skillSourceDir = join(sourceRoot, skill.directoryName);\n\n for (const agentId of options.selectedAgents) {\n const adapter = ADAPTER_REGISTRY[agentId];\n if (!adapter) {\n continue;\n }\n\n const files = await adapter.installSkill(options.targetPath, skill, skillSourceDir);\n createdFiles.push(...files);\n }\n }\n\n return {\n installedSkills: options.selectedSkillIds,\n configuredAgents: options.selectedAgents,\n createdFiles,\n };\n}\n","import { cpSync, mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class AntigravityAdapter implements AgentAdapter {\n readonly id = \"antigravity\" as const;\n readonly name = \"Antigravity\";\n readonly targetRelativeDir = \".agents/skills\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n skillSourceDir: string\n ): Promise<string[]> {\n const destination = join(targetPath, this.targetRelativeDir, skill.directoryName);\n\n if (!existsSync(destination)) {\n mkdirSync(destination, { recursive: true });\n }\n\n cpSync(skillSourceDir, destination, { recursive: true });\n\n return [destination];\n }\n}\n","import { cpSync, mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class ClaudeAdapter implements AgentAdapter {\n readonly id = \"claude\" as const;\n readonly name = \"Claude Code\";\n readonly targetRelativeDir = \".claude/skills\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n skillSourceDir: string\n ): Promise<string[]> {\n const destination = join(targetPath, this.targetRelativeDir, skill.directoryName);\n\n if (!existsSync(destination)) {\n mkdirSync(destination, { recursive: true });\n }\n\n cpSync(skillSourceDir, destination, { recursive: true });\n\n return [destination];\n }\n}\n","import { writeFileSync, mkdirSync, existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class CursorAdapter implements AgentAdapter {\n readonly id = \"cursor\" as const;\n readonly name = \"Cursor\";\n readonly targetRelativeDir = \".cursor/rules\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n skillSourceDir: string\n ): Promise<string[]> {\n const rulesDir = join(targetPath, this.targetRelativeDir);\n\n if (!existsSync(rulesDir)) {\n mkdirSync(rulesDir, { recursive: true });\n }\n\n const skillMdPath = join(skillSourceDir, \"SKILL.md\");\n let content = \"\";\n\n if (existsSync(skillMdPath)) {\n content = readFileSync(skillMdPath, \"utf-8\");\n }\n\n const mdcHeader = `---\ndescription: ${skill.description}\nglobs: [\"**/*\"]\nalwaysApply: false\n---\n\n`;\n\n const cleanedBody = content.replace(/^---[\\s\\S]*?---\\s*/, \"\");\n const targetFile = join(rulesDir, `${skill.id}.mdc`);\n\n writeFileSync(targetFile, `${mdcHeader}${cleanedBody}`, \"utf-8\");\n\n return [targetFile];\n }\n}\n","import { cpSync, mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class GeminiAdapter implements AgentAdapter {\n readonly id = \"gemini\" as const;\n readonly name = \"Gemini Code Assist\";\n readonly targetRelativeDir = \".gemini/skills\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n skillSourceDir: string\n ): Promise<string[]> {\n const destination = join(targetPath, this.targetRelativeDir, skill.directoryName);\n\n if (!existsSync(destination)) {\n mkdirSync(destination, { recursive: true });\n }\n\n cpSync(skillSourceDir, destination, { recursive: true });\n\n return [destination];\n }\n}\n","import { fileURLToPath } from \"node:url\";\nimport { dirname, resolve, join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nexport function resolveSkillsSourceRoot(): string {\n const currentFilePath = fileURLToPath(import.meta.url);\n const currentDir = dirname(currentFilePath);\n\n const candidate1 = resolve(currentDir, \"../../../skills\");\n if (existsSync(candidate1)) {\n return candidate1;\n }\n\n const candidate2 = resolve(currentDir, \"../../skills\");\n if (existsSync(candidate2)) {\n return candidate2;\n }\n\n const candidate3 = resolve(process.cwd(), \"skills\");\n if (existsSync(candidate3)) {\n return candidate3;\n }\n\n return join(process.cwd(), \"skills\");\n}\n","import { detectProjectContext } from \"@/domains/detector/project-detector\";\nimport { runInteractiveUi } from \"@/domains/ui/ui-orchestrator\";\nimport { installSkills } from \"@/domains/installer/install-orchestrator\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport interface CliOptions {\n skills?: string[];\n agents?: AgentId[];\n yes?: boolean;\n}\n\nexport async function runGrooveSkills(options: CliOptions = {}): Promise<void> {\n const targetPath = process.cwd();\n const context = detectProjectContext(targetPath);\n\n if (options.skills && options.skills.length > 0 && options.agents && options.agents.length > 0) {\n await installSkills({\n targetPath,\n selectedSkillIds: options.skills,\n selectedAgents: options.agents,\n forceOverwrite: Boolean(options.yes),\n });\n return;\n }\n\n await runInteractiveUi(context);\n}\n"],"mappings":"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","names":["p","pc","p","p","existsSync","join","join","existsSync","join","cpSync","mkdirSync","existsSync","join","mkdirSync","existsSync","readFileSync","join","cpSync","mkdirSync","existsSync","join","join","existsSync","join","pc","s","confirm"]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runGrooveSkills
4
+ } from "./chunk-75476IDS.js";
5
+
6
+ // bin/cli.ts
7
+ import { Command } from "commander";
8
+ var program = new Command();
9
+ program.name("grooveskills").description("Multi-agent AI skills and architecture standards installer").version("1.0.0").option("-s, --skills <skills...>", "Specific skill IDs to install (e.g. best-practices hono)").option("-a, --agents <agents...>", "Target AI agents (antigravity claude cursor gemini)").option("-y, --yes", "Automatically confirm overwrite of existing skills", false).action(async (options) => {
10
+ try {
11
+ await runGrooveSkills({
12
+ skills: options.skills,
13
+ agents: options.agents,
14
+ yes: options.yes
15
+ });
16
+ } catch (error) {
17
+ console.error("An unexpected error occurred while running GrooveSkills:", error);
18
+ process.exit(1);
19
+ }
20
+ });
21
+ program.parse();
22
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../bin/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { runGrooveSkills } from \"@/main\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nconst program = new Command();\n\nprogram\n .name(\"grooveskills\")\n .description(\"Multi-agent AI skills and architecture standards installer\")\n .version(\"1.0.0\")\n .option(\"-s, --skills <skills...>\", \"Specific skill IDs to install (e.g. best-practices hono)\")\n .option(\"-a, --agents <agents...>\", \"Target AI agents (antigravity claude cursor gemini)\")\n .option(\"-y, --yes\", \"Automatically confirm overwrite of existing skills\", false)\n .action(async (options: { skills?: string[]; agents?: string[]; yes?: boolean }) => {\n try {\n await runGrooveSkills({\n skills: options.skills,\n agents: options.agents as AgentId[] | undefined,\n yes: options.yes,\n });\n } catch (error) {\n console.error(\"An unexpected error occurred while running GrooveSkills:\", error);\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;AACA,SAAS,eAAe;AAIxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,cAAc,EACnB,YAAY,4DAA4D,EACxE,QAAQ,OAAO,EACf,OAAO,4BAA4B,0DAA0D,EAC7F,OAAO,4BAA4B,qDAAqD,EACxF,OAAO,aAAa,sDAAsD,KAAK,EAC/E,OAAO,OAAO,YAAqE;AAClF,MAAI;AACF,UAAM,gBAAgB;AAAA,MACpB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,KAAK,QAAQ;AAAA,IACf,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,4DAA4D,KAAK;AAC/E,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":[]}
package/dist/main.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ type AgentId = "antigravity" | "claude" | "cursor" | "gemini";
2
+
3
+ interface CliOptions {
4
+ skills?: string[];
5
+ agents?: AgentId[];
6
+ yes?: boolean;
7
+ }
8
+ declare function runGrooveSkills(options?: CliOptions): Promise<void>;
9
+
10
+ export { type CliOptions, runGrooveSkills };
package/dist/main.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ runGrooveSkills
3
+ } from "./chunk-75476IDS.js";
4
+ export {
5
+ runGrooveSkills
6
+ };
7
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@3lineas/grooveskills",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Multi-agent CLI to install opinionated AI coding skills and architectural standards (Hono, React, Next.js, and more)",
5
+ "type": "module",
6
+ "bin": {
7
+ "grooveskills": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/main.js",
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup --watch",
13
+ "check-types": "tsc --noEmit",
14
+ "prepublishOnly": "pnpm run check-types && pnpm run build"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/3-Lineas/GrooveSkills.git"
22
+ },
23
+ "keywords": [
24
+ "skills",
25
+ "agents",
26
+ "antigravity",
27
+ "claude",
28
+ "cursor",
29
+ "gemini",
30
+ "hono",
31
+ "valibot"
32
+ ],
33
+ "author": "3-Lineas",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "@clack/prompts": "^0.9.1",
37
+ "commander": "^13.1.0",
38
+ "picocolors": "^1.1.1",
39
+ "valibot": "^1.5.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.20.5",
43
+ "tsup": "^8.5.1",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "skills",
49
+ "README.md"
50
+ ]
51
+ }
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: best-practices
3
+ description: Universal coding standards, clean architecture, error handling, and file structure guidelines. TRIGGER when writing, creating, or refactoring ANY file, function, module, or component in ANY programming language (TypeScript, PHP, Dart, Rust, Go, Python, etc.) across the project.
4
+ ---
5
+
6
+ # Universal Best Practices & Clean Architecture
7
+
8
+ Universal guidelines applicable to **any programming language** and **any file** in the project.
9
+
10
+ ---
11
+
12
+ ## 1. Golden Rules (Non-negotiable)
13
+
14
+ 1. **Strict Typing (Zero `any`, Zero `dynamic`)**:
15
+ - Every variable, parameter, property, and return value must have a concrete type.
16
+ - For TypeScript: `any` is forbidden. Use generics or `unknown` with type guards.
17
+ - For Dart: `dynamic` is forbidden. Use generics or `Object`.
18
+ - For PHP: Use strict typing (`declare(strict_types=1);`), typed properties, arguments, and return types.
19
+ - For Rust: Explicit types and structured `Result<T, E>` / `Option<T>`.
20
+
21
+ 2. **Always Use Path Aliases (`@/...`)**:
22
+ - Never use deep relative paths (`../../..`).
23
+ - Use configured aliases (e.g. `@/actions/...`, `@/domains/...`, or language package namespaces).
24
+
25
+ 3. **Zero Barrel Files (No re-exporting `index.ts`)**:
26
+ - Never create `index.ts`, `mod.rs`, or library barrel files whose only purpose is re-exporting.
27
+ - Always import directly and explicitly from the file defining the symbol.
28
+
29
+ 4. **Early Return / Guard Clauses (No Nested `if`)**:
30
+ - Never nest `if` conditions ("pyramid of doom").
31
+ - Validate preconditions first and fail fast (return or throw). The happy path stays unindented.
32
+
33
+ 5. **Single Responsibility Principle (SRP per file)**:
34
+ - Every file must do exactly one thing. One action per action file, one endpoint per route file.
35
+
36
+ 6. **Hard Size Limit: 200 Lines Max**:
37
+ - No file may exceed 200 lines. If a file approaches 200 lines, extract helper logic into dedicated atomic files.
38
+
39
+ 7. **Structured Logging and Safe Error Handling**:
40
+ - Wrap I/O operations (network, database, file system) in `try / catch` or `Result`.
41
+ - Log errors with contextual metadata (`file`, `line`, `details`).
42
+ - Propagate typed errors (`AppError`) up to the controller/router layer.
43
+
44
+ 8. **Polite, Persuasive, and Professional Communication**:
45
+ - User-facing messages, validation feedback, and notifications must be courteous, engaging, solution-oriented, and professional in English.
46
+ - Celebrate user milestones, guide them clearly through errors, and encourage action.
47
+
48
+ ---
49
+
50
+ ## 2. Guard Clauses Example
51
+
52
+ ```ts
53
+ // ✅ CORRECT (Early return / Guard clause):
54
+ export function processTransaction(tx: Transaction): Receipt {
55
+ if (!tx.isValid) {
56
+ throw new AppError({
57
+ message: "Please provide a valid transaction to continue.",
58
+ statusCode: 400,
59
+ });
60
+ }
61
+
62
+ if (tx.isExpired) {
63
+ throw new AppError({
64
+ message: "Your payment session has timed out. Please initiate a new request.",
65
+ statusCode: 410,
66
+ });
67
+ }
68
+
69
+ return executePayment(tx);
70
+ }
71
+
72
+ // ❌ FORBIDDEN (Nested ifs):
73
+ export function processTransaction(tx: Transaction) {
74
+ if (tx.isValid) {
75
+ if (!tx.isExpired) {
76
+ return executePayment(tx);
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 3. Direct Import Example
85
+
86
+ ```ts
87
+ // ✅ CORRECT (Direct import with alias):
88
+ import { registerUserAction } from "@/actions/auth/register-user.action.ts";
89
+ import { AppError } from "@/errors/app-error.ts";
90
+
91
+ // ❌ FORBIDDEN (Barrel import & relative path):
92
+ import { registerUserAction } from "../../actions/auth";
93
+ ```
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: hono
3
+ description: Use when building or modifying Hono APIs, endpoints, middlewares, or actions. TRIGGER when the project imports 'hono', when modifying routes in src/modules/ or actions in src/actions/, or when the user mentions Hono, API endpoints, or routing. Always validate with Valibot (* as v from 'valibot') and adhere to the 'best-practices' skill.
4
+ ---
5
+
6
+ # Hono Architecture & Opinionated Best Practices
7
+
8
+ Standardized guide for building robust, scalable, enterprise-grade Hono applications with isolated domain layers, strict Valibot validation, and typed Actions.
9
+
10
+ ---
11
+
12
+ ## 1. Golden Rules (Non-negotiable)
13
+
14
+ 1. **Standard Schema Validator with Valibot**:
15
+ - Always use **Valibot** (`* as v from "valibot"`) with `@hono/standard-validator` (`sValidator`) or `@hono/valibot-validator`.
16
+ - Never use Zod.
17
+ 2. **Mandatory Path Aliases (`@/...`)**:
18
+ - Import all internal modules using configured path aliases (`@/actions/...`, `@/schemas/...`, `@/errors/...`).
19
+ 3. **Zero Barrel Files**:
20
+ - Never create `index.ts` files that re-export multiple routes or actions.
21
+ - Import directly from each specific action and schema file.
22
+ 4. **Independent Route per File by Domain**:
23
+ - Every single endpoint resides in its own file (`src/modules/{domain}/{action}.ts`).
24
+ - Group them under the domain router `src/modules/{domain}/{domain}.router.ts`.
25
+ 5. **Business Logic Exclusively in Actions**:
26
+ - All business logic lives inside `src/actions/{domain}/{action}.action.ts`.
27
+ - Routers are slim controllers: validate input, call the action, return the response.
28
+ 6. **Database Connections strictly in Actions**:
29
+ - Never execute SQL, D1, Drizzle, or Prisma queries inside routes or middleware.
30
+ - The database instance is injected from Hono context (`ctx.get("db")`) into the action function.
31
+ 7. **Single Responsibility Principle (SRP)**:
32
+ - Each action performs **exactly one** business action.
33
+ 8. **Hard File Limit**:
34
+ - Maximum **200 lines per file**.
35
+ 9. **Unified Error Handling**:
36
+ - Throw typed `AppError` instances for business failures.
37
+ - Wrap I/O operations in `try / catch`, log with structured `Logger`, and re-throw or propagate to `globalErrorHandler`.
38
+ 10. **Courteous and Professional User Feedback**:
39
+ - Return polite, engaging, and solution-oriented messages in English (`ActionResponse<T>`).
40
+
41
+ ---
42
+
43
+ ## 2. Directory Taxonomy
44
+
45
+ ```text
46
+ apps/api/src/
47
+ ├── actions/ # Pure business logic layer
48
+ │ └── auth/
49
+ │ ├── register-user.action.ts # One atomic action (< 100 lines)
50
+ │ └── login-user.action.ts
51
+ ├── modules/ # Thin HTTP controllers
52
+ │ └── auth/
53
+ │ ├── register-user.ts # Endpoint definition (< 80 lines)
54
+ │ └── auth.router.ts # Domain router mounting endpoints (< 60 lines)
55
+ ├── schemas/ # Valibot schemas
56
+ │ └── auth/
57
+ │ └── register-user.schema.ts # Strict input schema (< 50 lines)
58
+ ├── errors/
59
+ │ ├── app-error.ts # Base application error with auto-logging
60
+ │ └── error-handler.ts # Central Hono error handler
61
+ └── routers/
62
+ └── api.router.ts # Root router exporting ApiRouter type for RPC
63
+ ```
64
+
65
+ ---
66
+
67
+ ## 3. Valibot Validation Example
68
+
69
+ ```ts
70
+ import { sValidator } from "@hono/standard-validator";
71
+ import * as v from "valibot";
72
+
73
+ export const loginSchema = v.object({
74
+ email: v.pipe(
75
+ v.string("Please provide your email address."),
76
+ v.email("Please provide a valid email address.")
77
+ ),
78
+ password: v.pipe(
79
+ v.string("Password is required."),
80
+ v.minLength(8, "Your password must be at least 8 characters long.")
81
+ ),
82
+ });
83
+
84
+ export type LoginInput = v.InferOutput<typeof loginSchema>;
85
+ ```
86
+
87
+ ---
88
+
89
+ ## 4. Standard Response Contract
90
+
91
+ ```ts
92
+ export interface ActionResponse<T = unknown> {
93
+ success: boolean;
94
+ message?: string;
95
+ error?: string;
96
+ data?: T;
97
+ }
98
+ ```
@@ -0,0 +1,47 @@
1
+ # Action Template (Single Responsibility Principle)
2
+
3
+ Atomic business logic decoupled from HTTP context. Database queries must be isolated inside Actions.
4
+
5
+ ```ts
6
+ import { AppError } from "@/errors/app-error.ts";
7
+ import { Logger } from "@/logger/logger.ts";
8
+ import type { RegisterUserInput } from "@/schemas/auth/register-user.schema.ts";
9
+
10
+ export interface DatabaseConnection {
11
+ findUserByEmail(email: string): Promise<{ id: string; email: string } | null>;
12
+ createUser(data: RegisterUserInput): Promise<{ id: string; email: string; fullName: string }>;
13
+ }
14
+
15
+ export async function registerUserAction(
16
+ db: DatabaseConnection,
17
+ input: RegisterUserInput
18
+ ): Promise<{ id: string; email: string; fullName: string }> {
19
+ const logger = new Logger("Auth:Action:RegisterUser");
20
+
21
+ try {
22
+ const existing = await db.findUserByEmail(input.email);
23
+
24
+ if (existing) {
25
+ throw new AppError({
26
+ message: "An account with this email address already exists. Please sign in or use another email.",
27
+ code: "USER_ALREADY_EXISTS",
28
+ statusCode: 409,
29
+ silent: true,
30
+ });
31
+ }
32
+
33
+ return await db.createUser(input);
34
+ } catch (error) {
35
+ if (error instanceof AppError) {
36
+ throw error;
37
+ }
38
+
39
+ logger.error("Database connection failure while registering user", { file: "register-user.action.ts" }, error);
40
+ throw new AppError({
41
+ message: "We encountered a temporary issue while creating your account. Please try again shortly.",
42
+ code: "DATABASE_ERROR",
43
+ statusCode: 500,
44
+ });
45
+ }
46
+ }
47
+ ```
@@ -0,0 +1,45 @@
1
+ # AppError Template
2
+
3
+ Use this template to create application errors with automatic logging.
4
+
5
+ ```ts
6
+ import type { ContentfulStatusCode } from "hono/utils/http-status";
7
+ import { Logger } from "@/logger/logger.ts";
8
+
9
+ export interface AppErrorParams {
10
+ message: string;
11
+ statusCode?: ContentfulStatusCode;
12
+ code?: string;
13
+ details?: unknown;
14
+ silent?: boolean;
15
+ }
16
+
17
+ export class AppError extends Error {
18
+ public statusCode: ContentfulStatusCode;
19
+ public code: string;
20
+ public details?: unknown;
21
+
22
+ constructor({
23
+ message,
24
+ statusCode = 400,
25
+ code = "BAD_REQUEST",
26
+ details,
27
+ silent = false,
28
+ }: AppErrorParams) {
29
+ super(message);
30
+ this.name = "AppError";
31
+ this.statusCode = statusCode;
32
+ this.code = code;
33
+ this.details = details;
34
+
35
+ if (silent) {
36
+ return;
37
+ }
38
+
39
+ const logger = new Logger(`API:Error:${this.name}`);
40
+ logger.warn(`${code}: ${message}`, {
41
+ details: details as Record<string, unknown>,
42
+ });
43
+ }
44
+ }
45
+ ```
@@ -0,0 +1,52 @@
1
+ # Global Error Handler Template
2
+
3
+ Centralized Hono error handler for AppError, Valibot validation issues, and unexpected crashes.
4
+
5
+ ```ts
6
+ import type { ErrorHandler } from "hono";
7
+ import { AppError } from "@/errors/app-error.ts";
8
+ import { Logger } from "@/logger/logger.ts";
9
+
10
+ export const globalErrorHandler: ErrorHandler = (error, ctx) => {
11
+ const logger = new Logger("API:GlobalErrorHandler");
12
+ const logContext = {
13
+ file: "error-handler.ts",
14
+ method: ctx.req.method,
15
+ url: ctx.req.url,
16
+ };
17
+
18
+ if (error instanceof AppError) {
19
+ return ctx.json(
20
+ {
21
+ success: false,
22
+ message: error.message,
23
+ error: error.code,
24
+ },
25
+ error.statusCode
26
+ );
27
+ }
28
+
29
+ if (error.name === "ValiError" || error.message.includes("validation")) {
30
+ logger.warn("Validation error encountered", logContext, error);
31
+ return ctx.json(
32
+ {
33
+ success: false,
34
+ message: "Some submitted fields are incomplete or invalid. Please check your data and try again.",
35
+ error: "VALIDATION_ERROR",
36
+ },
37
+ 400
38
+ );
39
+ }
40
+
41
+ logger.error("Unhandled server exception caught in router", logContext, error);
42
+
43
+ return ctx.json(
44
+ {
45
+ success: false,
46
+ message: "We encountered an unexpected server issue. Our team is already looking into it. Please try again soon.",
47
+ error: "INTERNAL_SERVER_ERROR",
48
+ },
49
+ 500
50
+ );
51
+ };
52
+ ```
@@ -0,0 +1,94 @@
1
+ # Logger Template
2
+
3
+ Universal structured logger for development (ANSI colored) and Cloudflare Workers (structured JSON).
4
+
5
+ ```ts
6
+ export type LogLevel = "debug" | "info" | "warn" | "error";
7
+
8
+ export interface LogContext {
9
+ file?: string;
10
+ line?: number;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ export class Logger {
15
+ private serviceName: string;
16
+
17
+ constructor(serviceName: string) {
18
+ this.serviceName = serviceName;
19
+ }
20
+
21
+ private isDev(): boolean {
22
+ if (typeof process !== "undefined" && process.env) {
23
+ return (
24
+ process.env.NODE_ENV === "development" ||
25
+ process.env.NODE_ENV === "test"
26
+ );
27
+ }
28
+ return true;
29
+ }
30
+
31
+ private log(
32
+ level: LogLevel,
33
+ message: string,
34
+ context?: LogContext,
35
+ ...args: unknown[]
36
+ ): void {
37
+ const timestamp = new Date().toISOString();
38
+
39
+ if (this.isDev()) {
40
+ const colors: Record<LogLevel, string> = {
41
+ debug: "\x1b[36m",
42
+ info: "\x1b[32m",
43
+ warn: "\x1b[33m",
44
+ error: "\x1b[31m",
45
+ };
46
+ const reset = "\x1b[0m";
47
+ const color = colors[level] || reset;
48
+ const origin = context?.file ? ` [${context.file}${context.line ? `:${context.line}` : ""}]` : "";
49
+
50
+ console.log(
51
+ `${color}[${timestamp}] [${level.toUpperCase()}] [${this.serviceName}]${origin}:${reset} ${message}`,
52
+ context ?? "",
53
+ ...args,
54
+ );
55
+ return;
56
+ }
57
+
58
+ const payload = {
59
+ timestamp,
60
+ level,
61
+ serviceName: this.serviceName,
62
+ message,
63
+ context,
64
+ details: args.length > 0 ? args : undefined,
65
+ };
66
+
67
+ if (level === "error") {
68
+ console.error(JSON.stringify(payload));
69
+ return;
70
+ }
71
+ if (level === "warn") {
72
+ console.warn(JSON.stringify(payload));
73
+ return;
74
+ }
75
+ console.log(JSON.stringify(payload));
76
+ }
77
+
78
+ debug(message: string, context?: LogContext, ...args: unknown[]): void {
79
+ this.log("debug", message, context, ...args);
80
+ }
81
+
82
+ info(message: string, context?: LogContext, ...args: unknown[]): void {
83
+ this.log("info", message, context, ...args);
84
+ }
85
+
86
+ warn(message: string, context?: LogContext, ...args: unknown[]): void {
87
+ this.log("warn", message, context, ...args);
88
+ }
89
+
90
+ error(message: string, context?: LogContext, ...args: unknown[]): void {
91
+ this.log("error", message, context, ...args);
92
+ }
93
+ }
94
+ ```
@@ -0,0 +1,35 @@
1
+ # Hono Route Template
2
+
3
+ Slim HTTP controller that validates request body with Valibot and delegates directly to the Action.
4
+
5
+ ```ts
6
+ import { Hono } from "hono";
7
+ import { sValidator } from "@hono/standard-validator";
8
+ import { registerUserSchema } from "@/schemas/auth/register-user.schema.ts";
9
+ import { registerUserAction } from "@/actions/auth/register-user.action.ts";
10
+ import type { DatabaseConnection } from "@/actions/auth/register-user.action.ts";
11
+
12
+ export interface CustomVariables {
13
+ db: DatabaseConnection;
14
+ }
15
+
16
+ export const registerUserRoute = new Hono<{ Variables: CustomVariables }>().post(
17
+ "/",
18
+ sValidator("json", registerUserSchema),
19
+ async (ctx) => {
20
+ const input = ctx.req.valid("json");
21
+ const db = ctx.get("db");
22
+
23
+ const createdUser = await registerUserAction(db, input);
24
+
25
+ return ctx.json(
26
+ {
27
+ success: true,
28
+ message: "Welcome aboard! Your account has been created successfully.",
29
+ data: createdUser,
30
+ },
31
+ 201
32
+ );
33
+ }
34
+ );
35
+ ```
@@ -0,0 +1,24 @@
1
+ # Valibot Schema Template
2
+
3
+ Type-safe schema validation with courteous, persuasive, and professional error messages.
4
+
5
+ ```ts
6
+ import * as v from "valibot";
7
+
8
+ export const registerUserSchema = v.object({
9
+ email: v.pipe(
10
+ v.string("Please provide your email address."),
11
+ v.email("Please enter a valid email address to continue.")
12
+ ),
13
+ password: v.pipe(
14
+ v.string("A secure password is required."),
15
+ v.minLength(8, "For your account safety, your password must be at least 8 characters long.")
16
+ ),
17
+ fullName: v.pipe(
18
+ v.string("Please share your name with us."),
19
+ v.minLength(2, "Your name should be at least 2 characters long.")
20
+ ),
21
+ });
22
+
23
+ export type RegisterUserInput = v.InferOutput<typeof registerUserSchema>;
24
+ ```