@jakkrichm/create-nexus-devflow 2.9.0 → 2.9.3

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 (62) hide show
  1. package/dist/bin/create-nexus-devflow.d.ts +6 -2
  2. package/dist/bin/create-nexus-devflow.js +120 -17
  3. package/dist/bin/create-nexus-devflow.js.map +1 -1
  4. package/dist/lib/dashboard-page.d.ts +1 -1
  5. package/dist/lib/dashboard-page.js +76 -0
  6. package/dist/lib/dashboard-page.js.map +1 -1
  7. package/dist/lib/project-config.d.ts +1 -1
  8. package/dist/lib/project-config.js +4 -1
  9. package/dist/lib/project-config.js.map +1 -1
  10. package/dist/lib/run-state.d.ts +39 -0
  11. package/dist/lib/run-state.js +203 -0
  12. package/dist/lib/run-state.js.map +1 -0
  13. package/dist/lib/skill-manager.d.ts +24 -1
  14. package/dist/lib/skill-manager.js +206 -68
  15. package/dist/lib/skill-manager.js.map +1 -1
  16. package/dist/lib/status.d.ts +4 -1
  17. package/dist/lib/status.js +49 -3
  18. package/dist/lib/status.js.map +1 -1
  19. package/dist/lib/webview-studio.js +169 -47
  20. package/dist/lib/webview-studio.js.map +1 -1
  21. package/package.json +1 -1
  22. package/template/.agents/skills/adopt/SKILL.md +4 -0
  23. package/template/.agents/skills/audit/SKILL.md +4 -0
  24. package/template/.agents/skills/autopilot/SKILL.md +4 -0
  25. package/template/.agents/skills/check/SKILL.md +4 -0
  26. package/template/.agents/skills/ci/SKILL.md +4 -0
  27. package/template/.agents/skills/complete/SKILL.md +7 -1
  28. package/template/.agents/skills/continuous/SKILL.md +4 -0
  29. package/template/.agents/skills/debug/SKILL.md +4 -0
  30. package/template/.agents/skills/discovery/SKILL.md +8 -4
  31. package/template/.agents/skills/feature/SKILL.md +4 -0
  32. package/template/.agents/skills/fix/SKILL.md +4 -0
  33. package/template/.agents/skills/implement/SKILL.md +4 -0
  34. package/template/.agents/skills/onboard/SKILL.md +4 -0
  35. package/template/.agents/skills/overview/SKILL.md +4 -0
  36. package/template/.agents/skills/prototype/SKILL.md +4 -0
  37. package/template/.agents/skills/release/SKILL.md +4 -0
  38. package/template/.agents/skills/rollback/SKILL.md +18 -5
  39. package/template/.agents/skills/status/SKILL.md +7 -0
  40. package/template/.agents/skills/tests/SKILL.md +4 -0
  41. package/template/.claude/skills/adopt/SKILL.md +4 -0
  42. package/template/.claude/skills/audit/SKILL.md +4 -0
  43. package/template/.claude/skills/autopilot/SKILL.md +4 -0
  44. package/template/.claude/skills/check/SKILL.md +4 -0
  45. package/template/.claude/skills/ci/SKILL.md +4 -0
  46. package/template/.claude/skills/complete/SKILL.md +7 -1
  47. package/template/.claude/skills/continuous/SKILL.md +4 -0
  48. package/template/.claude/skills/debug/SKILL.md +4 -0
  49. package/template/.claude/skills/discovery/SKILL.md +8 -4
  50. package/template/.claude/skills/feature/SKILL.md +4 -0
  51. package/template/.claude/skills/fix/SKILL.md +4 -0
  52. package/template/.claude/skills/implement/SKILL.md +4 -0
  53. package/template/.claude/skills/onboard/SKILL.md +4 -0
  54. package/template/.claude/skills/overview/SKILL.md +4 -0
  55. package/template/.claude/skills/prototype/SKILL.md +4 -0
  56. package/template/.claude/skills/release/SKILL.md +4 -0
  57. package/template/.claude/skills/rollback/SKILL.md +18 -5
  58. package/template/.claude/skills/status/SKILL.md +7 -0
  59. package/template/.claude/skills/tests/SKILL.md +4 -0
  60. package/template/AGENTS.md +24 -5
  61. package/template/devflow/build-plan.md +16 -0
  62. package/template/devflow/context/ai-interaction.md +4 -4
