@zaganjade/pi-multi-skill 1.3.0 → 1.3.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 +50 -8
- package/package.json +1 -1
- package/skill-bundles.example.json +17 -17
- package/src/bmad-auto.ts +99 -99
- package/src/build.ts +352 -270
- package/src/bundles.ts +138 -138
- package/src/discover.ts +161 -161
- package/src/index.ts +13 -17
- package/src/metadata.ts +170 -170
- package/src/parse-args.ts +80 -80
- package/src/registry.ts +46 -46
- package/src/suggestions.ts +70 -70
- package/src/types.ts +64 -64
package/README.md
CHANGED
|
@@ -4,6 +4,42 @@ Load multiple skills at once in [pi](https://github.com/earendil-works/pi-mono)
|
|
|
4
4
|
|
|
5
5
|
**Version 1.3.0** — skill chaining, bundle presets, conflict resolution, parallel dispatch, activation stats, `/skills-last`, `/skills-setup`, and bundle attribution in pi-usage.
|
|
6
6
|
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🚀 Install
|
|
10
|
+
|
|
11
|
+
> ⚠️ **Use `pi install` — NOT `npm install`.**
|
|
12
|
+
> Plain `npm install` only drops files in `node_modules`. Pi does **not** scan
|
|
13
|
+
> that folder, so the package is **never detected** and `/skills` won't appear.
|
|
14
|
+
> You must register it with `pi install` so it lands in `"packages"` in
|
|
15
|
+
> `~/.pi/agent/settings.json`.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pi install npm:@zaganjade/pi-multi-skill
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then inside pi:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/reload
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Verify it loaded:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pi list # should show npm:@zaganjade/pi-multi-skill
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Type `/` — `/skills` should appear in slash autocomplete.
|
|
34
|
+
|
|
35
|
+
**Don't do this** (common mistake — package installs but pi ignores it):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g @zaganjade/pi-multi-skill # ❌ pi will not detect this
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
7
43
|
## Why?
|
|
8
44
|
|
|
9
45
|
Pi's built-in `/skill:name` loads one skill at a time. When you need several skills working together (e.g. `bmad-master` + `analyst` + `pm`, or `systematic-debugging` + `test-driven-development`), invoking them one by one is slow and easy to forget.
|
|
@@ -163,19 +199,25 @@ Skills are found from **all** of these sources (merged, deduplicated by name):
|
|
|
163
199
|
|
|
164
200
|
This means bundles like `@debug` and `@cc-feature` work even when Superpowers skills live in the Claude plugin cache rather than `~/.claude/skills`.
|
|
165
201
|
|
|
166
|
-
###
|
|
202
|
+
### Pi-native skill display
|
|
167
203
|
|
|
168
|
-
|
|
204
|
+
Pi collapses user messages only when the **entire** message matches its native skill envelope:
|
|
169
205
|
|
|
170
206
|
```xml
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
</
|
|
207
|
+
<skill name="skill-a, skill-b" location="pi-multi-skill">
|
|
208
|
+
<manually_attached_skills count="2" bundles="@debug">
|
|
209
|
+
…priority rules…
|
|
210
|
+
<skill name="skill-a" location="/path/to/SKILL.md">…</skill>
|
|
211
|
+
<skill name="skill-b" location="/path/to/SKILL.md">…</skill>
|
|
212
|
+
<user_query>Your instructions here</user_query>
|
|
213
|
+
</manually_attached_skills>
|
|
214
|
+
</skill>
|
|
177
215
|
```
|
|
178
216
|
|
|
217
|
+
- **1 skill, no extras** → single native block → `[skill] skill-name`
|
|
218
|
+
- **2+ skills** (or bundles/instructions) → outer native block hides all inner content → `[skill] a, b, c` (Ctrl+O to expand)
|
|
219
|
+
- Inner `<manually_attached_skills bundles="…">` is preserved for **pi-usage** bundle attribution
|
|
220
|
+
|
|
179
221
|
### Session hints
|
|
180
222
|
|
|
181
223
|
After each user turn, the extension may suggest a relevant bundle (non-blocking `info` notification):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaganjade/pi-multi-skill",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Chain multiple skills via /skills — bundles (JSON/YAML), load modes, BMAD --auto, /skills-last, /skills-setup, conflict resolution, parallel dispatch, activation stats, bundle attribution for pi-usage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
{
|
|
2
|
-
"bundles": {
|
|
3
|
-
"my-stack": {
|
|
4
|
-
"description": "Only skills installed on this machine — no BMAD required",
|
|
5
|
-
"skills": ["frontend-design", "create-rule"],
|
|
6
|
-
"order": "explicit",
|
|
7
|
-
"default_mode": "meta"
|
|
8
|
-
},
|
|
9
|
-
"my-team-planning": {
|
|
10
|
-
"description": "Custom team planning workflow (requires BMAD)",
|
|
11
|
-
"skills": ["bmad-master", "analyst", "pm"],
|
|
12
|
-
"order": "process-first",
|
|
13
|
-
"default_mode": "meta",
|
|
14
|
-
"requires": "BMAD Method"
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"bundles": {
|
|
3
|
+
"my-stack": {
|
|
4
|
+
"description": "Only skills installed on this machine — no BMAD required",
|
|
5
|
+
"skills": ["frontend-design", "create-rule"],
|
|
6
|
+
"order": "explicit",
|
|
7
|
+
"default_mode": "meta"
|
|
8
|
+
},
|
|
9
|
+
"my-team-planning": {
|
|
10
|
+
"description": "Custom team planning workflow (requires BMAD)",
|
|
11
|
+
"skills": ["bmad-master", "analyst", "pm"],
|
|
12
|
+
"order": "process-first",
|
|
13
|
+
"default_mode": "meta",
|
|
14
|
+
"requires": "BMAD Method"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/bmad-auto.ts
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
const PHASE_SKILLS: Record<string, string[]> = {
|
|
5
|
-
analysis: ["bmad-master", "analyst"],
|
|
6
|
-
planning: ["bmad-master", "analyst", "pm"],
|
|
7
|
-
solutioning: ["bmad-master", "architect", "ux-designer"],
|
|
8
|
-
implementation: ["bmad-master", "developer", "scrum-master"],
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function readText(path: string): string | null {
|
|
12
|
-
try {
|
|
13
|
-
return readFileSync(path, "utf-8");
|
|
14
|
-
} catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function workflowStatus(content: string, key: string): string | null {
|
|
20
|
-
const patterns = [
|
|
21
|
-
new RegExp(`${key}:\\s*\\n[\\s\\S]*?status:\\s*([\\w-]+)`, "i"),
|
|
22
|
-
new RegExp(`${key}:[\\s\\S]*?status:\\s*([\\w\\s-]+)`, "i"),
|
|
23
|
-
];
|
|
24
|
-
for (const re of patterns) {
|
|
25
|
-
const match = content.match(re);
|
|
26
|
-
if (match) return match[1].trim().toLowerCase().replace(/\s+/g, "-");
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function isIncomplete(status: string | null): boolean {
|
|
32
|
-
if (!status) return true;
|
|
33
|
-
return /not-started|not_started|pending|required|in-progress|in_progress|started/.test(
|
|
34
|
-
status,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function detectPhase(content: string): keyof typeof PHASE_SKILLS {
|
|
39
|
-
const checks: Array<[string, keyof typeof PHASE_SKILLS]> = [
|
|
40
|
-
["product-brief", "analysis"],
|
|
41
|
-
["brainstorm", "analysis"],
|
|
42
|
-
["research", "analysis"],
|
|
43
|
-
["prd", "planning"],
|
|
44
|
-
["tech-spec", "planning"],
|
|
45
|
-
["tech_spec", "planning"],
|
|
46
|
-
["architecture", "solutioning"],
|
|
47
|
-
["ux-design", "solutioning"],
|
|
48
|
-
["sprint-planning", "implementation"],
|
|
49
|
-
["dev-story", "implementation"],
|
|
50
|
-
["create-story", "implementation"],
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
for (const [key, phase] of checks) {
|
|
54
|
-
if (isIncomplete(workflowStatus(content, key))) return phase;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (/implementation|phase:\s*4/i.test(content)) return "implementation";
|
|
58
|
-
if (/solutioning|phase:\s*3/i.test(content)) return "solutioning";
|
|
59
|
-
if (/planning|phase:\s*2/i.test(content)) return "planning";
|
|
60
|
-
return "analysis";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function projectLevel(cwd: string): number {
|
|
64
|
-
const configPath = join(cwd, "bmad", "config.yaml");
|
|
65
|
-
const content = readText(configPath);
|
|
66
|
-
if (!content) return 1;
|
|
67
|
-
const match = content.match(/project_level:\s*(\d)/i);
|
|
68
|
-
return match ? Number.parseInt(match[1], 10) : 1;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function resolveBmadAutoSkills(cwd: string): string[] {
|
|
72
|
-
const statusPath = join(cwd, "docs", "bmm-workflow-status.yaml");
|
|
73
|
-
const content = readText(statusPath);
|
|
74
|
-
|
|
75
|
-
if (!content) {
|
|
76
|
-
const level = projectLevel(cwd);
|
|
77
|
-
if (level <= 1) return ["bmad-master", "pm"];
|
|
78
|
-
return ["bmad-master", "analyst"];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const phase = detectPhase(content);
|
|
82
|
-
const skills = [...(PHASE_SKILLS[phase] ?? ["bmad-master"])];
|
|
83
|
-
|
|
84
|
-
const level = projectLevel(cwd);
|
|
85
|
-
if (level <= 1 && phase === "planning" && !skills.includes("pm")) {
|
|
86
|
-
skills.push("pm");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return [...new Set(skills)];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function bmadAutoHint(cwd: string): string | null {
|
|
93
|
-
const statusPath = join(cwd, "docs", "bmm-workflow-status.yaml");
|
|
94
|
-
if (!existsSync(statusPath)) {
|
|
95
|
-
return "BMAD status not found — loaded bmad-master with planning defaults";
|
|
96
|
-
}
|
|
97
|
-
const phase = detectPhase(readText(statusPath) ?? "");
|
|
98
|
-
return `BMAD --auto detected phase: ${phase}`;
|
|
99
|
-
}
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const PHASE_SKILLS: Record<string, string[]> = {
|
|
5
|
+
analysis: ["bmad-master", "analyst"],
|
|
6
|
+
planning: ["bmad-master", "analyst", "pm"],
|
|
7
|
+
solutioning: ["bmad-master", "architect", "ux-designer"],
|
|
8
|
+
implementation: ["bmad-master", "developer", "scrum-master"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function readText(path: string): string | null {
|
|
12
|
+
try {
|
|
13
|
+
return readFileSync(path, "utf-8");
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function workflowStatus(content: string, key: string): string | null {
|
|
20
|
+
const patterns = [
|
|
21
|
+
new RegExp(`${key}:\\s*\\n[\\s\\S]*?status:\\s*([\\w-]+)`, "i"),
|
|
22
|
+
new RegExp(`${key}:[\\s\\S]*?status:\\s*([\\w\\s-]+)`, "i"),
|
|
23
|
+
];
|
|
24
|
+
for (const re of patterns) {
|
|
25
|
+
const match = content.match(re);
|
|
26
|
+
if (match) return match[1].trim().toLowerCase().replace(/\s+/g, "-");
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isIncomplete(status: string | null): boolean {
|
|
32
|
+
if (!status) return true;
|
|
33
|
+
return /not-started|not_started|pending|required|in-progress|in_progress|started/.test(
|
|
34
|
+
status,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function detectPhase(content: string): keyof typeof PHASE_SKILLS {
|
|
39
|
+
const checks: Array<[string, keyof typeof PHASE_SKILLS]> = [
|
|
40
|
+
["product-brief", "analysis"],
|
|
41
|
+
["brainstorm", "analysis"],
|
|
42
|
+
["research", "analysis"],
|
|
43
|
+
["prd", "planning"],
|
|
44
|
+
["tech-spec", "planning"],
|
|
45
|
+
["tech_spec", "planning"],
|
|
46
|
+
["architecture", "solutioning"],
|
|
47
|
+
["ux-design", "solutioning"],
|
|
48
|
+
["sprint-planning", "implementation"],
|
|
49
|
+
["dev-story", "implementation"],
|
|
50
|
+
["create-story", "implementation"],
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
for (const [key, phase] of checks) {
|
|
54
|
+
if (isIncomplete(workflowStatus(content, key))) return phase;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (/implementation|phase:\s*4/i.test(content)) return "implementation";
|
|
58
|
+
if (/solutioning|phase:\s*3/i.test(content)) return "solutioning";
|
|
59
|
+
if (/planning|phase:\s*2/i.test(content)) return "planning";
|
|
60
|
+
return "analysis";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function projectLevel(cwd: string): number {
|
|
64
|
+
const configPath = join(cwd, "bmad", "config.yaml");
|
|
65
|
+
const content = readText(configPath);
|
|
66
|
+
if (!content) return 1;
|
|
67
|
+
const match = content.match(/project_level:\s*(\d)/i);
|
|
68
|
+
return match ? Number.parseInt(match[1], 10) : 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveBmadAutoSkills(cwd: string): string[] {
|
|
72
|
+
const statusPath = join(cwd, "docs", "bmm-workflow-status.yaml");
|
|
73
|
+
const content = readText(statusPath);
|
|
74
|
+
|
|
75
|
+
if (!content) {
|
|
76
|
+
const level = projectLevel(cwd);
|
|
77
|
+
if (level <= 1) return ["bmad-master", "pm"];
|
|
78
|
+
return ["bmad-master", "analyst"];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const phase = detectPhase(content);
|
|
82
|
+
const skills = [...(PHASE_SKILLS[phase] ?? ["bmad-master"])];
|
|
83
|
+
|
|
84
|
+
const level = projectLevel(cwd);
|
|
85
|
+
if (level <= 1 && phase === "planning" && !skills.includes("pm")) {
|
|
86
|
+
skills.push("pm");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [...new Set(skills)];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function bmadAutoHint(cwd: string): string | null {
|
|
93
|
+
const statusPath = join(cwd, "docs", "bmm-workflow-status.yaml");
|
|
94
|
+
if (!existsSync(statusPath)) {
|
|
95
|
+
return "BMAD status not found — loaded bmad-master with planning defaults";
|
|
96
|
+
}
|
|
97
|
+
const phase = detectPhase(readText(statusPath) ?? "");
|
|
98
|
+
return `BMAD --auto detected phase: ${phase}`;
|
|
99
|
+
}
|