@3lineas/grooveskills 1.0.3 → 1.1.2
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 +57 -19
- package/dist/chunk-5Q6O3BRA.js +737 -0
- package/dist/chunk-5Q6O3BRA.js.map +1 -0
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/skills/groove-harness/SKILL.md +50 -0
- package/dist/chunk-6FZFZCBX.js +0 -473
- package/dist/chunk-6FZFZCBX.js.map +0 -1
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
// src/domains/detector/project-detector.ts
|
|
2
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3
|
+
import { join as join2 } from "path";
|
|
4
|
+
|
|
5
|
+
// src/domains/detector/detect-agents.ts
|
|
6
|
+
import { readdirSync } from "fs";
|
|
7
|
+
function detectAgents(targetPath) {
|
|
8
|
+
const detected = [];
|
|
9
|
+
try {
|
|
10
|
+
const entries = new Set(readdirSync(targetPath));
|
|
11
|
+
if (entries.has(".agents")) {
|
|
12
|
+
detected.push("antigravity");
|
|
13
|
+
}
|
|
14
|
+
if (entries.has(".claude")) {
|
|
15
|
+
detected.push("claude");
|
|
16
|
+
}
|
|
17
|
+
if (entries.has(".cursor")) {
|
|
18
|
+
detected.push("cursor");
|
|
19
|
+
}
|
|
20
|
+
if (entries.has(".gemini")) {
|
|
21
|
+
detected.push("gemini");
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
return detected;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/domains/detector/detect-dependencies.ts
|
|
29
|
+
import { readFileSync, existsSync } from "fs";
|
|
30
|
+
import { join } from "path";
|
|
31
|
+
function detectDependencies(targetPath) {
|
|
32
|
+
const packageJsonPath = join(targetPath, "package.json");
|
|
33
|
+
if (!existsSync(packageJsonPath)) {
|
|
34
|
+
return { dependencies: [], exists: false };
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const raw = readFileSync(packageJsonPath, "utf-8");
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
const direct = Object.keys(parsed.dependencies ?? {});
|
|
40
|
+
const dev = Object.keys(parsed.devDependencies ?? {});
|
|
41
|
+
const all = Array.from(/* @__PURE__ */ new Set([...direct, ...dev]));
|
|
42
|
+
return { dependencies: all, exists: true };
|
|
43
|
+
} catch {
|
|
44
|
+
return { dependencies: [], exists: true };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/domains/registry/skill-catalog.ts
|
|
49
|
+
var SKILL_CATALOG = [
|
|
50
|
+
{
|
|
51
|
+
id: "groove-harness",
|
|
52
|
+
name: "GrooveSkills Harness",
|
|
53
|
+
description: "Lightweight project harness, context memory, and 3-lane task execution (Answer, Task, Spec) for AI coding agents",
|
|
54
|
+
dependencies: [],
|
|
55
|
+
directoryName: "groove-harness"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "best-practices",
|
|
59
|
+
name: "Universal Best Practices",
|
|
60
|
+
description: "Multi-language clean code standards, early returns, no barrel files, strict typing, <200 lines",
|
|
61
|
+
dependencies: [],
|
|
62
|
+
directoryName: "best-practices"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "hono",
|
|
66
|
+
name: "Hono Architecture (Valibot)",
|
|
67
|
+
description: "Enterprise Hono API standard with Valibot validation, isolated Actions layer, and RPC bindings",
|
|
68
|
+
dependencies: ["hono", "@hono/valibot-validator", "@hono/standard-validator"],
|
|
69
|
+
directoryName: "hono"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "react-core",
|
|
73
|
+
name: "React Core Fundamentals",
|
|
74
|
+
description: "Opinionated React fundamentals: function components, JSX, immutable state, core hooks, lists, and controlled inputs",
|
|
75
|
+
dependencies: ["react", "react-dom"],
|
|
76
|
+
directoryName: "react-core"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "react-advanced",
|
|
80
|
+
name: "React 19 Advanced & Compiler Architecture",
|
|
81
|
+
description: "Modern React 19 platform features: React Compiler auto-memoization, concurrent rendering, use(), Actions, virtualization, and RSC boundaries",
|
|
82
|
+
dependencies: ["react", "react-dom"],
|
|
83
|
+
directoryName: "react-advanced"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "react-create-page",
|
|
87
|
+
name: "React Page & View Architecture",
|
|
88
|
+
description: "Clean architecture for React pages: declarative page coordinators, domain hooks, isolated API actions, Valibot + Formisch forms, and atomic co-location",
|
|
89
|
+
dependencies: ["react", "react-dom"],
|
|
90
|
+
directoryName: "react-create-page"
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// src/domains/detector/project-detector.ts
|
|
95
|
+
function detectProjectContext(targetPath) {
|
|
96
|
+
const detectedAgents = detectAgents(targetPath);
|
|
97
|
+
const { dependencies, exists } = detectDependencies(targetPath);
|
|
98
|
+
const suggestedSkillIds = ["best-practices"];
|
|
99
|
+
for (const skill of SKILL_CATALOG) {
|
|
100
|
+
if (skill.dependencies.length === 0) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const hasMatch = skill.dependencies.some((dep) => dependencies.includes(dep));
|
|
104
|
+
if (hasMatch && !suggestedSkillIds.includes(skill.id)) {
|
|
105
|
+
suggestedSkillIds.push(skill.id);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const harnessExists = existsSync2(join2(targetPath, ".grooveskills", "memory", "context.md"));
|
|
109
|
+
return {
|
|
110
|
+
targetPath,
|
|
111
|
+
detectedDependencies: dependencies,
|
|
112
|
+
suggestedSkillIds,
|
|
113
|
+
detectedAgents,
|
|
114
|
+
packageJsonExists: exists,
|
|
115
|
+
harnessExists
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/domains/ui/ui-orchestrator.ts
|
|
120
|
+
import * as p5 from "@clack/prompts";
|
|
121
|
+
import pc2 from "picocolors";
|
|
122
|
+
|
|
123
|
+
// src/domains/ui/banners.ts
|
|
124
|
+
import pc from "picocolors";
|
|
125
|
+
function renderBanner() {
|
|
126
|
+
const title = pc.bold(pc.white(" GROOVE SKILLS "));
|
|
127
|
+
const badge = pc.bgMagenta(pc.black(title));
|
|
128
|
+
const subtitle = pc.dim("Multi-Agent AI Skills & Architectural Standards");
|
|
129
|
+
return `${badge} ${subtitle}
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/domains/ui/prompt-skills.ts
|
|
134
|
+
import * as p from "@clack/prompts";
|
|
135
|
+
async function promptSkills(suggestedSkillIds) {
|
|
136
|
+
const options = SKILL_CATALOG.map((skill) => {
|
|
137
|
+
const isSuggested = suggestedSkillIds.includes(skill.id);
|
|
138
|
+
return {
|
|
139
|
+
value: skill.id,
|
|
140
|
+
label: skill.name,
|
|
141
|
+
hint: isSuggested ? "Suggested based on your project stack" : skill.description
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
const selected = await p.multiselect({
|
|
145
|
+
message: "Which skills would you like to install in this project?",
|
|
146
|
+
options,
|
|
147
|
+
initialValues: suggestedSkillIds,
|
|
148
|
+
required: true
|
|
149
|
+
});
|
|
150
|
+
return selected;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/domains/ui/prompt-agents.ts
|
|
154
|
+
import * as p2 from "@clack/prompts";
|
|
155
|
+
async function promptAgents(detectedAgents) {
|
|
156
|
+
const options = [
|
|
157
|
+
{ value: "antigravity", label: "Antigravity", hint: ".agents/skills (Google Agentic IDE)" },
|
|
158
|
+
{ value: "claude", label: "Claude Code", hint: ".claude/skills (Anthropic Claude Code)" },
|
|
159
|
+
{ value: "cursor", label: "Cursor", hint: ".cursor/rules/*.mdc (Cursor IDE Rules)" },
|
|
160
|
+
{ value: "gemini", label: "Gemini Code Assist", hint: ".gemini/skills (Google Gemini)" }
|
|
161
|
+
];
|
|
162
|
+
const initialValues = detectedAgents.length > 0 ? detectedAgents : ["antigravity"];
|
|
163
|
+
const selected = await p2.multiselect({
|
|
164
|
+
message: "Which AI agents should be configured?",
|
|
165
|
+
options,
|
|
166
|
+
initialValues,
|
|
167
|
+
required: true
|
|
168
|
+
});
|
|
169
|
+
return selected;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/domains/ui/prompt-harness.ts
|
|
173
|
+
import * as p3 from "@clack/prompts";
|
|
174
|
+
async function promptHarness(harnessExists) {
|
|
175
|
+
const message = harnessExists ? "GrooveSkills Harness is already initialized. Would you like to update agent rules and memory templates?" : "Would you like to initialize the GrooveSkills Harness in .grooveskills/ (project memory, ADR, and 3-lane task execution)?";
|
|
176
|
+
const confirm3 = await p3.confirm({
|
|
177
|
+
message,
|
|
178
|
+
initialValue: true
|
|
179
|
+
});
|
|
180
|
+
return confirm3;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/domains/ui/prompt-confirm-overwrite.ts
|
|
184
|
+
import * as p4 from "@clack/prompts";
|
|
185
|
+
async function promptConfirmOverwrite() {
|
|
186
|
+
return await p4.confirm({
|
|
187
|
+
message: "Some skills already exist in this project. Would you like to overwrite them with the latest version?",
|
|
188
|
+
initialValue: true
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/domains/installer/verify-skill-exists.ts
|
|
193
|
+
import { lstatSync } from "fs";
|
|
194
|
+
import { join as join3 } from "path";
|
|
195
|
+
var AGENT_DESTINATION_MAP = {
|
|
196
|
+
antigravity: (dir) => join3(".agents", "skills", dir),
|
|
197
|
+
claude: (dir) => join3(".claude", "skills", dir),
|
|
198
|
+
cursor: (dir) => join3(".cursor", "rules", `${dir}.mdc`),
|
|
199
|
+
gemini: (dir) => join3(".gemini", "skills", dir)
|
|
200
|
+
};
|
|
201
|
+
function verifySkillExists(targetPath, skillDirectoryName, agents) {
|
|
202
|
+
for (const agent of agents) {
|
|
203
|
+
const relativePathResolver = AGENT_DESTINATION_MAP[agent];
|
|
204
|
+
const destination = join3(targetPath, relativePathResolver(skillDirectoryName));
|
|
205
|
+
try {
|
|
206
|
+
lstatSync(destination);
|
|
207
|
+
return true;
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/domains/installer/install-orchestrator.ts
|
|
215
|
+
import { join as join13 } from "path";
|
|
216
|
+
|
|
217
|
+
// src/domains/adapters/antigravity.adapter.ts
|
|
218
|
+
import { mkdirSync, existsSync as existsSync3 } from "fs";
|
|
219
|
+
import { join as join5 } from "path";
|
|
220
|
+
|
|
221
|
+
// src/domains/project-store/project-paths.ts
|
|
222
|
+
import { join as join4 } from "path";
|
|
223
|
+
function getProjectStoreDir(targetPath) {
|
|
224
|
+
return join4(targetPath, ".grooveskills");
|
|
225
|
+
}
|
|
226
|
+
function getProjectSkillsDir(targetPath) {
|
|
227
|
+
return join4(getProjectStoreDir(targetPath), "skills");
|
|
228
|
+
}
|
|
229
|
+
function getProjectCursorRulesDir(targetPath) {
|
|
230
|
+
return join4(getProjectStoreDir(targetPath), "cursor-rules");
|
|
231
|
+
}
|
|
232
|
+
function getProjectMemoryDir(targetPath) {
|
|
233
|
+
return join4(getProjectStoreDir(targetPath), "memory");
|
|
234
|
+
}
|
|
235
|
+
function getProjectTasksDir(targetPath) {
|
|
236
|
+
return join4(getProjectMemoryDir(targetPath), "tasks");
|
|
237
|
+
}
|
|
238
|
+
function getProjectConfigPath(targetPath) {
|
|
239
|
+
return join4(getProjectStoreDir(targetPath), "config.json");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/domains/adapters/link-helper.ts
|
|
243
|
+
import { lstatSync as lstatSync2, unlinkSync, rmSync, symlinkSync } from "fs";
|
|
244
|
+
import { dirname, relative } from "path";
|
|
245
|
+
function removeExistingLinkPath(path) {
|
|
246
|
+
try {
|
|
247
|
+
const lstat = lstatSync2(path);
|
|
248
|
+
if (lstat.isDirectory() && !lstat.isSymbolicLink()) {
|
|
249
|
+
rmSync(path, { recursive: true, force: true });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
unlinkSync(path);
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function createOrReplaceSymlink(target, linkPath, type) {
|
|
257
|
+
removeExistingLinkPath(linkPath);
|
|
258
|
+
const linkDir = dirname(linkPath);
|
|
259
|
+
const relativeTarget = relative(linkDir, target);
|
|
260
|
+
symlinkSync(relativeTarget, linkPath, type);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/domains/adapters/antigravity.adapter.ts
|
|
264
|
+
var AntigravityAdapter = class {
|
|
265
|
+
id = "antigravity";
|
|
266
|
+
name = "Antigravity";
|
|
267
|
+
targetRelativeDir = ".agents/skills";
|
|
268
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
269
|
+
const parentDir = join5(targetPath, this.targetRelativeDir);
|
|
270
|
+
if (!existsSync3(parentDir)) {
|
|
271
|
+
mkdirSync(parentDir, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
const destination = join5(parentDir, skill.directoryName);
|
|
274
|
+
const projectSkillDir = join5(getProjectSkillsDir(targetPath), skill.directoryName);
|
|
275
|
+
createOrReplaceSymlink(projectSkillDir, destination, "dir");
|
|
276
|
+
return [destination];
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/domains/adapters/claude.adapter.ts
|
|
281
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
282
|
+
import { join as join6 } from "path";
|
|
283
|
+
var ClaudeAdapter = class {
|
|
284
|
+
id = "claude";
|
|
285
|
+
name = "Claude Code";
|
|
286
|
+
targetRelativeDir = ".claude/skills";
|
|
287
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
288
|
+
const parentDir = join6(targetPath, this.targetRelativeDir);
|
|
289
|
+
if (!existsSync4(parentDir)) {
|
|
290
|
+
mkdirSync2(parentDir, { recursive: true });
|
|
291
|
+
}
|
|
292
|
+
const destination = join6(parentDir, skill.directoryName);
|
|
293
|
+
const projectSkillDir = join6(getProjectSkillsDir(targetPath), skill.directoryName);
|
|
294
|
+
createOrReplaceSymlink(projectSkillDir, destination, "dir");
|
|
295
|
+
return [destination];
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/domains/adapters/cursor.adapter.ts
|
|
300
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
301
|
+
import { join as join7 } from "path";
|
|
302
|
+
var CursorAdapter = class {
|
|
303
|
+
id = "cursor";
|
|
304
|
+
name = "Cursor";
|
|
305
|
+
targetRelativeDir = ".cursor/rules";
|
|
306
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
307
|
+
const rulesDir = join7(targetPath, this.targetRelativeDir);
|
|
308
|
+
if (!existsSync5(rulesDir)) {
|
|
309
|
+
mkdirSync3(rulesDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
const filename = `${skill.id}.mdc`;
|
|
312
|
+
const destination = join7(rulesDir, filename);
|
|
313
|
+
const projectRuleFile = join7(getProjectCursorRulesDir(targetPath), filename);
|
|
314
|
+
createOrReplaceSymlink(projectRuleFile, destination, "file");
|
|
315
|
+
return [destination];
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// src/domains/adapters/gemini.adapter.ts
|
|
320
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
|
|
321
|
+
import { join as join8 } from "path";
|
|
322
|
+
var GeminiAdapter = class {
|
|
323
|
+
id = "gemini";
|
|
324
|
+
name = "Gemini Code Assist";
|
|
325
|
+
targetRelativeDir = ".gemini/skills";
|
|
326
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
327
|
+
const parentDir = join8(targetPath, this.targetRelativeDir);
|
|
328
|
+
if (!existsSync6(parentDir)) {
|
|
329
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
330
|
+
}
|
|
331
|
+
const destination = join8(parentDir, skill.directoryName);
|
|
332
|
+
const projectSkillDir = join8(getProjectSkillsDir(targetPath), skill.directoryName);
|
|
333
|
+
createOrReplaceSymlink(projectSkillDir, destination, "dir");
|
|
334
|
+
return [destination];
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/domains/project-store/sync-project-store.ts
|
|
339
|
+
import { cpSync, mkdirSync as mkdirSync5, existsSync as existsSync8, writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
340
|
+
import { join as join10 } from "path";
|
|
341
|
+
|
|
342
|
+
// src/domains/installer/resolve-skills-source.ts
|
|
343
|
+
import { fileURLToPath } from "url";
|
|
344
|
+
import { dirname as dirname2, resolve, join as join9 } from "path";
|
|
345
|
+
import { existsSync as existsSync7 } from "fs";
|
|
346
|
+
function resolveSkillsSourceRoot() {
|
|
347
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
348
|
+
const currentDir = dirname2(currentFilePath);
|
|
349
|
+
const candidateFromDist = resolve(currentDir, "../skills");
|
|
350
|
+
if (existsSync7(candidateFromDist)) {
|
|
351
|
+
return candidateFromDist;
|
|
352
|
+
}
|
|
353
|
+
const candidateFromSrc = resolve(currentDir, "../../../skills");
|
|
354
|
+
if (existsSync7(candidateFromSrc)) {
|
|
355
|
+
return candidateFromSrc;
|
|
356
|
+
}
|
|
357
|
+
let searchDir = currentDir;
|
|
358
|
+
for (let i = 0; i < 5; i++) {
|
|
359
|
+
const candidate = join9(searchDir, "skills");
|
|
360
|
+
if (existsSync7(candidate)) {
|
|
361
|
+
return candidate;
|
|
362
|
+
}
|
|
363
|
+
const parent = dirname2(searchDir);
|
|
364
|
+
if (parent === searchDir) {
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
searchDir = parent;
|
|
368
|
+
}
|
|
369
|
+
throw new Error(
|
|
370
|
+
`GrooveSkills internal error: Unable to locate bundled skills directory from ${currentDir}`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/domains/project-store/sync-project-store.ts
|
|
375
|
+
function syncProjectStore(targetPath, selectedSkillIds, selectedAgents) {
|
|
376
|
+
const bundledSkillsDir = resolveSkillsSourceRoot();
|
|
377
|
+
const projectSkillsDir = getProjectSkillsDir(targetPath);
|
|
378
|
+
const projectCursorRulesDir = getProjectCursorRulesDir(targetPath);
|
|
379
|
+
if (!existsSync8(projectSkillsDir)) {
|
|
380
|
+
mkdirSync5(projectSkillsDir, { recursive: true });
|
|
381
|
+
}
|
|
382
|
+
const needsCursorRules = selectedAgents.includes("cursor");
|
|
383
|
+
if (needsCursorRules && !existsSync8(projectCursorRulesDir)) {
|
|
384
|
+
mkdirSync5(projectCursorRulesDir, { recursive: true });
|
|
385
|
+
}
|
|
386
|
+
for (const skillId of selectedSkillIds) {
|
|
387
|
+
const skill = SKILL_CATALOG.find((item) => item.id === skillId);
|
|
388
|
+
if (!skill) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const sourceSkillDir = join10(bundledSkillsDir, skill.directoryName);
|
|
392
|
+
const destSkillDir = join10(projectSkillsDir, skill.directoryName);
|
|
393
|
+
if (existsSync8(sourceSkillDir)) {
|
|
394
|
+
cpSync(sourceSkillDir, destSkillDir, { recursive: true });
|
|
395
|
+
}
|
|
396
|
+
if (!needsCursorRules) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const skillMdPath = join10(destSkillDir, "SKILL.md");
|
|
400
|
+
if (!existsSync8(skillMdPath)) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const content = readFileSync2(skillMdPath, "utf-8");
|
|
404
|
+
const mdcHeader = `---
|
|
405
|
+
description: ${skill.description}
|
|
406
|
+
globs: ["**/*"]
|
|
407
|
+
alwaysApply: false
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
`;
|
|
411
|
+
const cleanedBody = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
412
|
+
const targetMdc = join10(projectCursorRulesDir, `${skill.id}.mdc`);
|
|
413
|
+
writeFileSync(targetMdc, `${mdcHeader}${cleanedBody}`, "utf-8");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/domains/harness/install-harness.ts
|
|
418
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
419
|
+
import { join as join12 } from "path";
|
|
420
|
+
|
|
421
|
+
// src/domains/harness/harness-templates.ts
|
|
422
|
+
function buildContextTemplate(detectedDependencies) {
|
|
423
|
+
const depsFormatted = detectedDependencies.length > 0 ? detectedDependencies.map((d) => `- \`${d}\``).join("\n") : "- (No external dependencies detected)";
|
|
424
|
+
return `# Project Context & Architecture
|
|
425
|
+
|
|
426
|
+
This file provides AI agents with instant architectural orientation for this project.
|
|
427
|
+
|
|
428
|
+
## 1. Stack & Dependencies
|
|
429
|
+
${depsFormatted}
|
|
430
|
+
|
|
431
|
+
## 2. Directory Structure Conventions
|
|
432
|
+
- Follow single-responsibility per file (<200 lines).
|
|
433
|
+
- Maintain direct imports with path aliases; avoid barrel files.
|
|
434
|
+
- Keep domain boundaries explicit and clean.
|
|
435
|
+
|
|
436
|
+
## 3. Essential Commands
|
|
437
|
+
- Build: Refer to \`package.json\` scripts.
|
|
438
|
+
- Typecheck: Refer to TypeScript configuration.
|
|
439
|
+
- Tests: Refer to test runner configuration.
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
function buildDecisionsTemplate() {
|
|
443
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
444
|
+
return `# Architecture Decision Records (ADR)
|
|
445
|
+
|
|
446
|
+
An append-only log of significant technical and architectural choices.
|
|
447
|
+
|
|
448
|
+
| Date | Title | Decision | Reason & Consequences |
|
|
449
|
+
| :--- | :--- | :--- | :--- |
|
|
450
|
+
| ${now} | Adopt GrooveSkills Harness | Initialized \`.grooveskills/\` control plane | Clean, encapsulated agent memory and 3-lane execution flow |
|
|
451
|
+
`;
|
|
452
|
+
}
|
|
453
|
+
function buildTaskTemplate() {
|
|
454
|
+
return `---
|
|
455
|
+
status: in-progress
|
|
456
|
+
createdAt: \${CURRENT_DATE}
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
# Feature / Task Title
|
|
460
|
+
|
|
461
|
+
## Intent
|
|
462
|
+
Describe what changes are being made and why they are necessary.
|
|
463
|
+
|
|
464
|
+
## Scope
|
|
465
|
+
- In scope:
|
|
466
|
+
- Out of scope:
|
|
467
|
+
|
|
468
|
+
## Checklist
|
|
469
|
+
- [ ] Task 1: (Define expected observed proof)
|
|
470
|
+
- [ ] Task 2: (Define expected observed proof)
|
|
471
|
+
|
|
472
|
+
## Evidence
|
|
473
|
+
- Observed test/typecheck/build commands or runtime outputs proving completion.
|
|
474
|
+
|
|
475
|
+
## Next
|
|
476
|
+
- Next concrete step if interrupted.
|
|
477
|
+
`;
|
|
478
|
+
}
|
|
479
|
+
function buildAgentHarnessBlock() {
|
|
480
|
+
return `<!-- grooveskills:harness:start -->
|
|
481
|
+
# GrooveSkills Harness \u2014 Agent Rules & Memory
|
|
482
|
+
|
|
483
|
+
This project uses the **GrooveSkills Harness** located in \`.grooveskills/\`.
|
|
484
|
+
|
|
485
|
+
## 1. Three-Lane Rule
|
|
486
|
+
- **Answer**: Explanations, audits, or questions. Read-only; write zero code.
|
|
487
|
+
- **Task**: Direct implementation. Record intent, scope, and checklist in \`.grooveskills/memory/tasks/<slug>.md\`. Only check off tasks with observed evidence under \`Evidence\`.
|
|
488
|
+
- **Spec**: Ambiguous or high-impact architecture. Clarify and validate the spec in \`.grooveskills/memory/tasks/<slug>.md\` before writing code.
|
|
489
|
+
|
|
490
|
+
## 2. Memory Locations
|
|
491
|
+
- Architecture context: \`.grooveskills/memory/context.md\`
|
|
492
|
+
- Technical decisions (ADR): \`.grooveskills/memory/decisions.md\`
|
|
493
|
+
- Active tasks & evidence: \`.grooveskills/memory/tasks/\`
|
|
494
|
+
<!-- grooveskills:harness:end -->`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/domains/harness/upsert-agent-instructions.ts
|
|
498
|
+
import { existsSync as existsSync9, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
499
|
+
import { join as join11 } from "path";
|
|
500
|
+
var START_MARKER = "<!-- grooveskills:harness:start -->";
|
|
501
|
+
var END_MARKER = "<!-- grooveskills:harness:end -->";
|
|
502
|
+
function applyHarnessBlock(filePath) {
|
|
503
|
+
const block = buildAgentHarnessBlock();
|
|
504
|
+
if (!existsSync9(filePath)) {
|
|
505
|
+
writeFileSync2(filePath, `${block}
|
|
506
|
+
`, "utf-8");
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const existingContent = readFileSync3(filePath, "utf-8");
|
|
510
|
+
if (existingContent.includes(START_MARKER) && existingContent.includes(END_MARKER)) {
|
|
511
|
+
const regex = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}`, "g");
|
|
512
|
+
const updated = existingContent.replace(regex, block);
|
|
513
|
+
writeFileSync2(filePath, updated, "utf-8");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
517
|
+
writeFileSync2(filePath, `${existingContent}${separator}${block}
|
|
518
|
+
`, "utf-8");
|
|
519
|
+
}
|
|
520
|
+
function upsertAgentInstructions(targetPath, selectedAgents) {
|
|
521
|
+
const updatedFiles = [];
|
|
522
|
+
const agentsMdPath = join11(targetPath, "AGENTS.md");
|
|
523
|
+
applyHarnessBlock(agentsMdPath);
|
|
524
|
+
updatedFiles.push(agentsMdPath);
|
|
525
|
+
if (selectedAgents.includes("claude")) {
|
|
526
|
+
const claudeMdPath = join11(targetPath, "CLAUDE.md");
|
|
527
|
+
applyHarnessBlock(claudeMdPath);
|
|
528
|
+
updatedFiles.push(claudeMdPath);
|
|
529
|
+
}
|
|
530
|
+
return updatedFiles;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/domains/harness/install-harness.ts
|
|
534
|
+
function installHarness(options) {
|
|
535
|
+
const memoryDir = getProjectMemoryDir(options.targetPath);
|
|
536
|
+
const tasksDir = getProjectTasksDir(options.targetPath);
|
|
537
|
+
if (!existsSync10(tasksDir)) {
|
|
538
|
+
mkdirSync6(tasksDir, { recursive: true });
|
|
539
|
+
}
|
|
540
|
+
const createdFiles = [];
|
|
541
|
+
const contextPath = join12(memoryDir, "context.md");
|
|
542
|
+
if (!existsSync10(contextPath)) {
|
|
543
|
+
writeFileSync3(contextPath, buildContextTemplate(options.detectedDependencies), "utf-8");
|
|
544
|
+
createdFiles.push(contextPath);
|
|
545
|
+
}
|
|
546
|
+
const decisionsPath = join12(memoryDir, "decisions.md");
|
|
547
|
+
if (!existsSync10(decisionsPath)) {
|
|
548
|
+
writeFileSync3(decisionsPath, buildDecisionsTemplate(), "utf-8");
|
|
549
|
+
createdFiles.push(decisionsPath);
|
|
550
|
+
}
|
|
551
|
+
const templateTaskPath = join12(tasksDir, "_template.md");
|
|
552
|
+
if (!existsSync10(templateTaskPath)) {
|
|
553
|
+
writeFileSync3(templateTaskPath, buildTaskTemplate(), "utf-8");
|
|
554
|
+
createdFiles.push(templateTaskPath);
|
|
555
|
+
}
|
|
556
|
+
const instructions = upsertAgentInstructions(options.targetPath, options.selectedAgents);
|
|
557
|
+
createdFiles.push(...instructions);
|
|
558
|
+
return createdFiles;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/domains/harness/write-project-config.ts
|
|
562
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
563
|
+
function writeProjectConfig(targetPath, newSkills, newAgents, harnessEnabled) {
|
|
564
|
+
const configPath = getProjectConfigPath(targetPath);
|
|
565
|
+
let existingSkills = [];
|
|
566
|
+
let existingAgents = [];
|
|
567
|
+
let existingHarness = false;
|
|
568
|
+
if (existsSync11(configPath)) {
|
|
569
|
+
try {
|
|
570
|
+
const parsed = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
571
|
+
if (Array.isArray(parsed.skills)) {
|
|
572
|
+
existingSkills = parsed.skills;
|
|
573
|
+
}
|
|
574
|
+
if (Array.isArray(parsed.agents)) {
|
|
575
|
+
existingAgents = parsed.agents;
|
|
576
|
+
}
|
|
577
|
+
if (typeof parsed.harness === "boolean") {
|
|
578
|
+
existingHarness = parsed.harness;
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const mergedSkills = Array.from(/* @__PURE__ */ new Set([...existingSkills, ...newSkills]));
|
|
584
|
+
const mergedAgents = Array.from(/* @__PURE__ */ new Set([...existingAgents, ...newAgents]));
|
|
585
|
+
const config = {
|
|
586
|
+
version: 1,
|
|
587
|
+
harness: existingHarness || harnessEnabled,
|
|
588
|
+
agents: mergedAgents,
|
|
589
|
+
skills: mergedSkills,
|
|
590
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
591
|
+
};
|
|
592
|
+
writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
|
|
593
|
+
`, "utf-8");
|
|
594
|
+
return configPath;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/domains/installer/install-orchestrator.ts
|
|
598
|
+
var ADAPTER_REGISTRY = {
|
|
599
|
+
antigravity: new AntigravityAdapter(),
|
|
600
|
+
claude: new ClaudeAdapter(),
|
|
601
|
+
cursor: new CursorAdapter(),
|
|
602
|
+
gemini: new GeminiAdapter()
|
|
603
|
+
};
|
|
604
|
+
async function installSkills(options) {
|
|
605
|
+
const skillsToInstall = [...options.selectedSkillIds];
|
|
606
|
+
if (options.enableHarness && !skillsToInstall.includes("groove-harness")) {
|
|
607
|
+
skillsToInstall.push("groove-harness");
|
|
608
|
+
}
|
|
609
|
+
syncProjectStore(options.targetPath, skillsToInstall, options.selectedAgents);
|
|
610
|
+
const projectSkillsDir = getProjectSkillsDir(options.targetPath);
|
|
611
|
+
const createdFiles = [];
|
|
612
|
+
for (const skillId of skillsToInstall) {
|
|
613
|
+
const skill = SKILL_CATALOG.find((item) => item.id === skillId);
|
|
614
|
+
if (!skill) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const skillSourceDir = join13(projectSkillsDir, skill.directoryName);
|
|
618
|
+
for (const agentId of options.selectedAgents) {
|
|
619
|
+
const adapter = ADAPTER_REGISTRY[agentId];
|
|
620
|
+
if (!adapter) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const files = await adapter.installSkill(options.targetPath, skill, skillSourceDir);
|
|
624
|
+
createdFiles.push(...files);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
let harnessInitialized = false;
|
|
628
|
+
if (options.enableHarness) {
|
|
629
|
+
const harnessFiles = installHarness({
|
|
630
|
+
targetPath: options.targetPath,
|
|
631
|
+
selectedAgents: options.selectedAgents,
|
|
632
|
+
selectedSkillIds: skillsToInstall,
|
|
633
|
+
detectedDependencies: options.detectedDependencies || []
|
|
634
|
+
});
|
|
635
|
+
createdFiles.push(...harnessFiles);
|
|
636
|
+
harnessInitialized = true;
|
|
637
|
+
}
|
|
638
|
+
const configPath = writeProjectConfig(
|
|
639
|
+
options.targetPath,
|
|
640
|
+
skillsToInstall,
|
|
641
|
+
options.selectedAgents,
|
|
642
|
+
Boolean(options.enableHarness)
|
|
643
|
+
);
|
|
644
|
+
createdFiles.push(configPath);
|
|
645
|
+
return {
|
|
646
|
+
installedSkills: skillsToInstall,
|
|
647
|
+
configuredAgents: options.selectedAgents,
|
|
648
|
+
createdFiles,
|
|
649
|
+
harnessInitialized
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/domains/ui/ui-orchestrator.ts
|
|
654
|
+
async function runInteractiveUi(context) {
|
|
655
|
+
console.log(renderBanner());
|
|
656
|
+
p5.intro(pc2.magenta("Welcome to GrooveSkills! Let's tailor your AI agents with clean architectural standards."));
|
|
657
|
+
if (context.detectedDependencies.length > 0) {
|
|
658
|
+
p5.note(
|
|
659
|
+
`Detected dependencies: ${context.detectedDependencies.map((d) => pc2.bold(d)).join(", ")}
|
|
660
|
+
Active AI folders: ${context.detectedAgents.map((a) => pc2.green(a)).join(", ") || "None (will be created)"}`,
|
|
661
|
+
"Project Environment"
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
const selectedSkills = await promptSkills(context.suggestedSkillIds);
|
|
665
|
+
if (p5.isCancel(selectedSkills)) {
|
|
666
|
+
p5.cancel("Installation cancelled. Have a wonderful day!");
|
|
667
|
+
process.exit(0);
|
|
668
|
+
}
|
|
669
|
+
const selectedAgents = await promptAgents(context.detectedAgents);
|
|
670
|
+
if (p5.isCancel(selectedAgents)) {
|
|
671
|
+
p5.cancel("Installation cancelled. Have a wonderful day!");
|
|
672
|
+
process.exit(0);
|
|
673
|
+
}
|
|
674
|
+
const harnessChoice = await promptHarness(context.harnessExists);
|
|
675
|
+
if (p5.isCancel(harnessChoice)) {
|
|
676
|
+
p5.cancel("Installation cancelled. Have a wonderful day!");
|
|
677
|
+
process.exit(0);
|
|
678
|
+
}
|
|
679
|
+
let forceOverwrite = true;
|
|
680
|
+
const anyExists = selectedSkills.some((skillId) => {
|
|
681
|
+
const meta = SKILL_CATALOG.find((s2) => s2.id === skillId);
|
|
682
|
+
return meta ? verifySkillExists(context.targetPath, meta.directoryName, selectedAgents) : false;
|
|
683
|
+
});
|
|
684
|
+
if (anyExists) {
|
|
685
|
+
const confirm3 = await promptConfirmOverwrite();
|
|
686
|
+
if (p5.isCancel(confirm3) || !confirm3) {
|
|
687
|
+
p5.cancel("Installation stopped to protect existing skills.");
|
|
688
|
+
process.exit(0);
|
|
689
|
+
}
|
|
690
|
+
forceOverwrite = Boolean(confirm3);
|
|
691
|
+
}
|
|
692
|
+
const s = p5.spinner();
|
|
693
|
+
s.start("Installing and adapting skills across your configured AI agents...");
|
|
694
|
+
const result = await installSkills({
|
|
695
|
+
targetPath: context.targetPath,
|
|
696
|
+
selectedSkillIds: selectedSkills,
|
|
697
|
+
selectedAgents,
|
|
698
|
+
forceOverwrite,
|
|
699
|
+
enableHarness: Boolean(harnessChoice),
|
|
700
|
+
detectedDependencies: context.detectedDependencies
|
|
701
|
+
});
|
|
702
|
+
s.stop(pc2.green("All skills and harness have been successfully configured!"));
|
|
703
|
+
const harnessStatus = result.harnessInitialized ? pc2.green("Initialized in .grooveskills/") : pc2.dim("Skipped");
|
|
704
|
+
p5.note(
|
|
705
|
+
`Configured skills: ${result.installedSkills.map((s2) => pc2.bold(s2)).join(", ")}
|
|
706
|
+
Target agents: ${result.configuredAgents.map((a) => pc2.cyan(a)).join(", ")}
|
|
707
|
+
GrooveSkills Harness: ${harnessStatus}`,
|
|
708
|
+
"Installation Summary"
|
|
709
|
+
);
|
|
710
|
+
p5.outro(pc2.green("Your AI programming pair is now empowered with your team's architectural habits! \u{1F680}"));
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/main.ts
|
|
714
|
+
async function runGrooveSkills(options = {}) {
|
|
715
|
+
const targetPath = process.cwd();
|
|
716
|
+
const context = detectProjectContext(targetPath);
|
|
717
|
+
const hasSkills = options.skills && options.skills.length > 0;
|
|
718
|
+
const hasAgents = options.agents && options.agents.length > 0;
|
|
719
|
+
const isDirectHarness = Boolean(options.harness) && hasAgents;
|
|
720
|
+
if (hasSkills && hasAgents || isDirectHarness) {
|
|
721
|
+
await installSkills({
|
|
722
|
+
targetPath,
|
|
723
|
+
selectedSkillIds: options.skills || [],
|
|
724
|
+
selectedAgents: options.agents || [],
|
|
725
|
+
forceOverwrite: Boolean(options.yes),
|
|
726
|
+
enableHarness: options.harness,
|
|
727
|
+
detectedDependencies: context.detectedDependencies
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
await runInteractiveUi(context);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export {
|
|
735
|
+
runGrooveSkills
|
|
736
|
+
};
|
|
737
|
+
//# sourceMappingURL=chunk-5Q6O3BRA.js.map
|