@@ -0,0 +1,203 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const RUN_STATE_PATH = path.join("devflow", ".state", "run.json");
4
+ const COMMAND_PATTERN = /^[a-z][a-z-]{0,31}$/;
5
+ const RUN_STATUSES = new Set([
6
+ "running",
7
+ "blocked",
8
+ "ready",
9
+ "completed"
10
+ ]);
11
+ const RUN_BOUNDARIES = new Set([
12
+ "read-only",
13
+ "reviewed",
14
+ "local-only"
15
+ ]);
16
+ const RUN_STALE_AFTER_MS = 60 * 60 * 1000;
17
+ async function readRunState(projectRoot, now = new Date()) {
18
+ const runStatePath = path.join(projectRoot, RUN_STATE_PATH);
19
+ try {
20
+ const stats = await fs.lstat(runStatePath);
21
+ if (stats.isSymbolicLink()) {
22
+ return malformedSummary({
23
+ code: "unsafe_run_state_path",
24
+ message: "Dashboard run state is a symbolic link and was not read."
25
+ });
26
+ }
27
+ if (!stats.isFile()) {
28
+ return malformedSummary({
29
+ code: "invalid_run_state_path",
30
+ message: "Dashboard run state path is not a regular file."
31
+ });
32
+ }
33
+ return parseRunState(await fs.readFile(runStatePath, "utf8"), now);
34
+ }
35
+ catch (error) {
36
+ if (getErrorCode(error) === "ENOENT") {
37
+ return idleSummary();
38
+ }
39
+ throw error;
40
+ }
41
+ }
42
+ function parseRunState(value, now = new Date()) {
43
+ let parsed;
44
+ try {
45
+ parsed = JSON.parse(value);
46
+ }
47
+ catch {
48
+ return malformedSummary({
49
+ code: "malformed_run_state",
50
+ message: "Dashboard run state is not valid JSON."
51
+ });
52
+ }
53
+ if (!isRecord(parsed)) {
54
+ return malformedRunState();
55
+ }
56
+ const command = readString(parsed.command);
57
+ const status = readString(parsed.status);
58
+ const summary = readString(parsed.summary);
59
+ const detail = readOptionalString(parsed.detail);
60
+ const boundary = readOptionalString(parsed.boundary);
61
+ const startedAt = readString(parsed.startedAt);
62
+ const updatedAt = readString(parsed.updatedAt);
63
+ const resumeCommand = readOptionalString(parsed.resumeCommand);
64
+ const progress = parseProgress(parsed.progress);
65
+ const feature = parseFeature(parsed.feature);
66
+ if (parsed.schemaVersion !== 1 ||
67
+ !command ||
68
+ !COMMAND_PATTERN.test(command) ||
69
+ !status ||
70
+ !RUN_STATUSES.has(status) ||
71
+ !summary ||
72
+ !startedAt ||
73
+ !updatedAt ||
74
+ !isTimestamp(startedAt) ||
75
+ !isTimestamp(updatedAt) ||
76
+ Date.parse(startedAt) > Date.parse(updatedAt) ||
77
+ (parsed.detail !== undefined && detail === null) ||
78
+ (parsed.boundary !== undefined && (!boundary || !RUN_BOUNDARIES.has(boundary))) ||
79
+ (parsed.resumeCommand !== undefined && resumeCommand === null) ||
80
+ (parsed.progress !== undefined && progress === null) ||
81
+ (parsed.feature !== undefined && feature === null)) {
82
+ return malformedRunState();
83
+ }
84
+ const freshness = status === "running" &&
85
+ now.getTime() - Date.parse(updatedAt) > RUN_STALE_AFTER_MS
86
+ ? "stale"
87
+ : "current";
88
+ const warnings = freshness === "stale"
89
+ ? [
90
+ {
91
+ code: "stale_run_state",
92
+ message: `Recorded /${command} activity has not updated for over one hour and may have been interrupted.`
93
+ }
94
+ ]
95
+ : [];
96
+ return {
97
+ state: "recorded",
98
+ mode: command === "autopilot"
99
+ ? "autopilot"
100
+ : command === "continuous"
101
+ ? "continuous"
102
+ : "manual",
103
+ command,
104
+ status,
105
+ freshness,
106
+ summary,
107
+ detail,
108
+ boundary,
109
+ startedAt,
110
+ updatedAt,
111
+ resumeCommand,
112
+ progress,
113
+ feature,
114
+ warnings
115
+ };
116
+ }
117
+ function parseProgress(value) {
118
+ if (value === undefined) {
119
+ return null;
120
+ }
121
+ if (!isRecord(value)) {
122
+ return null;
123
+ }
124
+ const label = readString(value.label);
125
+ const current = value.current;
126
+ const total = value.total;
127
+ if (!label ||
128
+ !Number.isInteger(current) ||
129
+ !Number.isInteger(total) ||
130
+ Number(current) < 0 ||
131
+ Number(total) < 1 ||
132
+ Number(current) > Number(total)) {
133
+ return null;
134
+ }
135
+ return { label, current: Number(current), total: Number(total) };
136
+ }
137
+ function parseFeature(value) {
138
+ if (value === undefined) {
139
+ return null;
140
+ }
141
+ if (!isRecord(value)) {
142
+ return null;
143
+ }
144
+ const id = readOptionalString(value.id);
145
+ const title = readString(value.title);
146
+ if (!title || (value.id !== undefined && id === null)) {
147
+ return null;
148
+ }
149
+ return { id, title };
150
+ }
151
+ function malformedRunState() {
152
+ return malformedSummary({
153
+ code: "malformed_run_state",
154
+ message: "Dashboard run state does not match schema version 1."
155
+ });
156
+ }
157
+ function idleSummary() {
158
+ return {
159
+ state: "idle",
160
+ mode: "manual",
161
+ command: null,
162
+ status: null,
163
+ freshness: null,
164
+ summary: null,
165
+ detail: null,
166
+ boundary: null,
167
+ startedAt: null,
168
+ updatedAt: null,
169
+ resumeCommand: null,
170
+ progress: null,
171
+ feature: null,
172
+ warnings: []
173
+ };
174
+ }
175
+ function malformedSummary(warning) {
176
+ return {
177
+ ...idleSummary(),
178
+ state: "malformed",
179
+ warnings: [warning]
180
+ };
181
+ }
182
+ function readString(value) {
183
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
184
+ }
185
+ function readOptionalString(value) {
186
+ return value === undefined ? null : readString(value);
187
+ }
188
+ function isTimestamp(value) {
189
+ return !Number.isNaN(Date.parse(value));
190
+ }
191
+ function isRecord(value) {
192
+ return typeof value === "object" && value !== null && !Array.isArray(value);
193
+ }
194
+ function getErrorCode(error) {
195
+ return typeof error === "object" &&
196
+ error !== null &&
197
+ "code" in error &&
198
+ typeof error.code === "string"
199
+ ? error.code
200
+ : undefined;
201
+ }
202
+ export { RUN_STATE_PATH, parseRunState, readRunState };
203
+ //# sourceMappingURL=run-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../lib/run-state.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,9 @@ export interface SkillListResult {
23
23
  }
