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