agent-skillboard 0.2.7 → 0.2.9
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/CHANGELOG.md +34 -0
- package/README.md +46 -24
- package/bin/postinstall.mjs +61 -0
- package/docs/install.md +64 -25
- package/docs/reference.md +43 -0
- package/docs/user-flow.md +11 -0
- package/package.json +3 -2
- package/src/advisor/action-core.mjs +1 -1
- package/src/advisor/actions.mjs +1 -0
- package/src/agent-inventory-platforms.mjs +10 -17
- package/src/agent-inventory.mjs +7 -4
- package/src/agent-skill-import.mjs +291 -0
- package/src/agent-skill-roots.mjs +114 -0
- package/src/brief-renderer.mjs +2 -2
- package/src/cli.mjs +134 -19
- package/src/control/skill-crud.mjs +1 -0
- package/src/control/variant-files.mjs +2 -2
- package/src/doctor.mjs +1 -1
- package/src/hook-plan.mjs +1 -1
- package/src/install-output-detector.mjs +2 -2
- package/src/lifecycle-cli.mjs +169 -9
- package/src/lifecycle-content.mjs +1 -1
- package/src/skill-paths.mjs +1 -1
- package/src/source-cache.mjs +1 -1
- package/src/source-profiles.mjs +4 -4
- package/src/source-verification.mjs +1 -1
- package/src/uninstall.mjs +1 -0
- package/src/workspace.mjs +1 -1
|
@@ -35,7 +35,7 @@ export function resolveVariantSnapshotFile({ configPath, snapshotPath }) {
|
|
|
35
35
|
throw new Error(`snapshot path must stay under ${SNAPSHOT_ROOT}`);
|
|
36
36
|
}
|
|
37
37
|
return {
|
|
38
|
-
storedPath: relative(configDir, absolutePath).
|
|
38
|
+
storedPath: relative(configDir, absolutePath).replace(/\\/g, "/"),
|
|
39
39
|
absolutePath,
|
|
40
40
|
rootPath: root
|
|
41
41
|
};
|
|
@@ -213,7 +213,7 @@ function normalizeStoredSnapshotPath(value) {
|
|
|
213
213
|
if (value.includes("\0")) {
|
|
214
214
|
throw new Error("snapshot path must not contain null bytes");
|
|
215
215
|
}
|
|
216
|
-
const normalized = value.
|
|
216
|
+
const normalized = value.replace(/\\/g, "/");
|
|
217
217
|
if (normalized.startsWith("/") || normalized.startsWith("//") || /^[A-Za-z]:\//.test(normalized)) {
|
|
218
218
|
throw new Error("snapshot path must be relative to the config directory");
|
|
219
219
|
}
|
package/src/doctor.mjs
CHANGED
|
@@ -45,7 +45,7 @@ export async function doctorProject(options = {}) {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
if (!configExists) {
|
|
48
|
-
return finalizeDoctor(base, ["run skillboard
|
|
48
|
+
return finalizeDoctor(base, ["run skillboard setup once per user/agent install if agents should use SkillBoard for skill priority"]);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
let workspace;
|
package/src/hook-plan.mjs
CHANGED
|
@@ -315,9 +315,9 @@ function normalizePath(value, root) {
|
|
|
315
315
|
|
|
316
316
|
function displayPath(path, root) {
|
|
317
317
|
if (!isAbsolute(path)) {
|
|
318
|
-
return path.
|
|
318
|
+
return path.replace(/\\/g, "/");
|
|
319
319
|
}
|
|
320
|
-
const rel = relative(root, path).
|
|
320
|
+
const rel = relative(root, path).replace(/\\/g, "/");
|
|
321
321
|
return rel.startsWith("..") ? path : rel;
|
|
322
322
|
}
|
|
323
323
|
|
package/src/lifecycle-cli.mjs
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
import { setupAgentSkillTargets, supportedAgentNames } from "./agent-skill-roots.mjs";
|
|
2
5
|
import { initProject } from "./init.mjs";
|
|
3
6
|
import { uninstallProject } from "./uninstall.mjs";
|
|
4
7
|
|
|
8
|
+
const AGENT_INTEGRATION_START = "<!-- BEGIN SKILLBOARD AGENT INTEGRATION -->";
|
|
9
|
+
const AGENT_INTEGRATION_END = "<!-- END SKILLBOARD AGENT INTEGRATION -->";
|
|
10
|
+
|
|
5
11
|
export async function runInitCommand(options, stdout, runtime = defaultRuntime()) {
|
|
6
12
|
const root = resolve(options.get("dir") ?? ".");
|
|
7
13
|
const result = await initProject({
|
|
@@ -36,6 +42,39 @@ export async function runInitCommand(options, stdout, runtime = defaultRuntime()
|
|
|
36
42
|
return 0;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
export async function runSetupCommand(options, stdout, runtime = defaultRuntime()) {
|
|
46
|
+
if (options.get("dir") !== undefined) {
|
|
47
|
+
throw new Error("skillboard setup is agent-layer setup and does not accept --dir");
|
|
48
|
+
}
|
|
49
|
+
const targets = await agentSetupTargets(options, runtime);
|
|
50
|
+
if (targets.length === 0) {
|
|
51
|
+
stdout.write("No supported agent user skill roots were detected.\n");
|
|
52
|
+
stdout.write(`Create a supported agent home or pass --agent ${supportedAgentNames().join(",")} to choose targets.\n`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
if (options.get("yes") !== "true") {
|
|
56
|
+
writeSetupConfirmation(stdout, targets, commandPrefix(runtime));
|
|
57
|
+
if (!canPrompt(runtime)) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
const confirmed = await promptForSetup(runtime);
|
|
61
|
+
if (!confirmed) {
|
|
62
|
+
stdout.write("Skipped SkillBoard setup.\n");
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const result = await installAgentIntegration(targets);
|
|
67
|
+
stdout.write("SkillBoard agent integration installed.\n");
|
|
68
|
+
writeList(stdout, "Created", result.created);
|
|
69
|
+
writeList(stdout, "Updated", result.updated);
|
|
70
|
+
writeList(stdout, "Unchanged", result.unchanged);
|
|
71
|
+
writeList(stdout, "Preserved", result.preserved);
|
|
72
|
+
stdout.write("Next:\n");
|
|
73
|
+
stdout.write("- Restart or refresh agents that cache user skills.\n");
|
|
74
|
+
stdout.write('- Ask the agent in a workspace: "Which skill should you use for this?"\n');
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
39
78
|
export async function runUninstallCommand(options, stdout) {
|
|
40
79
|
const removeConfig = options.get("remove-config") === "true";
|
|
41
80
|
const resetConfig = options.get("reset-config") === "true";
|
|
@@ -85,15 +124,15 @@ function writeCountedList(stdout, label, values) {
|
|
|
85
124
|
}
|
|
86
125
|
|
|
87
126
|
function writeSafetyDefault(stdout, safety) {
|
|
88
|
-
stdout.write("
|
|
89
|
-
stdout.write("- Installed
|
|
127
|
+
stdout.write("Skill selection default:\n");
|
|
128
|
+
stdout.write("- Installed user skills are usable unless runtime, user, or local instructions disable them.\n");
|
|
90
129
|
if (safety.automatic === 0) {
|
|
91
130
|
stdout.write("- No automatic model invocation was enabled.\n");
|
|
92
131
|
} else {
|
|
93
132
|
stdout.write(`- ${safety.automatic} automatic skills enabled by existing policy.\n`);
|
|
94
133
|
}
|
|
95
|
-
stdout.write("- Imported local skills are
|
|
96
|
-
stdout.write("- Runtime/plugin/system skills require source review before
|
|
134
|
+
stdout.write("- Imported local skills are available on request in generated local policy.\n");
|
|
135
|
+
stdout.write("- Runtime/plugin/system skills require source review before automatic invocation.\n");
|
|
97
136
|
stdout.write(`- ${safety.automatic} automatic skills enabled\n`);
|
|
98
137
|
stdout.write(`- ${safety.manualOnly} manual-only skills available\n`);
|
|
99
138
|
stdout.write(`- ${safety.routerOnly} router-only skills available\n`);
|
|
@@ -120,6 +159,40 @@ function writeNextCommands(stdout, next) {
|
|
|
120
159
|
stdout.write(`- ${next.command} brief --workflow ${shellQuote(workflow)}${dir} --verbose\n`);
|
|
121
160
|
}
|
|
122
161
|
|
|
162
|
+
function writeSetupConfirmation(stdout, targets, command) {
|
|
163
|
+
stdout.write("SkillBoard setup installs agent-layer integration, not project files.\n");
|
|
164
|
+
stdout.write("It writes a SkillBoard guidance skill into detected user agent skill roots so agents can resolve skill priority when choices overlap.\n");
|
|
165
|
+
stdout.write("Targets:\n");
|
|
166
|
+
for (const target of targets) {
|
|
167
|
+
stdout.write(`- ${target.agent}: ${target.skillPath}\n`);
|
|
168
|
+
}
|
|
169
|
+
stdout.write("Run with --yes to install agent-layer integration:\n");
|
|
170
|
+
stdout.write(`- ${command} setup --agent ${targets.map((target) => target.agent).join(",")} --yes\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function canPrompt(runtime) {
|
|
174
|
+
return runtime.stdin?.isTTY === true && runtime.stdout?.isTTY === true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function promptForSetup(runtime) {
|
|
178
|
+
const rl = createInterface({
|
|
179
|
+
input: runtime.stdin,
|
|
180
|
+
output: runtime.stdout
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
const answer = await question(rl, "Install SkillBoard agent integration now? [y/N] ");
|
|
184
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
185
|
+
} finally {
|
|
186
|
+
rl.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function question(rl, prompt) {
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
rl.question(prompt, resolve);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
123
196
|
function formatList(values) {
|
|
124
197
|
return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
|
|
125
198
|
}
|
|
@@ -143,9 +216,96 @@ function defaultRuntime() {
|
|
|
143
216
|
};
|
|
144
217
|
}
|
|
145
218
|
|
|
219
|
+
async function agentSetupTargets(options, runtime) {
|
|
220
|
+
const env = runtime.env ?? process.env;
|
|
221
|
+
const home = env.HOME ?? env.USERPROFILE;
|
|
222
|
+
const requested = readCsv(options.get("agent"));
|
|
223
|
+
const supported = new Set(supportedAgentNames());
|
|
224
|
+
const names = requested.length === 0 ? supportedAgentNames() : requested;
|
|
225
|
+
const targets = [];
|
|
226
|
+
for (const name of names) {
|
|
227
|
+
if (!supported.has(name)) {
|
|
228
|
+
throw new Error(`Unsupported setup agent: ${name}`);
|
|
229
|
+
}
|
|
230
|
+
targets.push(...await setupAgentSkillTargets(name, home, env, { includeFallback: requested.length > 0 }));
|
|
231
|
+
}
|
|
232
|
+
return targets;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function installAgentIntegration(targets) {
|
|
236
|
+
const created = [];
|
|
237
|
+
const updated = [];
|
|
238
|
+
const unchanged = [];
|
|
239
|
+
const preserved = [];
|
|
240
|
+
const content = agentIntegrationSkill();
|
|
241
|
+
for (const target of targets) {
|
|
242
|
+
const existing = await readFile(target.skillPath, "utf8").catch((error) => {
|
|
243
|
+
if (error?.code === "ENOENT") {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
});
|
|
248
|
+
if (existing === content) {
|
|
249
|
+
unchanged.push(`${target.agent}:${target.skillPath}`);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (existing !== null && !existing.includes(AGENT_INTEGRATION_START)) {
|
|
253
|
+
preserved.push(`${target.agent}:${target.skillPath}`);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
await mkdir(dirname(target.skillPath), { recursive: true });
|
|
257
|
+
await writeFile(target.skillPath, content, "utf8");
|
|
258
|
+
(existing === null ? created : updated).push(`${target.agent}:${target.skillPath}`);
|
|
259
|
+
}
|
|
260
|
+
return { created, updated, unchanged, preserved };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function agentIntegrationSkill() {
|
|
264
|
+
return `---
|
|
265
|
+
name: skillboard
|
|
266
|
+
description: Use SkillBoard when several installed skills could apply, a skill choice is ambiguous, or the user asks which skill should be used, preferred, avoided, reviewed, or prioritized for the current workflow.
|
|
267
|
+
---
|
|
268
|
+
${AGENT_INTEGRATION_START}
|
|
269
|
+
# SkillBoard Agent Integration
|
|
270
|
+
|
|
271
|
+
Use this skill to let SkillBoard guide skill selection above individual projects.
|
|
272
|
+
|
|
273
|
+
## Layering
|
|
274
|
+
|
|
275
|
+
- SkillBoard is the user-level control plane for skill priority, overlap resolution, and workflow-aware routing.
|
|
276
|
+
- Project management belongs to the agent or workspace layer. Do not initialize, attach, rewrite, or manage a project just because SkillBoard is installed.
|
|
277
|
+
- Package install and \`skillboard setup\` install user-agent guidance only.
|
|
278
|
+
|
|
279
|
+
## Default Behavior
|
|
280
|
+
|
|
281
|
+
- Installed user skills are usable by default unless the runtime, user, or local instructions disable them.
|
|
282
|
+
- Do not ask for permission merely because you selected a skill.
|
|
283
|
+
- When you use a skill, disclose it briefly at the start and completion.
|
|
284
|
+
- Use SkillBoard when more than one skill matches, when skills overlap, or when workflow priority should choose between plausible skills.
|
|
285
|
+
|
|
286
|
+
## Cross-Agent Skill Reuse
|
|
287
|
+
|
|
288
|
+
- When the user wants to use a skill from another agent, run \`skillboard import-skill --from <source-agent> --to <this-agent> --skill <skill> --json\`.
|
|
289
|
+
- If SkillBoard reports the skill is compatible, install it with \`--yes\` and use the copied target-agent skill.
|
|
290
|
+
- If SkillBoard reports \`needs-adaptation\`, explain the compatibility reasons and ask before changing the skill body for this agent.
|
|
291
|
+
- If the user approves, inspect the source skill, write an adapted \`SKILL.md\` for this agent, then install it with \`skillboard import-skill --from <source-agent> --to <this-agent> --skill <skill> --adapted-file <adapted-skill.md> --yes --json\`.
|
|
292
|
+
- This is still user-agent setup. Do not create or mutate project policy files for cross-agent skill reuse.
|
|
293
|
+
|
|
294
|
+
## Ambiguity Resolution
|
|
295
|
+
|
|
296
|
+
1. Identify the candidate skills that match the request.
|
|
297
|
+
2. Prefer the skill whose description, workflow guidance, and local instructions most directly match the user's task.
|
|
298
|
+
3. If a project or agent has explicit SkillBoard policy, use \`skillboard brief --intent <request> --json\` or \`skillboard route <intent> --workflow <name> --json\` to break ties.
|
|
299
|
+
4. If the best choice is still ambiguous or the choice would change persistent policy, ask the user which priority to remember.
|
|
300
|
+
5. Continue with the selected skill; do not stop only because other candidate skills exist.
|
|
301
|
+
|
|
302
|
+
${AGENT_INTEGRATION_END}
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
|
|
146
306
|
function commandPrefix(runtime) {
|
|
147
307
|
const entrypoint = runtime.entrypointPath ?? "";
|
|
148
|
-
const normalized = entrypoint.
|
|
308
|
+
const normalized = entrypoint.replace(/\\/g, "/");
|
|
149
309
|
if (normalized.includes("/_npx/")) {
|
|
150
310
|
const packageSpec = runtime.packageSpec ?? "agent-skillboard";
|
|
151
311
|
return `npx --yes --package ${shellQuote(packageSpec)} skillboard`;
|
|
@@ -160,7 +320,7 @@ function isSourceTreeEntrypoint(entrypoint) {
|
|
|
160
320
|
if (entrypoint === "") {
|
|
161
321
|
return false;
|
|
162
322
|
}
|
|
163
|
-
const normalized = entrypoint.
|
|
323
|
+
const normalized = entrypoint.replace(/\\/g, "/");
|
|
164
324
|
return (normalized === "bin/skillboard.mjs" || normalized.endsWith("/bin/skillboard.mjs"))
|
|
165
325
|
&& !normalized.includes("/node_modules/")
|
|
166
326
|
&& !normalized.includes("/_npx/")
|
|
@@ -169,7 +329,7 @@ function isSourceTreeEntrypoint(entrypoint) {
|
|
|
169
329
|
|
|
170
330
|
function sourceTreeEntrypoint(entrypoint, cwd) {
|
|
171
331
|
const absoluteEntrypoint = isAbsolute(entrypoint) ? entrypoint : resolve(cwd, entrypoint);
|
|
172
|
-
const relativeEntrypoint = relative(cwd, absoluteEntrypoint).
|
|
332
|
+
const relativeEntrypoint = relative(cwd, absoluteEntrypoint).replace(/\\/g, "/");
|
|
173
333
|
if (!relativeEntrypoint.startsWith("../") && relativeEntrypoint !== ".." && !isAbsolute(relativeEntrypoint)) {
|
|
174
334
|
return shellQuote(relativeEntrypoint);
|
|
175
335
|
}
|
|
@@ -180,5 +340,5 @@ function shellQuote(value) {
|
|
|
180
340
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
|
|
181
341
|
return value;
|
|
182
342
|
}
|
|
183
|
-
return `'${value.
|
|
343
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
184
344
|
}
|
|
@@ -31,7 +31,7 @@ Your responsibility is to answer skill availability questions from SkillBoard, t
|
|
|
31
31
|
|
|
32
32
|
## Availability
|
|
33
33
|
|
|
34
|
-
- Use SkillBoard as the source of truth; installed \`SKILL.md\` files are not
|
|
34
|
+
- Use SkillBoard as the source of truth for project-local policy and workflow priority; installed \`SKILL.md\` files are candidates, not enough to resolve overlap by themselves.
|
|
35
35
|
- Read the current brief before answering: \`skillboard brief --json --config skillboard.config.yaml --skills skills\`. If the workflow is known, include \`--workflow <name>\`; add \`--include-actions\` when the user wants you to mediate a change.
|
|
36
36
|
- When the user asks which skill fits a task, read \`skillboard brief --intent <request> --json --config skillboard.config.yaml --skills skills\`. Include \`--workflow <name>\` when known. Read \`assistant_guidance.route\`; use \`recommended_skill\`, \`fallback_skills\`, \`route_candidates\`, \`post_use_policy_suggestion\`, and \`guard_command\` instead of guessing from raw skill text. Inspect \`route_candidates\` when several skills match so denied candidates and selected fallbacks are clear. If \`post_use_policy_suggestion\` is present, work first with the allowed routed skill, then ask after completion whether to remember the suggested policy. If no skill matches, ask a clarifying question before choosing a skill.
|
|
37
37
|
- Treat the brief sections headed "What your AI can use now", "Needs your decision", and "Blocked for safety" as the availability summary; do not infer availability from \`SKILL.md\` bodies.
|
package/src/skill-paths.mjs
CHANGED
|
@@ -5,7 +5,7 @@ export function normalizeSkillPath(value, label = "skill path") {
|
|
|
5
5
|
if (value.includes("\0")) {
|
|
6
6
|
throw new Error(`${label} must not contain null bytes`);
|
|
7
7
|
}
|
|
8
|
-
const normalized = value.
|
|
8
|
+
const normalized = value.replace(/\\/g, "/");
|
|
9
9
|
if (normalized.startsWith("/") || normalized.startsWith("//") || /^[A-Za-z]:\//.test(normalized)) {
|
|
10
10
|
throw new Error(`${label} must be relative to the skills root`);
|
|
11
11
|
}
|
package/src/source-cache.mjs
CHANGED
|
@@ -164,7 +164,7 @@ function resolveUnderRoot(root, path) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
function relativePath(root, path) {
|
|
167
|
-
const rel = relative(root, path).
|
|
167
|
+
const rel = relative(root, path).replace(/\\/g, "/");
|
|
168
168
|
return rel.startsWith("..") ? path : rel;
|
|
169
169
|
}
|
|
170
170
|
|
package/src/source-profiles.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export async function importSource(options) {
|
|
|
14
14
|
const matchedFiles = files
|
|
15
15
|
.map((file) => ({
|
|
16
16
|
file,
|
|
17
|
-
relativeFile: relative(sourceRoot, file).
|
|
17
|
+
relativeFile: relative(sourceRoot, file).replace(/\\/g, "/")
|
|
18
18
|
}))
|
|
19
19
|
.filter((entry) => matchesAnyProfilePattern(entry.relativeFile, profile.skillPaths))
|
|
20
20
|
.sort((left, right) => left.relativeFile.localeCompare(right.relativeFile));
|
|
@@ -238,9 +238,9 @@ function matchingPathRule(file, profile) {
|
|
|
238
238
|
function patternToRegExp(pattern) {
|
|
239
239
|
const escaped = pattern
|
|
240
240
|
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
241
|
-
.
|
|
242
|
-
.
|
|
243
|
-
.
|
|
241
|
+
.split("**").join("\0")
|
|
242
|
+
.split("*").join("[^/]*")
|
|
243
|
+
.split("\0").join(".*");
|
|
244
244
|
return new RegExp(`^${escaped}$`);
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -165,7 +165,7 @@ export async function sourceDigest(path) {
|
|
|
165
165
|
|
|
166
166
|
async function addPathDigest(hash, root, path) {
|
|
167
167
|
const stats = await lstat(path);
|
|
168
|
-
const rel = relative(root, path).
|
|
168
|
+
const rel = relative(root, path).replace(/\\/g, "/") || ".";
|
|
169
169
|
if (stats.isSymbolicLink()) {
|
|
170
170
|
hash.update(`symlink\0${rel}\0${await readlink(path)}\n`);
|
|
171
171
|
return;
|
package/src/uninstall.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// allow: SIZE_OK - uninstall lifecycle split is deferred from the 0.2.7 release gate.
|
|
1
2
|
import { access, lstat, readFile, readdir, rm, rmdir, writeFile } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { BRIDGE_END, BRIDGE_START, defaultConfig, hookReadme, profileReadme } from "./lifecycle-content.mjs";
|
package/src/workspace.mjs
CHANGED
|
@@ -51,7 +51,7 @@ async function discoverInstalledSkills(skillsRoot, declaredSkills, options = {})
|
|
|
51
51
|
const skillFiles = await findSkillFiles(skillsRoot);
|
|
52
52
|
for (const file of skillFiles) {
|
|
53
53
|
const frontmatter = parseSkillFrontmatter(await readFile(file, "utf8"));
|
|
54
|
-
const path = relative(skillsRoot, file).
|
|
54
|
+
const path = relative(skillsRoot, file).replace(/\\/g, "/").replace(/\/SKILL\.md$/, "");
|
|
55
55
|
const declared = declaredSkills.find((skill) => skill.path === path);
|
|
56
56
|
appendInstalledSkill(installed, installedKeys, {
|
|
57
57
|
id: declared?.id ?? frontmatter.name ?? path,
|