@jakkrichm/create-nexus-devflow 2.13.0 → 2.15.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 +4 -4
- package/dist/bin/create-nexus-devflow.d.ts +1 -1
- package/dist/bin/create-nexus-devflow.js +4 -3
- package/dist/bin/create-nexus-devflow.js.map +1 -1
- package/dist/lib/branch-context.d.ts +1 -0
- package/dist/lib/branch-context.js +8 -3
- package/dist/lib/branch-context.js.map +1 -1
- package/dist/lib/dashboard-page.d.ts +5 -2
- package/dist/lib/dashboard-page.js +7 -388
- package/dist/lib/dashboard-page.js.map +1 -1
- package/dist/lib/dashboard-snapshot.d.ts +22 -3
- package/dist/lib/dashboard-snapshot.js +154 -3
- package/dist/lib/dashboard-snapshot.js.map +1 -1
- package/dist/lib/delivery-lifecycle-engine.d.ts +1 -1
- package/dist/lib/delivery-lifecycle-engine.js +1 -1
- package/dist/lib/delivery-lifecycle-engine.js.map +1 -1
- package/dist/lib/mcp-server-engine.d.ts +57 -0
- package/dist/lib/mcp-server-engine.js +678 -0
- package/dist/lib/mcp-server-engine.js.map +1 -0
- package/dist/lib/mcp.d.ts +15 -27
- package/dist/lib/mcp.js +20 -646
- package/dist/lib/mcp.js.map +1 -1
- package/dist/lib/project-status-engine.d.ts +110 -0
- package/dist/lib/project-status-engine.js +493 -0
- package/dist/lib/project-status-engine.js.map +1 -0
- package/dist/lib/skill-manager.d.ts +2 -82
- package/dist/lib/skill-manager.js +15 -856
- package/dist/lib/skill-manager.js.map +1 -1
- package/dist/lib/skill-registry-engine.d.ts +121 -0
- package/dist/lib/skill-registry-engine.js +824 -0
- package/dist/lib/skill-registry-engine.js.map +1 -0
- package/dist/lib/status.d.ts +1 -90
- package/dist/lib/status.js +7 -467
- package/dist/lib/status.js.map +1 -1
- package/dist/lib/studio-view-renderer.d.ts +17 -1
- package/dist/lib/studio-view-renderer.js +855 -3
- package/dist/lib/studio-view-renderer.js.map +1 -1
- package/dist/lib/tooling/commands/scan-doc-contract.js +1 -0
- package/dist/lib/tooling/commands/scan-doc-contract.js.map +1 -1
- package/dist/lib/update.js +1 -1
- package/dist/lib/update.js.map +1 -1
- package/package.json +1 -1
- package/template/.agents/skills/complete/SKILL.md +14 -13
- package/template/.agents/skills/implement/SKILL.md +36 -2
- package/template/.claude/skills/complete/SKILL.md +14 -13
- package/template/.claude/skills/implement/SKILL.md +36 -2
- package/template/AGENTS.md +1 -1
- package/template/devflow/build-plan.md +5 -4
- package/template/devflow/context/ai-interaction.md +4 -2
- package/template/devflow/docs/USAGE.md +145 -0
- package/template/devflow/docs/diagrams/nexus-devflow-architecture.html +14821 -0
- package/template/devflow/docs/diagrams/nexus-devflow-architecture.json +80 -0
- package/template/devflow/docs/diagrams/nexus-devflow-lifecycle.html +14860 -0
- package/template/devflow/docs/diagrams/nexus-devflow-lifecycle.json +88 -0
- package/template/devflow/docs/examples/living-spec/adr.example.md +43 -0
- package/template/devflow/docs/examples/living-spec/current-feature.example.md +113 -0
- package/template/devflow/docs/examples/living-spec/discovery.example.md +45 -0
- package/template/devflow/docs/examples/living-spec/ideas.example.md +22 -0
- package/template/devflow/docs/governance-rules.md +39 -0
- package/template/devflow/docs/manual-review-workflow-spec.md +116 -0
- package/template/devflow/docs/markdown-metadata-contract.md +140 -0
- package/template/devflow/docs/quickstart.md +119 -0
- package/template/devflow/docs/release-process.md +52 -0
- package/template/devflow/docs/skill-selection-policy.md +64 -0
- package/template/devflow/docs/workflow-surface-map.md +85 -0
|
@@ -1,874 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
|
-
import { loadCoreSkillInventory } from "./core-skill-inventory.js";
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
export const DEV_ROLE_SKILLS = Object.freeze([
|
|
10
|
-
"adopt", "audit", "autopilot", "brainstorm", "brief", "browser-tests",
|
|
11
|
-
"bughunter", "check", "ci", "complete", "continuous", "convert-any-to-md",
|
|
12
|
-
"debug", "devflow", "discovery", "doctor", "feature", "fix", "grill",
|
|
13
|
-
"idea", "implement", "onboard", "overview", "prototype", "release",
|
|
14
|
-
"report-html", "rollback", "setup-tests", "status", "test", "try"
|
|
15
|
-
]);
|
|
16
|
-
export const SA_ROLE_SKILLS = Object.freeze([
|
|
17
|
-
"analyze", "audit", "brainstorm", "brief", "bughunter", "convert-any-to-md",
|
|
18
|
-
"devflow", "discovery", "doctor", "grill", "idea", "overview", "prototype",
|
|
19
|
-
"report-html", "status"
|
|
20
|
-
]);
|
|
21
|
-
export const FULL_ROLE_SKILLS = Object.freeze([
|
|
22
|
-
"adopt", "analyze", "audit", "autopilot", "brainstorm", "brief", "browser-tests",
|
|
23
|
-
"bughunter", "check", "ci", "complete", "continuous", "convert-any-to-md",
|
|
24
|
-
"debug", "devflow", "discovery", "doctor", "feature", "fix", "grill",
|
|
25
|
-
"idea", "implement", "onboard", "overview", "prototype", "release",
|
|
26
|
-
"report-html", "rollback", "setup-tests", "status", "test", "try"
|
|
27
|
-
]);
|
|
28
|
-
export function getSkillsForRole(role, allCoreSkills) {
|
|
29
|
-
switch (role) {
|
|
30
|
-
case "sa":
|
|
31
|
-
return SA_ROLE_SKILLS;
|
|
32
|
-
case "full":
|
|
33
|
-
return allCoreSkills || FULL_ROLE_SKILLS;
|
|
34
|
-
case "dev":
|
|
35
|
-
default:
|
|
36
|
-
return DEV_ROLE_SKILLS;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export const KNOWN_SKILL_ALIASES = {
|
|
40
|
-
archify: {
|
|
41
|
-
source: "https://github.com/tt-a1i/archify",
|
|
42
|
-
description: "Interactive technical system architecture, dataflow, and sequence trace diagrams"
|
|
43
|
-
},
|
|
44
|
-
"diagram-design": {
|
|
45
|
-
source: "https://github.com/cathrynlavery/diagram-design",
|
|
46
|
-
description: "39 editorial visual diagram templates (Business, Quadrants, Timelines, Mindmaps, Radar)"
|
|
47
|
-
},
|
|
48
|
-
"9arm-skills": {
|
|
49
|
-
source: "https://github.com/thananon/9arm-skills",
|
|
50
|
-
all: true,
|
|
51
|
-
description: "6 specialized skills (debug-mantra, post-mortem, qwen-agent, scrutinize, management-talk, qwenchance)"
|
|
52
|
-
},
|
|
53
|
-
"9arm": {
|
|
54
|
-
source: "https://github.com/thananon/9arm-skills",
|
|
55
|
-
all: true,
|
|
56
|
-
description: "4 specialized skills (debug-mantra, post-mortem, scrutinize, management-talk)"
|
|
57
|
-
},
|
|
58
|
-
bughunter: {
|
|
59
|
-
source: "https://github.com/elementalsouls/Claude-BugHunter",
|
|
60
|
-
type: "compound-knowledge",
|
|
61
|
-
referencePath: "devflow/.vendor/bughunter",
|
|
62
|
-
description: "[devflow] Offensive security orchestrator & bug hunting guide"
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
export const RECOMMENDED_THIRD_PARTY_SKILLS = Object.freeze([
|
|
66
|
-
{
|
|
67
|
-
source: "https://github.com/tt-a1i/archify",
|
|
68
|
-
description: "Interactive technical system architecture, dataflow, and sequence trace diagrams"
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
source: "https://github.com/cathrynlavery/diagram-design",
|
|
72
|
-
description: "39 editorial visual diagram templates (Business, Quadrants, Timelines, Mindmaps, Radar)"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
source: "https://github.com/thananon/9arm-skills",
|
|
76
|
-
all: true,
|
|
77
|
-
description: "4 specialized skills (debug-mantra, post-mortem, scrutinize, management-talk)"
|
|
78
|
-
}
|
|
79
|
-
]);
|
|
80
|
-
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
81
|
-
const MANIFEST_PATH = path.join(".nexus", "nexus-devflow.json");
|
|
82
|
-
const AGENT_MANIFEST_PATH = "agent-bundle.manifest.json";
|
|
83
|
-
export function parseSkillFrontmatter(content) {
|
|
84
|
-
if (!content.startsWith("---"))
|
|
85
|
-
return {};
|
|
86
|
-
const parts = content.split("---");
|
|
87
|
-
if (parts.length < 3)
|
|
88
|
-
return {};
|
|
89
|
-
const lines = parts[1].split("\n");
|
|
90
|
-
let name;
|
|
91
|
-
let description;
|
|
92
|
-
let version;
|
|
93
|
-
for (const line of lines) {
|
|
94
|
-
const trimmed = line.trim();
|
|
95
|
-
if (trimmed.startsWith("name:")) {
|
|
96
|
-
name = trimmed.replace("name:", "").trim().replace(/^["']|["']$/g, "");
|
|
97
|
-
}
|
|
98
|
-
else if (trimmed.startsWith("description:")) {
|
|
99
|
-
description = trimmed.replace("description:", "").trim().replace(/^["']|["']$/g, "");
|
|
100
|
-
}
|
|
101
|
-
else if (trimmed.startsWith("version:")) {
|
|
102
|
-
version = trimmed.replace("version:", "").trim().replace(/^["']|["']$/g, "");
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Also check metadata: version block
|
|
106
|
-
const metaMatch = parts[1].match(/version:\s*["']?([^"'\r\n]+)["']?/);
|
|
107
|
-
if (!version && metaMatch) {
|
|
108
|
-
version = metaMatch[1].trim();
|
|
109
|
-
}
|
|
110
|
-
return { name, description, version };
|
|
1
|
+
import { SkillRegistryEngine, DefaultGitRepositoryAdapter, DEV_ROLE_SKILLS, SA_ROLE_SKILLS, FULL_ROLE_SKILLS, getSkillsForRole, KNOWN_SKILL_ALIASES, RECOMMENDED_THIRD_PARTY_SKILLS, parseSkillFrontmatter, discoverSkillsInDirectory, findSkillSourceDirectory } from "./skill-registry-engine.js";
|
|
2
|
+
export { SkillRegistryEngine, DefaultGitRepositoryAdapter, DEV_ROLE_SKILLS, SA_ROLE_SKILLS, FULL_ROLE_SKILLS, getSkillsForRole, KNOWN_SKILL_ALIASES, RECOMMENDED_THIRD_PARTY_SKILLS, parseSkillFrontmatter, discoverSkillsInDirectory, findSkillSourceDirectory };
|
|
3
|
+
function getEngine(projectRoot) {
|
|
4
|
+
return new SkillRegistryEngine(projectRoot);
|
|
111
5
|
}
|
|
112
6
|
export async function readDevflowManifest(projectRoot) {
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const raw = await fs.readFile(fullPath, "utf8");
|
|
116
|
-
return JSON.parse(raw);
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
if (err.code === "ENOENT") {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
throw err;
|
|
123
|
-
}
|
|
7
|
+
return getEngine(projectRoot).readManifest();
|
|
124
8
|
}
|
|
125
9
|
export async function writeDevflowManifest(projectRoot, manifest) {
|
|
126
|
-
|
|
127
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
128
|
-
await fs.writeFile(fullPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
10
|
+
return getEngine(projectRoot).writeManifest(manifest);
|
|
129
11
|
}
|
|
130
12
|
export async function listInstalledSkills(projectRoot) {
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
const inventory = await loadCoreSkillInventory(path.join(projectRoot, AGENT_MANIFEST_PATH));
|
|
134
|
-
coreNameSet = inventory.nameSet;
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
// fallback if manifest not found
|
|
138
|
-
}
|
|
139
|
-
const manifest = await readDevflowManifest(projectRoot);
|
|
140
|
-
const recordedThirdParty = Array.isArray(manifest?.thirdPartySkills)
|
|
141
|
-
? manifest.thirdPartySkills
|
|
142
|
-
: [];
|
|
143
|
-
const thirdPartyMap = new Map();
|
|
144
|
-
for (const record of recordedThirdParty) {
|
|
145
|
-
if (record?.name) {
|
|
146
|
-
thirdPartyMap.set(record.name, record);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const agentsDir = path.join(projectRoot, ".agents", "skills");
|
|
150
|
-
const claudeDir = path.join(projectRoot, ".claude", "skills");
|
|
151
|
-
const agentsSkills = new Set();
|
|
152
|
-
const claudeSkills = new Set();
|
|
153
|
-
try {
|
|
154
|
-
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
155
|
-
for (const e of entries) {
|
|
156
|
-
if (e.isDirectory())
|
|
157
|
-
agentsSkills.add(e.name);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// ignore missing dir
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
const entries = await fs.readdir(claudeDir, { withFileTypes: true });
|
|
165
|
-
for (const e of entries) {
|
|
166
|
-
if (e.isDirectory())
|
|
167
|
-
claudeSkills.add(e.name);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// ignore missing dir
|
|
172
|
-
}
|
|
173
|
-
const allNames = new Set([...agentsSkills, ...claudeSkills, ...thirdPartyMap.keys()]);
|
|
174
|
-
const coreSkills = [];
|
|
175
|
-
const thirdPartySkills = [];
|
|
176
|
-
for (const name of [...allNames].sort()) {
|
|
177
|
-
const inAgents = agentsSkills.has(name);
|
|
178
|
-
const inClaude = claudeSkills.has(name);
|
|
179
|
-
const adapters = [];
|
|
180
|
-
if (inAgents)
|
|
181
|
-
adapters.push(".agents");
|
|
182
|
-
if (inClaude)
|
|
183
|
-
adapters.push(".claude");
|
|
184
|
-
const skillFilePath = inAgents
|
|
185
|
-
? path.join(agentsDir, name, "SKILL.md")
|
|
186
|
-
: path.join(claudeDir, name, "SKILL.md");
|
|
187
|
-
let description = "";
|
|
188
|
-
let version = "";
|
|
189
|
-
try {
|
|
190
|
-
const content = await fs.readFile(skillFilePath, "utf8");
|
|
191
|
-
const meta = parseSkillFrontmatter(content);
|
|
192
|
-
description = meta.description || "";
|
|
193
|
-
version = meta.version || "";
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
// no readable SKILL.md
|
|
197
|
-
}
|
|
198
|
-
const recorded = thirdPartyMap.get(name);
|
|
199
|
-
const isCore = coreNameSet.has(name);
|
|
200
|
-
let category = "local-extension";
|
|
201
|
-
if (isCore) {
|
|
202
|
-
category = "core";
|
|
203
|
-
}
|
|
204
|
-
else if (recorded) {
|
|
205
|
-
category = "third-party";
|
|
206
|
-
}
|
|
207
|
-
const detail = {
|
|
208
|
-
name,
|
|
209
|
-
category,
|
|
210
|
-
description: description || recorded?.description || "",
|
|
211
|
-
version: version || recorded?.version,
|
|
212
|
-
source: recorded?.source,
|
|
213
|
-
adapters,
|
|
214
|
-
synced: inAgents && inClaude,
|
|
215
|
-
path: inAgents ? `.agents/skills/${name}` : `.claude/skills/${name}`
|
|
216
|
-
};
|
|
217
|
-
if (isCore) {
|
|
218
|
-
coreSkills.push(detail);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
thirdPartySkills.push(detail);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return {
|
|
225
|
-
coreSkills,
|
|
226
|
-
thirdPartySkills,
|
|
227
|
-
totalCount: coreSkills.length + thirdPartySkills.length
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
export async function discoverSkillsInDirectory(rootDir, maxDepth = 5, currentDepth = 0) {
|
|
231
|
-
const results = [];
|
|
232
|
-
if (currentDepth > maxDepth)
|
|
233
|
-
return results;
|
|
234
|
-
const skillMdPath = path.join(rootDir, "SKILL.md");
|
|
235
|
-
if (fsSync.existsSync(skillMdPath)) {
|
|
236
|
-
try {
|
|
237
|
-
const content = await fs.readFile(skillMdPath, "utf8");
|
|
238
|
-
const meta = parseSkillFrontmatter(content);
|
|
239
|
-
const skillName = meta.name || path.basename(rootDir);
|
|
240
|
-
results.push({
|
|
241
|
-
sourceSkillPath: rootDir,
|
|
242
|
-
skillName,
|
|
243
|
-
meta
|
|
244
|
-
});
|
|
245
|
-
return results;
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
// ignore read error
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
let entries;
|
|
252
|
-
try {
|
|
253
|
-
entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
return results;
|
|
257
|
-
}
|
|
258
|
-
const IGNORED_DIRS = new Set([".git", "node_modules", "dist", ".nexus", "build", "coverage", ".turbo"]);
|
|
259
|
-
for (const entry of entries) {
|
|
260
|
-
if (entry.isDirectory() && !IGNORED_DIRS.has(entry.name)) {
|
|
261
|
-
const subDir = path.join(rootDir, entry.name);
|
|
262
|
-
const subResults = await discoverSkillsInDirectory(subDir, maxDepth, currentDepth + 1);
|
|
263
|
-
results.push(...subResults);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return results;
|
|
267
|
-
}
|
|
268
|
-
export async function findSkillSourceDirectory(sourceDir, requestedName) {
|
|
269
|
-
const discovered = await discoverSkillsInDirectory(sourceDir);
|
|
270
|
-
if (discovered.length === 0) {
|
|
271
|
-
throw new Error(`Could not find any valid skill with SKILL.md in source: ${sourceDir}`);
|
|
272
|
-
}
|
|
273
|
-
if (requestedName) {
|
|
274
|
-
const match = discovered.find((s) => s.skillName === requestedName || path.basename(s.sourceSkillPath) === requestedName);
|
|
275
|
-
if (match) {
|
|
276
|
-
return {
|
|
277
|
-
sourceSkillPath: match.sourceSkillPath,
|
|
278
|
-
skillName: requestedName,
|
|
279
|
-
meta: match.meta
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
const available = discovered.map((s) => s.skillName).join(", ");
|
|
283
|
-
throw new Error(`Skill "${requestedName}" not found in source. Available skills (${discovered.length}): ${available}`);
|
|
284
|
-
}
|
|
285
|
-
if (discovered.length === 1) {
|
|
286
|
-
return {
|
|
287
|
-
sourceSkillPath: discovered[0].sourceSkillPath,
|
|
288
|
-
skillName: discovered[0].skillName,
|
|
289
|
-
meta: discovered[0].meta
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
// Check if one matches the sourceDir basename
|
|
293
|
-
const baseName = path.basename(sourceDir);
|
|
294
|
-
const baseMatch = discovered.find((s) => s.skillName === baseName || path.basename(s.sourceSkillPath) === baseName);
|
|
295
|
-
if (baseMatch) {
|
|
296
|
-
return {
|
|
297
|
-
sourceSkillPath: baseMatch.sourceSkillPath,
|
|
298
|
-
skillName: baseMatch.skillName,
|
|
299
|
-
meta: baseMatch.meta
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
const available = discovered.map((s) => s.skillName).join(", ");
|
|
303
|
-
throw new Error(`Multiple skills found in source (${discovered.length} skills: ${available}). Please specify --name <skill-name> or use --all to install all skills.`);
|
|
13
|
+
return getEngine(projectRoot).list();
|
|
304
14
|
}
|
|
305
15
|
export async function installThirdPartySkill(projectRoot, source, options) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
let compoundName = "bughunter";
|
|
310
|
-
let compoundRefPath = "devflow/.vendor/bughunter";
|
|
311
|
-
if (alias) {
|
|
312
|
-
effectiveSource = alias.source;
|
|
313
|
-
if (alias.type === "compound-knowledge") {
|
|
314
|
-
isCompound = true;
|
|
315
|
-
compoundName = source.toLowerCase();
|
|
316
|
-
compoundRefPath = alias.referencePath || path.join("devflow", ".vendor", compoundName);
|
|
317
|
-
}
|
|
318
|
-
if (alias.all && options?.all === undefined) {
|
|
319
|
-
options = { ...options, all: true };
|
|
320
|
-
}
|
|
321
|
-
if (alias.name && !options?.name) {
|
|
322
|
-
options = { ...options, name: alias.name };
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
else if (source.includes("Claude-BugHunter")) {
|
|
326
|
-
isCompound = true;
|
|
327
|
-
compoundName = "bughunter";
|
|
328
|
-
compoundRefPath = "devflow/.vendor/bughunter";
|
|
329
|
-
}
|
|
330
|
-
const isGitUrl = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(effectiveSource);
|
|
331
|
-
let tempCloneDir = null;
|
|
332
|
-
let sourceDirectory = effectiveSource;
|
|
333
|
-
let coreNameSet = new Set();
|
|
334
|
-
try {
|
|
335
|
-
const inventory = await loadCoreSkillInventory(path.join(projectRoot, AGENT_MANIFEST_PATH));
|
|
336
|
-
coreNameSet = inventory.nameSet;
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
// ignore
|
|
340
|
-
}
|
|
341
|
-
try {
|
|
342
|
-
if (isGitUrl) {
|
|
343
|
-
tempCloneDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-skill-"));
|
|
344
|
-
await execFileAsync("git", ["clone", "--depth", "1", effectiveSource, tempCloneDir]);
|
|
345
|
-
sourceDirectory = tempCloneDir;
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
sourceDirectory = path.isAbsolute(effectiveSource) ? effectiveSource : path.resolve(projectRoot, effectiveSource);
|
|
349
|
-
if (!fsSync.existsSync(sourceDirectory)) {
|
|
350
|
-
throw new Error(`Source directory does not exist: ${effectiveSource}`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
const recordedSource = options?.overrideSource || effectiveSource;
|
|
354
|
-
const recordedType = isCompound ? "compound-knowledge" : isGitUrl || options?.overrideSource ? "git" : "local";
|
|
355
|
-
if (isCompound) {
|
|
356
|
-
const targetRefDir = path.join(projectRoot, compoundRefPath);
|
|
357
|
-
await fs.mkdir(targetRefDir, { recursive: true });
|
|
358
|
-
const srcSkills = path.join(sourceDirectory, "skills");
|
|
359
|
-
if (fsSync.existsSync(srcSkills)) {
|
|
360
|
-
await fs.cp(srcSkills, path.join(targetRefDir, "skills"), { recursive: true });
|
|
361
|
-
}
|
|
362
|
-
const srcCommands = path.join(sourceDirectory, "commands");
|
|
363
|
-
if (fsSync.existsSync(srcCommands)) {
|
|
364
|
-
await fs.cp(srcCommands, path.join(targetRefDir, "commands"), { recursive: true });
|
|
365
|
-
}
|
|
366
|
-
const srcReports = path.join(sourceDirectory, "docs", "disclosed-reports");
|
|
367
|
-
if (fsSync.existsSync(srcReports)) {
|
|
368
|
-
await fs.cp(srcReports, path.join(targetRefDir, "disclosed-reports"), { recursive: true });
|
|
369
|
-
}
|
|
370
|
-
for (const guideFile of ["ENGAGEMENTS.md", "USAGE.md", "README.md", "INSTALL.md"]) {
|
|
371
|
-
const srcFile = path.join(sourceDirectory, guideFile);
|
|
372
|
-
if (fsSync.existsSync(srcFile)) {
|
|
373
|
-
await fs.copyFile(srcFile, path.join(targetRefDir, guideFile));
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
// Ensure Master Skill is written in .agents and .claude
|
|
377
|
-
const masterContent = `---
|
|
378
|
-
name: ${compoundName}
|
|
379
|
-
description: "[devflow] Offensive security orchestrator & bug hunting guide. Indexes 83 vulnerability classes, 5-phase methodology (Think, Hunt, Perimeter, Ship), 681 disclosed HackerOne patterns, and JIT reference guides in ${compoundRefPath}/. Use when running /bughunter, performing security reviews, verifying auth/injection risks in /check or /audit, or testing API endpoints for vulnerabilities."
|
|
380
|
-
argument-hint: "[{target, vuln-class, or topic}]"
|
|
381
|
-
---
|
|
382
|
-
|
|
383
|
-
# 🛡️ ${compoundName} - Offensive Security & Vulnerability Assessment Orchestrator
|
|
384
|
-
|
|
385
|
-
$ARGUMENTS
|
|
386
|
-
|
|
387
|
-
\`${compoundName}\` is the master security testing orchestrator in Nexus-DevFlow, bringing the complete power of **Claude-BugHunter** into your development lifecycle with **Zero Token Bloat**.
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## ⚠️ Pre-Flight Check (Knowledge Base Availability)
|
|
392
|
-
|
|
393
|
-
Before executing any BugHunter analysis or security test:
|
|
394
|
-
1. **Check if \`${compoundRefPath}/\` exists in this project using your file inspection tool**.
|
|
395
|
-
2. **If \`${compoundRefPath}/\` is MISSING / NOT INSTALLED**:
|
|
396
|
-
- **DO NOT hallucinate payloads or fake security reports**.
|
|
397
|
-
- Inform the user in their configured communication language (per \`devflow/config.json\` and \`AGENTS.md\`):
|
|
398
|
-
- State clearly that the BugHunter knowledge base (83 Vulnerability Classes, Payloads & 681 Disclosed Reports) is not yet downloaded in this project.
|
|
399
|
-
- Provide the exact installation command:
|
|
400
|
-
\`\`\`bash
|
|
401
|
-
npx @jakkrichm/create-nexus-devflow skill add ${compoundName}
|
|
402
|
-
\`\`\`
|
|
403
|
-
- Offer to run the installation command on their behalf.
|
|
404
|
-
- Stop and wait for installation before proceeding.
|
|
405
|
-
3. **If \`${compoundRefPath}/\` is PRESENT**:
|
|
406
|
-
- Proceed with the JIT Knowledge Map and 5-phase testing methodology below.
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
### 📦 Full Upstream Arsenal in \`${compoundRefPath}/\`:
|
|
411
|
-
- **83 Full Skills** (\`${compoundRefPath}/skills/<skill-name>/SKILL.md\`): Detailed detection patterns, bypass tables, and payloads for all 83 classes.
|
|
412
|
-
- **15 Slash Commands** (\`${compoundRefPath}/commands/<command>.md\`): \`hunt\`, \`recon\`, \`triage\`, \`validate\`, \`chain\`, \`report\`, \`scope\`, \`token-scan\`, \`surface\`, \`autopilot\`, etc.
|
|
413
|
-
- **681 Disclosed HackerOne Reports** (\`${compoundRefPath}/disclosed-reports/<class>.md\`): Real-world vulnerability citations across 24 core classes.
|
|
414
|
-
- **Engagement Scaffolding** (\`${compoundRefPath}/ENGAGEMENTS.md\`): Comprehensive directory and methodology scaffolding for security assessments.
|
|
415
|
-
|
|
416
|
-
---
|
|
417
|
-
|
|
418
|
-
## 🧭 The 4-Layer Architecture & 5-Phase Methodology
|
|
419
|
-
|
|
420
|
-
\`\`\`text
|
|
421
|
-
┌────────────────────────────────────────────────────────────────────────┐
|
|
422
|
-
│ 1. THINK │ bb-methodology, redteam-mindset, 5-phase workflow │
|
|
423
|
-
├───────────────┼────────────────────────────────────────────────────────┤
|
|
424
|
-
│ 2. HUNT │ 58 web app vulnerability classes (IDOR, SSRF, SQLi) │
|
|
425
|
-
├───────────────┼────────────────────────────────────────────────────────┤
|
|
426
|
-
│ 3. PERIMETER │ M365/Entra, Okta, vCenter, Cloud IAM, SSL-VPN │
|
|
427
|
-
├───────────────┼────────────────────────────────────────────────────────┤
|
|
428
|
-
│ 4. SHIP │ 7-Question Gate, VRT-aware triage, H1/Bugcrowd reports │
|
|
429
|
-
└────────────────────────────────────────────────────────────────────────┘
|
|
430
|
-
\`\`\`
|
|
431
|
-
|
|
432
|
-
### The 5-Phase Workflow:
|
|
433
|
-
1. **Recon & Scope**: Fingerprint tech stack, enumerate subdomains/endpoints, define in-scope vs. out-of-scope boundaries (\`skills/web2-recon/\`, \`skills/recon-scope-triage/\`).
|
|
434
|
-
2. **Map & Surface Ranking**: Identify high-value targets (Auth, Payment, GraphQL mutations, File uploads, Webhook receivers) (\`commands/surface.md\`).
|
|
435
|
-
3. **Hunt & Test**: Apply vulnerability-specific bypass tables, edge-case payloads, and condition variations (\`skills/hunt-*/SKILL.md\`).
|
|
436
|
-
4. **Validate**: Apply the **7-Question Gate** (Impact, Pre-conditions, Repro steps, Root cause, Severity score) (\`skills/triage-validation/\`).
|
|
437
|
-
5. **Report / Remediate**: Produce actionable vulnerability proof, remediation advice, or bug bounty reports (\`commands/report.md\`, \`skills/report-writing/\`).
|
|
438
|
-
|
|
439
|
-
---
|
|
440
|
-
|
|
441
|
-
## 🗂️ Just-in-Time (JIT) Reference Knowledge Map
|
|
442
|
-
|
|
443
|
-
Before analyzing or probing any security concern, **ALWAYS read the relevant reference document in \`${compoundRefPath}/\` using your file reading tool (\`view_file\` / \`grep_search\`)**:
|
|
444
|
-
|
|
445
|
-
| Target Category | Specific Skill Path in \`${compoundRefPath}/\` | Disclosed Reports Path |
|
|
446
|
-
| :--- | :--- | :--- |
|
|
447
|
-
| **Index & Overview** | \`${compoundRefPath}/INDEX.md\` | - |
|
|
448
|
-
| **IDOR / BOLA** | \`skills/hunt-idor/SKILL.md\` | \`disclosed-reports/hunt-idor.md\` |
|
|
449
|
-
| **OAuth 2.0 / SSO** | \`skills/hunt-oauth/SKILL.md\` | \`disclosed-reports/hunt-oauth.md\` |
|
|
450
|
-
| **JWT Flaws & Crypto** | \`skills/hunt-jwt-crypto/SKILL.md\` | \`disclosed-reports/hunt-jwt-crypto.md\` |
|
|
451
|
-
| **SSRF (Cloud IMDS)** | \`skills/hunt-ssrf/SKILL.md\` | \`disclosed-reports/hunt-ssrf.md\` |
|
|
452
|
-
| **SQL & NoSQL Injection** | \`skills/hunt-sqli/SKILL.md\`, \`skills/hunt-nosqli/SKILL.md\` | \`disclosed-reports/hunt-sqli.md\` |
|
|
453
|
-
| **XSS & DOM Injection** | \`skills/hunt-xss/SKILL.md\`, \`skills/hunt-dom/SKILL.md\` | \`disclosed-reports/hunt-xss.md\` |
|
|
454
|
-
| **GraphQL & APIs** | \`skills/hunt-graphql/SKILL.md\`, \`skills/hunt-fintech-graphql/\` | \`disclosed-reports/hunt-graphql.md\` |
|
|
455
|
-
| **Next.js / Node.js** | \`skills/hunt-nextjs/SKILL.md\`, \`skills/hunt-nodejs/SKILL.md\` | - |
|
|
456
|
-
| **Cloud IAM & Perimeter** | \`skills/hunt-cloud-misconfig/\`, \`skills/m365-entra-attack/\` | \`disclosed-reports/hunt-cloud-misconfig.md\` |
|
|
457
|
-
| **CI/CD & Kubernetes** | \`skills/hunt-cicd/SKILL.md\`, \`skills/hunt-k8s/SKILL.md\` | - |
|
|
458
|
-
| **LLM & AI Security** | \`skills/hunt-llm-ai/SKILL.md\`, \`skills/hunt-rag-vector/SKILL.md\` | - |
|
|
459
|
-
|
|
460
|
-
---
|
|
461
|
-
|
|
462
|
-
## 🔗 Integration with Nexus-DevFlow Core Lifecycle
|
|
463
|
-
|
|
464
|
-
1. **During \`/check\` (Security & QA Verification)**:
|
|
465
|
-
- When verifying a feature dealing with Auth, Multi-tenancy, or Data Exports, run a BugHunter self-assessment:
|
|
466
|
-
*Read \`${compoundRefPath}/skills/hunt-idor/SKILL.md\` to test for IDOR and token tampering vulnerabilities on the affected endpoint.*
|
|
467
|
-
2. **During \`/audit\` (Code Audit)**:
|
|
468
|
-
- Review code against Fowler smells AND offensive attack vectors simultaneously, logging findings into \`devflow/context/{xxx-slug}/findings.md\`.
|
|
469
|
-
3. **During \`/debug\` (Security Incident Investigation)**:
|
|
470
|
-
- Trace vulnerability root causes using the 681 disclosed report patterns in \`disclosed-reports/\`.
|
|
471
|
-
|
|
472
|
-
---
|
|
473
|
-
|
|
474
|
-
## 🌐 Artifact & Communication Language
|
|
475
|
-
|
|
476
|
-
All generated reports, findings recorded in \`devflow/context/{xxx-slug}/findings.md\`, and user communication MUST follow the project's central configuration in \`devflow/config.json\` (and \`AGENTS.md\` directive #5 / \`ai-interaction.md\`, defaulting to Thai \`th\`), while code snippets, CVE IDs, HTTP payloads, and technical identifiers remain in English.
|
|
477
|
-
|
|
478
|
-
---
|
|
479
|
-
|
|
480
|
-
## 🔄 Updating Knowledge Base
|
|
481
|
-
|
|
482
|
-
To update all 83 skills, 15 commands, and report patterns to the latest upstream version:
|
|
483
|
-
\`\`\`bash
|
|
484
|
-
npx @jakkrichm/create-nexus-devflow skill update ${compoundName}
|
|
485
|
-
\`\`\`
|
|
486
|
-
`;
|
|
487
|
-
const agentTarget = path.join(projectRoot, ".agents", "skills", compoundName, "SKILL.md");
|
|
488
|
-
const claudeTarget = path.join(projectRoot, ".claude", "skills", compoundName, "SKILL.md");
|
|
489
|
-
await fs.mkdir(path.dirname(agentTarget), { recursive: true });
|
|
490
|
-
await fs.mkdir(path.dirname(claudeTarget), { recursive: true });
|
|
491
|
-
if (!fsSync.existsSync(agentTarget) || options?.force) {
|
|
492
|
-
await fs.writeFile(agentTarget, masterContent, "utf8");
|
|
493
|
-
}
|
|
494
|
-
if (!fsSync.existsSync(claudeTarget) || options?.force) {
|
|
495
|
-
await fs.writeFile(claudeTarget, masterContent, "utf8");
|
|
496
|
-
}
|
|
497
|
-
// Update manifest
|
|
498
|
-
const manifest = (await readDevflowManifest(projectRoot)) || {
|
|
499
|
-
schemaVersion: 1,
|
|
500
|
-
name: "nexus-devflow",
|
|
501
|
-
package: "@jakkrichm/create-nexus-devflow",
|
|
502
|
-
version: "2.9.5"
|
|
503
|
-
};
|
|
504
|
-
const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
|
|
505
|
-
? manifest.thirdPartySkills
|
|
506
|
-
: [];
|
|
507
|
-
const filtered = existingThirdParty.filter((s) => s.name !== compoundName);
|
|
508
|
-
filtered.push({
|
|
509
|
-
name: compoundName,
|
|
510
|
-
source: recordedSource,
|
|
511
|
-
version: "2.0.0",
|
|
512
|
-
description: "Offensive security orchestrator & bug hunting guide",
|
|
513
|
-
installedAt: new Date().toISOString(),
|
|
514
|
-
type: "compound-knowledge",
|
|
515
|
-
referencePath: compoundRefPath
|
|
516
|
-
});
|
|
517
|
-
manifest.thirdPartySkills = filtered;
|
|
518
|
-
await writeDevflowManifest(projectRoot, manifest);
|
|
519
|
-
return {
|
|
520
|
-
name: compoundName,
|
|
521
|
-
category: "third-party",
|
|
522
|
-
description: "Offensive security orchestrator & bug hunting guide",
|
|
523
|
-
version: "2.0.0",
|
|
524
|
-
source: recordedSource,
|
|
525
|
-
adapters: [".agents", ".claude"],
|
|
526
|
-
synced: true,
|
|
527
|
-
path: `.agents/skills/${compoundName}`
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
if (options?.all) {
|
|
531
|
-
const discovered = await discoverSkillsInDirectory(sourceDirectory);
|
|
532
|
-
if (discovered.length === 0) {
|
|
533
|
-
throw new Error(`Could not find any valid skill with SKILL.md in source: ${source}`);
|
|
534
|
-
}
|
|
535
|
-
const installedList = [];
|
|
536
|
-
const manifest = (await readDevflowManifest(projectRoot)) || {
|
|
537
|
-
schemaVersion: 1,
|
|
538
|
-
name: "nexus-devflow",
|
|
539
|
-
package: "@jakkrichm/create-nexus-devflow",
|
|
540
|
-
version: "2.9.3"
|
|
541
|
-
};
|
|
542
|
-
const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
|
|
543
|
-
? manifest.thirdPartySkills
|
|
544
|
-
: [];
|
|
545
|
-
let updatedThirdParty = [...existingThirdParty];
|
|
546
|
-
for (const skill of discovered) {
|
|
547
|
-
const skillName = skill.skillName;
|
|
548
|
-
if (!SKILL_NAME_PATTERN.test(skillName)) {
|
|
549
|
-
continue; // skip invalid names in batch mode
|
|
550
|
-
}
|
|
551
|
-
if (coreNameSet.has(skillName) && !options?.force) {
|
|
552
|
-
continue; // skip core collisions in batch mode
|
|
553
|
-
}
|
|
554
|
-
const targetAgentsSkillDir = path.join(projectRoot, ".agents", "skills", skillName);
|
|
555
|
-
const targetClaudeSkillDir = path.join(projectRoot, ".claude", "skills", skillName);
|
|
556
|
-
await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
|
|
557
|
-
await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
|
|
558
|
-
await fs.mkdir(path.dirname(targetAgentsSkillDir), { recursive: true });
|
|
559
|
-
await fs.mkdir(path.dirname(targetClaudeSkillDir), { recursive: true });
|
|
560
|
-
await fs.cp(skill.sourceSkillPath, targetAgentsSkillDir, { recursive: true });
|
|
561
|
-
await fs.cp(skill.sourceSkillPath, targetClaudeSkillDir, { recursive: true });
|
|
562
|
-
updatedThirdParty = updatedThirdParty.filter((s) => s.name !== skillName);
|
|
563
|
-
updatedThirdParty.push({
|
|
564
|
-
name: skillName,
|
|
565
|
-
source: recordedSource,
|
|
566
|
-
version: skill.meta.version || "1.0.0",
|
|
567
|
-
description: skill.meta.description || "",
|
|
568
|
-
installedAt: new Date().toISOString(),
|
|
569
|
-
type: recordedType
|
|
570
|
-
});
|
|
571
|
-
installedList.push({
|
|
572
|
-
name: skillName,
|
|
573
|
-
category: "third-party",
|
|
574
|
-
description: skill.meta.description || "",
|
|
575
|
-
version: skill.meta.version || "1.0.0",
|
|
576
|
-
source: recordedSource,
|
|
577
|
-
adapters: [".agents", ".claude"],
|
|
578
|
-
synced: true,
|
|
579
|
-
path: `.agents/skills/${skillName}`
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
manifest.thirdPartySkills = updatedThirdParty;
|
|
583
|
-
await writeDevflowManifest(projectRoot, manifest);
|
|
584
|
-
return installedList;
|
|
585
|
-
}
|
|
586
|
-
const { sourceSkillPath, skillName, meta } = await findSkillSourceDirectory(sourceDirectory, options?.name);
|
|
587
|
-
if (!SKILL_NAME_PATTERN.test(skillName)) {
|
|
588
|
-
throw new Error(`Invalid skill name: "${skillName}". Must use kebab-case (e.g. "diagram-design").`);
|
|
589
|
-
}
|
|
590
|
-
if (coreNameSet.has(skillName) && !options?.force) {
|
|
591
|
-
throw new Error(`Cannot install skill with name "${skillName}" as it conflicts with a Core DevFlow Skill.`);
|
|
592
|
-
}
|
|
593
|
-
const targetAgentsSkillDir = path.join(projectRoot, ".agents", "skills", skillName);
|
|
594
|
-
const targetClaudeSkillDir = path.join(projectRoot, ".claude", "skills", skillName);
|
|
595
|
-
await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
|
|
596
|
-
await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
|
|
597
|
-
await fs.mkdir(path.dirname(targetAgentsSkillDir), { recursive: true });
|
|
598
|
-
await fs.mkdir(path.dirname(targetClaudeSkillDir), { recursive: true });
|
|
599
|
-
// Copy verbatim to .agents/skills/<name>
|
|
600
|
-
await fs.cp(sourceSkillPath, targetAgentsSkillDir, { recursive: true });
|
|
601
|
-
// Copy verbatim to .claude/skills/<name>
|
|
602
|
-
await fs.cp(sourceSkillPath, targetClaudeSkillDir, { recursive: true });
|
|
603
|
-
// Update .nexus/nexus-devflow.json
|
|
604
|
-
const manifest = (await readDevflowManifest(projectRoot)) || {
|
|
605
|
-
schemaVersion: 1,
|
|
606
|
-
name: "nexus-devflow",
|
|
607
|
-
package: "@jakkrichm/create-nexus-devflow",
|
|
608
|
-
version: "2.9.3"
|
|
609
|
-
};
|
|
610
|
-
const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
|
|
611
|
-
? manifest.thirdPartySkills
|
|
612
|
-
: [];
|
|
613
|
-
const existingRecord = existingThirdParty.find((s) => s.name === skillName);
|
|
614
|
-
const filtered = existingThirdParty.filter((s) => s.name !== skillName);
|
|
615
|
-
filtered.push({
|
|
616
|
-
name: skillName,
|
|
617
|
-
source: recordedSource,
|
|
618
|
-
version: meta.version || existingRecord?.version || "1.0.0",
|
|
619
|
-
description: meta.description || existingRecord?.description || "",
|
|
620
|
-
installedAt: new Date().toISOString(),
|
|
621
|
-
type: existingRecord?.type || recordedType,
|
|
622
|
-
referencePath: existingRecord?.referencePath
|
|
623
|
-
});
|
|
624
|
-
manifest.thirdPartySkills = filtered;
|
|
625
|
-
await writeDevflowManifest(projectRoot, manifest);
|
|
626
|
-
return {
|
|
627
|
-
name: skillName,
|
|
628
|
-
category: "third-party",
|
|
629
|
-
description: meta.description || existingRecord?.description || "",
|
|
630
|
-
version: meta.version || existingRecord?.version || "1.0.0",
|
|
631
|
-
source: recordedSource,
|
|
632
|
-
adapters: [".agents", ".claude"],
|
|
633
|
-
synced: true,
|
|
634
|
-
path: `.agents/skills/${skillName}`
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
finally {
|
|
638
|
-
if (tempCloneDir) {
|
|
639
|
-
try {
|
|
640
|
-
await fs.rm(tempCloneDir, { recursive: true, force: true });
|
|
641
|
-
}
|
|
642
|
-
catch {
|
|
643
|
-
// ignore cleanup error
|
|
644
|
-
}
|
|
645
|
-
}
|
|
16
|
+
const result = await getEngine(projectRoot).install(source, options);
|
|
17
|
+
if (result.details.length === 1 && !options?.all) {
|
|
18
|
+
return result.details[0];
|
|
646
19
|
}
|
|
20
|
+
return result.details;
|
|
647
21
|
}
|
|
648
22
|
export async function removeThirdPartySkill(projectRoot, name) {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const inventory = await loadCoreSkillInventory(path.join(projectRoot, AGENT_MANIFEST_PATH));
|
|
652
|
-
coreNameSet = inventory.nameSet;
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
// ignore
|
|
656
|
-
}
|
|
657
|
-
if (coreNameSet.has(name)) {
|
|
658
|
-
throw new Error(`Cannot remove Core Skill: "${name}". Core skills are managed by Nexus-DevFlow.`);
|
|
659
|
-
}
|
|
660
|
-
const targetAgentsSkillDir = path.join(projectRoot, ".agents", "skills", name);
|
|
661
|
-
const targetClaudeSkillDir = path.join(projectRoot, ".claude", "skills", name);
|
|
662
|
-
let removedAny = false;
|
|
663
|
-
if (fsSync.existsSync(targetAgentsSkillDir)) {
|
|
664
|
-
await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
|
|
665
|
-
removedAny = true;
|
|
666
|
-
}
|
|
667
|
-
if (fsSync.existsSync(targetClaudeSkillDir)) {
|
|
668
|
-
await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
|
|
669
|
-
removedAny = true;
|
|
670
|
-
}
|
|
671
|
-
const manifest = await readDevflowManifest(projectRoot);
|
|
672
|
-
if (manifest && Array.isArray(manifest.thirdPartySkills)) {
|
|
673
|
-
const prevList = manifest.thirdPartySkills;
|
|
674
|
-
const nextList = prevList.filter((s) => s.name !== name);
|
|
675
|
-
if (nextList.length !== prevList.length) {
|
|
676
|
-
manifest.thirdPartySkills = nextList;
|
|
677
|
-
await writeDevflowManifest(projectRoot, manifest);
|
|
678
|
-
removedAny = true;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return removedAny;
|
|
23
|
+
const res = await getEngine(projectRoot).remove(name);
|
|
24
|
+
return res.removed;
|
|
682
25
|
}
|
|
683
26
|
export async function syncSkills(projectRoot) {
|
|
684
|
-
|
|
685
|
-
const claudeDir = path.join(projectRoot, ".claude", "skills");
|
|
686
|
-
if (!fsSync.existsSync(agentsDir)) {
|
|
687
|
-
return { syncedCount: 0, skills: [] };
|
|
688
|
-
}
|
|
689
|
-
await fs.rm(claudeDir, { recursive: true, force: true });
|
|
690
|
-
await fs.mkdir(claudeDir, { recursive: true });
|
|
691
|
-
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
692
|
-
const syncedSkills = [];
|
|
693
|
-
for (const entry of entries) {
|
|
694
|
-
if (entry.isDirectory()) {
|
|
695
|
-
const src = path.join(agentsDir, entry.name);
|
|
696
|
-
const dst = path.join(claudeDir, entry.name);
|
|
697
|
-
await fs.cp(src, dst, { recursive: true });
|
|
698
|
-
syncedSkills.push(entry.name);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
return {
|
|
702
|
-
syncedCount: syncedSkills.length,
|
|
703
|
-
skills: syncedSkills
|
|
704
|
-
};
|
|
27
|
+
return getEngine(projectRoot).sync();
|
|
705
28
|
}
|
|
706
29
|
export async function updateThirdPartySkills(projectRoot, targetSkillName, options) {
|
|
707
|
-
|
|
708
|
-
const existingThirdParty = Array.isArray(manifest?.thirdPartySkills)
|
|
709
|
-
? manifest.thirdPartySkills
|
|
710
|
-
: [];
|
|
711
|
-
if (existingThirdParty.length === 0) {
|
|
712
|
-
return { updatedSkills: [], failedSkills: [], totalUpdated: 0 };
|
|
713
|
-
}
|
|
714
|
-
let skillsToUpdate = existingThirdParty;
|
|
715
|
-
if (targetSkillName && targetSkillName !== "--all") {
|
|
716
|
-
skillsToUpdate = existingThirdParty.filter((s) => s.name === targetSkillName);
|
|
717
|
-
if (skillsToUpdate.length === 0) {
|
|
718
|
-
throw new Error(`Skill "${targetSkillName}" is not installed as a third-party skill.`);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
// Group by source to avoid duplicate cloning
|
|
722
|
-
const sourceToSkillsMap = new Map();
|
|
723
|
-
for (const skill of skillsToUpdate) {
|
|
724
|
-
const list = sourceToSkillsMap.get(skill.source) || [];
|
|
725
|
-
list.push(skill);
|
|
726
|
-
sourceToSkillsMap.set(skill.source, list);
|
|
727
|
-
}
|
|
728
|
-
const updatedSkills = [];
|
|
729
|
-
const failedSkills = [];
|
|
730
|
-
for (const [source, skills] of sourceToSkillsMap) {
|
|
731
|
-
const isGit = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(source);
|
|
732
|
-
let tempCloneDir = null;
|
|
733
|
-
try {
|
|
734
|
-
let sourceDir = source;
|
|
735
|
-
if (isGit) {
|
|
736
|
-
tempCloneDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-skill-update-"));
|
|
737
|
-
await execFileAsync("git", ["clone", "--depth", "1", source, tempCloneDir]);
|
|
738
|
-
sourceDir = tempCloneDir;
|
|
739
|
-
}
|
|
740
|
-
for (const skill of skills) {
|
|
741
|
-
try {
|
|
742
|
-
if (skill.type === "compound-knowledge") {
|
|
743
|
-
let directSkillFound = false;
|
|
744
|
-
try {
|
|
745
|
-
const detail = (await installThirdPartySkill(projectRoot, sourceDir, {
|
|
746
|
-
name: skill.name,
|
|
747
|
-
force: true,
|
|
748
|
-
overrideSource: isGit ? source : undefined
|
|
749
|
-
}));
|
|
750
|
-
updatedSkills.push(detail);
|
|
751
|
-
directSkillFound = true;
|
|
752
|
-
}
|
|
753
|
-
catch {
|
|
754
|
-
directSkillFound = false;
|
|
755
|
-
}
|
|
756
|
-
if (!directSkillFound) {
|
|
757
|
-
const refRelPath = skill.referencePath || path.join("devflow", ".vendor", skill.name);
|
|
758
|
-
const targetRefDir = path.join(projectRoot, refRelPath);
|
|
759
|
-
await fs.mkdir(targetRefDir, { recursive: true });
|
|
760
|
-
// 1. Sync source skills directory to reference/skills
|
|
761
|
-
const srcSkills = path.join(sourceDir, "skills");
|
|
762
|
-
if (fsSync.existsSync(srcSkills)) {
|
|
763
|
-
await fs.cp(srcSkills, path.join(targetRefDir, "skills"), { recursive: true });
|
|
764
|
-
}
|
|
765
|
-
// 2. Sync source commands directory to reference/commands
|
|
766
|
-
const srcCommands = path.join(sourceDir, "commands");
|
|
767
|
-
if (fsSync.existsSync(srcCommands)) {
|
|
768
|
-
await fs.cp(srcCommands, path.join(targetRefDir, "commands"), { recursive: true });
|
|
769
|
-
}
|
|
770
|
-
// 3. Sync source disclosed-reports directory
|
|
771
|
-
const srcReports = path.join(sourceDir, "docs", "disclosed-reports");
|
|
772
|
-
if (fsSync.existsSync(srcReports)) {
|
|
773
|
-
await fs.cp(srcReports, path.join(targetRefDir, "disclosed-reports"), { recursive: true });
|
|
774
|
-
}
|
|
775
|
-
// 4. Sync source docs directory
|
|
776
|
-
const srcDocs = path.join(sourceDir, "docs");
|
|
777
|
-
if (fsSync.existsSync(srcDocs)) {
|
|
778
|
-
await fs.cp(srcDocs, path.join(targetRefDir, "docs"), { recursive: true });
|
|
779
|
-
}
|
|
780
|
-
// 5. Copy notable guide files
|
|
781
|
-
for (const guideFile of ["ENGAGEMENTS.md", "USAGE.md", "README.md", "INSTALL.md"]) {
|
|
782
|
-
const srcFile = path.join(sourceDir, guideFile);
|
|
783
|
-
if (fsSync.existsSync(srcFile)) {
|
|
784
|
-
await fs.copyFile(srcFile, path.join(targetRefDir, guideFile));
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
let newVersion = skill.version || "1.0.0";
|
|
788
|
-
try {
|
|
789
|
-
const pkgPath = path.join(sourceDir, "package.json");
|
|
790
|
-
if (fsSync.existsSync(pkgPath)) {
|
|
791
|
-
const pkgRaw = await fs.readFile(pkgPath, "utf8");
|
|
792
|
-
const pkg = JSON.parse(pkgRaw);
|
|
793
|
-
if (pkg.version)
|
|
794
|
-
newVersion = pkg.version;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
catch {
|
|
798
|
-
// ignore
|
|
799
|
-
}
|
|
800
|
-
// Update manifest
|
|
801
|
-
const currentManifest = (await readDevflowManifest(projectRoot)) || {};
|
|
802
|
-
const existingList = Array.isArray(currentManifest.thirdPartySkills)
|
|
803
|
-
? currentManifest.thirdPartySkills
|
|
804
|
-
: [];
|
|
805
|
-
const updatedList = existingList.map((s) => {
|
|
806
|
-
if (s.name === skill.name) {
|
|
807
|
-
return {
|
|
808
|
-
...s,
|
|
809
|
-
version: newVersion,
|
|
810
|
-
installedAt: new Date().toISOString(),
|
|
811
|
-
type: "compound-knowledge",
|
|
812
|
-
referencePath: refRelPath
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
return s;
|
|
816
|
-
});
|
|
817
|
-
currentManifest.thirdPartySkills = updatedList;
|
|
818
|
-
await writeDevflowManifest(projectRoot, currentManifest);
|
|
819
|
-
updatedSkills.push({
|
|
820
|
-
name: skill.name,
|
|
821
|
-
category: "third-party",
|
|
822
|
-
description: skill.description || "Compound Knowledge Skill",
|
|
823
|
-
version: newVersion,
|
|
824
|
-
source: skill.source,
|
|
825
|
-
adapters: [".agents", ".claude"],
|
|
826
|
-
synced: true,
|
|
827
|
-
path: `.agents/skills/${skill.name}`
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
else {
|
|
832
|
-
const detail = (await installThirdPartySkill(projectRoot, sourceDir, {
|
|
833
|
-
name: skill.name,
|
|
834
|
-
force: true,
|
|
835
|
-
overrideSource: isGit ? source : undefined
|
|
836
|
-
}));
|
|
837
|
-
updatedSkills.push(detail);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
catch (err) {
|
|
841
|
-
failedSkills.push({
|
|
842
|
-
name: skill.name,
|
|
843
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
catch (err) {
|
|
849
|
-
for (const skill of skills) {
|
|
850
|
-
failedSkills.push({
|
|
851
|
-
name: skill.name,
|
|
852
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
finally {
|
|
857
|
-
if (tempCloneDir) {
|
|
858
|
-
try {
|
|
859
|
-
await fs.rm(tempCloneDir, { recursive: true, force: true });
|
|
860
|
-
}
|
|
861
|
-
catch {
|
|
862
|
-
// ignore
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
return {
|
|
868
|
-
updatedSkills,
|
|
869
|
-
failedSkills,
|
|
870
|
-
totalUpdated: updatedSkills.length
|
|
871
|
-
};
|
|
30
|
+
return getEngine(projectRoot).update(targetSkillName, options);
|
|
872
31
|
}
|
|
873
32
|
export async function installRecommendedSkills(projectRoot, options) {
|
|
874
33
|
const presets = options?.presets || RECOMMENDED_THIRD_PARTY_SKILLS;
|