@groupby/ai-dev 0.1.1 → 0.2.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 +9 -0
- package/dist/index.js +260 -21
- package/package.json +15 -10
- package/toolsets/rzlv-flow/README.md +57 -0
- package/toolsets/rzlv-flow/docs/mcp-setup.md +126 -0
- package/toolsets/rzlv-flow/resources/README.md +16 -0
- package/toolsets/rzlv-flow/resources/confluence-file-structure.md +179 -0
- package/toolsets/rzlv-flow/resources/confluence-page-templates/README.md +19 -0
- package/toolsets/rzlv-flow/resources/confluence-page-templates/decisions.md +36 -0
- package/toolsets/rzlv-flow/resources/confluence-page-templates/initiative-overview.md +40 -0
- package/toolsets/rzlv-flow/resources/confluence-page-templates/strategic-context.md +44 -0
- package/toolsets/rzlv-flow/resources/confluence-page-templates/technical-architecture.md +48 -0
- package/toolsets/rzlv-flow/resources/fcmp-protocol.md +331 -0
- package/toolsets/rzlv-flow/resources/jira-file-structure.md +177 -0
- package/toolsets/rzlv-flow/resources/sync-state-format.md +209 -0
- package/toolsets/rzlv-flow/skills/atlassian-orchestrator/SKILL.md +643 -0
- package/toolsets/rzlv-flow/skills/context-analyst/SKILL.md +265 -0
- package/toolsets/rzlv-flow/skills/jira-comment/SKILL.md +89 -0
- package/toolsets/rzlv-flow/skills/jira-daily-triage/SKILL.md +135 -0
- package/toolsets/rzlv-flow/skills/jira-sprint-status/SKILL.md +116 -0
- package/toolsets/rzlv-flow/skills/jira-status/SKILL.md +97 -0
- package/toolsets/rzlv-flow/skills/jira-sync/SKILL.md +148 -0
- package/toolsets/rzlv-flow/skills/jira-ticket-focus/SKILL.md +240 -0
- package/toolsets/rzlv-flow/skills/jira-ticket-trace/SKILL.md +112 -0
- package/toolsets/rzlv-flow/skills/jira-wrap-sync/SKILL.md +227 -0
- package/toolsets/toolsets/rzlv-flow/README.md +102 -0
- package/toolsets/toolsets/rzlv-flow/docs/getting-started.md +102 -0
- package/toolsets/toolsets/rzlv-flow/docs/mcp-setup.md +126 -0
- package/toolsets/toolsets/rzlv-flow/resources/README.md +16 -0
- package/toolsets/toolsets/rzlv-flow/resources/confluence-file-structure.md +285 -0
- package/toolsets/toolsets/rzlv-flow/resources/confluence-page-templates/README.md +19 -0
- package/toolsets/toolsets/rzlv-flow/resources/confluence-page-templates/decisions.md +36 -0
- package/toolsets/toolsets/rzlv-flow/resources/confluence-page-templates/initiative-overview.md +40 -0
- package/toolsets/toolsets/rzlv-flow/resources/confluence-page-templates/strategic-context.md +44 -0
- package/toolsets/toolsets/rzlv-flow/resources/confluence-page-templates/technical-architecture.md +48 -0
- package/toolsets/toolsets/rzlv-flow/resources/fcmp-protocol.md +331 -0
- package/toolsets/toolsets/rzlv-flow/resources/jira-file-structure.md +177 -0
- package/toolsets/toolsets/rzlv-flow/resources/sync-state-format.md +318 -0
- package/toolsets/toolsets/rzlv-flow/skills/atlassian-orchestrator/SKILL.md +643 -0
- package/toolsets/toolsets/rzlv-flow/skills/confluence-fetch/SKILL.md +189 -0
- package/toolsets/toolsets/rzlv-flow/skills/confluence-publish/SKILL.md +178 -0
- package/toolsets/toolsets/rzlv-flow/skills/context-analyst/SKILL.md +265 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-comment/SKILL.md +89 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-daily-triage/SKILL.md +143 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-sprint-status/SKILL.md +143 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-status/SKILL.md +97 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-sync/SKILL.md +148 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-ticket-focus/SKILL.md +245 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-ticket-trace/SKILL.md +112 -0
- package/toolsets/toolsets/rzlv-flow/skills/jira-wrap-sync/SKILL.md +260 -0
package/README.md
CHANGED
|
@@ -88,6 +88,15 @@ npm run build
|
|
|
88
88
|
# Output is in dist/
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### Running tests
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm test # Run all tests once
|
|
95
|
+
npm run test:watch # Watch mode (re-runs on file changes)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Tests use [Vitest](https://vitest.dev/) and cover frontmatter parsing, skill discovery, installation logic, and SKILL.md validation across the repo.
|
|
99
|
+
|
|
91
100
|
The CLI is written in TypeScript and bundled to ESM with [tsup](https://github.com/egoist/tsup).
|
|
92
101
|
|
|
93
102
|
## Repository
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/list.ts
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
+
import fs2 from "fs-extra";
|
|
8
9
|
|
|
9
10
|
// src/lib/discovery.ts
|
|
10
11
|
import path from "path";
|
|
@@ -34,7 +35,8 @@ var PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
|
34
35
|
async function discoverSkills() {
|
|
35
36
|
const libraryPattern = "skills/library/*/SKILL.md";
|
|
36
37
|
const teamPattern = "teams/*/skills/*/SKILL.md";
|
|
37
|
-
const
|
|
38
|
+
const toolsetPattern = "toolsets/*/skills/*/SKILL.md";
|
|
39
|
+
const matches = await fg([libraryPattern, teamPattern, toolsetPattern], {
|
|
38
40
|
cwd: PACKAGE_ROOT,
|
|
39
41
|
absolute: true
|
|
40
42
|
});
|
|
@@ -46,8 +48,13 @@ async function discoverSkills() {
|
|
|
46
48
|
const relativePath = path.relative(PACKAGE_ROOT, match);
|
|
47
49
|
let sourceType;
|
|
48
50
|
let teamName;
|
|
51
|
+
let toolsetName;
|
|
49
52
|
if (relativePath.startsWith("skills/library/")) {
|
|
50
53
|
sourceType = "library";
|
|
54
|
+
} else if (relativePath.startsWith("toolsets/")) {
|
|
55
|
+
sourceType = "toolset";
|
|
56
|
+
const parts = relativePath.split(path.sep);
|
|
57
|
+
toolsetName = parts[1];
|
|
51
58
|
} else {
|
|
52
59
|
sourceType = "team";
|
|
53
60
|
const parts = relativePath.split(path.sep);
|
|
@@ -59,6 +66,7 @@ async function discoverSkills() {
|
|
|
59
66
|
sourcePath: skillFolder,
|
|
60
67
|
sourceType,
|
|
61
68
|
teamName,
|
|
69
|
+
toolsetName,
|
|
62
70
|
frontmatter: data
|
|
63
71
|
});
|
|
64
72
|
}
|
|
@@ -79,9 +87,37 @@ async function discoverTeams() {
|
|
|
79
87
|
skills: skills2
|
|
80
88
|
}));
|
|
81
89
|
}
|
|
90
|
+
async function discoverToolsets() {
|
|
91
|
+
const skills = await discoverSkills();
|
|
92
|
+
const toolsetMap = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const skill of skills) {
|
|
94
|
+
if (skill.sourceType === "toolset" && skill.toolsetName) {
|
|
95
|
+
const existing = toolsetMap.get(skill.toolsetName) || [];
|
|
96
|
+
existing.push(skill);
|
|
97
|
+
toolsetMap.set(skill.toolsetName, existing);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const toolsets = [];
|
|
101
|
+
for (const [name, toolsetSkills] of toolsetMap) {
|
|
102
|
+
const resourcesPath = path.join(PACKAGE_ROOT, "toolsets", name, "resources");
|
|
103
|
+
let hasResources = false;
|
|
104
|
+
if (await fs.pathExists(resourcesPath)) {
|
|
105
|
+
const entries = await fs.readdir(resourcesPath);
|
|
106
|
+
hasResources = entries.some((e) => e !== "README.md");
|
|
107
|
+
}
|
|
108
|
+
toolsets.push({ name, skills: toolsetSkills, resourcesPath, hasResources });
|
|
109
|
+
}
|
|
110
|
+
return toolsets;
|
|
111
|
+
}
|
|
82
112
|
async function findSkill(name) {
|
|
83
113
|
const skills = await discoverSkills();
|
|
84
|
-
return skills.find((s) => s.name === name && s.sourceType === "library") || skills.find((s) => s.name === name);
|
|
114
|
+
return skills.find((s) => s.name === name && s.sourceType === "library") || skills.find((s) => s.name === name && s.sourceType === "team") || skills.find((s) => s.name === name);
|
|
115
|
+
}
|
|
116
|
+
async function findToolset(name) {
|
|
117
|
+
const toolsets = await discoverToolsets();
|
|
118
|
+
return toolsets.find(
|
|
119
|
+
(t) => t.name === name || t.name.toLowerCase() === name.toLowerCase()
|
|
120
|
+
);
|
|
85
121
|
}
|
|
86
122
|
|
|
87
123
|
// src/commands/list.ts
|
|
@@ -100,6 +136,7 @@ async function listSkills() {
|
|
|
100
136
|
}
|
|
101
137
|
const librarySkills = skills.filter((s) => s.sourceType === "library");
|
|
102
138
|
const teamSkills = skills.filter((s) => s.sourceType === "team");
|
|
139
|
+
const toolsetSkills = skills.filter((s) => s.sourceType === "toolset");
|
|
103
140
|
if (librarySkills.length > 0) {
|
|
104
141
|
console.log(chalk.bold("\nLibrary"));
|
|
105
142
|
for (const s of librarySkills) {
|
|
@@ -120,6 +157,20 @@ ${formatTeamName(team)}`));
|
|
|
120
157
|
console.log(` ${chalk.cyan(s.name.padEnd(24))} ${truncate(s.description, 60)}`);
|
|
121
158
|
}
|
|
122
159
|
}
|
|
160
|
+
const toolsetMap = /* @__PURE__ */ new Map();
|
|
161
|
+
for (const s of toolsetSkills) {
|
|
162
|
+
const tsName = s.toolsetName || "unknown";
|
|
163
|
+
const existing = toolsetMap.get(tsName) || [];
|
|
164
|
+
existing.push(s);
|
|
165
|
+
toolsetMap.set(tsName, existing);
|
|
166
|
+
}
|
|
167
|
+
for (const [tsName, skillList] of toolsetMap) {
|
|
168
|
+
console.log(chalk.bold(`
|
|
169
|
+
${tsName} ${chalk.dim("(toolset)")}`));
|
|
170
|
+
for (const s of skillList) {
|
|
171
|
+
console.log(` ${chalk.cyan(s.name.padEnd(24))} ${truncate(s.description, 60)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
123
174
|
console.log();
|
|
124
175
|
}
|
|
125
176
|
async function listTeams() {
|
|
@@ -136,6 +187,24 @@ async function listTeams() {
|
|
|
136
187
|
}
|
|
137
188
|
console.log();
|
|
138
189
|
}
|
|
190
|
+
async function listToolsets() {
|
|
191
|
+
const toolsets = await discoverToolsets();
|
|
192
|
+
if (toolsets.length === 0) {
|
|
193
|
+
console.log(chalk.yellow("No toolsets found."));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
console.log(chalk.bold("\nToolsets"));
|
|
197
|
+
for (const t of toolsets) {
|
|
198
|
+
const parts = [`${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`];
|
|
199
|
+
if (t.hasResources) {
|
|
200
|
+
const entries = await fs2.readdir(t.resourcesPath);
|
|
201
|
+
const count = entries.filter((e) => e !== "README.md").length;
|
|
202
|
+
parts.push(`${count} resource${count !== 1 ? "s" : ""}`);
|
|
203
|
+
}
|
|
204
|
+
console.log(` ${chalk.cyan(t.name.padEnd(24))} ${parts.join(", ")}`);
|
|
205
|
+
}
|
|
206
|
+
console.log();
|
|
207
|
+
}
|
|
139
208
|
async function listAll() {
|
|
140
209
|
await listSkills();
|
|
141
210
|
const teams = await discoverTeams();
|
|
@@ -148,11 +217,26 @@ async function listAll() {
|
|
|
148
217
|
}
|
|
149
218
|
console.log();
|
|
150
219
|
}
|
|
220
|
+
const toolsets = await discoverToolsets();
|
|
221
|
+
if (toolsets.length > 0) {
|
|
222
|
+
console.log(chalk.bold("Toolsets"));
|
|
223
|
+
for (const t of toolsets) {
|
|
224
|
+
const parts = [`${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`];
|
|
225
|
+
if (t.hasResources) {
|
|
226
|
+
const entries = await fs2.readdir(t.resourcesPath);
|
|
227
|
+
const count = entries.filter((e) => e !== "README.md").length;
|
|
228
|
+
parts.push(`${count} resource${count !== 1 ? "s" : ""}`);
|
|
229
|
+
}
|
|
230
|
+
console.log(` ${chalk.cyan(t.name.padEnd(24))} ${parts.join(", ")}`);
|
|
231
|
+
}
|
|
232
|
+
console.log();
|
|
233
|
+
}
|
|
151
234
|
}
|
|
152
235
|
function registerListCommand(program2) {
|
|
153
236
|
const list = program2.command("list").description("List available skills and teams");
|
|
154
237
|
list.command("skills").description("List all available skills").action(listSkills);
|
|
155
238
|
list.command("teams").description("List teams and their skill counts").action(listTeams);
|
|
239
|
+
list.command("toolsets").description("List toolsets with skill and resource counts").action(listToolsets);
|
|
156
240
|
list.action(listAll);
|
|
157
241
|
}
|
|
158
242
|
|
|
@@ -162,7 +246,7 @@ import chalk3 from "chalk";
|
|
|
162
246
|
|
|
163
247
|
// src/lib/clients.ts
|
|
164
248
|
import path2 from "path";
|
|
165
|
-
import
|
|
249
|
+
import fs3 from "fs-extra";
|
|
166
250
|
var ALL_CLIENTS = [
|
|
167
251
|
{ name: "Copilot", skillsDir: ".github/skills", detectDir: ".github" },
|
|
168
252
|
{ name: "Claude Code", skillsDir: ".claude/skills", detectDir: ".claude" },
|
|
@@ -172,7 +256,7 @@ async function detectClients(targetDir) {
|
|
|
172
256
|
const detected = [];
|
|
173
257
|
for (const client of ALL_CLIENTS) {
|
|
174
258
|
const dirPath = path2.join(targetDir, client.detectDir);
|
|
175
|
-
if (await
|
|
259
|
+
if (await fs3.pathExists(dirPath)) {
|
|
176
260
|
detected.push(client);
|
|
177
261
|
}
|
|
178
262
|
}
|
|
@@ -181,20 +265,20 @@ async function detectClients(targetDir) {
|
|
|
181
265
|
|
|
182
266
|
// src/lib/installer.ts
|
|
183
267
|
import path3 from "path";
|
|
184
|
-
import
|
|
268
|
+
import fs4 from "fs-extra";
|
|
185
269
|
async function handleFile(srcPath, destPath, options, result, onConflict, contentOverride) {
|
|
186
270
|
const relativeDest = path3.relative(options.targetDir, destPath);
|
|
187
|
-
if (await
|
|
188
|
-
const existingContent = await
|
|
189
|
-
const newContent = contentOverride ?? await
|
|
271
|
+
if (await fs4.pathExists(destPath)) {
|
|
272
|
+
const existingContent = await fs4.readFile(destPath, "utf-8");
|
|
273
|
+
const newContent = contentOverride ?? await fs4.readFile(srcPath, "utf-8");
|
|
190
274
|
if (existingContent === newContent) {
|
|
191
275
|
result.skipped.push(relativeDest);
|
|
192
276
|
return;
|
|
193
277
|
}
|
|
194
278
|
if (options.force) {
|
|
195
279
|
if (!options.dryRun) {
|
|
196
|
-
await
|
|
197
|
-
await
|
|
280
|
+
await fs4.ensureDir(path3.dirname(destPath));
|
|
281
|
+
await fs4.writeFile(destPath, newContent);
|
|
198
282
|
}
|
|
199
283
|
result.overwritten.push(relativeDest);
|
|
200
284
|
} else if (options.skipExisting) {
|
|
@@ -203,8 +287,8 @@ async function handleFile(srcPath, destPath, options, result, onConflict, conten
|
|
|
203
287
|
const choice = await onConflict(relativeDest);
|
|
204
288
|
if (choice === "overwrite") {
|
|
205
289
|
if (!options.dryRun) {
|
|
206
|
-
await
|
|
207
|
-
await
|
|
290
|
+
await fs4.ensureDir(path3.dirname(destPath));
|
|
291
|
+
await fs4.writeFile(destPath, newContent);
|
|
208
292
|
}
|
|
209
293
|
result.overwritten.push(relativeDest);
|
|
210
294
|
} else {
|
|
@@ -215,11 +299,11 @@ async function handleFile(srcPath, destPath, options, result, onConflict, conten
|
|
|
215
299
|
}
|
|
216
300
|
} else {
|
|
217
301
|
if (!options.dryRun) {
|
|
218
|
-
await
|
|
302
|
+
await fs4.ensureDir(path3.dirname(destPath));
|
|
219
303
|
if (contentOverride) {
|
|
220
|
-
await
|
|
304
|
+
await fs4.writeFile(destPath, contentOverride);
|
|
221
305
|
} else {
|
|
222
|
-
await
|
|
306
|
+
await fs4.copy(srcPath, destPath);
|
|
223
307
|
}
|
|
224
308
|
}
|
|
225
309
|
result.created.push(relativeDest);
|
|
@@ -233,10 +317,10 @@ async function installSkill(skill, options, onConflict) {
|
|
|
233
317
|
overwritten: []
|
|
234
318
|
};
|
|
235
319
|
const destFolder = path3.join(options.targetDir, "docs", "ai", "skills", skill.name);
|
|
236
|
-
const sourceFiles = await
|
|
320
|
+
const sourceFiles = await fs4.readdir(skill.sourcePath);
|
|
237
321
|
for (const file of sourceFiles) {
|
|
238
322
|
const srcFile = path3.join(skill.sourcePath, file);
|
|
239
|
-
const stat = await
|
|
323
|
+
const stat = await fs4.stat(srcFile);
|
|
240
324
|
if (stat.isFile()) {
|
|
241
325
|
const destFile = path3.join(destFolder, file);
|
|
242
326
|
await handleFile(srcFile, destFile, options, result, onConflict);
|
|
@@ -261,6 +345,30 @@ async function installSkills(skills, options, onConflict) {
|
|
|
261
345
|
}
|
|
262
346
|
return results;
|
|
263
347
|
}
|
|
348
|
+
async function installResources(toolset, options, onConflict) {
|
|
349
|
+
const result = {
|
|
350
|
+
toolset: toolset.name,
|
|
351
|
+
created: [],
|
|
352
|
+
skipped: [],
|
|
353
|
+
overwritten: []
|
|
354
|
+
};
|
|
355
|
+
if (!toolset.hasResources) return result;
|
|
356
|
+
const entries = await fs4.readdir(toolset.resourcesPath);
|
|
357
|
+
const resourceFiles = entries.filter((e) => e !== "README.md");
|
|
358
|
+
const destDir = path3.join(options.targetDir, "docs", "ai", "resources");
|
|
359
|
+
const fileResult = { skill: toolset.name, created: [], skipped: [], overwritten: [] };
|
|
360
|
+
for (const file of resourceFiles) {
|
|
361
|
+
const srcFile = path3.join(toolset.resourcesPath, file);
|
|
362
|
+
const stat = await fs4.stat(srcFile);
|
|
363
|
+
if (!stat.isFile()) continue;
|
|
364
|
+
const destFile = path3.join(destDir, file);
|
|
365
|
+
await handleFile(srcFile, destFile, options, fileResult, onConflict);
|
|
366
|
+
}
|
|
367
|
+
result.created = fileResult.created;
|
|
368
|
+
result.skipped = fileResult.skipped;
|
|
369
|
+
result.overwritten = fileResult.overwritten;
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
264
372
|
|
|
265
373
|
// src/lib/prompts.ts
|
|
266
374
|
import { select, checkbox, confirm } from "@inquirer/prompts";
|
|
@@ -268,6 +376,7 @@ import chalk2 from "chalk";
|
|
|
268
376
|
async function promptSelectSkills(skills) {
|
|
269
377
|
const librarySkills = skills.filter((s) => s.sourceType === "library");
|
|
270
378
|
const teamSkills = skills.filter((s) => s.sourceType === "team");
|
|
379
|
+
const toolsetSkills = skills.filter((s) => s.sourceType === "toolset");
|
|
271
380
|
const choices = [];
|
|
272
381
|
if (librarySkills.length > 0) {
|
|
273
382
|
choices.push(
|
|
@@ -292,6 +401,21 @@ async function promptSelectSkills(skills) {
|
|
|
292
401
|
}))
|
|
293
402
|
);
|
|
294
403
|
}
|
|
404
|
+
const toolsetMap = /* @__PURE__ */ new Map();
|
|
405
|
+
for (const s of toolsetSkills) {
|
|
406
|
+
const tsName = s.toolsetName || "unknown";
|
|
407
|
+
const existing = toolsetMap.get(tsName) || [];
|
|
408
|
+
existing.push(s);
|
|
409
|
+
toolsetMap.set(tsName, existing);
|
|
410
|
+
}
|
|
411
|
+
for (const [tsName, tsList] of toolsetMap) {
|
|
412
|
+
choices.push(
|
|
413
|
+
...tsList.map((s) => ({
|
|
414
|
+
name: `${chalk2.dim(`[${tsName}]`)} ${s.name} \u2014 ${truncate2(s.description, 60)}`,
|
|
415
|
+
value: s
|
|
416
|
+
}))
|
|
417
|
+
);
|
|
418
|
+
}
|
|
295
419
|
return checkbox({
|
|
296
420
|
message: "Select skills to install:",
|
|
297
421
|
choices,
|
|
@@ -307,6 +431,15 @@ async function promptSelectTeam(teams) {
|
|
|
307
431
|
}))
|
|
308
432
|
});
|
|
309
433
|
}
|
|
434
|
+
async function promptSelectToolset(toolsets) {
|
|
435
|
+
return select({
|
|
436
|
+
message: "Select a toolset:",
|
|
437
|
+
choices: toolsets.map((t) => ({
|
|
438
|
+
name: `${t.name} (${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}${t.hasResources ? " + resources" : ""})`,
|
|
439
|
+
value: t
|
|
440
|
+
}))
|
|
441
|
+
});
|
|
442
|
+
}
|
|
310
443
|
async function promptSelectClients(allClients, detectedClients) {
|
|
311
444
|
const detectedNames = new Set(detectedClients.map((c) => c.name));
|
|
312
445
|
return checkbox({
|
|
@@ -352,7 +485,7 @@ function formatTeamName2(name) {
|
|
|
352
485
|
}
|
|
353
486
|
|
|
354
487
|
// src/commands/install.ts
|
|
355
|
-
function printResults(results, options) {
|
|
488
|
+
function printResults(results, options, resourceResult) {
|
|
356
489
|
const prefix = options.dryRun ? chalk3.yellow("[DRY RUN] ") : "";
|
|
357
490
|
const createdWord = options.dryRun ? "would create" : "created";
|
|
358
491
|
const skippedWord = "up to date";
|
|
@@ -360,6 +493,16 @@ function printResults(results, options) {
|
|
|
360
493
|
console.log(`
|
|
361
494
|
${prefix}${chalk3.bold("Install complete")}
|
|
362
495
|
`);
|
|
496
|
+
if (resourceResult) {
|
|
497
|
+
const rParts = [];
|
|
498
|
+
if (resourceResult.created.length > 0) rParts.push(chalk3.green(`${resourceResult.created.length} ${createdWord}`));
|
|
499
|
+
if (resourceResult.skipped.length > 0) rParts.push(chalk3.dim(`${resourceResult.skipped.length} ${skippedWord}`));
|
|
500
|
+
if (resourceResult.overwritten.length > 0) rParts.push(chalk3.yellow(`${resourceResult.overwritten.length} ${overwrittenWord}`));
|
|
501
|
+
if (rParts.length > 0) {
|
|
502
|
+
console.log(` ${chalk3.bold("Resources")} ${rParts.join(", ")}`);
|
|
503
|
+
console.log();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
363
506
|
console.log(" Skill Status");
|
|
364
507
|
console.log(" " + "\u2500".repeat(50));
|
|
365
508
|
for (const r of results) {
|
|
@@ -404,7 +547,14 @@ async function installSkillCmd(name, _opts, cmd) {
|
|
|
404
547
|
dryRun
|
|
405
548
|
};
|
|
406
549
|
const result = await installSkill(skill, options, force || skipExisting ? void 0 : promptConflict);
|
|
407
|
-
|
|
550
|
+
let resourceResult;
|
|
551
|
+
if (skill.sourceType === "toolset" && skill.toolsetName) {
|
|
552
|
+
const toolset = await findToolset(skill.toolsetName);
|
|
553
|
+
if (toolset && toolset.hasResources) {
|
|
554
|
+
resourceResult = await installResources(toolset, options, force || skipExisting ? void 0 : promptConflict);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
printResults([result], options, resourceResult);
|
|
408
558
|
}
|
|
409
559
|
async function installTeamCmd(name, _opts, cmd) {
|
|
410
560
|
const parentOpts = cmd.parent.opts();
|
|
@@ -451,16 +601,56 @@ async function installTeamCmd(name, _opts, cmd) {
|
|
|
451
601
|
);
|
|
452
602
|
printResults(results, options);
|
|
453
603
|
}
|
|
604
|
+
async function installToolsetCmd(name, _opts, cmd) {
|
|
605
|
+
const parentOpts = cmd.parent.opts();
|
|
606
|
+
const force = Boolean(parentOpts.force);
|
|
607
|
+
const skipExisting = Boolean(parentOpts.skipExisting);
|
|
608
|
+
const dryRun = Boolean(parentOpts.dryRun);
|
|
609
|
+
const targetDir = path4.resolve(
|
|
610
|
+
parentOpts.target || process.cwd()
|
|
611
|
+
);
|
|
612
|
+
const toolset = await findToolset(name);
|
|
613
|
+
if (!toolset) {
|
|
614
|
+
const allToolsets = await discoverToolsets();
|
|
615
|
+
console.log(chalk3.red(`Toolset "${name}" not found.`));
|
|
616
|
+
if (allToolsets.length > 0) {
|
|
617
|
+
console.log(`Available toolsets: ${allToolsets.map((t) => t.name).join(", ")}`);
|
|
618
|
+
}
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
const detected = await detectClients(targetDir);
|
|
622
|
+
let clients;
|
|
623
|
+
if (force) {
|
|
624
|
+
clients = detected.length > 0 ? detected : ALL_CLIENTS;
|
|
625
|
+
} else {
|
|
626
|
+
clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
627
|
+
}
|
|
628
|
+
const options = {
|
|
629
|
+
targetDir,
|
|
630
|
+
clients,
|
|
631
|
+
force,
|
|
632
|
+
skipExisting,
|
|
633
|
+
dryRun
|
|
634
|
+
};
|
|
635
|
+
const conflictHandler = force || skipExisting ? void 0 : promptConflict;
|
|
636
|
+
let resourceResult;
|
|
637
|
+
if (toolset.hasResources) {
|
|
638
|
+
resourceResult = await installResources(toolset, options, conflictHandler);
|
|
639
|
+
}
|
|
640
|
+
const results = await installSkills(toolset.skills, options, conflictHandler);
|
|
641
|
+
printResults(results, options, resourceResult);
|
|
642
|
+
}
|
|
454
643
|
function registerInstallCommand(program2) {
|
|
455
644
|
const install = program2.command("install").description("Install skills").option("--force", "Overwrite existing files without prompting").option("--skip-existing", "Skip files that already exist without prompting").option("--dry-run", "Show what would be installed without writing files").option("--target <dir>", "Install to a different directory (default: CWD)");
|
|
456
645
|
install.command("skill <name>").description("Install a specific skill").action(installSkillCmd);
|
|
457
646
|
install.command("team <name>").description("Install all skills for a team").action(installTeamCmd);
|
|
647
|
+
install.command("toolset <name>").description("Install all skills and resources for a toolset").action(installToolsetCmd);
|
|
458
648
|
}
|
|
459
649
|
|
|
460
650
|
// src/commands/interactive.ts
|
|
461
651
|
import { select as select2 } from "@inquirer/prompts";
|
|
462
652
|
import chalk4 from "chalk";
|
|
463
|
-
function printResults2(results, options) {
|
|
653
|
+
function printResults2(results, options, resourceResult) {
|
|
464
654
|
const prefix = options.dryRun ? chalk4.yellow("[DRY RUN] ") : "";
|
|
465
655
|
const createdWord = options.dryRun ? "would create" : "created";
|
|
466
656
|
const skippedWord = "up to date";
|
|
@@ -468,6 +658,16 @@ function printResults2(results, options) {
|
|
|
468
658
|
console.log(`
|
|
469
659
|
${prefix}${chalk4.bold("Install complete")}
|
|
470
660
|
`);
|
|
661
|
+
if (resourceResult) {
|
|
662
|
+
const rParts = [];
|
|
663
|
+
if (resourceResult.created.length > 0) rParts.push(chalk4.green(`${resourceResult.created.length} ${createdWord}`));
|
|
664
|
+
if (resourceResult.skipped.length > 0) rParts.push(chalk4.dim(`${resourceResult.skipped.length} ${skippedWord}`));
|
|
665
|
+
if (resourceResult.overwritten.length > 0) rParts.push(chalk4.yellow(`${resourceResult.overwritten.length} ${overwrittenWord}`));
|
|
666
|
+
if (rParts.length > 0) {
|
|
667
|
+
console.log(` ${chalk4.bold("Resources")} ${rParts.join(", ")}`);
|
|
668
|
+
console.log();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
471
671
|
console.log(" Skill Status");
|
|
472
672
|
console.log(" " + "\u2500".repeat(50));
|
|
473
673
|
for (const r of results) {
|
|
@@ -521,6 +721,15 @@ ${formatTeamName3(team)}`));
|
|
|
521
721
|
);
|
|
522
722
|
}
|
|
523
723
|
}
|
|
724
|
+
const toolsets = await discoverToolsets();
|
|
725
|
+
if (toolsets.length > 0) {
|
|
726
|
+
console.log(chalk4.bold("\nToolsets"));
|
|
727
|
+
for (const t of toolsets) {
|
|
728
|
+
console.log(
|
|
729
|
+
` ${chalk4.cyan(t.name.padEnd(24))} ${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
524
733
|
console.log();
|
|
525
734
|
}
|
|
526
735
|
async function runInteractive() {
|
|
@@ -530,6 +739,7 @@ async function runInteractive() {
|
|
|
530
739
|
choices: [
|
|
531
740
|
{ name: "Install skill(s)", value: "install-skills" },
|
|
532
741
|
{ name: "Install team skills", value: "install-team" },
|
|
742
|
+
{ name: "Install toolset", value: "install-toolset" },
|
|
533
743
|
{ name: "List available skills & teams", value: "list" }
|
|
534
744
|
]
|
|
535
745
|
});
|
|
@@ -591,6 +801,33 @@ async function runInteractive() {
|
|
|
591
801
|
};
|
|
592
802
|
const results = await installSkills(skillsToInstall, options, promptConflict);
|
|
593
803
|
printResults2(results, options);
|
|
804
|
+
} else if (action === "install-toolset") {
|
|
805
|
+
const toolsets = await discoverToolsets();
|
|
806
|
+
if (toolsets.length === 0) {
|
|
807
|
+
console.log(chalk4.yellow("No toolsets found."));
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const toolset = await promptSelectToolset(toolsets);
|
|
811
|
+
const detected = await detectClients(targetDir);
|
|
812
|
+
const clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
813
|
+
const confirmed = await promptConfirmInstall(toolset.skills, clients, targetDir);
|
|
814
|
+
if (!confirmed) {
|
|
815
|
+
console.log(chalk4.dim("Cancelled."));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const options = {
|
|
819
|
+
targetDir,
|
|
820
|
+
clients,
|
|
821
|
+
force: false,
|
|
822
|
+
skipExisting: false,
|
|
823
|
+
dryRun: false
|
|
824
|
+
};
|
|
825
|
+
let resourceResult;
|
|
826
|
+
if (toolset.hasResources) {
|
|
827
|
+
resourceResult = await installResources(toolset, options, promptConflict);
|
|
828
|
+
}
|
|
829
|
+
const results = await installSkills(toolset.skills, options, promptConflict);
|
|
830
|
+
printResults2(results, options, resourceResult);
|
|
594
831
|
}
|
|
595
832
|
}
|
|
596
833
|
|
|
@@ -604,9 +841,11 @@ program.addHelpText(
|
|
|
604
841
|
`
|
|
605
842
|
Examples:
|
|
606
843
|
$ npx @groupby/ai-dev Interactive mode
|
|
607
|
-
$ npx @groupby/ai-dev list List skills and
|
|
844
|
+
$ npx @groupby/ai-dev list List skills, teams, and toolsets
|
|
845
|
+
$ npx @groupby/ai-dev list toolsets List available toolsets
|
|
608
846
|
$ npx @groupby/ai-dev install skill frontend-design Install a single skill
|
|
609
847
|
$ npx @groupby/ai-dev install team brain-studio Install all team skills
|
|
848
|
+
$ npx @groupby/ai-dev install toolset rzlv-flow Install all skills + resources for a toolset
|
|
610
849
|
$ npx @groupby/ai-dev install team brain-studio --dry-run`
|
|
611
850
|
);
|
|
612
851
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groupby/ai-dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Interactive installer for Rezolve Ai development skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/",
|
|
11
11
|
"skills/",
|
|
12
|
-
"teams/"
|
|
12
|
+
"teams/",
|
|
13
|
+
"toolsets/"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
|
-
"prebuild": "cp -r ../skills ./skills && cp -r ../teams ./teams",
|
|
16
|
+
"prebuild": "cp -r ../skills ./skills && cp -r ../teams ./teams && cp -r ../toolsets ./toolsets",
|
|
16
17
|
"build": "tsup",
|
|
17
|
-
"prepublishOnly": "npm run build"
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:coverage": "vitest run --coverage"
|
|
18
22
|
},
|
|
19
23
|
"repository": {
|
|
20
24
|
"type": "git",
|
|
@@ -28,17 +32,18 @@
|
|
|
28
32
|
"access": "restricted"
|
|
29
33
|
},
|
|
30
34
|
"dependencies": {
|
|
31
|
-
"commander": "^13.0.0",
|
|
32
35
|
"@inquirer/prompts": "^7.0.0",
|
|
33
36
|
"chalk": "^5.3.0",
|
|
34
|
-
"
|
|
37
|
+
"commander": "^13.0.0",
|
|
35
38
|
"fast-glob": "^3.3.0",
|
|
36
|
-
"fs-extra": "^11.2.0"
|
|
39
|
+
"fs-extra": "^11.2.0",
|
|
40
|
+
"gray-matter": "^4.0.3"
|
|
37
41
|
},
|
|
38
42
|
"devDependencies": {
|
|
39
|
-
"typescript": "^5.5.0",
|
|
40
|
-
"@types/node": "^20.0.0",
|
|
41
43
|
"@types/fs-extra": "^11.0.0",
|
|
42
|
-
"
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"tsup": "^8.0.0",
|
|
46
|
+
"typescript": "^5.5.0",
|
|
47
|
+
"vitest": "^4.1.3"
|
|
43
48
|
}
|
|
44
49
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# rzlv-flow: Atlassian Intelligence Skills
|
|
2
|
+
|
|
3
|
+
A toolset of modular, composable skills for Jira and Confluence developer workflows. Covers daily triage, ticket focus, wrap/sync, sprint status, context loading, and more — all without requiring the full BMAD Method or the rzlv-flow monolith.
|
|
4
|
+
|
|
5
|
+
Each skill is standalone and can be installed individually or as a complete suite.
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
| Skill | Description | Status |
|
|
10
|
+
|-------|-------------|--------|
|
|
11
|
+
| `jira-daily-triage` | Start-of-day ticket triage with sprint detection | Planned |
|
|
12
|
+
| `jira-ticket-focus` | Deep-load a ticket with full context | Planned |
|
|
13
|
+
| `jira-wrap-sync` | Wrap up work, draft comment, multi-action sync | Planned |
|
|
14
|
+
| `jira-ticket-trace` | Trace code back to Jira requirements | Planned |
|
|
15
|
+
| `jira-comment` | Add a comment to a Jira ticket with FCMP safety | Planned |
|
|
16
|
+
| `jira-status` | Change ticket status with transition validation | Planned |
|
|
17
|
+
| `jira-sprint-status` | Sprint status report with completion metrics | Planned |
|
|
18
|
+
| `jira-sync` | Force-sync local Jira context with remote state | Planned |
|
|
19
|
+
| `atlassian-orchestrator` | Pull surrounding ticket context and create structures | Complete |
|
|
20
|
+
| `context-analyst` | Transform meeting transcripts into structured docs | Complete |
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
### Required: Atlassian MCP (`atlassian-rovo`)
|
|
25
|
+
|
|
26
|
+
All Jira and Confluence skills require the Atlassian MCP server for API access to your Atlassian instance.
|
|
27
|
+
|
|
28
|
+
### Optional: GitHub MCP
|
|
29
|
+
|
|
30
|
+
Only needed for PR creation features in `jira-wrap-sync`. Not required for any other skills.
|
|
31
|
+
|
|
32
|
+
See [docs/mcp-setup.md](docs/mcp-setup.md) for detailed setup instructions.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx @groupby/ai-dev install toolset rzlv-flow # All skills + resources
|
|
38
|
+
npx @groupby/ai-dev install skill jira-daily-triage # Individual skill
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> **Note:** CLI toolset support is coming soon. For now, skills can be copied manually from this repository into your project's `docs/ai/skills/` directory, and resources into `docs/ai/resources/`.
|
|
42
|
+
|
|
43
|
+
## BMAD Compatibility
|
|
44
|
+
|
|
45
|
+
These skills are compatible with BMAD's Jira file structure (`docs/jira/{instance}/{project}/...`) but do **not** require BMAD. They were originally developed as part of the BMAD "Atlassian Intelligence Suite" and have been extracted as standalone skills. If BMAD is installed in your project, these skills coexist without conflict.
|
|
46
|
+
|
|
47
|
+
## Shared Resources
|
|
48
|
+
|
|
49
|
+
Skills reference shared resource files installed to `docs/ai/resources/` in your project:
|
|
50
|
+
|
|
51
|
+
| Resource | Description |
|
|
52
|
+
|----------|-------------|
|
|
53
|
+
| `fcmp-protocol.md` | Fetch-Compare-Merge-Push sync pattern for safe Atlassian read/write |
|
|
54
|
+
| `jira-file-structure.md` | Standard directory layout for local Jira mirrors |
|
|
55
|
+
| `confluence-file-structure.md` | Leaf Bundle pattern for Confluence page mirrors |
|
|
56
|
+
| `sync-state-format.md` | YAML frontmatter schema for ticket and page files |
|
|
57
|
+
| `confluence-page-templates/` | Starter scaffolds for Confluence pages (initiative overview, strategic context, technical architecture, decisions) |
|