24
24
  export interface InstallSkillOptions {
25
25
  name?: string;
26
+ all?: boolean;
26
27
  force?: boolean;
28
+ overrideSource?: string;
27
29
  }
28
30
  export declare function parseSkillFrontmatter(content: string): {
29
31
  name?: string;
@@ -33,6 +35,16 @@ export declare function parseSkillFrontmatter(content: string): {
33
35
  export declare function readDevflowManifest(projectRoot: string): Promise<Record<string, unknown> | null>;
34
36
  export declare function writeDevflowManifest(projectRoot: string, manifest: Record<string, unknown>): Promise<void>;
35
37
  export declare function listInstalledSkills(projectRoot: string): Promise<SkillListResult>;
38
+ export interface DiscoveredSkill {
39
+ sourceSkillPath: string;
40
+ skillName: string;
41
+ meta: {
42
+ name?: string;
43
+ description?: string;
44
+ version?: string;
45
+ };
46
+ }
47
+ export declare function discoverSkillsInDirectory(rootDir: string, maxDepth?: number, currentDepth?: number): Promise<DiscoveredSkill[]>;
36
48
  export declare function findSkillSourceDirectory(sourceDir: string, requestedName?: string): Promise<{
37
49
  sourceSkillPath: string;
38
50
  skillName: string;
@@ -42,9 +54,20 @@ export declare function findSkillSourceDirectory(sourceDir: string, requestedNam
42
54
  version?: string;
43
55
  };
44
56
  }>;
45
- export declare function installThirdPartySkill(projectRoot: string, source: string, options?: InstallSkillOptions): Promise<SkillDetail>;
57
+ export declare function installThirdPartySkill(projectRoot: string, source: string, options?: InstallSkillOptions): Promise<SkillDetail | SkillDetail[]>;
46
58
  export declare function removeThirdPartySkill(projectRoot: string, name: string): Promise<boolean>;
47
59
  export declare function syncSkills(projectRoot: string): Promise<{
48
60
  syncedCount: number;
49
61
  skills: string[];
50
62
  }>;
63
+ export interface SkillUpdateResult {
64
+ updatedSkills: SkillDetail[];
65
+ failedSkills: Array<{
66
+ name: string;
67
+ reason: string;
68
+ }>;
69
+ totalUpdated: number;
70
+ }
71
+ export declare function updateThirdPartySkills(projectRoot: string, targetSkillName?: string, options?: {
72
+ force?: boolean;
73
+ }): Promise<SkillUpdateResult>;
@@ -156,76 +156,80 @@ export async function listInstalledSkills(projectRoot) {
156
156
  totalCount: coreSkills.length + thirdPartySkills.length
157
157
  };
158
158
  }
159
- export async function findSkillSourceDirectory(sourceDir, requestedName) {
160
- // Check 1: If sourceDir itself has SKILL.md
161
- const rootSkillMd = path.join(sourceDir, "SKILL.md");
162
- if (fsSync.existsSync(rootSkillMd)) {
163
- const content = await fs.readFile(rootSkillMd, "utf8");
164
- const meta = parseSkillFrontmatter(content);
165
- const skillName = requestedName || meta.name || path.basename(sourceDir);
166
- return { sourceSkillPath: sourceDir, skillName, meta };
167
- }
168
- // Check 2: Check inside skills/ subfolder
169
- const skillsSubDir = path.join(sourceDir, "skills");
170
- if (fsSync.existsSync(skillsSubDir)) {
171
- const entries = await fs.readdir(skillsSubDir, { withFileTypes: true });
172
- const skillDirs = entries.filter((e) => e.isDirectory());
173
- if (requestedName) {
174
- const targetDir = path.join(skillsSubDir, requestedName);
175
- if (fsSync.existsSync(path.join(targetDir, "SKILL.md"))) {
176
- const content = await fs.readFile(path.join(targetDir, "SKILL.md"), "utf8");
177
- const meta = parseSkillFrontmatter(content);
178
- return { sourceSkillPath: targetDir, skillName: requestedName, meta };
179
- }
180
- }
181
- if (skillDirs.length === 1) {
182
- const onlyDir = skillDirs[0].name;
183
- const targetDir = path.join(skillsSubDir, onlyDir);
184
- if (fsSync.existsSync(path.join(targetDir, "SKILL.md"))) {
185
- const content = await fs.readFile(path.join(targetDir, "SKILL.md"), "utf8");
186
- const meta = parseSkillFrontmatter(content);
187
- return { sourceSkillPath: targetDir, skillName: requestedName || meta.name || onlyDir, meta };
188
- }
159
+ export async function discoverSkillsInDirectory(rootDir, maxDepth = 5, currentDepth = 0) {
160
+ const results = [];
161
+ if (currentDepth > maxDepth)
162
+ return results;
163
+ const skillMdPath = path.join(rootDir, "SKILL.md");
164
+ if (fsSync.existsSync(skillMdPath)) {
165
+ try {
166
+ const content = await fs.readFile(skillMdPath, "utf8");
167
+ const meta = parseSkillFrontmatter(content);
168
+ const skillName = meta.name || path.basename(rootDir);
169
+ results.push({
170
+ sourceSkillPath: rootDir,
171
+ skillName,
172
+ meta
173
+ });
174
+ return results;
189
175
  }
190
- if (skillDirs.length > 1) {
191
- // If one matches the requested name or base name of source
192
- const baseName = path.basename(sourceDir);
193
- for (const dir of skillDirs) {
194
- if (dir.name === requestedName || dir.name === baseName) {
195
- const targetDir = path.join(skillsSubDir, dir.name);
196
- if (fsSync.existsSync(path.join(targetDir, "SKILL.md"))) {
197
- const content = await fs.readFile(path.join(targetDir, "SKILL.md"), "utf8");
198
- const meta = parseSkillFrontmatter(content);
199
- return { sourceSkillPath: targetDir, skillName: dir.name, meta };
200
- }
201
- }
202
- }
176
+ catch {
177
+ // ignore read error
203
178
  }
204
179
  }
205
- // Check 3: Check .agents/skills subfolder
206
- const dotAgentsSkills = path.join(sourceDir, ".agents", "skills");
207
- if (fsSync.existsSync(dotAgentsSkills)) {
208
- const entries = await fs.readdir(dotAgentsSkills, { withFileTypes: true });
209
- const skillDirs = entries.filter((e) => e.isDirectory());
210
- if (requestedName) {
211
- const targetDir = path.join(dotAgentsSkills, requestedName);
212
- if (fsSync.existsSync(path.join(targetDir, "SKILL.md"))) {
213
- const content = await fs.readFile(path.join(targetDir, "SKILL.md"), "utf8");
214
- const meta = parseSkillFrontmatter(content);
215
- return { sourceSkillPath: targetDir, skillName: requestedName, meta };
216
- }
180
+ let entries;
181
+ try {
182
+ entries = await fs.readdir(rootDir, { withFileTypes: true });
183
+ }
184
+ catch {
185
+ return results;
186
+ }
187
+ const IGNORED_DIRS = new Set([".git", "node_modules", "dist", ".nexus", "build", "coverage", ".turbo"]);
188
+ for (const entry of entries) {
189
+ if (entry.isDirectory() && !IGNORED_DIRS.has(entry.name)) {
190
+ const subDir = path.join(rootDir, entry.name);
191
+ const subResults = await discoverSkillsInDirectory(subDir, maxDepth, currentDepth + 1);
192
+ results.push(...subResults);
217
193
  }
218
- if (skillDirs.length === 1) {
219
- const onlyDir = skillDirs[0].name;
220
- const targetDir = path.join(dotAgentsSkills, onlyDir);
221
- if (fsSync.existsSync(path.join(targetDir, "SKILL.md"))) {
222
- const content = await fs.readFile(path.join(targetDir, "SKILL.md"), "utf8");
223
- const meta = parseSkillFrontmatter(content);
224
- return { sourceSkillPath: targetDir, skillName: requestedName || meta.name || onlyDir, meta };
225
- }
194
+ }
195
+ return results;
196
+ }
197
+ export async function findSkillSourceDirectory(sourceDir, requestedName) {
198
+ const discovered = await discoverSkillsInDirectory(sourceDir);
199
+ if (discovered.length === 0) {
200
+ throw new Error(`Could not find any valid skill with SKILL.md in source: ${sourceDir}`);
201
+ }
202
+ if (requestedName) {
203
+ const match = discovered.find((s) => s.skillName === requestedName || path.basename(s.sourceSkillPath) === requestedName);
204
+ if (match) {
205
+ return {
206
+ sourceSkillPath: match.sourceSkillPath,
207
+ skillName: requestedName,
208
+ meta: match.meta
209
+ };
226
210
  }
211
+ const available = discovered.map((s) => s.skillName).join(", ");
212
+ throw new Error(`Skill "${requestedName}" not found in source. Available skills (${discovered.length}): ${available}`);
213
+ }
214
+ if (discovered.length === 1) {
215
+ return {
216
+ sourceSkillPath: discovered[0].sourceSkillPath,
217
+ skillName: discovered[0].skillName,
218
+ meta: discovered[0].meta
219
+ };
227
220
  }
228
- throw new Error(`Could not find a valid skill with SKILL.md in source: ${sourceDir}`);
221
+ // Check if one matches the sourceDir basename
222
+ const baseName = path.basename(sourceDir);
223
+ const baseMatch = discovered.find((s) => s.skillName === baseName || path.basename(s.sourceSkillPath) === baseName);
224
+ if (baseMatch) {
225
+ return {
226
+ sourceSkillPath: baseMatch.sourceSkillPath,
227
+ skillName: baseMatch.skillName,
228
+ meta: baseMatch.meta
229
+ };
230
+ }
231
+ const available = discovered.map((s) => s.skillName).join(", ");
232
+ throw new Error(`Multiple skills found in source (${discovered.length} skills: ${available}). Please specify --name <skill-name> or use --all to install all skills.`);
229
233
  }
230
234
  export async function installThirdPartySkill(projectRoot, source, options) {
231
235
  const isGitUrl = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(source);
@@ -251,6 +255,64 @@ export async function installThirdPartySkill(projectRoot, source, options) {
251
255
  throw new Error(`Source directory does not exist: ${source}`);
252
256
  }
253
257
  }
258
+ const recordedSource = options?.overrideSource || source;
259
+ const recordedType = isGitUrl || options?.overrideSource ? "git" : "local";
260
+ if (options?.all) {
261
+ const discovered = await discoverSkillsInDirectory(sourceDirectory);
262
+ if (discovered.length === 0) {
263
+ throw new Error(`Could not find any valid skill with SKILL.md in source: ${source}`);
264
+ }
265
+ const installedList = [];
266
+ const manifest = (await readDevflowManifest(projectRoot)) || {
267
+ schemaVersion: 1,
268
+ name: "nexus-devflow",
269
+ package: "@jakkrichm/create-nexus-devflow",
270
+ version: "2.9.3"
271
+ };
272
+ const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
273
+ ? manifest.thirdPartySkills
274
+ : [];
275
+ let updatedThirdParty = [...existingThirdParty];
276
+ for (const skill of discovered) {
277
+ const skillName = skill.skillName;
278
+ if (!SKILL_NAME_PATTERN.test(skillName)) {
279
+ continue; // skip invalid names in batch mode
280
+ }
281
+ if (coreNameSet.has(skillName) && !options?.force) {
282
+ continue; // skip core collisions in batch mode
283
+ }
284
+ const targetAgentsSkillDir = path.join(projectRoot, ".agents", "skills", skillName);
285
+ const targetClaudeSkillDir = path.join(projectRoot, ".claude", "skills", skillName);
286
+ await fs.rm(targetAgentsSkillDir, { recursive: true, force: true });
287
+ await fs.rm(targetClaudeSkillDir, { recursive: true, force: true });
288
+ await fs.mkdir(path.dirname(targetAgentsSkillDir), { recursive: true });
289
+ await fs.mkdir(path.dirname(targetClaudeSkillDir), { recursive: true });
290
+ await fs.cp(skill.sourceSkillPath, targetAgentsSkillDir, { recursive: true });
291
+ await fs.cp(skill.sourceSkillPath, targetClaudeSkillDir, { recursive: true });
292
+ updatedThirdParty = updatedThirdParty.filter((s) => s.name !== skillName);
293
+ updatedThirdParty.push({
294
+ name: skillName,
295
+ source: recordedSource,
296
+ version: skill.meta.version || "1.0.0",
297
+ description: skill.meta.description || "",
298
+ installedAt: new Date().toISOString(),
299
+ type: recordedType
300
+ });
301
+ installedList.push({
302
+ name: skillName,
303
+ category: "third-party",
304
+ description: skill.meta.description || "",
305
+ version: skill.meta.version || "1.0.0",
306
+ source: recordedSource,
307
+ adapters: [".agents", ".claude"],
308
+ synced: true,
309
+ path: `.agents/skills/${skillName}`
310
+ });
311
+ }
312
+ manifest.thirdPartySkills = updatedThirdParty;
313
+ await writeDevflowManifest(projectRoot, manifest);
314
+ return installedList;
315
+ }
254
316
  const { sourceSkillPath, skillName, meta } = await findSkillSourceDirectory(sourceDirectory, options?.name);
255
317
  if (!SKILL_NAME_PATTERN.test(skillName)) {
256
318
  throw new Error(`Invalid skill name: "${skillName}". Must use kebab-case (e.g. "diagram-design").`);
@@ -273,7 +335,7 @@ export async function installThirdPartySkill(projectRoot, source, options) {
273
335
  schemaVersion: 1,
274
336
  name: "nexus-devflow",
275
337
  package: "@jakkrichm/create-nexus-devflow",
276
- version: "2.9.0"
338
+ version: "2.9.3"
277
339
  };
278
340
  const existingThirdParty = Array.isArray(manifest.thirdPartySkills)
279
341
  ? manifest.thirdPartySkills
@@ -281,11 +343,11 @@ export async function installThirdPartySkill(projectRoot, source, options) {
281
343
  const filtered = existingThirdParty.filter((s) => s.name !== skillName);
282
344
  filtered.push({
283
345
  name: skillName,
284
- source,
346
+ source: recordedSource,
285
347
  version: meta.version || "1.0.0",
286
348
  description: meta.description || "",
287
349
  installedAt: new Date().toISOString(),
288
- type: isGitUrl ? "git" : "local"
350
+ type: recordedType
289
351
  });
290
352
  manifest.thirdPartySkills = filtered;
291
353
  await writeDevflowManifest(projectRoot, manifest);
@@ -294,7 +356,7 @@ export async function installThirdPartySkill(projectRoot, source, options) {
294
356
  category: "third-party",
295
357
  description: meta.description || "",
296
358
  version: meta.version || "1.0.0",
297
- source,
359
+ source: recordedSource,
298
360
  adapters: [".agents", ".claude"],
299
361
  synced: true,
300
362
  path: `.agents/skills/${skillName}`
@@ -369,4 +431,80 @@ export async function syncSkills(projectRoot) {
369
431
  skills: syncedSkills
370
432
  };
371
433
  }
434
+ export async function updateThirdPartySkills(projectRoot, targetSkillName, options) {
435
+ const manifest = await readDevflowManifest(projectRoot);
436
+ const existingThirdParty = Array.isArray(manifest?.thirdPartySkills)
437
+ ? manifest.thirdPartySkills
438
+ : [];
439
+ if (existingThirdParty.length === 0) {
440
+ return { updatedSkills: [], failedSkills: [], totalUpdated: 0 };
441
+ }
442
+ let skillsToUpdate = existingThirdParty;
443
+ if (targetSkillName && targetSkillName !== "--all") {
444
+ skillsToUpdate = existingThirdParty.filter((s) => s.name === targetSkillName);
445
+ if (skillsToUpdate.length === 0) {
446
+ throw new Error(`Skill "${targetSkillName}" is not installed as a third-party skill.`);
447
+ }
448
+ }
449
+ // Group by source to avoid duplicate cloning
450
+ const sourceToSkillsMap = new Map();
451
+ for (const skill of skillsToUpdate) {
452
+ const list = sourceToSkillsMap.get(skill.source) || [];
453
+ list.push(skill);
454
+ sourceToSkillsMap.set(skill.source, list);
455
+ }
456
+ const updatedSkills = [];
457
+ const failedSkills = [];
458
+ for (const [source, skills] of sourceToSkillsMap) {
459
+ const isGit = /^https?:\/\/|^git@|^ssh:\/\/|\.git$/.test(source);
460
+ let tempCloneDir = null;
461
+ try {
462
+ let sourceDir = source;
463
+ if (isGit) {
464
+ tempCloneDir = await fs.mkdtemp(path.join(os.tmpdir(), "nexus-skill-update-"));
465
+ await execFileAsync("git", ["clone", "--depth", "1", source, tempCloneDir]);
466
+ sourceDir = tempCloneDir;
467
+ }
468
+ for (const skill of skills) {
469
+ try {
470
+ const detail = (await installThirdPartySkill(projectRoot, sourceDir, {
471
+ name: skill.name,
472
+ force: true,
473
+ overrideSource: isGit ? source : undefined
474
+ }));
475
+ updatedSkills.push(detail);
476
+ }
477
+ catch (err) {
478
+ failedSkills.push({
479
+ name: skill.name,
480
+ reason: err instanceof Error ? err.message : String(err)
481
+ });
482
+ }
483
+ }
484
+ }
485
+ catch (err) {
486
+ for (const skill of skills) {
487
+ failedSkills.push({
488
+ name: skill.name,
489
+ reason: err instanceof Error ? err.message : String(err)
490
+ });
491
+ }
492
+ }
493
+ finally {
494
+ if (tempCloneDir) {
495
+ try {
496
+ await fs.rm(tempCloneDir, { recursive: true, force: true });
497
+ }
498
+ catch {
499
+ // ignore
500
+ }
501
+ }
502
+ }
503
+ }
504
+ return {
505
+ updatedSkills,
506
+ failedSkills,
507
+ totalUpdated: updatedSkills.length
508
+ };
509
+ }
372
510
  //# sourceMappingURL=skill-manager.js.map