agent-skillboard 0.1.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/CONTRIBUTING.md +83 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/bin/skillboard.mjs +4 -0
- package/docs/adapters.md +127 -0
- package/docs/capabilities.md +107 -0
- package/docs/install.md +346 -0
- package/docs/plans/20260625-080025-skillboard-mvp-review.md +189 -0
- package/docs/plans/README.md +20 -0
- package/docs/policy-model.md +251 -0
- package/docs/positioning.md +94 -0
- package/docs/profiles.md +166 -0
- package/docs/rollout-runbook.md +60 -0
- package/docs/user-flow.md +231 -0
- package/docs/versioning.md +201 -0
- package/examples/multi-source-skills/anthropic/docx/SKILL.md +8 -0
- package/examples/multi-source-skills/anthropic/skill-creator/SKILL.md +8 -0
- package/examples/multi-source-skills/matt/grill-me/SKILL.md +8 -0
- package/examples/multi-source-skills/matt/tdd/SKILL.md +8 -0
- package/examples/multi-source-skills/omo/review-work/SKILL.md +8 -0
- package/examples/multi-source-skills/omo/ulw-plan/SKILL.md +8 -0
- package/examples/multi-source-skills/private/tdd-work-continuity/SKILL.md +8 -0
- package/examples/multi-source-skills/private/workflow-router/SKILL.md +8 -0
- package/examples/multi-source-skills/wshobson/python-testing/SKILL.md +8 -0
- package/examples/multi-source-skills/wshobson/security-review/SKILL.md +8 -0
- package/examples/multi-source.config.yaml +271 -0
- package/examples/skillboard.config.yaml +194 -0
- package/examples/skills/grill-me/SKILL.md +9 -0
- package/examples/skills/grill-with-docs/SKILL.md +9 -0
- package/examples/skills/requirement-intake/SKILL.md +9 -0
- package/examples/skills/tdd/SKILL.md +8 -0
- package/package.json +58 -0
- package/profiles/anthropics-skills.yaml +17 -0
- package/profiles/mattpocock-skills.yaml +26 -0
- package/profiles/oh-my-openagent.yaml +32 -0
- package/profiles/voltagent-awesome-agent-skills.yaml +18 -0
- package/profiles/wshobson-agents.yaml +19 -0
- package/src/advisor/action-core.mjs +74 -0
- package/src/advisor/actions.mjs +256 -0
- package/src/advisor/application-commands.mjs +59 -0
- package/src/advisor/apply-action.mjs +183 -0
- package/src/advisor/schema.mjs +112 -0
- package/src/advisor/setup-actions.mjs +42 -0
- package/src/advisor/skills.mjs +191 -0
- package/src/advisor/sort.mjs +15 -0
- package/src/advisor/sources.mjs +160 -0
- package/src/advisor/trust-policy.mjs +65 -0
- package/src/advisor/workflow.mjs +41 -0
- package/src/advisor.mjs +134 -0
- package/src/agent-inventory-platforms.mjs +100 -0
- package/src/agent-inventory.mjs +804 -0
- package/src/brief-cli.mjs +40 -0
- package/src/brief-renderer.mjs +362 -0
- package/src/change-plan.mjs +169 -0
- package/src/cli.mjs +1171 -0
- package/src/config-helpers.mjs +72 -0
- package/src/control/can-use-guard.mjs +91 -0
- package/src/control/config-write.mjs +163 -0
- package/src/control/skill-crud.mjs +394 -0
- package/src/control/source-trust.mjs +161 -0
- package/src/control/workflow-crud.mjs +104 -0
- package/src/control.mjs +197 -0
- package/src/doctor.mjs +299 -0
- package/src/domain/constants.mjs +47 -0
- package/src/domain/indexes.mjs +29 -0
- package/src/domain/rules/capabilities.mjs +33 -0
- package/src/domain/rules/harnesses.mjs +16 -0
- package/src/domain/rules/install-units.mjs +95 -0
- package/src/domain/rules/skills.mjs +105 -0
- package/src/domain/rules/workflows.mjs +105 -0
- package/src/domain/skill-state-matrix.mjs +79 -0
- package/src/domain/source-classes.mjs +99 -0
- package/src/hook-plan.mjs +152 -0
- package/src/impact.mjs +41 -0
- package/src/index.mjs +82 -0
- package/src/init.mjs +136 -0
- package/src/install-output-detector.mjs +337 -0
- package/src/install-units.mjs +62 -0
- package/src/inventory-refresh.mjs +45 -0
- package/src/lifecycle-cli.mjs +166 -0
- package/src/lifecycle-content.mjs +87 -0
- package/src/policy.mjs +42 -0
- package/src/reconcile.mjs +111 -0
- package/src/report.mjs +151 -0
- package/src/review.mjs +88 -0
- package/src/rollout.mjs +453 -0
- package/src/skill-paths.mjs +17 -0
- package/src/source-cache.mjs +178 -0
- package/src/source-profile-loader.mjs +160 -0
- package/src/source-profiles.mjs +299 -0
- package/src/source-verification.mjs +252 -0
- package/src/uninstall.mjs +258 -0
- package/src/workspace.mjs +219 -0
- package/tsconfig.lsp.json +15 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import {
|
|
5
|
+
readBoolean,
|
|
6
|
+
readOptionalString,
|
|
7
|
+
readString,
|
|
8
|
+
readStringList,
|
|
9
|
+
requireRecord
|
|
10
|
+
} from "./config-helpers.mjs";
|
|
11
|
+
import {
|
|
12
|
+
EXPOSURE_VALUES,
|
|
13
|
+
HARNESS_STATUS_VALUES,
|
|
14
|
+
INVOCATION_VALUES,
|
|
15
|
+
STATUS_VALUES
|
|
16
|
+
} from "./domain/constants.mjs";
|
|
17
|
+
import { parseInstallUnits } from "./install-units.mjs";
|
|
18
|
+
import { normalizeSkillPath } from "./skill-paths.mjs";
|
|
19
|
+
|
|
20
|
+
export async function loadWorkspace(options) {
|
|
21
|
+
const configText = await readFile(options.configPath, "utf8");
|
|
22
|
+
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
|
+
return {
|
|
27
|
+
version,
|
|
28
|
+
defaults: parseDefaults(config.defaults),
|
|
29
|
+
installedSkills: await discoverInstalledSkills(options.skillsRoot, skills),
|
|
30
|
+
skills,
|
|
31
|
+
capabilities: parseCapabilities(config.capabilities),
|
|
32
|
+
harnesses: parseHarnesses(config.harnesses),
|
|
33
|
+
installUnits: parseInstallUnits(config.install_units),
|
|
34
|
+
workflows: parseWorkflows(config.workflows)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function discoverInstalledSkills(skillsRoot, declaredSkills) {
|
|
39
|
+
if (skillsRoot === undefined) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const skillFiles = await findSkillFiles(skillsRoot);
|
|
43
|
+
const installed = [];
|
|
44
|
+
for (const file of skillFiles) {
|
|
45
|
+
const frontmatter = parseSkillFrontmatter(await readFile(file, "utf8"));
|
|
46
|
+
const path = relative(skillsRoot, file).replaceAll("\\", "/").replace(/\/SKILL\.md$/, "");
|
|
47
|
+
const declared = declaredSkills.find((skill) => skill.path === path);
|
|
48
|
+
installed.push({
|
|
49
|
+
id: declared?.id ?? frontmatter.name ?? path,
|
|
50
|
+
name: frontmatter.name,
|
|
51
|
+
description: frontmatter.description,
|
|
52
|
+
path
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return installed.sort((left, right) => left.path.localeCompare(right.path));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function findSkillFiles(root, seen = new Set()) {
|
|
59
|
+
const files = [];
|
|
60
|
+
const resolvedRoot = await realpath(root).catch(() => root);
|
|
61
|
+
if (seen.has(resolvedRoot)) {
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
seen.add(resolvedRoot);
|
|
65
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const path = join(root, entry.name);
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
files.push(...(await findSkillFiles(path, seen)));
|
|
70
|
+
} else if (entry.isSymbolicLink()) {
|
|
71
|
+
const target = await stat(path).catch(() => undefined);
|
|
72
|
+
if (target?.isDirectory()) {
|
|
73
|
+
files.push(...(await findSkillFiles(path, seen)));
|
|
74
|
+
} else if (target?.isFile() && entry.name === "SKILL.md") {
|
|
75
|
+
files.push(path);
|
|
76
|
+
}
|
|
77
|
+
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
78
|
+
files.push(path);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseSkillFrontmatter(text) {
|
|
85
|
+
const match = /^---[ \t]*\r?\n(?<body>[\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
86
|
+
if (match?.groups === undefined) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"SKILL.md is missing YAML frontmatter. " +
|
|
89
|
+
"Add --- delimiters at the top of the file with at least 'name' and 'description'.\n" +
|
|
90
|
+
"Example:\n" +
|
|
91
|
+
"---\n" +
|
|
92
|
+
"name: my-skill\n" +
|
|
93
|
+
"description: What this skill does in one sentence.\n" +
|
|
94
|
+
"---\n" +
|
|
95
|
+
"See docs/user-flow.md for the first-time skill guide."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const raw = requireRecord(YAML.parse(match.groups.body), "SKILL.md frontmatter");
|
|
99
|
+
return {
|
|
100
|
+
name: readString(raw, "name", ""),
|
|
101
|
+
description: readString(raw, "description", "")
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseVersion(value) {
|
|
106
|
+
if (value === undefined) {
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
if (value !== 1) {
|
|
110
|
+
throw new Error(`Unsupported config version: ${value}`);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseDefaults(value) {
|
|
116
|
+
const raw = requireRecord(value ?? {}, "defaults");
|
|
117
|
+
return {
|
|
118
|
+
invocationPolicy: readString(raw, "invocation_policy", "deny-by-default"),
|
|
119
|
+
allowModelInvocation: readBoolean(raw, "allow_model_invocation", false),
|
|
120
|
+
requireExplicitWorkflow: readBoolean(raw, "require_explicit_workflow", true)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseSkills(value) {
|
|
125
|
+
const raw = requireRecord(value ?? {}, "skills");
|
|
126
|
+
return Object.entries(raw).map(([id, entry]) => {
|
|
127
|
+
const skill = requireRecord(entry, `skills.${id}`);
|
|
128
|
+
const status = readString(skill, "status", "vendor");
|
|
129
|
+
const invocation = readString(skill, "invocation", "manual-only");
|
|
130
|
+
const exposure = readString(skill, "exposure", "exported");
|
|
131
|
+
if (!STATUS_VALUES.has(status)) {
|
|
132
|
+
throw new Error(`Unsupported status for ${id}: ${status}`);
|
|
133
|
+
}
|
|
134
|
+
if (!INVOCATION_VALUES.has(invocation)) {
|
|
135
|
+
throw new Error(`Unsupported invocation for ${id}: ${invocation}`);
|
|
136
|
+
}
|
|
137
|
+
if (!EXPOSURE_VALUES.has(exposure)) {
|
|
138
|
+
throw new Error(`Unsupported exposure for ${id}: ${exposure}`);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
path: normalizeSkillPath(readString(skill, "path", id), `skills.${id}.path`),
|
|
143
|
+
status,
|
|
144
|
+
invocation,
|
|
145
|
+
exposure,
|
|
146
|
+
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
|
+
|
|
155
|
+
function parseCapabilities(value) {
|
|
156
|
+
const raw = requireRecord(value ?? {}, "capabilities");
|
|
157
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
158
|
+
const capability = requireRecord(entry, `capabilities.${name}`);
|
|
159
|
+
const defaultPolicy = readString(capability, "default_policy", "manual-only");
|
|
160
|
+
if (!INVOCATION_VALUES.has(defaultPolicy)) {
|
|
161
|
+
throw new Error(`Unsupported capability default_policy for ${name}: ${defaultPolicy}`);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
name,
|
|
165
|
+
canonical: readString(capability, "canonical", ""),
|
|
166
|
+
alternatives: readStringList(capability, "alternatives"),
|
|
167
|
+
defaultPolicy
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseHarnesses(value) {
|
|
173
|
+
const raw = requireRecord(value ?? {}, "harnesses");
|
|
174
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
175
|
+
const harness = requireRecord(entry, `harnesses.${name}`);
|
|
176
|
+
const status = readString(harness, "status", "available");
|
|
177
|
+
if (!HARNESS_STATUS_VALUES.has(status)) {
|
|
178
|
+
throw new Error(`Unsupported harness status for ${name}: ${status}`);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name,
|
|
182
|
+
status,
|
|
183
|
+
workflows: readStringList(harness, "workflows"),
|
|
184
|
+
commands: readStringList(harness, "commands")
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function parseWorkflows(value) {
|
|
190
|
+
const raw = requireRecord(value ?? {}, "workflows");
|
|
191
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
192
|
+
const workflow = requireRecord(entry, `workflows.${name}`);
|
|
193
|
+
return {
|
|
194
|
+
name,
|
|
195
|
+
harness: readString(workflow, "harness", "unspecified"),
|
|
196
|
+
activeSkills: readStringList(workflow, "active_skills"),
|
|
197
|
+
blockedSkills: readStringList(workflow, "blocked_skills"),
|
|
198
|
+
requiredOutputs: readStringList(workflow, "required_outputs"),
|
|
199
|
+
requiredCapabilities: parseRequiredCapabilities(workflow.required_capabilities, `workflows.${name}.required_capabilities`)
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseRequiredCapabilities(value, label) {
|
|
205
|
+
const raw = requireRecord(value ?? {}, label);
|
|
206
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
207
|
+
const capability = requireRecord(entry, `${label}.${name}`);
|
|
208
|
+
const policy = readString(capability, "policy", "manual-only");
|
|
209
|
+
if (!INVOCATION_VALUES.has(policy)) {
|
|
210
|
+
throw new Error(`Unsupported capability policy for ${label}.${name}: ${policy}`);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
name,
|
|
214
|
+
preferred: readString(capability, "preferred", ""),
|
|
215
|
+
fallback: readStringList(capability, "fallback"),
|
|
216
|
+
policy
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
4
|
+
"checkJs": true,
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"module": "NodeNext",
|
|
7
|
+
"moduleResolution": "NodeNext",
|
|
8
|
+
"target": "ES2022",
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": false,
|
|
12
|
+
"noImplicitAny": false
|
|
13
|
+
},
|
|
14
|
+
"include": ["bin/**/*.mjs", "src/**/*.mjs", "test/**/*.mjs"]
|
|
15
|
+
}
|