@alavida/agentpack 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/skills/agentpack-cli/SKILL.md +12 -0
- package/skills/agentpack-cli/references/skill-lifecycle.md +21 -1
- package/src/application/plugins/build-plugin.js +5 -0
- package/src/application/plugins/inspect-plugin-bundle.js +5 -0
- package/src/application/plugins/validate-plugin-bundle.js +5 -0
- package/src/application/skills/inspect-skill.js +5 -0
- package/src/application/skills/list-stale-skills.js +9 -0
- package/src/application/skills/validate-skills.js +5 -0
- package/src/commands/plugin.js +7 -4
- package/src/commands/skills.js +7 -8
- package/src/domain/skills/skill-graph.js +136 -0
- package/src/domain/skills/skill-model.js +187 -0
- package/src/domain/skills/skill-provenance.js +69 -0
- package/src/infrastructure/fs/build-state-repository.js +16 -0
- package/src/infrastructure/fs/install-state-repository.js +16 -0
- package/src/infrastructure/runtime/materialize-skills.js +117 -0
- package/src/infrastructure/runtime/watch-tree.js +44 -0
- package/src/lib/plugins.js +27 -89
- package/src/lib/skills.js +81 -447
package/src/lib/skills.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, watch, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
3
|
import { execFileSync } from 'node:child_process';
|
|
5
4
|
import { findAllWorkbenches, findRepoRoot, findWorkbenchContext, resolveWorkbenchFlag } from './context.js';
|
|
5
|
+
import {
|
|
6
|
+
buildReverseDependencies,
|
|
7
|
+
buildSkillGraph,
|
|
8
|
+
buildSkillStatusMap,
|
|
9
|
+
readNodeStatus,
|
|
10
|
+
} from '../domain/skills/skill-graph.js';
|
|
11
|
+
import { readInstallState } from '../infrastructure/fs/install-state-repository.js';
|
|
12
|
+
import {
|
|
13
|
+
ensureSkillLink,
|
|
14
|
+
rebuildInstallState,
|
|
15
|
+
removePathIfExists,
|
|
16
|
+
removeSkillLinks,
|
|
17
|
+
removeSkillLinksByNames,
|
|
18
|
+
} from '../infrastructure/runtime/materialize-skills.js';
|
|
19
|
+
import {
|
|
20
|
+
normalizeDisplayPath,
|
|
21
|
+
normalizeRepoPath,
|
|
22
|
+
parseSkillFrontmatterFile,
|
|
23
|
+
readPackageMetadata,
|
|
24
|
+
} from '../domain/skills/skill-model.js';
|
|
25
|
+
import {
|
|
26
|
+
buildStateRecordForPackageDir,
|
|
27
|
+
compareRecordedSources,
|
|
28
|
+
hashFile,
|
|
29
|
+
readBuildState,
|
|
30
|
+
writeBuildState,
|
|
31
|
+
} from '../domain/skills/skill-provenance.js';
|
|
6
32
|
import { AgentpackError, EXIT_CODES, NetworkError, NotFoundError, ValidationError } from '../utils/errors.js';
|
|
7
33
|
|
|
8
34
|
const GITHUB_PACKAGES_REGISTRY = 'https://npm.pkg.github.com';
|
|
@@ -17,173 +43,6 @@ function inferManagedScope(packageName) {
|
|
|
17
43
|
return MANAGED_PACKAGE_SCOPES.find((scope) => packageName?.startsWith(`${scope}/`)) || null;
|
|
18
44
|
}
|
|
19
45
|
|
|
20
|
-
function parseScalar(value) {
|
|
21
|
-
const trimmed = value.trim();
|
|
22
|
-
if (
|
|
23
|
-
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
24
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
25
|
-
) {
|
|
26
|
-
return trimmed.slice(1, -1);
|
|
27
|
-
}
|
|
28
|
-
return trimmed;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function foldBlockScalar(lines, startIndex, baseIndent) {
|
|
32
|
-
const values = [];
|
|
33
|
-
let index = startIndex + 1;
|
|
34
|
-
|
|
35
|
-
while (index < lines.length) {
|
|
36
|
-
const rawLine = lines[index];
|
|
37
|
-
if (!rawLine.trim()) {
|
|
38
|
-
values.push('');
|
|
39
|
-
index += 1;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const indentMatch = rawLine.match(/^(\s*)/);
|
|
44
|
-
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
45
|
-
if (indent <= baseIndent) break;
|
|
46
|
-
|
|
47
|
-
values.push(rawLine.slice(baseIndent + 2).trimEnd());
|
|
48
|
-
index += 1;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const folded = values
|
|
52
|
-
.join('\n')
|
|
53
|
-
.split('\n\n')
|
|
54
|
-
.map((chunk) => chunk.split('\n').join(' ').trim())
|
|
55
|
-
.filter((chunk, idx, arr) => chunk.length > 0 || idx < arr.length - 1)
|
|
56
|
-
.join('\n\n')
|
|
57
|
-
.trim();
|
|
58
|
-
|
|
59
|
-
return { value: folded, nextIndex: index };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function ensureContainer(target, key) {
|
|
63
|
-
if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
|
|
64
|
-
target[key] = {};
|
|
65
|
-
}
|
|
66
|
-
return target[key];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function parseSkillFrontmatterFile(skillFilePath) {
|
|
70
|
-
if (!existsSync(skillFilePath)) {
|
|
71
|
-
throw new NotFoundError(`skill file not found: ${skillFilePath}`, { code: 'skill_not_found' });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const content = readFileSync(skillFilePath, 'utf-8');
|
|
75
|
-
if (!content.startsWith('---\n')) {
|
|
76
|
-
throw new ValidationError('SKILL.md missing frontmatter', { code: 'missing_frontmatter' });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const fmEnd = content.indexOf('\n---', 4);
|
|
80
|
-
if (fmEnd === -1) {
|
|
81
|
-
throw new ValidationError('SKILL.md has unclosed frontmatter', { code: 'unclosed_frontmatter' });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const lines = content.slice(4, fmEnd).split('\n');
|
|
85
|
-
const fields = {};
|
|
86
|
-
let activeArrayKey = null;
|
|
87
|
-
let activeArrayTarget = null;
|
|
88
|
-
let activeParentKey = null;
|
|
89
|
-
|
|
90
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
91
|
-
const rawLine = lines[index];
|
|
92
|
-
const line = rawLine.trimEnd();
|
|
93
|
-
if (!line.trim()) continue;
|
|
94
|
-
|
|
95
|
-
const listMatch = rawLine.match(/^(\s*)-\s+(.+)$/);
|
|
96
|
-
if (listMatch && activeArrayKey && activeArrayTarget) {
|
|
97
|
-
activeArrayTarget[activeArrayKey].push(parseScalar(listMatch[2]));
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const nestedKeyMatch = rawLine.match(/^\s{2}([A-Za-z][\w-]*):\s*(.*)$/);
|
|
102
|
-
if (nestedKeyMatch && activeParentKey) {
|
|
103
|
-
const [, key, value] = nestedKeyMatch;
|
|
104
|
-
const parent = ensureContainer(fields, activeParentKey);
|
|
105
|
-
if (value === '') {
|
|
106
|
-
parent[key] = [];
|
|
107
|
-
activeArrayKey = key;
|
|
108
|
-
activeArrayTarget = parent;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
parent[key] = parseScalar(value);
|
|
113
|
-
activeArrayKey = null;
|
|
114
|
-
activeArrayTarget = null;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const keyMatch = rawLine.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
|
|
119
|
-
if (!keyMatch) continue;
|
|
120
|
-
|
|
121
|
-
const [, key, value] = keyMatch;
|
|
122
|
-
if (value === '>' || value === '|') {
|
|
123
|
-
const { value: blockValue, nextIndex } = foldBlockScalar(lines, index, 0);
|
|
124
|
-
fields[key] = blockValue;
|
|
125
|
-
activeParentKey = null;
|
|
126
|
-
activeArrayKey = null;
|
|
127
|
-
activeArrayTarget = null;
|
|
128
|
-
index = nextIndex - 1;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (value === '') {
|
|
133
|
-
fields[key] = fields[key] && typeof fields[key] === 'object' && !Array.isArray(fields[key])
|
|
134
|
-
? fields[key]
|
|
135
|
-
: [];
|
|
136
|
-
activeParentKey = key;
|
|
137
|
-
activeArrayKey = Array.isArray(fields[key]) ? key : null;
|
|
138
|
-
activeArrayTarget = Array.isArray(fields[key]) ? fields : null;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
fields[key] = parseScalar(value);
|
|
143
|
-
activeParentKey = null;
|
|
144
|
-
activeArrayKey = null;
|
|
145
|
-
activeArrayTarget = null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!fields.name) {
|
|
149
|
-
throw new ValidationError('SKILL.md frontmatter missing "name" field', { code: 'missing_name' });
|
|
150
|
-
}
|
|
151
|
-
if (!fields.description) {
|
|
152
|
-
throw new ValidationError('SKILL.md frontmatter missing "description" field', { code: 'missing_description' });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
name: fields.name,
|
|
157
|
-
description: fields.description,
|
|
158
|
-
sources: Array.isArray(fields.metadata?.sources)
|
|
159
|
-
? fields.metadata.sources
|
|
160
|
-
: (Array.isArray(fields.sources) ? fields.sources : []),
|
|
161
|
-
requires: Array.isArray(fields.metadata?.requires)
|
|
162
|
-
? fields.metadata.requires
|
|
163
|
-
: (Array.isArray(fields.requires) ? fields.requires : []),
|
|
164
|
-
status: typeof fields.metadata?.status === 'string' ? fields.metadata.status : null,
|
|
165
|
-
replacement: typeof fields.metadata?.replacement === 'string' ? fields.metadata.replacement : null,
|
|
166
|
-
message: typeof fields.metadata?.message === 'string' ? fields.metadata.message : null,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function normalizeDisplayPath(repoRoot, absolutePath) {
|
|
171
|
-
return relative(repoRoot, absolutePath).split('\\').join('/');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function ensureDir(pathValue) {
|
|
175
|
-
mkdirSync(pathValue, { recursive: true });
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function ensureSkillLink(repoRoot, baseDir, skillName, skillDir) {
|
|
179
|
-
const skillsDir = join(repoRoot, baseDir, 'skills');
|
|
180
|
-
ensureDir(skillsDir);
|
|
181
|
-
const linkPath = join(skillsDir, skillName);
|
|
182
|
-
removePathIfExists(linkPath);
|
|
183
|
-
symlinkSync(skillDir, linkPath, 'dir');
|
|
184
|
-
return normalizeDisplayPath(repoRoot, linkPath);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
46
|
function resolveDevLinkedSkills(repoRoot, rootSkillDir) {
|
|
188
47
|
const queue = [rootSkillDir];
|
|
189
48
|
const seenDirs = new Set();
|
|
@@ -296,32 +155,6 @@ export function findPackageDirByName(repoRoot, packageName) {
|
|
|
296
155
|
return null;
|
|
297
156
|
}
|
|
298
157
|
|
|
299
|
-
export function readPackageMetadata(packageDir) {
|
|
300
|
-
const packageJsonPath = join(packageDir, 'package.json');
|
|
301
|
-
if (!existsSync(packageJsonPath)) {
|
|
302
|
-
return {
|
|
303
|
-
packageName: null,
|
|
304
|
-
packageVersion: null,
|
|
305
|
-
dependencies: {},
|
|
306
|
-
devDependencies: {},
|
|
307
|
-
files: null,
|
|
308
|
-
repository: null,
|
|
309
|
-
publishConfigRegistry: null,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
314
|
-
return {
|
|
315
|
-
packageName: pkg.name || null,
|
|
316
|
-
packageVersion: pkg.version || null,
|
|
317
|
-
dependencies: pkg.dependencies || {},
|
|
318
|
-
devDependencies: pkg.devDependencies || {},
|
|
319
|
-
files: Array.isArray(pkg.files) ? pkg.files : null,
|
|
320
|
-
repository: pkg.repository || null,
|
|
321
|
-
publishConfigRegistry: pkg.publishConfig?.registry || null,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
158
|
function readPackageJson(packageDir) {
|
|
326
159
|
const packageJsonPath = join(packageDir, 'package.json');
|
|
327
160
|
if (!existsSync(packageJsonPath)) {
|
|
@@ -398,8 +231,8 @@ export function devSkill(target, {
|
|
|
398
231
|
const links = [];
|
|
399
232
|
|
|
400
233
|
for (const linkedSkill of linkedSkills) {
|
|
401
|
-
links.push(ensureSkillLink(repoRoot, '.claude', linkedSkill.name, linkedSkill.skillDir));
|
|
402
|
-
links.push(ensureSkillLink(repoRoot, '.agents', linkedSkill.name, linkedSkill.skillDir));
|
|
234
|
+
links.push(ensureSkillLink(repoRoot, '.claude', linkedSkill.name, linkedSkill.skillDir, normalizeDisplayPath));
|
|
235
|
+
links.push(ensureSkillLink(repoRoot, '.agents', linkedSkill.name, linkedSkill.skillDir, normalizeDisplayPath));
|
|
403
236
|
}
|
|
404
237
|
|
|
405
238
|
return {
|
|
@@ -427,27 +260,6 @@ export function devSkill(target, {
|
|
|
427
260
|
}
|
|
428
261
|
}
|
|
429
262
|
|
|
430
|
-
function removeSkillLinks(repoRoot, name) {
|
|
431
|
-
const removed = [];
|
|
432
|
-
for (const pathValue of [
|
|
433
|
-
join(repoRoot, '.claude', 'skills', name),
|
|
434
|
-
join(repoRoot, '.agents', 'skills', name),
|
|
435
|
-
]) {
|
|
436
|
-
if (!existsSync(pathValue)) continue;
|
|
437
|
-
removePathIfExists(pathValue);
|
|
438
|
-
removed.push(normalizeDisplayPath(repoRoot, pathValue));
|
|
439
|
-
}
|
|
440
|
-
return removed;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function removeSkillLinksByNames(repoRoot, names) {
|
|
444
|
-
const removed = [];
|
|
445
|
-
for (const name of names) {
|
|
446
|
-
removed.push(...removeSkillLinks(repoRoot, name));
|
|
447
|
-
}
|
|
448
|
-
return [...new Set(removed)];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
263
|
export function startSkillDev(target, {
|
|
452
264
|
cwd = process.cwd(),
|
|
453
265
|
sync = true,
|
|
@@ -466,7 +278,7 @@ export function startSkillDev(target, {
|
|
|
466
278
|
closed = true;
|
|
467
279
|
clearTimeout(timer);
|
|
468
280
|
if (watcher) watcher.close();
|
|
469
|
-
const removed = removeSkillLinksByNames(repoRoot, currentNames);
|
|
281
|
+
const removed = removeSkillLinksByNames(repoRoot, currentNames, normalizeDisplayPath);
|
|
470
282
|
detachProcessCleanup();
|
|
471
283
|
return {
|
|
472
284
|
name: currentNames[0] || null,
|
|
@@ -498,7 +310,7 @@ export function startSkillDev(target, {
|
|
|
498
310
|
const nextNames = result.linkedSkills.map((entry) => entry.name);
|
|
499
311
|
const staleNames = currentNames.filter((name) => !nextNames.includes(name));
|
|
500
312
|
if (staleNames.length > 0) {
|
|
501
|
-
removeSkillLinksByNames(repoRoot, staleNames);
|
|
313
|
+
removeSkillLinksByNames(repoRoot, staleNames, normalizeDisplayPath);
|
|
502
314
|
}
|
|
503
315
|
currentNames = nextNames;
|
|
504
316
|
return result;
|
|
@@ -544,7 +356,7 @@ export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
|
|
|
544
356
|
});
|
|
545
357
|
}
|
|
546
358
|
|
|
547
|
-
const removed = removeSkillLinks(repoRoot, name);
|
|
359
|
+
const removed = removeSkillLinks(repoRoot, name, normalizeDisplayPath);
|
|
548
360
|
|
|
549
361
|
return {
|
|
550
362
|
name,
|
|
@@ -553,10 +365,6 @@ export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
|
|
|
553
365
|
};
|
|
554
366
|
}
|
|
555
367
|
|
|
556
|
-
export function normalizeRepoPath(repoRoot, absolutePath) {
|
|
557
|
-
return normalizeDisplayPath(repoRoot, absolutePath);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
368
|
function buildValidateNextSteps(packageMetadata, valid) {
|
|
561
369
|
if (!valid || !packageMetadata.packageName) return [];
|
|
562
370
|
|
|
@@ -737,48 +545,16 @@ export function inspectSkill(target, { cwd = process.cwd() } = {}) {
|
|
|
737
545
|
};
|
|
738
546
|
}
|
|
739
547
|
|
|
740
|
-
function readSkillGraphNode(repoRoot, packageDir, directInstallNames = new Set()) {
|
|
741
|
-
const skillFile = join(packageDir, 'SKILL.md');
|
|
742
|
-
if (!existsSync(skillFile)) return null;
|
|
743
|
-
|
|
744
|
-
const skillMetadata = parseSkillFrontmatterFile(skillFile);
|
|
745
|
-
const packageMetadata = readPackageMetadata(packageDir);
|
|
746
|
-
if (!packageMetadata.packageName) return null;
|
|
747
|
-
|
|
748
|
-
const dependencyNames = Object.keys(packageMetadata.dependencies || {})
|
|
749
|
-
.filter((dependencyName) => {
|
|
750
|
-
const localPackageDir = findPackageDirByName(repoRoot, dependencyName);
|
|
751
|
-
if (localPackageDir && existsSync(join(localPackageDir, 'SKILL.md'))) return true;
|
|
752
|
-
const installedPackageDir = join(repoRoot, 'node_modules', ...dependencyName.split('/'));
|
|
753
|
-
return existsSync(join(installedPackageDir, 'SKILL.md'));
|
|
754
|
-
})
|
|
755
|
-
.sort((a, b) => a.localeCompare(b));
|
|
756
|
-
|
|
757
|
-
return {
|
|
758
|
-
name: skillMetadata.name,
|
|
759
|
-
packageName: packageMetadata.packageName,
|
|
760
|
-
packageVersion: packageMetadata.packageVersion,
|
|
761
|
-
skillPath: normalizeDisplayPath(repoRoot, packageDir),
|
|
762
|
-
skillFile: normalizeDisplayPath(repoRoot, skillFile),
|
|
763
|
-
direct: directInstallNames.has(packageMetadata.packageName),
|
|
764
|
-
dependencies: dependencyNames,
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
|
|
768
548
|
function buildAuthoredSkillGraph(repoRoot) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
return nodes;
|
|
549
|
+
return buildSkillGraph(repoRoot, listPackagedSkillDirs(repoRoot), {
|
|
550
|
+
parseSkillFrontmatterFile,
|
|
551
|
+
readPackageMetadata,
|
|
552
|
+
findPackageDirByName,
|
|
553
|
+
normalizeDisplayPath,
|
|
554
|
+
});
|
|
778
555
|
}
|
|
779
556
|
|
|
780
557
|
function buildInstalledSkillGraph(repoRoot) {
|
|
781
|
-
const nodes = new Map();
|
|
782
558
|
const installState = readInstallState(repoRoot);
|
|
783
559
|
const directInstallNames = new Set(
|
|
784
560
|
Object.entries(installState.installs || {})
|
|
@@ -786,73 +562,19 @@ function buildInstalledSkillGraph(repoRoot) {
|
|
|
786
562
|
.map(([packageName]) => packageName)
|
|
787
563
|
);
|
|
788
564
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
function buildReverseDependencies(nodes) {
|
|
799
|
-
const reverse = new Map();
|
|
800
|
-
for (const packageName of nodes.keys()) reverse.set(packageName, []);
|
|
801
|
-
|
|
802
|
-
for (const node of nodes.values()) {
|
|
803
|
-
for (const dependencyName of node.dependencies) {
|
|
804
|
-
if (!reverse.has(dependencyName)) continue;
|
|
805
|
-
reverse.get(dependencyName).push(node.packageName);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
for (const values of reverse.values()) values.sort((a, b) => a.localeCompare(b));
|
|
810
|
-
return reverse;
|
|
565
|
+
return buildSkillGraph(repoRoot, listInstalledPackageDirs(join(repoRoot, 'node_modules')), {
|
|
566
|
+
directInstallNames,
|
|
567
|
+
parseSkillFrontmatterFile,
|
|
568
|
+
readPackageMetadata,
|
|
569
|
+
findPackageDirByName,
|
|
570
|
+
normalizeDisplayPath,
|
|
571
|
+
});
|
|
811
572
|
}
|
|
812
573
|
|
|
813
|
-
function
|
|
574
|
+
function buildSkillStatusMapForRepo(repoRoot) {
|
|
814
575
|
const nodes = buildAuthoredSkillGraph(repoRoot);
|
|
815
576
|
const staleSkills = new Set(listStaleSkills({ cwd: repoRoot }).map((skill) => skill.packageName));
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
function resolveStatus(packageName, seen = new Set()) {
|
|
819
|
-
if (cache.has(packageName)) return cache.get(packageName);
|
|
820
|
-
if (staleSkills.has(packageName)) {
|
|
821
|
-
cache.set(packageName, 'stale');
|
|
822
|
-
return 'stale';
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (seen.has(packageName)) return 'current';
|
|
826
|
-
seen.add(packageName);
|
|
827
|
-
|
|
828
|
-
const node = nodes.get(packageName);
|
|
829
|
-
if (!node) {
|
|
830
|
-
cache.set(packageName, null);
|
|
831
|
-
return null;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
const dependencyStatuses = node.dependencies
|
|
835
|
-
.map((dependencyName) => resolveStatus(dependencyName, new Set(seen)))
|
|
836
|
-
.filter(Boolean);
|
|
837
|
-
|
|
838
|
-
const status = dependencyStatuses.some((value) => value === 'stale' || value === 'affected')
|
|
839
|
-
? 'affected'
|
|
840
|
-
: 'current';
|
|
841
|
-
|
|
842
|
-
cache.set(packageName, status);
|
|
843
|
-
return status;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
for (const packageName of nodes.keys()) {
|
|
847
|
-
resolveStatus(packageName);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
return cache;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function readNodeStatus(statusMap, packageName) {
|
|
854
|
-
if (!statusMap) return null;
|
|
855
|
-
return statusMap.get(packageName) || null;
|
|
577
|
+
return buildSkillStatusMap(nodes, staleSkills);
|
|
856
578
|
}
|
|
857
579
|
|
|
858
580
|
export function inspectSkillDependencies(target, {
|
|
@@ -863,7 +585,7 @@ export function inspectSkillDependencies(target, {
|
|
|
863
585
|
const authoredNodes = buildAuthoredSkillGraph(repoRoot);
|
|
864
586
|
const installedNodes = buildInstalledSkillGraph(repoRoot);
|
|
865
587
|
const statusRoot = discoveryRoot ? resolve(discoveryRoot) : repoRoot;
|
|
866
|
-
const statusMap =
|
|
588
|
+
const statusMap = buildSkillStatusMapForRepo(statusRoot);
|
|
867
589
|
|
|
868
590
|
const authoredTarget = authoredNodes.get(target) || null;
|
|
869
591
|
const installedTarget = installedNodes.get(target) || null;
|
|
@@ -1086,6 +808,26 @@ export function validateSkills(target, { cwd = process.cwd() } = {}) {
|
|
|
1086
808
|
const validCount = skills.filter((skill) => skill.valid).length;
|
|
1087
809
|
const invalidCount = skills.length - validCount;
|
|
1088
810
|
|
|
811
|
+
if (validCount > 0) {
|
|
812
|
+
const buildState = readBuildState(repoRoot);
|
|
813
|
+
|
|
814
|
+
for (const packageDir of packageDirs) {
|
|
815
|
+
const packageMetadata = readPackageMetadata(packageDir);
|
|
816
|
+
const result = skills.find((skill) => skill.packageName === packageMetadata.packageName);
|
|
817
|
+
if (!result?.valid) continue;
|
|
818
|
+
|
|
819
|
+
const { packageName, record } = buildStateRecordForPackageDir(repoRoot, packageDir, {
|
|
820
|
+
parseSkillFrontmatterFile,
|
|
821
|
+
readPackageMetadata,
|
|
822
|
+
normalizeDisplayPath,
|
|
823
|
+
});
|
|
824
|
+
if (!packageName) continue;
|
|
825
|
+
buildState.skills[packageName] = record;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
writeBuildState(repoRoot, buildState);
|
|
829
|
+
}
|
|
830
|
+
|
|
1089
831
|
return {
|
|
1090
832
|
valid: invalidCount === 0,
|
|
1091
833
|
count: skills.length,
|
|
@@ -1094,67 +836,16 @@ export function validateSkills(target, { cwd = process.cwd() } = {}) {
|
|
|
1094
836
|
skills,
|
|
1095
837
|
};
|
|
1096
838
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
function hashFile(filePath) {
|
|
1100
|
-
const digest = createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
1101
|
-
return `sha256:${digest}`;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
839
|
function normalizeRelativePath(pathValue) {
|
|
1105
840
|
return pathValue.split('\\').join('/');
|
|
1106
841
|
}
|
|
1107
842
|
|
|
1108
|
-
function readBuildState(repoRoot) {
|
|
1109
|
-
const buildStatePath = join(repoRoot, '.agentpack', 'build-state.json');
|
|
1110
|
-
if (!existsSync(buildStatePath)) {
|
|
1111
|
-
return { version: 1, skills: {} };
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
return JSON.parse(readFileSync(buildStatePath, 'utf-8'));
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
function readInstallState(repoRoot) {
|
|
1118
|
-
const installStatePath = join(repoRoot, '.agentpack', 'install.json');
|
|
1119
|
-
if (!existsSync(installStatePath)) {
|
|
1120
|
-
return { version: 1, installs: {} };
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
return JSON.parse(readFileSync(installStatePath, 'utf-8'));
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
function writeInstallState(repoRoot, state) {
|
|
1127
|
-
mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
|
|
1128
|
-
writeFileSync(join(repoRoot, '.agentpack', 'install.json'), JSON.stringify(state, null, 2) + '\n');
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
843
|
function normalizeRequestedTarget(target, cwd = process.cwd()) {
|
|
1132
844
|
if (typeof target !== 'string') return target;
|
|
1133
845
|
if (target.startsWith('@')) return target;
|
|
1134
846
|
return normalizeRelativePath(resolve(cwd, target));
|
|
1135
847
|
}
|
|
1136
848
|
|
|
1137
|
-
function compareRecordedSources(repoRoot, record) {
|
|
1138
|
-
const changes = [];
|
|
1139
|
-
const recordedSources = record.sources || {};
|
|
1140
|
-
|
|
1141
|
-
for (const [sourcePath, sourceRecord] of Object.entries(recordedSources)) {
|
|
1142
|
-
const absoluteSourcePath = join(repoRoot, sourcePath);
|
|
1143
|
-
const currentHash = hashFile(absoluteSourcePath);
|
|
1144
|
-
const recordedHash = sourceRecord.hash;
|
|
1145
|
-
|
|
1146
|
-
if (currentHash !== recordedHash) {
|
|
1147
|
-
changes.push({
|
|
1148
|
-
path: sourcePath,
|
|
1149
|
-
recorded: recordedHash,
|
|
1150
|
-
current: currentHash,
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
return changes;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
849
|
function listPackagedSkillDirs(repoRoot) {
|
|
1159
850
|
const stack = [repoRoot];
|
|
1160
851
|
const results = [];
|
|
@@ -1387,73 +1078,6 @@ function resolveNpmInstallTargets(directTargetMap) {
|
|
|
1387
1078
|
return [...new Set(npmInstallTargets)];
|
|
1388
1079
|
}
|
|
1389
1080
|
|
|
1390
|
-
function ensureSymlink(targetPath, linkPath) {
|
|
1391
|
-
rmSync(linkPath, { recursive: true, force: true });
|
|
1392
|
-
mkdirSync(dirname(linkPath), { recursive: true });
|
|
1393
|
-
symlinkSync(targetPath, linkPath, 'dir');
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
function removePathIfExists(pathValue) {
|
|
1397
|
-
rmSync(pathValue, { recursive: true, force: true });
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
function buildInstallRecord(repoRoot, packageDir, directTargetMap) {
|
|
1401
|
-
const packageMetadata = readPackageMetadata(packageDir);
|
|
1402
|
-
if (!packageMetadata.packageName) return null;
|
|
1403
|
-
|
|
1404
|
-
const skillMetadata = parseSkillFrontmatterFile(join(packageDir, 'SKILL.md'));
|
|
1405
|
-
const skillDirName = skillMetadata.name;
|
|
1406
|
-
const materializations = [];
|
|
1407
|
-
|
|
1408
|
-
const claudeTargetAbs = join(repoRoot, '.claude', 'skills', skillDirName);
|
|
1409
|
-
ensureSymlink(packageDir, claudeTargetAbs);
|
|
1410
|
-
materializations.push({
|
|
1411
|
-
target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
|
|
1412
|
-
mode: 'symlink',
|
|
1413
|
-
});
|
|
1414
|
-
|
|
1415
|
-
const agentsTargetAbs = join(repoRoot, '.agents', 'skills', skillDirName);
|
|
1416
|
-
ensureSymlink(packageDir, agentsTargetAbs);
|
|
1417
|
-
materializations.push({
|
|
1418
|
-
target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
|
|
1419
|
-
mode: 'symlink',
|
|
1420
|
-
});
|
|
1421
|
-
|
|
1422
|
-
return {
|
|
1423
|
-
packageName: packageMetadata.packageName,
|
|
1424
|
-
direct: directTargetMap.has(packageMetadata.packageName),
|
|
1425
|
-
requestedTarget: directTargetMap.get(packageMetadata.packageName) || null,
|
|
1426
|
-
packageVersion: packageMetadata.packageVersion,
|
|
1427
|
-
sourcePackagePath: normalizeRelativePath(relative(repoRoot, packageDir)),
|
|
1428
|
-
materializations,
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
function rebuildInstallState(repoRoot, directTargetMap) {
|
|
1433
|
-
const packageDirs = listInstalledPackageDirs(join(repoRoot, 'node_modules'));
|
|
1434
|
-
const installs = {};
|
|
1435
|
-
|
|
1436
|
-
for (const packageDir of packageDirs) {
|
|
1437
|
-
const skillFile = join(packageDir, 'SKILL.md');
|
|
1438
|
-
if (!existsSync(skillFile)) continue;
|
|
1439
|
-
|
|
1440
|
-
const record = buildInstallRecord(repoRoot, packageDir, directTargetMap);
|
|
1441
|
-
if (!record) continue;
|
|
1442
|
-
|
|
1443
|
-
installs[record.packageName] = {
|
|
1444
|
-
direct: record.direct,
|
|
1445
|
-
requested_target: record.requestedTarget,
|
|
1446
|
-
package_version: record.packageVersion,
|
|
1447
|
-
source_package_path: record.sourcePackagePath,
|
|
1448
|
-
materializations: record.materializations,
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
const state = { version: 1, installs };
|
|
1453
|
-
writeInstallState(repoRoot, state);
|
|
1454
|
-
return state;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
1081
|
export function installSkills(targets, { cwd = process.cwd() } = {}) {
|
|
1458
1082
|
const repoRoot = findRepoRoot(cwd);
|
|
1459
1083
|
const previousState = readInstallState(repoRoot);
|
|
@@ -1493,7 +1117,12 @@ export function installSkills(targets, { cwd = process.cwd() } = {}) {
|
|
|
1493
1117
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1494
1118
|
});
|
|
1495
1119
|
|
|
1496
|
-
return rebuildInstallState(repoRoot, directTargetMap
|
|
1120
|
+
return rebuildInstallState(repoRoot, directTargetMap, {
|
|
1121
|
+
listInstalledPackageDirs,
|
|
1122
|
+
parseSkillFrontmatterFile,
|
|
1123
|
+
readPackageMetadata,
|
|
1124
|
+
normalizeRelativePath,
|
|
1125
|
+
});
|
|
1497
1126
|
}
|
|
1498
1127
|
|
|
1499
1128
|
export function inspectSkillsEnv({ cwd = process.cwd() } = {}) {
|
|
@@ -1809,7 +1438,12 @@ export function uninstallSkills(target, { cwd = process.cwd() } = {}) {
|
|
|
1809
1438
|
});
|
|
1810
1439
|
}
|
|
1811
1440
|
|
|
1812
|
-
const nextState = rebuildInstallState(repoRoot, nextDirectTargetMap
|
|
1441
|
+
const nextState = rebuildInstallState(repoRoot, nextDirectTargetMap, {
|
|
1442
|
+
listInstalledPackageDirs,
|
|
1443
|
+
parseSkillFrontmatterFile,
|
|
1444
|
+
readPackageMetadata,
|
|
1445
|
+
normalizeRelativePath,
|
|
1446
|
+
});
|
|
1813
1447
|
const remainingTargets = new Set(
|
|
1814
1448
|
Object.values(nextState.installs)
|
|
1815
1449
|
.flatMap((install) => (install.materializations || []).map((entry) => entry.target))
|