@jakkrichm/create-nexus-devflow 2.14.0 → 2.15.1

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.
Files changed (42) hide show
  1. package/dist/lib/branch-context.d.ts +1 -0
  2. package/dist/lib/branch-context.js +8 -3
  3. package/dist/lib/branch-context.js.map +1 -1
  4. package/dist/lib/dashboard-page.d.ts +5 -2
  5. package/dist/lib/dashboard-page.js +7 -388
  6. package/dist/lib/dashboard-page.js.map +1 -1
  7. package/dist/lib/dashboard-snapshot.d.ts +22 -3
  8. package/dist/lib/dashboard-snapshot.js +154 -3
  9. package/dist/lib/dashboard-snapshot.js.map +1 -1
  10. package/dist/lib/delivery-lifecycle-engine.d.ts +1 -1
  11. package/dist/lib/delivery-lifecycle-engine.js +1 -1
  12. package/dist/lib/delivery-lifecycle-engine.js.map +1 -1
  13. package/dist/lib/mcp-server-engine.d.ts +57 -0
  14. package/dist/lib/mcp-server-engine.js +678 -0
  15. package/dist/lib/mcp-server-engine.js.map +1 -0
  16. package/dist/lib/mcp.d.ts +15 -27
  17. package/dist/lib/mcp.js +20 -646
  18. package/dist/lib/mcp.js.map +1 -1
  19. package/dist/lib/project-status-engine.d.ts +110 -0
  20. package/dist/lib/project-status-engine.js +493 -0
  21. package/dist/lib/project-status-engine.js.map +1 -0
  22. package/dist/lib/skill-manager.d.ts +2 -82
  23. package/dist/lib/skill-manager.js +15 -1168
  24. package/dist/lib/skill-manager.js.map +1 -1
  25. package/dist/lib/skill-registry-engine.d.ts +121 -0
  26. package/dist/lib/skill-registry-engine.js +810 -0
  27. package/dist/lib/skill-registry-engine.js.map +1 -0
  28. package/dist/lib/status.d.ts +1 -90
  29. package/dist/lib/status.js +7 -467
  30. package/dist/lib/status.js.map +1 -1
  31. package/dist/lib/studio-view-renderer.d.ts +17 -1
  32. package/dist/lib/studio-view-renderer.js +855 -3
  33. package/dist/lib/studio-view-renderer.js.map +1 -1
  34. package/package.json +1 -1
  35. package/template/.agents/skills/complete/SKILL.md +14 -13
  36. package/template/.agents/skills/debug/SKILL.md +14 -0
  37. package/template/.agents/skills/implement/SKILL.md +36 -2
  38. package/template/.claude/skills/complete/SKILL.md +14 -13
  39. package/template/.claude/skills/debug/SKILL.md +14 -0
  40. package/template/.claude/skills/implement/SKILL.md +36 -2
  41. package/template/devflow/build-plan.md +9 -5
  42. package/template/devflow/context/ai-interaction.md +4 -2
