agent-skillboard 0.1.2 → 0.2.1
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 +154 -631
- package/docs/adapters.md +96 -96
- package/docs/ai-skill-routing-goal.md +112 -0
- package/docs/capabilities.md +6 -0
- package/docs/install.md +155 -105
- package/docs/plans/skillboard-variant-lifecycle-handoff.md +56 -0
- package/docs/policy-model.md +266 -214
- package/docs/positioning.md +94 -94
- package/docs/reference.md +349 -0
- package/docs/routing.md +85 -0
- package/docs/user-flow.md +75 -16
- package/docs/value-proof.md +190 -0
- package/docs/variant-lifecycle.md +86 -0
- package/docs/versioning.md +149 -138
- package/examples/multi-source-skills/anthropic/docx/SKILL.md +8 -8
- package/examples/multi-source-skills/anthropic/skill-creator/SKILL.md +8 -8
- package/examples/multi-source-skills/matt/grill-me/SKILL.md +8 -8
- package/examples/multi-source-skills/matt/tdd/SKILL.md +8 -8
- package/examples/multi-source-skills/omo/review-work/SKILL.md +8 -8
- package/examples/multi-source-skills/omo/ulw-plan/SKILL.md +8 -8
- package/examples/multi-source-skills/private/tdd-work-continuity/SKILL.md +8 -8
- package/examples/multi-source-skills/private/workflow-router/SKILL.md +8 -8
- package/examples/multi-source-skills/wshobson/python-testing/SKILL.md +8 -8
- package/examples/multi-source-skills/wshobson/security-review/SKILL.md +8 -8
- package/examples/skills/grill-me/SKILL.md +9 -9
- package/examples/skills/grill-with-docs/SKILL.md +9 -9
- package/examples/skills/requirement-intake/SKILL.md +9 -9
- package/examples/skills/tdd/SKILL.md +8 -8
- package/package.json +7 -3
- package/src/advisor/guidance.mjs +232 -0
- package/src/advisor/schema.mjs +2 -0
- package/src/advisor/skills.mjs +2 -0
- package/src/advisor.mjs +36 -7
- package/src/brief-cli.mjs +6 -5
- package/src/brief-renderer.mjs +225 -8
- package/src/cli.mjs +574 -27
- package/src/config-helpers.mjs +34 -18
- package/src/conflicts.mjs +70 -0
- package/src/control/can-use-guard.mjs +8 -3
- package/src/control/skill-crud.mjs +142 -0
- package/src/control/skill-variants.mjs +221 -0
- package/src/control/source-trust.mjs +1 -0
- package/src/control/variant-files.mjs +265 -0
- package/src/control/variant-lifecycle-config.mjs +156 -0
- package/src/control/variant-reset.mjs +171 -0
- package/src/control/variant-status.mjs +75 -0
- package/src/control.mjs +13 -1
- package/src/domain/rules/skills.mjs +60 -0
- package/src/domain/rules/workflows.mjs +13 -0
- package/src/impact.mjs +21 -12
- package/src/index.mjs +13 -1
- package/src/lifecycle-cli.mjs +18 -7
- package/src/lifecycle-content.mjs +29 -22
- package/src/route.mjs +537 -0
- package/src/source-verification.mjs +7 -3
- package/src/workspace.mjs +141 -43
- package/tsconfig.lsp.json +1 -1
package/src/workspace.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
4
|
import YAML from "yaml";
|
|
4
5
|
import {
|
|
5
6
|
readBoolean,
|
|
7
|
+
readOptionalRecord,
|
|
6
8
|
readOptionalString,
|
|
9
|
+
readRequiredString,
|
|
7
10
|
readString,
|
|
8
11
|
readStringList,
|
|
9
12
|
requireRecord
|
|
@@ -20,40 +23,106 @@ import { normalizeSkillPath } from "./skill-paths.mjs";
|
|
|
20
23
|
export async function loadWorkspace(options) {
|
|
21
24
|
const configText = await readFile(options.configPath, "utf8");
|
|
22
25
|
const parsed = YAML.parse(configText);
|
|
23
|
-
const config = requireRecord(parsed, "config root");
|
|
24
|
-
const version = parseVersion(config.version);
|
|
25
|
-
const skills = parseSkills(config.skills);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
skills,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
path
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
26
|
+
const config = requireRecord(parsed, "config root");
|
|
27
|
+
const version = parseVersion(config.version);
|
|
28
|
+
const skills = parseSkills(config.skills);
|
|
29
|
+
const installUnits = parseInstallUnits(config.install_units);
|
|
30
|
+
return {
|
|
31
|
+
version,
|
|
32
|
+
defaults: parseDefaults(config.defaults),
|
|
33
|
+
installedSkills: await discoverInstalledSkills(options.skillsRoot, skills, {
|
|
34
|
+
configPath: options.configPath,
|
|
35
|
+
env: options.env ?? process.env,
|
|
36
|
+
home: options.home,
|
|
37
|
+
installUnits
|
|
38
|
+
}),
|
|
39
|
+
skills,
|
|
40
|
+
capabilities: parseCapabilities(config.capabilities),
|
|
41
|
+
harnesses: parseHarnesses(config.harnesses),
|
|
42
|
+
installUnits,
|
|
43
|
+
workflows: parseWorkflows(config.workflows)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function discoverInstalledSkills(skillsRoot, declaredSkills, options = {}) {
|
|
48
|
+
const installed = [];
|
|
49
|
+
const installedKeys = new Set();
|
|
50
|
+
if (skillsRoot !== undefined) {
|
|
51
|
+
const skillFiles = await findSkillFiles(skillsRoot);
|
|
52
|
+
for (const file of skillFiles) {
|
|
53
|
+
const frontmatter = parseSkillFrontmatter(await readFile(file, "utf8"));
|
|
54
|
+
const path = relative(skillsRoot, file).replaceAll("\\", "/").replace(/\/SKILL\.md$/, "");
|
|
55
|
+
const declared = declaredSkills.find((skill) => skill.path === path);
|
|
56
|
+
appendInstalledSkill(installed, installedKeys, {
|
|
57
|
+
id: declared?.id ?? frontmatter.name ?? path,
|
|
58
|
+
name: frontmatter.name,
|
|
59
|
+
description: frontmatter.description,
|
|
60
|
+
path
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await appendInstallUnitSkillMetadata(installed, installedKeys, declaredSkills, options);
|
|
65
|
+
return installed.sort((left, right) => left.path.localeCompare(right.path));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function appendInstallUnitSkillMetadata(installed, installedKeys, declaredSkills, options) {
|
|
69
|
+
const units = new Map((options.installUnits ?? []).map((unit) => [unit.id, unit]));
|
|
70
|
+
for (const skill of declaredSkills) {
|
|
71
|
+
if (installedKeys.has(skill.id) || installedKeys.has(skill.path)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const unit = units.get(skill.ownerInstallUnit);
|
|
75
|
+
const root = resolveStoredPath(unit?.cachePath, options);
|
|
76
|
+
if (root === undefined) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const frontmatter = await readOptionalSkillFrontmatter(join(root, skill.path, "SKILL.md"));
|
|
80
|
+
if (frontmatter === null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
appendInstalledSkill(installed, installedKeys, {
|
|
84
|
+
id: skill.id,
|
|
85
|
+
name: frontmatter.name,
|
|
86
|
+
description: frontmatter.description,
|
|
87
|
+
path: skill.path
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function readOptionalSkillFrontmatter(file) {
|
|
93
|
+
const text = await readFile(file, "utf8").catch(() => null);
|
|
94
|
+
if (text === null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return parseSkillFrontmatter(text);
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function appendInstalledSkill(installed, installedKeys, skill) {
|
|
105
|
+
installed.push(skill);
|
|
106
|
+
installedKeys.add(skill.id);
|
|
107
|
+
installedKeys.add(skill.path);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveStoredPath(value, options) {
|
|
111
|
+
if (value === undefined || value.trim() === "") {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const home = options.home ?? options.env?.HOME ?? options.env?.USERPROFILE ?? homedir();
|
|
115
|
+
if (value === "~") {
|
|
116
|
+
return home;
|
|
117
|
+
}
|
|
118
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
119
|
+
return join(home, value.slice(2));
|
|
120
|
+
}
|
|
121
|
+
if (isAbsolute(value)) {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
return resolve(dirname(options.configPath ?? "."), value);
|
|
125
|
+
}
|
|
57
126
|
|
|
58
127
|
async function findSkillFiles(root, seen = new Set()) {
|
|
59
128
|
const files = [];
|
|
@@ -144,14 +213,43 @@ function parseSkills(value) {
|
|
|
144
213
|
invocation,
|
|
145
214
|
exposure,
|
|
146
215
|
category: readString(skill, "category", "uncategorized"),
|
|
147
|
-
canonicalFor: readStringList(skill, "canonical_for"),
|
|
148
|
-
conflictsWith: readStringList(skill, "conflicts_with"),
|
|
149
|
-
replacedBy: readOptionalString(skill, "replaced_by"),
|
|
150
|
-
ownerInstallUnit: readOptionalString(skill, "owner_install_unit")
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
216
|
+
canonicalFor: readStringList(skill, "canonical_for"),
|
|
217
|
+
conflictsWith: readStringList(skill, "conflicts_with"),
|
|
218
|
+
replacedBy: readOptionalString(skill, "replaced_by"),
|
|
219
|
+
ownerInstallUnit: readOptionalString(skill, "owner_install_unit"),
|
|
220
|
+
variant: parseSkillVariant(skill, `skills.${id}.variant`)
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function parseSkillVariant(skill, label) {
|
|
226
|
+
const raw = readOptionalRecord(skill, "variant", label);
|
|
227
|
+
if (raw === undefined) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const approved = readOptionalRecord(raw, "approved", `${label}.approved`);
|
|
231
|
+
return {
|
|
232
|
+
of: readRequiredString(raw, "of", `${label}.of`),
|
|
233
|
+
adaptedFor: readOptionalString(raw, "adapted_for") ?? null,
|
|
234
|
+
capability: readRequiredString(raw, "capability", `${label}.capability`),
|
|
235
|
+
workflow: readRequiredString(raw, "workflow", `${label}.workflow`),
|
|
236
|
+
status: readRequiredString(raw, "status", `${label}.status`),
|
|
237
|
+
base: parseVariantCheckpoint(raw, "base", `${label}.base`),
|
|
238
|
+
...(approved === undefined ? {} : { approved: parseVariantCheckpoint(raw, "approved", `${label}.approved`) })
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parseVariantCheckpoint(raw, key, label) {
|
|
243
|
+
const checkpoint = readOptionalRecord(raw, key, label);
|
|
244
|
+
if (checkpoint === undefined) {
|
|
245
|
+
throw new Error(`${label} must be a mapping`);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
contentDigest: readRequiredString(checkpoint, "content_digest", `${label}.content_digest`),
|
|
249
|
+
snapshot: readRequiredString(checkpoint, "snapshot", `${label}.snapshot`)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
155
253
|
function parseCapabilities(value) {
|
|
156
254
|
const raw = requireRecord(value ?? {}, "capabilities");
|
|
157
255
|
return Object.entries(raw).map(([name, entry]) => {
|
package/tsconfig.lsp.json
CHANGED