@vltpkg/graph 1.4.0 → 1.5.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 +59 -3
- package/dist/actual/load.js +24 -12
- package/dist/build.js +30 -5
- package/dist/dependencies.d.ts +11 -0
- package/dist/dependencies.js +47 -7
- package/dist/filter-nodes-by-query.d.ts +39 -0
- package/dist/filter-nodes-by-query.js +100 -0
- package/dist/ideal/append-nodes.js +11 -6
- package/dist/ideal/get-importer-specs.js +17 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/install.d.ts +23 -8
- package/dist/install.js +24 -1
- package/dist/lockfile/load-edges.js +10 -2
- package/dist/reify/build.d.ts +7 -0
- package/dist/reify/check-needed-build.d.ts +5 -0
- package/dist/reify/check-needed-build.js +13 -0
- package/dist/reify/index.js +4 -38
- package/dist/reify/update-importers-package-json.js +5 -9
- package/dist/skills/dirs.d.ts +58 -0
- package/dist/skills/dirs.js +211 -0
- package/dist/skills/discover.d.ts +73 -0
- package/dist/skills/discover.js +234 -0
- package/dist/skills/gitignore.d.ts +13 -0
- package/dist/skills/gitignore.js +53 -0
- package/dist/skills/index.d.ts +123 -0
- package/dist/skills/index.js +225 -0
- package/dist/skills/lazy.d.ts +8 -0
- package/dist/skills/lazy.js +8 -0
- package/dist/skills/mask.d.ts +26 -0
- package/dist/skills/mask.js +43 -0
- package/dist/skills/mounts.d.ts +64 -0
- package/dist/skills/mounts.js +136 -0
- package/dist/uninstall.d.ts +24 -7
- package/dist/uninstall.js +24 -1
- package/dist/update.d.ts +24 -4
- package/dist/update.js +24 -1
- package/dist/visualization/mermaid-output.d.ts +8 -0
- package/dist/visualization/mermaid-output.js +25 -8
- package/package.json +21 -21
package/dist/reify/build.d.ts
CHANGED
|
@@ -9,5 +9,12 @@ import type { Node } from '../node.ts';
|
|
|
9
9
|
export type BuildResult = {
|
|
10
10
|
success: Node[];
|
|
11
11
|
failure: Node[];
|
|
12
|
+
/** needed builds skipped as they have no security data */
|
|
13
|
+
skipped?: Node[];
|
|
14
|
+
/**
|
|
15
|
+
* why `skipped` builds have no security data: the request timed
|
|
16
|
+
* out, or socket.dev has not scanned them yet
|
|
17
|
+
*/
|
|
18
|
+
skippedReason?: 'timeout' | 'unscanned';
|
|
12
19
|
};
|
|
13
20
|
export declare const build: (diff: Diff, packageJson: PackageJson, scurry: PathScurry, allowScriptsNodes: Set<DepID>) => Promise<BuildResult>;
|
|
@@ -32,3 +32,8 @@ export type CheckNeededBuildOptions = {
|
|
|
32
32
|
* @returns {BuildData} The BuildData object containing Node objects that need building
|
|
33
33
|
*/
|
|
34
34
|
export declare const checkNeededBuild: (options: CheckNeededBuildOptions) => BuildData;
|
|
35
|
+
/**
|
|
36
|
+
* Copy build state from the actual graph onto ideal nodes that are
|
|
37
|
+
* not re-extracted, since `vlt-lock.json` does not store it.
|
|
38
|
+
*/
|
|
39
|
+
export declare const carryBuildState: (diff: Diff) => void;
|
|
@@ -70,3 +70,16 @@ export const checkNeededBuild = (options) => {
|
|
|
70
70
|
};
|
|
71
71
|
return buildData;
|
|
72
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Copy build state from the actual graph onto ideal nodes that are
|
|
75
|
+
* not re-extracted, since `vlt-lock.json` does not store it.
|
|
76
|
+
*/
|
|
77
|
+
export const carryBuildState = (diff) => {
|
|
78
|
+
for (const [id, node] of diff.to.nodes) {
|
|
79
|
+
if (diff.nodes.add.has(node))
|
|
80
|
+
continue;
|
|
81
|
+
const prev = diff.from.nodes.get(id);
|
|
82
|
+
if (prev)
|
|
83
|
+
node.buildState = prev.buildState;
|
|
84
|
+
}
|
|
85
|
+
};
|
package/dist/reify/index.js
CHANGED
|
@@ -8,51 +8,16 @@ import { addEdges } from "./add-edges.js";
|
|
|
8
8
|
import { addNodes } from "./add-nodes.js";
|
|
9
9
|
import { build } from "./build.js";
|
|
10
10
|
import { deleteEdges } from "./delete-edges.js";
|
|
11
|
-
import { checkNeededBuild } from "./check-needed-build.js";
|
|
11
|
+
import { carryBuildState, checkNeededBuild, } from "./check-needed-build.js";
|
|
12
12
|
import { deleteNodes } from "./delete-nodes.js";
|
|
13
13
|
import { internalHoist } from "./internal-hoist.js";
|
|
14
14
|
import { pruneUnsupportedOptional } from "./optional-fail.js";
|
|
15
15
|
import { rollback } from "./rollback.js";
|
|
16
16
|
import { updatePackageJson } from "./update-importers-package-json.js";
|
|
17
17
|
import { copyFileSync } from 'node:fs';
|
|
18
|
-
import { Query } from '@vltpkg/query';
|
|
19
|
-
import { SecurityArchive } from '@vltpkg/security-archive';
|
|
20
18
|
import { binChmodAll } from "./bin-chmod.js";
|
|
19
|
+
import { filterNodesByQuery } from "../filter-nodes-by-query.js";
|
|
21
20
|
const limit = Math.max(availableParallelism() - 1, 1) * 8;
|
|
22
|
-
/**
|
|
23
|
-
* Filter nodes using a DSS query string
|
|
24
|
-
*/
|
|
25
|
-
const filterNodesByQuery = async (graph, allowScriptsQuery) => {
|
|
26
|
-
// shortcut no packages included
|
|
27
|
-
if (allowScriptsQuery === ':not(*)' /* c8 ignore next */ ||
|
|
28
|
-
!allowScriptsQuery) {
|
|
29
|
-
return new Set();
|
|
30
|
-
}
|
|
31
|
-
// shortcut all packages included
|
|
32
|
-
if (allowScriptsQuery === '*') {
|
|
33
|
-
return new Set(graph.nodes.keys());
|
|
34
|
-
}
|
|
35
|
-
/* c8 ignore start */
|
|
36
|
-
const securityArchive = Query.hasSecuritySelectors(allowScriptsQuery) ?
|
|
37
|
-
await SecurityArchive.start({
|
|
38
|
-
nodes: [...graph.nodes.values()],
|
|
39
|
-
})
|
|
40
|
-
: undefined;
|
|
41
|
-
/* c8 ignore stop */
|
|
42
|
-
const edges = graph.edges;
|
|
43
|
-
const nodes = new Set(graph.nodes.values());
|
|
44
|
-
const importers = graph.importers;
|
|
45
|
-
const query = new Query({
|
|
46
|
-
edges,
|
|
47
|
-
nodes,
|
|
48
|
-
importers,
|
|
49
|
-
securityArchive,
|
|
50
|
-
});
|
|
51
|
-
const { nodes: resultNodes } = await query.search(allowScriptsQuery, {
|
|
52
|
-
signal: new AbortController().signal,
|
|
53
|
-
});
|
|
54
|
-
return new Set(resultNodes.map(node => node.id));
|
|
55
|
-
};
|
|
56
21
|
/**
|
|
57
22
|
* Make the current project match the supplied graph.
|
|
58
23
|
*/
|
|
@@ -65,6 +30,7 @@ export const reify = async (options) => {
|
|
|
65
30
|
loadManifests: true,
|
|
66
31
|
});
|
|
67
32
|
const diff = new Diff(actual, graph);
|
|
33
|
+
carryBuildState(diff);
|
|
68
34
|
// normalise importer edge specs and compute the package.json changes
|
|
69
35
|
// before deciding whether anything needs reifying: an add whose target
|
|
70
36
|
// is already in place has no node diff but still has a saved value to
|
|
@@ -173,7 +139,7 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
|
|
|
173
139
|
await build(diff, packageJson, scurry, allowScriptsNodes);
|
|
174
140
|
// set the buildQueue on the result object containing
|
|
175
141
|
// an array with all the ids of nodes that need building
|
|
176
|
-
res.buildQueue = [...diff.nodes.
|
|
142
|
+
res.buildQueue = [...diff.to.nodes.values()]
|
|
177
143
|
.filter(node => node.buildState === 'needed')
|
|
178
144
|
.map(node => node.id);
|
|
179
145
|
// if we had to change the actual graph along the way,
|
|
@@ -2,15 +2,9 @@ import { splitDepID } from '@vltpkg/dep-id';
|
|
|
2
2
|
import { error } from '@vltpkg/error-cause';
|
|
3
3
|
import { Spec } from '@vltpkg/spec';
|
|
4
4
|
import { longDependencyTypes } from '@vltpkg/types';
|
|
5
|
+
import { longDependencyTypeNames } from "../dependencies.js";
|
|
5
6
|
import { resolveSaveType } from "../resolve-save-type.js";
|
|
6
7
|
import { calculateSaveValue } from "./calculate-save-value.js";
|
|
7
|
-
const depTypesMap = new Map([
|
|
8
|
-
['prod', 'dependencies'],
|
|
9
|
-
['dev', 'devDependencies'],
|
|
10
|
-
['peer', 'peerDependencies'],
|
|
11
|
-
['peerOptional', 'peerDependencies'],
|
|
12
|
-
['optional', 'optionalDependencies'],
|
|
13
|
-
]);
|
|
14
8
|
const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
|
|
15
9
|
const node = graph.nodes.get(nodeId);
|
|
16
10
|
if (!node) {
|
|
@@ -51,10 +45,12 @@ const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
|
|
|
51
45
|
const [name, dep] = deleteNameOrAddItem;
|
|
52
46
|
// peerOptional also needs to add peerDependenciesMeta entry
|
|
53
47
|
const depTypeShort = resolveSaveType(node, name, dep.type);
|
|
54
|
-
const depType =
|
|
48
|
+
const depType = Object.hasOwn(longDependencyTypeNames, depTypeShort) ?
|
|
49
|
+
longDependencyTypeNames[depTypeShort]
|
|
50
|
+
: undefined;
|
|
55
51
|
if (!depType) {
|
|
56
52
|
throw error('Failed to retrieve dependency type', {
|
|
57
|
-
validOptions:
|
|
53
|
+
validOptions: Object.keys(longDependencyTypeNames),
|
|
58
54
|
found: dep.type,
|
|
59
55
|
});
|
|
60
56
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Always a target when detecting: read by most agents. */
|
|
2
|
+
export declare const AGENTS_SKILLS = ".agents/skills";
|
|
3
|
+
/** Set (non-empty) inside Claude Code. */
|
|
4
|
+
export declare const CLAUDE_ENV: readonly ["CLAUDECODE", "CLAUDE_CODE"];
|
|
5
|
+
/**
|
|
6
|
+
* Agent folder at the project root → its skills dir.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AGENT_ROOTS: readonly (readonly [
|
|
9
|
+
marker: string,
|
|
10
|
+
dir: string
|
|
11
|
+
])[];
|
|
12
|
+
/**
|
|
13
|
+
* An agent skills dir.
|
|
14
|
+
*/
|
|
15
|
+
export type SkillsDir = {
|
|
16
|
+
/** `/`-separated, relative to projectRoot */
|
|
17
|
+
dir: string;
|
|
18
|
+
/** realpath (a missing tail appended as is) */
|
|
19
|
+
real: string;
|
|
20
|
+
/** `<dev>:<ino>` (no inode: the path); unset when missing */
|
|
21
|
+
id?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Result of {@link skillsDirs}.
|
|
25
|
+
*/
|
|
26
|
+
export type SkillsDirs = {
|
|
27
|
+
/** dirs to link into */
|
|
28
|
+
targets: SkillsDir[];
|
|
29
|
+
/** existing dirs to look for vlt links in: targets first */
|
|
30
|
+
scan: SkillsDir[];
|
|
31
|
+
/** targets dropped: `<dir> (<reason>)` */
|
|
32
|
+
skipped: string[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Options for {@link skillsDirs}.
|
|
36
|
+
*/
|
|
37
|
+
export type SkillsDirsOptions = {
|
|
38
|
+
projectRoot: string;
|
|
39
|
+
/** `skills-dir`: replaces detection; `[]` none */
|
|
40
|
+
configured?: string[];
|
|
41
|
+
/** default `process.env` */
|
|
42
|
+
env?: Record<string, string | undefined>;
|
|
43
|
+
/** create missing targets */
|
|
44
|
+
create?: boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Throw EUSAGE unless each entry is a dir inside projectRoot, not in
|
|
48
|
+
* `node_modules`.
|
|
49
|
+
*/
|
|
50
|
+
export declare const assertSkillsDirs: (projectRoot: string, list?: string[]) => void;
|
|
51
|
+
/**
|
|
52
|
+
* The project's agent skill dirs. `configured` replaces detection:
|
|
53
|
+
* `.agents/skills`, plus `<folder>/skills` for each agent folder at
|
|
54
|
+
* the root, `.claude/skills` inside Claude Code, and existing
|
|
55
|
+
* `.<name>/skills` dirs. Dirs must resolve inside projectRoot, not in
|
|
56
|
+
* `node_modules`; the same dir is listed once.
|
|
57
|
+
*/
|
|
58
|
+
export declare const skillsDirs: ({ projectRoot, configured, env, create, }: SkillsDirsOptions) => SkillsDirs;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
import { lstatSync, mkdirSync, readdirSync, realpathSync, statSync, } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { norm } from "./mounts.js";
|
|
5
|
+
/** Always a target when detecting: read by most agents. */
|
|
6
|
+
export const AGENTS_SKILLS = '.agents/skills';
|
|
7
|
+
/** Set (non-empty) inside Claude Code. */
|
|
8
|
+
export const CLAUDE_ENV = ['CLAUDECODE', 'CLAUDE_CODE'];
|
|
9
|
+
/**
|
|
10
|
+
* Agent folder at the project root → its skills dir.
|
|
11
|
+
*/
|
|
12
|
+
// vercel-labs/skills v1.7.2 src/agents.ts: skillsDir per agent (line), dot roots only
|
|
13
|
+
export const AGENT_ROOTS = [
|
|
14
|
+
['.adal', '.adal/skills'], // 813
|
|
15
|
+
['.aider-desk', '.aider-desk/skills'], // 84
|
|
16
|
+
['.augment', '.augment/skills'], // 140
|
|
17
|
+
['.autohand', '.autohand/skills'], // 131
|
|
18
|
+
['.bob', '.bob/skills'], // 149
|
|
19
|
+
['.claude', '.claude/skills'], // 158
|
|
20
|
+
['.codeartsdoer', '.codeartsdoer/skills'], // 191
|
|
21
|
+
['.codebuddy', '.codebuddy/skills'], // 200
|
|
22
|
+
['.codemaker', '.codemaker/skills'], // 209
|
|
23
|
+
['.codestudio', '.codestudio/skills'], // 218
|
|
24
|
+
['.commandcode', '.commandcode/skills'], // 236
|
|
25
|
+
['.continue', '.continue/skills'], // 245
|
|
26
|
+
['.cortex', '.cortex/skills'], // 254
|
|
27
|
+
['.crush', '.crush/skills'], // 263
|
|
28
|
+
['.devin', '.devin/skills'], // 291
|
|
29
|
+
['.forge', '.forge/skills'], // 345
|
|
30
|
+
['.goose', '.goose/skills'], // 382
|
|
31
|
+
['.grok', '.grok/skills'], // 391
|
|
32
|
+
['.hermes', '.hermes/skills'], // 400
|
|
33
|
+
['.iflow', '.iflow/skills'], // 436
|
|
34
|
+
['.inferencesh', '.inferencesh/skills'], // 409
|
|
35
|
+
['.jazz', '.jazz/skills'], // 418
|
|
36
|
+
['.junie', '.junie/skills'], // 427
|
|
37
|
+
['.kimchi', '.kimchi/skills'], // 456
|
|
38
|
+
['.kiro', '.kiro/skills'], // 474
|
|
39
|
+
['.kode', '.kode/skills'], // 483
|
|
40
|
+
['.lingma', '.lingma/skills'], // 492
|
|
41
|
+
['.mcpjam', '.mcpjam/skills'], // 511
|
|
42
|
+
['.minimax', '.minimax/skills'], // 520
|
|
43
|
+
['.moxby', '.moxby/skills'], // 538
|
|
44
|
+
['.mux', '.mux/skills'], // 547
|
|
45
|
+
['.neovate', '.neovate/skills'], // 782
|
|
46
|
+
['.ona', '.ona/skills'], // 574
|
|
47
|
+
['.openhands', '.openhands/skills'], // 565
|
|
48
|
+
['.pochi', '.pochi/skills'], // 791
|
|
49
|
+
['.posit/assistant', '.posit/assistant/skills'], // 595
|
|
50
|
+
['.qoder', '.qoder/skills'], // 604,613
|
|
51
|
+
['.qwen', '.qwen/skills'], // 622
|
|
52
|
+
['.reasonix', '.reasonix/skills'], // 641
|
|
53
|
+
['.roo', '.roo/skills'], // 659
|
|
54
|
+
['.rovodev', '.rovodev/skills'], // 650
|
|
55
|
+
['.tabnine', '.tabnine/agent/skills'], // 678
|
|
56
|
+
['.terramind', '.terramind/skills'], // 687
|
|
57
|
+
['.tinycloud', '.tinycloud/skills'], // 696
|
|
58
|
+
['.trae', '.trae/skills'], // 705,714
|
|
59
|
+
['.vibe', '.vibe/skills'], // 529
|
|
60
|
+
['.windsurf', '.windsurf/skills'], // 732
|
|
61
|
+
['.zcode', '.zcode/skills'], // 755
|
|
62
|
+
['.zencoder', '.zencoder/skills'], // 764,773
|
|
63
|
+
];
|
|
64
|
+
// strictly below root
|
|
65
|
+
const inside = (root, p) => {
|
|
66
|
+
const r = relative(root, p);
|
|
67
|
+
return (!!r && r !== '..' && !r.startsWith('..' + sep) && !isAbsolute(r));
|
|
68
|
+
};
|
|
69
|
+
// a `node_modules` segment below root
|
|
70
|
+
const inNodeModules = (root, p) => relative(root, p)
|
|
71
|
+
.split(sep)
|
|
72
|
+
.some(s => s.toLowerCase() === 'node_modules');
|
|
73
|
+
const isDir = (path) => {
|
|
74
|
+
try {
|
|
75
|
+
return statSync(path).isDirectory();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Throw EUSAGE unless each entry is a dir inside projectRoot, not in
|
|
83
|
+
* `node_modules`.
|
|
84
|
+
*/
|
|
85
|
+
export const assertSkillsDirs = (projectRoot, list = []) => {
|
|
86
|
+
for (const e of list) {
|
|
87
|
+
const p = resolve(projectRoot, e);
|
|
88
|
+
if (e &&
|
|
89
|
+
(!inside(projectRoot, p) || inNodeModules(projectRoot, p))) {
|
|
90
|
+
throw error('Invalid --skills-dir: must be a dir inside the project, not in node_modules', { code: 'EUSAGE', found: e }, assertSkillsDirs);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
// walk `dir` from the real root, one component at a time, so a
|
|
95
|
+
// symlink out of the project is caught before anything is made in it.
|
|
96
|
+
// A string is the reason it was skipped (fs errors: the code)
|
|
97
|
+
const walk = (realRoot, dir, create) => {
|
|
98
|
+
const parts = dir.split('/');
|
|
99
|
+
let real = realRoot;
|
|
100
|
+
try {
|
|
101
|
+
for (const [i, part] of parts.entries()) {
|
|
102
|
+
const next = join(real, part);
|
|
103
|
+
let st = lstatSync(next, { throwIfNoEntry: false });
|
|
104
|
+
if (!st) {
|
|
105
|
+
if (!create)
|
|
106
|
+
return { dir, real: join(real, ...parts.slice(i)) };
|
|
107
|
+
try {
|
|
108
|
+
mkdirSync(next);
|
|
109
|
+
}
|
|
110
|
+
catch (er) {
|
|
111
|
+
if (er.code !== 'EEXIST')
|
|
112
|
+
throw er;
|
|
113
|
+
}
|
|
114
|
+
st = lstatSync(next);
|
|
115
|
+
}
|
|
116
|
+
if (st.isSymbolicLink()) {
|
|
117
|
+
let r;
|
|
118
|
+
try {
|
|
119
|
+
r = realpathSync(next);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return 'not a directory';
|
|
123
|
+
}
|
|
124
|
+
if (!inside(realRoot, r))
|
|
125
|
+
return 'outside the project';
|
|
126
|
+
if (inNodeModules(realRoot, r))
|
|
127
|
+
return 'inside node_modules';
|
|
128
|
+
if (!isDir(r))
|
|
129
|
+
return 'not a directory';
|
|
130
|
+
real = r;
|
|
131
|
+
}
|
|
132
|
+
else if (st.isDirectory()) {
|
|
133
|
+
real = next;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return 'not a directory';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const { dev, ino } = statSync(real, { bigint: true });
|
|
140
|
+
// some filesystems report no inode: compare paths
|
|
141
|
+
return { dir, real, id: ino ? `${dev}:${ino}` : norm(real) };
|
|
142
|
+
}
|
|
143
|
+
catch (er) {
|
|
144
|
+
return String(er.code);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* The project's agent skill dirs. `configured` replaces detection:
|
|
149
|
+
* `.agents/skills`, plus `<folder>/skills` for each agent folder at
|
|
150
|
+
* the root, `.claude/skills` inside Claude Code, and existing
|
|
151
|
+
* `.<name>/skills` dirs. Dirs must resolve inside projectRoot, not in
|
|
152
|
+
* `node_modules`; the same dir is listed once.
|
|
153
|
+
*/
|
|
154
|
+
export const skillsDirs = ({ projectRoot, configured, env = process.env, create = false, }) => {
|
|
155
|
+
const realRoot = realpathSync(projectRoot);
|
|
156
|
+
const res = { targets: [], scan: [], skipped: [] };
|
|
157
|
+
const dots = readdirSync(realRoot)
|
|
158
|
+
.filter(e => e.startsWith('.'))
|
|
159
|
+
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
160
|
+
.map(e => `${e}/skills`);
|
|
161
|
+
let list;
|
|
162
|
+
if (configured) {
|
|
163
|
+
assertSkillsDirs(projectRoot, configured);
|
|
164
|
+
list = configured.filter(Boolean);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const found = new Set();
|
|
168
|
+
for (const [marker, d] of AGENT_ROOTS) {
|
|
169
|
+
if (isDir(join(realRoot, marker)))
|
|
170
|
+
found.add(d);
|
|
171
|
+
}
|
|
172
|
+
if (CLAUDE_ENV.some(k => env[k]))
|
|
173
|
+
found.add('.claude/skills');
|
|
174
|
+
for (const d of dots) {
|
|
175
|
+
if (isDir(join(realRoot, d)))
|
|
176
|
+
found.add(d);
|
|
177
|
+
}
|
|
178
|
+
list = [
|
|
179
|
+
AGENTS_SKILLS,
|
|
180
|
+
...[...found].sort((a, b) => a.localeCompare(b, 'en')),
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
const dirs = new Set(list.map(e => relative(projectRoot, resolve(projectRoot, e))
|
|
184
|
+
.split(sep)
|
|
185
|
+
.join('/')));
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
for (const dir of dirs) {
|
|
188
|
+
const d = walk(realRoot, dir, create);
|
|
189
|
+
if (typeof d === 'string') {
|
|
190
|
+
res.skipped.push(`${dir} (${d})`);
|
|
191
|
+
}
|
|
192
|
+
else if (!seen.has(d.id ?? d.real)) {
|
|
193
|
+
seen.add(d.id ?? d.real);
|
|
194
|
+
res.targets.push(d);
|
|
195
|
+
if (d.id)
|
|
196
|
+
res.scan.push(d);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const dir of [
|
|
200
|
+
AGENTS_SKILLS,
|
|
201
|
+
...AGENT_ROOTS.map(([, d]) => d),
|
|
202
|
+
...dots,
|
|
203
|
+
]) {
|
|
204
|
+
const d = walk(realRoot, dir, false);
|
|
205
|
+
if (typeof d !== 'string' && d.id && !seen.has(d.id)) {
|
|
206
|
+
seen.add(d.id);
|
|
207
|
+
res.scan.push(d);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return res;
|
|
211
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { DepID } from '@vltpkg/dep-id';
|
|
2
|
+
import type { PathScurry } from 'path-scurry';
|
|
3
|
+
import type { Node } from '../node.ts';
|
|
4
|
+
import type { SkillMount } from './mounts.ts';
|
|
5
|
+
/**
|
|
6
|
+
* An agent skill (a dir with a `SKILL.md`) shipped by an installed
|
|
7
|
+
* package. See https://agentskills.io/specification
|
|
8
|
+
*/
|
|
9
|
+
export type Skill = {
|
|
10
|
+
/** package name, e.g. `@s/bar` */
|
|
11
|
+
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* name a direct dep is installed under, when not `name`, as `vlt ls`
|
|
14
|
+
* shows it
|
|
15
|
+
*/
|
|
16
|
+
alias?: string;
|
|
17
|
+
/** package version */
|
|
18
|
+
version?: string;
|
|
19
|
+
/** package DepID */
|
|
20
|
+
id: DepID;
|
|
21
|
+
/**
|
|
22
|
+
* skill name: its dir name (`skills/<dir>`, `dist/skills/<dir>`,
|
|
23
|
+
* `.agents/skills/<dir>`), or the root SKILL.md `name`
|
|
24
|
+
*/
|
|
25
|
+
skill: string;
|
|
26
|
+
/**
|
|
27
|
+
* `SKILL.md` frontmatter `name`, when it differs from `skill`
|
|
28
|
+
* (matched by `:skill()`)
|
|
29
|
+
*/
|
|
30
|
+
frontmatterName?: string;
|
|
31
|
+
/** frontmatter description (folded to one line, control chars stripped) */
|
|
32
|
+
description?: string;
|
|
33
|
+
/** realpath of the dir holding SKILL.md */
|
|
34
|
+
path: string;
|
|
35
|
+
/** dirs holding a vlt link named `skill` to `path` */
|
|
36
|
+
linked: string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Result of `discoverSkills`.
|
|
40
|
+
*/
|
|
41
|
+
export type DiscoveredSkills = {
|
|
42
|
+
/** skills of the picked version of each package, sorted by name */
|
|
43
|
+
skills: Skill[];
|
|
44
|
+
/** every scanned package version, w/ the skills it ships */
|
|
45
|
+
scanned: Map<DepID, Pick<Skill, 'skill' | 'path'>[]>;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The `SKILL.md` frontmatter fields vlt reads.
|
|
49
|
+
*/
|
|
50
|
+
export type SkillFrontmatter = {
|
|
51
|
+
name?: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Read `name` and `description` from a `SKILL.md` YAML frontmatter.
|
|
56
|
+
* Minimal line reader: top-level keys, plain/quoted/folded values.
|
|
57
|
+
*/
|
|
58
|
+
export declare const readFrontmatter: (file: string) => SkillFrontmatter;
|
|
59
|
+
/** Ships agent skills, as `discoverSkills` finds them. */
|
|
60
|
+
export declare const shipsSkills: (node: Node) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Find the agent skills shipped by the given installed packages.
|
|
63
|
+
*
|
|
64
|
+
* Only packages in the vlt store are scanned, for a root `SKILL.md`,
|
|
65
|
+
* then `<dir>/SKILL.md` in `skills/`, `dist/skills/` and
|
|
66
|
+
* `.agents/skills/`: the first skill of a name wins. One version per
|
|
67
|
+
* package is picked, even if it ships none: a dep of the main
|
|
68
|
+
* importer, else of any importer, else the highest version, then
|
|
69
|
+
* DepID. Same-name skills of different packages are all returned,
|
|
70
|
+
* sorted by rank then DepID (`linkSkills` resolves them). `mounts`
|
|
71
|
+
* set each skill's `linked` dirs.
|
|
72
|
+
*/
|
|
73
|
+
export declare const discoverSkills: (nodes: Iterable<Node>, scurry: PathScurry, mounts?: SkillMount[]) => DiscoveredSkills;
|