@@ -0,0 +1,810 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import fsSync from "node:fs";
4
+ import os from "node:os";
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: "5 specialized skills (post-mortem, qwen-agent, scrutinize, management-talk, qwenchance)"
52
+ },
53
+ "9arm": {
54
+ source: "https://github.com/thananon/9arm-skills",
55
+ all: true,
56
+ description: "3 specialized skills (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
+ const metaMatch = parts[1].match(/version:\s*["']?([^"'\r\n]+)["']?/);
106
+ if (!version && metaMatch) {
107
+ version = metaMatch[1].trim();
108
+ }
109
+ return { name, description, version };
110
+ }
111
+ export async function discoverSkillsInDirectory(searchDir, baseDir = searchDir) {
112
+ const discovered = [];
113
+ async function walk(currentDir) {
114
+ let entries;
115
+ try {
116
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
117
+ }
118
+ catch {
119
+ return;
120
+ }
121
+ const hasSkillMd = entries.some((e) => e.isFile() && e.name.toLowerCase() === "skill.md");
122
+ if (hasSkillMd) {
123
+ const skillMdPath = path.join(currentDir, "SKILL.md");
124
+ let meta = {};
125
+ try {
126
+ const content = await fs.readFile(skillMdPath, "utf8");
127
+ meta = parseSkillFrontmatter(content);
128
+ }
129
+ catch {
130
+ // ignore
131
+ }
132
+ const inferredName = path.basename(currentDir);
133
+ const skillName = meta.name || inferredName;
134
+ discovered.push({ skillName, sourceSkillPath: currentDir, meta });
135
+ return;
136
+ }
137
+ for (const entry of entries) {
138
+ if (entry.isDirectory() &&
139
+ !entry.name.startsWith(".") &&
140
+ entry.name !== "node_modules" &&
141
+ entry.name !== "vendor") {
142
+ await walk(path.join(currentDir, entry.name));
143
+ }
144
+ }
145
+ }
146
+ await walk(searchDir);
147
+ return discovered;
148
+ }
149
+ export async function findSkillSourceDirectory(extractedDir, targetSkillName) {
150
+ const discovered = await discoverSkillsInDirectory(extractedDir);
151
+ if (discovered.length === 0) {
152
+ throw new Error(`No skill definition (SKILL.md) found in repository. Make sure the repository contains at least one folder with a SKILL.md file.`);
153
+ }
154
+ if (targetSkillName) {
155
+ const match = discovered.find((d) => d.skillName === targetSkillName || path.basename(d.sourceSkillPath) === targetSkillName);
156
+ if (match) {
157
+ return match;
158
+ }
159
+ const available = discovered.map((d) => d.skillName).join(", ");
160
+ throw new Error(`Skill "${targetSkillName}" not found in source repository. Available skills: ${available}`);
161
+ }
162
+ if (discovered.length === 1) {
163
+ return discovered[0];
164
+ }
165
+ const available = discovered.map((d) => `"${d.skillName}"`).join(", ");
166
+ throw new Error(`Multiple skills found in source (${discovered.length} skills: ${available}). Please specify --name <skill-name> or use --all to install all skills.`);
167
+ }
168
+ export class DefaultGitRepositoryAdapter {
169
+ async fetchPackage(source, targetDir, _options) {
170
+ const isGitUrl = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(source);
171
+ if (isGitUrl) {
172
+ await execFileAsync("git", ["clone", "--depth", "1", source, targetDir]);
173
+ }
174
+ else {
175
+ const src = path.resolve(source);
176
+ if (!fsSync.existsSync(src)) {
177
+ throw new Error(`Source directory does not exist: ${source}`);
178
+ }
179
+ await fs.cp(src, targetDir, { recursive: true });
180
+ }
181
+ }
182
+ }
183
+ // ---------------------------------------------------------------------------
184
+ // SkillRegistryEngine Core
185
+ // ---------------------------------------------------------------------------
186
+ export class SkillRegistryEngine {
187
+ projectRoot;
188
+ repositoryAdapter;
189
+ constructor(projectRoot, repositoryAdapter = new DefaultGitRepositoryAdapter()) {
190
+ this.projectRoot = projectRoot;
191
+ this.repositoryAdapter = repositoryAdapter;
192
+ }
193
+ async readManifest() {
194
+ const fullPath = path.join(this.projectRoot, MANIFEST_PATH);
195
+ try {
196
+ const raw = await fs.readFile(fullPath, "utf8");
197
+ return JSON.parse(raw);
198
+ }
199
+ catch (err) {
200
+ if (err.code === "ENOENT") {
201
+ return null;
202
+ }
203
+ throw err;
204
+ }
205
+ }
206
+ async writeManifest(manifest) {
207
+ const fullPath = path.join(this.projectRoot, MANIFEST_PATH);
208
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
209
+ await fs.writeFile(fullPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
210
+ }
211
+ async list(options) {
212
+ let coreNameSet = new Set();
213
+ try {
214
+ const inventory = await loadCoreSkillInventory(path.join(this.projectRoot, AGENT_MANIFEST_PATH));
215
+ coreNameSet = inventory.nameSet;
216
+ }
217
+ catch {
218
+ // fallback
219
+ }
220
+ const manifest = await this.readManifest();
221
+ const recordedThirdParty = Array.isArray(manifest?.thirdPartySkills)
222
+ ? manifest.thirdPartySkills
223
+ : [];
224
+ const thirdPartyMap = new Map();
225
+ for (const record of recordedThirdParty) {
226
+ if (record?.name) {
227
+ thirdPartyMap.set(record.name, record);
228
+ }
229
+ }
230
+ const agentsDir = path.join(this.projectRoot, ".agents", "skills");
231
+ const claudeDir = path.join(this.projectRoot, ".claude", "skills");
232
+ const agentsSkills = new Set();
233
+ const claudeSkills = new Set();
234
+ try {
235
+ const entries = await fs.readdir(agentsDir, { withFileTypes: true });
236
+ for (const e of entries) {
237
+ if (e.isDirectory())
238
+ agentsSkills.add(e.name);
239
+ }
240
+ }
241
+ catch {
242
+ // ignore
243
+ }
244
+ try {
245
+ const entries = await fs.readdir(claudeDir, { withFileTypes: true });
246
+ for (const e of entries) {
247
+ if (e.isDirectory())
248
+ claudeSkills.add(e.name);
249
+ }
250
+ }
251
+ catch {
252
+ // ignore
253
+ }
254
+ const allSkillNames = new Set([...agentsSkills, ...claudeSkills, ...thirdPartyMap.keys()]);
255
+ const coreSkills = [];
256
+ const thirdPartySkills = [];
257
+ const roleSkills = options?.role ? getSkillsForRole(options.role) : null;
258
+ for (const skillName of allSkillNames) {
259
+ if (roleSkills && !roleSkills.includes(skillName) && !thirdPartyMap.has(skillName)) {
260
+ continue;
261
+ }
262
+ const inAgents = agentsSkills.has(skillName);
263
+ const inClaude = claudeSkills.has(skillName);
264
+ const adapters = [];
265
+ if (inAgents)
266
+ adapters.push(".agents");
267
+ if (inClaude)
268
+ adapters.push(".claude");
269
+ const synced = inAgents && inClaude;
270
+ let description = "";
271
+ let version = "1.0.0";
272
+ const skillPath = inAgents
273
+ ? path.join(agentsDir, skillName, "SKILL.md")
274
+ : path.join(claudeDir, skillName, "SKILL.md");
275
+ try {
276
+ const content = await fs.readFile(skillPath, "utf8");
277
+ const parsed = parseSkillFrontmatter(content);
278
+ if (parsed.description)
279
+ description = parsed.description;
280
+ if (parsed.version)
281
+ version = parsed.version;
282
+ }
283
+ catch {
284
+ // ignore
285
+ }
286
+ const isRecordedThirdParty = thirdPartyMap.has(skillName);
287
+ const isCore = coreNameSet.has(skillName);
288
+ if (isRecordedThirdParty) {
289
+ const record = thirdPartyMap.get(skillName);
290
+ thirdPartySkills.push({
291
+ name: skillName,
292
+ category: "third-party",
293
+ description: record.description || description,
294
+ version: record.version || version,
295
+ source: record.source,
296
+ adapters,
297
+ synced,
298
+ path: inAgents ? `.agents/skills/${skillName}` : `.claude/skills/${skillName}`
299
+ });
300
+ }
301
+ else if (isCore) {
302
+ coreSkills.push({
303
+ name: skillName,
304
+ category: "core",
305
+ description,
306
+ version,
307
+ adapters,
308
+ synced,
309
+ path: inAgents ? `.agents/skills/${skillName}` : `.claude/skills/${skillName}`
310
+ });
311
+ }
312
+ else {
313
+ thirdPartySkills.push({
314
+ name: skillName,
315
+ category: "local-extension",
316
+ description,
317
+ version,
318
+ adapters,
319
+ synced,
320
+ path: inAgents ? `.agents/skills/${skillName}` : `.claude/skills/${skillName}`
321
+ });
322
+ }
323
+ }
324
+ coreSkills.sort((a, b) => a.name.localeCompare(b.name));
325
+ thirdPartySkills.sort((a, b) => a.name.localeCompare(b.name));
326
+ return {
327
+ coreSkills,
328
+ thirdPartySkills,
329
+ totalCount: coreSkills.length + thirdPartySkills.length
330
+ };
331
+ }
332
+ async install(sourceOrOptions, options) {
333
+ let source;
334
+ let opts;
335
+ if (typeof sourceOrOptions === "string") {
336
+ source = sourceOrOptions;
337
+ opts = options || {};
338
+ }
339
+ else {
340
+ opts = sourceOrOptions || {};
341
+ source = opts.overrideSource || opts.name || "";
342
+ }
343
+ if (!source && !opts.overrideSource && !opts.name) {
344
+ throw new Error("No skill source or name provided to install.");
345
+ }
346
+ const alias = KNOWN_SKILL_ALIASES[source.toLowerCase()];
347
+ let effectiveSource = source;
348
+ let isCompound = false;
349
+ let compoundName = "bughunter";
350
+ let compoundRefPath = "devflow/.vendor/bughunter";
351
+ if (alias) {
352
+ effectiveSource = alias.source;
353
+ if (alias.type === "compound-knowledge") {
354
+ isCompound = true;
355
+ compoundName = alias.name || source.toLowerCase();
356
+ compoundRefPath = alias.referencePath || path.join("devflow", ".vendor", compoundName);
357
+ }
358
+ if (alias.all && opts.all === undefined) {
359
+ opts = { ...opts, all: true };
360
+ }
361
+ if (alias.name && !opts.name) {
362
+ opts = { ...opts, name: alias.name };
363
+ }
364
+ }
365
+ else if (source.includes("Claude-BugHunter")) {
366
+ isCompound = true;
367
+ compoundName = "bughunter";
368
+ compoundRefPath = "devflow/.vendor/bughunter";
369
+ }
370
+ else if (source.includes("mattpocock/skills") || source.includes("matt-pocock")) {
371
+ isCompound = true;
372
+ compoundName = "matt-pocock";
373
+ compoundRefPath = "devflow/.vendor/matt-pocock";
374
+ }
375
+ let tempCloneDir = null;
376
+ let coreNameSet = new Set();
377
+ try {
378
+ const inventory = await loadCoreSkillInventory(path.join(this.projectRoot, AGENT_MANIFEST_PATH));
379
+ coreNameSet = inventory.nameSet;
380
+ }
381
+ catch {
382
+ // ignore
383
+ }
384
+ try {
385
+ tempCloneDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-skill-fetch-"));
386
+ await this.repositoryAdapter.fetchPackage(effectiveSource, tempCloneDir);
387
+ const sourceDirectory = tempCloneDir;
388
+ const recordedSource = opts.overrideSource || effectiveSource;
389
+ const isGitUrl = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(effectiveSource);
390
+ const recordedType = isCompound ? "compound-knowledge" : isGitUrl || opts.overrideSource ? "git" : "local";
391
+ if (isCompound) {
392
+ const targetRefDir = path.join(this.projectRoot, compoundRefPath);
393
+ await fs.mkdir(targetRefDir, { recursive: true });
394
+ const srcSkills = path.join(sourceDirectory, "skills");
395
+ if (fsSync.existsSync(srcSkills)) {
396
+ await fs.cp(srcSkills, path.join(targetRefDir, "skills"), { recursive: true });
397
+ }
398
+ const srcCommands = path.join(sourceDirectory, "commands");
399
+ if (fsSync.existsSync(srcCommands)) {
400
+ await fs.cp(srcCommands, path.join(targetRefDir, "commands"), { recursive: true });
401
+ }
402
+ const srcDocs = path.join(sourceDirectory, "docs");
403
+ if (fsSync.existsSync(srcDocs)) {
404
+ await fs.cp(srcDocs, path.join(targetRefDir, "docs"), { recursive: true });
405
+ }
406
+ for (const guideFile of [
407
+ "ENGAGEMENTS.md", "USAGE.md", "README.md", "INSTALL.md",
408
+ "CONTEXT.md", "AGENTS.md", "CLAUDE.md"
409
+ ]) {
410
+ const srcFile = path.join(sourceDirectory, guideFile);
411
+ if (fsSync.existsSync(srcFile)) {
412
+ await fs.copyFile(srcFile, path.join(targetRefDir, guideFile));
413
+ }
414
+ }
415
+ const isMatt = compoundName === "matt-pocock";
416
+ const compoundDesc = isMatt
417
+ ? "Master Matt Pocock's 6 AI-engineering flows (Getting Started, Main Flow, Shaping, Upkeep, Productivity, Reference)"
418
+ : "[devflow] Offensive security orchestrator & bug hunting guide";
419
+ let compoundVer = "1.0.0";
420
+ try {
421
+ const pkgPath = path.join(sourceDirectory, "package.json");
422
+ if (fsSync.existsSync(pkgPath)) {
423
+ const pkgRaw = await fs.readFile(pkgPath, "utf8");
424
+ const pkg = JSON.parse(pkgRaw);
425
+ if (pkg.version)
426
+ compoundVer = pkg.version;
427
+ }
428
+ }
429
+ catch {
430
+ // ignore
431
+ }
432
+ const masterSkillContent = `---\nname: ${compoundName}\ndescription: "${compoundDesc}"\nversion: ${compoundVer}\n---\n# ${compoundName}\n`;
433
+ const targetAgentsDir = path.join(this.projectRoot, ".agents", "skills", compoundName);
434
+ const targetClaudeDir = path.join(this.projectRoot, ".claude", "skills", compoundName);
435
+ await fs.mkdir(targetAgentsDir, { recursive: true });
436
+ await fs.mkdir(targetClaudeDir, { recursive: true });
437
+ await fs.writeFile(path.join(targetAgentsDir, "SKILL.md"), masterSkillContent, "utf8");
438
+ await fs.writeFile(path.join(targetClaudeDir, "SKILL.md"), masterSkillContent, "utf8");
439
+ const manifest = (await this.readManifest()) || {
440
+ schemaVersion: 1,
441
+ name: "nexus-devflow",
442
+ package: "@jakkrichm/create-nexus-devflow",
443
+ version: "2.14.0"
444
+ };
445
+ const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
446
+ ? manifest.thirdPartySkills
447
+ : [];
448
+ const filtered = existingThirdParty.filter((s) => s.name !== compoundName);
449
+ filtered.push({
450
+ name: compoundName,
451
+ source: recordedSource,
452
+ version: compoundVer,
453
+ description: compoundDesc,
454
+ installedAt: new Date().toISOString(),
455
+ type: "compound-knowledge",
456
+ referencePath: compoundRefPath
457
+ });
458
+ manifest.thirdPartySkills = filtered;
459
+ await this.writeManifest(manifest);
460
+ const detail = {
461
+ name: compoundName,
462
+ category: "third-party",
463
+ description: compoundDesc,
464
+ version: compoundVer,
465
+ source: recordedSource,
466
+ adapters: [".agents", ".claude"],
467
+ synced: true,
468
+ path: `.agents/skills/${compoundName}`
469
+ };
470
+ return {
471
+ installedSkills: [compoundName],
472
+ failedSkills: [],
473
+ details: [detail]
474
+ };
475
+ }
476
+ if (opts.all) {
477
+ const discovered = await discoverSkillsInDirectory(sourceDirectory);
478
+ if (discovered.length === 0) {
479
+ throw new Error(`No skills found in repository to install.`);
480
+ }
481
+ const installedList = [];
482
+ const manifest = (await this.readManifest()) || {
483
+ schemaVersion: 1,
484
+ name: "nexus-devflow",
485
+ package: "@jakkrichm/create-nexus-devflow",
486
+ version: "2.14.0"
487
+ };
488
+ const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
489
+ ? manifest.thirdPartySkills
490
+ : [];
491
+ let updatedThirdParty = [...existingThirdParty];
492
+ for (const skill of discovered) {
493
+ const skillName = skill.skillName;
494
+ if (!SKILL_NAME_PATTERN.test(skillName) || (coreNameSet.has(skillName) && !opts.force)) {
495
+ continue;
496
+ }
497
+ const targetAgentsSkillDir = path.join(this.projectRoot, ".agents", "skills", skillName);
498
+ const targetClaudeSkillDir = path.join(this.projectRoot, ".claude", "skills", skillName);
499
+ await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
500
+ await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
501
+ await fs.mkdir(path.dirname(targetAgentsSkillDir), { recursive: true });
502
+ await fs.mkdir(path.dirname(targetClaudeSkillDir), { recursive: true });
503
+ await fs.cp(skill.sourceSkillPath, targetAgentsSkillDir, { recursive: true });
504
+ await fs.cp(skill.sourceSkillPath, targetClaudeSkillDir, { recursive: true });
505
+ updatedThirdParty = updatedThirdParty.filter((s) => s.name !== skillName);
506
+ updatedThirdParty.push({
507
+ name: skillName,
508
+ source: recordedSource,
509
+ version: skill.meta.version || "1.0.0",
510
+ description: skill.meta.description || "",
511
+ installedAt: new Date().toISOString(),
512
+ type: recordedType
513
+ });
514
+ installedList.push({
515
+ name: skillName,
516
+ category: "third-party",
517
+ description: skill.meta.description || "",
518
+ version: skill.meta.version || "1.0.0",
519
+ source: recordedSource,
520
+ adapters: [".agents", ".claude"],
521
+ synced: true,
522
+ path: `.agents/skills/${skillName}`
523
+ });
524
+ }
525
+ manifest.thirdPartySkills = updatedThirdParty;
526
+ await this.writeManifest(manifest);
527
+ return {
528
+ installedSkills: installedList.map((s) => s.name),
529
+ failedSkills: [],
530
+ details: installedList
531
+ };
532
+ }
533
+ const { sourceSkillPath, skillName, meta } = await findSkillSourceDirectory(sourceDirectory, opts.name);
534
+ if (!SKILL_NAME_PATTERN.test(skillName)) {
535
+ throw new Error(`Invalid skill name: "${skillName}". Must use kebab-case (e.g. "diagram-design").`);
536
+ }
537
+ if (coreNameSet.has(skillName) && !opts.force) {
538
+ throw new Error(`Cannot install skill with name "${skillName}" as it conflicts with a Core DevFlow Skill.`);
539
+ }
540
+ const targetAgentsSkillDir = path.join(this.projectRoot, ".agents", "skills", skillName);
541
+ const targetClaudeSkillDir = path.join(this.projectRoot, ".claude", "skills", skillName);
542
+ await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
543
+ await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
544
+ await fs.mkdir(path.dirname(targetAgentsSkillDir), { recursive: true });
545
+ await fs.mkdir(path.dirname(targetClaudeSkillDir), { recursive: true });
546
+ await fs.cp(sourceSkillPath, targetAgentsSkillDir, { recursive: true });
547
+ await fs.cp(sourceSkillPath, targetClaudeSkillDir, { recursive: true });
548
+ const manifest = (await this.readManifest()) || {
549
+ schemaVersion: 1,
550
+ name: "nexus-devflow",
551
+ package: "@jakkrichm/create-nexus-devflow",
552
+ version: "2.14.0"
553
+ };
554
+ const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
555
+ ? manifest.thirdPartySkills
556
+ : [];
557
+ const filtered = existingThirdParty.filter((s) => s.name !== skillName);
558
+ filtered.push({
559
+ name: skillName,
560
+ source: recordedSource,
561
+ version: meta.version || "1.0.0",
562
+ description: meta.description || "",
563
+ installedAt: new Date().toISOString(),
564
+ type: recordedType
565
+ });
566
+ manifest.thirdPartySkills = filtered;
567
+ await this.writeManifest(manifest);
568
+ const detail = {
569
+ name: skillName,
570
+ category: "third-party",
571
+ description: meta.description || "",
572
+ version: meta.version || "1.0.0",
573
+ source: recordedSource,
574
+ adapters: [".agents", ".claude"],
575
+ synced: true,
576
+ path: `.agents/skills/${skillName}`
577
+ };
578
+ return {
579
+ installedSkills: [skillName],
580
+ failedSkills: [],
581
+ details: [detail]
582
+ };
583
+ }
584
+ finally {
585
+ if (tempCloneDir) {
586
+ try {
587
+ await fs.rm(tempCloneDir, { recursive: true, force: true });
588
+ }
589
+ catch {
590
+ // ignore
591
+ }
592
+ }
593
+ }
594
+ }
595
+ async sync() {
596
+ const agentsDir = path.join(this.projectRoot, ".agents", "skills");
597
+ const claudeDir = path.join(this.projectRoot, ".claude", "skills");
598
+ if (!fsSync.existsSync(agentsDir)) {
599
+ return { syncedCount: 0, skills: [] };
600
+ }
601
+ await fs.rm(claudeDir, { recursive: true, force: true });
602
+ await fs.mkdir(claudeDir, { recursive: true });
603
+ const entries = await fs.readdir(agentsDir, { withFileTypes: true });
604
+ const syncedSkills = [];
605
+ for (const entry of entries) {
606
+ if (entry.isDirectory()) {
607
+ const src = path.join(agentsDir, entry.name);
608
+ const dst = path.join(claudeDir, entry.name);
609
+ await fs.cp(src, dst, { recursive: true });
610
+ syncedSkills.push(entry.name);
611
+ }
612
+ }
613
+ return {
614
+ syncedCount: syncedSkills.length,
615
+ skills: syncedSkills
616
+ };
617
+ }
618
+ async remove(name) {
619
+ let coreNameSet = new Set();
620
+ try {
621
+ const inventory = await loadCoreSkillInventory(path.join(this.projectRoot, AGENT_MANIFEST_PATH));
622
+ coreNameSet = inventory.nameSet;
623
+ }
624
+ catch {
625
+ // ignore
626
+ }
627
+ if (coreNameSet.has(name)) {
628
+ throw new Error(`Cannot remove Core Skill: "${name}". Core skills are managed by Nexus-DevFlow.`);
629
+ }
630
+ const targetAgentsSkillDir = path.join(this.projectRoot, ".agents", "skills", name);
631
+ const targetClaudeSkillDir = path.join(this.projectRoot, ".claude", "skills", name);
632
+ let removedAny = false;
633
+ if (fsSync.existsSync(targetAgentsSkillDir)) {
634
+ await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
635
+ removedAny = true;
636
+ }
637
+ if (fsSync.existsSync(targetClaudeSkillDir)) {
638
+ await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
639
+ removedAny = true;
640
+ }
641
+ const manifest = await this.readManifest();
642
+ if (manifest && Array.isArray(manifest.thirdPartySkills)) {
643
+ const prevList = manifest.thirdPartySkills;
644
+ const nextList = prevList.filter((s) => s.name !== name);
645
+ if (nextList.length !== prevList.length) {
646
+ manifest.thirdPartySkills = nextList;
647
+ await this.writeManifest(manifest);
648
+ removedAny = true;
649
+ }
650
+ }
651
+ return { removed: removedAny };
652
+ }
653
+ async update(targetSkillName, options) {
654
+ const manifest = await this.readManifest();
655
+ const existingThirdParty = Array.isArray(manifest?.thirdPartySkills)
656
+ ? manifest.thirdPartySkills
657
+ : [];
658
+ if (existingThirdParty.length === 0) {
659
+ return { updatedSkills: [], failedSkills: [], totalUpdated: 0 };
660
+ }
661
+ let skillsToUpdate = existingThirdParty;
662
+ if (targetSkillName && targetSkillName !== "--all") {
663
+ skillsToUpdate = existingThirdParty.filter((s) => s.name === targetSkillName);
664
+ if (skillsToUpdate.length === 0) {
665
+ throw new Error(`Skill "${targetSkillName}" is not installed as a third-party skill.`);
666
+ }
667
+ }
668
+ const sourceToSkillsMap = new Map();
669
+ for (const skill of skillsToUpdate) {
670
+ const list = sourceToSkillsMap.get(skill.source) || [];
671
+ list.push(skill);
672
+ sourceToSkillsMap.set(skill.source, list);
673
+ }
674
+ const updatedSkills = [];
675
+ const failedSkills = [];
676
+ for (const [source, skills] of sourceToSkillsMap) {
677
+ const isGit = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(source);
678
+ let tempCloneDir = null;
679
+ try {
680
+ let sourceDir = source;
681
+ if (isGit) {
682
+ tempCloneDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-skill-update-"));
683
+ await this.repositoryAdapter.fetchPackage(source, tempCloneDir);
684
+ sourceDir = tempCloneDir;
685
+ }
686
+ for (const skill of skills) {
687
+ try {
688
+ if (skill.type === "compound-knowledge") {
689
+ let directSkillFound = false;
690
+ try {
691
+ const res = await this.install(sourceDir, {
692
+ name: skill.name,
693
+ force: true,
694
+ overrideSource: isGit ? source : undefined
695
+ });
696
+ if (res.details[0]) {
697
+ updatedSkills.push(res.details[0]);
698
+ directSkillFound = true;
699
+ }
700
+ }
701
+ catch {
702
+ directSkillFound = false;
703
+ }
704
+ if (!directSkillFound) {
705
+ const refRelPath = skill.referencePath || path.join("devflow", ".vendor", skill.name);
706
+ const targetRefDir = path.join(this.projectRoot, refRelPath);
707
+ await fs.mkdir(targetRefDir, { recursive: true });
708
+ const srcSkills = path.join(sourceDir, "skills");
709
+ if (fsSync.existsSync(srcSkills)) {
710
+ await fs.cp(srcSkills, path.join(targetRefDir, "skills"), { recursive: true });
711
+ }
712
+ const srcCommands = path.join(sourceDir, "commands");
713
+ if (fsSync.existsSync(srcCommands)) {
714
+ await fs.cp(srcCommands, path.join(targetRefDir, "commands"), { recursive: true });
715
+ }
716
+ const srcReports = path.join(sourceDir, "docs", "disclosed-reports");
717
+ if (fsSync.existsSync(srcReports)) {
718
+ await fs.cp(srcReports, path.join(targetRefDir, "disclosed-reports"), { recursive: true });
719
+ }
720
+ const srcDocs = path.join(sourceDir, "docs");
721
+ if (fsSync.existsSync(srcDocs)) {
722
+ await fs.cp(srcDocs, path.join(targetRefDir, "docs"), { recursive: true });
723
+ }
724
+ for (const guideFile of ["ENGAGEMENTS.md", "USAGE.md", "README.md", "INSTALL.md"]) {
725
+ const srcFile = path.join(sourceDir, guideFile);
726
+ if (fsSync.existsSync(srcFile)) {
727
+ await fs.copyFile(srcFile, path.join(targetRefDir, guideFile));
728
+ }
729
+ }
730
+ let newVersion = skill.version || "1.0.0";
731
+ try {
732
+ const pkgPath = path.join(sourceDir, "package.json");
733
+ if (fsSync.existsSync(pkgPath)) {
734
+ const pkgRaw = await fs.readFile(pkgPath, "utf8");
735
+ const pkg = JSON.parse(pkgRaw);
736
+ if (pkg.version)
737
+ newVersion = pkg.version;
738
+ }
739
+ }
740
+ catch {
741
+ // ignore
742
+ }
743
+ const currentManifest = (await this.readManifest()) || {};
744
+ const existingList = Array.isArray(currentManifest.thirdPartySkills)
745
+ ? currentManifest.thirdPartySkills
746
+ : [];
747
+ const updatedList = existingList.map((s) => {
748
+ if (s.name === skill.name) {
749
+ return {
750
+ ...s,
751
+ version: newVersion,
752
+ installedAt: new Date().toISOString(),
753
+ type: "compound-knowledge",
754
+ referencePath: refRelPath
755
+ };
756
+ }
757
+ return s;
758
+ });
759
+ currentManifest.thirdPartySkills = updatedList;
760
+ await this.writeManifest(currentManifest);
761
+ updatedSkills.push({
762
+ name: skill.name,
763
+ category: "third-party",
764
+ description: skill.description || "Compound Knowledge Skill",
765
+ version: newVersion,
766
+ source: skill.source,
767
+ adapters: [".agents", ".claude"],
768
+ synced: true,
769
+ path: `.agents/skills/${skill.name}`
770
+ });
771
+ }
772
+ }
773
+ else {
774
+ const res = await this.install(sourceDir, {
775
+ name: skill.name,
776
+ force: true,
777
+ overrideSource: isGit ? source : undefined
778
+ });
779
+ if (res.details[0]) {
780
+ updatedSkills.push(res.details[0]);
781
+ }
782
+ }
783
+ }
784
+ catch (err) {
785
+ failedSkills.push({
786
+ name: skill.name,
787
+ reason: err instanceof Error ? err.message : String(err)
788
+ });
789
+ }
790
+ }
791
+ }
792
+ finally {
793
+ if (tempCloneDir) {
794
+ try {
795
+ await fs.rm(tempCloneDir, { recursive: true, force: true });
796
+ }
797
+ catch {
798
+ // ignore
799
+ }
800
+ }
801
+ }
802
+ }
803
+ return {
804
+ updatedSkills,
805
+ failedSkills,
806
+ totalUpdated: updatedSkills.length
807
+ };
808
+ }
809
+ }
810
+ //# sourceMappingURL=skill-registry-engine.js.map